diff --git a/packages/analytics-remote-config/src/remote-config.ts b/packages/analytics-remote-config/src/remote-config.ts index 02fc0bd9c..c6ee48c07 100644 --- a/packages/analytics-remote-config/src/remote-config.ts +++ b/packages/analytics-remote-config/src/remote-config.ts @@ -23,7 +23,7 @@ export class RemoteConfigFetch localConfig: Config; retryTimeout = 1000; attempts = 0; - lastFetchedSessionId: number | string | undefined; + lastFetchedSessionId: number | undefined; sessionTargetingMatch = false; configKeys: string[]; metrics: RemoteConfigMetric = {}; @@ -36,7 +36,7 @@ export class RemoteConfigFetch getRemoteConfig = async ( configNamespace: string, key: K, - sessionId?: number | string, + sessionId?: number, ): Promise => { const fetchStartTime = Date.now(); // Finally fetch via API @@ -64,7 +64,7 @@ export class RemoteConfigFetch return REMOTE_CONFIG_SERVER_URL; } - fetchWithTimeout = async (sessionId?: number | string): Promise | void> => { + fetchWithTimeout = async (sessionId?: number): Promise | void> => { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const remoteConfig = await this.fetchRemoteConfig(controller.signal, sessionId); @@ -74,7 +74,7 @@ export class RemoteConfigFetch fetchRemoteConfig = async ( signal: AbortController['signal'], - sessionId?: number | string, + sessionId?: number, ): Promise | void> => { if (sessionId === this.lastFetchedSessionId && this.attempts >= this.localConfig.flushMaxRetries) { return this.completeRequest({ err: MAX_RETRIES_EXCEEDED_MESSAGE }); @@ -128,7 +128,7 @@ export class RemoteConfigFetch retryFetch = async ( signal: AbortController['signal'], - sessionId?: number | string, + sessionId?: number, ): Promise | void> => { await new Promise((resolve) => setTimeout(resolve, this.attempts * this.retryTimeout)); return this.fetchRemoteConfig(signal, sessionId); diff --git a/packages/analytics-remote-config/src/types.ts b/packages/analytics-remote-config/src/types.ts index f0591d261..4e7e841e0 100644 --- a/packages/analytics-remote-config/src/types.ts +++ b/packages/analytics-remote-config/src/types.ts @@ -10,7 +10,7 @@ export interface BaseRemoteConfigFetch { getRemoteConfig: ( configNamespace: string, key: K, - sessionId?: number | string, + sessionId?: number, ) => Promise; } diff --git a/packages/plugin-session-replay-browser/src/session-replay.ts b/packages/plugin-session-replay-browser/src/session-replay.ts index 85604f2d8..e33f78477 100644 --- a/packages/plugin-session-replay-browser/src/session-replay.ts +++ b/packages/plugin-session-replay-browser/src/session-replay.ts @@ -48,7 +48,7 @@ export class SessionReplayPlugin implements EnrichmentPlugin { instanceName: this.config.instanceName, deviceId: this.config.deviceId, optOut: this.config.optOut, - sessionId: this.options.customSessionId ? undefined : this.config.sessionId, + sessionId: this.config.sessionId, loggerProvider: this.config.loggerProvider, logLevel: this.config.logLevel, flushMaxRetries: this.config.flushMaxRetries, @@ -74,41 +74,21 @@ export class SessionReplayPlugin implements EnrichmentPlugin { async execute(event: Event) { try { - if (this.options.customSessionId) { - const sessionId = this.options.customSessionId(event); - if (sessionId) { - // On event, synchronize the session id to the custom session id from the event. This may - // suffer from offline/delayed events messing up the state stored - if (sessionId !== sessionReplay.getSessionId()) { - await sessionReplay.setSessionId(sessionId).promise; - } - - const sessionRecordingProperties = sessionReplay.getSessionReplayProperties(); - event.event_properties = { - ...event.event_properties, - ...sessionRecordingProperties, - }; - } - } else { - // On event, synchronize the session id to the what's on the browserConfig (source of truth) - // Choosing not to read from event object here, concerned about offline/delayed events messing up the state stored - // in SR. - const sessionId: string | number | undefined = this.config.sessionId; - if (sessionId && sessionId !== sessionReplay.getSessionId()) { - await sessionReplay.setSessionId(sessionId).promise; - } - - // Treating config.sessionId as source of truth, if the event's session id doesn't match, the - // event is not of the current session (offline/late events). In that case, don't tag the events - if (sessionId && sessionId === event.session_id) { - const sessionRecordingProperties = sessionReplay.getSessionReplayProperties(); - event.event_properties = { - ...event.event_properties, - ...sessionRecordingProperties, - }; - } + // On event, synchronize the session id to the what's on the browserConfig (source of truth) + // Choosing not to read from event object here, concerned about offline/delayed events messing up the state stored + // in SR. + if (this.config.sessionId && this.config.sessionId !== sessionReplay.getSessionId()) { + await sessionReplay.setSessionId(this.config.sessionId).promise; + } + // Treating config.sessionId as source of truth, if the event's session id doesn't match, the + // event is not of the current session (offline/late events). In that case, don't tag the events + if (this.config.sessionId && this.config.sessionId === event.session_id) { + const sessionRecordingProperties = sessionReplay.getSessionReplayProperties(); + event.event_properties = { + ...event.event_properties, + ...sessionRecordingProperties, + }; } - return Promise.resolve(event); } catch (error) { this.config.loggerProvider.error(`Session Replay: Failed to enrich event due to ${(error as Error).message}`); diff --git a/packages/plugin-session-replay-browser/src/typings/session-replay.ts b/packages/plugin-session-replay-browser/src/typings/session-replay.ts index 7a58e7c0e..317baa1a6 100644 --- a/packages/plugin-session-replay-browser/src/typings/session-replay.ts +++ b/packages/plugin-session-replay-browser/src/typings/session-replay.ts @@ -1,4 +1,3 @@ -import { Event } from '@amplitude/analytics-types'; import { StoreType } from '@amplitude/session-replay-browser'; export type MaskLevel = @@ -27,5 +26,4 @@ export interface SessionReplayOptions { shouldInlineStylesheet?: boolean; performanceConfig?: SessionReplayPerformanceConfig; storeType?: StoreType; - customSessionId?: (event: Event) => string | undefined; } diff --git a/packages/plugin-session-replay-browser/test/session-replay.test.ts b/packages/plugin-session-replay-browser/test/session-replay.test.ts index e7101fa05..49437fbd7 100644 --- a/packages/plugin-session-replay-browser/test/session-replay.test.ts +++ b/packages/plugin-session-replay-browser/test/session-replay.test.ts @@ -1,4 +1,4 @@ -import { BrowserClient, BrowserConfig, LogLevel, Logger, Plugin, Event } from '@amplitude/analytics-types'; +import { BrowserClient, BrowserConfig, LogLevel, Logger, Plugin } from '@amplitude/analytics-types'; import * as sessionReplayBrowser from '@amplitude/session-replay-browser'; import { SessionReplayPlugin, sessionReplayPlugin } from '../src/session-replay'; import { VERSION } from '../src/version'; @@ -304,84 +304,6 @@ describe('SessionReplayPlugin', () => { }); await sessionReplay.teardown?.(); }); - - test('should update the session id on any event when using custom session id', async () => { - const sessionReplay = sessionReplayPlugin({ - customSessionId: (event: Event) => { - const event_properties = event.event_properties as { [key: string]: any }; - if (!event_properties) { - return; - } - return event_properties['custom_session_id'] as string | undefined; - }, - }); - await sessionReplay.setup({ ...mockConfig }); - getSessionId.mockReturnValueOnce('test_122'); - const event = { - event_type: 'event_type', - event_properties: { - property_a: true, - property_b: 123, - custom_session_id: 'test_123', - }, - session_id: 124, - }; - - await sessionReplay.execute(event); - expect(setSessionId).toHaveBeenCalledTimes(1); - expect(setSessionId).toHaveBeenCalledWith('test_123'); - }); - - test('should not update the session id when using custom session id and it does not change', async () => { - const sessionReplay = sessionReplayPlugin({ - customSessionId: (event: Event) => { - const event_properties = event.event_properties as { [key: string]: any }; - if (!event_properties) { - return; - } - return event_properties['custom_session_id'] as string | undefined; - }, - }); - await sessionReplay.setup({ ...mockConfig }); - getSessionId.mockReturnValueOnce('test_123'); - const event = { - event_type: 'event_type', - event_properties: { - property_a: true, - property_b: 123, - custom_session_id: 'test_123', - }, - session_id: 124, - }; - - await sessionReplay.execute(event); - expect(setSessionId).not.toHaveBeenCalled(); - }); - - test('should do nothing when the custom session id cannot be found', async () => { - const sessionReplay = sessionReplayPlugin({ - customSessionId: (event: Event) => { - const event_properties = event.event_properties as { [key: string]: any }; - if (!event_properties) { - return; - } - return event_properties['custom_session_id'] as string | undefined; - }, - }); - await sessionReplay.setup({ ...mockConfig }); - const event = { - event_type: 'event_type', - event_properties: { - property_a: true, - property_b: 123, - }, - session_id: 124, - }; - - const enrichedEvent = await sessionReplay.execute(event); - expect(setSessionId).not.toHaveBeenCalled(); - expect(enrichedEvent).toEqual(event); - }); }); describe('getSessionReplayProperties', () => { diff --git a/packages/session-replay-browser/src/config/joined-config.ts b/packages/session-replay-browser/src/config/joined-config.ts index 7c74d2d05..19554d66b 100644 --- a/packages/session-replay-browser/src/config/joined-config.ts +++ b/packages/session-replay-browser/src/config/joined-config.ts @@ -46,7 +46,7 @@ export class SessionReplayJoinedConfigGenerator { this.remoteConfigFetch = remoteConfigFetch; } - async generateJoinedConfig(sessionId?: string | number): Promise { + async generateJoinedConfig(sessionId?: number): Promise { const config: SessionReplayJoinedConfig = { ...this.localConfig }; // Special case here as optOut is implemented via getter/setter config.optOut = this.localConfig.optOut; diff --git a/packages/session-replay-browser/src/config/types.ts b/packages/session-replay-browser/src/config/types.ts index 55f602379..ed22ba448 100644 --- a/packages/session-replay-browser/src/config/types.ts +++ b/packages/session-replay-browser/src/config/types.ts @@ -67,7 +67,7 @@ export interface SessionReplayRemoteConfigFetch { } export interface SessionReplayJoinedConfigGenerator { - generateJoinedConfig: (sessionId?: string | number) => Promise; + generateJoinedConfig: (sessionId?: number) => Promise; } export interface SessionReplayVersion { diff --git a/packages/session-replay-browser/src/events/base-events-store.ts b/packages/session-replay-browser/src/events/base-events-store.ts index 44cfb5cfc..8f22c9cda 100644 --- a/packages/session-replay-browser/src/events/base-events-store.ts +++ b/packages/session-replay-browser/src/events/base-events-store.ts @@ -29,12 +29,12 @@ export abstract class BaseEventsStore implements EventsStore { } abstract addEventToCurrentSequence( - sessionId: string | number, + sessionId: number, event: string, ): Promise | undefined>; abstract getSequencesToSend(): Promise[] | undefined>; abstract storeCurrentSequence(sessionId: number): Promise | undefined>; - abstract storeSendingEvents(sessionId: string | number, events: Events): Promise; + abstract storeSendingEvents(sessionId: number, events: Events): Promise; abstract cleanUpSessionEventsStore(sessionId: number, sequenceId: KeyType): Promise; /** diff --git a/packages/session-replay-browser/src/events/event-compressor.ts b/packages/session-replay-browser/src/events/event-compressor.ts index 600e73356..4581ec023 100644 --- a/packages/session-replay-browser/src/events/event-compressor.ts +++ b/packages/session-replay-browser/src/events/event-compressor.ts @@ -6,7 +6,7 @@ import { getGlobalScope } from '@amplitude/analytics-client-common'; interface TaskQueue { event: eventWithTime; - sessionId: string | number; + sessionId: number; } const DEFAULT_TIMEOUT = 2000; @@ -46,7 +46,7 @@ export class EventCompressor { } // Add an event to the task queue if idle callback is supported or compress the event directly - public enqueueEvent(event: eventWithTime, sessionId: string | number): void { + public enqueueEvent(event: eventWithTime, sessionId: number): void { if (this.canUseIdleCallback && this.config.performanceConfig?.enabled) { this.config.loggerProvider.debug('Enqueuing event for processing during idle time.'); this.taskQueue.push({ event, sessionId }); @@ -86,7 +86,7 @@ export class EventCompressor { return JSON.stringify(packedEvent); }; - public addCompressedEvent = (event: eventWithTime, sessionId: string | number) => { + public addCompressedEvent = (event: eventWithTime, sessionId: number) => { const compressedEvent = this.compressEvent(event); if (this.eventsManager && this.deviceId) { diff --git a/packages/session-replay-browser/src/events/events-idb-store.ts b/packages/session-replay-browser/src/events/events-idb-store.ts index 38e03451d..558156b10 100644 --- a/packages/session-replay-browser/src/events/events-idb-store.ts +++ b/packages/session-replay-browser/src/events/events-idb-store.ts @@ -18,7 +18,7 @@ export interface SessionReplayDB extends DBSchema { sequencesToSend: { key: number; value: Omit, 'sequenceId'>; - indexes: { sessionId: string | number }; + indexes: { sessionId: number }; }; } @@ -105,7 +105,7 @@ export class SessionReplayEventsIDBStore extends BaseEventsStore { static async new( type: EventType, args: Omit, - sessionId?: string | number, + sessionId?: number, ): Promise { try { const dbSuffix = type === 'replay' ? '' : `_${type}`; @@ -173,10 +173,7 @@ export class SessionReplayEventsIDBStore extends BaseEventsStore { events: currentSequenceData.events, }); - await this.db.put<'sessionCurrentSequence'>(currentSequenceKey, { - sessionId, - events: [], - }); + await this.db.put<'sessionCurrentSequence'>(currentSequenceKey, { sessionId, events: [] }); return { ...currentSequenceData, @@ -254,7 +251,7 @@ export class SessionReplayEventsIDBStore extends BaseEventsStore { } }; - transitionFromKeyValStore = async (sessionId?: string | number) => { + transitionFromKeyValStore = async (sessionId?: number) => { try { const keyValDb = await keyValDatabaseExists(); if (!keyValDb) { diff --git a/packages/session-replay-browser/src/events/events-manager.ts b/packages/session-replay-browser/src/events/events-manager.ts index 8a143db38..4e79b8ee9 100644 --- a/packages/session-replay-browser/src/events/events-manager.ts +++ b/packages/session-replay-browser/src/events/events-manager.ts @@ -24,7 +24,7 @@ export const createEventsManager = async ({ type: Type; minInterval?: number; maxInterval?: number; - sessionId?: string | number; + sessionId?: number; payloadBatcher?: PayloadBatcher; storeType: StoreType; }): Promise> => { @@ -65,7 +65,7 @@ export const createEventsManager = async ({ sequenceId, }: { events: string[]; - sessionId: string | number; + sessionId: number; deviceId: string; sequenceId?: number; }) => { diff --git a/packages/session-replay-browser/src/events/events-memory-store.ts b/packages/session-replay-browser/src/events/events-memory-store.ts index 5c79aae12..5f0ed015a 100644 --- a/packages/session-replay-browser/src/events/events-memory-store.ts +++ b/packages/session-replay-browser/src/events/events-memory-store.ts @@ -2,15 +2,15 @@ import { Events, SendingSequencesReturn } from '../typings/session-replay'; import { BaseEventsStore } from './base-events-store'; export class InMemoryEventsStore extends BaseEventsStore { - private finalizedSequences: Record = {}; - private sequences: Record = {}; + private finalizedSequences: Record = {}; + private sequences: Record = {}; private sequenceId = 0; - private resetCurrentSequence(sessionId: string | number) { + private resetCurrentSequence(sessionId: number) { this.sequences[sessionId] = []; } - private addSequence(sessionId: string | number): SendingSequencesReturn { + private addSequence(sessionId: number): SendingSequencesReturn { const sequenceId = this.sequenceId++; const events = [...this.sequences[sessionId]]; this.finalizedSequences[sequenceId] = { sessionId, events }; @@ -26,7 +26,7 @@ export class InMemoryEventsStore extends BaseEventsStore { })); } - async storeCurrentSequence(sessionId: string | number): Promise | undefined> { + async storeCurrentSequence(sessionId: number): Promise | undefined> { if (!this.sequences[sessionId]) { return undefined; } diff --git a/packages/session-replay-browser/src/helpers.ts b/packages/session-replay-browser/src/helpers.ts index 001808232..7c5d9ec75 100644 --- a/packages/session-replay-browser/src/helpers.ts +++ b/packages/session-replay-browser/src/helpers.ts @@ -1,14 +1,12 @@ import { getGlobalScope } from '@amplitude/analytics-client-common'; -import { ServerZone } from '@amplitude/analytics-types'; -import { getInputType } from '@amplitude/rrweb-snapshot'; +import { KB_SIZE, MASK_TEXT_CLASS, UNMASK_TEXT_CLASS } from './constants'; import { DEFAULT_MASK_LEVEL, MaskLevel, PrivacyConfig, SessionReplayJoinedConfig } from './config/types'; +import { getInputType } from '@amplitude/rrweb-snapshot'; +import { ServerZone } from '@amplitude/analytics-types'; import { - KB_SIZE, - MASK_TEXT_CLASS, - SESSION_REPLAY_EU_URL, + SESSION_REPLAY_EU_URL as SESSION_REPLAY_EU_SERVER_URL, SESSION_REPLAY_SERVER_URL, - SESSION_REPLAY_STAGING_URL, - UNMASK_TEXT_CLASS, + SESSION_REPLAY_STAGING_URL as SESSION_REPLAY_STAGING_SERVER_URL, } from './constants'; import { StorageData } from './typings/session-replay'; @@ -111,7 +109,7 @@ export const generateHashCode = function (str: string) { return hash; }; -export const isSessionInSample = function (sessionId: string | number, sampleRate: number) { +export const isSessionInSample = function (sessionId: number, sampleRate: number) { const hashNumber = generateHashCode(sessionId.toString()); const absHash = Math.abs(hashNumber); const absHashMultiply = absHash * 31; @@ -124,17 +122,17 @@ export const getCurrentUrl = () => { return globalScope?.location ? globalScope.location.href : ''; }; -export const generateSessionReplayId = (sessionId: string | number, deviceId: string): string => { +export const generateSessionReplayId = (sessionId: number, deviceId: string): string => { return `${deviceId}/${sessionId}`; }; export const getServerUrl = (serverZone?: keyof typeof ServerZone): string => { if (serverZone === ServerZone.STAGING) { - return SESSION_REPLAY_STAGING_URL; + return SESSION_REPLAY_STAGING_SERVER_URL; } if (serverZone === ServerZone.EU) { - return SESSION_REPLAY_EU_URL; + return SESSION_REPLAY_EU_SERVER_URL; } return SESSION_REPLAY_SERVER_URL; diff --git a/packages/session-replay-browser/src/hooks/click.ts b/packages/session-replay-browser/src/hooks/click.ts index a70abd6be..821bcf059 100644 --- a/packages/session-replay-browser/src/hooks/click.ts +++ b/packages/session-replay-browser/src/hooks/click.ts @@ -21,7 +21,7 @@ export type ClickEvent = { export type ClickEventWithCount = ClickEvent & { count: number }; type Options = { - sessionId: string | number; + sessionId: number; deviceIdFn: () => string | undefined; eventsManager: AmplitudeSessionReplayEventsManager<'interaction', string>; }; diff --git a/packages/session-replay-browser/src/identifiers.ts b/packages/session-replay-browser/src/identifiers.ts index a71122a9c..a12a573ba 100644 --- a/packages/session-replay-browser/src/identifiers.ts +++ b/packages/session-replay-browser/src/identifiers.ts @@ -2,11 +2,11 @@ import { generateSessionReplayId } from './helpers'; import { SessionIdentifiers as ISessionIdentifiers } from './typings/session-replay'; export class SessionIdentifiers implements ISessionIdentifiers { - deviceId?: string; - sessionId?: string | number; - sessionReplayId?: string; + deviceId?: string | undefined; + sessionId?: number | undefined; + sessionReplayId?: string | undefined; - constructor({ sessionId, deviceId }: { sessionId?: string | number; deviceId?: string }) { + constructor({ sessionId, deviceId }: { sessionId?: number; deviceId?: string }) { this.deviceId = deviceId; this.sessionId = sessionId; diff --git a/packages/session-replay-browser/src/session-replay.ts b/packages/session-replay-browser/src/session-replay.ts index 97708603d..b0195fbf1 100644 --- a/packages/session-replay-browser/src/session-replay.ts +++ b/packages/session-replay-browser/src/session-replay.ts @@ -147,11 +147,11 @@ export class SessionReplay implements AmplitudeSessionReplay { this.initialize(true); } - setSessionId(sessionId: string | number, deviceId?: string) { + setSessionId(sessionId: number, deviceId?: string) { return returnWrapper(this.asyncSetSessionId(sessionId, deviceId)); } - async asyncSetSessionId(sessionId: string | number, deviceId?: string) { + async asyncSetSessionId(sessionId: number, deviceId?: string) { const previousSessionId = this.identifiers && this.identifiers.sessionId; if (previousSessionId) { this.sendEvents(previousSessionId); @@ -230,7 +230,7 @@ export class SessionReplay implements AmplitudeSessionReplay { }); }; - sendEvents(sessionId?: string | number) { + sendEvents(sessionId?: number) { const sessionIdToSend = sessionId || this.identifiers?.sessionId; const deviceId = this.getDeviceId(); this.eventsManager && diff --git a/packages/session-replay-browser/src/typings/session-replay.ts b/packages/session-replay-browser/src/typings/session-replay.ts index 81cb3c13e..c4f06a2f8 100644 --- a/packages/session-replay-browser/src/typings/session-replay.ts +++ b/packages/session-replay-browser/src/typings/session-replay.ts @@ -20,8 +20,8 @@ export type EventType = 'replay' | 'interaction'; export interface SessionReplayDestinationSessionMetadata { type: EventType; - sessionId: string | number; - deviceId: string | undefined; + sessionId: number; + deviceId?: string; version?: SessionReplayVersion; } @@ -41,7 +41,7 @@ export interface SessionReplayDestinationContext extends SessionReplayDestinatio export interface SendingSequencesReturn { sequenceId: KeyType; - sessionId: string | number; + sessionId: number; events: Events; } @@ -53,28 +53,25 @@ export interface EventsStore { /** * Moves current sequence of events to long term storage and resets short term storage. */ - storeCurrentSequence(sessionId: string | number): Promise | undefined>; + storeCurrentSequence(sessionId: number): Promise | undefined>; /** * Adds events to the current IDB sequence. Returns events that should be * sent to the track destination right away if should split events is true. */ - addEventToCurrentSequence( - sessionId: string | number, - event: string, - ): Promise | undefined>; + addEventToCurrentSequence(sessionId: number, event: string): Promise | undefined>; /** * Returns the sequence id associated with the events batch. * @returns the new sequence id or undefined if it cannot be determined or on any error. */ - storeSendingEvents(sessionId: string | number, events: Events): Promise; + storeSendingEvents(sessionId: number, events: Events): Promise; /** * Permanently removes the events batch for the session/sequence pair. */ - cleanUpSessionEventsStore(sessionId: string | number, sequenceId?: KeyType): Promise; + cleanUpSessionEventsStore(sessionId: number, sequenceId?: KeyType): Promise; } export interface SessionIdentifiers { deviceId?: string; - sessionId?: string | number; + sessionId?: number; sessionReplayId?: string; } @@ -82,8 +79,8 @@ export type SessionReplayOptions = Omit AmplitudeReturn; - setSessionId: (sessionId: string | number, deviceId?: string) => AmplitudeReturn; - getSessionId: () => string | number | undefined; + setSessionId: (sessionId: number, deviceId?: string) => AmplitudeReturn; + getSessionId: () => number | undefined; getSessionReplayProperties: () => { [key: string]: boolean | string | null }; flush: (useRetry: boolean) => Promise; shutdown: () => void; @@ -118,14 +115,14 @@ export interface SessionReplayEventsManager { event, deviceId, }: { - sessionId: string | number; + sessionId: number; event: { type: Type; data: Event }; deviceId: string; }): void; /** * Move events in short term storage to long term storage and send immediately to the track destination. */ - sendCurrentSequenceEvents({ sessionId, deviceId }: { sessionId: string | number; deviceId: string }): void; + sendCurrentSequenceEvents({ sessionId, deviceId }: { sessionId: number; deviceId: string }): void; /** * Flush the track destination queue immediately. This should invoke sends for all the events in the queue. */