@amplitude/session-replay-browser 1.38.0 → 1.39.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/config/types.d.ts +6 -0
- package/lib/cjs/config/types.d.ts.map +1 -1
- package/lib/cjs/config/types.js.map +1 -1
- package/lib/cjs/events/event-compressor.d.ts +2 -0
- package/lib/cjs/events/event-compressor.d.ts.map +1 -1
- package/lib/cjs/events/event-compressor.js +54 -6
- package/lib/cjs/events/event-compressor.js.map +1 -1
- package/lib/cjs/events/merge-mutation-events.d.ts +10 -0
- package/lib/cjs/events/merge-mutation-events.d.ts.map +1 -0
- package/lib/cjs/events/merge-mutation-events.js +232 -0
- package/lib/cjs/events/merge-mutation-events.js.map +1 -0
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/cjs/version.js.map +1 -1
- package/lib/esm/config/types.d.ts +6 -0
- package/lib/esm/config/types.d.ts.map +1 -1
- package/lib/esm/config/types.js.map +1 -1
- package/lib/esm/events/event-compressor.d.ts +2 -0
- package/lib/esm/events/event-compressor.d.ts.map +1 -1
- package/lib/esm/events/event-compressor.js +55 -7
- package/lib/esm/events/event-compressor.js.map +1 -1
- package/lib/esm/events/merge-mutation-events.d.ts +10 -0
- package/lib/esm/events/merge-mutation-events.d.ts.map +1 -0
- package/lib/esm/events/merge-mutation-events.js +228 -0
- package/lib/esm/events/merge-mutation-events.js.map +1 -0
- package/lib/esm/version.d.ts +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
|
@@ -256,6 +256,12 @@ export interface SessionReplayPerformanceConfig {
|
|
|
256
256
|
* before executing the deferred compression task, even if the browser is not idle.
|
|
257
257
|
*/
|
|
258
258
|
timeout?: number;
|
|
259
|
+
/**
|
|
260
|
+
* If enabled, consecutive mutation events will be merged into a single event before
|
|
261
|
+
* compression, reducing stored event count without changing replay semantics.
|
|
262
|
+
* Defaults to false.
|
|
263
|
+
*/
|
|
264
|
+
mergeMutations?: boolean;
|
|
259
265
|
/**
|
|
260
266
|
* Performance configuration for interaction tracking (clicks, scrolls).
|
|
261
267
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":";;;AAuDa,QAAA,kBAAkB,GAAG,QAAQ,CAAC","sourcesContent":["import { IConfig, LogLevel, ILogger } from '@amplitude/analytics-core';\nimport { StoreType, ConsoleLogLevel } from '../typings/session-replay';\nimport { TargetingFlag } from '@amplitude/targeting';\n\nexport interface SamplingConfig {\n sample_rate: number;\n capture_enabled: boolean;\n}\n\nexport interface InteractionConfig {\n trackEveryNms?: number;\n enabled: boolean; // defaults to false\n batch: boolean; // defaults to false\n /**\n * UGC filter rules.\n */\n ugcFilterRules?: UGCFilterRule[];\n}\n\nexport interface LoggingConfig {\n console: {\n enabled: boolean;\n levels: ConsoleLogLevel[];\n };\n network?: {\n enabled: boolean;\n body?: {\n request?: boolean;\n response?: boolean;\n maxBodySizeBytes?: number;\n };\n };\n}\n\nexport type TargetingConfig = TargetingFlag;\n\nexport type SessionReplayRemoteConfig = {\n sr_sampling_config?: SamplingConfig;\n sr_privacy_config?: PrivacyConfig;\n sr_interaction_config?: InteractionConfig;\n sr_logging_config?: LoggingConfig;\n sr_targeting_config?: TargetingConfig;\n};\n\nexport interface SessionReplayRemoteConfigAPIResponse {\n configs: {\n sessionReplay: SessionReplayRemoteConfig;\n };\n}\n\nexport type MaskLevel =\n | 'light' // only mask a subset of inputs that's deemed sensitive - password, credit card, telephone #, email. These are information we never want to capture.\n | 'medium' // mask all inputs\n | 'conservative'; // mask all inputs and all texts\n\nexport const DEFAULT_MASK_LEVEL = 'medium';\n\n// err on the side of excluding more\nexport type PrivacyConfig = {\n blockSelector?: string | string[]; // exclude in the UI\n defaultMaskLevel?: MaskLevel;\n maskSelector?: string[];\n unmaskSelector?: string[];\n maskAttributes?: string[]; // HTML attribute names to mask (e.g. [\"placeholder\", \"aria-label\"])\n /**\n * Per-URL overrides for `defaultMaskLevel`. Each entry contains a glob pattern (`match`)\n * and a `maskLevel` to apply when the current page URL matches that pattern.\n * Rules are evaluated in order; the first match wins. Remote rules take precedence\n * over local rules (remote entries are prepended before local entries).\n *\n * @example\n * urlMaskLevels: [\n * { match: 'https://example.com/checkout/*', maskLevel: 'conservative' },\n * { match: 'https://example.com/public/*', maskLevel: 'light' },\n * ]\n */\n urlMaskLevels?: Array<{ match: string; maskLevel: MaskLevel }>;\n};\n\n/**\n * UGC filter rule.\n */\nexport type UGCFilterRule = {\n /**\n * The selector of the UGC element.\n */\n selector: string;\n /**\n * The replacement text for the UGC element.\n */\n replacement: string;\n};\n\nexport interface SessionReplayLocalConfig extends IConfig {\n apiKey: string;\n loggerProvider: ILogger;\n /**\n * LogLevel.None or LogLevel.Error or LogLevel.Warn or LogLevel.Verbose or LogLevel.Debug.\n * Sets the log level.\n *\n * @defaultValue LogLevel.Warn\n */\n logLevel: LogLevel;\n /**\n * The maximum number of retries allowed for sending replay events.\n * Once this limit is reached, failed events will no longer be sent.\n *\n * @defaultValue 2\n */\n flushMaxRetries: number;\n /**\n * Use this option to control how many sessions to select for replay collection.\n * The number should be a decimal between 0 and 1, for example 0.4, representing\n * the fraction of sessions to have randomly selected for replay collection.\n * Over a large number of sessions, 0.4 would select 40% of those sessions.\n * Sample rates as small as six decimal places (0.000001) are supported.\n *\n * @defaultValue 0\n */\n sampleRate: number;\n privacyConfig?: PrivacyConfig;\n /**\n * Adds additional debug event property to help debug instrumentation issues\n * (such as mismatching apps). Only recommended for debugging initial setup,\n * and not recommended for production.\n */\n debugMode?: boolean;\n /**\n * Specifies the endpoint URL to fetch remote configuration.\n * If provided, it overrides the default server zone configuration.\n */\n configServerUrl?: string;\n /**\n * Specifies the endpoint URL for sending session replay data.\n * If provided, it overrides the default server zone configuration.\n */\n trackServerUrl?: string;\n /**\n * If stylesheets are inlined, the contents of the stylesheet will be stored.\n * During replay, the stored stylesheet will be used instead of attempting to fetch it remotely.\n * This prevents replays from appearing broken due to missing stylesheets.\n * Note: Inlining stylesheets may not work in all cases.\n */\n shouldInlineStylesheet?: boolean;\n version?: SessionReplayVersion;\n /**\n * Performance configuration config. If enabled, we will defer compression\n * to be done during the browser's idle periods.\n */\n performanceConfig?: SessionReplayPerformanceConfig;\n /**\n * Specifies how replay events should be stored. `idb` uses IndexedDB to persist replay events\n * when all events cannot be sent during capture. `memory` stores replay events only in memory,\n * meaning events are lost when the page is closed. If IndexedDB is unavailable, the system falls back to `memory`.\n */\n storeType: StoreType;\n\n /**\n * If true, the SDK will compress replay events using a web worker.\n * This offloads compression to a separate thread, improving performance on the main thread.\n *\n * @defaultValue false\n */\n useWebWorker?: boolean;\n\n userProperties?: { [key: string]: any };\n\n /**\n * Remove certain parts of the DOM from being captured. These are typically ignored when blocking by selectors.\n */\n omitElementTags?: {\n /**\n * If true, removes script tags from the DOM, but not noscript tags.\n */\n script?: boolean;\n /**\n * If true, removes comment tags from the DOM.\n */\n comment?: boolean;\n };\n\n /**\n * If true, applies a background color to blocked elements in the replay.\n * This helps visualize which elements are blocked from being captured.\n */\n applyBackgroundColorToBlockedElements?: boolean;\n /**\n * Enables URL change polling as a fallback for SPA route tracking.\n * When enabled, the SDK will periodically check for URL changes every second\n * in addition to patching the History API. This is useful for edge cases where\n * route changes might bypass the standard History API methods.\n *\n * @defaultValue false\n */\n enableUrlChangePolling?: boolean;\n /**\n * Specifies the interval in milliseconds for URL change polling when enableUrlChangePolling is true.\n * The SDK will check for URL changes at this interval as a fallback for SPA route tracking.\n *\n * @defaultValue 1000\n */\n urlChangePollingInterval?: number;\n /**\n * Whether to capture document title in URL change events.\n * When disabled, the title field will be empty in URL change events.\n *\n * @defaultValue false\n */\n captureDocumentTitle?: boolean;\n interactionConfig?: InteractionConfig;\n /**\n * When true (default), the CSS rules of any `adoptedStyleSheets` on shadow roots and\n * the document are serialized **inline** within the full snapshot. This makes the snapshot\n * self-contained so that shadow DOM styles are replayed correctly even if subsequent\n * incremental `AdoptedStyleSheet` events are dropped in transit.\n *\n * Set to `false` to revert to the legacy behavior where adopted stylesheet rules are\n * emitted as separate incremental events (which may be lost if delivery is unreliable).\n * Only consider opting out if snapshot payload size is a critical concern.\n *\n * @defaultValue true\n */\n captureAdoptedStyleSheets?: boolean;\n}\n\nexport interface SessionReplayJoinedConfig extends SessionReplayLocalConfig {\n captureEnabled?: boolean;\n interactionConfig?: InteractionConfig;\n loggingConfig?: LoggingConfig;\n targetingConfig?: TargetingConfig;\n}\n\nexport interface SessionReplayConfigs {\n localConfig: SessionReplayLocalConfig;\n joinedConfig: SessionReplayJoinedConfig;\n remoteConfig: SessionReplayRemoteConfig | undefined;\n}\nexport interface SessionReplayJoinedConfigGenerator {\n generateJoinedConfig: () => Promise<SessionReplayConfigs>;\n}\n\nexport interface SessionReplayMetadata {\n remoteConfig: SessionReplayRemoteConfig | undefined;\n localConfig: SessionReplayLocalConfig;\n joinedConfig: SessionReplayJoinedConfig;\n framework?: {\n name: string;\n version: string;\n };\n sessionId: string | number | undefined;\n hashValue?: number;\n sampleRate: number;\n replaySDKType: string | null;\n replaySDKVersion: string | undefined;\n standaloneSDKType: string;\n standaloneSDKVersion: string | undefined;\n}\n\nexport interface SessionReplayVersion {\n version: string;\n type: SessionReplayType;\n}\n\n/**\n * Configuration options for session replay performance.\n */\nexport interface SessionReplayPerformanceConfig {\n /**\n * If enabled, event compression will be deferred to occur during the browser's idle periods.\n */\n enabled: boolean;\n /**\n * Optional timeout in milliseconds for the `requestIdleCallback` API.\n * If specified, this value will be used to set a maximum time for the browser to wait\n * before executing the deferred compression task, even if the browser is not idle.\n */\n timeout?: number;\n /**\n * Performance configuration for interaction tracking (clicks, scrolls).\n */\n interaction?: InteractionPerformanceConfig;\n}\n\n/**\n * Performance configuration for interaction tracking, specifically for CSS selector generation.\n */\nexport interface InteractionPerformanceConfig {\n /**\n * Maximum time in milliseconds allowed for CSS selector generation.\n * If selector generation takes longer than this, it will throw a timeout error.\n * Default: undefined (no timeout limit)\n */\n timeoutMs?: number;\n /**\n * Maximum number of attempts to optimize/simplify the CSS selector path.\n * Higher values may produce shorter selectors but take longer to compute.\n * Default: 10000\n */\n maxNumberOfTries?: number;\n /**\n * Maximum number of CSS selector combinations to test for uniqueness.\n * If more combinations would be generated, falls back to a simpler strategy.\n * Default: 1000\n */\n threshold?: number;\n}\n\nexport type SessionReplayType = 'standalone' | 'plugin' | 'segment';\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":";;;AAuDa,QAAA,kBAAkB,GAAG,QAAQ,CAAC","sourcesContent":["import { IConfig, LogLevel, ILogger } from '@amplitude/analytics-core';\nimport { StoreType, ConsoleLogLevel } from '../typings/session-replay';\nimport { TargetingFlag } from '@amplitude/targeting';\n\nexport interface SamplingConfig {\n sample_rate: number;\n capture_enabled: boolean;\n}\n\nexport interface InteractionConfig {\n trackEveryNms?: number;\n enabled: boolean; // defaults to false\n batch: boolean; // defaults to false\n /**\n * UGC filter rules.\n */\n ugcFilterRules?: UGCFilterRule[];\n}\n\nexport interface LoggingConfig {\n console: {\n enabled: boolean;\n levels: ConsoleLogLevel[];\n };\n network?: {\n enabled: boolean;\n body?: {\n request?: boolean;\n response?: boolean;\n maxBodySizeBytes?: number;\n };\n };\n}\n\nexport type TargetingConfig = TargetingFlag;\n\nexport type SessionReplayRemoteConfig = {\n sr_sampling_config?: SamplingConfig;\n sr_privacy_config?: PrivacyConfig;\n sr_interaction_config?: InteractionConfig;\n sr_logging_config?: LoggingConfig;\n sr_targeting_config?: TargetingConfig;\n};\n\nexport interface SessionReplayRemoteConfigAPIResponse {\n configs: {\n sessionReplay: SessionReplayRemoteConfig;\n };\n}\n\nexport type MaskLevel =\n | 'light' // only mask a subset of inputs that's deemed sensitive - password, credit card, telephone #, email. These are information we never want to capture.\n | 'medium' // mask all inputs\n | 'conservative'; // mask all inputs and all texts\n\nexport const DEFAULT_MASK_LEVEL = 'medium';\n\n// err on the side of excluding more\nexport type PrivacyConfig = {\n blockSelector?: string | string[]; // exclude in the UI\n defaultMaskLevel?: MaskLevel;\n maskSelector?: string[];\n unmaskSelector?: string[];\n maskAttributes?: string[]; // HTML attribute names to mask (e.g. [\"placeholder\", \"aria-label\"])\n /**\n * Per-URL overrides for `defaultMaskLevel`. Each entry contains a glob pattern (`match`)\n * and a `maskLevel` to apply when the current page URL matches that pattern.\n * Rules are evaluated in order; the first match wins. Remote rules take precedence\n * over local rules (remote entries are prepended before local entries).\n *\n * @example\n * urlMaskLevels: [\n * { match: 'https://example.com/checkout/*', maskLevel: 'conservative' },\n * { match: 'https://example.com/public/*', maskLevel: 'light' },\n * ]\n */\n urlMaskLevels?: Array<{ match: string; maskLevel: MaskLevel }>;\n};\n\n/**\n * UGC filter rule.\n */\nexport type UGCFilterRule = {\n /**\n * The selector of the UGC element.\n */\n selector: string;\n /**\n * The replacement text for the UGC element.\n */\n replacement: string;\n};\n\nexport interface SessionReplayLocalConfig extends IConfig {\n apiKey: string;\n loggerProvider: ILogger;\n /**\n * LogLevel.None or LogLevel.Error or LogLevel.Warn or LogLevel.Verbose or LogLevel.Debug.\n * Sets the log level.\n *\n * @defaultValue LogLevel.Warn\n */\n logLevel: LogLevel;\n /**\n * The maximum number of retries allowed for sending replay events.\n * Once this limit is reached, failed events will no longer be sent.\n *\n * @defaultValue 2\n */\n flushMaxRetries: number;\n /**\n * Use this option to control how many sessions to select for replay collection.\n * The number should be a decimal between 0 and 1, for example 0.4, representing\n * the fraction of sessions to have randomly selected for replay collection.\n * Over a large number of sessions, 0.4 would select 40% of those sessions.\n * Sample rates as small as six decimal places (0.000001) are supported.\n *\n * @defaultValue 0\n */\n sampleRate: number;\n privacyConfig?: PrivacyConfig;\n /**\n * Adds additional debug event property to help debug instrumentation issues\n * (such as mismatching apps). Only recommended for debugging initial setup,\n * and not recommended for production.\n */\n debugMode?: boolean;\n /**\n * Specifies the endpoint URL to fetch remote configuration.\n * If provided, it overrides the default server zone configuration.\n */\n configServerUrl?: string;\n /**\n * Specifies the endpoint URL for sending session replay data.\n * If provided, it overrides the default server zone configuration.\n */\n trackServerUrl?: string;\n /**\n * If stylesheets are inlined, the contents of the stylesheet will be stored.\n * During replay, the stored stylesheet will be used instead of attempting to fetch it remotely.\n * This prevents replays from appearing broken due to missing stylesheets.\n * Note: Inlining stylesheets may not work in all cases.\n */\n shouldInlineStylesheet?: boolean;\n version?: SessionReplayVersion;\n /**\n * Performance configuration config. If enabled, we will defer compression\n * to be done during the browser's idle periods.\n */\n performanceConfig?: SessionReplayPerformanceConfig;\n /**\n * Specifies how replay events should be stored. `idb` uses IndexedDB to persist replay events\n * when all events cannot be sent during capture. `memory` stores replay events only in memory,\n * meaning events are lost when the page is closed. If IndexedDB is unavailable, the system falls back to `memory`.\n */\n storeType: StoreType;\n\n /**\n * If true, the SDK will compress replay events using a web worker.\n * This offloads compression to a separate thread, improving performance on the main thread.\n *\n * @defaultValue false\n */\n useWebWorker?: boolean;\n\n userProperties?: { [key: string]: any };\n\n /**\n * Remove certain parts of the DOM from being captured. These are typically ignored when blocking by selectors.\n */\n omitElementTags?: {\n /**\n * If true, removes script tags from the DOM, but not noscript tags.\n */\n script?: boolean;\n /**\n * If true, removes comment tags from the DOM.\n */\n comment?: boolean;\n };\n\n /**\n * If true, applies a background color to blocked elements in the replay.\n * This helps visualize which elements are blocked from being captured.\n */\n applyBackgroundColorToBlockedElements?: boolean;\n /**\n * Enables URL change polling as a fallback for SPA route tracking.\n * When enabled, the SDK will periodically check for URL changes every second\n * in addition to patching the History API. This is useful for edge cases where\n * route changes might bypass the standard History API methods.\n *\n * @defaultValue false\n */\n enableUrlChangePolling?: boolean;\n /**\n * Specifies the interval in milliseconds for URL change polling when enableUrlChangePolling is true.\n * The SDK will check for URL changes at this interval as a fallback for SPA route tracking.\n *\n * @defaultValue 1000\n */\n urlChangePollingInterval?: number;\n /**\n * Whether to capture document title in URL change events.\n * When disabled, the title field will be empty in URL change events.\n *\n * @defaultValue false\n */\n captureDocumentTitle?: boolean;\n interactionConfig?: InteractionConfig;\n /**\n * When true (default), the CSS rules of any `adoptedStyleSheets` on shadow roots and\n * the document are serialized **inline** within the full snapshot. This makes the snapshot\n * self-contained so that shadow DOM styles are replayed correctly even if subsequent\n * incremental `AdoptedStyleSheet` events are dropped in transit.\n *\n * Set to `false` to revert to the legacy behavior where adopted stylesheet rules are\n * emitted as separate incremental events (which may be lost if delivery is unreliable).\n * Only consider opting out if snapshot payload size is a critical concern.\n *\n * @defaultValue true\n */\n captureAdoptedStyleSheets?: boolean;\n}\n\nexport interface SessionReplayJoinedConfig extends SessionReplayLocalConfig {\n captureEnabled?: boolean;\n interactionConfig?: InteractionConfig;\n loggingConfig?: LoggingConfig;\n targetingConfig?: TargetingConfig;\n}\n\nexport interface SessionReplayConfigs {\n localConfig: SessionReplayLocalConfig;\n joinedConfig: SessionReplayJoinedConfig;\n remoteConfig: SessionReplayRemoteConfig | undefined;\n}\nexport interface SessionReplayJoinedConfigGenerator {\n generateJoinedConfig: () => Promise<SessionReplayConfigs>;\n}\n\nexport interface SessionReplayMetadata {\n remoteConfig: SessionReplayRemoteConfig | undefined;\n localConfig: SessionReplayLocalConfig;\n joinedConfig: SessionReplayJoinedConfig;\n framework?: {\n name: string;\n version: string;\n };\n sessionId: string | number | undefined;\n hashValue?: number;\n sampleRate: number;\n replaySDKType: string | null;\n replaySDKVersion: string | undefined;\n standaloneSDKType: string;\n standaloneSDKVersion: string | undefined;\n}\n\nexport interface SessionReplayVersion {\n version: string;\n type: SessionReplayType;\n}\n\n/**\n * Configuration options for session replay performance.\n */\nexport interface SessionReplayPerformanceConfig {\n /**\n * If enabled, event compression will be deferred to occur during the browser's idle periods.\n */\n enabled: boolean;\n /**\n * Optional timeout in milliseconds for the `requestIdleCallback` API.\n * If specified, this value will be used to set a maximum time for the browser to wait\n * before executing the deferred compression task, even if the browser is not idle.\n */\n timeout?: number;\n /**\n * If enabled, consecutive mutation events will be merged into a single event before\n * compression, reducing stored event count without changing replay semantics.\n * Defaults to false.\n */\n mergeMutations?: boolean;\n /**\n * Performance configuration for interaction tracking (clicks, scrolls).\n */\n interaction?: InteractionPerformanceConfig;\n}\n\n/**\n * Performance configuration for interaction tracking, specifically for CSS selector generation.\n */\nexport interface InteractionPerformanceConfig {\n /**\n * Maximum time in milliseconds allowed for CSS selector generation.\n * If selector generation takes longer than this, it will throw a timeout error.\n * Default: undefined (no timeout limit)\n */\n timeoutMs?: number;\n /**\n * Maximum number of attempts to optimize/simplify the CSS selector path.\n * Higher values may produce shorter selectors but take longer to compute.\n * Default: 10000\n */\n maxNumberOfTries?: number;\n /**\n * Maximum number of CSS selector combinations to test for uniqueness.\n * If more combinations would be generated, falls back to a simpler strategy.\n * Default: 1000\n */\n threshold?: number;\n}\n\nexport type SessionReplayType = 'standalone' | 'plugin' | 'segment';\n"]}
|
|
@@ -7,6 +7,7 @@ interface TaskQueue {
|
|
|
7
7
|
}
|
|
8
8
|
export declare class EventCompressor {
|
|
9
9
|
taskQueue: TaskQueue[];
|
|
10
|
+
pendingQueue: TaskQueue[];
|
|
10
11
|
isProcessing: boolean;
|
|
11
12
|
eventsManager?: SessionReplayEventsManager<'replay' | 'interaction', string>;
|
|
12
13
|
config: SessionReplayJoinedConfig;
|
|
@@ -22,6 +23,7 @@ export declare class EventCompressor {
|
|
|
22
23
|
compressEvent: (event: eventWithTime) => string;
|
|
23
24
|
private addCompressedEventToManager;
|
|
24
25
|
addCompressedEvent: (event: eventWithTime, sessionId: string | number) => void;
|
|
26
|
+
private mergeMutationTasks;
|
|
25
27
|
terminate: () => void;
|
|
26
28
|
}
|
|
27
29
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-compressor.d.ts","sourceRoot":"","sources":["../../../src/events/event-compressor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"event-compressor.d.ts","sourceRoot":"","sources":["../../../src/events/event-compressor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAGvE,UAAU,SAAS;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAGD,qBAAa,eAAe;IAC1B,SAAS,EAAE,SAAS,EAAE,CAAM;IAC5B,YAAY,EAAE,SAAS,EAAE,CAAM;IAC/B,YAAY,UAAS;IACrB,aAAa,CAAC,EAAE,0BAA0B,CAAC,QAAQ,GAAG,aAAa,EAAE,MAAM,CAAC,CAAC;IAC7E,MAAM,EAAE,yBAAyB,CAAC;IAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,kBAAkB,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB,CAAC,EAAE,MAAM,IAAI,CAAC;gBAGnC,aAAa,EAAE,0BAA0B,CAAC,QAAQ,GAAG,aAAa,EAAE,MAAM,CAAC,EAC3E,MAAM,EAAE,yBAAyB,EACjC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,YAAY,CAAC,EAAE,MAAM,EACrB,uBAAuB,CAAC,EAAE,MAAM,IAAI;IAuC/B,sBAAsB,IAAI,IAAI;IAa9B,YAAY,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAmCpE,YAAY,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI;IA6BrD,aAAa,UAAW,aAAa,KAAG,MAAM,CAS5C;IAEF,OAAO,CAAC,2BAA2B,CAQjC;IAEK,kBAAkB,UAAW,aAAa,aAAa,MAAM,GAAG,MAAM,UAkB3E;IAKF,OAAO,CAAC,kBAAkB;IA4BnB,SAAS,aAEd;CACH"}
|
|
@@ -4,12 +4,14 @@ exports.EventCompressor = void 0;
|
|
|
4
4
|
var tslib_1 = require("tslib");
|
|
5
5
|
var analytics_core_1 = require("@amplitude/analytics-core");
|
|
6
6
|
var rrweb_types_1 = require("@amplitude/rrweb-types");
|
|
7
|
+
var merge_mutation_events_1 = require("./merge-mutation-events");
|
|
7
8
|
var DEFAULT_TIMEOUT = 2000;
|
|
8
9
|
var EventCompressor = /** @class */ (function () {
|
|
9
10
|
function EventCompressor(eventsManager, config, deviceId, workerScript, onFullSnapshotProcessed) {
|
|
10
11
|
var _this = this;
|
|
11
12
|
var _a;
|
|
12
13
|
this.taskQueue = [];
|
|
14
|
+
this.pendingQueue = [];
|
|
13
15
|
this.isProcessing = false;
|
|
14
16
|
this.compressEvent = function (event) {
|
|
15
17
|
// Serialize with type+timestamp first for streaming parser compatibility.
|
|
@@ -109,10 +111,11 @@ var EventCompressor = /** @class */ (function () {
|
|
|
109
111
|
// Those events reference the pre-snapshot DOM and must be sent before
|
|
110
112
|
// the full snapshot; if we let them be processed later they'd arrive at
|
|
111
113
|
// the server after the snapshot and cause "node not found" replay errors.
|
|
112
|
-
if (this.taskQueue.length > 0) {
|
|
114
|
+
if (this.taskQueue.length > 0 || this.pendingQueue.length > 0) {
|
|
115
|
+
var allTasks = tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(this.taskQueue.splice(0)), false), tslib_1.__read(this.mergeMutationTasks(this.pendingQueue.splice(0))), false);
|
|
113
116
|
try {
|
|
114
|
-
for (var
|
|
115
|
-
var task =
|
|
117
|
+
for (var allTasks_1 = tslib_1.__values(allTasks), allTasks_1_1 = allTasks_1.next(); !allTasks_1_1.done; allTasks_1_1 = allTasks_1.next()) {
|
|
118
|
+
var task = allTasks_1_1.value;
|
|
116
119
|
var compressed = this.compressEvent(task.event);
|
|
117
120
|
this.addCompressedEventToManager(compressed, task.sessionId);
|
|
118
121
|
}
|
|
@@ -120,7 +123,7 @@ var EventCompressor = /** @class */ (function () {
|
|
|
120
123
|
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
121
124
|
finally {
|
|
122
125
|
try {
|
|
123
|
-
if (
|
|
126
|
+
if (allTasks_1_1 && !allTasks_1_1.done && (_a = allTasks_1.return)) _a.call(allTasks_1);
|
|
124
127
|
}
|
|
125
128
|
finally { if (e_1) throw e_1.error; }
|
|
126
129
|
}
|
|
@@ -133,7 +136,7 @@ var EventCompressor = /** @class */ (function () {
|
|
|
133
136
|
}
|
|
134
137
|
if (this.canUseIdleCallback && ((_c = this.config.performanceConfig) === null || _c === void 0 ? void 0 : _c.enabled)) {
|
|
135
138
|
this.config.loggerProvider.debug('Enqueuing event for processing during idle time.');
|
|
136
|
-
this.
|
|
139
|
+
this.pendingQueue.push({ event: event, sessionId: sessionId });
|
|
137
140
|
this.scheduleIdleProcessing();
|
|
138
141
|
}
|
|
139
142
|
else {
|
|
@@ -143,7 +146,14 @@ var EventCompressor = /** @class */ (function () {
|
|
|
143
146
|
};
|
|
144
147
|
// Process the task queue during idle time
|
|
145
148
|
EventCompressor.prototype.processQueue = function (idleDeadline) {
|
|
149
|
+
var _a;
|
|
146
150
|
var _this = this;
|
|
151
|
+
// Merge newly-arrived pending events and append to the already-merged taskQueue.
|
|
152
|
+
// Keeping them separate prevents re-merging already-merged tasks on subsequent calls,
|
|
153
|
+
// which would corrupt move semantics for nodes that appear in multiple merge passes.
|
|
154
|
+
if (this.pendingQueue.length > 0) {
|
|
155
|
+
(_a = this.taskQueue).push.apply(_a, tslib_1.__spreadArray([], tslib_1.__read(this.mergeMutationTasks(this.pendingQueue.splice(0))), false));
|
|
156
|
+
}
|
|
147
157
|
// Process tasks while there's idle time or until the max number of tasks is reached
|
|
148
158
|
while (this.taskQueue.length > 0 && (idleDeadline.timeRemaining() > 0 || idleDeadline.didTimeout)) {
|
|
149
159
|
var task = this.taskQueue.shift();
|
|
@@ -153,7 +163,7 @@ var EventCompressor = /** @class */ (function () {
|
|
|
153
163
|
}
|
|
154
164
|
}
|
|
155
165
|
// If there are still tasks in the queue, schedule the next idle callback
|
|
156
|
-
if (this.taskQueue.length > 0) {
|
|
166
|
+
if (this.taskQueue.length > 0 || this.pendingQueue.length > 0) {
|
|
157
167
|
requestIdleCallback(function (idleDeadline) {
|
|
158
168
|
_this.processQueue(idleDeadline);
|
|
159
169
|
}, { timeout: this.timeout });
|
|
@@ -162,6 +172,44 @@ var EventCompressor = /** @class */ (function () {
|
|
|
162
172
|
this.isProcessing = false;
|
|
163
173
|
}
|
|
164
174
|
};
|
|
175
|
+
// Merge consecutive mutation tasks with the same sessionId before processing,
|
|
176
|
+
// reducing the number of events serialized and stored without changing replay semantics.
|
|
177
|
+
// Only runs when performanceConfig.mergeMutations is explicitly enabled.
|
|
178
|
+
EventCompressor.prototype.mergeMutationTasks = function (tasks) {
|
|
179
|
+
var e_2, _a;
|
|
180
|
+
var _b;
|
|
181
|
+
if (!((_b = this.config.performanceConfig) === null || _b === void 0 ? void 0 : _b.mergeMutations))
|
|
182
|
+
return tasks;
|
|
183
|
+
if (tasks.length <= 1)
|
|
184
|
+
return tasks;
|
|
185
|
+
var result = [];
|
|
186
|
+
var i = 0;
|
|
187
|
+
while (i < tasks.length) {
|
|
188
|
+
var sessionId = tasks[i].sessionId;
|
|
189
|
+
// Find the end of the current session run
|
|
190
|
+
var j = i + 1;
|
|
191
|
+
while (j < tasks.length && tasks[j].sessionId === sessionId) {
|
|
192
|
+
j++;
|
|
193
|
+
}
|
|
194
|
+
// Merge consecutive mutations within this session run; non-mutations pass through unchanged
|
|
195
|
+
var merged = (0, merge_mutation_events_1.mergeMutationEvents)(tasks.slice(i, j).map(function (t) { return t.event; }));
|
|
196
|
+
try {
|
|
197
|
+
for (var merged_1 = (e_2 = void 0, tslib_1.__values(merged)), merged_1_1 = merged_1.next(); !merged_1_1.done; merged_1_1 = merged_1.next()) {
|
|
198
|
+
var event_2 = merged_1_1.value;
|
|
199
|
+
result.push({ event: event_2, sessionId: sessionId });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
203
|
+
finally {
|
|
204
|
+
try {
|
|
205
|
+
if (merged_1_1 && !merged_1_1.done && (_a = merged_1.return)) _a.call(merged_1);
|
|
206
|
+
}
|
|
207
|
+
finally { if (e_2) throw e_2.error; }
|
|
208
|
+
}
|
|
209
|
+
i = j;
|
|
210
|
+
}
|
|
211
|
+
return result;
|
|
212
|
+
};
|
|
165
213
|
return EventCompressor;
|
|
166
214
|
}());
|
|
167
215
|
exports.EventCompressor = EventCompressor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-compressor.js","sourceRoot":"","sources":["../../../src/events/event-compressor.ts"],"names":[],"mappings":"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","sourcesContent":["import { getGlobalScope } from '@amplitude/analytics-core';\nimport { EventType as RRWebEventType } from '@amplitude/rrweb-types';\nimport type { eventWithTime } from '@amplitude/rrweb-types';\nimport { SessionReplayJoinedConfig } from '../config/types';\nimport { SessionReplayEventsManager } from '../typings/session-replay';\n\ninterface TaskQueue {\n event: eventWithTime;\n sessionId: string | number;\n}\n\nconst DEFAULT_TIMEOUT = 2000;\nexport class EventCompressor {\n taskQueue: TaskQueue[] = [];\n isProcessing = false;\n eventsManager?: SessionReplayEventsManager<'replay' | 'interaction', string>;\n config: SessionReplayJoinedConfig;\n deviceId: string | undefined;\n canUseIdleCallback: boolean | undefined;\n timeout: number;\n worker?: Worker;\n onFullSnapshotProcessed?: () => void;\n\n constructor(\n eventsManager: SessionReplayEventsManager<'replay' | 'interaction', string>,\n config: SessionReplayJoinedConfig,\n deviceId: string | undefined,\n workerScript?: string,\n onFullSnapshotProcessed?: () => void,\n ) {\n const globalScope = getGlobalScope();\n this.canUseIdleCallback = globalScope && 'requestIdleCallback' in globalScope;\n this.eventsManager = eventsManager;\n this.config = config;\n this.deviceId = deviceId;\n this.timeout = config.performanceConfig?.timeout || DEFAULT_TIMEOUT;\n this.onFullSnapshotProcessed = onFullSnapshotProcessed;\n\n if (workerScript) {\n config.loggerProvider.log('Enabling web worker for compression');\n\n try {\n const blob = new Blob([workerScript], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(blobUrl);\n\n worker.onerror = (e) => {\n e.preventDefault();\n config.loggerProvider.error(\n `Worker failed, falling back to non-worker compression: ${e.message} (${e.filename}:${e.lineno})`,\n );\n worker.terminate();\n this.worker = undefined;\n };\n worker.onmessage = (e) => {\n const { compressedEvent, sessionId } = e.data as Record<string, string>;\n this.addCompressedEventToManager(compressedEvent, sessionId);\n };\n\n this.worker = worker;\n } catch (error) {\n config.loggerProvider.error('Failed to create worker, falling back to non-worker compression:', error);\n }\n }\n }\n\n // Schedule processing during idle time\n public scheduleIdleProcessing(): void {\n if (!this.isProcessing) {\n this.isProcessing = true;\n requestIdleCallback(\n (idleDeadline) => {\n this.processQueue(idleDeadline);\n },\n { timeout: this.timeout },\n );\n }\n }\n\n // Add an event to the task queue if idle callback is supported or compress the event directly\n public enqueueEvent(event: eventWithTime, sessionId: string | number): void {\n // Full snapshot (type 2) is the most critical event — a replay cannot be played without it.\n // Process and flush immediately rather than waiting for the idle scheduler or web worker,\n // maximising the chance it is delivered before the user exits the page.\n if (event.type === RRWebEventType.FullSnapshot) {\n this.config.loggerProvider.debug('Processing full snapshot immediately.');\n // Drain any events still pending in the idle-callback queue first.\n // Those events reference the pre-snapshot DOM and must be sent before\n // the full snapshot; if we let them be processed later they'd arrive at\n // the server after the snapshot and cause \"node not found\" replay errors.\n if (this.taskQueue.length > 0) {\n for (const task of this.taskQueue.splice(0)) {\n const compressed = this.compressEvent(task.event);\n this.addCompressedEventToManager(compressed, task.sessionId);\n }\n this.isProcessing = false;\n }\n const compressedEvent = this.compressEvent(event);\n this.addCompressedEventToManager(compressedEvent, sessionId);\n this.onFullSnapshotProcessed?.();\n return;\n }\n\n if (this.canUseIdleCallback && this.config.performanceConfig?.enabled) {\n this.config.loggerProvider.debug('Enqueuing event for processing during idle time.');\n this.taskQueue.push({ event, sessionId });\n this.scheduleIdleProcessing();\n } else {\n this.config.loggerProvider.debug('Processing event without idle callback.');\n this.addCompressedEvent(event, sessionId);\n }\n }\n\n // Process the task queue during idle time\n public processQueue(idleDeadline: IdleDeadline): void {\n // Process tasks while there's idle time or until the max number of tasks is reached\n while (this.taskQueue.length > 0 && (idleDeadline.timeRemaining() > 0 || idleDeadline.didTimeout)) {\n const task = this.taskQueue.shift();\n if (task) {\n const { event, sessionId } = task;\n this.addCompressedEvent(event, sessionId);\n }\n }\n\n // If there are still tasks in the queue, schedule the next idle callback\n if (this.taskQueue.length > 0) {\n requestIdleCallback(\n (idleDeadline) => {\n this.processQueue(idleDeadline);\n },\n { timeout: this.timeout },\n );\n } else {\n this.isProcessing = false;\n }\n }\n\n compressEvent = (event: eventWithTime): string => {\n // Serialize with type+timestamp first for streaming parser compatibility.\n // JS engines serialize non-integer string keys in insertion order (ES2015 spec,\n // reliable across V8/SpiderMonkey/JSC), so explicit construction controls key order.\n // `delay` is an rrweb player field: an optional ms offset applied on top of\n // `timestamp` during replay to smooth out batched/throttled events. Preserve\n // it when present so playback timing is accurate.\n const { type, timestamp, delay, data } = event as eventWithTime & { delay?: number };\n return delay != null ? JSON.stringify({ type, timestamp, delay, data }) : JSON.stringify({ type, timestamp, data });\n };\n\n private addCompressedEventToManager = (compressedEvent: string, sessionId: string | number) => {\n if (this.eventsManager && this.deviceId) {\n this.eventsManager.addEvent({\n event: { type: 'replay', data: compressedEvent },\n sessionId,\n deviceId: this.deviceId,\n });\n }\n };\n\n public addCompressedEvent = (event: eventWithTime, sessionId: string | number) => {\n if (this.worker) {\n // This indirectly compresses the event.\n try {\n this.worker.postMessage({ event, sessionId });\n } catch (err: any) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n if (err.name === 'DataCloneError') {\n // fallback: serialize\n this.worker.postMessage(JSON.stringify({ event, sessionId }));\n } else {\n this.config.loggerProvider.warn('Unexpected error while posting message to worker:', err);\n }\n }\n } else {\n const compressedEvent = this.compressEvent(event);\n this.addCompressedEventToManager(compressedEvent, sessionId);\n }\n };\n\n public terminate = () => {\n this.worker?.terminate();\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"event-compressor.js","sourceRoot":"","sources":["../../../src/events/event-compressor.ts"],"names":[],"mappings":"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","sourcesContent":["import { getGlobalScope } from '@amplitude/analytics-core';\nimport { EventType as RRWebEventType } from '@amplitude/rrweb-types';\nimport type { eventWithTime } from '@amplitude/rrweb-types';\nimport { SessionReplayJoinedConfig } from '../config/types';\nimport { SessionReplayEventsManager } from '../typings/session-replay';\nimport { mergeMutationEvents } from './merge-mutation-events';\n\ninterface TaskQueue {\n event: eventWithTime;\n sessionId: string | number;\n}\n\nconst DEFAULT_TIMEOUT = 2000;\nexport class EventCompressor {\n taskQueue: TaskQueue[] = [];\n pendingQueue: TaskQueue[] = [];\n isProcessing = false;\n eventsManager?: SessionReplayEventsManager<'replay' | 'interaction', string>;\n config: SessionReplayJoinedConfig;\n deviceId: string | undefined;\n canUseIdleCallback: boolean | undefined;\n timeout: number;\n worker?: Worker;\n onFullSnapshotProcessed?: () => void;\n\n constructor(\n eventsManager: SessionReplayEventsManager<'replay' | 'interaction', string>,\n config: SessionReplayJoinedConfig,\n deviceId: string | undefined,\n workerScript?: string,\n onFullSnapshotProcessed?: () => void,\n ) {\n const globalScope = getGlobalScope();\n this.canUseIdleCallback = globalScope && 'requestIdleCallback' in globalScope;\n this.eventsManager = eventsManager;\n this.config = config;\n this.deviceId = deviceId;\n this.timeout = config.performanceConfig?.timeout || DEFAULT_TIMEOUT;\n this.onFullSnapshotProcessed = onFullSnapshotProcessed;\n\n if (workerScript) {\n config.loggerProvider.log('Enabling web worker for compression');\n\n try {\n const blob = new Blob([workerScript], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(blobUrl);\n\n worker.onerror = (e) => {\n e.preventDefault();\n config.loggerProvider.error(\n `Worker failed, falling back to non-worker compression: ${e.message} (${e.filename}:${e.lineno})`,\n );\n worker.terminate();\n this.worker = undefined;\n };\n worker.onmessage = (e) => {\n const { compressedEvent, sessionId } = e.data as Record<string, string>;\n this.addCompressedEventToManager(compressedEvent, sessionId);\n };\n\n this.worker = worker;\n } catch (error) {\n config.loggerProvider.error('Failed to create worker, falling back to non-worker compression:', error);\n }\n }\n }\n\n // Schedule processing during idle time\n public scheduleIdleProcessing(): void {\n if (!this.isProcessing) {\n this.isProcessing = true;\n requestIdleCallback(\n (idleDeadline) => {\n this.processQueue(idleDeadline);\n },\n { timeout: this.timeout },\n );\n }\n }\n\n // Add an event to the task queue if idle callback is supported or compress the event directly\n public enqueueEvent(event: eventWithTime, sessionId: string | number): void {\n // Full snapshot (type 2) is the most critical event — a replay cannot be played without it.\n // Process and flush immediately rather than waiting for the idle scheduler or web worker,\n // maximising the chance it is delivered before the user exits the page.\n if (event.type === RRWebEventType.FullSnapshot) {\n this.config.loggerProvider.debug('Processing full snapshot immediately.');\n // Drain any events still pending in the idle-callback queue first.\n // Those events reference the pre-snapshot DOM and must be sent before\n // the full snapshot; if we let them be processed later they'd arrive at\n // the server after the snapshot and cause \"node not found\" replay errors.\n if (this.taskQueue.length > 0 || this.pendingQueue.length > 0) {\n const allTasks = [...this.taskQueue.splice(0), ...this.mergeMutationTasks(this.pendingQueue.splice(0))];\n for (const task of allTasks) {\n const compressed = this.compressEvent(task.event);\n this.addCompressedEventToManager(compressed, task.sessionId);\n }\n this.isProcessing = false;\n }\n const compressedEvent = this.compressEvent(event);\n this.addCompressedEventToManager(compressedEvent, sessionId);\n this.onFullSnapshotProcessed?.();\n return;\n }\n\n if (this.canUseIdleCallback && this.config.performanceConfig?.enabled) {\n this.config.loggerProvider.debug('Enqueuing event for processing during idle time.');\n this.pendingQueue.push({ event, sessionId });\n this.scheduleIdleProcessing();\n } else {\n this.config.loggerProvider.debug('Processing event without idle callback.');\n this.addCompressedEvent(event, sessionId);\n }\n }\n\n // Process the task queue during idle time\n public processQueue(idleDeadline: IdleDeadline): void {\n // Merge newly-arrived pending events and append to the already-merged taskQueue.\n // Keeping them separate prevents re-merging already-merged tasks on subsequent calls,\n // which would corrupt move semantics for nodes that appear in multiple merge passes.\n if (this.pendingQueue.length > 0) {\n this.taskQueue.push(...this.mergeMutationTasks(this.pendingQueue.splice(0)));\n }\n // Process tasks while there's idle time or until the max number of tasks is reached\n while (this.taskQueue.length > 0 && (idleDeadline.timeRemaining() > 0 || idleDeadline.didTimeout)) {\n const task = this.taskQueue.shift();\n if (task) {\n const { event, sessionId } = task;\n this.addCompressedEvent(event, sessionId);\n }\n }\n\n // If there are still tasks in the queue, schedule the next idle callback\n if (this.taskQueue.length > 0 || this.pendingQueue.length > 0) {\n requestIdleCallback(\n (idleDeadline) => {\n this.processQueue(idleDeadline);\n },\n { timeout: this.timeout },\n );\n } else {\n this.isProcessing = false;\n }\n }\n\n compressEvent = (event: eventWithTime): string => {\n // Serialize with type+timestamp first for streaming parser compatibility.\n // JS engines serialize non-integer string keys in insertion order (ES2015 spec,\n // reliable across V8/SpiderMonkey/JSC), so explicit construction controls key order.\n // `delay` is an rrweb player field: an optional ms offset applied on top of\n // `timestamp` during replay to smooth out batched/throttled events. Preserve\n // it when present so playback timing is accurate.\n const { type, timestamp, delay, data } = event as eventWithTime & { delay?: number };\n return delay != null ? JSON.stringify({ type, timestamp, delay, data }) : JSON.stringify({ type, timestamp, data });\n };\n\n private addCompressedEventToManager = (compressedEvent: string, sessionId: string | number) => {\n if (this.eventsManager && this.deviceId) {\n this.eventsManager.addEvent({\n event: { type: 'replay', data: compressedEvent },\n sessionId,\n deviceId: this.deviceId,\n });\n }\n };\n\n public addCompressedEvent = (event: eventWithTime, sessionId: string | number) => {\n if (this.worker) {\n // This indirectly compresses the event.\n try {\n this.worker.postMessage({ event, sessionId });\n } catch (err: any) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n if (err.name === 'DataCloneError') {\n // fallback: serialize\n this.worker.postMessage(JSON.stringify({ event, sessionId }));\n } else {\n this.config.loggerProvider.warn('Unexpected error while posting message to worker:', err);\n }\n }\n } else {\n const compressedEvent = this.compressEvent(event);\n this.addCompressedEventToManager(compressedEvent, sessionId);\n }\n };\n\n // Merge consecutive mutation tasks with the same sessionId before processing,\n // reducing the number of events serialized and stored without changing replay semantics.\n // Only runs when performanceConfig.mergeMutations is explicitly enabled.\n private mergeMutationTasks(tasks: TaskQueue[]): TaskQueue[] {\n if (!this.config.performanceConfig?.mergeMutations) return tasks;\n if (tasks.length <= 1) return tasks;\n\n const result: TaskQueue[] = [];\n let i = 0;\n\n while (i < tasks.length) {\n const sessionId = tasks[i].sessionId;\n\n // Find the end of the current session run\n let j = i + 1;\n while (j < tasks.length && tasks[j].sessionId === sessionId) {\n j++;\n }\n\n // Merge consecutive mutations within this session run; non-mutations pass through unchanged\n const merged = mergeMutationEvents(tasks.slice(i, j).map((t) => t.event));\n for (const event of merged) {\n result.push({ event, sessionId });\n }\n\n i = j;\n }\n\n return result;\n }\n\n public terminate = () => {\n this.worker?.terminate();\n };\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { eventWithTime } from '@amplitude/rrweb-types';
|
|
2
|
+
/**
|
|
3
|
+
* Merges consecutive IncrementalSnapshot mutation events into a single event,
|
|
4
|
+
* reducing overall event count without changing replay semantics.
|
|
5
|
+
*
|
|
6
|
+
* isAttachIframe events are never merged — they carry a full iframe document
|
|
7
|
+
* tree and must remain isolated.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mergeMutationEvents(events: eventWithTime[]): eventWithTime[];
|
|
10
|
+
//# sourceMappingURL=merge-mutation-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-mutation-events.d.ts","sourceRoot":"","sources":["../../../src/events/merge-mutation-events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,wBAAwB,CAAC;AAiK1E;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAuB5E"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeMutationEvents = void 0;
|
|
4
|
+
var tslib_1 = require("tslib");
|
|
5
|
+
var rrweb_types_1 = require("@amplitude/rrweb-types");
|
|
6
|
+
function isMergeableMutation(event) {
|
|
7
|
+
if (event.type !== rrweb_types_1.EventType.IncrementalSnapshot)
|
|
8
|
+
return false;
|
|
9
|
+
var data = event.data;
|
|
10
|
+
return data.source === rrweb_types_1.IncrementalSource.Mutation && !data.isAttachIframe;
|
|
11
|
+
}
|
|
12
|
+
function mergeGroup(events) {
|
|
13
|
+
var e_1, _a, e_2, _b;
|
|
14
|
+
var first = events[0];
|
|
15
|
+
// Track first/last event index for each node's adds and removes.
|
|
16
|
+
// lastParentById: final parent from most recent add (last-write-wins).
|
|
17
|
+
var firstAddEventIndex = new Map();
|
|
18
|
+
var lastAddEventIndex = new Map();
|
|
19
|
+
var firstRemoveEventIndex = new Map();
|
|
20
|
+
var lastRemoveEventIndex = new Map();
|
|
21
|
+
var lastParentById = new Map();
|
|
22
|
+
events.forEach(function (e, i) {
|
|
23
|
+
var e_3, _a, e_4, _b;
|
|
24
|
+
var data = e.data;
|
|
25
|
+
try {
|
|
26
|
+
for (var _c = tslib_1.__values(data.adds), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
27
|
+
var add = _d.value;
|
|
28
|
+
if (!firstAddEventIndex.has(add.node.id))
|
|
29
|
+
firstAddEventIndex.set(add.node.id, i);
|
|
30
|
+
lastAddEventIndex.set(add.node.id, i);
|
|
31
|
+
lastParentById.set(add.node.id, add.parentId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
35
|
+
finally {
|
|
36
|
+
try {
|
|
37
|
+
if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
|
|
38
|
+
}
|
|
39
|
+
finally { if (e_3) throw e_3.error; }
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
for (var _e = tslib_1.__values(data.removes), _f = _e.next(); !_f.done; _f = _e.next()) {
|
|
43
|
+
var remove = _f.value;
|
|
44
|
+
if (!firstRemoveEventIndex.has(remove.id))
|
|
45
|
+
firstRemoveEventIndex.set(remove.id, i);
|
|
46
|
+
lastRemoveEventIndex.set(remove.id, i);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
50
|
+
finally {
|
|
51
|
+
try {
|
|
52
|
+
if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
|
|
53
|
+
}
|
|
54
|
+
finally { if (e_4) throw e_4.error; }
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Classify nodes that appear in both adds and removes within this window:
|
|
58
|
+
//
|
|
59
|
+
// Pure transient: created here (firstAddIdx < firstRemoveIdx) and ultimately removed
|
|
60
|
+
// (lastAddIdx < lastRemoveIdx). Cancel all adds + all removes.
|
|
61
|
+
//
|
|
62
|
+
// Pre-existing transient: pre-existed in DOM (firstRemoveIdx < firstAddIdx — removed before
|
|
63
|
+
// first add), then re-added, then removed again (lastAddIdx < lastRemoveIdx).
|
|
64
|
+
// The rrweb replayer processes all removes first: the re-add would still
|
|
65
|
+
// execute after both removes, leaving the node present when it should be
|
|
66
|
+
// absent. Fix: cancel the re-add and all post-add removes; keep only the
|
|
67
|
+
// pre-add removes (they represent the legitimate removal from the original
|
|
68
|
+
// location).
|
|
69
|
+
var transientIds = new Set();
|
|
70
|
+
var preExistingTransientIds = new Set();
|
|
71
|
+
try {
|
|
72
|
+
for (var firstAddEventIndex_1 = tslib_1.__values(firstAddEventIndex), firstAddEventIndex_1_1 = firstAddEventIndex_1.next(); !firstAddEventIndex_1_1.done; firstAddEventIndex_1_1 = firstAddEventIndex_1.next()) {
|
|
73
|
+
var _c = tslib_1.__read(firstAddEventIndex_1_1.value, 2), id = _c[0], firstAddIdx = _c[1];
|
|
74
|
+
var firstRemoveIdx = firstRemoveEventIndex.get(id);
|
|
75
|
+
if (firstRemoveIdx === undefined)
|
|
76
|
+
continue;
|
|
77
|
+
var lastAddIdx = lastAddEventIndex.get(id);
|
|
78
|
+
var lastRemoveIdx = lastRemoveEventIndex.get(id);
|
|
79
|
+
if (lastAddIdx >= lastRemoveIdx)
|
|
80
|
+
continue; // ultimately present — keep as-is
|
|
81
|
+
if (firstAddIdx < firstRemoveIdx) {
|
|
82
|
+
transientIds.add(id);
|
|
83
|
+
}
|
|
84
|
+
else if (firstRemoveIdx < firstAddIdx) {
|
|
85
|
+
// firstRemoveIdx < firstAddIdx: pre-existing node removed, re-added, then removed again
|
|
86
|
+
preExistingTransientIds.add(id);
|
|
87
|
+
}
|
|
88
|
+
// firstAddIdx === firstRemoveIdx: same-event move (remove+add in one rrweb event) followed
|
|
89
|
+
// by a later remove — keep all operations so the move and final removal survive
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
93
|
+
finally {
|
|
94
|
+
try {
|
|
95
|
+
if (firstAddEventIndex_1_1 && !firstAddEventIndex_1_1.done && (_a = firstAddEventIndex_1.return)) _a.call(firstAddEventIndex_1);
|
|
96
|
+
}
|
|
97
|
+
finally { if (e_1) throw e_1.error; }
|
|
98
|
+
}
|
|
99
|
+
// Cascade: nodes whose FINAL parent is effectively cancelled (transient or pre-existing-transient)
|
|
100
|
+
// would be orphaned, so treat them as cancelled too.
|
|
101
|
+
// Use lastParentById so a node moved away from a cancelled parent to a live one is not wrongly elided.
|
|
102
|
+
//
|
|
103
|
+
// Three cascade outcomes mirror the main-loop classification:
|
|
104
|
+
// transientIds: no pre-existing removes (node created in window), or no remove/add overlap
|
|
105
|
+
// preExistingTransientIds: nodeFirstRemoveIdx < nodeFirstAddIdx (pre-existing, removed before re-add)
|
|
106
|
+
// cascadeDropAddsOnlyIds: nodeFirstRemoveIdx === nodeFirstAddIdx (same-event move to cancelled parent)
|
|
107
|
+
// → drop adds but preserve removes from non-cancelled parents
|
|
108
|
+
var cascadeDropAddsOnlyIds = new Set();
|
|
109
|
+
if (transientIds.size > 0 || preExistingTransientIds.size > 0) {
|
|
110
|
+
var changed = true;
|
|
111
|
+
while (changed) {
|
|
112
|
+
changed = false;
|
|
113
|
+
try {
|
|
114
|
+
for (var lastParentById_1 = (e_2 = void 0, tslib_1.__values(lastParentById)), lastParentById_1_1 = lastParentById_1.next(); !lastParentById_1_1.done; lastParentById_1_1 = lastParentById_1.next()) {
|
|
115
|
+
var _d = tslib_1.__read(lastParentById_1_1.value, 2), nodeId = _d[0], parentId = _d[1];
|
|
116
|
+
if (!transientIds.has(nodeId) &&
|
|
117
|
+
!preExistingTransientIds.has(nodeId) &&
|
|
118
|
+
!cascadeDropAddsOnlyIds.has(nodeId) &&
|
|
119
|
+
(transientIds.has(parentId) || preExistingTransientIds.has(parentId) || cascadeDropAddsOnlyIds.has(parentId))) {
|
|
120
|
+
var nodeFirstRemoveIdx = firstRemoveEventIndex.get(nodeId);
|
|
121
|
+
var nodeFirstAddIdx = firstAddEventIndex.get(nodeId);
|
|
122
|
+
if (nodeFirstRemoveIdx !== undefined &&
|
|
123
|
+
nodeFirstAddIdx !== undefined &&
|
|
124
|
+
nodeFirstRemoveIdx < nodeFirstAddIdx) {
|
|
125
|
+
preExistingTransientIds.add(nodeId);
|
|
126
|
+
}
|
|
127
|
+
else if (nodeFirstRemoveIdx !== undefined &&
|
|
128
|
+
nodeFirstAddIdx !== undefined &&
|
|
129
|
+
nodeFirstRemoveIdx === nodeFirstAddIdx) {
|
|
130
|
+
cascadeDropAddsOnlyIds.add(nodeId);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
transientIds.add(nodeId);
|
|
134
|
+
}
|
|
135
|
+
changed = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
140
|
+
finally {
|
|
141
|
+
try {
|
|
142
|
+
if (lastParentById_1_1 && !lastParentById_1_1.done && (_b = lastParentById_1.return)) _b.call(lastParentById_1);
|
|
143
|
+
}
|
|
144
|
+
finally { if (e_2) throw e_2.error; }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
var needsFilter = transientIds.size > 0 || preExistingTransientIds.size > 0 || cascadeDropAddsOnlyIds.size > 0;
|
|
149
|
+
// Build filtered removes by iterating per event so we know each remove's event index.
|
|
150
|
+
// Pure transients: drop all removes.
|
|
151
|
+
// Pre-existing transients: drop removes at eventIdx >= firstAddIdx (the cancelled re-add cycle);
|
|
152
|
+
// keep removes at eventIdx < firstAddIdx (legitimate pre-window removal).
|
|
153
|
+
// Cascade drop-adds-only: keep removes from non-cancelled parents; drop removes from cancelled parents
|
|
154
|
+
// (the cancelled parent is never added in the replay, so a remove from it
|
|
155
|
+
// would reference a non-existent node in the replayer).
|
|
156
|
+
var filteredRemoves = [];
|
|
157
|
+
events.forEach(function (e, eventIdx) {
|
|
158
|
+
var e_5, _a;
|
|
159
|
+
try {
|
|
160
|
+
for (var _b = tslib_1.__values(e.data.removes), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
161
|
+
var r = _c.value;
|
|
162
|
+
if (transientIds.has(r.id))
|
|
163
|
+
continue;
|
|
164
|
+
if (preExistingTransientIds.has(r.id) && eventIdx >= firstAddEventIndex.get(r.id))
|
|
165
|
+
continue;
|
|
166
|
+
if (cascadeDropAddsOnlyIds.has(r.id) &&
|
|
167
|
+
(transientIds.has(r.parentId) ||
|
|
168
|
+
preExistingTransientIds.has(r.parentId) ||
|
|
169
|
+
cascadeDropAddsOnlyIds.has(r.parentId)))
|
|
170
|
+
continue;
|
|
171
|
+
filteredRemoves.push(r);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
175
|
+
finally {
|
|
176
|
+
try {
|
|
177
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
178
|
+
}
|
|
179
|
+
finally { if (e_5) throw e_5.error; }
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
var allAdds = events.flatMap(function (e) { return e.data.adds; });
|
|
183
|
+
var allTexts = events.flatMap(function (e) { return e.data.texts; });
|
|
184
|
+
var allAttributes = events.flatMap(function (e) { return e.data.attributes; });
|
|
185
|
+
var merged = {
|
|
186
|
+
source: rrweb_types_1.IncrementalSource.Mutation,
|
|
187
|
+
removes: filteredRemoves,
|
|
188
|
+
adds: needsFilter
|
|
189
|
+
? allAdds.filter(function (a) {
|
|
190
|
+
return !transientIds.has(a.node.id) &&
|
|
191
|
+
!preExistingTransientIds.has(a.node.id) &&
|
|
192
|
+
!cascadeDropAddsOnlyIds.has(a.node.id);
|
|
193
|
+
})
|
|
194
|
+
: allAdds,
|
|
195
|
+
texts: needsFilter
|
|
196
|
+
? allTexts.filter(function (t) { return !transientIds.has(t.id) && !preExistingTransientIds.has(t.id) && !cascadeDropAddsOnlyIds.has(t.id); })
|
|
197
|
+
: allTexts,
|
|
198
|
+
attributes: needsFilter
|
|
199
|
+
? allAttributes.filter(function (a) { return !transientIds.has(a.id) && !preExistingTransientIds.has(a.id) && !cascadeDropAddsOnlyIds.has(a.id); })
|
|
200
|
+
: allAttributes,
|
|
201
|
+
};
|
|
202
|
+
return tslib_1.__assign(tslib_1.__assign({}, first), { data: merged });
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Merges consecutive IncrementalSnapshot mutation events into a single event,
|
|
206
|
+
* reducing overall event count without changing replay semantics.
|
|
207
|
+
*
|
|
208
|
+
* isAttachIframe events are never merged — they carry a full iframe document
|
|
209
|
+
* tree and must remain isolated.
|
|
210
|
+
*/
|
|
211
|
+
function mergeMutationEvents(events) {
|
|
212
|
+
if (events.length <= 1)
|
|
213
|
+
return events;
|
|
214
|
+
var result = [];
|
|
215
|
+
var i = 0;
|
|
216
|
+
while (i < events.length) {
|
|
217
|
+
if (!isMergeableMutation(events[i])) {
|
|
218
|
+
result.push(events[i]);
|
|
219
|
+
i++;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
var j = i + 1;
|
|
223
|
+
while (j < events.length && isMergeableMutation(events[j])) {
|
|
224
|
+
j++;
|
|
225
|
+
}
|
|
226
|
+
result.push(j > i + 1 ? mergeGroup(events.slice(i, j)) : events[i]);
|
|
227
|
+
i = j;
|
|
228
|
+
}
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
exports.mergeMutationEvents = mergeMutationEvents;
|
|
232
|
+
//# sourceMappingURL=merge-mutation-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-mutation-events.js","sourceRoot":"","sources":["../../../src/events/merge-mutation-events.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventType, IncrementalSource } from '@amplitude/rrweb-types';\nimport type { eventWithTime, mutationData } from '@amplitude/rrweb-types';\n\nfunction isMergeableMutation(event: eventWithTime): boolean {\n if (event.type !== EventType.IncrementalSnapshot) return false;\n const data = event.data as mutationData;\n return data.source === IncrementalSource.Mutation && !data.isAttachIframe;\n}\n\nfunction mergeGroup(events: eventWithTime[]): eventWithTime {\n const first = events[0];\n\n // Track first/last event index for each node's adds and removes.\n // lastParentById: final parent from most recent add (last-write-wins).\n const firstAddEventIndex = new Map<number, number>();\n const lastAddEventIndex = new Map<number, number>();\n const firstRemoveEventIndex = new Map<number, number>();\n const lastRemoveEventIndex = new Map<number, number>();\n const lastParentById = new Map<number, number>();\n events.forEach((e, i) => {\n const data = e.data as mutationData;\n for (const add of data.adds) {\n if (!firstAddEventIndex.has(add.node.id)) firstAddEventIndex.set(add.node.id, i);\n lastAddEventIndex.set(add.node.id, i);\n lastParentById.set(add.node.id, add.parentId);\n }\n for (const remove of data.removes) {\n if (!firstRemoveEventIndex.has(remove.id)) firstRemoveEventIndex.set(remove.id, i);\n lastRemoveEventIndex.set(remove.id, i);\n }\n });\n\n // Classify nodes that appear in both adds and removes within this window:\n //\n // Pure transient: created here (firstAddIdx < firstRemoveIdx) and ultimately removed\n // (lastAddIdx < lastRemoveIdx). Cancel all adds + all removes.\n //\n // Pre-existing transient: pre-existed in DOM (firstRemoveIdx < firstAddIdx — removed before\n // first add), then re-added, then removed again (lastAddIdx < lastRemoveIdx).\n // The rrweb replayer processes all removes first: the re-add would still\n // execute after both removes, leaving the node present when it should be\n // absent. Fix: cancel the re-add and all post-add removes; keep only the\n // pre-add removes (they represent the legitimate removal from the original\n // location).\n const transientIds = new Set<number>();\n const preExistingTransientIds = new Set<number>();\n\n for (const [id, firstAddIdx] of firstAddEventIndex) {\n const firstRemoveIdx = firstRemoveEventIndex.get(id);\n if (firstRemoveIdx === undefined) continue;\n const lastAddIdx = lastAddEventIndex.get(id)!;\n const lastRemoveIdx = lastRemoveEventIndex.get(id)!;\n if (lastAddIdx >= lastRemoveIdx) continue; // ultimately present — keep as-is\n\n if (firstAddIdx < firstRemoveIdx) {\n transientIds.add(id);\n } else if (firstRemoveIdx < firstAddIdx) {\n // firstRemoveIdx < firstAddIdx: pre-existing node removed, re-added, then removed again\n preExistingTransientIds.add(id);\n }\n // firstAddIdx === firstRemoveIdx: same-event move (remove+add in one rrweb event) followed\n // by a later remove — keep all operations so the move and final removal survive\n }\n\n // Cascade: nodes whose FINAL parent is effectively cancelled (transient or pre-existing-transient)\n // would be orphaned, so treat them as cancelled too.\n // Use lastParentById so a node moved away from a cancelled parent to a live one is not wrongly elided.\n //\n // Three cascade outcomes mirror the main-loop classification:\n // transientIds: no pre-existing removes (node created in window), or no remove/add overlap\n // preExistingTransientIds: nodeFirstRemoveIdx < nodeFirstAddIdx (pre-existing, removed before re-add)\n // cascadeDropAddsOnlyIds: nodeFirstRemoveIdx === nodeFirstAddIdx (same-event move to cancelled parent)\n // → drop adds but preserve removes from non-cancelled parents\n const cascadeDropAddsOnlyIds = new Set<number>();\n if (transientIds.size > 0 || preExistingTransientIds.size > 0) {\n let changed = true;\n while (changed) {\n changed = false;\n for (const [nodeId, parentId] of lastParentById) {\n if (\n !transientIds.has(nodeId) &&\n !preExistingTransientIds.has(nodeId) &&\n !cascadeDropAddsOnlyIds.has(nodeId) &&\n (transientIds.has(parentId) || preExistingTransientIds.has(parentId) || cascadeDropAddsOnlyIds.has(parentId))\n ) {\n const nodeFirstRemoveIdx = firstRemoveEventIndex.get(nodeId);\n const nodeFirstAddIdx = firstAddEventIndex.get(nodeId);\n if (\n nodeFirstRemoveIdx !== undefined &&\n nodeFirstAddIdx !== undefined &&\n nodeFirstRemoveIdx < nodeFirstAddIdx\n ) {\n preExistingTransientIds.add(nodeId);\n } else if (\n nodeFirstRemoveIdx !== undefined &&\n nodeFirstAddIdx !== undefined &&\n nodeFirstRemoveIdx === nodeFirstAddIdx\n ) {\n cascadeDropAddsOnlyIds.add(nodeId);\n } else {\n transientIds.add(nodeId);\n }\n changed = true;\n }\n }\n }\n }\n\n const needsFilter = transientIds.size > 0 || preExistingTransientIds.size > 0 || cascadeDropAddsOnlyIds.size > 0;\n\n // Build filtered removes by iterating per event so we know each remove's event index.\n // Pure transients: drop all removes.\n // Pre-existing transients: drop removes at eventIdx >= firstAddIdx (the cancelled re-add cycle);\n // keep removes at eventIdx < firstAddIdx (legitimate pre-window removal).\n // Cascade drop-adds-only: keep removes from non-cancelled parents; drop removes from cancelled parents\n // (the cancelled parent is never added in the replay, so a remove from it\n // would reference a non-existent node in the replayer).\n const filteredRemoves: mutationData['removes'][0][] = [];\n events.forEach((e, eventIdx) => {\n for (const r of (e.data as mutationData).removes) {\n if (transientIds.has(r.id)) continue;\n if (preExistingTransientIds.has(r.id) && eventIdx >= firstAddEventIndex.get(r.id)!) continue;\n if (\n cascadeDropAddsOnlyIds.has(r.id) &&\n (transientIds.has(r.parentId) ||\n preExistingTransientIds.has(r.parentId) ||\n cascadeDropAddsOnlyIds.has(r.parentId))\n )\n continue;\n filteredRemoves.push(r);\n }\n });\n\n const allAdds = events.flatMap((e) => (e.data as mutationData).adds);\n const allTexts = events.flatMap((e) => (e.data as mutationData).texts);\n const allAttributes = events.flatMap((e) => (e.data as mutationData).attributes);\n\n const merged: mutationData = {\n source: IncrementalSource.Mutation,\n removes: filteredRemoves,\n adds: needsFilter\n ? allAdds.filter(\n (a) =>\n !transientIds.has(a.node.id) &&\n !preExistingTransientIds.has(a.node.id) &&\n !cascadeDropAddsOnlyIds.has(a.node.id),\n )\n : allAdds,\n texts: needsFilter\n ? allTexts.filter(\n (t) => !transientIds.has(t.id) && !preExistingTransientIds.has(t.id) && !cascadeDropAddsOnlyIds.has(t.id),\n )\n : allTexts,\n attributes: needsFilter\n ? allAttributes.filter(\n (a) => !transientIds.has(a.id) && !preExistingTransientIds.has(a.id) && !cascadeDropAddsOnlyIds.has(a.id),\n )\n : allAttributes,\n };\n return { ...first, data: merged } as eventWithTime;\n}\n\n/**\n * Merges consecutive IncrementalSnapshot mutation events into a single event,\n * reducing overall event count without changing replay semantics.\n *\n * isAttachIframe events are never merged — they carry a full iframe document\n * tree and must remain isolated.\n */\nexport function mergeMutationEvents(events: eventWithTime[]): eventWithTime[] {\n if (events.length <= 1) return events;\n\n const result: eventWithTime[] = [];\n let i = 0;\n\n while (i < events.length) {\n if (!isMergeableMutation(events[i])) {\n result.push(events[i]);\n i++;\n continue;\n }\n\n let j = i + 1;\n while (j < events.length && isMergeableMutation(events[j])) {\n j++;\n }\n\n result.push(j > i + 1 ? mergeGroup(events.slice(i, j)) : events[i]);\n i = j;\n }\n\n return result;\n}\n"]}
|
package/lib/cjs/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.39.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
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,QAAQ,CAAC","sourcesContent":["// Autogenerated by `pnpm version-file`. DO NOT EDIT\nexport const VERSION = '1.
|
|
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.39.0';\n"]}
|
|
@@ -256,6 +256,12 @@ export interface SessionReplayPerformanceConfig {
|
|
|
256
256
|
* before executing the deferred compression task, even if the browser is not idle.
|
|
257
257
|
*/
|
|
258
258
|
timeout?: number;
|
|
259
|
+
/**
|
|
260
|
+
* If enabled, consecutive mutation events will be merged into a single event before
|
|
261
|
+
* compression, reducing stored event count without changing replay semantics.
|
|
262
|
+
* Defaults to false.
|
|
263
|
+
*/
|
|
264
|
+
mergeMutations?: boolean;
|
|
259
265
|
/**
|
|
260
266
|
* Performance configuration for interaction tracking (clicks, scrolls).
|
|
261
267
|
*/
|