@amplitude/session-replay-browser 1.51.4 → 1.52.0-codexsemantic-review-integration.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.
Files changed (87) hide show
  1. package/lib/cjs/config/joined-config.d.ts.map +1 -1
  2. package/lib/cjs/config/joined-config.js +25 -18
  3. package/lib/cjs/config/joined-config.js.map +1 -1
  4. package/lib/cjs/config/local-config.d.ts +3 -0
  5. package/lib/cjs/config/local-config.d.ts.map +1 -1
  6. package/lib/cjs/config/local-config.js +2 -0
  7. package/lib/cjs/config/local-config.js.map +1 -1
  8. package/lib/cjs/config/types.d.ts +6 -0
  9. package/lib/cjs/config/types.d.ts.map +1 -1
  10. package/lib/cjs/config/types.js.map +1 -1
  11. package/lib/cjs/hooks/click.d.ts +8 -2
  12. package/lib/cjs/hooks/click.d.ts.map +1 -1
  13. package/lib/cjs/hooks/click.js +42 -3
  14. package/lib/cjs/hooks/click.js.map +1 -1
  15. package/lib/cjs/plugins/url-tracking-plugin.d.ts +3 -0
  16. package/lib/cjs/plugins/url-tracking-plugin.d.ts.map +1 -1
  17. package/lib/cjs/plugins/url-tracking-plugin.js +16 -9
  18. package/lib/cjs/plugins/url-tracking-plugin.js.map +1 -1
  19. package/lib/cjs/semantic-identity.d.ts +5 -0
  20. package/lib/cjs/semantic-identity.d.ts.map +1 -0
  21. package/lib/cjs/semantic-identity.js +67 -0
  22. package/lib/cjs/semantic-identity.js.map +1 -0
  23. package/lib/cjs/semantic.d.ts +24 -0
  24. package/lib/cjs/semantic.d.ts.map +1 -0
  25. package/lib/cjs/semantic.js +131 -0
  26. package/lib/cjs/semantic.js.map +1 -0
  27. package/lib/cjs/session-replay.d.ts +4 -1
  28. package/lib/cjs/session-replay.d.ts.map +1 -1
  29. package/lib/cjs/session-replay.js +155 -80
  30. package/lib/cjs/session-replay.js.map +1 -1
  31. package/lib/cjs/utils/rrweb.d.ts +3 -0
  32. package/lib/cjs/utils/rrweb.d.ts.map +1 -1
  33. package/lib/cjs/utils/rrweb.js.map +1 -1
  34. package/lib/cjs/version.d.ts +1 -1
  35. package/lib/cjs/version.d.ts.map +1 -1
  36. package/lib/cjs/version.js +1 -1
  37. package/lib/cjs/version.js.map +1 -1
  38. package/lib/esm/config/joined-config.d.ts.map +1 -1
  39. package/lib/esm/config/joined-config.js +25 -18
  40. package/lib/esm/config/joined-config.js.map +1 -1
  41. package/lib/esm/config/local-config.d.ts +3 -0
  42. package/lib/esm/config/local-config.d.ts.map +1 -1
  43. package/lib/esm/config/local-config.js +2 -0
  44. package/lib/esm/config/local-config.js.map +1 -1
  45. package/lib/esm/config/types.d.ts +6 -0
  46. package/lib/esm/config/types.d.ts.map +1 -1
  47. package/lib/esm/config/types.js.map +1 -1
  48. package/lib/esm/hooks/click.d.ts +8 -2
  49. package/lib/esm/hooks/click.d.ts.map +1 -1
  50. package/lib/esm/hooks/click.js +42 -3
  51. package/lib/esm/hooks/click.js.map +1 -1
  52. package/lib/esm/plugins/url-tracking-plugin.d.ts +3 -0
  53. package/lib/esm/plugins/url-tracking-plugin.d.ts.map +1 -1
  54. package/lib/esm/plugins/url-tracking-plugin.js +16 -9
  55. package/lib/esm/plugins/url-tracking-plugin.js.map +1 -1
  56. package/lib/esm/semantic-identity.d.ts +5 -0
  57. package/lib/esm/semantic-identity.d.ts.map +1 -0
  58. package/lib/esm/semantic-identity.js +63 -0
  59. package/lib/esm/semantic-identity.js.map +1 -0
  60. package/lib/esm/semantic.d.ts +24 -0
  61. package/lib/esm/semantic.d.ts.map +1 -0
  62. package/lib/esm/semantic.js +128 -0
  63. package/lib/esm/semantic.js.map +1 -0
  64. package/lib/esm/session-replay.d.ts +4 -1
  65. package/lib/esm/session-replay.d.ts.map +1 -1
  66. package/lib/esm/session-replay.js +155 -80
  67. package/lib/esm/session-replay.js.map +1 -1
  68. package/lib/esm/utils/rrweb.d.ts +3 -0
  69. package/lib/esm/utils/rrweb.d.ts.map +1 -1
  70. package/lib/esm/utils/rrweb.js.map +1 -1
  71. package/lib/esm/version.d.ts +1 -1
  72. package/lib/esm/version.d.ts.map +1 -1
  73. package/lib/esm/version.js +1 -1
  74. package/lib/esm/version.js.map +1 -1
  75. package/lib/scripts/console-plugin-min.js +1 -1
  76. package/lib/scripts/console-plugin-min.js.gz +0 -0
  77. package/lib/scripts/console-plugin-min.js.map +1 -1
  78. package/lib/scripts/index-min.js +1 -1
  79. package/lib/scripts/index-min.js.gz +0 -0
  80. package/lib/scripts/index-min.js.map +1 -1
  81. package/lib/scripts/rrweb-record-min.js +1 -1
  82. package/lib/scripts/rrweb-record-min.js.gz +0 -0
  83. package/lib/scripts/rrweb-record-min.js.map +1 -1
  84. package/lib/scripts/session-replay-browser-min.js +1 -1
  85. package/lib/scripts/session-replay-browser-min.js.gz +0 -0
  86. package/lib/scripts/session-replay-browser-min.js.map +1 -1
  87. package/package.json +7 -6
