Fix identified session ID generation
This commit is contained in:
1 parent
4691a344c5
commit
cb801230cc
3 files changed
+72
-2
No files matched your search
@@ -10,6 +10,7 @@ import { fetchWebsite } from '@/lib/load';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { badRequest, forbidden, json, serverError } from '@/lib/response';
|
||||
import { anyObjectParam, urlOrPathParam } from '@/lib/schema';
|
||||
import { getSessionId } from '@/lib/session';
|
||||
import { safeDecodeURI, safeDecodeURIComponent } from '@/lib/url';
|
||||
import { createSession, saveEvent, saveSessionData, saveSessionLink } from '@/queries/sql';
|
||||
|
||||
@@ -155,7 +156,7 @@ export async function POST(request: Request) {
|
||||
const sessionSalt = getSalt(saltRotation, createdAt);
|
||||
const visitSalt = hash(startOfHour(createdAt).toUTCString());
|
||||
|
||||
const sessionId = id ? uuid(sourceId, id) : uuid(sourceId, ip, userAgent, sessionSalt);
|
||||
const sessionId = getSessionId(sourceId, ip, userAgent, sessionSalt, id);
|
||||
|
||||
// Create a session if not found
|
||||
if (!clickhouse.enabled && !cache?.sessionId) {
|
||||
@@ -284,7 +285,7 @@ export async function POST(request: Request) {
|
||||
if (websiteId && id) {
|
||||
// Persist both memberships so stitched activity can be resolved from
|
||||
// either the anonymous session or the identified session.
|
||||
const anonymousSessionId = uuid(sourceId, ip, userAgent, sessionSalt);
|
||||
const anonymousSessionId = getSessionId(sourceId, ip, userAgent, sessionSalt);
|
||||
const linkedSessionIds = [...new Set([anonymousSessionId, sessionId])];
|
||||
const newLinkId = hash(...linkedSessionIds, id);
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { uuid } from './crypto';
|
||||
import { getSessionId } from './session';
|
||||
|
||||
const sourceId = '2e2fc91f-77a7-4e5d-a7b1-18a74c67b4e0';
|
||||
const sessionSalt = 'monthly-session-salt';
|
||||
|
||||
test('keeps identified sessions stable on the same device', () => {
|
||||
const args = [sourceId, '192.0.2.1', 'Desktop Browser', sessionSalt, 'user-123'] as const;
|
||||
|
||||
expect(getSessionId(...args)).toBe(getSessionId(...args));
|
||||
});
|
||||
|
||||
test('creates separate sessions for the same distinct ID on different devices', () => {
|
||||
const desktopSessionId = getSessionId(
|
||||
sourceId,
|
||||
'192.0.2.1',
|
||||
'Desktop Browser',
|
||||
sessionSalt,
|
||||
'user-123',
|
||||
);
|
||||
const mobileSessionId = getSessionId(
|
||||
sourceId,
|
||||
'198.51.100.2',
|
||||
'Mobile Browser',
|
||||
sessionSalt,
|
||||
'user-123',
|
||||
);
|
||||
|
||||
expect(desktopSessionId).not.toBe(mobileSessionId);
|
||||
});
|
||||
|
||||
test('does not merge different identified users on the same device', () => {
|
||||
const firstUserSessionId = getSessionId(
|
||||
sourceId,
|
||||
'192.0.2.1',
|
||||
'Desktop Browser',
|
||||
sessionSalt,
|
||||
'user-123',
|
||||
);
|
||||
const secondUserSessionId = getSessionId(
|
||||
sourceId,
|
||||
'192.0.2.1',
|
||||
'Desktop Browser',
|
||||
sessionSalt,
|
||||
'user-456',
|
||||
);
|
||||
|
||||
expect(firstUserSessionId).not.toBe(secondUserSessionId);
|
||||
});
|
||||
|
||||
test('preserves anonymous session IDs', () => {
|
||||
expect(getSessionId(sourceId, '192.0.2.1', 'Desktop Browser', sessionSalt)).toBe(
|
||||
uuid(sourceId, '192.0.2.1', 'Desktop Browser', sessionSalt),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { uuid } from './crypto';
|
||||
|
||||
export function getSessionId(
|
||||
sourceId: string,
|
||||
ip: string,
|
||||
userAgent: string,
|
||||
sessionSalt: string,
|
||||
distinctId?: string,
|
||||
) {
|
||||
return distinctId
|
||||
? uuid(sourceId, ip, userAgent, sessionSalt, distinctId)
|
||||
: uuid(sourceId, ip, userAgent, sessionSalt);
|
||||
}
|
||||
Reference in new issue
Block a user