@amplitude/session-replay-browser 1.45.0-sr-perf-reliability-rc3.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/events/events-manager.js +1 -1
- package/lib/cjs/events/events-manager.js.map +1 -1
- package/lib/cjs/track-destination.d.ts +2 -2
- package/lib/cjs/track-destination.js +3 -3
- package/lib/cjs/track-destination.js.map +1 -1
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.d.ts.map +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/cjs/version.js.map +1 -1
- package/lib/esm/events/events-manager.js +1 -1
- package/lib/esm/events/events-manager.js.map +1 -1
- package/lib/esm/track-destination.d.ts +2 -2
- package/lib/esm/track-destination.js +3 -3
- package/lib/esm/track-destination.js.map +1 -1
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.d.ts.map +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/esm/version.js.map +1 -1
- package/lib/scripts/index-min.js +1 -1
- package/lib/scripts/index-min.js.gz +0 -0
- package/lib/scripts/index-min.js.map +1 -1
- package/lib/scripts/session-replay-browser-min.js +1 -1
- package/lib/scripts/session-replay-browser-min.js.gz +0 -0
- package/lib/scripts/session-replay-browser-min.js.map +1 -1
- package/package.json +4 -4
|
@@ -206,7 +206,7 @@ var createEventsManager = function (_a) {
|
|
|
206
206
|
return [2 /*return*/];
|
|
207
207
|
}
|
|
208
208
|
config.loggerProvider.log("Draining ".concat(sequencesToSend.length, " stored sequence(s) from previous session."));
|
|
209
|
-
//
|
|
209
|
+
// These persisted sequences are about to be enqueued back-to-back. Without
|
|
210
210
|
// coalescing they flush as N separate POSTs — a request flood on page load. Mark the
|
|
211
211
|
// imminent flush to merge same-identity batches (the enqueues below are synchronous, so
|
|
212
212
|
// the whole backlog lands in the queue before the deferred flush consumes the flag). Skip
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-manager.js","sourceRoot":"","sources":["../../../src/events/events-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n SessionReplayEventsManager as AmplitudeSessionReplayEventsManager,\n EventsStore,\n EventType,\n StoreType,\n} from '../typings/session-replay';\n\nimport { SessionReplayJoinedConfig } from '../config/types';\nimport { MAX_SINGLE_EVENT_SIZE } from '../constants';\nimport { getStorageSize } from '../helpers';\nimport { PayloadBatcher, SessionReplayTrackDestination } from '../track-destination';\nimport { SessionReplayEventsIDBStore } from './events-idb-store';\nimport { InMemoryEventsStore } from './events-memory-store';\n\nexport type EventsManagerWithBeacon<Type extends EventType> = AmplitudeSessionReplayEventsManager<Type, string> & {\n /**\n * Returns current pending events (since last flush) for synchronous access on page exit.\n * Used to populate a sendBeacon payload when the page is unloading.\n */\n getBeaconEvents(): string[];\n /**\n * Drops all pending beacon events. Used when the session is decided to be below\n * the min duration threshold so its events don't leak into a later session's beacon.\n */\n dropPendingBeaconEvents(): void;\n trackDestination: SessionReplayTrackDestination;\n};\n\nexport const createEventsManager = async <Type extends EventType>({\n config,\n minInterval,\n maxInterval,\n maxPersistedEventsSize,\n type,\n payloadBatcher,\n storeType,\n trackDestinationWorkerScript,\n shouldSend,\n}: {\n config: SessionReplayJoinedConfig;\n type: Type;\n minInterval?: number;\n maxInterval?: number;\n maxPersistedEventsSize?: number;\n payloadBatcher?: PayloadBatcher;\n storeType: StoreType;\n trackDestinationWorkerScript?: string;\n shouldSend?: () => boolean;\n}): Promise<EventsManagerWithBeacon<Type>> => {\n // Configurable per-single-event drop cap (defaults to MAX_SINGLE_EVENT_SIZE); enforced both\n // here as a pre-send backstop and at capture time in EventCompressor.\n const maxSingleEventSize = config.maxSingleEventSizeBytes ?? MAX_SINGLE_EVENT_SIZE;\n const trackDestination = new SessionReplayTrackDestination({\n ...config,\n loggerProvider: config.loggerProvider,\n payloadBatcher,\n workerScript: trackDestinationWorkerScript,\n });\n\n const getMemoryStore = (): EventsStore<number> => {\n return new InMemoryEventsStore({\n loggerProvider: config.loggerProvider,\n maxInterval,\n minInterval,\n maxPersistedEventsSize,\n });\n };\n\n let lastKnownDeviceId: string | undefined;\n let usingIdbStore = false;\n let store!: EventsStore<number>;\n\n const switchToMemoryStore = async () => {\n if (!usingIdbStore) return;\n usingIdbStore = false;\n config.loggerProvider.warn('IDB store is experiencing repeated failures; falling back to in-memory event store.');\n const sequences = lastKnownDeviceId ? await store.getSequencesToSend() : undefined;\n store = getMemoryStore();\n if (sequences && lastKnownDeviceId) {\n const deviceId = lastKnownDeviceId;\n sequences.forEach((seq) => {\n sendEventsList({ sequenceId: seq.sequenceId, events: seq.events, sessionId: seq.sessionId, deviceId });\n });\n }\n };\n\n const getIdbStoreOrFallback = async (): Promise<EventsStore<number>> => {\n const idb = await SessionReplayEventsIDBStore.new(type, {\n loggerProvider: config.loggerProvider,\n minInterval,\n maxInterval,\n maxPersistedEventsSize,\n apiKey: config.apiKey,\n onPersistentFailure: () => {\n void switchToMemoryStore();\n },\n });\n if (!idb) {\n config.loggerProvider.log('Failed to initialize idb store, falling back to memory store.');\n return getMemoryStore();\n }\n usingIdbStore = true;\n return idb;\n };\n\n store = storeType === 'idb' ? await getIdbStoreOrFallback() : getMemoryStore();\n\n // Beacon buffer: a sliding window of pending (unsent) event strings for synchronous\n // access on page exit. Uses an absolute index counter to correctly handle concurrent\n // async flushes without losing events added between the flush call and its resolution.\n const beaconBuffer: string[] = [];\n let beaconWindowStart = 0; // absolute index of the first element in beaconBuffer\n\n const advanceBeaconWindow = (upToAbsoluteIdx: number) => {\n if (upToAbsoluteIdx <= beaconWindowStart) return;\n const trimCount = Math.min(upToAbsoluteIdx - beaconWindowStart, beaconBuffer.length);\n if (trimCount > 0) {\n beaconBuffer.splice(0, trimCount);\n beaconWindowStart = upToAbsoluteIdx;\n }\n };\n\n /**\n * Immediately sends events to the track destination.\n */\n const sendEventsList = ({\n events: rawEvents,\n sessionId,\n deviceId,\n sequenceId,\n }: {\n events: string[];\n sessionId: string | number;\n deviceId: string;\n sequenceId?: number;\n }) => {\n // Backstop for events that entered IDB before the per-event size guard in\n // addCompressedEventToManager (e.g. stored by a previous SDK version or via\n // storeCurrentSequence/sendStoredEvents which bypass the capture-time check).\n // Compare UTF-8 byte size, not JS char count, to match the server-side limit.\n const sizedEvents = rawEvents.map((e) => ({ event: e, bytes: new Blob([e]).size }));\n const oversized = sizedEvents.filter((s) => s.bytes > maxSingleEventSize);\n if (oversized.length > 0) {\n config.loggerProvider.warn(\n `Dropping ${oversized.length} oversized event(s) from session replay sequence before send. Sizes: ${oversized\n .map((s) => `${Math.round(s.bytes / 1024)} KB`)\n .join(\n ', ',\n )}. If this recurs, please open a GitHub issue at https://github.com/amplitude/Amplitude-TypeScript/issues or contact Amplitude support.`,\n );\n }\n const events =\n oversized.length > 0 ? sizedEvents.filter((s) => s.bytes <= maxSingleEventSize).map((s) => s.event) : rawEvents;\n if (events.length === 0) {\n store.cleanUpSessionEventsStore(sessionId, sequenceId).catch((e) => {\n config.loggerProvider.warn('Failed to clean up session replay events store:', e);\n });\n return;\n }\n\n if (config.debugMode) {\n getStorageSize()\n .then(({ totalStorageSize, percentOfQuota, usageDetails }) => {\n config.loggerProvider.debug(\n `Total storage size: ${totalStorageSize} KB, percentage of quota: ${percentOfQuota}%, usage details: ${usageDetails}`,\n );\n })\n .catch(() => {\n // swallow error\n });\n }\n\n trackDestination.sendEventsList({\n events: events,\n sessionId: sessionId,\n flushMaxRetries: config.flushMaxRetries,\n apiKey: config.apiKey,\n deviceId: deviceId,\n sampleRate: config.sampleRate,\n serverZone: config.serverZone,\n version: config.version,\n type,\n onComplete: async () => {\n await store.cleanUpSessionEventsStore(sessionId, sequenceId);\n return;\n },\n });\n };\n\n const sendCurrentSequenceEvents = ({ sessionId, deviceId }: { sessionId: number; deviceId: string }) => {\n lastKnownDeviceId = deviceId;\n // Evaluate shouldSend synchronously before the async store read. asyncSetSessionId\n // updates sessionStartTime immediately after calling sendEvents(), so by the time\n // storeCurrentSequence resolves the start time would reflect the new session and\n // the elapsed check would compute ~0ms, silently dropping the previous session's events.\n if (shouldSend && !shouldSend()) return;\n // Snapshot the absolute end-index before the async store read so that any events\n // pushed after this point are NOT considered sent and remain in the beacon buffer.\n const snapshotAbsIdx = beaconWindowStart + beaconBuffer.length;\n store\n .storeCurrentSequence(sessionId)\n .then((currentSequence) => {\n if (currentSequence) {\n advanceBeaconWindow(snapshotAbsIdx);\n sendEventsList({\n sequenceId: currentSequence.sequenceId,\n events: currentSequence.events,\n sessionId: currentSequence.sessionId,\n deviceId,\n });\n }\n })\n .catch((e) => {\n config.loggerProvider.warn('Failed to get current sequence of session replay events for session:', e);\n });\n };\n\n const sendStoredEvents = async ({ deviceId }: { deviceId: string }) => {\n lastKnownDeviceId = deviceId;\n const sequencesToSend = await store.getSequencesToSend();\n if (!sequencesToSend?.length) {\n return;\n }\n config.loggerProvider.log(`Draining ${sequencesToSend.length} stored sequence(s) from previous session.`);\n // SR-4660: these persisted sequences are about to be enqueued back-to-back. Without\n // coalescing they flush as N separate POSTs — a request flood on page load. Mark the\n // imminent flush to merge same-identity batches (the enqueues below are synchronous, so\n // the whole backlog lands in the queue before the deferred flush consumes the flag). Skip\n // the single-sequence case where there's nothing to merge.\n if (sequencesToSend.length > 1) {\n trackDestination.markCoalesceNextFlush();\n }\n sequencesToSend.forEach((sequence) => {\n sendEventsList({\n sequenceId: sequence.sequenceId,\n events: sequence.events,\n sessionId: sequence.sessionId,\n deviceId,\n });\n });\n };\n\n const addEvent = ({\n event,\n sessionId,\n deviceId,\n }: {\n event: { type: Type; data: string };\n sessionId: number;\n deviceId: string;\n }) => {\n lastKnownDeviceId = deviceId;\n // Capture shouldSend synchronously before the async store write, for the same\n // reason as sendCurrentSequenceEvents: asyncSetSessionId updates sessionStartTime\n // synchronously, so evaluating inside the .then() would use the new session's\n // start time and compute ~0ms elapsed, silently dropping a valid batch.\n const canSend = !shouldSend || shouldSend();\n // Record the absolute index of this event in the beacon buffer before the async\n // store operation. If a batch split occurs, we advance the window up to (but not\n // including) this event so that it starts the next pending window.\n const absIdx = beaconWindowStart + beaconBuffer.length;\n beaconBuffer.push(event.data);\n store\n .addEventToCurrentSequence(sessionId, event.data)\n .then((sequenceToSend) => {\n if (sequenceToSend) {\n if (!canSend) {\n // The split atomically moved events to sequencesToSend; without cleanup\n // they would be unconditionally replayed on next page load via\n // sendStoredEvents, bypassing the min session duration gate. The split\n // batch must also be dropped from the beacon buffer so it isn't sent\n // via sendBeacon on page unload (potentially attributed to a later session).\n store.cleanUpSessionEventsStore(sequenceToSend.sessionId, sequenceToSend.sequenceId).catch((e) => {\n config.loggerProvider.warn('Failed to clean up dropped session replay sequence:', e);\n });\n advanceBeaconWindow(absIdx);\n return;\n }\n // Events before absIdx belong to the split batch being sent; advance window.\n advanceBeaconWindow(absIdx);\n sendEventsList({\n sequenceId: sequenceToSend.sequenceId,\n events: sequenceToSend.events,\n sessionId: sequenceToSend.sessionId,\n deviceId,\n });\n }\n })\n .catch((e) => {\n config.loggerProvider.warn('Failed to add event to session replay capture:', e);\n });\n };\n\n async function flush(useRetry = false) {\n return trackDestination.flush(useRetry);\n }\n\n const getBeaconEvents = (): string[] => [...beaconBuffer];\n\n const dropPendingBeaconEvents = () => {\n advanceBeaconWindow(beaconWindowStart + beaconBuffer.length);\n };\n\n return {\n sendCurrentSequenceEvents,\n addEvent,\n sendStoredEvents,\n flush,\n getBeaconEvents,\n dropPendingBeaconEvents,\n trackDestination,\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"events-manager.js","sourceRoot":"","sources":["../../../src/events/events-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n SessionReplayEventsManager as AmplitudeSessionReplayEventsManager,\n EventsStore,\n EventType,\n StoreType,\n} from '../typings/session-replay';\n\nimport { SessionReplayJoinedConfig } from '../config/types';\nimport { MAX_SINGLE_EVENT_SIZE } from '../constants';\nimport { getStorageSize } from '../helpers';\nimport { PayloadBatcher, SessionReplayTrackDestination } from '../track-destination';\nimport { SessionReplayEventsIDBStore } from './events-idb-store';\nimport { InMemoryEventsStore } from './events-memory-store';\n\nexport type EventsManagerWithBeacon<Type extends EventType> = AmplitudeSessionReplayEventsManager<Type, string> & {\n /**\n * Returns current pending events (since last flush) for synchronous access on page exit.\n * Used to populate a sendBeacon payload when the page is unloading.\n */\n getBeaconEvents(): string[];\n /**\n * Drops all pending beacon events. Used when the session is decided to be below\n * the min duration threshold so its events don't leak into a later session's beacon.\n */\n dropPendingBeaconEvents(): void;\n trackDestination: SessionReplayTrackDestination;\n};\n\nexport const createEventsManager = async <Type extends EventType>({\n config,\n minInterval,\n maxInterval,\n maxPersistedEventsSize,\n type,\n payloadBatcher,\n storeType,\n trackDestinationWorkerScript,\n shouldSend,\n}: {\n config: SessionReplayJoinedConfig;\n type: Type;\n minInterval?: number;\n maxInterval?: number;\n maxPersistedEventsSize?: number;\n payloadBatcher?: PayloadBatcher;\n storeType: StoreType;\n trackDestinationWorkerScript?: string;\n shouldSend?: () => boolean;\n}): Promise<EventsManagerWithBeacon<Type>> => {\n // Configurable per-single-event drop cap (defaults to MAX_SINGLE_EVENT_SIZE); enforced both\n // here as a pre-send backstop and at capture time in EventCompressor.\n const maxSingleEventSize = config.maxSingleEventSizeBytes ?? MAX_SINGLE_EVENT_SIZE;\n const trackDestination = new SessionReplayTrackDestination({\n ...config,\n loggerProvider: config.loggerProvider,\n payloadBatcher,\n workerScript: trackDestinationWorkerScript,\n });\n\n const getMemoryStore = (): EventsStore<number> => {\n return new InMemoryEventsStore({\n loggerProvider: config.loggerProvider,\n maxInterval,\n minInterval,\n maxPersistedEventsSize,\n });\n };\n\n let lastKnownDeviceId: string | undefined;\n let usingIdbStore = false;\n let store!: EventsStore<number>;\n\n const switchToMemoryStore = async () => {\n if (!usingIdbStore) return;\n usingIdbStore = false;\n config.loggerProvider.warn('IDB store is experiencing repeated failures; falling back to in-memory event store.');\n const sequences = lastKnownDeviceId ? await store.getSequencesToSend() : undefined;\n store = getMemoryStore();\n if (sequences && lastKnownDeviceId) {\n const deviceId = lastKnownDeviceId;\n sequences.forEach((seq) => {\n sendEventsList({ sequenceId: seq.sequenceId, events: seq.events, sessionId: seq.sessionId, deviceId });\n });\n }\n };\n\n const getIdbStoreOrFallback = async (): Promise<EventsStore<number>> => {\n const idb = await SessionReplayEventsIDBStore.new(type, {\n loggerProvider: config.loggerProvider,\n minInterval,\n maxInterval,\n maxPersistedEventsSize,\n apiKey: config.apiKey,\n onPersistentFailure: () => {\n void switchToMemoryStore();\n },\n });\n if (!idb) {\n config.loggerProvider.log('Failed to initialize idb store, falling back to memory store.');\n return getMemoryStore();\n }\n usingIdbStore = true;\n return idb;\n };\n\n store = storeType === 'idb' ? await getIdbStoreOrFallback() : getMemoryStore();\n\n // Beacon buffer: a sliding window of pending (unsent) event strings for synchronous\n // access on page exit. Uses an absolute index counter to correctly handle concurrent\n // async flushes without losing events added between the flush call and its resolution.\n const beaconBuffer: string[] = [];\n let beaconWindowStart = 0; // absolute index of the first element in beaconBuffer\n\n const advanceBeaconWindow = (upToAbsoluteIdx: number) => {\n if (upToAbsoluteIdx <= beaconWindowStart) return;\n const trimCount = Math.min(upToAbsoluteIdx - beaconWindowStart, beaconBuffer.length);\n if (trimCount > 0) {\n beaconBuffer.splice(0, trimCount);\n beaconWindowStart = upToAbsoluteIdx;\n }\n };\n\n /**\n * Immediately sends events to the track destination.\n */\n const sendEventsList = ({\n events: rawEvents,\n sessionId,\n deviceId,\n sequenceId,\n }: {\n events: string[];\n sessionId: string | number;\n deviceId: string;\n sequenceId?: number;\n }) => {\n // Backstop for events that entered IDB before the per-event size guard in\n // addCompressedEventToManager (e.g. stored by a previous SDK version or via\n // storeCurrentSequence/sendStoredEvents which bypass the capture-time check).\n // Compare UTF-8 byte size, not JS char count, to match the server-side limit.\n const sizedEvents = rawEvents.map((e) => ({ event: e, bytes: new Blob([e]).size }));\n const oversized = sizedEvents.filter((s) => s.bytes > maxSingleEventSize);\n if (oversized.length > 0) {\n config.loggerProvider.warn(\n `Dropping ${oversized.length} oversized event(s) from session replay sequence before send. Sizes: ${oversized\n .map((s) => `${Math.round(s.bytes / 1024)} KB`)\n .join(\n ', ',\n )}. If this recurs, please open a GitHub issue at https://github.com/amplitude/Amplitude-TypeScript/issues or contact Amplitude support.`,\n );\n }\n const events =\n oversized.length > 0 ? sizedEvents.filter((s) => s.bytes <= maxSingleEventSize).map((s) => s.event) : rawEvents;\n if (events.length === 0) {\n store.cleanUpSessionEventsStore(sessionId, sequenceId).catch((e) => {\n config.loggerProvider.warn('Failed to clean up session replay events store:', e);\n });\n return;\n }\n\n if (config.debugMode) {\n getStorageSize()\n .then(({ totalStorageSize, percentOfQuota, usageDetails }) => {\n config.loggerProvider.debug(\n `Total storage size: ${totalStorageSize} KB, percentage of quota: ${percentOfQuota}%, usage details: ${usageDetails}`,\n );\n })\n .catch(() => {\n // swallow error\n });\n }\n\n trackDestination.sendEventsList({\n events: events,\n sessionId: sessionId,\n flushMaxRetries: config.flushMaxRetries,\n apiKey: config.apiKey,\n deviceId: deviceId,\n sampleRate: config.sampleRate,\n serverZone: config.serverZone,\n version: config.version,\n type,\n onComplete: async () => {\n await store.cleanUpSessionEventsStore(sessionId, sequenceId);\n return;\n },\n });\n };\n\n const sendCurrentSequenceEvents = ({ sessionId, deviceId }: { sessionId: number; deviceId: string }) => {\n lastKnownDeviceId = deviceId;\n // Evaluate shouldSend synchronously before the async store read. asyncSetSessionId\n // updates sessionStartTime immediately after calling sendEvents(), so by the time\n // storeCurrentSequence resolves the start time would reflect the new session and\n // the elapsed check would compute ~0ms, silently dropping the previous session's events.\n if (shouldSend && !shouldSend()) return;\n // Snapshot the absolute end-index before the async store read so that any events\n // pushed after this point are NOT considered sent and remain in the beacon buffer.\n const snapshotAbsIdx = beaconWindowStart + beaconBuffer.length;\n store\n .storeCurrentSequence(sessionId)\n .then((currentSequence) => {\n if (currentSequence) {\n advanceBeaconWindow(snapshotAbsIdx);\n sendEventsList({\n sequenceId: currentSequence.sequenceId,\n events: currentSequence.events,\n sessionId: currentSequence.sessionId,\n deviceId,\n });\n }\n })\n .catch((e) => {\n config.loggerProvider.warn('Failed to get current sequence of session replay events for session:', e);\n });\n };\n\n const sendStoredEvents = async ({ deviceId }: { deviceId: string }) => {\n lastKnownDeviceId = deviceId;\n const sequencesToSend = await store.getSequencesToSend();\n if (!sequencesToSend?.length) {\n return;\n }\n config.loggerProvider.log(`Draining ${sequencesToSend.length} stored sequence(s) from previous session.`);\n // These persisted sequences are about to be enqueued back-to-back. Without\n // coalescing they flush as N separate POSTs — a request flood on page load. Mark the\n // imminent flush to merge same-identity batches (the enqueues below are synchronous, so\n // the whole backlog lands in the queue before the deferred flush consumes the flag). Skip\n // the single-sequence case where there's nothing to merge.\n if (sequencesToSend.length > 1) {\n trackDestination.markCoalesceNextFlush();\n }\n sequencesToSend.forEach((sequence) => {\n sendEventsList({\n sequenceId: sequence.sequenceId,\n events: sequence.events,\n sessionId: sequence.sessionId,\n deviceId,\n });\n });\n };\n\n const addEvent = ({\n event,\n sessionId,\n deviceId,\n }: {\n event: { type: Type; data: string };\n sessionId: number;\n deviceId: string;\n }) => {\n lastKnownDeviceId = deviceId;\n // Capture shouldSend synchronously before the async store write, for the same\n // reason as sendCurrentSequenceEvents: asyncSetSessionId updates sessionStartTime\n // synchronously, so evaluating inside the .then() would use the new session's\n // start time and compute ~0ms elapsed, silently dropping a valid batch.\n const canSend = !shouldSend || shouldSend();\n // Record the absolute index of this event in the beacon buffer before the async\n // store operation. If a batch split occurs, we advance the window up to (but not\n // including) this event so that it starts the next pending window.\n const absIdx = beaconWindowStart + beaconBuffer.length;\n beaconBuffer.push(event.data);\n store\n .addEventToCurrentSequence(sessionId, event.data)\n .then((sequenceToSend) => {\n if (sequenceToSend) {\n if (!canSend) {\n // The split atomically moved events to sequencesToSend; without cleanup\n // they would be unconditionally replayed on next page load via\n // sendStoredEvents, bypassing the min session duration gate. The split\n // batch must also be dropped from the beacon buffer so it isn't sent\n // via sendBeacon on page unload (potentially attributed to a later session).\n store.cleanUpSessionEventsStore(sequenceToSend.sessionId, sequenceToSend.sequenceId).catch((e) => {\n config.loggerProvider.warn('Failed to clean up dropped session replay sequence:', e);\n });\n advanceBeaconWindow(absIdx);\n return;\n }\n // Events before absIdx belong to the split batch being sent; advance window.\n advanceBeaconWindow(absIdx);\n sendEventsList({\n sequenceId: sequenceToSend.sequenceId,\n events: sequenceToSend.events,\n sessionId: sequenceToSend.sessionId,\n deviceId,\n });\n }\n })\n .catch((e) => {\n config.loggerProvider.warn('Failed to add event to session replay capture:', e);\n });\n };\n\n async function flush(useRetry = false) {\n return trackDestination.flush(useRetry);\n }\n\n const getBeaconEvents = (): string[] => [...beaconBuffer];\n\n const dropPendingBeaconEvents = () => {\n advanceBeaconWindow(beaconWindowStart + beaconBuffer.length);\n };\n\n return {\n sendCurrentSequenceEvents,\n addEvent,\n sendStoredEvents,\n flush,\n getBeaconEvents,\n dropPendingBeaconEvents,\n trackDestination,\n };\n};\n"]}
|
|
@@ -41,7 +41,7 @@ export declare class SessionReplayTrackDestination implements AmplitudeSessionRe
|
|
|
41
41
|
* sequences replayed back-to-back on init via sendStoredEvents). Because those enqueues are
|
|
42
42
|
* synchronous and the flush is deferred to the next tick via schedule(0), the whole backlog
|
|
43
43
|
* lands in the queue before the flag is consumed — collapsing N small POSTs into far fewer
|
|
44
|
-
* and avoiding the request flood observed on page load
|
|
44
|
+
* and avoiding the request flood observed on page load. Steady-state live capture
|
|
45
45
|
* never sets this flag, so its sending behavior is unchanged.
|
|
46
46
|
*
|
|
47
47
|
* Schedules a flush so the flag is always consumed by the next flush, even when every
|
|
@@ -74,7 +74,7 @@ export declare class SessionReplayTrackDestination implements AmplitudeSessionRe
|
|
|
74
74
|
*/
|
|
75
75
|
private mergeQueueAfterThrottle;
|
|
76
76
|
/**
|
|
77
|
-
* Page-load backlog drain path
|
|
77
|
+
* Page-load backlog drain path: on init the SDK replays every persisted sequence
|
|
78
78
|
* from a prior session via sendStoredEvents. Enqueued back-to-back they would flush as N
|
|
79
79
|
* separate POSTs — a request flood on page load that feeds volume spikes and throttling.
|
|
80
80
|
* Reuses the exact same identity-grouped merge as the post-throttle path so the backlog
|
|
@@ -41,7 +41,7 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
41
41
|
// count, so collapsing N queued batches into one POST directly reduces throttle pressure.
|
|
42
42
|
this.mergeOnNextFlush = false;
|
|
43
43
|
// Set by markCoalesceNextFlush() before the page-load backlog is enqueued; consumed by
|
|
44
|
-
// flush() to coalesce the drained persisted sequences
|
|
44
|
+
// flush() to coalesce the drained persisted sequences. Distinct from
|
|
45
45
|
// mergeOnNextFlush so the drain isn't conflated with a throttle pause for logging.
|
|
46
46
|
this.coalesceNextFlush = false;
|
|
47
47
|
// Gates the merge log to once per throttle pause window — mirroring the throttle log's
|
|
@@ -161,7 +161,7 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
161
161
|
* sequences replayed back-to-back on init via sendStoredEvents). Because those enqueues are
|
|
162
162
|
* synchronous and the flush is deferred to the next tick via schedule(0), the whole backlog
|
|
163
163
|
* lands in the queue before the flag is consumed — collapsing N small POSTs into far fewer
|
|
164
|
-
* and avoiding the request flood observed on page load
|
|
164
|
+
* and avoiding the request flood observed on page load. Steady-state live capture
|
|
165
165
|
* never sets this flag, so its sending behavior is unchanged.
|
|
166
166
|
*
|
|
167
167
|
* Schedules a flush so the flag is always consumed by the next flush, even when every
|
|
@@ -352,7 +352,7 @@ var SessionReplayTrackDestination = /** @class */ (function () {
|
|
|
352
352
|
return merged;
|
|
353
353
|
};
|
|
354
354
|
/**
|
|
355
|
-
* Page-load backlog drain path
|
|
355
|
+
* Page-load backlog drain path: on init the SDK replays every persisted sequence
|
|
356
356
|
* from a prior session via sendStoredEvents. Enqueued back-to-back they would flush as N
|
|
357
357
|
* separate POSTs — a request flood on page load that feeds volume spikes and throttling.
|
|
358
358
|
* Reuses the exact same identity-grouped merge as the post-throttle path so the backlog
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track-destination.js","sourceRoot":"","sources":["../../src/track-destination.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseTransport, getGlobalScope, ILogger, ServerZone, Status } from '@amplitude/analytics-core';\nimport { getCurrentUrl, getServerUrl } from './helpers';\nimport {\n MAX_RETRIES_EXCEEDED_MESSAGE,\n MISSING_API_KEY_MESSAGE,\n MISSING_DEVICE_ID_MESSAGE,\n SESSION_KILLED_MESSAGE,\n UNEXPECTED_ERROR_MESSAGE,\n UNEXPECTED_NETWORK_ERROR_MESSAGE,\n} from './messages';\nimport {\n SessionReplayTrackDestination as AmplitudeSessionReplayTrackDestination,\n SessionReplayDestination,\n SessionReplayDestinationContext,\n} from './typings/session-replay';\nimport { VERSION } from './version';\nimport {\n MAX_URL_LENGTH,\n KB_SIZE,\n MAX_KEEPALIVE_BYTES,\n WAF_PAYLOAD_TOO_LARGE_PATTERN,\n EVENT_SKIPPED_HEADER,\n EVENT_SKIP_CODE_THROTTLED,\n EVENT_SKIP_CODE_INVALID_RANGE,\n EVENT_SKIP_CODE_CAPTURE_DISABLED,\n THROTTLED_FLUSH_PAUSE_MS,\n MERGE_AFTER_THROTTLE_SOFT_CAP,\n SEND_TIMEOUT_MS,\n} from './constants';\nimport { gzipJson } from './utils/gzip';\n\ninterface WorkerCompleteMessage {\n type: 'complete';\n id: string;\n err?: string;\n // null when the response was a clean 200 (no skip header), undefined when the\n // request did not produce a 200, otherwise the server's skip-code string.\n skipCode?: string | null;\n}\ninterface WorkerLogMessage {\n type: 'log' | 'warn';\n id: string;\n message: string;\n}\ninterface WorkerPayloadTooLargeMessage {\n type: 'payload_too_large';\n id: string;\n isWaf: boolean;\n}\ntype WorkerMessage = WorkerCompleteMessage | WorkerLogMessage | WorkerPayloadTooLargeMessage;\n\nexport type PayloadBatcher = ({ version, events }: { version: number; events: string[] }) => {\n version: number;\n events: unknown[];\n};\n\n// Bounded so a long-lived SDK instance can't accumulate kill records indefinitely;\n// sessions are time-bounded in practice, this cap is just a defensive ceiling.\nconst MAX_KILLED_SESSIONS = 256;\n\n// Defensive ceiling on retained timed-out worker requests (see timedOutWorkerRequests).\n// In normal operation each entry is removed when the worker's late message arrives; this\n// cap only guards the pathological case of a wedged worker that never replies at all.\nconst MAX_TIMED_OUT_WORKER_REQUESTS = 256;\n\nexport class SessionReplayTrackDestination implements AmplitudeSessionReplayTrackDestination {\n loggerProvider: ILogger;\n storageKey = '';\n trackServerUrl?: string;\n retryTimeout = 1000;\n // Defaults to true (gzip enabled) so existing call sites that don't pass the flag\n // retain pre-flag behavior. The local-config layer also defaults to true; this\n // belt-and-braces default protects direct constructor callers (e.g. tests).\n private enableTransportCompression: boolean;\n // Milliseconds before an in-flight send is aborted; <= 0 disables the abort/timeout.\n // Defaults to SEND_TIMEOUT_MS. Configurable so large slow-but-succeeding uploads aren't\n // killed (and retried) at an over-aggressive default. See config.sendTimeoutMs.\n private sendTimeoutMs: number;\n private scheduled: ReturnType<typeof setTimeout> | null = null;\n payloadBatcher: PayloadBatcher;\n queue: SessionReplayDestinationContext[] = [];\n private worker?: Worker;\n private sendIdCounter = 0;\n private pendingWorkerRequests = new Map<\n string,\n { context: SessionReplayDestinationContext; resolve: () => void; timeout?: ReturnType<typeof setTimeout> }\n >();\n // Requests the main thread stopped awaiting after SEND_TIMEOUT_MS (so the serial flush\n // loop could proceed) but whose worker may still be retrying in the background. We keep\n // the context — bounded, like killedSessions — so a *late* worker complete/payload_too_large\n // can still run completeRequest and settle the store record. Without this the late message\n // is dropped (its id is gone from pendingWorkerRequests), so a successful late delivery would\n // leave the IDB/memory record behind for sendStoredEvents to re-upload as duplicate replay data.\n private timedOutWorkerRequests = new Map<string, SessionReplayDestinationContext>();\n // Server back-pressure state, fed by the X-Session-Replay-Event-Skipped header on 200s.\n // The server uses this header (instead of 4xx) to signal a deliberate no-retry drop so SDKs\n // don't retry-storm. We honor it here by slowing or stopping our flush schedule.\n private flushPauseUntilMs = 0;\n // Set when schedule() defers a flush because we're inside a throttle pause; consumed by\n // flush() to merge same-session contexts before sending. Throttling is enforced by request\n // count, so collapsing N queued batches into one POST directly reduces throttle pressure.\n private mergeOnNextFlush = false;\n // Set by markCoalesceNextFlush() before the page-load backlog is enqueued; consumed by\n // flush() to coalesce the drained persisted sequences (SR-4660). Distinct from\n // mergeOnNextFlush so the drain isn't conflated with a throttle pause for logging.\n private coalesceNextFlush = false;\n // Gates the merge log to once per throttle pause window — mirroring the throttle log's\n // transition-only gating — so a sustained throttle scenario doesn't spam logs every cycle.\n private mergeLogFiredThisPause = false;\n private killedSessions = new Set<string | number>();\n\n constructor({\n trackServerUrl,\n loggerProvider,\n payloadBatcher,\n workerScript,\n enableTransportCompression,\n sendTimeoutMs,\n }: {\n trackServerUrl?: string;\n loggerProvider: ILogger;\n payloadBatcher?: PayloadBatcher;\n workerScript?: string;\n enableTransportCompression?: boolean;\n sendTimeoutMs?: number;\n }) {\n this.loggerProvider = loggerProvider;\n this.payloadBatcher = payloadBatcher ? payloadBatcher : (payload) => payload;\n this.trackServerUrl = trackServerUrl;\n this.enableTransportCompression = enableTransportCompression ?? true;\n this.sendTimeoutMs = sendTimeoutMs ?? SEND_TIMEOUT_MS;\n\n if (workerScript) {\n try {\n const blob = new Blob([workerScript], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(blobUrl);\n worker.onerror = (e) => {\n e.preventDefault();\n loggerProvider.error(\n `Track destination worker failed, falling back to main-thread sending: ${e.message} (${e.filename}:${e.lineno})`,\n );\n worker.terminate();\n this.worker = undefined;\n // Resolve pending promises so flush() doesn't hang. Do NOT call completeRequest\n // here — the events were never delivered, so onComplete must not fire and the\n // IDB/memory store entries must remain intact for recovery by sendStoredEvents.\n for (const [, pending] of this.pendingWorkerRequests) {\n // Cancel the per-request timeout — onerror already settles every pending\n // promise, so leaving the timer armed would fire a spurious timeout warn later.\n if (pending.timeout) clearTimeout(pending.timeout);\n loggerProvider.warn(`Session replay event send failed due to worker crash: ${e.message}`);\n pending.resolve();\n }\n this.pendingWorkerRequests.clear();\n // The worker is gone, so no late completion can arrive for timed-out requests either.\n // Drop the retained contexts to free memory; their store records stay intact (we never\n // completeRequest here) so sendStoredEvents can recover them on next init.\n this.timedOutWorkerRequests.clear();\n };\n worker.onmessage = (e: MessageEvent<WorkerMessage>) => {\n const msg = e.data;\n if (msg.type === 'log') {\n loggerProvider.log(msg.message);\n } else if (msg.type === 'warn') {\n loggerProvider.warn(msg.message);\n } else if (msg.type === 'payload_too_large') {\n const pending = this.pendingWorkerRequests.get(msg.id);\n if (pending) {\n if (pending.timeout) clearTimeout(pending.timeout);\n this.handlePayloadTooLargeResponse(pending.context, msg.isWaf);\n pending.resolve();\n this.pendingWorkerRequests.delete(msg.id);\n } else {\n // Late message for a request the main thread already timed out: the worker still\n // determined the payload was too large, so split-and-retry off the original record.\n const timedOut = this.timedOutWorkerRequests.get(msg.id);\n if (timedOut) {\n this.timedOutWorkerRequests.delete(msg.id);\n this.handlePayloadTooLargeResponse(timedOut, msg.isWaf);\n }\n }\n } else if (msg.type === 'complete') {\n const pending = this.pendingWorkerRequests.get(msg.id);\n if (pending) {\n if (pending.timeout) clearTimeout(pending.timeout);\n if (msg.skipCode !== undefined) {\n this.applyServerDirective(pending.context.sessionId, msg.skipCode);\n }\n this.completeRequest({ context: pending.context });\n pending.resolve();\n this.pendingWorkerRequests.delete(msg.id);\n } else {\n // Late completion for a request the main thread already timed out. The worker's\n // actual outcome (delivered, or retries exhausted) is authoritative, so settle the\n // store record by it rather than leaving it behind for sendStoredEvents to re-upload.\n const timedOut = this.timedOutWorkerRequests.get(msg.id);\n if (timedOut) {\n this.timedOutWorkerRequests.delete(msg.id);\n if (msg.skipCode !== undefined) {\n this.applyServerDirective(timedOut.sessionId, msg.skipCode);\n }\n this.completeRequest({ context: timedOut });\n }\n }\n }\n };\n this.worker = worker;\n } catch (error) {\n loggerProvider.error('Failed to create track destination worker, falling back to main-thread sending:', error);\n }\n }\n }\n\n sendEventsList(destinationData: SessionReplayDestination) {\n this.addToQueue({\n ...destinationData,\n attempts: 0,\n timeout: 0,\n });\n }\n\n /**\n * Marks the next scheduled flush to coalesce its queued contexts by destination identity.\n * Callers use this immediately before enqueuing the page-load backlog drain (many persisted\n * sequences replayed back-to-back on init via sendStoredEvents). Because those enqueues are\n * synchronous and the flush is deferred to the next tick via schedule(0), the whole backlog\n * lands in the queue before the flag is consumed — collapsing N small POSTs into far fewer\n * and avoiding the request flood observed on page load (SR-4660). Steady-state live capture\n * never sets this flag, so its sending behavior is unchanged.\n *\n * Schedules a flush so the flag is always consumed by the next flush, even when every\n * backlog sequence is dropped before reaching the queue (e.g. all events oversized) and no\n * enqueue schedules one itself. Otherwise the flag would stick and a later unrelated live\n * flush could coalesce live batches as if they were a page-load drain.\n */\n markCoalesceNextFlush() {\n this.coalesceNextFlush = true;\n this.schedule(0);\n }\n\n /**\n * Sends events via navigator.sendBeacon on page exit.\n * Beacon payloads are sent as uncompressed JSON because sendBeacon does not support\n * Content-Encoding, and small incremental batches don't benefit much from compression.\n * The full snapshot has already been sent eagerly via fetch, so the beacon only needs\n * to cover the remaining incremental events since the last fetch flush.\n */\n sendBeacon({\n events,\n sessionId,\n deviceId,\n apiKey,\n serverZone,\n }: {\n events: string[];\n sessionId: string | number;\n deviceId: string;\n apiKey: string;\n serverZone?: keyof typeof ServerZone;\n }) {\n const MAX_BEACON_BYTES = 64 * 1024;\n const byteLength = (s: string) => new Blob([s]).size;\n let trimmedEvents = events;\n let payload = JSON.stringify({ version: 2, events: trimmedEvents });\n if (byteLength(payload) > MAX_BEACON_BYTES) {\n // Binary search for the largest prefix that fits within the beacon size limit.\n // Uses Blob.size to get the UTF-8 byte count, which is what sendBeacon measures.\n let lo = 0;\n let hi = trimmedEvents.length;\n while (lo < hi) {\n const mid = Math.floor((lo + hi + 1) / 2);\n if (byteLength(JSON.stringify({ version: 2, events: trimmedEvents.slice(0, mid) })) <= MAX_BEACON_BYTES) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n trimmedEvents = trimmedEvents.slice(0, lo);\n payload = JSON.stringify({ version: 2, events: trimmedEvents });\n this.loggerProvider.warn(\n `sendBeacon payload exceeded 64 KB limit, trimmed from ${events.length} to ${trimmedEvents.length} events`,\n );\n }\n if (trimmedEvents.length === 0) {\n return;\n }\n const urlParams = new URLSearchParams({\n device_id: deviceId,\n session_id: String(sessionId),\n type: 'replay',\n api_key: apiKey,\n });\n const serverUrl = `${getServerUrl(serverZone, this.trackServerUrl)}?${urlParams.toString()}`;\n const globalScope = getGlobalScope();\n try {\n // Wrap in a Blob to set Content-Type: application/json; a plain string would\n // cause the browser to send Content-Type: text/plain, which the server rejects.\n const payloadBlob = new Blob([payload], { type: 'application/json' });\n const sent = globalScope?.navigator?.sendBeacon?.(serverUrl, payloadBlob);\n if (sent === false) {\n this.loggerProvider.warn('sendBeacon failed to queue session replay payload');\n }\n } catch {\n // Best effort — no fallback on page exit.\n }\n }\n\n addToQueue(...list: SessionReplayDestinationContext[]) {\n const tryable = list.filter((context) => {\n if (this.killedSessions.has(context.sessionId)) {\n // Server has signaled capture_disabled or session_in_invalid_range for this session;\n // drop the batch (and clean up its IDB record via onComplete) instead of POSTing.\n this.completeRequest({\n context,\n err: SESSION_KILLED_MESSAGE,\n });\n return false;\n }\n if (context.attempts < (context.flushMaxRetries || 0)) {\n context.attempts += 1;\n return true;\n }\n this.completeRequest({\n context,\n err: MAX_RETRIES_EXCEEDED_MESSAGE,\n });\n return false;\n });\n tryable.forEach((context) => {\n this.queue = this.queue.concat(context);\n this.schedule(0);\n });\n }\n\n schedule(timeout: number) {\n if (this.scheduled) return;\n // If the server signaled throttling on a recent 200, defer the next flush until the\n // pause window ends. This lets us keep batching events without retry-storming the server.\n const pauseRemaining = this.flushPauseUntilMs - Date.now();\n const isPaused = pauseRemaining > 0;\n const effectiveTimeout = pauseRemaining > timeout ? pauseRemaining : timeout;\n if (isPaused) {\n // Mark the upcoming flush for merge: contexts piling up during the pause should\n // be coalesced into one POST per (session, device, api, type, ...) group.\n this.mergeOnNextFlush = true;\n }\n this.scheduled = setTimeout(() => {\n void this.flush(true).then(() => {\n if (this.queue.length > 0) {\n this.schedule(timeout);\n }\n });\n }, effectiveTimeout);\n }\n\n async flush(useRetry = false) {\n let list = this.queue;\n this.queue = [];\n\n if (this.scheduled) {\n clearTimeout(this.scheduled);\n this.scheduled = null;\n }\n\n if (this.mergeOnNextFlush) {\n this.mergeOnNextFlush = false;\n // A throttle merge already coalesces by identity; clear the drain flag too so the\n // same backlog isn't passed through a redundant second merge on a later flush.\n this.coalesceNextFlush = false;\n list = this.mergeQueueAfterThrottle(list);\n } else if (this.coalesceNextFlush) {\n this.coalesceNextFlush = false;\n list = this.mergeDrainBacklog(list);\n }\n\n for (const context of list) {\n await this.send(context, useRetry);\n }\n }\n\n /**\n * Post-throttle release path: coalesce the queued contexts, then log once per pause window.\n * Delegates the actual merging to the shared coalesceByIdentity helper so the drain path\n * (mergeDrainBacklog) and this path stay byte-for-byte identical in how they merge.\n */\n private mergeQueueAfterThrottle(list: SessionReplayDestinationContext[]): SessionReplayDestinationContext[] {\n const merged = this.coalesceByIdentity(list);\n if (merged.length < list.length && !this.mergeLogFiredThisPause) {\n this.mergeLogFiredThisPause = true;\n this.loggerProvider.log(\n `Session replay throttle pause ended; merged ${list.length} queued batches into ${merged.length} request(s)`,\n );\n }\n return merged;\n }\n\n /**\n * Page-load backlog drain path (SR-4660): on init the SDK replays every persisted sequence\n * from a prior session via sendStoredEvents. Enqueued back-to-back they would flush as N\n * separate POSTs — a request flood on page load that feeds volume spikes and throttling.\n * Reuses the exact same identity-grouped merge as the post-throttle path so the backlog\n * collapses into far fewer requests, with onComplete fanned out so each source IDB record\n * is still cleaned up exactly once on success.\n */\n private mergeDrainBacklog(list: SessionReplayDestinationContext[]): SessionReplayDestinationContext[] {\n const merged = this.coalesceByIdentity(list);\n if (merged.length < list.length) {\n this.loggerProvider.log(\n `Session replay coalesced ${list.length} persisted page-load backlog batches into ${merged.length} request(s)`,\n );\n }\n return merged;\n }\n\n /**\n * Coalesces queued contexts that share the same destination identity into fewer requests.\n * Identity covers everything that affects the request URL, routing, or per-request semantics\n * — splitting on any difference keeps each merged POST indistinguishable from the source\n * contexts it replaced.\n *\n * Greedy concat with a soft byte-length cap (`MERGE_AFTER_THROTTLE_SOFT_CAP`) keeps merged\n * payloads well under the 413 ceiling; on the rare oversized merge, the existing\n * split-and-retry path still bisects safely.\n *\n * The merged context's `onComplete` fans out to every source context's callback so each\n * underlying IDB sequence record is cleaned up exactly once on success.\n */\n private coalesceByIdentity(list: SessionReplayDestinationContext[]): SessionReplayDestinationContext[] {\n if (list.length <= 1) return list;\n\n const groups = new Map<string, SessionReplayDestinationContext[]>();\n for (const ctx of list) {\n // Anything that can change the URL, headers, or backend routing must split groups.\n const key = [\n ctx.sessionId,\n ctx.deviceId ?? '',\n ctx.apiKey ?? '',\n ctx.type,\n ctx.serverZone ?? '',\n ctx.sampleRate,\n ctx.version?.type ?? '',\n ctx.version?.version ?? '',\n ].join('|');\n const arr = groups.get(key);\n if (arr) arr.push(ctx);\n else groups.set(key, [ctx]);\n }\n\n const merged: SessionReplayDestinationContext[] = [];\n for (const group of groups.values()) {\n if (group.length === 1) {\n merged.push(group[0]);\n continue;\n }\n let current: SessionReplayDestinationContext | null = null;\n let currentBytes = 0;\n const flushCurrent = () => {\n if (current) merged.push(current);\n current = null;\n currentBytes = 0;\n };\n for (const ctx of group) {\n // UTF-8 byte size, matching how the events store enforces MAX_EVENT_LIST_SIZE\n // (see base-events-store.ts:getStringSize). Using char length would let a CJK/\n // emoji-heavy payload sneak past the cap.\n const ctxBytes = ctx.events.reduce((sum, e) => sum + new Blob([e]).size, 0);\n if (current === null) {\n // Reset attempts to 0 on the merged context so the post-throttle delivery gets a\n // full retry budget. The throttle pause has already absorbed back-pressure; the\n // alternative (Math.max of source attempts) would collapse N source budgets into\n // one and end-of-life all N IDB records on a single retry exhaustion.\n current = { ...ctx, events: [...ctx.events], attempts: 0 };\n currentBytes = ctxBytes;\n continue;\n }\n if (currentBytes + ctxBytes > MERGE_AFTER_THROTTLE_SOFT_CAP) {\n flushCurrent();\n current = { ...ctx, events: [...ctx.events], attempts: 0 };\n currentBytes = ctxBytes;\n continue;\n }\n const prevOnComplete = current.onComplete;\n const ctxOnComplete = ctx.onComplete;\n current.events = current.events.concat(ctx.events);\n currentBytes += ctxBytes;\n current.onComplete = async () => {\n // allSettled (not all): an underlying store cleanup failure in one shouldn't\n // block the other, and the merged onComplete is invoked fire-and-forget via\n // `void context.onComplete()` — a rejection from `Promise.all` would surface\n // as an unhandled rejection. Errors stay encapsulated in the source callbacks.\n await Promise.allSettled([prevOnComplete(), ctxOnComplete()]);\n };\n }\n flushCurrent();\n }\n\n return merged;\n }\n\n async send(context: SessionReplayDestinationContext, useRetry = true) {\n // A kill directive can arrive between flush() snapshotting the queue and us reaching\n // each context. Re-check before hitting the network so we don't waste POSTs on a\n // session the server has already told us to stop sending for.\n if (this.killedSessions.has(context.sessionId)) {\n return this.completeRequest({ context, err: SESSION_KILLED_MESSAGE });\n }\n const apiKey = context.apiKey;\n if (!apiKey) {\n return this.completeRequest({ context, err: MISSING_API_KEY_MESSAGE });\n }\n const deviceId = context.deviceId;\n if (!deviceId) {\n return this.completeRequest({ context, err: MISSING_DEVICE_ID_MESSAGE });\n }\n\n const payload = this.payloadBatcher({\n version: 1,\n events: context.events,\n });\n\n if (payload.events.length === 0) {\n this.completeRequest({ context });\n return;\n }\n\n const { worker } = this;\n if (worker) {\n return this.sendViaWorker(worker, context, payload, useRetry);\n }\n\n return this.sendOnMainThread(apiKey, deviceId, context, payload, useRetry);\n }\n\n private async sendViaWorker(\n worker: Worker,\n context: SessionReplayDestinationContext,\n payload: { version: number; events: unknown[] },\n useRetry: boolean,\n ): Promise<void> {\n const id = `${++this.sendIdCounter}`;\n return new Promise<void>((resolve) => {\n // The worker only resolves this promise when it posts back complete/payload_too_large.\n // If the worker's own fetch hangs, no message ever arrives, so this promise — and the\n // serial flush loop awaiting it — would hang forever while pendingWorkerRequests grows\n // unbounded. On timeout we resolve so flush() proceeds, but deliberately do NOT call\n // completeRequest: like the worker-crash path above, the events were never confirmed\n // delivered, so onComplete must not fire and the IDB/memory store must stay intact for\n // recovery by sendStoredEvents.\n // sendTimeoutMs <= 0 disables the wait timer entirely: we then rely solely on the\n // worker's own complete/payload_too_large message to settle. This reintroduces the\n // hang risk this timer guards against, so it is an explicit experiment opt-out.\n const timeout =\n this.sendTimeoutMs > 0\n ? setTimeout(() => {\n const pending = this.pendingWorkerRequests.get(id);\n if (!pending) return;\n this.pendingWorkerRequests.delete(id);\n // Retain the context so a *late* worker complete/payload_too_large can still settle the\n // store record (see timedOutWorkerRequests). Without this, a worker that ultimately\n // delivers after we stopped awaiting would leave the record behind → duplicate upload.\n this.rememberTimedOutRequest(id, pending.context);\n this.loggerProvider.warn(\n `Session replay worker send timed out after ${this.sendTimeoutMs}ms; leaving events for retry`,\n );\n pending.resolve();\n }, this.sendTimeoutMs)\n : undefined;\n this.pendingWorkerRequests.set(id, { context, resolve, timeout });\n worker.postMessage({\n type: 'send',\n id,\n payload,\n useRetry,\n context: {\n apiKey: context.apiKey,\n deviceId: context.deviceId,\n sessionId: context.sessionId,\n events: context.events,\n eventType: context.type,\n flushMaxRetries: context.flushMaxRetries ?? 0,\n sampleRate: context.sampleRate,\n serverZone: context.serverZone,\n trackServerUrl: this.trackServerUrl,\n version: context.version,\n currentUrl: getCurrentUrl(),\n sdkVersion: VERSION,\n enableTransportCompression: this.enableTransportCompression,\n sendTimeoutMs: this.sendTimeoutMs,\n },\n });\n });\n }\n\n private rememberTimedOutRequest(id: string, context: SessionReplayDestinationContext) {\n this.timedOutWorkerRequests.set(id, context);\n // Bound memory: a wedged worker that never replies must not let this grow without limit.\n // Map preserves insertion order, so deleting the first key evicts the oldest entry.\n if (this.timedOutWorkerRequests.size > MAX_TIMED_OUT_WORKER_REQUESTS) {\n for (const oldest of this.timedOutWorkerRequests.keys()) {\n this.timedOutWorkerRequests.delete(oldest);\n break;\n }\n }\n }\n\n private async sendOnMainThread(\n apiKey: string,\n deviceId: string,\n context: SessionReplayDestinationContext,\n payload: { version: number; events: unknown[] },\n useRetry: boolean,\n ): Promise<void> {\n const url = getCurrentUrl();\n const version = VERSION;\n const sampleRate = context.sampleRate;\n const urlParams = new URLSearchParams({\n device_id: deviceId,\n session_id: `${context.sessionId}`,\n type: `${context.type}`,\n });\n const sessionReplayLibrary = `${context.version?.type ?? 'standalone'}/${context.version?.version ?? version}`;\n\n try {\n const payloadJson = JSON.stringify(payload);\n // Only await gzip when (a) the customer hasn't opted out and (b) CompressionStream\n // is actually available; skipping the await entirely preserves the synchronous\n // fast-path for browsers/environments (e.g. Jest) that don't support it, keeping\n // retry-timing tests unaffected.\n const globalScope = getGlobalScope();\n const gzipped =\n this.enableTransportCompression && globalScope && 'CompressionStream' in globalScope\n ? await gzipJson(payloadJson, globalScope)\n : null;\n const payloadSize = gzipped ? gzipped.byteLength : new Blob([payloadJson]).size;\n // fetch() has no native timeout. A request stuck \"pending\" forever would block the\n // serial flush loop indefinitely (head-of-line blocking), so we abort it after\n // SEND_TIMEOUT_MS. The abort surfaces as an AbortError in the catch below, where it's\n // routed as a retryable network failure when useRetry is true.\n const controller = new AbortController();\n const options: RequestInit = {\n headers: {\n 'Content-Type': 'application/json',\n Accept: '*/*',\n Authorization: `Bearer ${apiKey}`,\n 'X-Client-Version': version,\n 'X-Client-Library': sessionReplayLibrary,\n 'X-Client-Url': url.substring(0, MAX_URL_LENGTH), // limit url length to 1000 characters to avoid ELB 400 error\n 'X-Client-Sample-Rate': `${sampleRate}`,\n 'X-Sampling-Hash-Alg': 'xxhash32',\n ...(gzipped ? { 'Content-Encoding': 'gzip' } : {}),\n },\n body: (gzipped ?? payloadJson) as BodyInit,\n method: 'POST',\n // keepalive lets the request survive page navigation, preventing 499 (client-closed) errors.\n // Must stay under the browser's 64 KB keepalive budget; large payloads skip it.\n keepalive: payloadSize <= MAX_KEEPALIVE_BYTES,\n signal: controller.signal,\n };\n\n const serverUrl = `${getServerUrl(context.serverZone, this.trackServerUrl)}?${urlParams.toString()}`;\n // Final defensive guard: never POST a zero-event payload. Upper layers (events-manager\n // oversize filter, send()'s post-batcher check, store-layer filters) should already\n // have caught this — but SR-4284 fleet logs show ~416 empty-body 400s/24h slipping\n // through somehow, so a cheap belt-and-braces check immediately before fetch prevents\n // any future regression from re-introducing the same server rejection.\n if (payload.events.length === 0) {\n this.completeRequest({ context });\n return;\n }\n // sendTimeoutMs <= 0 disables the abort: the request can then hang indefinitely\n // (head-of-line blocking the serial flush). Explicit experiment opt-out only.\n const sendTimeout =\n this.sendTimeoutMs > 0\n ? setTimeout(() => {\n controller.abort();\n }, this.sendTimeoutMs)\n : undefined;\n let res: Response;\n try {\n res = await fetch(serverUrl, options);\n } finally {\n // Clear on success and on error alike so a settled request never leaves an armed\n // timer that would abort a later reused controller or fire a stray callback.\n if (sendTimeout) clearTimeout(sendTimeout);\n }\n if (res === null) {\n this.completeRequest({ context, err: UNEXPECTED_ERROR_MESSAGE });\n return;\n }\n if (res.status >= 200 && res.status < 300) {\n const skipCode = res.headers?.get?.(EVENT_SKIPPED_HEADER) ?? null;\n this.applyServerDirective(context.sessionId, skipCode);\n }\n if (!useRetry) {\n let responseBody = '';\n try {\n responseBody = JSON.stringify(res.body, null, 2);\n } catch {\n // to avoid crash, but don't care about the error, add comment to avoid empty block lint error\n }\n this.completeRequest({ context, success: `${res.status}: ${responseBody}` });\n } else {\n let responseBody = '';\n if (res.status === 413) {\n try {\n responseBody = await res.text();\n } catch {\n // best effort\n }\n }\n await this.handleReponse(res.status, context, responseBody);\n }\n } catch (e) {\n // A send timeout aborts the fetch, which rejects with an AbortError. Treat that as a\n // transient network failure and route it through the same retry budget/backoff as a\n // 5xx (so a single stalled request doesn't permanently drop the batch) when retries\n // are enabled. completeRequest fires onComplete exactly once via either branch\n // (handleOtherResponse only completes on retry exhaustion), so onComplete can't fire\n // twice. Non-abort errors keep the original complete-with-error behavior.\n // Browsers reject an aborted fetch with a DOMException named 'AbortError', which is NOT an\n // Error instance — an `instanceof Error` check would misroute every send-timeout abort to\n // the fatal completeRequest path, defeating the retry. Match on the name across any thrown\n // object (DOMException or Error) instead.\n const isAbort = !!e && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError';\n if (isAbort && useRetry) {\n await this.handleOtherResponse(context);\n } else {\n this.completeRequest({ context, err: e as string });\n }\n }\n }\n\n async handleReponse(status: number, context: SessionReplayDestinationContext, responseBody = '') {\n const parsedStatus = new BaseTransport().buildStatus(status);\n switch (parsedStatus) {\n case Status.Success:\n this.handleSuccessResponse(context);\n break;\n case Status.Failed:\n case Status.Timeout: // 408: server timed out waiting for request, data not received\n case Status.RateLimit: // 429: retry with existing backoff rather than silently dropping\n await this.handleOtherResponse(context);\n break;\n case Status.PayloadTooLarge:\n this.handlePayloadTooLargeResponse(context, WAF_PAYLOAD_TOO_LARGE_PATTERN.test(responseBody));\n break;\n default:\n // 499 (client closed connection / upstream dropped) is also retryable\n if (status === 499) {\n await this.handleOtherResponse(context);\n break;\n }\n this.completeRequest({ context, err: UNEXPECTED_NETWORK_ERROR_MESSAGE });\n }\n }\n\n handlePayloadTooLargeResponse(context: SessionReplayDestinationContext, isWaf: boolean): void {\n const source = isWaf ? 'WAF (compressed payload too large)' : 'server (event too large)';\n const totalSizeKB = Math.round(context.events.reduce((sum, e) => sum + e.length, 0) / KB_SIZE);\n\n if (!isWaf) {\n this.completeRequest({\n context,\n err: `Session replay event batch dropped: ${source} rejected payload (${context.events.length} events, ${totalSizeKB} KB) — not retrying non-WAF 413`,\n });\n return;\n }\n\n if (context.events.length === 1) {\n this.completeRequest({\n context,\n err: `Session replay event dropped: single event (${totalSizeKB} KB, 1 event) rejected by ${source} — cannot split further`,\n });\n return;\n }\n\n this.loggerProvider.warn(\n `Session replay event batch rejected by ${source} (${context.events.length} events, ${totalSizeKB} KB total) — splitting and retrying`,\n );\n\n // Clean up the original IDB record, then re-enqueue both halves as new in-memory batches.\n // For a merged-on-throttle context (mergeQueueAfterThrottle), this onComplete is the\n // fanned-out callback covering N source IDB records — they'll all be cleaned up here.\n // Halves get noop onCompletes, so a page-close between this cleanup and a half delivery\n // means up to N source sequences are lost. The merge soft cap (1.4MB chars) is well under\n // the 10MB compressed 413 ceiling, so a 413 on a merged context is exceedingly rare in\n // practice; the alternative — deferring source cleanup until both halves complete — would\n // significantly complicate the retry path for marginal benefit.\n void context.onComplete();\n const noop = (): Promise<void> => Promise.resolve();\n const mid = Math.floor(context.events.length / 2);\n this.sendEventsList({ ...context, events: context.events.slice(0, mid), onComplete: noop });\n this.sendEventsList({ ...context, events: context.events.slice(mid), onComplete: noop });\n }\n\n handleSuccessResponse(context: SessionReplayDestinationContext) {\n const sizeOfEventsList = Math.round(new Blob(context.events).size / KB_SIZE);\n this.completeRequest({\n context,\n success: `Session replay event batch tracked successfully for session id ${context.sessionId}, size of events: ${sizeOfEventsList} KB`,\n });\n }\n\n async handleOtherResponse(context: SessionReplayDestinationContext) {\n const delay = Math.random() * context.attempts * this.retryTimeout;\n context.attempts++;\n if (context.attempts > (context.flushMaxRetries || 0)) {\n this.completeRequest({ context, err: MAX_RETRIES_EXCEEDED_MESSAGE });\n return;\n }\n await new Promise<void>((resolve) => setTimeout(resolve, delay));\n await this.send(context, true);\n }\n\n completeRequest({\n context,\n err,\n success,\n }: {\n context: SessionReplayDestinationContext;\n err?: string;\n success?: string;\n }) {\n void context.onComplete();\n if (err) {\n this.loggerProvider.warn(err);\n } else if (success) {\n this.loggerProvider.log(success);\n }\n }\n\n /**\n * Applies the server's back-pressure signal carried on a 200 response.\n *\n * - `EVENT_SKIP_CODE_THROTTLED` (server-side rate limit): pause the flush schedule\n * for `THROTTLED_FLUSH_PAUSE_MS` so we keep batching events instead of retry-storming.\n * - `EVENT_SKIP_CODE_CAPTURE_DISABLED` / `EVENT_SKIP_CODE_INVALID_RANGE`: hard kill\n * switch for this session — drop the queued contexts and stop accepting new ones.\n * New sessions are unaffected.\n * - `null` (clean 200, no header): clear any throttle pause; subsequent flushes resume\n * on the normal cadence.\n */\n private applyServerDirective(sessionId: string | number, skipCode: string | null) {\n if (skipCode === null) {\n this.flushPauseUntilMs = 0;\n this.mergeLogFiredThisPause = false;\n return;\n }\n if (skipCode === EVENT_SKIP_CODE_THROTTLED) {\n const wasInPause = this.flushPauseUntilMs > Date.now();\n this.flushPauseUntilMs = Date.now() + THROTTLED_FLUSH_PAUSE_MS;\n // Log only on pause-state transitions — a throttled server may reply to many\n // batches per minute, and one log per batch would flood the console.\n if (!wasInPause) {\n this.loggerProvider.log(\n `Session replay throttled by server; pausing flush schedule for ${THROTTLED_FLUSH_PAUSE_MS / 1000}s`,\n );\n }\n return;\n }\n if (skipCode === EVENT_SKIP_CODE_CAPTURE_DISABLED || skipCode === EVENT_SKIP_CODE_INVALID_RANGE) {\n this.killSession(sessionId, skipCode);\n }\n // Unknown skip codes are ignored — the server may add new ones, and our default\n // behavior (treat as a normal 200) preserves throughput rather than penalizing the\n // session for a code we don't recognize.\n }\n\n private killSession(sessionId: string | number, skipCode: string) {\n if (this.killedSessions.has(sessionId)) return;\n this.killedSessions.add(sessionId);\n // Set preserves insertion order, so deleting the first key evicts the oldest entry.\n if (this.killedSessions.size > MAX_KILLED_SESSIONS) {\n for (const oldest of this.killedSessions) {\n this.killedSessions.delete(oldest);\n break;\n }\n }\n this.loggerProvider.log(\n `Session replay capture stopped for session ${sessionId} by server directive ${skipCode}; remaining events will be dropped`,\n );\n // Drain any queued contexts for this session so their IDB records get cleaned up\n // via onComplete, instead of sitting in the queue waiting for a flush we'll never make.\n const remaining: SessionReplayDestinationContext[] = [];\n for (const queued of this.queue) {\n if (queued.sessionId === sessionId) {\n this.completeRequest({ context: queued, err: SESSION_KILLED_MESSAGE });\n } else {\n remaining.push(queued);\n }\n }\n this.queue = remaining;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"track-destination.js","sourceRoot":"","sources":["../../src/track-destination.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseTransport, getGlobalScope, ILogger, ServerZone, Status } from '@amplitude/analytics-core';\nimport { getCurrentUrl, getServerUrl } from './helpers';\nimport {\n MAX_RETRIES_EXCEEDED_MESSAGE,\n MISSING_API_KEY_MESSAGE,\n MISSING_DEVICE_ID_MESSAGE,\n SESSION_KILLED_MESSAGE,\n UNEXPECTED_ERROR_MESSAGE,\n UNEXPECTED_NETWORK_ERROR_MESSAGE,\n} from './messages';\nimport {\n SessionReplayTrackDestination as AmplitudeSessionReplayTrackDestination,\n SessionReplayDestination,\n SessionReplayDestinationContext,\n} from './typings/session-replay';\nimport { VERSION } from './version';\nimport {\n MAX_URL_LENGTH,\n KB_SIZE,\n MAX_KEEPALIVE_BYTES,\n WAF_PAYLOAD_TOO_LARGE_PATTERN,\n EVENT_SKIPPED_HEADER,\n EVENT_SKIP_CODE_THROTTLED,\n EVENT_SKIP_CODE_INVALID_RANGE,\n EVENT_SKIP_CODE_CAPTURE_DISABLED,\n THROTTLED_FLUSH_PAUSE_MS,\n MERGE_AFTER_THROTTLE_SOFT_CAP,\n SEND_TIMEOUT_MS,\n} from './constants';\nimport { gzipJson } from './utils/gzip';\n\ninterface WorkerCompleteMessage {\n type: 'complete';\n id: string;\n err?: string;\n // null when the response was a clean 200 (no skip header), undefined when the\n // request did not produce a 200, otherwise the server's skip-code string.\n skipCode?: string | null;\n}\ninterface WorkerLogMessage {\n type: 'log' | 'warn';\n id: string;\n message: string;\n}\ninterface WorkerPayloadTooLargeMessage {\n type: 'payload_too_large';\n id: string;\n isWaf: boolean;\n}\ntype WorkerMessage = WorkerCompleteMessage | WorkerLogMessage | WorkerPayloadTooLargeMessage;\n\nexport type PayloadBatcher = ({ version, events }: { version: number; events: string[] }) => {\n version: number;\n events: unknown[];\n};\n\n// Bounded so a long-lived SDK instance can't accumulate kill records indefinitely;\n// sessions are time-bounded in practice, this cap is just a defensive ceiling.\nconst MAX_KILLED_SESSIONS = 256;\n\n// Defensive ceiling on retained timed-out worker requests (see timedOutWorkerRequests).\n// In normal operation each entry is removed when the worker's late message arrives; this\n// cap only guards the pathological case of a wedged worker that never replies at all.\nconst MAX_TIMED_OUT_WORKER_REQUESTS = 256;\n\nexport class SessionReplayTrackDestination implements AmplitudeSessionReplayTrackDestination {\n loggerProvider: ILogger;\n storageKey = '';\n trackServerUrl?: string;\n retryTimeout = 1000;\n // Defaults to true (gzip enabled) so existing call sites that don't pass the flag\n // retain pre-flag behavior. The local-config layer also defaults to true; this\n // belt-and-braces default protects direct constructor callers (e.g. tests).\n private enableTransportCompression: boolean;\n // Milliseconds before an in-flight send is aborted; <= 0 disables the abort/timeout.\n // Defaults to SEND_TIMEOUT_MS. Configurable so large slow-but-succeeding uploads aren't\n // killed (and retried) at an over-aggressive default. See config.sendTimeoutMs.\n private sendTimeoutMs: number;\n private scheduled: ReturnType<typeof setTimeout> | null = null;\n payloadBatcher: PayloadBatcher;\n queue: SessionReplayDestinationContext[] = [];\n private worker?: Worker;\n private sendIdCounter = 0;\n private pendingWorkerRequests = new Map<\n string,\n { context: SessionReplayDestinationContext; resolve: () => void; timeout?: ReturnType<typeof setTimeout> }\n >();\n // Requests the main thread stopped awaiting after SEND_TIMEOUT_MS (so the serial flush\n // loop could proceed) but whose worker may still be retrying in the background. We keep\n // the context — bounded, like killedSessions — so a *late* worker complete/payload_too_large\n // can still run completeRequest and settle the store record. Without this the late message\n // is dropped (its id is gone from pendingWorkerRequests), so a successful late delivery would\n // leave the IDB/memory record behind for sendStoredEvents to re-upload as duplicate replay data.\n private timedOutWorkerRequests = new Map<string, SessionReplayDestinationContext>();\n // Server back-pressure state, fed by the X-Session-Replay-Event-Skipped header on 200s.\n // The server uses this header (instead of 4xx) to signal a deliberate no-retry drop so SDKs\n // don't retry-storm. We honor it here by slowing or stopping our flush schedule.\n private flushPauseUntilMs = 0;\n // Set when schedule() defers a flush because we're inside a throttle pause; consumed by\n // flush() to merge same-session contexts before sending. Throttling is enforced by request\n // count, so collapsing N queued batches into one POST directly reduces throttle pressure.\n private mergeOnNextFlush = false;\n // Set by markCoalesceNextFlush() before the page-load backlog is enqueued; consumed by\n // flush() to coalesce the drained persisted sequences. Distinct from\n // mergeOnNextFlush so the drain isn't conflated with a throttle pause for logging.\n private coalesceNextFlush = false;\n // Gates the merge log to once per throttle pause window — mirroring the throttle log's\n // transition-only gating — so a sustained throttle scenario doesn't spam logs every cycle.\n private mergeLogFiredThisPause = false;\n private killedSessions = new Set<string | number>();\n\n constructor({\n trackServerUrl,\n loggerProvider,\n payloadBatcher,\n workerScript,\n enableTransportCompression,\n sendTimeoutMs,\n }: {\n trackServerUrl?: string;\n loggerProvider: ILogger;\n payloadBatcher?: PayloadBatcher;\n workerScript?: string;\n enableTransportCompression?: boolean;\n sendTimeoutMs?: number;\n }) {\n this.loggerProvider = loggerProvider;\n this.payloadBatcher = payloadBatcher ? payloadBatcher : (payload) => payload;\n this.trackServerUrl = trackServerUrl;\n this.enableTransportCompression = enableTransportCompression ?? true;\n this.sendTimeoutMs = sendTimeoutMs ?? SEND_TIMEOUT_MS;\n\n if (workerScript) {\n try {\n const blob = new Blob([workerScript], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(blobUrl);\n worker.onerror = (e) => {\n e.preventDefault();\n loggerProvider.error(\n `Track destination worker failed, falling back to main-thread sending: ${e.message} (${e.filename}:${e.lineno})`,\n );\n worker.terminate();\n this.worker = undefined;\n // Resolve pending promises so flush() doesn't hang. Do NOT call completeRequest\n // here — the events were never delivered, so onComplete must not fire and the\n // IDB/memory store entries must remain intact for recovery by sendStoredEvents.\n for (const [, pending] of this.pendingWorkerRequests) {\n // Cancel the per-request timeout — onerror already settles every pending\n // promise, so leaving the timer armed would fire a spurious timeout warn later.\n if (pending.timeout) clearTimeout(pending.timeout);\n loggerProvider.warn(`Session replay event send failed due to worker crash: ${e.message}`);\n pending.resolve();\n }\n this.pendingWorkerRequests.clear();\n // The worker is gone, so no late completion can arrive for timed-out requests either.\n // Drop the retained contexts to free memory; their store records stay intact (we never\n // completeRequest here) so sendStoredEvents can recover them on next init.\n this.timedOutWorkerRequests.clear();\n };\n worker.onmessage = (e: MessageEvent<WorkerMessage>) => {\n const msg = e.data;\n if (msg.type === 'log') {\n loggerProvider.log(msg.message);\n } else if (msg.type === 'warn') {\n loggerProvider.warn(msg.message);\n } else if (msg.type === 'payload_too_large') {\n const pending = this.pendingWorkerRequests.get(msg.id);\n if (pending) {\n if (pending.timeout) clearTimeout(pending.timeout);\n this.handlePayloadTooLargeResponse(pending.context, msg.isWaf);\n pending.resolve();\n this.pendingWorkerRequests.delete(msg.id);\n } else {\n // Late message for a request the main thread already timed out: the worker still\n // determined the payload was too large, so split-and-retry off the original record.\n const timedOut = this.timedOutWorkerRequests.get(msg.id);\n if (timedOut) {\n this.timedOutWorkerRequests.delete(msg.id);\n this.handlePayloadTooLargeResponse(timedOut, msg.isWaf);\n }\n }\n } else if (msg.type === 'complete') {\n const pending = this.pendingWorkerRequests.get(msg.id);\n if (pending) {\n if (pending.timeout) clearTimeout(pending.timeout);\n if (msg.skipCode !== undefined) {\n this.applyServerDirective(pending.context.sessionId, msg.skipCode);\n }\n this.completeRequest({ context: pending.context });\n pending.resolve();\n this.pendingWorkerRequests.delete(msg.id);\n } else {\n // Late completion for a request the main thread already timed out. The worker's\n // actual outcome (delivered, or retries exhausted) is authoritative, so settle the\n // store record by it rather than leaving it behind for sendStoredEvents to re-upload.\n const timedOut = this.timedOutWorkerRequests.get(msg.id);\n if (timedOut) {\n this.timedOutWorkerRequests.delete(msg.id);\n if (msg.skipCode !== undefined) {\n this.applyServerDirective(timedOut.sessionId, msg.skipCode);\n }\n this.completeRequest({ context: timedOut });\n }\n }\n }\n };\n this.worker = worker;\n } catch (error) {\n loggerProvider.error('Failed to create track destination worker, falling back to main-thread sending:', error);\n }\n }\n }\n\n sendEventsList(destinationData: SessionReplayDestination) {\n this.addToQueue({\n ...destinationData,\n attempts: 0,\n timeout: 0,\n });\n }\n\n /**\n * Marks the next scheduled flush to coalesce its queued contexts by destination identity.\n * Callers use this immediately before enqueuing the page-load backlog drain (many persisted\n * sequences replayed back-to-back on init via sendStoredEvents). Because those enqueues are\n * synchronous and the flush is deferred to the next tick via schedule(0), the whole backlog\n * lands in the queue before the flag is consumed — collapsing N small POSTs into far fewer\n * and avoiding the request flood observed on page load. Steady-state live capture\n * never sets this flag, so its sending behavior is unchanged.\n *\n * Schedules a flush so the flag is always consumed by the next flush, even when every\n * backlog sequence is dropped before reaching the queue (e.g. all events oversized) and no\n * enqueue schedules one itself. Otherwise the flag would stick and a later unrelated live\n * flush could coalesce live batches as if they were a page-load drain.\n */\n markCoalesceNextFlush() {\n this.coalesceNextFlush = true;\n this.schedule(0);\n }\n\n /**\n * Sends events via navigator.sendBeacon on page exit.\n * Beacon payloads are sent as uncompressed JSON because sendBeacon does not support\n * Content-Encoding, and small incremental batches don't benefit much from compression.\n * The full snapshot has already been sent eagerly via fetch, so the beacon only needs\n * to cover the remaining incremental events since the last fetch flush.\n */\n sendBeacon({\n events,\n sessionId,\n deviceId,\n apiKey,\n serverZone,\n }: {\n events: string[];\n sessionId: string | number;\n deviceId: string;\n apiKey: string;\n serverZone?: keyof typeof ServerZone;\n }) {\n const MAX_BEACON_BYTES = 64 * 1024;\n const byteLength = (s: string) => new Blob([s]).size;\n let trimmedEvents = events;\n let payload = JSON.stringify({ version: 2, events: trimmedEvents });\n if (byteLength(payload) > MAX_BEACON_BYTES) {\n // Binary search for the largest prefix that fits within the beacon size limit.\n // Uses Blob.size to get the UTF-8 byte count, which is what sendBeacon measures.\n let lo = 0;\n let hi = trimmedEvents.length;\n while (lo < hi) {\n const mid = Math.floor((lo + hi + 1) / 2);\n if (byteLength(JSON.stringify({ version: 2, events: trimmedEvents.slice(0, mid) })) <= MAX_BEACON_BYTES) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n trimmedEvents = trimmedEvents.slice(0, lo);\n payload = JSON.stringify({ version: 2, events: trimmedEvents });\n this.loggerProvider.warn(\n `sendBeacon payload exceeded 64 KB limit, trimmed from ${events.length} to ${trimmedEvents.length} events`,\n );\n }\n if (trimmedEvents.length === 0) {\n return;\n }\n const urlParams = new URLSearchParams({\n device_id: deviceId,\n session_id: String(sessionId),\n type: 'replay',\n api_key: apiKey,\n });\n const serverUrl = `${getServerUrl(serverZone, this.trackServerUrl)}?${urlParams.toString()}`;\n const globalScope = getGlobalScope();\n try {\n // Wrap in a Blob to set Content-Type: application/json; a plain string would\n // cause the browser to send Content-Type: text/plain, which the server rejects.\n const payloadBlob = new Blob([payload], { type: 'application/json' });\n const sent = globalScope?.navigator?.sendBeacon?.(serverUrl, payloadBlob);\n if (sent === false) {\n this.loggerProvider.warn('sendBeacon failed to queue session replay payload');\n }\n } catch {\n // Best effort — no fallback on page exit.\n }\n }\n\n addToQueue(...list: SessionReplayDestinationContext[]) {\n const tryable = list.filter((context) => {\n if (this.killedSessions.has(context.sessionId)) {\n // Server has signaled capture_disabled or session_in_invalid_range for this session;\n // drop the batch (and clean up its IDB record via onComplete) instead of POSTing.\n this.completeRequest({\n context,\n err: SESSION_KILLED_MESSAGE,\n });\n return false;\n }\n if (context.attempts < (context.flushMaxRetries || 0)) {\n context.attempts += 1;\n return true;\n }\n this.completeRequest({\n context,\n err: MAX_RETRIES_EXCEEDED_MESSAGE,\n });\n return false;\n });\n tryable.forEach((context) => {\n this.queue = this.queue.concat(context);\n this.schedule(0);\n });\n }\n\n schedule(timeout: number) {\n if (this.scheduled) return;\n // If the server signaled throttling on a recent 200, defer the next flush until the\n // pause window ends. This lets us keep batching events without retry-storming the server.\n const pauseRemaining = this.flushPauseUntilMs - Date.now();\n const isPaused = pauseRemaining > 0;\n const effectiveTimeout = pauseRemaining > timeout ? pauseRemaining : timeout;\n if (isPaused) {\n // Mark the upcoming flush for merge: contexts piling up during the pause should\n // be coalesced into one POST per (session, device, api, type, ...) group.\n this.mergeOnNextFlush = true;\n }\n this.scheduled = setTimeout(() => {\n void this.flush(true).then(() => {\n if (this.queue.length > 0) {\n this.schedule(timeout);\n }\n });\n }, effectiveTimeout);\n }\n\n async flush(useRetry = false) {\n let list = this.queue;\n this.queue = [];\n\n if (this.scheduled) {\n clearTimeout(this.scheduled);\n this.scheduled = null;\n }\n\n if (this.mergeOnNextFlush) {\n this.mergeOnNextFlush = false;\n // A throttle merge already coalesces by identity; clear the drain flag too so the\n // same backlog isn't passed through a redundant second merge on a later flush.\n this.coalesceNextFlush = false;\n list = this.mergeQueueAfterThrottle(list);\n } else if (this.coalesceNextFlush) {\n this.coalesceNextFlush = false;\n list = this.mergeDrainBacklog(list);\n }\n\n for (const context of list) {\n await this.send(context, useRetry);\n }\n }\n\n /**\n * Post-throttle release path: coalesce the queued contexts, then log once per pause window.\n * Delegates the actual merging to the shared coalesceByIdentity helper so the drain path\n * (mergeDrainBacklog) and this path stay byte-for-byte identical in how they merge.\n */\n private mergeQueueAfterThrottle(list: SessionReplayDestinationContext[]): SessionReplayDestinationContext[] {\n const merged = this.coalesceByIdentity(list);\n if (merged.length < list.length && !this.mergeLogFiredThisPause) {\n this.mergeLogFiredThisPause = true;\n this.loggerProvider.log(\n `Session replay throttle pause ended; merged ${list.length} queued batches into ${merged.length} request(s)`,\n );\n }\n return merged;\n }\n\n /**\n * Page-load backlog drain path: on init the SDK replays every persisted sequence\n * from a prior session via sendStoredEvents. Enqueued back-to-back they would flush as N\n * separate POSTs — a request flood on page load that feeds volume spikes and throttling.\n * Reuses the exact same identity-grouped merge as the post-throttle path so the backlog\n * collapses into far fewer requests, with onComplete fanned out so each source IDB record\n * is still cleaned up exactly once on success.\n */\n private mergeDrainBacklog(list: SessionReplayDestinationContext[]): SessionReplayDestinationContext[] {\n const merged = this.coalesceByIdentity(list);\n if (merged.length < list.length) {\n this.loggerProvider.log(\n `Session replay coalesced ${list.length} persisted page-load backlog batches into ${merged.length} request(s)`,\n );\n }\n return merged;\n }\n\n /**\n * Coalesces queued contexts that share the same destination identity into fewer requests.\n * Identity covers everything that affects the request URL, routing, or per-request semantics\n * — splitting on any difference keeps each merged POST indistinguishable from the source\n * contexts it replaced.\n *\n * Greedy concat with a soft byte-length cap (`MERGE_AFTER_THROTTLE_SOFT_CAP`) keeps merged\n * payloads well under the 413 ceiling; on the rare oversized merge, the existing\n * split-and-retry path still bisects safely.\n *\n * The merged context's `onComplete` fans out to every source context's callback so each\n * underlying IDB sequence record is cleaned up exactly once on success.\n */\n private coalesceByIdentity(list: SessionReplayDestinationContext[]): SessionReplayDestinationContext[] {\n if (list.length <= 1) return list;\n\n const groups = new Map<string, SessionReplayDestinationContext[]>();\n for (const ctx of list) {\n // Anything that can change the URL, headers, or backend routing must split groups.\n const key = [\n ctx.sessionId,\n ctx.deviceId ?? '',\n ctx.apiKey ?? '',\n ctx.type,\n ctx.serverZone ?? '',\n ctx.sampleRate,\n ctx.version?.type ?? '',\n ctx.version?.version ?? '',\n ].join('|');\n const arr = groups.get(key);\n if (arr) arr.push(ctx);\n else groups.set(key, [ctx]);\n }\n\n const merged: SessionReplayDestinationContext[] = [];\n for (const group of groups.values()) {\n if (group.length === 1) {\n merged.push(group[0]);\n continue;\n }\n let current: SessionReplayDestinationContext | null = null;\n let currentBytes = 0;\n const flushCurrent = () => {\n if (current) merged.push(current);\n current = null;\n currentBytes = 0;\n };\n for (const ctx of group) {\n // UTF-8 byte size, matching how the events store enforces MAX_EVENT_LIST_SIZE\n // (see base-events-store.ts:getStringSize). Using char length would let a CJK/\n // emoji-heavy payload sneak past the cap.\n const ctxBytes = ctx.events.reduce((sum, e) => sum + new Blob([e]).size, 0);\n if (current === null) {\n // Reset attempts to 0 on the merged context so the post-throttle delivery gets a\n // full retry budget. The throttle pause has already absorbed back-pressure; the\n // alternative (Math.max of source attempts) would collapse N source budgets into\n // one and end-of-life all N IDB records on a single retry exhaustion.\n current = { ...ctx, events: [...ctx.events], attempts: 0 };\n currentBytes = ctxBytes;\n continue;\n }\n if (currentBytes + ctxBytes > MERGE_AFTER_THROTTLE_SOFT_CAP) {\n flushCurrent();\n current = { ...ctx, events: [...ctx.events], attempts: 0 };\n currentBytes = ctxBytes;\n continue;\n }\n const prevOnComplete = current.onComplete;\n const ctxOnComplete = ctx.onComplete;\n current.events = current.events.concat(ctx.events);\n currentBytes += ctxBytes;\n current.onComplete = async () => {\n // allSettled (not all): an underlying store cleanup failure in one shouldn't\n // block the other, and the merged onComplete is invoked fire-and-forget via\n // `void context.onComplete()` — a rejection from `Promise.all` would surface\n // as an unhandled rejection. Errors stay encapsulated in the source callbacks.\n await Promise.allSettled([prevOnComplete(), ctxOnComplete()]);\n };\n }\n flushCurrent();\n }\n\n return merged;\n }\n\n async send(context: SessionReplayDestinationContext, useRetry = true) {\n // A kill directive can arrive between flush() snapshotting the queue and us reaching\n // each context. Re-check before hitting the network so we don't waste POSTs on a\n // session the server has already told us to stop sending for.\n if (this.killedSessions.has(context.sessionId)) {\n return this.completeRequest({ context, err: SESSION_KILLED_MESSAGE });\n }\n const apiKey = context.apiKey;\n if (!apiKey) {\n return this.completeRequest({ context, err: MISSING_API_KEY_MESSAGE });\n }\n const deviceId = context.deviceId;\n if (!deviceId) {\n return this.completeRequest({ context, err: MISSING_DEVICE_ID_MESSAGE });\n }\n\n const payload = this.payloadBatcher({\n version: 1,\n events: context.events,\n });\n\n if (payload.events.length === 0) {\n this.completeRequest({ context });\n return;\n }\n\n const { worker } = this;\n if (worker) {\n return this.sendViaWorker(worker, context, payload, useRetry);\n }\n\n return this.sendOnMainThread(apiKey, deviceId, context, payload, useRetry);\n }\n\n private async sendViaWorker(\n worker: Worker,\n context: SessionReplayDestinationContext,\n payload: { version: number; events: unknown[] },\n useRetry: boolean,\n ): Promise<void> {\n const id = `${++this.sendIdCounter}`;\n return new Promise<void>((resolve) => {\n // The worker only resolves this promise when it posts back complete/payload_too_large.\n // If the worker's own fetch hangs, no message ever arrives, so this promise — and the\n // serial flush loop awaiting it — would hang forever while pendingWorkerRequests grows\n // unbounded. On timeout we resolve so flush() proceeds, but deliberately do NOT call\n // completeRequest: like the worker-crash path above, the events were never confirmed\n // delivered, so onComplete must not fire and the IDB/memory store must stay intact for\n // recovery by sendStoredEvents.\n // sendTimeoutMs <= 0 disables the wait timer entirely: we then rely solely on the\n // worker's own complete/payload_too_large message to settle. This reintroduces the\n // hang risk this timer guards against, so it is an explicit experiment opt-out.\n const timeout =\n this.sendTimeoutMs > 0\n ? setTimeout(() => {\n const pending = this.pendingWorkerRequests.get(id);\n if (!pending) return;\n this.pendingWorkerRequests.delete(id);\n // Retain the context so a *late* worker complete/payload_too_large can still settle the\n // store record (see timedOutWorkerRequests). Without this, a worker that ultimately\n // delivers after we stopped awaiting would leave the record behind → duplicate upload.\n this.rememberTimedOutRequest(id, pending.context);\n this.loggerProvider.warn(\n `Session replay worker send timed out after ${this.sendTimeoutMs}ms; leaving events for retry`,\n );\n pending.resolve();\n }, this.sendTimeoutMs)\n : undefined;\n this.pendingWorkerRequests.set(id, { context, resolve, timeout });\n worker.postMessage({\n type: 'send',\n id,\n payload,\n useRetry,\n context: {\n apiKey: context.apiKey,\n deviceId: context.deviceId,\n sessionId: context.sessionId,\n events: context.events,\n eventType: context.type,\n flushMaxRetries: context.flushMaxRetries ?? 0,\n sampleRate: context.sampleRate,\n serverZone: context.serverZone,\n trackServerUrl: this.trackServerUrl,\n version: context.version,\n currentUrl: getCurrentUrl(),\n sdkVersion: VERSION,\n enableTransportCompression: this.enableTransportCompression,\n sendTimeoutMs: this.sendTimeoutMs,\n },\n });\n });\n }\n\n private rememberTimedOutRequest(id: string, context: SessionReplayDestinationContext) {\n this.timedOutWorkerRequests.set(id, context);\n // Bound memory: a wedged worker that never replies must not let this grow without limit.\n // Map preserves insertion order, so deleting the first key evicts the oldest entry.\n if (this.timedOutWorkerRequests.size > MAX_TIMED_OUT_WORKER_REQUESTS) {\n for (const oldest of this.timedOutWorkerRequests.keys()) {\n this.timedOutWorkerRequests.delete(oldest);\n break;\n }\n }\n }\n\n private async sendOnMainThread(\n apiKey: string,\n deviceId: string,\n context: SessionReplayDestinationContext,\n payload: { version: number; events: unknown[] },\n useRetry: boolean,\n ): Promise<void> {\n const url = getCurrentUrl();\n const version = VERSION;\n const sampleRate = context.sampleRate;\n const urlParams = new URLSearchParams({\n device_id: deviceId,\n session_id: `${context.sessionId}`,\n type: `${context.type}`,\n });\n const sessionReplayLibrary = `${context.version?.type ?? 'standalone'}/${context.version?.version ?? version}`;\n\n try {\n const payloadJson = JSON.stringify(payload);\n // Only await gzip when (a) the customer hasn't opted out and (b) CompressionStream\n // is actually available; skipping the await entirely preserves the synchronous\n // fast-path for browsers/environments (e.g. Jest) that don't support it, keeping\n // retry-timing tests unaffected.\n const globalScope = getGlobalScope();\n const gzipped =\n this.enableTransportCompression && globalScope && 'CompressionStream' in globalScope\n ? await gzipJson(payloadJson, globalScope)\n : null;\n const payloadSize = gzipped ? gzipped.byteLength : new Blob([payloadJson]).size;\n // fetch() has no native timeout. A request stuck \"pending\" forever would block the\n // serial flush loop indefinitely (head-of-line blocking), so we abort it after\n // SEND_TIMEOUT_MS. The abort surfaces as an AbortError in the catch below, where it's\n // routed as a retryable network failure when useRetry is true.\n const controller = new AbortController();\n const options: RequestInit = {\n headers: {\n 'Content-Type': 'application/json',\n Accept: '*/*',\n Authorization: `Bearer ${apiKey}`,\n 'X-Client-Version': version,\n 'X-Client-Library': sessionReplayLibrary,\n 'X-Client-Url': url.substring(0, MAX_URL_LENGTH), // limit url length to 1000 characters to avoid ELB 400 error\n 'X-Client-Sample-Rate': `${sampleRate}`,\n 'X-Sampling-Hash-Alg': 'xxhash32',\n ...(gzipped ? { 'Content-Encoding': 'gzip' } : {}),\n },\n body: (gzipped ?? payloadJson) as BodyInit,\n method: 'POST',\n // keepalive lets the request survive page navigation, preventing 499 (client-closed) errors.\n // Must stay under the browser's 64 KB keepalive budget; large payloads skip it.\n keepalive: payloadSize <= MAX_KEEPALIVE_BYTES,\n signal: controller.signal,\n };\n\n const serverUrl = `${getServerUrl(context.serverZone, this.trackServerUrl)}?${urlParams.toString()}`;\n // Final defensive guard: never POST a zero-event payload. Upper layers (events-manager\n // oversize filter, send()'s post-batcher check, store-layer filters) should already\n // have caught this — but SR-4284 fleet logs show ~416 empty-body 400s/24h slipping\n // through somehow, so a cheap belt-and-braces check immediately before fetch prevents\n // any future regression from re-introducing the same server rejection.\n if (payload.events.length === 0) {\n this.completeRequest({ context });\n return;\n }\n // sendTimeoutMs <= 0 disables the abort: the request can then hang indefinitely\n // (head-of-line blocking the serial flush). Explicit experiment opt-out only.\n const sendTimeout =\n this.sendTimeoutMs > 0\n ? setTimeout(() => {\n controller.abort();\n }, this.sendTimeoutMs)\n : undefined;\n let res: Response;\n try {\n res = await fetch(serverUrl, options);\n } finally {\n // Clear on success and on error alike so a settled request never leaves an armed\n // timer that would abort a later reused controller or fire a stray callback.\n if (sendTimeout) clearTimeout(sendTimeout);\n }\n if (res === null) {\n this.completeRequest({ context, err: UNEXPECTED_ERROR_MESSAGE });\n return;\n }\n if (res.status >= 200 && res.status < 300) {\n const skipCode = res.headers?.get?.(EVENT_SKIPPED_HEADER) ?? null;\n this.applyServerDirective(context.sessionId, skipCode);\n }\n if (!useRetry) {\n let responseBody = '';\n try {\n responseBody = JSON.stringify(res.body, null, 2);\n } catch {\n // to avoid crash, but don't care about the error, add comment to avoid empty block lint error\n }\n this.completeRequest({ context, success: `${res.status}: ${responseBody}` });\n } else {\n let responseBody = '';\n if (res.status === 413) {\n try {\n responseBody = await res.text();\n } catch {\n // best effort\n }\n }\n await this.handleReponse(res.status, context, responseBody);\n }\n } catch (e) {\n // A send timeout aborts the fetch, which rejects with an AbortError. Treat that as a\n // transient network failure and route it through the same retry budget/backoff as a\n // 5xx (so a single stalled request doesn't permanently drop the batch) when retries\n // are enabled. completeRequest fires onComplete exactly once via either branch\n // (handleOtherResponse only completes on retry exhaustion), so onComplete can't fire\n // twice. Non-abort errors keep the original complete-with-error behavior.\n // Browsers reject an aborted fetch with a DOMException named 'AbortError', which is NOT an\n // Error instance — an `instanceof Error` check would misroute every send-timeout abort to\n // the fatal completeRequest path, defeating the retry. Match on the name across any thrown\n // object (DOMException or Error) instead.\n const isAbort = !!e && typeof e === 'object' && (e as { name?: unknown }).name === 'AbortError';\n if (isAbort && useRetry) {\n await this.handleOtherResponse(context);\n } else {\n this.completeRequest({ context, err: e as string });\n }\n }\n }\n\n async handleReponse(status: number, context: SessionReplayDestinationContext, responseBody = '') {\n const parsedStatus = new BaseTransport().buildStatus(status);\n switch (parsedStatus) {\n case Status.Success:\n this.handleSuccessResponse(context);\n break;\n case Status.Failed:\n case Status.Timeout: // 408: server timed out waiting for request, data not received\n case Status.RateLimit: // 429: retry with existing backoff rather than silently dropping\n await this.handleOtherResponse(context);\n break;\n case Status.PayloadTooLarge:\n this.handlePayloadTooLargeResponse(context, WAF_PAYLOAD_TOO_LARGE_PATTERN.test(responseBody));\n break;\n default:\n // 499 (client closed connection / upstream dropped) is also retryable\n if (status === 499) {\n await this.handleOtherResponse(context);\n break;\n }\n this.completeRequest({ context, err: UNEXPECTED_NETWORK_ERROR_MESSAGE });\n }\n }\n\n handlePayloadTooLargeResponse(context: SessionReplayDestinationContext, isWaf: boolean): void {\n const source = isWaf ? 'WAF (compressed payload too large)' : 'server (event too large)';\n const totalSizeKB = Math.round(context.events.reduce((sum, e) => sum + e.length, 0) / KB_SIZE);\n\n if (!isWaf) {\n this.completeRequest({\n context,\n err: `Session replay event batch dropped: ${source} rejected payload (${context.events.length} events, ${totalSizeKB} KB) — not retrying non-WAF 413`,\n });\n return;\n }\n\n if (context.events.length === 1) {\n this.completeRequest({\n context,\n err: `Session replay event dropped: single event (${totalSizeKB} KB, 1 event) rejected by ${source} — cannot split further`,\n });\n return;\n }\n\n this.loggerProvider.warn(\n `Session replay event batch rejected by ${source} (${context.events.length} events, ${totalSizeKB} KB total) — splitting and retrying`,\n );\n\n // Clean up the original IDB record, then re-enqueue both halves as new in-memory batches.\n // For a merged-on-throttle context (mergeQueueAfterThrottle), this onComplete is the\n // fanned-out callback covering N source IDB records — they'll all be cleaned up here.\n // Halves get noop onCompletes, so a page-close between this cleanup and a half delivery\n // means up to N source sequences are lost. The merge soft cap (1.4MB chars) is well under\n // the 10MB compressed 413 ceiling, so a 413 on a merged context is exceedingly rare in\n // practice; the alternative — deferring source cleanup until both halves complete — would\n // significantly complicate the retry path for marginal benefit.\n void context.onComplete();\n const noop = (): Promise<void> => Promise.resolve();\n const mid = Math.floor(context.events.length / 2);\n this.sendEventsList({ ...context, events: context.events.slice(0, mid), onComplete: noop });\n this.sendEventsList({ ...context, events: context.events.slice(mid), onComplete: noop });\n }\n\n handleSuccessResponse(context: SessionReplayDestinationContext) {\n const sizeOfEventsList = Math.round(new Blob(context.events).size / KB_SIZE);\n this.completeRequest({\n context,\n success: `Session replay event batch tracked successfully for session id ${context.sessionId}, size of events: ${sizeOfEventsList} KB`,\n });\n }\n\n async handleOtherResponse(context: SessionReplayDestinationContext) {\n const delay = Math.random() * context.attempts * this.retryTimeout;\n context.attempts++;\n if (context.attempts > (context.flushMaxRetries || 0)) {\n this.completeRequest({ context, err: MAX_RETRIES_EXCEEDED_MESSAGE });\n return;\n }\n await new Promise<void>((resolve) => setTimeout(resolve, delay));\n await this.send(context, true);\n }\n\n completeRequest({\n context,\n err,\n success,\n }: {\n context: SessionReplayDestinationContext;\n err?: string;\n success?: string;\n }) {\n void context.onComplete();\n if (err) {\n this.loggerProvider.warn(err);\n } else if (success) {\n this.loggerProvider.log(success);\n }\n }\n\n /**\n * Applies the server's back-pressure signal carried on a 200 response.\n *\n * - `EVENT_SKIP_CODE_THROTTLED` (server-side rate limit): pause the flush schedule\n * for `THROTTLED_FLUSH_PAUSE_MS` so we keep batching events instead of retry-storming.\n * - `EVENT_SKIP_CODE_CAPTURE_DISABLED` / `EVENT_SKIP_CODE_INVALID_RANGE`: hard kill\n * switch for this session — drop the queued contexts and stop accepting new ones.\n * New sessions are unaffected.\n * - `null` (clean 200, no header): clear any throttle pause; subsequent flushes resume\n * on the normal cadence.\n */\n private applyServerDirective(sessionId: string | number, skipCode: string | null) {\n if (skipCode === null) {\n this.flushPauseUntilMs = 0;\n this.mergeLogFiredThisPause = false;\n return;\n }\n if (skipCode === EVENT_SKIP_CODE_THROTTLED) {\n const wasInPause = this.flushPauseUntilMs > Date.now();\n this.flushPauseUntilMs = Date.now() + THROTTLED_FLUSH_PAUSE_MS;\n // Log only on pause-state transitions — a throttled server may reply to many\n // batches per minute, and one log per batch would flood the console.\n if (!wasInPause) {\n this.loggerProvider.log(\n `Session replay throttled by server; pausing flush schedule for ${THROTTLED_FLUSH_PAUSE_MS / 1000}s`,\n );\n }\n return;\n }\n if (skipCode === EVENT_SKIP_CODE_CAPTURE_DISABLED || skipCode === EVENT_SKIP_CODE_INVALID_RANGE) {\n this.killSession(sessionId, skipCode);\n }\n // Unknown skip codes are ignored — the server may add new ones, and our default\n // behavior (treat as a normal 200) preserves throughput rather than penalizing the\n // session for a code we don't recognize.\n }\n\n private killSession(sessionId: string | number, skipCode: string) {\n if (this.killedSessions.has(sessionId)) return;\n this.killedSessions.add(sessionId);\n // Set preserves insertion order, so deleting the first key evicts the oldest entry.\n if (this.killedSessions.size > MAX_KILLED_SESSIONS) {\n for (const oldest of this.killedSessions) {\n this.killedSessions.delete(oldest);\n break;\n }\n }\n this.loggerProvider.log(\n `Session replay capture stopped for session ${sessionId} by server directive ${skipCode}; remaining events will be dropped`,\n );\n // Drain any queued contexts for this session so their IDB records get cleaned up\n // via onComplete, instead of sitting in the queue waiting for a flush we'll never make.\n const remaining: SessionReplayDestinationContext[] = [];\n for (const queued of this.queue) {\n if (queued.sessionId === sessionId) {\n this.completeRequest({ context: queued, err: SESSION_KILLED_MESSAGE });\n } else {\n remaining.push(queued);\n }\n }\n this.queue = remaining;\n }\n}\n"]}
|
package/lib/cjs/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "1.45.0
|
|
1
|
+
export declare const VERSION = "1.45.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/lib/cjs/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,OAAO,WAAW,CAAC"}
|
package/lib/cjs/version.js
CHANGED
package/lib/cjs/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";;;AAAA,oDAAoD;AACvC,QAAA,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";;;AAAA,oDAAoD;AACvC,QAAA,OAAO,GAAG,QAAQ,CAAC","sourcesContent":["// Autogenerated by `pnpm version-file`. DO NOT EDIT\nexport const VERSION = '1.45.0';\n"]}
|
|
@@ -203,7 +203,7 @@ export var createEventsManager = function (_a) {
|
|
|
203
203
|
return [2 /*return*/];
|
|
204
204
|
}
|
|
205
205
|
config.loggerProvider.log("Draining ".concat(sequencesToSend.length, " stored sequence(s) from previous session."));
|
|
206
|
-
//
|
|
206
|
+
// These persisted sequences are about to be enqueued back-to-back. Without
|
|
207
207
|
// coalescing they flush as N separate POSTs — a request flood on page load. Mark the
|
|
208
208
|
// imminent flush to merge same-identity batches (the enqueues below are synchronous, so
|
|
209
209
|
// the whole backlog lands in the queue before the deferred flush consumes the flag). Skip
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events-manager.js","sourceRoot":"","sources":["../../../src/events/events-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n SessionReplayEventsManager as AmplitudeSessionReplayEventsManager,\n EventsStore,\n EventType,\n StoreType,\n} from '../typings/session-replay';\n\nimport { SessionReplayJoinedConfig } from '../config/types';\nimport { MAX_SINGLE_EVENT_SIZE } from '../constants';\nimport { getStorageSize } from '../helpers';\nimport { PayloadBatcher, SessionReplayTrackDestination } from '../track-destination';\nimport { SessionReplayEventsIDBStore } from './events-idb-store';\nimport { InMemoryEventsStore } from './events-memory-store';\n\nexport type EventsManagerWithBeacon<Type extends EventType> = AmplitudeSessionReplayEventsManager<Type, string> & {\n /**\n * Returns current pending events (since last flush) for synchronous access on page exit.\n * Used to populate a sendBeacon payload when the page is unloading.\n */\n getBeaconEvents(): string[];\n /**\n * Drops all pending beacon events. Used when the session is decided to be below\n * the min duration threshold so its events don't leak into a later session's beacon.\n */\n dropPendingBeaconEvents(): void;\n trackDestination: SessionReplayTrackDestination;\n};\n\nexport const createEventsManager = async <Type extends EventType>({\n config,\n minInterval,\n maxInterval,\n maxPersistedEventsSize,\n type,\n payloadBatcher,\n storeType,\n trackDestinationWorkerScript,\n shouldSend,\n}: {\n config: SessionReplayJoinedConfig;\n type: Type;\n minInterval?: number;\n maxInterval?: number;\n maxPersistedEventsSize?: number;\n payloadBatcher?: PayloadBatcher;\n storeType: StoreType;\n trackDestinationWorkerScript?: string;\n shouldSend?: () => boolean;\n}): Promise<EventsManagerWithBeacon<Type>> => {\n // Configurable per-single-event drop cap (defaults to MAX_SINGLE_EVENT_SIZE); enforced both\n // here as a pre-send backstop and at capture time in EventCompressor.\n const maxSingleEventSize = config.maxSingleEventSizeBytes ?? MAX_SINGLE_EVENT_SIZE;\n const trackDestination = new SessionReplayTrackDestination({\n ...config,\n loggerProvider: config.loggerProvider,\n payloadBatcher,\n workerScript: trackDestinationWorkerScript,\n });\n\n const getMemoryStore = (): EventsStore<number> => {\n return new InMemoryEventsStore({\n loggerProvider: config.loggerProvider,\n maxInterval,\n minInterval,\n maxPersistedEventsSize,\n });\n };\n\n let lastKnownDeviceId: string | undefined;\n let usingIdbStore = false;\n let store!: EventsStore<number>;\n\n const switchToMemoryStore = async () => {\n if (!usingIdbStore) return;\n usingIdbStore = false;\n config.loggerProvider.warn('IDB store is experiencing repeated failures; falling back to in-memory event store.');\n const sequences = lastKnownDeviceId ? await store.getSequencesToSend() : undefined;\n store = getMemoryStore();\n if (sequences && lastKnownDeviceId) {\n const deviceId = lastKnownDeviceId;\n sequences.forEach((seq) => {\n sendEventsList({ sequenceId: seq.sequenceId, events: seq.events, sessionId: seq.sessionId, deviceId });\n });\n }\n };\n\n const getIdbStoreOrFallback = async (): Promise<EventsStore<number>> => {\n const idb = await SessionReplayEventsIDBStore.new(type, {\n loggerProvider: config.loggerProvider,\n minInterval,\n maxInterval,\n maxPersistedEventsSize,\n apiKey: config.apiKey,\n onPersistentFailure: () => {\n void switchToMemoryStore();\n },\n });\n if (!idb) {\n config.loggerProvider.log('Failed to initialize idb store, falling back to memory store.');\n return getMemoryStore();\n }\n usingIdbStore = true;\n return idb;\n };\n\n store = storeType === 'idb' ? await getIdbStoreOrFallback() : getMemoryStore();\n\n // Beacon buffer: a sliding window of pending (unsent) event strings for synchronous\n // access on page exit. Uses an absolute index counter to correctly handle concurrent\n // async flushes without losing events added between the flush call and its resolution.\n const beaconBuffer: string[] = [];\n let beaconWindowStart = 0; // absolute index of the first element in beaconBuffer\n\n const advanceBeaconWindow = (upToAbsoluteIdx: number) => {\n if (upToAbsoluteIdx <= beaconWindowStart) return;\n const trimCount = Math.min(upToAbsoluteIdx - beaconWindowStart, beaconBuffer.length);\n if (trimCount > 0) {\n beaconBuffer.splice(0, trimCount);\n beaconWindowStart = upToAbsoluteIdx;\n }\n };\n\n /**\n * Immediately sends events to the track destination.\n */\n const sendEventsList = ({\n events: rawEvents,\n sessionId,\n deviceId,\n sequenceId,\n }: {\n events: string[];\n sessionId: string | number;\n deviceId: string;\n sequenceId?: number;\n }) => {\n // Backstop for events that entered IDB before the per-event size guard in\n // addCompressedEventToManager (e.g. stored by a previous SDK version or via\n // storeCurrentSequence/sendStoredEvents which bypass the capture-time check).\n // Compare UTF-8 byte size, not JS char count, to match the server-side limit.\n const sizedEvents = rawEvents.map((e) => ({ event: e, bytes: new Blob([e]).size }));\n const oversized = sizedEvents.filter((s) => s.bytes > maxSingleEventSize);\n if (oversized.length > 0) {\n config.loggerProvider.warn(\n `Dropping ${oversized.length} oversized event(s) from session replay sequence before send. Sizes: ${oversized\n .map((s) => `${Math.round(s.bytes / 1024)} KB`)\n .join(\n ', ',\n )}. If this recurs, please open a GitHub issue at https://github.com/amplitude/Amplitude-TypeScript/issues or contact Amplitude support.`,\n );\n }\n const events =\n oversized.length > 0 ? sizedEvents.filter((s) => s.bytes <= maxSingleEventSize).map((s) => s.event) : rawEvents;\n if (events.length === 0) {\n store.cleanUpSessionEventsStore(sessionId, sequenceId).catch((e) => {\n config.loggerProvider.warn('Failed to clean up session replay events store:', e);\n });\n return;\n }\n\n if (config.debugMode) {\n getStorageSize()\n .then(({ totalStorageSize, percentOfQuota, usageDetails }) => {\n config.loggerProvider.debug(\n `Total storage size: ${totalStorageSize} KB, percentage of quota: ${percentOfQuota}%, usage details: ${usageDetails}`,\n );\n })\n .catch(() => {\n // swallow error\n });\n }\n\n trackDestination.sendEventsList({\n events: events,\n sessionId: sessionId,\n flushMaxRetries: config.flushMaxRetries,\n apiKey: config.apiKey,\n deviceId: deviceId,\n sampleRate: config.sampleRate,\n serverZone: config.serverZone,\n version: config.version,\n type,\n onComplete: async () => {\n await store.cleanUpSessionEventsStore(sessionId, sequenceId);\n return;\n },\n });\n };\n\n const sendCurrentSequenceEvents = ({ sessionId, deviceId }: { sessionId: number; deviceId: string }) => {\n lastKnownDeviceId = deviceId;\n // Evaluate shouldSend synchronously before the async store read. asyncSetSessionId\n // updates sessionStartTime immediately after calling sendEvents(), so by the time\n // storeCurrentSequence resolves the start time would reflect the new session and\n // the elapsed check would compute ~0ms, silently dropping the previous session's events.\n if (shouldSend && !shouldSend()) return;\n // Snapshot the absolute end-index before the async store read so that any events\n // pushed after this point are NOT considered sent and remain in the beacon buffer.\n const snapshotAbsIdx = beaconWindowStart + beaconBuffer.length;\n store\n .storeCurrentSequence(sessionId)\n .then((currentSequence) => {\n if (currentSequence) {\n advanceBeaconWindow(snapshotAbsIdx);\n sendEventsList({\n sequenceId: currentSequence.sequenceId,\n events: currentSequence.events,\n sessionId: currentSequence.sessionId,\n deviceId,\n });\n }\n })\n .catch((e) => {\n config.loggerProvider.warn('Failed to get current sequence of session replay events for session:', e);\n });\n };\n\n const sendStoredEvents = async ({ deviceId }: { deviceId: string }) => {\n lastKnownDeviceId = deviceId;\n const sequencesToSend = await store.getSequencesToSend();\n if (!sequencesToSend?.length) {\n return;\n }\n config.loggerProvider.log(`Draining ${sequencesToSend.length} stored sequence(s) from previous session.`);\n // SR-4660: these persisted sequences are about to be enqueued back-to-back. Without\n // coalescing they flush as N separate POSTs — a request flood on page load. Mark the\n // imminent flush to merge same-identity batches (the enqueues below are synchronous, so\n // the whole backlog lands in the queue before the deferred flush consumes the flag). Skip\n // the single-sequence case where there's nothing to merge.\n if (sequencesToSend.length > 1) {\n trackDestination.markCoalesceNextFlush();\n }\n sequencesToSend.forEach((sequence) => {\n sendEventsList({\n sequenceId: sequence.sequenceId,\n events: sequence.events,\n sessionId: sequence.sessionId,\n deviceId,\n });\n });\n };\n\n const addEvent = ({\n event,\n sessionId,\n deviceId,\n }: {\n event: { type: Type; data: string };\n sessionId: number;\n deviceId: string;\n }) => {\n lastKnownDeviceId = deviceId;\n // Capture shouldSend synchronously before the async store write, for the same\n // reason as sendCurrentSequenceEvents: asyncSetSessionId updates sessionStartTime\n // synchronously, so evaluating inside the .then() would use the new session's\n // start time and compute ~0ms elapsed, silently dropping a valid batch.\n const canSend = !shouldSend || shouldSend();\n // Record the absolute index of this event in the beacon buffer before the async\n // store operation. If a batch split occurs, we advance the window up to (but not\n // including) this event so that it starts the next pending window.\n const absIdx = beaconWindowStart + beaconBuffer.length;\n beaconBuffer.push(event.data);\n store\n .addEventToCurrentSequence(sessionId, event.data)\n .then((sequenceToSend) => {\n if (sequenceToSend) {\n if (!canSend) {\n // The split atomically moved events to sequencesToSend; without cleanup\n // they would be unconditionally replayed on next page load via\n // sendStoredEvents, bypassing the min session duration gate. The split\n // batch must also be dropped from the beacon buffer so it isn't sent\n // via sendBeacon on page unload (potentially attributed to a later session).\n store.cleanUpSessionEventsStore(sequenceToSend.sessionId, sequenceToSend.sequenceId).catch((e) => {\n config.loggerProvider.warn('Failed to clean up dropped session replay sequence:', e);\n });\n advanceBeaconWindow(absIdx);\n return;\n }\n // Events before absIdx belong to the split batch being sent; advance window.\n advanceBeaconWindow(absIdx);\n sendEventsList({\n sequenceId: sequenceToSend.sequenceId,\n events: sequenceToSend.events,\n sessionId: sequenceToSend.sessionId,\n deviceId,\n });\n }\n })\n .catch((e) => {\n config.loggerProvider.warn('Failed to add event to session replay capture:', e);\n });\n };\n\n async function flush(useRetry = false) {\n return trackDestination.flush(useRetry);\n }\n\n const getBeaconEvents = (): string[] => [...beaconBuffer];\n\n const dropPendingBeaconEvents = () => {\n advanceBeaconWindow(beaconWindowStart + beaconBuffer.length);\n };\n\n return {\n sendCurrentSequenceEvents,\n addEvent,\n sendStoredEvents,\n flush,\n getBeaconEvents,\n dropPendingBeaconEvents,\n trackDestination,\n };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"events-manager.js","sourceRoot":"","sources":["../../../src/events/events-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n SessionReplayEventsManager as AmplitudeSessionReplayEventsManager,\n EventsStore,\n EventType,\n StoreType,\n} from '../typings/session-replay';\n\nimport { SessionReplayJoinedConfig } from '../config/types';\nimport { MAX_SINGLE_EVENT_SIZE } from '../constants';\nimport { getStorageSize } from '../helpers';\nimport { PayloadBatcher, SessionReplayTrackDestination } from '../track-destination';\nimport { SessionReplayEventsIDBStore } from './events-idb-store';\nimport { InMemoryEventsStore } from './events-memory-store';\n\nexport type EventsManagerWithBeacon<Type extends EventType> = AmplitudeSessionReplayEventsManager<Type, string> & {\n /**\n * Returns current pending events (since last flush) for synchronous access on page exit.\n * Used to populate a sendBeacon payload when the page is unloading.\n */\n getBeaconEvents(): string[];\n /**\n * Drops all pending beacon events. Used when the session is decided to be below\n * the min duration threshold so its events don't leak into a later session's beacon.\n */\n dropPendingBeaconEvents(): void;\n trackDestination: SessionReplayTrackDestination;\n};\n\nexport const createEventsManager = async <Type extends EventType>({\n config,\n minInterval,\n maxInterval,\n maxPersistedEventsSize,\n type,\n payloadBatcher,\n storeType,\n trackDestinationWorkerScript,\n shouldSend,\n}: {\n config: SessionReplayJoinedConfig;\n type: Type;\n minInterval?: number;\n maxInterval?: number;\n maxPersistedEventsSize?: number;\n payloadBatcher?: PayloadBatcher;\n storeType: StoreType;\n trackDestinationWorkerScript?: string;\n shouldSend?: () => boolean;\n}): Promise<EventsManagerWithBeacon<Type>> => {\n // Configurable per-single-event drop cap (defaults to MAX_SINGLE_EVENT_SIZE); enforced both\n // here as a pre-send backstop and at capture time in EventCompressor.\n const maxSingleEventSize = config.maxSingleEventSizeBytes ?? MAX_SINGLE_EVENT_SIZE;\n const trackDestination = new SessionReplayTrackDestination({\n ...config,\n loggerProvider: config.loggerProvider,\n payloadBatcher,\n workerScript: trackDestinationWorkerScript,\n });\n\n const getMemoryStore = (): EventsStore<number> => {\n return new InMemoryEventsStore({\n loggerProvider: config.loggerProvider,\n maxInterval,\n minInterval,\n maxPersistedEventsSize,\n });\n };\n\n let lastKnownDeviceId: string | undefined;\n let usingIdbStore = false;\n let store!: EventsStore<number>;\n\n const switchToMemoryStore = async () => {\n if (!usingIdbStore) return;\n usingIdbStore = false;\n config.loggerProvider.warn('IDB store is experiencing repeated failures; falling back to in-memory event store.');\n const sequences = lastKnownDeviceId ? await store.getSequencesToSend() : undefined;\n store = getMemoryStore();\n if (sequences && lastKnownDeviceId) {\n const deviceId = lastKnownDeviceId;\n sequences.forEach((seq) => {\n sendEventsList({ sequenceId: seq.sequenceId, events: seq.events, sessionId: seq.sessionId, deviceId });\n });\n }\n };\n\n const getIdbStoreOrFallback = async (): Promise<EventsStore<number>> => {\n const idb = await SessionReplayEventsIDBStore.new(type, {\n loggerProvider: config.loggerProvider,\n minInterval,\n maxInterval,\n maxPersistedEventsSize,\n apiKey: config.apiKey,\n onPersistentFailure: () => {\n void switchToMemoryStore();\n },\n });\n if (!idb) {\n config.loggerProvider.log('Failed to initialize idb store, falling back to memory store.');\n return getMemoryStore();\n }\n usingIdbStore = true;\n return idb;\n };\n\n store = storeType === 'idb' ? await getIdbStoreOrFallback() : getMemoryStore();\n\n // Beacon buffer: a sliding window of pending (unsent) event strings for synchronous\n // access on page exit. Uses an absolute index counter to correctly handle concurrent\n // async flushes without losing events added between the flush call and its resolution.\n const beaconBuffer: string[] = [];\n let beaconWindowStart = 0; // absolute index of the first element in beaconBuffer\n\n const advanceBeaconWindow = (upToAbsoluteIdx: number) => {\n if (upToAbsoluteIdx <= beaconWindowStart) return;\n const trimCount = Math.min(upToAbsoluteIdx - beaconWindowStart, beaconBuffer.length);\n if (trimCount > 0) {\n beaconBuffer.splice(0, trimCount);\n beaconWindowStart = upToAbsoluteIdx;\n }\n };\n\n /**\n * Immediately sends events to the track destination.\n */\n const sendEventsList = ({\n events: rawEvents,\n sessionId,\n deviceId,\n sequenceId,\n }: {\n events: string[];\n sessionId: string | number;\n deviceId: string;\n sequenceId?: number;\n }) => {\n // Backstop for events that entered IDB before the per-event size guard in\n // addCompressedEventToManager (e.g. stored by a previous SDK version or via\n // storeCurrentSequence/sendStoredEvents which bypass the capture-time check).\n // Compare UTF-8 byte size, not JS char count, to match the server-side limit.\n const sizedEvents = rawEvents.map((e) => ({ event: e, bytes: new Blob([e]).size }));\n const oversized = sizedEvents.filter((s) => s.bytes > maxSingleEventSize);\n if (oversized.length > 0) {\n config.loggerProvider.warn(\n `Dropping ${oversized.length} oversized event(s) from session replay sequence before send. Sizes: ${oversized\n .map((s) => `${Math.round(s.bytes / 1024)} KB`)\n .join(\n ', ',\n )}. If this recurs, please open a GitHub issue at https://github.com/amplitude/Amplitude-TypeScript/issues or contact Amplitude support.`,\n );\n }\n const events =\n oversized.length > 0 ? sizedEvents.filter((s) => s.bytes <= maxSingleEventSize).map((s) => s.event) : rawEvents;\n if (events.length === 0) {\n store.cleanUpSessionEventsStore(sessionId, sequenceId).catch((e) => {\n config.loggerProvider.warn('Failed to clean up session replay events store:', e);\n });\n return;\n }\n\n if (config.debugMode) {\n getStorageSize()\n .then(({ totalStorageSize, percentOfQuota, usageDetails }) => {\n config.loggerProvider.debug(\n `Total storage size: ${totalStorageSize} KB, percentage of quota: ${percentOfQuota}%, usage details: ${usageDetails}`,\n );\n })\n .catch(() => {\n // swallow error\n });\n }\n\n trackDestination.sendEventsList({\n events: events,\n sessionId: sessionId,\n flushMaxRetries: config.flushMaxRetries,\n apiKey: config.apiKey,\n deviceId: deviceId,\n sampleRate: config.sampleRate,\n serverZone: config.serverZone,\n version: config.version,\n type,\n onComplete: async () => {\n await store.cleanUpSessionEventsStore(sessionId, sequenceId);\n return;\n },\n });\n };\n\n const sendCurrentSequenceEvents = ({ sessionId, deviceId }: { sessionId: number; deviceId: string }) => {\n lastKnownDeviceId = deviceId;\n // Evaluate shouldSend synchronously before the async store read. asyncSetSessionId\n // updates sessionStartTime immediately after calling sendEvents(), so by the time\n // storeCurrentSequence resolves the start time would reflect the new session and\n // the elapsed check would compute ~0ms, silently dropping the previous session's events.\n if (shouldSend && !shouldSend()) return;\n // Snapshot the absolute end-index before the async store read so that any events\n // pushed after this point are NOT considered sent and remain in the beacon buffer.\n const snapshotAbsIdx = beaconWindowStart + beaconBuffer.length;\n store\n .storeCurrentSequence(sessionId)\n .then((currentSequence) => {\n if (currentSequence) {\n advanceBeaconWindow(snapshotAbsIdx);\n sendEventsList({\n sequenceId: currentSequence.sequenceId,\n events: currentSequence.events,\n sessionId: currentSequence.sessionId,\n deviceId,\n });\n }\n })\n .catch((e) => {\n config.loggerProvider.warn('Failed to get current sequence of session replay events for session:', e);\n });\n };\n\n const sendStoredEvents = async ({ deviceId }: { deviceId: string }) => {\n lastKnownDeviceId = deviceId;\n const sequencesToSend = await store.getSequencesToSend();\n if (!sequencesToSend?.length) {\n return;\n }\n config.loggerProvider.log(`Draining ${sequencesToSend.length} stored sequence(s) from previous session.`);\n // These persisted sequences are about to be enqueued back-to-back. Without\n // coalescing they flush as N separate POSTs — a request flood on page load. Mark the\n // imminent flush to merge same-identity batches (the enqueues below are synchronous, so\n // the whole backlog lands in the queue before the deferred flush consumes the flag). Skip\n // the single-sequence case where there's nothing to merge.\n if (sequencesToSend.length > 1) {\n trackDestination.markCoalesceNextFlush();\n }\n sequencesToSend.forEach((sequence) => {\n sendEventsList({\n sequenceId: sequence.sequenceId,\n events: sequence.events,\n sessionId: sequence.sessionId,\n deviceId,\n });\n });\n };\n\n const addEvent = ({\n event,\n sessionId,\n deviceId,\n }: {\n event: { type: Type; data: string };\n sessionId: number;\n deviceId: string;\n }) => {\n lastKnownDeviceId = deviceId;\n // Capture shouldSend synchronously before the async store write, for the same\n // reason as sendCurrentSequenceEvents: asyncSetSessionId updates sessionStartTime\n // synchronously, so evaluating inside the .then() would use the new session's\n // start time and compute ~0ms elapsed, silently dropping a valid batch.\n const canSend = !shouldSend || shouldSend();\n // Record the absolute index of this event in the beacon buffer before the async\n // store operation. If a batch split occurs, we advance the window up to (but not\n // including) this event so that it starts the next pending window.\n const absIdx = beaconWindowStart + beaconBuffer.length;\n beaconBuffer.push(event.data);\n store\n .addEventToCurrentSequence(sessionId, event.data)\n .then((sequenceToSend) => {\n if (sequenceToSend) {\n if (!canSend) {\n // The split atomically moved events to sequencesToSend; without cleanup\n // they would be unconditionally replayed on next page load via\n // sendStoredEvents, bypassing the min session duration gate. The split\n // batch must also be dropped from the beacon buffer so it isn't sent\n // via sendBeacon on page unload (potentially attributed to a later session).\n store.cleanUpSessionEventsStore(sequenceToSend.sessionId, sequenceToSend.sequenceId).catch((e) => {\n config.loggerProvider.warn('Failed to clean up dropped session replay sequence:', e);\n });\n advanceBeaconWindow(absIdx);\n return;\n }\n // Events before absIdx belong to the split batch being sent; advance window.\n advanceBeaconWindow(absIdx);\n sendEventsList({\n sequenceId: sequenceToSend.sequenceId,\n events: sequenceToSend.events,\n sessionId: sequenceToSend.sessionId,\n deviceId,\n });\n }\n })\n .catch((e) => {\n config.loggerProvider.warn('Failed to add event to session replay capture:', e);\n });\n };\n\n async function flush(useRetry = false) {\n return trackDestination.flush(useRetry);\n }\n\n const getBeaconEvents = (): string[] => [...beaconBuffer];\n\n const dropPendingBeaconEvents = () => {\n advanceBeaconWindow(beaconWindowStart + beaconBuffer.length);\n };\n\n return {\n sendCurrentSequenceEvents,\n addEvent,\n sendStoredEvents,\n flush,\n getBeaconEvents,\n dropPendingBeaconEvents,\n trackDestination,\n };\n};\n"]}
|