@@ -51,6 +51,7 @@ var ClickHandler = /** @class */ (function () {
51
51
  this.createHook = function (_a) {
52
52
  var eventsManager = _a.eventsManager, sessionId = _a.sessionId, deviceIdFn = _a.deviceIdFn, mirror = _a.mirror, ugcFilterRules = _a.ugcFilterRules, performanceOptions = _a.performanceOptions;
53
53
  return function (e) {
54
+ var _a, _b, _c, _d, _e;
54
55
  if (e.type !== rrweb_types_1.MouseInteractions.Click) {
55
56
  return;
56
57
  }
@@ -71,7 +72,7 @@ var ClickHandler = /** @class */ (function () {
71
72
  var selector;
72
73
  if (node) {
73
74
  try {
74
- selector = (0, finder_1.finder)(node, performanceOptions);
75
+ selector = (_a = _this.getSelector(node, performanceOptions)) !== null && _a !== void 0 ? _a : undefined;
75
76
  }
76
77
  catch (err) {
77
78
  _this.logger.debug('error resolving selector from finder');
@@ -79,8 +80,8 @@ var ClickHandler = /** @class */ (function () {
79
80
  }
80
81
  var pageUrl = (0, helpers_1.getPageUrl)(location.href, ugcFilterRules);
81
82
  var event = {
82
- x: x + _this.scrollWatcher.currentScrollX,
83
- y: y + _this.scrollWatcher.currentScrollY,
83
+ x: x + ((_c = (_b = _this.scrollWatcher) === null || _b === void 0 ? void 0 : _b.currentScrollX) !== null && _c !== void 0 ? _c : 0),
84
+ y: y + ((_e = (_d = _this.scrollWatcher) === null || _d === void 0 ? void 0 : _d.currentScrollY) !== null && _e !== void 0 ? _e : 0),
84
85
  selector: selector,
85
86
  viewportHeight: innerHeight,
86
87
  viewportWidth: innerWidth,
@@ -97,6 +98,44 @@ var ClickHandler = /** @class */ (function () {
97
98
  this.logger = logger;
98
99
  this.scrollWatcher = scrollWatcher;
99
100
  }
101
+ /** Resolve once per native click when heatmaps and semantics use the same node. */
102
+ ClickHandler.prototype.getSelector = function (element, options) {
103
+ var _a, _b, _c;
104
+ if ((_a = this.clickSelectors) === null || _a === void 0 ? void 0 : _a.has(element))
105
+ return (_b = this.clickSelectors.get(element)) !== null && _b !== void 0 ? _b : null;
106
+ var selector = (0, finder_1.finder)(element, options);
107
+ (_c = this.clickSelectors) === null || _c === void 0 ? void 0 : _c.set(element, selector);
108
+ return selector;
109
+ };
110
+ /** Both consumers run before heatmap batching; the native event stays in memory only. */
111
+ ClickHandler.prototype.createSharedHook = function (heatmapHook, semanticClick) {
112
+ var _this = this;
113
+ return function (data, nativeEvent) {
114
+ if (data.type !== rrweb_types_1.MouseInteractions.Click)
115
+ return;
116
+ var previous = _this.clickSelectors;
117
+ _this.clickSelectors = new Map();
118
+ try {
119
+ try {
120
+ heatmapHook === null || heatmapHook === void 0 ? void 0 : heatmapHook(data);
121
+ }
122
+ catch (error) {
123
+ _this.logger.debug('Heatmap click hook failed', error);
124
+ }
125
+ if ((nativeEvent === null || nativeEvent === void 0 ? void 0 : nativeEvent.type) === 'click') {
126
+ try {
127
+ semanticClick === null || semanticClick === void 0 ? void 0 : semanticClick(nativeEvent);
128
+ }
129
+ catch (error) {
130
+ _this.logger.debug('Semantic click hook failed', error);
131
+ }
132
+ }
133
+ }
134
+ finally {
135
+ _this.clickSelectors = previous;
136
+ }
137
+ };
138
+ };
100
139
  return ClickHandler;
101
140
  }());
102
141
  exports.ClickHandler = ClickHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"click.js","sourceRoot":"","sources":["../../../src/hooks/click.ts"],"names":[],"mappings":"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","sourcesContent":["import type { mouseInteractionCallBack } from '@amplitude/rrweb-types';\nimport { MouseInteractions } from '@amplitude/rrweb-types';\nimport { Mirror } from '../utils/rrweb';\nimport { SessionReplayEventsManager as AmplitudeSessionReplayEventsManager } from '../typings/session-replay';\nimport { PayloadBatcher } from '../track-destination';\nimport { finder, Options as FinderOptions } from '../libs/finder';\nimport { getGlobalScope, ILogger } from '@amplitude/analytics-core';\nimport { UGCFilterRule, InteractionPerformanceConfig } from '../config/types';\nimport { getPageUrl } from '../helpers';\nimport { ScrollWatcher } from './scroll';\n\n// exported for testing\nexport type ClickEvent = {\n timestamp: number;\n x: number;\n y: number;\n viewportWidth: number;\n viewportHeight: number;\n pageUrl: string;\n selector?: string;\n type: 'click';\n};\n\n// exported for testing\nexport type ClickEventWithCount = ClickEvent & { count: number };\n\ntype Context = {\n sessionId: string | number;\n deviceIdFn: () => string | undefined;\n eventsManager: AmplitudeSessionReplayEventsManager<'interaction', string>;\n mirror: Mirror;\n ugcFilterRules: UGCFilterRule[];\n performanceOptions?: InteractionPerformanceConfig;\n};\n\nconst HOUR_IN_MILLISECONDS = 3_600_000;\n\nexport const clickNonBatcher: PayloadBatcher = ({ version, events }) => {\n const clickEvents: ClickEvent[] = [];\n events.forEach((evt: string) => {\n const record = JSON.parse(evt) as Record<string, unknown>;\n record.count = 1;\n if (record.type === 'click') {\n clickEvents.push(record as ClickEvent);\n }\n });\n return { version, events: clickEvents };\n};\n\nexport const clickBatcher: PayloadBatcher = ({ version, events }) => {\n const clickEvents: ClickEvent[] = [];\n events.forEach((evt: string) => {\n const record = JSON.parse(evt) as Record<string, unknown>;\n if (record.type === 'click') {\n clickEvents.push(record as ClickEvent);\n }\n });\n\n const reduced = clickEvents.reduce<Record<string, ClickEventWithCount>>((prev, curr) => {\n const { x, y, selector, timestamp } = curr;\n\n // round down to nearest hour.\n const hour = timestamp - (timestamp % HOUR_IN_MILLISECONDS);\n\n const k = `${x}:${y}:${selector ?? ''}:${hour}`;\n if (!prev[k]) {\n prev[k] = { ...curr, timestamp: hour, count: 1 };\n } else {\n prev[k].count += 1;\n }\n return prev;\n }, {});\n\n return { version, events: Object.values(reduced) };\n};\n\nexport class ClickHandler {\n private readonly logger: ILogger;\n private readonly scrollWatcher: ScrollWatcher;\n\n constructor(logger: ILogger, scrollWatcher: ScrollWatcher) {\n this.logger = logger;\n this.scrollWatcher = scrollWatcher;\n }\n\n createHook: (context: Context) => mouseInteractionCallBack = ({\n eventsManager,\n sessionId,\n deviceIdFn,\n mirror,\n ugcFilterRules,\n performanceOptions,\n }) => {\n return (e) => {\n if (e.type !== MouseInteractions.Click) {\n return;\n }\n\n const globalScope = getGlobalScope();\n if (!globalScope) {\n return;\n }\n\n const { location, innerHeight, innerWidth } = globalScope;\n // it only makes sense to send events if a pageUrl exists\n if (!location) {\n return;\n }\n\n const { x, y } = e;\n if (x === undefined || y === undefined) {\n return;\n }\n\n const node = mirror.getNode(e.id);\n let selector;\n if (node) {\n try {\n selector = finder(\n node as Element,\n performanceOptions as Pick<FinderOptions, 'timeoutMs' | 'maxNumberOfTries' | 'threshold'>,\n );\n } catch (err) {\n this.logger.debug('error resolving selector from finder');\n }\n }\n\n const pageUrl = getPageUrl(location.href, ugcFilterRules);\n\n const event: ClickEvent = {\n x: x + this.scrollWatcher.currentScrollX,\n y: y + this.scrollWatcher.currentScrollY,\n selector,\n\n viewportHeight: innerHeight,\n viewportWidth: innerWidth,\n pageUrl,\n timestamp: Date.now(),\n type: 'click',\n };\n const deviceId = deviceIdFn();\n if (deviceId) {\n eventsManager.addEvent({ sessionId, event: { type: 'interaction', data: JSON.stringify(event) }, deviceId });\n }\n };\n };\n}\n"]}
1
+ {"version":3,"file":"click.js","sourceRoot":"","sources":["../../../src/hooks/click.ts"],"names":[],"mappings":"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","sourcesContent":["import type { mouseInteractionCallBack } from '@amplitude/rrweb-types';\nimport { MouseInteractions } from '@amplitude/rrweb-types';\nimport { Mirror } from '../utils/rrweb';\nimport { SessionReplayEventsManager as AmplitudeSessionReplayEventsManager } from '../typings/session-replay';\nimport { PayloadBatcher } from '../track-destination';\nimport { finder, Options as FinderOptions } from '../libs/finder';\nimport { getGlobalScope, ILogger } from '@amplitude/analytics-core';\nimport { UGCFilterRule, InteractionPerformanceConfig } from '../config/types';\nimport { getPageUrl } from '../helpers';\nimport { ScrollWatcher } from './scroll';\n\n// exported for testing\nexport type ClickEvent = {\n timestamp: number;\n x: number;\n y: number;\n viewportWidth: number;\n viewportHeight: number;\n pageUrl: string;\n selector?: string;\n type: 'click';\n};\n\n// exported for testing\nexport type ClickEventWithCount = ClickEvent & { count: number };\n\ntype Context = {\n sessionId: string | number;\n deviceIdFn: () => string | undefined;\n eventsManager: AmplitudeSessionReplayEventsManager<'interaction', string>;\n mirror: Mirror;\n ugcFilterRules: UGCFilterRule[];\n performanceOptions?: InteractionPerformanceConfig;\n};\n\nconst HOUR_IN_MILLISECONDS = 3_600_000;\n\nexport const clickNonBatcher: PayloadBatcher = ({ version, events }) => {\n const clickEvents: ClickEvent[] = [];\n events.forEach((evt: string) => {\n const record = JSON.parse(evt) as Record<string, unknown>;\n record.count = 1;\n if (record.type === 'click') {\n clickEvents.push(record as ClickEvent);\n }\n });\n return { version, events: clickEvents };\n};\n\nexport const clickBatcher: PayloadBatcher = ({ version, events }) => {\n const clickEvents: ClickEvent[] = [];\n events.forEach((evt: string) => {\n const record = JSON.parse(evt) as Record<string, unknown>;\n if (record.type === 'click') {\n clickEvents.push(record as ClickEvent);\n }\n });\n\n const reduced = clickEvents.reduce<Record<string, ClickEventWithCount>>((prev, curr) => {\n const { x, y, selector, timestamp } = curr;\n\n // round down to nearest hour.\n const hour = timestamp - (timestamp % HOUR_IN_MILLISECONDS);\n\n const k = `${x}:${y}:${selector ?? ''}:${hour}`;\n if (!prev[k]) {\n prev[k] = { ...curr, timestamp: hour, count: 1 };\n } else {\n prev[k].count += 1;\n }\n return prev;\n }, {});\n\n return { version, events: Object.values(reduced) };\n};\n\nexport class ClickHandler {\n private readonly logger: ILogger;\n private readonly scrollWatcher?: ScrollWatcher;\n private clickSelectors?: Map<Element, string | null>;\n\n constructor(logger: ILogger, scrollWatcher?: ScrollWatcher) {\n this.logger = logger;\n this.scrollWatcher = scrollWatcher;\n }\n\n /** Resolve once per native click when heatmaps and semantics use the same node. */\n getSelector(element: Element, options?: Partial<FinderOptions>): string | null {\n if (this.clickSelectors?.has(element)) return this.clickSelectors.get(element) ?? null;\n const selector = finder(element, options);\n this.clickSelectors?.set(element, selector);\n return selector;\n }\n\n /** Both consumers run before heatmap batching; the native event stays in memory only. */\n createSharedHook(\n heatmapHook?: mouseInteractionCallBack,\n semanticClick?: (event: MouseEvent) => void,\n ): (data: Parameters<mouseInteractionCallBack>[0], nativeEvent?: MouseEvent | TouchEvent) => void {\n return (data, nativeEvent) => {\n if (data.type !== MouseInteractions.Click) return;\n const previous = this.clickSelectors;\n this.clickSelectors = new Map();\n try {\n try {\n heatmapHook?.(data);\n } catch (error) {\n this.logger.debug('Heatmap click hook failed', error);\n }\n if (nativeEvent?.type === 'click') {\n try {\n semanticClick?.(nativeEvent as MouseEvent);\n } catch (error) {\n this.logger.debug('Semantic click hook failed', error);\n }\n }\n } finally {\n this.clickSelectors = previous;\n }\n };\n }\n\n createHook: (context: Context) => mouseInteractionCallBack = ({\n eventsManager,\n sessionId,\n deviceIdFn,\n mirror,\n ugcFilterRules,\n performanceOptions,\n }) => {\n return (e) => {\n if (e.type !== MouseInteractions.Click) {\n return;\n }\n\n const globalScope = getGlobalScope();\n if (!globalScope) {\n return;\n }\n\n const { location, innerHeight, innerWidth } = globalScope;\n // it only makes sense to send events if a pageUrl exists\n if (!location) {\n return;\n }\n\n const { x, y } = e;\n if (x === undefined || y === undefined) {\n return;\n }\n\n const node = mirror.getNode(e.id);\n let selector;\n if (node) {\n try {\n selector = this.getSelector(node as Element, performanceOptions) ?? undefined;\n } catch (err) {\n this.logger.debug('error resolving selector from finder');\n }\n }\n\n const pageUrl = getPageUrl(location.href, ugcFilterRules);\n\n const event: ClickEvent = {\n x: x + (this.scrollWatcher?.currentScrollX ?? 0),\n y: y + (this.scrollWatcher?.currentScrollY ?? 0),\n selector,\n\n viewportHeight: innerHeight,\n viewportWidth: innerWidth,\n pageUrl,\n timestamp: Date.now(),\n type: 'click',\n };\n const deviceId = deviceIdFn();\n if (deviceId) {\n eventsManager.addEvent({ sessionId, event: { type: 'interaction', data: JSON.stringify(event) }, deviceId });\n }\n };\n };\n}\n"]}
@@ -31,11 +31,14 @@ export interface URLTrackingPluginOptions {
31
31
  }
32
32
  /** Options for subscribeToUrlChanges (polling vs history + hash) */
33
33
  export interface SubscribeToUrlChangesOptions {
34
+ /** Optional navigation metadata for semantic capture; existing callbacks remain unchanged. */
35
+ onNavigation?: (reason: URLChangeReason) => void;
34
36
  /** Use polling instead of history/popstate/hashchange (e.g. when history is unreliable) */
35
37
  enablePolling?: boolean;
36
38
  /** Polling interval in ms when enablePolling is true (default: 1000) */
37
39
  pollingInterval?: number;
38
40
  }
41
+ export type URLChangeReason = 'push' | 'replace' | 'pop' | 'hash';
39
42
  /**
40
43
  * Single helper for URL change detection. Supports:
41
44
  * - History API (pushState/replaceState) + popstate + hashchange (shared patch per scope)
@@ -1 +1 @@
1
- {"version":3,"file":"url-tracking-plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/url-tracking-plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,oEAAoE;AACpE,MAAM,WAAW,4BAA4B;IAC3C,2FAA2F;IAC3F,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAiBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EACnC,OAAO,GAAE,4BAAiC,GACzC,MAAM,IAAI,CAqGZ;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,wBAAwB,CAAC,CAqFxC;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,wCAA4B,CAAC"}
1
+ {"version":3,"file":"url-tracking-plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/url-tracking-plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;IACjC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,oEAAoE;AACpE,MAAM,WAAW,4BAA4B;IAC3C,8FAA8F;IAC9F,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IACjD,2FAA2F;IAC3F,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAKD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,MAAM,CAAC;AAclE;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EACnC,OAAO,GAAE,4BAAiC,GACzC,MAAM,IAAI,CA2GZ;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CAAC,wBAAwB,CAAC,CAqFxC;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,wCAA4B,CAAC"}
@@ -33,12 +33,14 @@ function subscribeToUrlChanges(globalScope, onUrlChange, options) {
33
33
  var getHref_1 = function () { var _a; return (_a = globalScope.location.href) !== null && _a !== void 0 ? _a : ''; };
34
34
  var lastHref_1 = getHref_1();
35
35
  var id_1 = globalScope.setInterval(function () {
36
+ var _a;
36
37
  var href = getHref_1();
37
38
  if (href === lastHref_1) {
38
39
  return;
39
40
  }
40
41
  lastHref_1 = href;
41
42
  onUrlChange(href);
43
+ (_a = options.onNavigation) === null || _a === void 0 ? void 0 : _a.call(options, 'replace');
42
44
  }, pollingInterval);
43
45
  return function () {
44
46
  if (id_1 != null) {
@@ -51,18 +53,18 @@ function subscribeToUrlChanges(globalScope, onUrlChange, options) {
51
53
  var lastHref_2 = undefined;
52
54
  var callbacks_1 = new Set();
53
55
  var getHref_2 = function () { var _a; return (_a = globalScope.location.href) !== null && _a !== void 0 ? _a : ''; };
54
- var notify_1 = function () {
56
+ var notify_1 = function (reason) {
55
57
  var href = getHref_2();
56
58
  if (lastHref_2 !== undefined && href === lastHref_2)
57
59
  return;
58
60
  lastHref_2 = href;
59
- callbacks_1.forEach(function (c) { return c(href); });
61
+ callbacks_1.forEach(function (c) { return c(href, reason); });
60
62
  };
61
63
  /**
62
64
  * Creates a patched version of history methods (pushState/replaceState)
63
65
  * that calls the original method and then emits a URL change event.
64
66
  */
65
- var createHistoryMethodPatch = function (originalMethod) {
67
+ var createHistoryMethodPatch = function (originalMethod, reason) {
66
68
  var patchedMethod = function () {
67
69
  var args = [];
68
70
  for (var _i = 0; _i < arguments.length; _i++) {
@@ -70,7 +72,7 @@ function subscribeToUrlChanges(globalScope, onUrlChange, options) {
70
72
  }
71
73
  var result = originalMethod.apply(this, args);
72
74
  // Read from location.href after history call so we always notify with resolved absolute URL.
73
- notify_1();
75
+ notify_1(reason);
74
76
  return result;
75
77
  };
76
78
  patchedMethod[PATCH_MARKER] = true;
@@ -80,19 +82,19 @@ function subscribeToUrlChanges(globalScope, onUrlChange, options) {
80
82
  if (history_1 === null || history_1 === void 0 ? void 0 : history_1.pushState) {
81
83
  var pushState = Reflect.get(history_1, 'pushState');
82
84
  if (!pushState[PATCH_MARKER]) {
83
- history_1.pushState = createHistoryMethodPatch(pushState);
85
+ history_1.pushState = createHistoryMethodPatch(pushState, 'push');
84
86
  }
85
87
  }
86
88
  if (history_1 === null || history_1 === void 0 ? void 0 : history_1.replaceState) {
87
89
  var replaceState = Reflect.get(history_1, 'replaceState');
88
90
  if (!replaceState[PATCH_MARKER]) {
89
- history_1.replaceState = createHistoryMethodPatch(replaceState);
91
+ history_1.replaceState = createHistoryMethodPatch(replaceState, 'replace');
90
92
  }
91
93
  }
92
94
  subscriptionState = {
93
95
  callbacks: callbacks_1,
94
96
  notify: notify_1,
95
- onPopStateOrHashChange: function () { return notify_1(); },
97
+ onPopStateOrHashChange: function (event) { return notify_1((event === null || event === void 0 ? void 0 : event.type) === 'hashchange' ? 'hash' : 'pop'); },
96
98
  listenersAttached: false,
97
99
  };
98
100
  urlChangeSubscriptionsByScope.set(globalScope, subscriptionState);
@@ -103,9 +105,14 @@ function subscribeToUrlChanges(globalScope, onUrlChange, options) {
103
105
  globalScope.addEventListener('hashchange', state.onPopStateOrHashChange);
104
106
  state.listenersAttached = true;
105
107
  }
106
- state.callbacks.add(onUrlChange);
108
+ var callback = function (href, reason) {
109
+ var _a;
110
+ onUrlChange(href);
111
+ (_a = options.onNavigation) === null || _a === void 0 ? void 0 : _a.call(options, reason);
112
+ };
113
+ state.callbacks.add(callback);
107
114
  return function () {
108
- state.callbacks.delete(onUrlChange);
115
+ state.callbacks.delete(callback);
109
116
  if (state.callbacks.size === 0) {
110
117
  // Do not restore history methods: we are not aware of patches applied by other scripts.
111
118
  if (state.listenersAttached) {
@@ -1 +1 @@
1
- {"version":3,"file":"url-tracking-plugin.js","sourceRoot":"","sources":["../../../src/plugins/url-tracking-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { getPageUrl } from '../helpers';\nimport { UGCFilterRule } from '../config/types';\nimport { DEFAULT_URL_CHANGE_POLLING_INTERVAL } from '../constants';\nimport { RecordPlugin } from '@amplitude/rrweb-types';\n\n/**\n * Event emitted when URL changes are detected by the plugin\n * Contains the current page URL, title, and viewport dimensions\n */\nexport interface URLChangeEvent {\n /** The current page URL (may be filtered if UGC rules are applied) */\n href: string;\n /** The current page title */\n title: string;\n /** Viewport height in pixels */\n viewportHeight: number;\n /** Viewport width in pixels */\n viewportWidth: number;\n /** The type of URL change event */\n type: string;\n}\n\n/**\n * Configuration options for the URL tracking plugin\n */\nexport interface URLTrackingPluginOptions {\n /** Rules for filtering sensitive URLs (User Generated Content) */\n ugcFilterRules?: UGCFilterRule[];\n /** Whether to use polling instead of history API events for URL detection */\n enablePolling?: boolean;\n /** Interval in milliseconds for polling URL changes (default: 1000ms) */\n pollingInterval?: number;\n /** Whether to capture document title in URL change events (default: false) */\n captureDocumentTitle?: boolean;\n}\n\n/** Options for subscribeToUrlChanges (polling vs history + hash) */\nexport interface SubscribeToUrlChangesOptions {\n /** Use polling instead of history/popstate/hashchange (e.g. when history is unreliable) */\n enablePolling?: boolean;\n /** Polling interval in ms when enablePolling is true (default: 1000) */\n pollingInterval?: number;\n}\n\n/** Patch detection marker to prevent double-patching */\nconst PATCH_MARKER = '__amplitude_url_tracking_patched__';\n\ntype PatchableHistoryMethod<T extends (...args: any[]) => any> = T & { [PATCH_MARKER]?: boolean };\n\ninterface UrlChangeSubscriptionState {\n callbacks: Set<(href: string) => void>;\n onPopStateOrHashChange: () => void;\n notify: () => void;\n listenersAttached: boolean;\n}\n\n/** Per-globalScope subscription state; persists to avoid wrapper stacking across re-subscribe cycles */\nconst urlChangeSubscriptionsByScope = new WeakMap<Window, UrlChangeSubscriptionState>();\n\n/**\n * Single helper for URL change detection. Supports:\n * - History API (pushState/replaceState) + popstate + hashchange (shared patch per scope)\n * - Optional polling (setInterval on location.href)\n *\n * Used by session-replay targeting (re-evaluate on URL change) and the URL tracking plugin\n * (emit rrweb events). Call the returned function to unsubscribe.\n *\n * @param globalScope - window (or equivalent); no-op if undefined\n * @param onUrlChange - called when the URL changes, with the new href\n * @param options - optional polling (default: event-based only)\n * @returns cleanup function to remove this subscription\n */\nexport function subscribeToUrlChanges(\n globalScope: Window | undefined,\n onUrlChange: (href: string) => void,\n options: SubscribeToUrlChangesOptions = {},\n): () => void {\n if (!globalScope?.location) {\n return (): void => {\n return;\n };\n }\n\n const { enablePolling = false, pollingInterval = DEFAULT_URL_CHANGE_POLLING_INTERVAL } = options;\n\n if (enablePolling) {\n const getHref = (): string => globalScope.location.href ?? '';\n let lastHref = getHref();\n const id = globalScope.setInterval(() => {\n const href = getHref();\n if (href === lastHref) {\n return;\n }\n lastHref = href;\n onUrlChange(href);\n }, pollingInterval);\n return (): void => {\n if (id != null) {\n globalScope.clearInterval(id);\n }\n };\n }\n\n let subscriptionState = urlChangeSubscriptionsByScope.get(globalScope);\n if (!subscriptionState) {\n let lastHref: string | undefined = undefined;\n const callbacks = new Set<(href: string) => void>();\n\n const getHref = (): string => globalScope.location.href ?? '';\n\n const notify = (): void => {\n const href = getHref();\n if (lastHref !== undefined && href === lastHref) return;\n lastHref = href;\n callbacks.forEach((c) => c(href));\n };\n\n /**\n * Creates a patched version of history methods (pushState/replaceState)\n * that calls the original method and then emits a URL change event.\n */\n const createHistoryMethodPatch = <T extends typeof history.pushState | typeof history.replaceState>(\n originalMethod: T,\n ) => {\n const patchedMethod = function (this: History, ...args: Parameters<T>) {\n const result = originalMethod.apply(this, args);\n // Read from location.href after history call so we always notify with resolved absolute URL.\n notify();\n return result;\n } as PatchableHistoryMethod<T>;\n\n patchedMethod[PATCH_MARKER] = true;\n return patchedMethod;\n };\n\n const history = globalScope.history;\n if (history?.pushState) {\n const pushState = Reflect.get(history, 'pushState') as PatchableHistoryMethod<History['pushState']>;\n if (!pushState[PATCH_MARKER]) {\n history.pushState = createHistoryMethodPatch(pushState);\n }\n }\n if (history?.replaceState) {\n const replaceState = Reflect.get(history, 'replaceState') as PatchableHistoryMethod<History['replaceState']>;\n if (!replaceState[PATCH_MARKER]) {\n history.replaceState = createHistoryMethodPatch(replaceState);\n }\n }\n\n subscriptionState = {\n callbacks,\n notify,\n onPopStateOrHashChange: () => notify(),\n listenersAttached: false,\n };\n urlChangeSubscriptionsByScope.set(globalScope, subscriptionState);\n }\n\n const state = subscriptionState;\n if (!state.listenersAttached) {\n globalScope.addEventListener('popstate', state.onPopStateOrHashChange);\n globalScope.addEventListener('hashchange', state.onPopStateOrHashChange);\n state.listenersAttached = true;\n }\n\n state.callbacks.add(onUrlChange);\n return (): void => {\n state.callbacks.delete(onUrlChange);\n if (state.callbacks.size === 0) {\n // Do not restore history methods: we are not aware of patches applied by other scripts.\n if (state.listenersAttached) {\n globalScope.removeEventListener('popstate', state.onPopStateOrHashChange);\n globalScope.removeEventListener('hashchange', state.onPopStateOrHashChange);\n state.listenersAttached = false;\n }\n }\n };\n}\n\n/**\n * Creates a URL tracking plugin for rrweb record function\n *\n * This plugin monitors URL changes in the browser and emits events when the URL changes.\n * It supports three tracking modes:\n * 1. Polling (if explicitly enabled) - periodically checks for URL changes\n * 2. History API + Hash routing (default) - patches pushState/replaceState, listens to popstate and hashchange\n * 3. Hash routing only (fallback) - listens to hashchange events when History API is unavailable\n *\n * The plugin handles edge cases gracefully:\n * - Missing or null location objects\n * - Undefined, null, or empty location.href values\n * - Temporal dead zone issues with variable declarations\n * - Consistent URL normalization across all code paths\n *\n * @param options Configuration options for URL tracking\n * @returns RecordPlugin instance that can be used with rrweb\n */\nexport function createUrlTrackingPlugin(\n options: URLTrackingPluginOptions = {},\n): RecordPlugin<URLTrackingPluginOptions> {\n return {\n name: 'amplitude/url-tracking@1',\n observer(cb, globalScope, pluginOptions?: URLTrackingPluginOptions) {\n // Merge options with plugin-level options taking precedence over constructor options\n const config = { ...options, ...pluginOptions };\n const ugcFilterRules = config.ugcFilterRules || [];\n const enablePolling = config.enablePolling ?? false;\n const pollingInterval = config.pollingInterval ?? DEFAULT_URL_CHANGE_POLLING_INTERVAL;\n const captureDocumentTitle = config.captureDocumentTitle ?? false;\n\n // Early return if no global scope is available\n if (!globalScope) {\n return () => {\n // No cleanup needed if no global scope available\n };\n }\n\n // Track the last URL to prevent duplicate events\n // Initialize to undefined to ensure first call always emits an event\n let lastTrackedUrl: string | undefined = undefined;\n\n // Helper functions\n /**\n * Gets the current URL with proper normalization\n * Handles edge cases where location.href might be undefined, null, or empty\n * Ensures consistent behavior across all code paths\n * @returns Normalized URL string (empty string if location unavailable)\n */\n const getCurrentUrl = (): string => {\n if (!globalScope.location) return '';\n return globalScope.location.href || '';\n };\n\n /**\n * Creates a URL change event with current page information\n * Applies UGC filtering if rules are configured\n * Uses getCurrentUrl() for consistent URL normalization\n * @returns URLChangeEvent with current page state\n */\n const createUrlChangeEvent = (): URLChangeEvent => {\n const { innerHeight, innerWidth, document } = globalScope;\n const currentUrl = getCurrentUrl();\n let currentTitle = '';\n if (captureDocumentTitle) {\n currentTitle = document?.title || '';\n }\n\n // Apply UGC filtering if rules are provided, otherwise use original URL\n const filteredUrl = ugcFilterRules.length > 0 ? getPageUrl(currentUrl, ugcFilterRules) : currentUrl;\n\n return {\n href: filteredUrl,\n title: currentTitle,\n viewportHeight: innerHeight,\n viewportWidth: innerWidth,\n type: 'url-change-event',\n };\n };\n\n /**\n * Emits a URL change event if the URL has actually changed\n * Always emits on first call (when lastTrackedUrl is undefined)\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\n };\n\n // Single helper: history + popstate + hashchange, or polling when enabled\n const unsubscribe = subscribeToUrlChanges(\n globalScope as Window,\n () => emitUrlChange(),\n enablePolling ? { enablePolling: true, pollingInterval } : {},\n );\n emitUrlChange();\n\n return (): void => unsubscribe();\n },\n options,\n };\n}\n\n/**\n * Default URL tracking plugin instance with default options\n * Can be used directly without custom configuration\n */\nexport const urlTrackingPlugin = createUrlTrackingPlugin();\n"]}
1
+ {"version":3,"file":"url-tracking-plugin.js","sourceRoot":"","sources":["../../../src/plugins/url-tracking-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import { getPageUrl } from '../helpers';\nimport { UGCFilterRule } from '../config/types';\nimport { DEFAULT_URL_CHANGE_POLLING_INTERVAL } from '../constants';\nimport { RecordPlugin } from '@amplitude/rrweb-types';\n\n/**\n * Event emitted when URL changes are detected by the plugin\n * Contains the current page URL, title, and viewport dimensions\n */\nexport interface URLChangeEvent {\n /** The current page URL (may be filtered if UGC rules are applied) */\n href: string;\n /** The current page title */\n title: string;\n /** Viewport height in pixels */\n viewportHeight: number;\n /** Viewport width in pixels */\n viewportWidth: number;\n /** The type of URL change event */\n type: string;\n}\n\n/**\n * Configuration options for the URL tracking plugin\n */\nexport interface URLTrackingPluginOptions {\n /** Rules for filtering sensitive URLs (User Generated Content) */\n ugcFilterRules?: UGCFilterRule[];\n /** Whether to use polling instead of history API events for URL detection */\n enablePolling?: boolean;\n /** Interval in milliseconds for polling URL changes (default: 1000ms) */\n pollingInterval?: number;\n /** Whether to capture document title in URL change events (default: false) */\n captureDocumentTitle?: boolean;\n}\n\n/** Options for subscribeToUrlChanges (polling vs history + hash) */\nexport interface SubscribeToUrlChangesOptions {\n /** Optional navigation metadata for semantic capture; existing callbacks remain unchanged. */\n onNavigation?: (reason: URLChangeReason) => void;\n /** Use polling instead of history/popstate/hashchange (e.g. when history is unreliable) */\n enablePolling?: boolean;\n /** Polling interval in ms when enablePolling is true (default: 1000) */\n pollingInterval?: number;\n}\n\n/** Patch detection marker to prevent double-patching */\nconst PATCH_MARKER = '__amplitude_url_tracking_patched__';\n\nexport type URLChangeReason = 'push' | 'replace' | 'pop' | 'hash';\n\ntype PatchableHistoryMethod<T extends (...args: any[]) => any> = T & { [PATCH_MARKER]?: boolean };\n\ninterface UrlChangeSubscriptionState {\n callbacks: Set<(href: string, reason: URLChangeReason) => void>;\n onPopStateOrHashChange: (event: Event) => void;\n notify: (reason: URLChangeReason) => void;\n listenersAttached: boolean;\n}\n\n/** Per-globalScope subscription state; persists to avoid wrapper stacking across re-subscribe cycles */\nconst urlChangeSubscriptionsByScope = new WeakMap<Window, UrlChangeSubscriptionState>();\n\n/**\n * Single helper for URL change detection. Supports:\n * - History API (pushState/replaceState) + popstate + hashchange (shared patch per scope)\n * - Optional polling (setInterval on location.href)\n *\n * Used by session-replay targeting (re-evaluate on URL change) and the URL tracking plugin\n * (emit rrweb events). Call the returned function to unsubscribe.\n *\n * @param globalScope - window (or equivalent); no-op if undefined\n * @param onUrlChange - called when the URL changes, with the new href\n * @param options - optional polling (default: event-based only)\n * @returns cleanup function to remove this subscription\n */\nexport function subscribeToUrlChanges(\n globalScope: Window | undefined,\n onUrlChange: (href: string) => void,\n options: SubscribeToUrlChangesOptions = {},\n): () => void {\n if (!globalScope?.location) {\n return (): void => {\n return;\n };\n }\n\n const { enablePolling = false, pollingInterval = DEFAULT_URL_CHANGE_POLLING_INTERVAL } = options;\n\n if (enablePolling) {\n const getHref = (): string => globalScope.location.href ?? '';\n let lastHref = getHref();\n const id = globalScope.setInterval(() => {\n const href = getHref();\n if (href === lastHref) {\n return;\n }\n lastHref = href;\n onUrlChange(href);\n options.onNavigation?.('replace');\n }, pollingInterval);\n return (): void => {\n if (id != null) {\n globalScope.clearInterval(id);\n }\n };\n }\n\n let subscriptionState = urlChangeSubscriptionsByScope.get(globalScope);\n if (!subscriptionState) {\n let lastHref: string | undefined = undefined;\n const callbacks = new Set<(href: string, reason: URLChangeReason) => void>();\n\n const getHref = (): string => globalScope.location.href ?? '';\n\n const notify = (reason: URLChangeReason): void => {\n const href = getHref();\n if (lastHref !== undefined && href === lastHref) return;\n lastHref = href;\n callbacks.forEach((c) => c(href, reason));\n };\n\n /**\n * Creates a patched version of history methods (pushState/replaceState)\n * that calls the original method and then emits a URL change event.\n */\n const createHistoryMethodPatch = <T extends typeof history.pushState | typeof history.replaceState>(\n originalMethod: T,\n reason: URLChangeReason,\n ) => {\n const patchedMethod = function (this: History, ...args: Parameters<T>) {\n const result = originalMethod.apply(this, args);\n // Read from location.href after history call so we always notify with resolved absolute URL.\n notify(reason);\n return result;\n } as PatchableHistoryMethod<T>;\n\n patchedMethod[PATCH_MARKER] = true;\n return patchedMethod;\n };\n\n const history = globalScope.history;\n if (history?.pushState) {\n const pushState = Reflect.get(history, 'pushState') as PatchableHistoryMethod<History['pushState']>;\n if (!pushState[PATCH_MARKER]) {\n history.pushState = createHistoryMethodPatch(pushState, 'push');\n }\n }\n if (history?.replaceState) {\n const replaceState = Reflect.get(history, 'replaceState') as PatchableHistoryMethod<History['replaceState']>;\n if (!replaceState[PATCH_MARKER]) {\n history.replaceState = createHistoryMethodPatch(replaceState, 'replace');\n }\n }\n\n subscriptionState = {\n callbacks,\n notify,\n onPopStateOrHashChange: (event) => notify(event?.type === 'hashchange' ? 'hash' : 'pop'),\n listenersAttached: false,\n };\n urlChangeSubscriptionsByScope.set(globalScope, subscriptionState);\n }\n\n const state = subscriptionState;\n if (!state.listenersAttached) {\n globalScope.addEventListener('popstate', state.onPopStateOrHashChange);\n globalScope.addEventListener('hashchange', state.onPopStateOrHashChange);\n state.listenersAttached = true;\n }\n\n const callback = (href: string, reason: URLChangeReason) => {\n onUrlChange(href);\n options.onNavigation?.(reason);\n };\n state.callbacks.add(callback);\n return (): void => {\n state.callbacks.delete(callback);\n if (state.callbacks.size === 0) {\n // Do not restore history methods: we are not aware of patches applied by other scripts.\n if (state.listenersAttached) {\n globalScope.removeEventListener('popstate', state.onPopStateOrHashChange);\n globalScope.removeEventListener('hashchange', state.onPopStateOrHashChange);\n state.listenersAttached = false;\n }\n }\n };\n}\n\n/**\n * Creates a URL tracking plugin for rrweb record function\n *\n * This plugin monitors URL changes in the browser and emits events when the URL changes.\n * It supports three tracking modes:\n * 1. Polling (if explicitly enabled) - periodically checks for URL changes\n * 2. History API + Hash routing (default) - patches pushState/replaceState, listens to popstate and hashchange\n * 3. Hash routing only (fallback) - listens to hashchange events when History API is unavailable\n *\n * The plugin handles edge cases gracefully:\n * - Missing or null location objects\n * - Undefined, null, or empty location.href values\n * - Temporal dead zone issues with variable declarations\n * - Consistent URL normalization across all code paths\n *\n * @param options Configuration options for URL tracking\n * @returns RecordPlugin instance that can be used with rrweb\n */\nexport function createUrlTrackingPlugin(\n options: URLTrackingPluginOptions = {},\n): RecordPlugin<URLTrackingPluginOptions> {\n return {\n name: 'amplitude/url-tracking@1',\n observer(cb, globalScope, pluginOptions?: URLTrackingPluginOptions) {\n // Merge options with plugin-level options taking precedence over constructor options\n const config = { ...options, ...pluginOptions };\n const ugcFilterRules = config.ugcFilterRules || [];\n const enablePolling = config.enablePolling ?? false;\n const pollingInterval = config.pollingInterval ?? DEFAULT_URL_CHANGE_POLLING_INTERVAL;\n const captureDocumentTitle = config.captureDocumentTitle ?? false;\n\n // Early return if no global scope is available\n if (!globalScope) {\n return () => {\n // No cleanup needed if no global scope available\n };\n }\n\n // Track the last URL to prevent duplicate events\n // Initialize to undefined to ensure first call always emits an event\n let lastTrackedUrl: string | undefined = undefined;\n\n // Helper functions\n /**\n * Gets the current URL with proper normalization\n * Handles edge cases where location.href might be undefined, null, or empty\n * Ensures consistent behavior across all code paths\n * @returns Normalized URL string (empty string if location unavailable)\n */\n const getCurrentUrl = (): string => {\n if (!globalScope.location) return '';\n return globalScope.location.href || '';\n };\n\n /**\n * Creates a URL change event with current page information\n * Applies UGC filtering if rules are configured\n * Uses getCurrentUrl() for consistent URL normalization\n * @returns URLChangeEvent with current page state\n */\n const createUrlChangeEvent = (): URLChangeEvent => {\n const { innerHeight, innerWidth, document } = globalScope;\n const currentUrl = getCurrentUrl();\n let currentTitle = '';\n if (captureDocumentTitle) {\n currentTitle = document?.title || '';\n }\n\n // Apply UGC filtering if rules are provided, otherwise use original URL\n const filteredUrl = ugcFilterRules.length > 0 ? getPageUrl(currentUrl, ugcFilterRules) : currentUrl;\n\n return {\n href: filteredUrl,\n title: currentTitle,\n viewportHeight: innerHeight,\n viewportWidth: innerWidth,\n type: 'url-change-event',\n };\n };\n\n /**\n * Emits a URL change event if the URL has actually changed\n * Always emits on first call (when lastTrackedUrl is undefined)\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\n };\n\n // Single helper: history + popstate + hashchange, or polling when enabled\n const unsubscribe = subscribeToUrlChanges(\n globalScope as Window,\n () => emitUrlChange(),\n enablePolling ? { enablePolling: true, pollingInterval } : {},\n );\n emitUrlChange();\n\n return (): void => unsubscribe();\n },\n options,\n };\n}\n\n/**\n * Default URL tracking plugin instance with default options\n * Can be used directly without custom configuration\n */\nexport const urlTrackingPlugin = createUrlTrackingPlugin();\n"]}
@@ -0,0 +1,5 @@
1
+ import { SemanticIdentity } from '@amplitude/amp-semantic';
2
+ export declare const SEMANTIC_TAB_STORAGE_KEY = "AMP_semantic_tab_v1";
3
+ /** Called only when semantic capture accepts an event. Shared by SDK instances in this document. */
4
+ export declare function getSemanticIdentity(scope: Window): SemanticIdentity;
5
+ //# sourceMappingURL=semantic-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-identity.d.ts","sourceRoot":"","sources":["../../src/semantic-identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhF,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAK9D,oGAAoG;AACpG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAiDnE"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSemanticIdentity = exports.SEMANTIC_TAB_STORAGE_KEY = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var amp_semantic_1 = require("@amplitude/amp-semantic");
6
+ exports.SEMANTIC_TAB_STORAGE_KEY = 'AMP_semantic_tab_v1';
7
+ var identities = new WeakMap();
8
+ var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
9
+ /** Called only when semantic capture accepts an event. Shared by SDK instances in this document. */
10
+ function getSemanticIdentity(scope) {
11
+ var document = scope.document;
12
+ var existing = identities.get(document);
13
+ if (existing)
14
+ return existing;
15
+ var identity = (0, amp_semantic_1.getDocumentIdentity)(document, scope);
16
+ // Child frames share their parent's sessionStorage. Do not claim its tab record.
17
+ if (scope.top === scope) {
18
+ try {
19
+ var storage_1 = scope.sessionStorage;
20
+ var read_1 = function () {
21
+ var _a;
22
+ try {
23
+ var value = JSON.parse((_a = storage_1.getItem(exports.SEMANTIC_TAB_STORAGE_KEY)) !== null && _a !== void 0 ? _a : 'null');
24
+ if (value && uuidPattern.test(value.tab_id) && (value.owner === null || typeof value.owner === 'string')) {
25
+ return value;
26
+ }
27
+ }
28
+ catch (_b) {
29
+ // Corrupt or blocked storage is equivalent to no saved identity.
30
+ }
31
+ return undefined;
32
+ };
33
+ var stored = read_1();
34
+ // A copied record still owned by a live document belongs to the opener/duplicated tab.
35
+ if ((stored === null || stored === void 0 ? void 0 : stored.owner) === null)
36
+ identity = Object.freeze(tslib_1.__assign(tslib_1.__assign({}, identity), { tab_id: stored.tab_id }));
37
+ var write_1 = function (owner) {
38
+ try {
39
+ storage_1.setItem(exports.SEMANTIC_TAB_STORAGE_KEY, JSON.stringify({ tab_id: identity.tab_id, owner: owner }));
40
+ }
41
+ catch (_a) {
42
+ // Keep the current in-memory identity stable if storage becomes unavailable.
43
+ }
44
+ };
45
+ storage_1.setItem(exports.SEMANTIC_TAB_STORAGE_KEY, JSON.stringify({ tab_id: identity.tab_id, owner: identity.document_id }));
46
+ scope.addEventListener('pagehide', function () {
47
+ var _a;
48
+ // Release on actual navigation, not beforeunload (which can be cancelled).
49
+ // This also allows same-tab navigation while the current document enters bfcache.
50
+ if (((_a = read_1()) === null || _a === void 0 ? void 0 : _a.owner) === identity.document_id)
51
+ write_1(null);
52
+ });
53
+ scope.addEventListener('pageshow', function (event) {
54
+ if (event.persisted)
55
+ write_1(identity.document_id);
56
+ });
57
+ }
58
+ catch (_a) {
59
+ // No storage access: identity lasts for this document only.
60
+ identity = (0, amp_semantic_1.getDocumentIdentity)(document, scope);
61
+ }
62
+ }
63
+ identities.set(document, identity);
64
+ return identity;
65
+ }
66
+ exports.getSemanticIdentity = getSemanticIdentity;
67
+ //# sourceMappingURL=semantic-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-identity.js","sourceRoot":"","sources":["../../src/semantic-identity.ts"],"names":[],"mappings":";;;;AAAA,wDAAgF;AAEnE,QAAA,wBAAwB,GAAG,qBAAqB,CAAC;AAC9D,IAAM,UAAU,GAAG,IAAI,OAAO,EAA8B,CAAC;AAE7D,IAAM,WAAW,GAAG,wEAAwE,CAAC;AAE7F,oGAAoG;AACpG,SAAgB,mBAAmB,CAAC,KAAa;IAC/C,IAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAChC,IAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,QAAQ,GAAG,IAAA,kCAAmB,EAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpD,iFAAiF;IACjF,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE;QACvB,IAAI;YACF,IAAM,SAAO,GAAG,KAAK,CAAC,cAAc,CAAC;YACrC,IAAM,MAAI,GAAG;;gBACX,IAAI;oBACF,IAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAA,SAAO,CAAC,OAAO,CAAC,gCAAwB,CAAC,mCAAI,MAAM,CAAqB,CAAC;oBAClG,IAAI,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,EAAE;wBACxG,OAAO,KAAK,CAAC;qBACd;iBACF;gBAAC,WAAM;oBACN,iEAAiE;iBAClE;gBACD,OAAO,SAAS,CAAC;YACnB,CAAC,CAAC;YACF,IAAM,MAAM,GAAG,MAAI,EAAE,CAAC;YACtB,uFAAuF;YACvF,IAAI,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,KAAK,MAAK,IAAI;gBAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,uCAAM,QAAQ,KAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAG,CAAC;YAC7F,IAAM,OAAK,GAAG,UAAC,KAAoB;gBACjC,IAAI;oBACF,SAAO,CAAC,OAAO,CAAC,gCAAwB,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC,CAAC;iBAC/F;gBAAC,WAAM;oBACN,6EAA6E;iBAC9E;YACH,CAAC,CAAC;YACF,SAAO,CAAC,OAAO,CACb,gCAAwB,EACxB,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CACzE,CAAC;YACF,KAAK,CAAC,gBAAgB,CAAC,UAAU,EAAE;;gBACjC,2EAA2E;gBAC3E,kFAAkF;gBAClF,IAAI,CAAA,MAAA,MAAI,EAAE,0CAAE,KAAK,MAAK,QAAQ,CAAC,WAAW;oBAAE,OAAK,CAAC,IAAI,CAAC,CAAC;YAC1D,CAAC,CAAC,CAAC;YACH,KAAK,CAAC,gBAAgB,CAAC,UAAU,EAAE,UAAC,KAAK;gBACvC,IAAI,KAAK,CAAC,SAAS;oBAAE,OAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YACnD,CAAC,CAAC,CAAC;SACJ;QAAC,WAAM;YACN,4DAA4D;YAC5D,QAAQ,GAAG,IAAA,kCAAmB,EAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;SACjD;KACF;IACD,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACnC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAjDD,kDAiDC","sourcesContent":["import { getDocumentIdentity, SemanticIdentity } from '@amplitude/amp-semantic';\n\nexport const SEMANTIC_TAB_STORAGE_KEY = 'AMP_semantic_tab_v1';\nconst identities = new WeakMap<Document, SemanticIdentity>();\ntype StoredTab = { tab_id: string; owner: string | null };\nconst uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/** Called only when semantic capture accepts an event. Shared by SDK instances in this document. */\nexport function getSemanticIdentity(scope: Window): SemanticIdentity {\n const document = scope.document;\n const existing = identities.get(document);\n if (existing) return existing;\n let identity = getDocumentIdentity(document, scope);\n // Child frames share their parent's sessionStorage. Do not claim its tab record.\n if (scope.top === scope) {\n try {\n const storage = scope.sessionStorage;\n const read = (): StoredTab | undefined => {\n try {\n const value = JSON.parse(storage.getItem(SEMANTIC_TAB_STORAGE_KEY) ?? 'null') as StoredTab | null;\n if (value && uuidPattern.test(value.tab_id) && (value.owner === null || typeof value.owner === 'string')) {\n return value;\n }\n } catch {\n // Corrupt or blocked storage is equivalent to no saved identity.\n }\n return undefined;\n };\n const stored = read();\n // A copied record still owned by a live document belongs to the opener/duplicated tab.\n if (stored?.owner === null) identity = Object.freeze({ ...identity, tab_id: stored.tab_id });\n const write = (owner: string | null) => {\n try {\n storage.setItem(SEMANTIC_TAB_STORAGE_KEY, JSON.stringify({ tab_id: identity.tab_id, owner }));\n } catch {\n // Keep the current in-memory identity stable if storage becomes unavailable.\n }\n };\n storage.setItem(\n SEMANTIC_TAB_STORAGE_KEY,\n JSON.stringify({ tab_id: identity.tab_id, owner: identity.document_id }),\n );\n scope.addEventListener('pagehide', () => {\n // Release on actual navigation, not beforeunload (which can be cancelled).\n // This also allows same-tab navigation while the current document enters bfcache.\n if (read()?.owner === identity.document_id) write(null);\n });\n scope.addEventListener('pageshow', (event) => {\n if (event.persisted) write(identity.document_id);\n });\n } catch {\n // No storage access: identity lasts for this document only.\n identity = getDocumentIdentity(document, scope);\n }\n }\n identities.set(document, identity);\n return identity;\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { RecordingSignal, SemanticCaptureConfig, SemanticHost } from '@amplitude/amp-semantic';
2
+ import { eventWithTime } from '@amplitude/rrweb-types';
3
+ /** Bridges accepted recorder events and startup ordering into semantic capture. */
4
+ export declare class ReplaySemanticAdapter {
5
+ private readonly capture;
6
+ private sessionId?;
7
+ private ready;
8
+ private firstSnapshot;
9
+ private pendingSnapshots;
10
+ private pendingEvents;
11
+ private startSignal;
12
+ private resumeReason;
13
+ private config;
14
+ constructor(host: SemanticHost);
15
+ prepare(config: SemanticCaptureConfig, sessionId: string | number): void;
16
+ /** rrweb may synchronously emit its initial snapshot before returning its cancellation callback. */
17
+ recordingReady(): void;
18
+ /** Called only after the underlying replay event is accepted for delivery. */
19
+ event(event: eventWithTime): void;
20
+ click(event: MouseEvent): void;
21
+ snapshot(): void;
22
+ stop(reason?: RecordingSignal['reason'], state?: 'stop' | 'pause'): void;
23
+ }
24
+ //# sourceMappingURL=semantic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../../src/semantic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,eAAe,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACtH,OAAO,EAAgC,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGrF,mFAAmF;AACnF,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2C;IACnE,OAAO,CAAC,SAAS,CAAC,CAAkB;IACpC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,WAAW,CAAuD;IAC1E,OAAO,CAAC,YAAY,CAAsC;IAC1D,OAAO,CAAC,MAAM,CAA6C;gBAE/C,IAAI,EAAE,YAAY;IAI9B,OAAO,CAAC,MAAM,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAiBxE,oGAAoG;IACpG,cAAc,IAAI,IAAI;IAYtB,8EAA8E;IAC9E,KAAK,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IA6BjC,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAI9B,QAAQ,IAAI,IAAI;IAahB,IAAI,CAAC,MAAM,GAAE,eAAe,CAAC,QAAQ,CAAe,EAAE,KAAK,GAAE,MAAM,GAAG,OAAgB,GAAG,IAAI;CAa9F"}
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReplaySemanticAdapter = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var amp_semantic_1 = require("@amplitude/amp-semantic");
6
+ var rrweb_types_1 = require("@amplitude/rrweb-types");
7
+ /** Bridges accepted recorder events and startup ordering into semantic capture. */
8
+ var ReplaySemanticAdapter = /** @class */ (function () {
9
+ function ReplaySemanticAdapter(host) {
10
+ this.ready = false;
11
+ this.firstSnapshot = false;
12
+ this.pendingSnapshots = 0;
13
+ this.pendingEvents = [];
14
+ this.startSignal = { state: 'start', reason: 'init' };
15
+ this.resumeReason = 'focus';
16
+ this.config = { enabled: false };
17
+ this.capture = (0, amp_semantic_1.createSemanticCapture)(this.config, host);
18
+ }
19
+ ReplaySemanticAdapter.prototype.prepare = function (config, sessionId) {
20
+ var newSession = this.sessionId !== sessionId;
21
+ var firstSession = this.sessionId === undefined;
22
+ this.config = config;
23
+ this.capture.updateConfig(config);
24
+ if (newSession)
25
+ this.capture.resetSession();
26
+ this.startSignal = {
27
+ state: newSession ? 'start' : 'resume',
28
+ reason: firstSession ? 'init' : newSession ? 'new_session' : this.resumeReason,
29
+ };
30
+ this.sessionId = sessionId;
31
+ this.ready = false;
32
+ this.firstSnapshot = false;
33
+ this.pendingSnapshots = 0;
34
+ this.pendingEvents = [];
35
+ };
36
+ /** rrweb may synchronously emit its initial snapshot before returning its cancellation callback. */
37
+ ReplaySemanticAdapter.prototype.recordingReady = function () {
38
+ var e_1, _a;
39
+ if (!this.config.enabled || this.ready)
40
+ return;
41
+ this.ready = true;
42
+ this.capture.start();
43
+ var events = this.pendingEvents;
44
+ this.pendingEvents = [];
45
+ try {
46
+ for (var events_1 = tslib_1.__values(events), events_1_1 = events_1.next(); !events_1_1.done; events_1_1 = events_1.next()) {
47
+ var event_1 = events_1_1.value;
48
+ this.event(event_1);
49
+ }
50
+ }
51
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
52
+ finally {
53
+ try {
54
+ if (events_1_1 && !events_1_1.done && (_a = events_1.return)) _a.call(events_1);
55
+ }
56
+ finally { if (e_1) throw e_1.error; }
57
+ }
58
+ var pending = this.pendingSnapshots;
59
+ this.pendingSnapshots = 0;
60
+ for (var i = 0; i < pending; i++)
61
+ this.snapshot();
62
+ };
63
+ /** Called only after the underlying replay event is accepted for delivery. */
64
+ ReplaySemanticAdapter.prototype.event = function (event) {
65
+ if (!this.config.enabled)
66
+ return;
67
+ var snapshot = event.type === rrweb_types_1.EventType.FullSnapshot;
68
+ var mutation = event.type === rrweb_types_1.EventType.IncrementalSnapshot && event.data.source === rrweb_types_1.IncrementalSource.Mutation;
69
+ var payload = event.type === rrweb_types_1.EventType.Plugin && event.data.plugin === 'amplitude/url-tracking@1'
70
+ ? event.data.payload
71
+ : undefined;
72
+ var url = payload && typeof payload === 'object' && payload.type === 'url-change-event' && typeof payload.href === 'string'
73
+ ? payload.href
74
+ : undefined;
75
+ if (!snapshot && !mutation && url === undefined)
76
+ return;
77
+ if (!this.ready) {
78
+ // Startup is synchronous; retain a bounded number of recorder events until start().
79
+ if (this.pendingEvents.length < 128)
80
+ this.pendingEvents.push(event);
81
+ return;
82
+ }
83
+ if (!this.capture.isActive())
84
+ return;
85
+ if (url !== undefined) {
86
+ this.capture.notify('url', { url: url, timestamp: event.timestamp });
87
+ }
88
+ else if (event.type === rrweb_types_1.EventType.FullSnapshot) {
89
+ this.capture.notify('dom', { type: 'snapshot', node: event.data.node });
90
+ this.snapshot();
91
+ }
92
+ else if (event.type === rrweb_types_1.EventType.IncrementalSnapshot && event.data.source === rrweb_types_1.IncrementalSource.Mutation) {
93
+ this.capture.notify('dom', tslib_1.__assign(tslib_1.__assign({}, event.data), { type: 'mutation' }));
94
+ }
95
+ };
96
+ ReplaySemanticAdapter.prototype.click = function (event) {
97
+ if (this.ready)
98
+ this.capture.notify('click', event);
99
+ };
100
+ ReplaySemanticAdapter.prototype.snapshot = function () {
101
+ if (!this.config.enabled)
102
+ return;
103
+ if (!this.ready) {
104
+ this.pendingSnapshots++;
105
+ return;
106
+ }
107
+ if (!this.firstSnapshot) {
108
+ this.firstSnapshot = true;
109
+ this.capture.notify('recording', this.startSignal);
110
+ }
111
+ this.capture.notify('snapshot', undefined);
112
+ };
113
+ ReplaySemanticAdapter.prototype.stop = function (reason, state) {
114
+ if (reason === void 0) { reason = 'targeting'; }
115
+ if (state === void 0) { state = 'stop'; }
116
+ var emitStop = this.ready && this.firstSnapshot;
117
+ this.ready = false;
118
+ this.firstSnapshot = false;
119
+ if (emitStop) {
120
+ this.resumeReason = reason;
121
+ this.capture.notify('recording', { state: state, reason: reason });
122
+ }
123
+ this.capture.stop();
124
+ this.pendingEvents = [];
125
+ this.ready = false;
126
+ this.pendingSnapshots = 0;
127
+ };
128
+ return ReplaySemanticAdapter;
129
+ }());
130
+ exports.ReplaySemanticAdapter = ReplaySemanticAdapter;
131
+ //# sourceMappingURL=semantic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../src/semantic.ts"],"names":[],"mappings":";;;;AAAA,wDAAsH;AACtH,sDAAqF;AAGrF,mFAAmF;AACnF;IAWE,+BAAY,IAAkB;QARtB,UAAK,GAAG,KAAK,CAAC;QACd,kBAAa,GAAG,KAAK,CAAC;QACtB,qBAAgB,GAAG,CAAC,CAAC;QACrB,kBAAa,GAAoB,EAAE,CAAC;QACpC,gBAAW,GAAoB,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAClE,iBAAY,GAA8B,OAAO,CAAC;QAClD,WAAM,GAA0B,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAGzD,IAAI,CAAC,OAAO,GAAG,IAAA,oCAAqB,EAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED,uCAAO,GAAP,UAAQ,MAA6B,EAAE,SAA0B;QAC/D,IAAM,UAAU,GAAG,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC;QAChD,IAAM,YAAY,GAAG,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC;QAClD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,UAAU;YAAE,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;QAC5C,IAAI,CAAC,WAAW,GAAG;YACjB,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;YACtC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY;SAC/E,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;IAC1B,CAAC;IAED,oGAAoG;IACpG,8CAAc,GAAd;;QACE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QAC/C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC;QAClC,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;;YACxB,KAAoB,IAAA,WAAA,iBAAA,MAAM,CAAA,8BAAA;gBAArB,IAAM,OAAK,mBAAA;gBAAY,IAAI,CAAC,KAAK,CAAC,OAAK,CAAC,CAAC;aAAA;;;;;;;;;QAC9C,IAAM,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC;QACtC,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE;YAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;IACpD,CAAC;IAED,8EAA8E;IAC9E,qCAAK,GAAL,UAAM,KAAoB;QACxB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QACjC,IAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,KAAK,uBAAS,CAAC,YAAY,CAAC;QACvD,IAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,KAAK,uBAAS,CAAC,mBAAmB,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,+BAAiB,CAAC,QAAQ,CAAC;QAClH,IAAM,OAAO,GACX,KAAK,CAAC,IAAI,KAAK,uBAAS,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,0BAA0B;YACjF,CAAC,CAAE,KAAK,CAAC,IAAI,CAAC,OAA0C;YACxD,CAAC,CAAC,SAAS,CAAC;QAChB,IAAM,GAAG,GACP,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,kBAAkB,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;YAC/G,CAAC,CAAC,OAAO,CAAC,IAAI;YACd,CAAC,CAAC,SAAS,CAAC;QAChB,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO;QACxD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;YACf,oFAAoF;YACpF,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG;gBAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpE,OAAO;SACR;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;YAAE,OAAO;QACrC,IAAI,GAAG,KAAK,SAAS,EAAE;YACrB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,GAAG,KAAA,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;SACjE;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAS,CAAC,YAAY,EAAE;YAChD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YACxE,IAAI,CAAC,QAAQ,EAAE,CAAC;SACjB;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAS,CAAC,mBAAmB,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,+BAAiB,CAAC,QAAQ,EAAE;YAC3G,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,wCAAO,KAAK,CAAC,IAAI,KAAE,IAAI,EAAE,UAAU,IAAG,CAAC;SACjE;IACH,CAAC;IAED,qCAAK,GAAL,UAAM,KAAiB;QACrB,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACtD,CAAC;IAED,wCAAQ,GAAR;QACE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QACjC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;YACf,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,OAAO;SACR;QACD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE;YACvB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;YAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;SACpD;QACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED,oCAAI,GAAJ,UAAK,MAA+C,EAAE,KAAgC;QAAjF,uBAAA,EAAA,oBAA+C;QAAE,sBAAA,EAAA,cAAgC;QACpF,IAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,aAAa,CAAC;QAClD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,QAAQ,EAAE;YACZ,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC;YAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,OAAA,EAAE,MAAM,QAAA,EAAE,CAAC,CAAC;SACrD;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACpB,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;IAC5B,CAAC;IACH,4BAAC;AAAD,CAAC,AAzGD,IAyGC;AAzGY,sDAAqB","sourcesContent":["import { createSemanticCapture, RecordingSignal, SemanticCaptureConfig, SemanticHost } from '@amplitude/amp-semantic';\nimport { EventType, IncrementalSource, eventWithTime } from '@amplitude/rrweb-types';\nimport type { URLChangeEvent } from './plugins/url-tracking-plugin';\n\n/** Bridges accepted recorder events and startup ordering into semantic capture. */\nexport class ReplaySemanticAdapter {\n private readonly capture: ReturnType<typeof createSemanticCapture>;\n private sessionId?: string | number;\n private ready = false;\n private firstSnapshot = false;\n private pendingSnapshots = 0;\n private pendingEvents: eventWithTime[] = [];\n private startSignal: RecordingSignal = { state: 'start', reason: 'init' };\n private resumeReason: RecordingSignal['reason'] = 'focus';\n private config: SemanticCaptureConfig = { enabled: false };\n\n constructor(host: SemanticHost) {\n this.capture = createSemanticCapture(this.config, host);\n }\n\n prepare(config: SemanticCaptureConfig, sessionId: string | number): void {\n const newSession = this.sessionId !== sessionId;\n const firstSession = this.sessionId === undefined;\n this.config = config;\n this.capture.updateConfig(config);\n if (newSession) this.capture.resetSession();\n this.startSignal = {\n state: newSession ? 'start' : 'resume',\n reason: firstSession ? 'init' : newSession ? 'new_session' : this.resumeReason,\n };\n this.sessionId = sessionId;\n this.ready = false;\n this.firstSnapshot = false;\n this.pendingSnapshots = 0;\n this.pendingEvents = [];\n }\n\n /** rrweb may synchronously emit its initial snapshot before returning its cancellation callback. */\n recordingReady(): void {\n if (!this.config.enabled || this.ready) return;\n this.ready = true;\n this.capture.start();\n const events = this.pendingEvents;\n this.pendingEvents = [];\n for (const event of events) this.event(event);\n const pending = this.pendingSnapshots;\n this.pendingSnapshots = 0;\n for (let i = 0; i < pending; i++) this.snapshot();\n }\n\n /** Called only after the underlying replay event is accepted for delivery. */\n event(event: eventWithTime): void {\n if (!this.config.enabled) return;\n const snapshot = event.type === EventType.FullSnapshot;\n const mutation = event.type === EventType.IncrementalSnapshot && event.data.source === IncrementalSource.Mutation;\n const payload =\n event.type === EventType.Plugin && event.data.plugin === 'amplitude/url-tracking@1'\n ? (event.data.payload as Partial<URLChangeEvent> | null)\n : undefined;\n const url =\n payload && typeof payload === 'object' && payload.type === 'url-change-event' && typeof payload.href === 'string'\n ? payload.href\n : undefined;\n if (!snapshot && !mutation && url === undefined) return;\n if (!this.ready) {\n // Startup is synchronous; retain a bounded number of recorder events until start().\n if (this.pendingEvents.length < 128) this.pendingEvents.push(event);\n return;\n }\n if (!this.capture.isActive()) return;\n if (url !== undefined) {\n this.capture.notify('url', { url, timestamp: event.timestamp });\n } else if (event.type === EventType.FullSnapshot) {\n this.capture.notify('dom', { type: 'snapshot', node: event.data.node });\n this.snapshot();\n } else if (event.type === EventType.IncrementalSnapshot && event.data.source === IncrementalSource.Mutation) {\n this.capture.notify('dom', { ...event.data, type: 'mutation' });\n }\n }\n\n click(event: MouseEvent): void {\n if (this.ready) this.capture.notify('click', event);\n }\n\n snapshot(): void {\n if (!this.config.enabled) return;\n if (!this.ready) {\n this.pendingSnapshots++;\n return;\n }\n if (!this.firstSnapshot) {\n this.firstSnapshot = true;\n this.capture.notify('recording', this.startSignal);\n }\n this.capture.notify('snapshot', undefined);\n }\n\n stop(reason: RecordingSignal['reason'] = 'targeting', state: 'stop' | 'pause' = 'stop'): void {\n const emitStop = this.ready && this.firstSnapshot;\n this.ready = false;\n this.firstSnapshot = false;\n if (emitStop) {\n this.resumeReason = reason;\n this.capture.notify('recording', { state, reason });\n }\n this.capture.stop();\n this.pendingEvents = [];\n this.ready = false;\n this.pendingSnapshots = 0;\n }\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import type { RecordingSignal } from '@amplitude/amp-semantic';
1
2
  import { ILogger } from '@amplitude/analytics-core';
2
3
  import { LoggingConfig, SessionReplayJoinedConfig, SessionReplayJoinedConfigGenerator } from './config/types';
3
4
  import { CustomRRwebEvent } from './constants';
@@ -34,6 +35,7 @@ export declare class SessionReplay implements AmplitudeSessionReplay {
34
35
  private networkObservers?;
35
36
  private metadata;
36
37
  private recordFunction;
38
+ private semanticAdapter?;
37
39
  private recordEventsInFlight;
38
40
  /**
39
41
  * When false, capture is paused by a customer `stop()` (or `shutdown()`) call.
@@ -114,12 +116,13 @@ export declare class SessionReplay implements AmplitudeSessionReplay {
114
116
  recordEvents(shouldLogMetadata?: boolean): Promise<void>;
115
117
  private shouldAbandonRecordStart;
116
118
  private _recordEvents;
119
+ private prepareSemanticCapture;
117
120
  private buildRRWebRecordOptions;
118
121
  private _recordEventsInChildMode;
119
122
  addCustomRRWebEvent: (eventName: CustomRRwebEvent, eventData?: {
120
123
  [key: string]: any;
121
124
  }, addStorageInfo?: boolean) => Promise<void>;
122
- stopRecordingEvents: () => void;
125
+ stopRecordingEvents: (reason?: RecordingSignal['reason'], state?: 'stop' | 'pause') => void;
123
126
  getDeviceId(): string | undefined;
124
127
  getSessionId(): string | number | undefined;
125
128
  start(): import("@amplitude/analytics-core").AmplitudeReturn<void>;