@amplitude/session-replay-browser 1.26.2 → 1.28.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 (114) hide show
  1. package/README.md +13 -3
  2. package/lib/cjs/config/joined-config.d.ts.map +1 -1
  3. package/lib/cjs/config/joined-config.js +10 -3
  4. package/lib/cjs/config/joined-config.js.map +1 -1
  5. package/lib/cjs/config/local-config.d.ts +3 -0
  6. package/lib/cjs/config/local-config.d.ts.map +1 -1
  7. package/lib/cjs/config/local-config.js +4 -1
  8. package/lib/cjs/config/local-config.js.map +1 -1
  9. package/lib/cjs/config/types.d.ts +30 -0
  10. package/lib/cjs/config/types.d.ts.map +1 -1
  11. package/lib/cjs/config/types.js.map +1 -1
  12. package/lib/cjs/constants.d.ts +3 -1
  13. package/lib/cjs/constants.d.ts.map +1 -1
  14. package/lib/cjs/constants.js +3 -1
  15. package/lib/cjs/constants.js.map +1 -1
  16. package/lib/cjs/helpers.d.ts +7 -0
  17. package/lib/cjs/helpers.d.ts.map +1 -1
  18. package/lib/cjs/index.d.ts +1 -1
  19. package/lib/cjs/index.d.ts.map +1 -1
  20. package/lib/cjs/index.js +2 -2
  21. package/lib/cjs/index.js.map +1 -1
  22. package/lib/cjs/plugins/index.d.ts +2 -0
  23. package/lib/cjs/plugins/index.d.ts.map +1 -0
  24. package/lib/cjs/plugins/index.js +5 -0
  25. package/lib/cjs/plugins/index.js.map +1 -0
  26. package/lib/cjs/plugins/url-tracking-plugin.d.ts +56 -0
  27. package/lib/cjs/plugins/url-tracking-plugin.d.ts.map +1 -0
  28. package/lib/cjs/plugins/url-tracking-plugin.js +187 -0
  29. package/lib/cjs/plugins/url-tracking-plugin.js.map +1 -0
  30. package/lib/cjs/session-replay-factory.d.ts.map +1 -1
  31. package/lib/cjs/session-replay-factory.js +1 -0
  32. package/lib/cjs/session-replay-factory.js.map +1 -1
  33. package/lib/cjs/session-replay.d.ts +12 -3
  34. package/lib/cjs/session-replay.d.ts.map +1 -1
  35. package/lib/cjs/session-replay.js +132 -19
  36. package/lib/cjs/session-replay.js.map +1 -1
  37. package/lib/cjs/targeting/targeting-idb-store.d.ts +38 -0
  38. package/lib/cjs/targeting/targeting-idb-store.d.ts.map +1 -0
  39. package/lib/cjs/targeting/targeting-idb-store.js +146 -0
  40. package/lib/cjs/targeting/targeting-idb-store.js.map +1 -0
  41. package/lib/cjs/targeting/targeting-manager.d.ts +11 -0
  42. package/lib/cjs/targeting/targeting-manager.d.ts.map +1 -0
  43. package/lib/cjs/targeting/targeting-manager.js +60 -0
  44. package/lib/cjs/targeting/targeting-manager.js.map +1 -0
  45. package/lib/cjs/typings/session-replay.d.ts +2 -0
  46. package/lib/cjs/typings/session-replay.d.ts.map +1 -1
  47. package/lib/cjs/typings/session-replay.js.map +1 -1
  48. package/lib/cjs/version.d.ts +1 -1
  49. package/lib/cjs/version.js +1 -1
  50. package/lib/cjs/version.js.map +1 -1
  51. package/lib/esm/config/joined-config.d.ts.map +1 -1
  52. package/lib/esm/config/joined-config.js +10 -3
  53. package/lib/esm/config/joined-config.js.map +1 -1
  54. package/lib/esm/config/local-config.d.ts +3 -0
  55. package/lib/esm/config/local-config.d.ts.map +1 -1
  56. package/lib/esm/config/local-config.js +5 -2
  57. package/lib/esm/config/local-config.js.map +1 -1
  58. package/lib/esm/config/types.d.ts +30 -0
  59. package/lib/esm/config/types.d.ts.map +1 -1
  60. package/lib/esm/config/types.js.map +1 -1
  61. package/lib/esm/constants.d.ts +3 -1
  62. package/lib/esm/constants.d.ts.map +1 -1
  63. package/lib/esm/constants.js +2 -0
  64. package/lib/esm/constants.js.map +1 -1
  65. package/lib/esm/helpers.d.ts +7 -0
  66. package/lib/esm/helpers.d.ts.map +1 -1
  67. package/lib/esm/index.d.ts +1 -1
  68. package/lib/esm/index.d.ts.map +1 -1
  69. package/lib/esm/index.js +1 -1
  70. package/lib/esm/index.js.map +1 -1
  71. package/lib/esm/plugins/index.d.ts +2 -0
  72. package/lib/esm/plugins/index.d.ts.map +1 -0
  73. package/lib/esm/plugins/index.js +2 -0
  74. package/lib/esm/plugins/index.js.map +1 -0
  75. package/lib/esm/plugins/url-tracking-plugin.d.ts +56 -0
  76. package/lib/esm/plugins/url-tracking-plugin.d.ts.map +1 -0
  77. package/lib/esm/plugins/url-tracking-plugin.js +183 -0
  78. package/lib/esm/plugins/url-tracking-plugin.js.map +1 -0
  79. package/lib/esm/session-replay-factory.d.ts.map +1 -1
  80. package/lib/esm/session-replay-factory.js +1 -0
  81. package/lib/esm/session-replay-factory.js.map +1 -1
  82. package/lib/esm/session-replay.d.ts +12 -3
  83. package/lib/esm/session-replay.d.ts.map +1 -1
  84. package/lib/esm/session-replay.js +133 -20
  85. package/lib/esm/session-replay.js.map +1 -1
  86. package/lib/esm/targeting/targeting-idb-store.d.ts +38 -0
  87. package/lib/esm/targeting/targeting-idb-store.d.ts.map +1 -0
  88. package/lib/esm/targeting/targeting-idb-store.js +143 -0
  89. package/lib/esm/targeting/targeting-idb-store.js.map +1 -0
  90. package/lib/esm/targeting/targeting-manager.d.ts +11 -0
  91. package/lib/esm/targeting/targeting-manager.d.ts.map +1 -0
  92. package/lib/esm/targeting/targeting-manager.js +56 -0
  93. package/lib/esm/targeting/targeting-manager.js.map +1 -0
  94. package/lib/esm/typings/session-replay.d.ts +2 -0
  95. package/lib/esm/typings/session-replay.d.ts.map +1 -1
  96. package/lib/esm/typings/session-replay.js.map +1 -1
  97. package/lib/esm/version.d.ts +1 -1
  98. package/lib/esm/version.js +1 -1
  99. package/lib/esm/version.js.map +1 -1
  100. package/lib/scripts/observers-min.js +1 -1
  101. package/lib/scripts/observers-min.js.gz +0 -0
  102. package/lib/scripts/observers-min.js.map +1 -1
  103. package/lib/scripts/session-replay-browser-esm.js +1 -1
  104. package/lib/scripts/session-replay-browser-esm.js.gz +0 -0
  105. package/lib/scripts/session-replay-browser-min.js +1 -1
  106. package/lib/scripts/session-replay-browser-min.js.gz +0 -0
  107. package/lib/scripts/session-replay-browser-min.js.map +1 -1
  108. package/lib/scripts/session-replay-min.js +1 -1
  109. package/lib/scripts/session-replay-min.js.gz +0 -0
  110. package/lib/scripts/session-replay-min.js.map +1 -1
  111. package/lib/scripts/targeting-min.js +2 -0
  112. package/lib/scripts/targeting-min.js.gz +0 -0
  113. package/lib/scripts/targeting-min.js.map +1 -0
  114. package/package.json +6 -3
@@ -0,0 +1,183 @@
1
+ import { __assign } from "tslib";
2
+ import { getPageUrl } from '../helpers';
3
+ import { DEFAULT_URL_CHANGE_POLLING_INTERVAL } from '../constants';
4
+ /**
5
+ * Creates a URL tracking plugin for rrweb record function
6
+ *
7
+ * This plugin monitors URL changes in the browser and emits events when the URL changes.
8
+ * It supports three tracking modes:
9
+ * 1. Polling (if explicitly enabled) - periodically checks for URL changes
10
+ * 2. History API + Hash routing (default) - patches pushState/replaceState, listens to popstate and hashchange
11
+ * 3. Hash routing only (fallback) - listens to hashchange events when History API is unavailable
12
+ *
13
+ * The plugin handles edge cases gracefully:
14
+ * - Missing or null location objects
15
+ * - Undefined, null, or empty location.href values
16
+ * - Temporal dead zone issues with variable declarations
17
+ * - Consistent URL normalization across all code paths
18
+ *
19
+ * @param options Configuration options for URL tracking
20
+ * @returns RecordPlugin instance that can be used with rrweb
21
+ */
22
+ export function createUrlTrackingPlugin(options) {
23
+ if (options === void 0) { options = {}; }
24
+ return {
25
+ name: 'amplitude/url-tracking@1',
26
+ observer: function (cb, globalScope, pluginOptions) {
27
+ var _a, _b, _c;
28
+ // Merge options with plugin-level options taking precedence over constructor options
29
+ var config = __assign(__assign({}, options), pluginOptions);
30
+ var ugcFilterRules = config.ugcFilterRules || [];
31
+ var enablePolling = (_a = config.enablePolling) !== null && _a !== void 0 ? _a : false;
32
+ var pollingInterval = (_b = config.pollingInterval) !== null && _b !== void 0 ? _b : DEFAULT_URL_CHANGE_POLLING_INTERVAL;
33
+ var captureDocumentTitle = (_c = config.captureDocumentTitle) !== null && _c !== void 0 ? _c : false;
34
+ // Early return if no global scope is available
35
+ if (!globalScope) {
36
+ return function () {
37
+ // No cleanup needed if no global scope available
38
+ };
39
+ }
40
+ // Track the last URL to prevent duplicate events
41
+ // Initialize to undefined to ensure first call always emits an event
42
+ var lastTrackedUrl = undefined;
43
+ // Helper functions
44
+ /**
45
+ * Gets the current URL with proper normalization
46
+ * Handles edge cases where location.href might be undefined, null, or empty
47
+ * Ensures consistent behavior across all code paths
48
+ * @returns Normalized URL string (empty string if location unavailable)
49
+ */
50
+ var getCurrentUrl = function () {
51
+ if (!globalScope.location)
52
+ return '';
53
+ return globalScope.location.href || '';
54
+ };
55
+ /**
56
+ * Creates a URL change event with current page information
57
+ * Applies UGC filtering if rules are configured
58
+ * Uses getCurrentUrl() for consistent URL normalization
59
+ * @returns URLChangeEvent with current page state
60
+ */
61
+ var createUrlChangeEvent = function () {
62
+ var innerHeight = globalScope.innerHeight, innerWidth = globalScope.innerWidth, document = globalScope.document;
63
+ var currentUrl = getCurrentUrl();
64
+ var currentTitle = '';
65
+ if (captureDocumentTitle) {
66
+ currentTitle = (document === null || document === void 0 ? void 0 : document.title) || '';
67
+ }
68
+ // Apply UGC filtering if rules are provided, otherwise use original URL
69
+ var filteredUrl = ugcFilterRules.length > 0 ? getPageUrl(currentUrl, ugcFilterRules) : currentUrl;
70
+ return {
71
+ href: filteredUrl,
72
+ title: currentTitle,
73
+ viewportHeight: innerHeight,
74
+ viewportWidth: innerWidth,
75
+ type: 'url-change-event',
76
+ };
77
+ };
78
+ /**
79
+ * Emits a URL change event if the URL has actually changed
80
+ * Always emits on first call (when lastTrackedUrl is undefined)
81
+ * Prevents duplicate events for the same URL on subsequent calls
82
+ * Handles edge cases like undefined/null/empty URLs gracefully
83
+ */
84
+ var emitUrlChange = function () {
85
+ var currentUrl = getCurrentUrl();
86
+ // Always emit on first call, or if URL actually changed
87
+ if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {
88
+ lastTrackedUrl = currentUrl;
89
+ var event_1 = createUrlChangeEvent();
90
+ cb(event_1);
91
+ }
92
+ };
93
+ /**
94
+ * Creates a patched version of history methods (pushState/replaceState)
95
+ * that calls the original method and then emits a URL change event
96
+ * Ensures URL changes are detected even when history methods are called programmatically
97
+ * @param originalMethod The original history method to patch
98
+ * @returns Patched function that calls original method then emits URL change event
99
+ */
100
+ var createHistoryMethodPatch = function (originalMethod) {
101
+ return function () {
102
+ var args = [];
103
+ for (var _i = 0; _i < arguments.length; _i++) {
104
+ args[_i] = arguments[_i];
105
+ }
106
+ // Call the original history method first
107
+ var result = originalMethod.apply(this, args);
108
+ // Then emit URL change event
109
+ emitUrlChange();
110
+ return result;
111
+ };
112
+ };
113
+ // Hashchange event handler - delegates to emitUrlChange for consistency
114
+ var hashChangeHandler = function () {
115
+ emitUrlChange();
116
+ };
117
+ // 1. if explicitly enable polling → use polling
118
+ if (enablePolling) {
119
+ // Use polling (covers everything)
120
+ var urlChangeInterval_1 = globalScope.setInterval(function () {
121
+ emitUrlChange();
122
+ }, pollingInterval);
123
+ // Emit initial URL immediately
124
+ emitUrlChange();
125
+ // Return cleanup function to stop polling
126
+ return function () {
127
+ if (urlChangeInterval_1) {
128
+ globalScope.clearInterval(urlChangeInterval_1);
129
+ }
130
+ };
131
+ }
132
+ // 2. if polling not enabled → check history, if exist, use history
133
+ if (globalScope.history) {
134
+ // Use History API + hashchange (covers History API + hash routing)
135
+ // Store original history methods for restoration during cleanup
136
+ var originalPushState_1 = globalScope.history.pushState.bind(globalScope.history);
137
+ var originalReplaceState_1 = globalScope.history.replaceState.bind(globalScope.history);
138
+ /**
139
+ * Sets up history method patching to intercept pushState and replaceState calls
140
+ * This ensures URL changes are detected even when history methods are called programmatically
141
+ */
142
+ var setupHistoryPatching = function () {
143
+ // Patch pushState to emit URL change events
144
+ globalScope.history.pushState = createHistoryMethodPatch(originalPushState_1);
145
+ // Patch replaceState to emit URL change events
146
+ globalScope.history.replaceState = createHistoryMethodPatch(originalReplaceState_1);
147
+ };
148
+ // Apply history method patches
149
+ setupHistoryPatching();
150
+ // Listen to popstate events for browser back/forward navigation
151
+ globalScope.addEventListener('popstate', emitUrlChange);
152
+ // Listen to hashchange events for hash routing
153
+ globalScope.addEventListener('hashchange', hashChangeHandler);
154
+ // Emit initial URL immediately
155
+ emitUrlChange();
156
+ // Return cleanup function to restore original state
157
+ return function () {
158
+ // Restore original history methods
159
+ globalScope.history.pushState = originalPushState_1;
160
+ globalScope.history.replaceState = originalReplaceState_1;
161
+ // Remove popstate event listener
162
+ globalScope.removeEventListener('popstate', emitUrlChange);
163
+ // Remove hashchange event listener
164
+ globalScope.removeEventListener('hashchange', hashChangeHandler);
165
+ };
166
+ }
167
+ // 3. if not, then the framework is probably using hash router → do hash
168
+ // Fallback: just hashchange (for pure hash routing)
169
+ globalScope.addEventListener('hashchange', hashChangeHandler);
170
+ emitUrlChange();
171
+ return function () {
172
+ globalScope.removeEventListener('hashchange', hashChangeHandler);
173
+ };
174
+ },
175
+ options: options,
176
+ };
177
+ }
178
+ /**
179
+ * Default URL tracking plugin instance with default options
180
+ * Can be used directly without custom configuration
181
+ */
182
+ export var urlTrackingPlugin = createUrlTrackingPlugin();
183
+ //# sourceMappingURL=url-tracking-plugin.js.map
@@ -0,0 +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/**\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 * Prevents duplicate events for the same URL on subsequent calls\n * Handles edge cases like undefined/null/empty URLs gracefully\n */\n const emitUrlChange = (): void => {\n const currentUrl = getCurrentUrl();\n\n // Always emit on first call, or if URL actually changed\n if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {\n lastTrackedUrl = currentUrl;\n const event = createUrlChangeEvent();\n cb(event);\n }\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 * Ensures URL changes are detected even when history methods are called programmatically\n * @param originalMethod The original history method to patch\n * @returns Patched function that calls original method then emits URL change event\n */\n const createHistoryMethodPatch = <T extends typeof history.pushState | typeof history.replaceState>(\n originalMethod: T,\n ) => {\n return function (this: History, ...args: Parameters<T>) {\n // Call the original history method first\n const result = originalMethod.apply(this, args);\n // Then emit URL change event\n emitUrlChange();\n return result;\n };\n };\n\n // Hashchange event handler - delegates to emitUrlChange for consistency\n const hashChangeHandler = () => {\n emitUrlChange();\n };\n\n // 1. if explicitly enable polling → use polling\n if (enablePolling) {\n // Use polling (covers everything)\n const urlChangeInterval = globalScope.setInterval(() => {\n emitUrlChange();\n }, pollingInterval);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to stop polling\n return () => {\n if (urlChangeInterval) {\n globalScope.clearInterval(urlChangeInterval);\n }\n };\n }\n\n // 2. if polling not enabled → check history, if exist, use history\n if (globalScope.history) {\n // Use History API + hashchange (covers History API + hash routing)\n // Store original history methods for restoration during cleanup\n const originalPushState = globalScope.history.pushState.bind(globalScope.history);\n const originalReplaceState = globalScope.history.replaceState.bind(globalScope.history);\n\n /**\n * Sets up history method patching to intercept pushState and replaceState calls\n * This ensures URL changes are detected even when history methods are called programmatically\n */\n const setupHistoryPatching = (): void => {\n // Patch pushState to emit URL change events\n globalScope.history.pushState = createHistoryMethodPatch(originalPushState);\n\n // Patch replaceState to emit URL change events\n globalScope.history.replaceState = createHistoryMethodPatch(originalReplaceState);\n };\n\n // Apply history method patches\n setupHistoryPatching();\n\n // Listen to popstate events for browser back/forward navigation\n globalScope.addEventListener('popstate', emitUrlChange);\n // Listen to hashchange events for hash routing\n globalScope.addEventListener('hashchange', hashChangeHandler);\n\n // Emit initial URL immediately\n emitUrlChange();\n\n // Return cleanup function to restore original state\n return () => {\n // Restore original history methods\n globalScope.history.pushState = originalPushState;\n globalScope.history.replaceState = originalReplaceState;\n\n // Remove popstate event listener\n globalScope.removeEventListener('popstate', emitUrlChange);\n // Remove hashchange event listener\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\n }\n\n // 3. if not, then the framework is probably using hash router → do hash\n // Fallback: just hashchange (for pure hash routing)\n globalScope.addEventListener('hashchange', hashChangeHandler);\n emitUrlChange();\n return () => {\n globalScope.removeEventListener('hashchange', hashChangeHandler);\n };\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 +1 @@
1
- {"version":3,"file":"session-replay-factory.d.ts","sourceRoot":"","sources":["../../src/session-replay-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,eAAO,MAAM,YAAY,kBAAmB,aAAa,WAAS,SAOjE,CAAC;;AA0BF,wBAAgC"}
1
+ {"version":3,"file":"session-replay-factory.d.ts","sourceRoot":"","sources":["../../src/session-replay-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE,eAAO,MAAM,YAAY,kBAAmB,aAAa,WAAS,SAOjE,CAAC;;AA+BF,wBAAgC"}
@@ -13,6 +13,7 @@ var createInstance = function () {
13
13
  var sessionReplay = new SessionReplay();
14
14
  return {
15
15
  init: debugWrapper(sessionReplay.init.bind(sessionReplay), 'init', getLogConfig(sessionReplay)),
16
+ evaluateTargetingAndCapture: debugWrapper(sessionReplay.evaluateTargetingAndCapture.bind(sessionReplay), 'evaluateTargetingAndRecord', getLogConfig(sessionReplay)),
16
17
  setSessionId: debugWrapper(sessionReplay.setSessionId.bind(sessionReplay), 'setSessionId', getLogConfig(sessionReplay)),
17
18
  getSessionId: debugWrapper(sessionReplay.getSessionId.bind(sessionReplay), 'getSessionId', getLogConfig(sessionReplay)),
18
19
  getSessionReplayProperties: debugWrapper(sessionReplay.getSessionReplayProperties.bind(sessionReplay), 'getSessionReplayProperties', getLogConfig(sessionReplay)),
@@ -1 +1 @@
1
- {"version":3,"file":"session-replay-factory.js","sourceRoot":"","sources":["../../src/session-replay-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAa,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD,MAAM,CAAC,IAAM,YAAY,GAAG,UAAC,aAA4B,IAAK,OAAA;IACpD,IAAA,MAAM,GAAK,aAAa,OAAlB,CAAmB;IAC3B,IAAA,KAAuC,MAAM,IAAI,gBAAgB,EAAE,EAAjD,MAAM,oBAAA,EAAE,QAAQ,cAAiC,CAAC;IAC1E,OAAO;QACL,MAAM,QAAA;QACN,QAAQ,UAAA;KACT,CAAC;AACJ,CAAC,EAP6D,CAO7D,CAAC;AAEF,IAAM,cAAc,GAAiC;IACnD,IAAM,aAAa,GAAG,IAAI,aAAa,EAAE,CAAC;IAC1C,OAAO;QACL,IAAI,EAAE,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC;QAC/F,YAAY,EAAE,YAAY,CACxB,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,EAC9C,cAAc,EACd,YAAY,CAAC,aAAa,CAAC,CAC5B;QACD,YAAY,EAAE,YAAY,CACxB,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,EAC9C,cAAc,EACd,YAAY,CAAC,aAAa,CAAC,CAC5B;QACD,0BAA0B,EAAE,YAAY,CACtC,aAAa,CAAC,0BAA0B,CAAC,IAAI,CAAC,aAAa,CAAC,EAC5D,4BAA4B,EAC5B,YAAY,CAAC,aAAa,CAAC,CAC5B;QACD,KAAK,EAAE,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC;QAClG,QAAQ,EAAE,YAAY,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC;KAC5G,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,cAAc,EAAE,CAAC","sourcesContent":["import { debugWrapper, LogConfig } from '@amplitude/analytics-core';\nimport { getDefaultConfig } from './config/local-config';\nimport { SessionReplay } from './session-replay';\nimport { AmplitudeSessionReplay } from './typings/session-replay';\n\nexport const getLogConfig = (sessionReplay: SessionReplay) => (): LogConfig => {\n const { config } = sessionReplay;\n const { loggerProvider: logger, logLevel } = config || getDefaultConfig();\n return {\n logger,\n logLevel,\n };\n};\n\nconst createInstance: () => AmplitudeSessionReplay = () => {\n const sessionReplay = new SessionReplay();\n return {\n init: debugWrapper(sessionReplay.init.bind(sessionReplay), 'init', getLogConfig(sessionReplay)),\n setSessionId: debugWrapper(\n sessionReplay.setSessionId.bind(sessionReplay),\n 'setSessionId',\n getLogConfig(sessionReplay),\n ),\n getSessionId: debugWrapper(\n sessionReplay.getSessionId.bind(sessionReplay),\n 'getSessionId',\n getLogConfig(sessionReplay),\n ),\n getSessionReplayProperties: debugWrapper(\n sessionReplay.getSessionReplayProperties.bind(sessionReplay),\n 'getSessionReplayProperties',\n getLogConfig(sessionReplay),\n ),\n flush: debugWrapper(sessionReplay.flush.bind(sessionReplay), 'flush', getLogConfig(sessionReplay)),\n shutdown: debugWrapper(sessionReplay.shutdown.bind(sessionReplay), 'shutdown', getLogConfig(sessionReplay)),\n };\n};\n\nexport default createInstance();\n"]}
1
+ {"version":3,"file":"session-replay-factory.js","sourceRoot":"","sources":["../../src/session-replay-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAa,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD,MAAM,CAAC,IAAM,YAAY,GAAG,UAAC,aAA4B,IAAK,OAAA;IACpD,IAAA,MAAM,GAAK,aAAa,OAAlB,CAAmB;IAC3B,IAAA,KAAuC,MAAM,IAAI,gBAAgB,EAAE,EAAjD,MAAM,oBAAA,EAAE,QAAQ,cAAiC,CAAC;IAC1E,OAAO;QACL,MAAM,QAAA;QACN,QAAQ,UAAA;KACT,CAAC;AACJ,CAAC,EAP6D,CAO7D,CAAC;AAEF,IAAM,cAAc,GAAiC;IACnD,IAAM,aAAa,GAAG,IAAI,aAAa,EAAE,CAAC;IAC1C,OAAO;QACL,IAAI,EAAE,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC;QAC/F,2BAA2B,EAAE,YAAY,CACvC,aAAa,CAAC,2BAA2B,CAAC,IAAI,CAAC,aAAa,CAAC,EAC7D,4BAA4B,EAC5B,YAAY,CAAC,aAAa,CAAC,CAC5B;QACD,YAAY,EAAE,YAAY,CACxB,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,EAC9C,cAAc,EACd,YAAY,CAAC,aAAa,CAAC,CAC5B;QACD,YAAY,EAAE,YAAY,CACxB,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,EAC9C,cAAc,EACd,YAAY,CAAC,aAAa,CAAC,CAC5B;QACD,0BAA0B,EAAE,YAAY,CACtC,aAAa,CAAC,0BAA0B,CAAC,IAAI,CAAC,aAAa,CAAC,EAC5D,4BAA4B,EAC5B,YAAY,CAAC,aAAa,CAAC,CAC5B;QACD,KAAK,EAAE,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC;QAClG,QAAQ,EAAE,YAAY,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,YAAY,CAAC,aAAa,CAAC,CAAC;KAC5G,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,cAAc,EAAE,CAAC","sourcesContent":["import { debugWrapper, LogConfig } from '@amplitude/analytics-core';\nimport { getDefaultConfig } from './config/local-config';\nimport { SessionReplay } from './session-replay';\nimport { AmplitudeSessionReplay } from './typings/session-replay';\n\nexport const getLogConfig = (sessionReplay: SessionReplay) => (): LogConfig => {\n const { config } = sessionReplay;\n const { loggerProvider: logger, logLevel } = config || getDefaultConfig();\n return {\n logger,\n logLevel,\n };\n};\n\nconst createInstance: () => AmplitudeSessionReplay = () => {\n const sessionReplay = new SessionReplay();\n return {\n init: debugWrapper(sessionReplay.init.bind(sessionReplay), 'init', getLogConfig(sessionReplay)),\n evaluateTargetingAndCapture: debugWrapper(\n sessionReplay.evaluateTargetingAndCapture.bind(sessionReplay),\n 'evaluateTargetingAndRecord',\n getLogConfig(sessionReplay),\n ),\n setSessionId: debugWrapper(\n sessionReplay.setSessionId.bind(sessionReplay),\n 'setSessionId',\n getLogConfig(sessionReplay),\n ),\n getSessionId: debugWrapper(\n sessionReplay.getSessionId.bind(sessionReplay),\n 'getSessionId',\n getLogConfig(sessionReplay),\n ),\n getSessionReplayProperties: debugWrapper(\n sessionReplay.getSessionReplayProperties.bind(sessionReplay),\n 'getSessionReplayProperties',\n getLogConfig(sessionReplay),\n ),\n flush: debugWrapper(sessionReplay.flush.bind(sessionReplay), 'flush', getLogConfig(sessionReplay)),\n shutdown: debugWrapper(sessionReplay.shutdown.bind(sessionReplay), 'shutdown', getLogConfig(sessionReplay)),\n };\n};\n\nexport default createInstance();\n"]}
@@ -1,8 +1,9 @@
1
1
  import { ILogger } from '@amplitude/analytics-core';
2
+ import { TargetingParameters } from '@amplitude/targeting';
2
3
  import { LoggingConfig, SessionReplayJoinedConfig, SessionReplayJoinedConfigGenerator } from './config/types';
3
4
  import { CustomRRwebEvent } from './constants';
4
- import { AmplitudeSessionReplay, SessionReplayEventsManager as AmplitudeSessionReplayEventsManager, SessionIdentifiers as ISessionIdentifiers, SessionReplayOptions } from './typings/session-replay';
5
5
  import { EventCompressor } from './events/event-compressor';
6
+ import { AmplitudeSessionReplay, SessionReplayEventsManager as AmplitudeSessionReplayEventsManager, SessionIdentifiers as ISessionIdentifiers, SessionReplayOptions } from './typings/session-replay';
6
7
  import type { RecordFunction } from './utils/rrweb';
7
8
  type PageLeaveFn = (e: PageTransitionEvent | Event) => void;
8
9
  export declare class SessionReplay implements AmplitudeSessionReplay {
@@ -15,6 +16,9 @@ export declare class SessionReplay implements AmplitudeSessionReplay {
15
16
  recordCancelCallback: ReturnType<RecordFunction> | null;
16
17
  eventCount: number;
17
18
  eventCompressor: EventCompressor | undefined;
19
+ sessionTargetingMatch: boolean;
20
+ private lastTargetingParams?;
21
+ private lastShouldRecordDecision?;
18
22
  pageLeaveFns: PageLeaveFn[];
19
23
  private scrollHook?;
20
24
  private networkObservers?;
@@ -25,7 +29,11 @@ export declare class SessionReplay implements AmplitudeSessionReplay {
25
29
  private teardownEventListeners;
26
30
  protected _init(apiKey: string, options: SessionReplayOptions): Promise<void>;
27
31
  setSessionId(sessionId: string | number, deviceId?: string): import("@amplitude/analytics-core").AmplitudeReturn<void>;
28
- asyncSetSessionId(sessionId: string | number, deviceId?: string): Promise<void>;
32
+ asyncSetSessionId(sessionId: string | number, deviceId?: string, options?: {
33
+ userProperties?: {
34
+ [key: string]: any;
35
+ };
36
+ }): Promise<void>;
29
37
  getSessionReplayProperties(): {
30
38
  [key: string]: string | null;
31
39
  };
@@ -37,13 +45,14 @@ export declare class SessionReplay implements AmplitudeSessionReplay {
37
45
  * prevent duplicate listener actions from firing.
38
46
  */
39
47
  private pageLeaveListener;
48
+ evaluateTargetingAndCapture: (targetingParams: Pick<TargetingParameters, 'event' | 'userProperties'>, isInit?: boolean) => Promise<void>;
40
49
  sendEvents(sessionId?: string | number): void;
41
50
  initialize(shouldSendStoredEvents?: boolean): Promise<void>;
42
51
  shouldOptOut(): boolean | undefined;
43
52
  getShouldRecord(): boolean;
44
53
  getBlockSelectors(): string | string[] | undefined;
45
54
  getMaskTextSelectors(): string | undefined;
46
- getRecordingPlugins(loggingConfig: LoggingConfig | undefined): Promise<import("@amplitude/rrweb-types").RecordPlugin<unknown>[] | undefined>;
55
+ getRecordingPlugins(loggingConfig: LoggingConfig | undefined): Promise<(import("@amplitude/rrweb-types").RecordPlugin<import("./plugins/url-tracking-plugin").URLTrackingPluginOptions> | import("@amplitude/rrweb-types").RecordPlugin<unknown>)[] | undefined>;
47
56
  private getRecordFunction;
48
57
  recordEvents(shouldLogMetadata?: boolean): Promise<void>;
49
58
  addCustomRRWebEvent: (eventName: CustomRRwebEvent, eventData?: {
@@ -1 +1 @@
1
- {"version":3,"file":"session-replay.d.ts","sourceRoot":"","sources":["../../src/session-replay.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,OAAO,EAER,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,kCAAkC,EAInC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,gBAAgB,EAMjB,MAAM,aAAa,CAAC;AAOrB,OAAO,EACL,sBAAsB,EACtB,0BAA0B,IAAI,mCAAmC,EAIjE,kBAAkB,IAAI,mBAAmB,EACzC,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAK5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,WAAW,GAAG,CAAC,CAAC,EAAE,mBAAmB,GAAG,KAAK,KAAK,IAAI,CAAC;AAE5D,qBAAa,aAAc,YAAW,sBAAsB;IAC1D,IAAI,SAAuC;IAC3C,MAAM,EAAE,yBAAyB,GAAG,SAAS,CAAC;IAC9C,qBAAqB,EAAE,kCAAkC,GAAG,SAAS,CAAC;IACtE,WAAW,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC7C,aAAa,CAAC,EAAE,mCAAmC,CAAC,QAAQ,GAAG,aAAa,EAAE,MAAM,CAAC,CAAC;IACtF,cAAc,EAAE,OAAO,CAAC;IACxB,oBAAoB,EAAE,UAAU,CAAC,cAAc,CAAC,GAAG,IAAI,CAAQ;IAC/D,UAAU,SAAK;IACf,eAAe,EAAE,eAAe,GAAG,SAAS,CAAC;IAG7C,YAAY,EAAE,WAAW,EAAE,CAAM;IACjC,OAAO,CAAC,UAAU,CAAC,CAAiB;IACpC,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAoC;IAGpD,OAAO,CAAC,cAAc,CAA+B;;IAMrD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB;IAIlD,OAAO,CAAC,sBAAsB,CAmB5B;cAEc,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB;IAwFnE,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;IAIpD,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;IAqBrE,0BAA0B;;;IAsC1B,YAAY,aAEV;IAEF,aAAa,aAIX;IAEF;;;;OAIG;IACH,OAAO,CAAC,iBAAiB,CAIvB;IAEF,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAShC,UAAU,CAAC,sBAAsB,UAAQ;IAgB/C,YAAY;IAUZ,eAAe;IAwBf,iBAAiB,IAAI,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAWlD,oBAAoB,IAAI,MAAM,GAAG,SAAS;IAapC,mBAAmB,CAAC,aAAa,EAAE,aAAa,GAAG,SAAS;YA2BpD,iBAAiB;IAezB,YAAY,CAAC,iBAAiB,UAAO;IA4G3C,mBAAmB,cACN,gBAAgB;;kDAmC3B;IAEF,mBAAmB,aAUjB;IAEF,WAAW;IAIX,YAAY;IAIN,KAAK,CAAC,QAAQ,UAAQ;IAI5B,QAAQ;IAMR,OAAO,CAAC,UAAU;IAYlB,OAAO,CAAC,WAAW;YAyBL,0BAA0B;CAUzC"}
1
+ {"version":3,"file":"session-replay.d.ts","sourceRoot":"","sources":["../../src/session-replay.ts"],"names":[],"mappings":"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"}
@@ -1,24 +1,27 @@
1
1
  import { __assign, __awaiter, __generator, __read, __spreadArray } from "tslib";
2
- import { Logger, returnWrapper, getAnalyticsConnector, getGlobalScope, } from '@amplitude/analytics-core';
2
+ import { getAnalyticsConnector, getGlobalScope, Logger, returnWrapper, SpecialEventType, } from '@amplitude/analytics-core';
3
3
  // Import only specific types to avoid pulling in the entire rrweb-types package
4
4
  import { EventType as RRWebEventType } from '@amplitude/rrweb-types';
5
5
  import { createSessionReplayJoinedConfigGenerator } from './config/joined-config';
6
6
  import { BLOCK_CLASS, CustomRRwebEvent, DEFAULT_SESSION_REPLAY_PROPERTY, INTERACTION_MAX_INTERVAL, INTERACTION_MIN_INTERVAL, MASK_TEXT_CLASS, SESSION_REPLAY_DEBUG_PROPERTY, } from './constants';
7
+ import { EventCompressor } from './events/event-compressor';
7
8
  import { createEventsManager } from './events/events-manager';
8
9
  import { MultiEventManager } from './events/multi-manager';
9
10
  import { generateHashCode, getDebugConfig, getPageUrl, getStorageSize, isSessionInSample, maskFn } from './helpers';
10
11
  import { clickBatcher, clickHook, clickNonBatcher } from './hooks/click';
11
12
  import { ScrollWatcher } from './hooks/scroll';
12
13
  import { SessionIdentifiers } from './identifiers';
13
- import { VERSION } from './version';
14
- import { EventCompressor } from './events/event-compressor';
15
14
  import { SafeLoggerProvider } from './logger';
15
+ import { evaluateTargetingAndStore } from './targeting/targeting-manager';
16
+ import { VERSION } from './version';
17
+ import { createUrlTrackingPlugin } from './plugins/url-tracking-plugin';
16
18
  var SessionReplay = /** @class */ (function () {
17
19
  function SessionReplay() {
18
20
  var _this = this;
19
21
  this.name = '@amplitude/session-replay-browser';
20
22
  this.recordCancelCallback = null;
21
23
  this.eventCount = 0;
24
+ this.sessionTargetingMatch = false;
22
25
  // Visible for testing only
23
26
  this.pageLeaveFns = [];
24
27
  // Cache the dynamically imported record function
@@ -62,6 +65,60 @@ var SessionReplay = /** @class */ (function () {
62
65
  fn(e);
63
66
  });
64
67
  };
68
+ this.evaluateTargetingAndCapture = function (targetingParams, isInit) {
69
+ if (isInit === void 0) { isInit = false; }
70
+ return __awaiter(_this, void 0, void 0, function () {
71
+ var eventForTargeting, _a;
72
+ return __generator(this, function (_b) {
73
+ switch (_b.label) {
74
+ case 0:
75
+ if (!this.identifiers || !this.identifiers.sessionId || !this.config) {
76
+ if (this.identifiers && !this.identifiers.sessionId) {
77
+ this.loggerProvider.log('Session ID has not been set yet, cannot evaluate targeting for Session Replay.');
78
+ }
79
+ else {
80
+ this.loggerProvider.warn('Session replay init has not been called, cannot evaluate targeting.');
81
+ }
82
+ return [2 /*return*/];
83
+ }
84
+ // Store targeting parameters for use in getShouldRecord
85
+ this.lastTargetingParams = targetingParams;
86
+ if (!(this.config.targetingConfig && !this.sessionTargetingMatch)) return [3 /*break*/, 2];
87
+ eventForTargeting = targetingParams.event;
88
+ if (eventForTargeting &&
89
+ Object.values(SpecialEventType).includes(eventForTargeting.event_type)) {
90
+ eventForTargeting = undefined;
91
+ }
92
+ // We're setting this on this class because fetching the value from idb
93
+ // is async, we need to access this value synchronously (for record
94
+ // and for getSessionReplayProperties - both synchronous fns)
95
+ _a = this;
96
+ return [4 /*yield*/, evaluateTargetingAndStore({
97
+ sessionId: this.identifiers.sessionId,
98
+ targetingConfig: this.config.targetingConfig,
99
+ loggerProvider: this.loggerProvider,
100
+ apiKey: this.config.apiKey,
101
+ targetingParams: { userProperties: targetingParams.userProperties, event: eventForTargeting },
102
+ })];
103
+ case 1:
104
+ // We're setting this on this class because fetching the value from idb
105
+ // is async, we need to access this value synchronously (for record
106
+ // and for getSessionReplayProperties - both synchronous fns)
107
+ _a.sessionTargetingMatch = _b.sent();
108
+ _b.label = 2;
109
+ case 2:
110
+ if (!isInit) return [3 /*break*/, 3];
111
+ void this.initialize(true);
112
+ return [3 /*break*/, 5];
113
+ case 3: return [4 /*yield*/, this.recordEvents()];
114
+ case 4:
115
+ _b.sent();
116
+ _b.label = 5;
117
+ case 5: return [2 /*return*/];
118
+ }
119
+ });
120
+ });
121
+ };
65
122
  this.addCustomRRWebEvent = function (eventName, eventData, addStorageInfo) {
66
123
  if (eventData === void 0) { eventData = {}; }
67
124
  if (addStorageInfo === void 0) { addStorageInfo = true; }
@@ -209,7 +266,9 @@ var SessionReplay = /** @class */ (function () {
209
266
  _j.sent();
210
267
  this.loggerProvider.log('Installing @amplitude/session-replay-browser.');
211
268
  this.teardownEventListeners(false);
212
- void this.initialize(true);
269
+ return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options.userProperties }, true)];
270
+ case 12:
271
+ _j.sent();
213
272
  return [2 /*return*/];
214
273
  }
215
274
  });
@@ -218,12 +277,14 @@ var SessionReplay = /** @class */ (function () {
218
277
  SessionReplay.prototype.setSessionId = function (sessionId, deviceId) {
219
278
  return returnWrapper(this.asyncSetSessionId(sessionId, deviceId));
220
279
  };
221
- SessionReplay.prototype.asyncSetSessionId = function (sessionId, deviceId) {
280
+ SessionReplay.prototype.asyncSetSessionId = function (sessionId, deviceId, options) {
222
281
  return __awaiter(this, void 0, void 0, function () {
223
282
  var previousSessionId, deviceIdForReplayId, joinedConfig;
224
283
  return __generator(this, function (_a) {
225
284
  switch (_a.label) {
226
285
  case 0:
286
+ this.sessionTargetingMatch = false;
287
+ this.lastShouldRecordDecision = undefined; // Reset targeting decision for new session
227
288
  previousSessionId = this.identifiers && this.identifiers.sessionId;
228
289
  if (previousSessionId) {
229
290
  this.sendEvents(previousSessionId);
@@ -239,8 +300,9 @@ var SessionReplay = /** @class */ (function () {
239
300
  joinedConfig = (_a.sent()).joinedConfig;
240
301
  this.config = joinedConfig;
241
302
  _a.label = 2;
242
- case 2:
243
- void this.recordEvents();
303
+ case 2: return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options === null || options === void 0 ? void 0 : options.userProperties })];
304
+ case 3:
305
+ _a.sent();
244
306
  return [2 /*return*/];
245
307
  }
246
308
  });
@@ -327,11 +389,49 @@ var SessionReplay = /** @class */ (function () {
327
389
  this.loggerProvider.log("Opting session ".concat(this.identifiers.sessionId, " out of recording due to optOut config."));
328
390
  return false;
329
391
  }
330
- var isInSample = isSessionInSample(this.identifiers.sessionId, this.config.sampleRate);
331
- if (!isInSample) {
332
- this.loggerProvider.log("Opting session ".concat(this.identifiers.sessionId, " out of recording due to sample rate."));
392
+ var shouldRecord = false;
393
+ var message = '';
394
+ var matched = false;
395
+ // If targetingConfig exists, we'll use the sessionTargetingMatch to determine whether to record
396
+ // Otherwise, we'll evaluate the session against the overall sample rate
397
+ if (this.config.targetingConfig) {
398
+ if (!this.sessionTargetingMatch) {
399
+ message = "Not capturing replays for session ".concat(this.identifiers.sessionId, " due to not matching targeting conditions.");
400
+ this.loggerProvider.log(message);
401
+ shouldRecord = false;
402
+ matched = false;
403
+ }
404
+ else {
405
+ message = "Capturing replays for session ".concat(this.identifiers.sessionId, " due to matching targeting conditions.");
406
+ this.loggerProvider.log(message);
407
+ shouldRecord = true;
408
+ matched = true;
409
+ }
410
+ }
411
+ else {
412
+ var isInSample = isSessionInSample(this.identifiers.sessionId, this.config.sampleRate);
413
+ if (!isInSample) {
414
+ message = "Opting session ".concat(this.identifiers.sessionId, " out of recording due to sample rate.");
415
+ this.loggerProvider.log(message);
416
+ shouldRecord = false;
417
+ matched = false;
418
+ }
419
+ else {
420
+ shouldRecord = true;
421
+ matched = true;
422
+ }
333
423
  }
334
- return isInSample;
424
+ // Only send custom rrweb event for targeting decision when the decision changes
425
+ if (this.lastShouldRecordDecision !== shouldRecord && this.config.targetingConfig) {
426
+ void this.addCustomRRWebEvent(CustomRRwebEvent.TARGETING_DECISION, {
427
+ message: message,
428
+ sessionId: this.identifiers.sessionId,
429
+ matched: matched,
430
+ targetingParams: this.lastTargetingParams,
431
+ });
432
+ this.lastShouldRecordDecision = shouldRecord;
433
+ }
434
+ return shouldRecord;
335
435
  };
336
436
  SessionReplay.prototype.getBlockSelectors = function () {
337
437
  var _a, _b, _c;
@@ -356,24 +456,37 @@ var SessionReplay = /** @class */ (function () {
356
456
  return maskSelector;
357
457
  };
358
458
  SessionReplay.prototype.getRecordingPlugins = function (loggingConfig) {
359
- var _a;
459
+ var _a, _b, _c, _d, _e, _f;
360
460
  return __awaiter(this, void 0, void 0, function () {
361
- var plugins, getRecordConsolePlugin, error_3;
362
- return __generator(this, function (_b) {
363
- switch (_b.label) {
461
+ var plugins, urlTrackingPlugin, getRecordConsolePlugin, error_3;
462
+ return __generator(this, function (_g) {
463
+ switch (_g.label) {
364
464
  case 0:
365
465
  plugins = [];
366
- if (!((_a = loggingConfig === null || loggingConfig === void 0 ? void 0 : loggingConfig.console) === null || _a === void 0 ? void 0 : _a.enabled)) return [3 /*break*/, 4];
367
- _b.label = 1;
466
+ // Add URL tracking plugin
467
+ try {
468
+ urlTrackingPlugin = createUrlTrackingPlugin({
469
+ ugcFilterRules: ((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.interactionConfig) === null || _b === void 0 ? void 0 : _b.ugcFilterRules) || [],
470
+ enablePolling: ((_c = this.config) === null || _c === void 0 ? void 0 : _c.enableUrlChangePolling) || false,
471
+ pollingInterval: (_d = this.config) === null || _d === void 0 ? void 0 : _d.urlChangePollingInterval,
472
+ captureDocumentTitle: (_e = this.config) === null || _e === void 0 ? void 0 : _e.captureDocumentTitle,
473
+ });
474
+ plugins.push(urlTrackingPlugin);
475
+ }
476
+ catch (error) {
477
+ this.loggerProvider.warn('Failed to create URL tracking plugin:', error);
478
+ }
479
+ if (!((_f = loggingConfig === null || loggingConfig === void 0 ? void 0 : loggingConfig.console) === null || _f === void 0 ? void 0 : _f.enabled)) return [3 /*break*/, 4];
480
+ _g.label = 1;
368
481
  case 1:
369
- _b.trys.push([1, 3, , 4]);
482
+ _g.trys.push([1, 3, , 4]);
370
483
  return [4 /*yield*/, import('@amplitude/rrweb-plugin-console-record')];
371
484
  case 2:
372
- getRecordConsolePlugin = (_b.sent()).getRecordConsolePlugin;
485
+ getRecordConsolePlugin = (_g.sent()).getRecordConsolePlugin;
373
486
  plugins.push(getRecordConsolePlugin({ level: loggingConfig.console.levels }));
374
487
  return [3 /*break*/, 4];
375
488
  case 3:
376
- error_3 = _b.sent();
489
+ error_3 = _g.sent();
377
490
  this.loggerProvider.warn('Failed to load console plugin:', error_3);
378
491
  return [3 /*break*/, 4];
379
492
  case 4: return [2 /*return*/, plugins.length > 0 ? plugins : undefined];