@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,187 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.urlTrackingPlugin = exports.createUrlTrackingPlugin = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var helpers_1 = require("../helpers");
6
+ var constants_1 = require("../constants");
7
+ /**
8
+ * Creates a URL tracking plugin for rrweb record function
9
+ *
10
+ * This plugin monitors URL changes in the browser and emits events when the URL changes.
11
+ * It supports three tracking modes:
12
+ * 1. Polling (if explicitly enabled) - periodically checks for URL changes
13
+ * 2. History API + Hash routing (default) - patches pushState/replaceState, listens to popstate and hashchange
14
+ * 3. Hash routing only (fallback) - listens to hashchange events when History API is unavailable
15
+ *
16
+ * The plugin handles edge cases gracefully:
17
+ * - Missing or null location objects
18
+ * - Undefined, null, or empty location.href values
19
+ * - Temporal dead zone issues with variable declarations
20
+ * - Consistent URL normalization across all code paths
21
+ *
22
+ * @param options Configuration options for URL tracking
23
+ * @returns RecordPlugin instance that can be used with rrweb
24
+ */
25
+ function createUrlTrackingPlugin(options) {
26
+ if (options === void 0) { options = {}; }
27
+ return {
28
+ name: 'amplitude/url-tracking@1',
29
+ observer: function (cb, globalScope, pluginOptions) {
30
+ var _a, _b, _c;
31
+ // Merge options with plugin-level options taking precedence over constructor options
32
+ var config = tslib_1.__assign(tslib_1.__assign({}, options), pluginOptions);
33
+ var ugcFilterRules = config.ugcFilterRules || [];
34
+ var enablePolling = (_a = config.enablePolling) !== null && _a !== void 0 ? _a : false;
35
+ var pollingInterval = (_b = config.pollingInterval) !== null && _b !== void 0 ? _b : constants_1.DEFAULT_URL_CHANGE_POLLING_INTERVAL;
36
+ var captureDocumentTitle = (_c = config.captureDocumentTitle) !== null && _c !== void 0 ? _c : false;
37
+ // Early return if no global scope is available
38
+ if (!globalScope) {
39
+ return function () {
40
+ // No cleanup needed if no global scope available
41
+ };
42
+ }
43
+ // Track the last URL to prevent duplicate events
44
+ // Initialize to undefined to ensure first call always emits an event
45
+ var lastTrackedUrl = undefined;
46
+ // Helper functions
47
+ /**
48
+ * Gets the current URL with proper normalization
49
+ * Handles edge cases where location.href might be undefined, null, or empty
50
+ * Ensures consistent behavior across all code paths
51
+ * @returns Normalized URL string (empty string if location unavailable)
52
+ */
53
+ var getCurrentUrl = function () {
54
+ if (!globalScope.location)
55
+ return '';
56
+ return globalScope.location.href || '';
57
+ };
58
+ /**
59
+ * Creates a URL change event with current page information
60
+ * Applies UGC filtering if rules are configured
61
+ * Uses getCurrentUrl() for consistent URL normalization
62
+ * @returns URLChangeEvent with current page state
63
+ */
64
+ var createUrlChangeEvent = function () {
65
+ var innerHeight = globalScope.innerHeight, innerWidth = globalScope.innerWidth, document = globalScope.document;
66
+ var currentUrl = getCurrentUrl();
67
+ var currentTitle = '';
68
+ if (captureDocumentTitle) {
69
+ currentTitle = (document === null || document === void 0 ? void 0 : document.title) || '';
70
+ }
71
+ // Apply UGC filtering if rules are provided, otherwise use original URL
72
+ var filteredUrl = ugcFilterRules.length > 0 ? (0, helpers_1.getPageUrl)(currentUrl, ugcFilterRules) : currentUrl;
73
+ return {
74
+ href: filteredUrl,
75
+ title: currentTitle,
76
+ viewportHeight: innerHeight,
77
+ viewportWidth: innerWidth,
78
+ type: 'url-change-event',
79
+ };
80
+ };
81
+ /**
82
+ * Emits a URL change event if the URL has actually changed
83
+ * Always emits on first call (when lastTrackedUrl is undefined)
84
+ * Prevents duplicate events for the same URL on subsequent calls
85
+ * Handles edge cases like undefined/null/empty URLs gracefully
86
+ */
87
+ var emitUrlChange = function () {
88
+ var currentUrl = getCurrentUrl();
89
+ // Always emit on first call, or if URL actually changed
90
+ if (lastTrackedUrl === undefined || currentUrl !== lastTrackedUrl) {
91
+ lastTrackedUrl = currentUrl;
92
+ var event_1 = createUrlChangeEvent();
93
+ cb(event_1);
94
+ }
95
+ };
96
+ /**
97
+ * Creates a patched version of history methods (pushState/replaceState)
98
+ * that calls the original method and then emits a URL change event
99
+ * Ensures URL changes are detected even when history methods are called programmatically
100
+ * @param originalMethod The original history method to patch
101
+ * @returns Patched function that calls original method then emits URL change event
102
+ */
103
+ var createHistoryMethodPatch = function (originalMethod) {
104
+ return function () {
105
+ var args = [];
106
+ for (var _i = 0; _i < arguments.length; _i++) {
107
+ args[_i] = arguments[_i];
108
+ }
109
+ // Call the original history method first
110
+ var result = originalMethod.apply(this, args);
111
+ // Then emit URL change event
112
+ emitUrlChange();
113
+ return result;
114
+ };
115
+ };
116
+ // Hashchange event handler - delegates to emitUrlChange for consistency
117
+ var hashChangeHandler = function () {
118
+ emitUrlChange();
119
+ };
120
+ // 1. if explicitly enable polling → use polling
121
+ if (enablePolling) {
122
+ // Use polling (covers everything)
123
+ var urlChangeInterval_1 = globalScope.setInterval(function () {
124
+ emitUrlChange();
125
+ }, pollingInterval);
126
+ // Emit initial URL immediately
127
+ emitUrlChange();
128
+ // Return cleanup function to stop polling
129
+ return function () {
130
+ if (urlChangeInterval_1) {
131
+ globalScope.clearInterval(urlChangeInterval_1);
132
+ }
133
+ };
134
+ }
135
+ // 2. if polling not enabled → check history, if exist, use history
136
+ if (globalScope.history) {
137
+ // Use History API + hashchange (covers History API + hash routing)
138
+ // Store original history methods for restoration during cleanup
139
+ var originalPushState_1 = globalScope.history.pushState.bind(globalScope.history);
140
+ var originalReplaceState_1 = globalScope.history.replaceState.bind(globalScope.history);
141
+ /**
142
+ * Sets up history method patching to intercept pushState and replaceState calls
143
+ * This ensures URL changes are detected even when history methods are called programmatically
144
+ */
145
+ var setupHistoryPatching = function () {
146
+ // Patch pushState to emit URL change events
147
+ globalScope.history.pushState = createHistoryMethodPatch(originalPushState_1);
148
+ // Patch replaceState to emit URL change events
149
+ globalScope.history.replaceState = createHistoryMethodPatch(originalReplaceState_1);
150
+ };
151
+ // Apply history method patches
152
+ setupHistoryPatching();
153
+ // Listen to popstate events for browser back/forward navigation
154
+ globalScope.addEventListener('popstate', emitUrlChange);
155
+ // Listen to hashchange events for hash routing
156
+ globalScope.addEventListener('hashchange', hashChangeHandler);
157
+ // Emit initial URL immediately
158
+ emitUrlChange();
159
+ // Return cleanup function to restore original state
160
+ return function () {
161
+ // Restore original history methods
162
+ globalScope.history.pushState = originalPushState_1;
163
+ globalScope.history.replaceState = originalReplaceState_1;
164
+ // Remove popstate event listener
165
+ globalScope.removeEventListener('popstate', emitUrlChange);
166
+ // Remove hashchange event listener
167
+ globalScope.removeEventListener('hashchange', hashChangeHandler);
168
+ };
169
+ }
170
+ // 3. if not, then the framework is probably using hash router → do hash
171
+ // Fallback: just hashchange (for pure hash routing)
172
+ globalScope.addEventListener('hashchange', hashChangeHandler);
173
+ emitUrlChange();
174
+ return function () {
175
+ globalScope.removeEventListener('hashchange', hashChangeHandler);
176
+ };
177
+ },
178
+ options: options,
179
+ };
180
+ }
181
+ exports.createUrlTrackingPlugin = createUrlTrackingPlugin;
182
+ /**
183
+ * Default URL tracking plugin instance with default options
184
+ * Can be used directly without custom configuration
185
+ */
186
+ exports.urlTrackingPlugin = createUrlTrackingPlugin();
187
+ //# 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":";;;;AAAA,sCAAwC;AAExC,0CAAmE;AAkCnE;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,uBAAuB,CACrC,OAAsC;IAAtC,wBAAA,EAAA,YAAsC;IAEtC,OAAO;QACL,IAAI,EAAE,0BAA0B;QAChC,QAAQ,YAAC,EAAE,EAAE,WAAW,EAAE,aAAwC;;YAChE,qFAAqF;YACrF,IAAM,MAAM,yCAAQ,OAAO,GAAK,aAAa,CAAE,CAAC;YAChD,IAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;YACnD,IAAM,aAAa,GAAG,MAAA,MAAM,CAAC,aAAa,mCAAI,KAAK,CAAC;YACpD,IAAM,eAAe,GAAG,MAAA,MAAM,CAAC,eAAe,mCAAI,+CAAmC,CAAC;YACtF,IAAM,oBAAoB,GAAG,MAAA,MAAM,CAAC,oBAAoB,mCAAI,KAAK,CAAC;YAElE,+CAA+C;YAC/C,IAAI,CAAC,WAAW,EAAE;gBAChB,OAAO;oBACL,iDAAiD;gBACnD,CAAC,CAAC;aACH;YAED,iDAAiD;YACjD,qEAAqE;YACrE,IAAI,cAAc,GAAuB,SAAS,CAAC;YAEnD,mBAAmB;YACnB;;;;;eAKG;YACH,IAAM,aAAa,GAAG;gBACpB,IAAI,CAAC,WAAW,CAAC,QAAQ;oBAAE,OAAO,EAAE,CAAC;gBACrC,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;YACzC,CAAC,CAAC;YAEF;;;;;eAKG;YACH,IAAM,oBAAoB,GAAG;gBACnB,IAAA,WAAW,GAA2B,WAAW,YAAtC,EAAE,UAAU,GAAe,WAAW,WAA1B,EAAE,QAAQ,GAAK,WAAW,SAAhB,CAAiB;gBAC1D,IAAM,UAAU,GAAG,aAAa,EAAE,CAAC;gBACnC,IAAI,YAAY,GAAG,EAAE,CAAC;gBACtB,IAAI,oBAAoB,EAAE;oBACxB,YAAY,GAAG,CAAA,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,KAAK,KAAI,EAAE,CAAC;iBACtC;gBAED,wEAAwE;gBACxE,IAAM,WAAW,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAA,oBAAU,EAAC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;gBAEpG,OAAO;oBACL,IAAI,EAAE,WAAW;oBACjB,KAAK,EAAE,YAAY;oBACnB,cAAc,EAAE,WAAW;oBAC3B,aAAa,EAAE,UAAU;oBACzB,IAAI,EAAE,kBAAkB;iBACzB,CAAC;YACJ,CAAC,CAAC;YAEF;;;;;eAKG;YACH,IAAM,aAAa,GAAG;gBACpB,IAAM,UAAU,GAAG,aAAa,EAAE,CAAC;gBAEnC,wDAAwD;gBACxD,IAAI,cAAc,KAAK,SAAS,IAAI,UAAU,KAAK,cAAc,EAAE;oBACjE,cAAc,GAAG,UAAU,CAAC;oBAC5B,IAAM,OAAK,GAAG,oBAAoB,EAAE,CAAC;oBACrC,EAAE,CAAC,OAAK,CAAC,CAAC;iBACX;YACH,CAAC,CAAC;YAEF;;;;;;eAMG;YACH,IAAM,wBAAwB,GAAG,UAC/B,cAAiB;gBAEjB,OAAO;oBAAyB,cAAsB;yBAAtB,UAAsB,EAAtB,qBAAsB,EAAtB,IAAsB;wBAAtB,yBAAsB;;oBACpD,yCAAyC;oBACzC,IAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBAChD,6BAA6B;oBAC7B,aAAa,EAAE,CAAC;oBAChB,OAAO,MAAM,CAAC;gBAChB,CAAC,CAAC;YACJ,CAAC,CAAC;YAEF,wEAAwE;YACxE,IAAM,iBAAiB,GAAG;gBACxB,aAAa,EAAE,CAAC;YAClB,CAAC,CAAC;YAEF,gDAAgD;YAChD,IAAI,aAAa,EAAE;gBACjB,kCAAkC;gBAClC,IAAM,mBAAiB,GAAG,WAAW,CAAC,WAAW,CAAC;oBAChD,aAAa,EAAE,CAAC;gBAClB,CAAC,EAAE,eAAe,CAAC,CAAC;gBAEpB,+BAA+B;gBAC/B,aAAa,EAAE,CAAC;gBAEhB,0CAA0C;gBAC1C,OAAO;oBACL,IAAI,mBAAiB,EAAE;wBACrB,WAAW,CAAC,aAAa,CAAC,mBAAiB,CAAC,CAAC;qBAC9C;gBACH,CAAC,CAAC;aACH;YAED,mEAAmE;YACnE,IAAI,WAAW,CAAC,OAAO,EAAE;gBACvB,mEAAmE;gBACnE,gEAAgE;gBAChE,IAAM,mBAAiB,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;gBAClF,IAAM,sBAAoB,GAAG,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;gBAExF;;;mBAGG;gBACH,IAAM,oBAAoB,GAAG;oBAC3B,4CAA4C;oBAC5C,WAAW,CAAC,OAAO,CAAC,SAAS,GAAG,wBAAwB,CAAC,mBAAiB,CAAC,CAAC;oBAE5E,+CAA+C;oBAC/C,WAAW,CAAC,OAAO,CAAC,YAAY,GAAG,wBAAwB,CAAC,sBAAoB,CAAC,CAAC;gBACpF,CAAC,CAAC;gBAEF,+BAA+B;gBAC/B,oBAAoB,EAAE,CAAC;gBAEvB,gEAAgE;gBAChE,WAAW,CAAC,gBAAgB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;gBACxD,+CAA+C;gBAC/C,WAAW,CAAC,gBAAgB,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;gBAE9D,+BAA+B;gBAC/B,aAAa,EAAE,CAAC;gBAEhB,oDAAoD;gBACpD,OAAO;oBACL,mCAAmC;oBACnC,WAAW,CAAC,OAAO,CAAC,SAAS,GAAG,mBAAiB,CAAC;oBAClD,WAAW,CAAC,OAAO,CAAC,YAAY,GAAG,sBAAoB,CAAC;oBAExD,iCAAiC;oBACjC,WAAW,CAAC,mBAAmB,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;oBAC3D,mCAAmC;oBACnC,WAAW,CAAC,mBAAmB,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;gBACnE,CAAC,CAAC;aACH;YAED,wEAAwE;YACxE,oDAAoD;YACpD,WAAW,CAAC,gBAAgB,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;YAC9D,aAAa,EAAE,CAAC;YAChB,OAAO;gBACL,WAAW,CAAC,mBAAmB,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;YACnE,CAAC,CAAC;QACJ,CAAC;QACD,OAAO,SAAA;KACR,CAAC;AACJ,CAAC;AA9KD,0DA8KC;AAED;;;GAGG;AACU,QAAA,iBAAiB,GAAG,uBAAuB,EAAE,CAAC","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"}
@@ -17,6 +17,7 @@ var createInstance = function () {
17
17
  var sessionReplay = new session_replay_1.SessionReplay();
18
18
  return {
19
19
  init: (0, analytics_core_1.debugWrapper)(sessionReplay.init.bind(sessionReplay), 'init', (0, exports.getLogConfig)(sessionReplay)),
20
+ evaluateTargetingAndCapture: (0, analytics_core_1.debugWrapper)(sessionReplay.evaluateTargetingAndCapture.bind(sessionReplay), 'evaluateTargetingAndRecord', (0, exports.getLogConfig)(sessionReplay)),
20
21
  setSessionId: (0, analytics_core_1.debugWrapper)(sessionReplay.setSessionId.bind(sessionReplay), 'setSessionId', (0, exports.getLogConfig)(sessionReplay)),
21
22
  getSessionId: (0, analytics_core_1.debugWrapper)(sessionReplay.getSessionId.bind(sessionReplay), 'getSessionId', (0, exports.getLogConfig)(sessionReplay)),
22
23
  getSessionReplayProperties: (0, analytics_core_1.debugWrapper)(sessionReplay.getSessionReplayProperties.bind(sessionReplay), 'getSessionReplayProperties', (0, exports.getLogConfig)(sessionReplay)),
@@ -1 +1 @@
1
- {"version":3,"file":"session-replay-factory.js","sourceRoot":"","sources":["../../src/session-replay-factory.ts"],"names":[],"mappings":";;;AAAA,4DAAoE;AACpE,sDAAyD;AACzD,mDAAiD;AAG1C,IAAM,YAAY,GAAG,UAAC,aAA4B,IAAK,OAAA;IACpD,IAAA,MAAM,GAAK,aAAa,OAAlB,CAAmB;IAC3B,IAAA,KAAuC,MAAM,IAAI,IAAA,+BAAgB,GAAE,EAAjD,MAAM,oBAAA,EAAE,QAAQ,cAAiC,CAAC;IAC1E,OAAO;QACL,MAAM,QAAA;QACN,QAAQ,UAAA;KACT,CAAC;AACJ,CAAC,EAP6D,CAO7D,CAAC;AAPW,QAAA,YAAY,gBAOvB;AAEF,IAAM,cAAc,GAAiC;IACnD,IAAM,aAAa,GAAG,IAAI,8BAAa,EAAE,CAAC;IAC1C,OAAO;QACL,IAAI,EAAE,IAAA,6BAAY,EAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,IAAA,oBAAY,EAAC,aAAa,CAAC,CAAC;QAC/F,YAAY,EAAE,IAAA,6BAAY,EACxB,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,EAC9C,cAAc,EACd,IAAA,oBAAY,EAAC,aAAa,CAAC,CAC5B;QACD,YAAY,EAAE,IAAA,6BAAY,EACxB,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,EAC9C,cAAc,EACd,IAAA,oBAAY,EAAC,aAAa,CAAC,CAC5B;QACD,0BAA0B,EAAE,IAAA,6BAAY,EACtC,aAAa,CAAC,0BAA0B,CAAC,IAAI,CAAC,aAAa,CAAC,EAC5D,4BAA4B,EAC5B,IAAA,oBAAY,EAAC,aAAa,CAAC,CAC5B;QACD,KAAK,EAAE,IAAA,6BAAY,EAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,IAAA,oBAAY,EAAC,aAAa,CAAC,CAAC;QAClG,QAAQ,EAAE,IAAA,6BAAY,EAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,IAAA,oBAAY,EAAC,aAAa,CAAC,CAAC;KAC5G,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAe,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,4DAAoE;AACpE,sDAAyD;AACzD,mDAAiD;AAG1C,IAAM,YAAY,GAAG,UAAC,aAA4B,IAAK,OAAA;IACpD,IAAA,MAAM,GAAK,aAAa,OAAlB,CAAmB;IAC3B,IAAA,KAAuC,MAAM,IAAI,IAAA,+BAAgB,GAAE,EAAjD,MAAM,oBAAA,EAAE,QAAQ,cAAiC,CAAC;IAC1E,OAAO;QACL,MAAM,QAAA;QACN,QAAQ,UAAA;KACT,CAAC;AACJ,CAAC,EAP6D,CAO7D,CAAC;AAPW,QAAA,YAAY,gBAOvB;AAEF,IAAM,cAAc,GAAiC;IACnD,IAAM,aAAa,GAAG,IAAI,8BAAa,EAAE,CAAC;IAC1C,OAAO;QACL,IAAI,EAAE,IAAA,6BAAY,EAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,IAAA,oBAAY,EAAC,aAAa,CAAC,CAAC;QAC/F,2BAA2B,EAAE,IAAA,6BAAY,EACvC,aAAa,CAAC,2BAA2B,CAAC,IAAI,CAAC,aAAa,CAAC,EAC7D,4BAA4B,EAC5B,IAAA,oBAAY,EAAC,aAAa,CAAC,CAC5B;QACD,YAAY,EAAE,IAAA,6BAAY,EACxB,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,EAC9C,cAAc,EACd,IAAA,oBAAY,EAAC,aAAa,CAAC,CAC5B;QACD,YAAY,EAAE,IAAA,6BAAY,EACxB,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,EAC9C,cAAc,EACd,IAAA,oBAAY,EAAC,aAAa,CAAC,CAC5B;QACD,0BAA0B,EAAE,IAAA,6BAAY,EACtC,aAAa,CAAC,0BAA0B,CAAC,IAAI,CAAC,aAAa,CAAC,EAC5D,4BAA4B,EAC5B,IAAA,oBAAY,EAAC,aAAa,CAAC,CAC5B;QACD,KAAK,EAAE,IAAA,6BAAY,EAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,IAAA,oBAAY,EAAC,aAAa,CAAC,CAAC;QAClG,QAAQ,EAAE,IAAA,6BAAY,EAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,UAAU,EAAE,IAAA,oBAAY,EAAC,aAAa,CAAC,CAAC;KAC5G,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAe,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":"AAAA,OAAO,EAGL,OAAO,EAKR,MAAM,2BAA2B,CAAC;AAInC,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,kCAAkC,EAInC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,gBAAgB,EAMjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAS5D,OAAO,EACL,sBAAsB,EACtB,0BAA0B,IAAI,mCAAmC,EAIjE,kBAAkB,IAAI,mBAAmB,EACzC,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAMlC,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;IAC7C,qBAAqB,UAAS;IAC9B,OAAO,CAAC,mBAAmB,CAAC,CAAwD;IACpF,OAAO,CAAC,wBAAwB,CAAC,CAAU;IAG3C,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,CACrB,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;SAAE,CAAA;KAAE;IAyBvD,0BAA0B;;;IAsC1B,YAAY,aAEV;IAEF,aAAa,aAIX;IAEF;;;;OAIG;IACH,OAAO,CAAC,iBAAiB,CAIvB;IAEF,2BAA2B,oBACR,KAAK,mBAAmB,EAAE,OAAO,GAAG,gBAAgB,CAAC,qCAyCtE;IAEF,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAShC,UAAU,CAAC,sBAAsB,UAAQ;IAgB/C,YAAY;IAUZ,eAAe;IA8Df,iBAAiB,IAAI,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAWlD,oBAAoB,IAAI,MAAM,GAAG,SAAS;IAapC,mBAAmB,CAAC,aAAa,EAAE,aAAa,GAAG,SAAS;YAyCpD,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"}
@@ -7,21 +7,24 @@ var analytics_core_1 = require("@amplitude/analytics-core");
7
7
  var rrweb_types_1 = require("@amplitude/rrweb-types");
8
8
  var joined_config_1 = require("./config/joined-config");
9
9
  var constants_1 = require("./constants");
10
+ var event_compressor_1 = require("./events/event-compressor");
10
11
  var events_manager_1 = require("./events/events-manager");
11
12
  var multi_manager_1 = require("./events/multi-manager");
12
13
  var helpers_1 = require("./helpers");
13
14
  var click_1 = require("./hooks/click");
14
15
  var scroll_1 = require("./hooks/scroll");
15
16
  var identifiers_1 = require("./identifiers");
16
- var version_1 = require("./version");
17
- var event_compressor_1 = require("./events/event-compressor");
18
17
  var logger_1 = require("./logger");
18
+ var targeting_manager_1 = require("./targeting/targeting-manager");
19
+ var version_1 = require("./version");
20
+ var url_tracking_plugin_1 = require("./plugins/url-tracking-plugin");
19
21
  var SessionReplay = /** @class */ (function () {
20
22
  function SessionReplay() {
21
23
  var _this = this;
22
24
  this.name = '@amplitude/session-replay-browser';
23
25
  this.recordCancelCallback = null;
24
26
  this.eventCount = 0;
27
+ this.sessionTargetingMatch = false;
25
28
  // Visible for testing only
26
29
  this.pageLeaveFns = [];
27
30
  // Cache the dynamically imported record function
@@ -65,6 +68,60 @@ var SessionReplay = /** @class */ (function () {
65
68
  fn(e);
66
69
  });
67
70
  };
71
+ this.evaluateTargetingAndCapture = function (targetingParams, isInit) {
72
+ if (isInit === void 0) { isInit = false; }
73
+ return tslib_1.__awaiter(_this, void 0, void 0, function () {
74
+ var eventForTargeting, _a;
75
+ return tslib_1.__generator(this, function (_b) {
76
+ switch (_b.label) {
77
+ case 0:
78
+ if (!this.identifiers || !this.identifiers.sessionId || !this.config) {
79
+ if (this.identifiers && !this.identifiers.sessionId) {
80
+ this.loggerProvider.log('Session ID has not been set yet, cannot evaluate targeting for Session Replay.');
81
+ }
82
+ else {
83
+ this.loggerProvider.warn('Session replay init has not been called, cannot evaluate targeting.');
84
+ }
85
+ return [2 /*return*/];
86
+ }
87
+ // Store targeting parameters for use in getShouldRecord
88
+ this.lastTargetingParams = targetingParams;
89
+ if (!(this.config.targetingConfig && !this.sessionTargetingMatch)) return [3 /*break*/, 2];
90
+ eventForTargeting = targetingParams.event;
91
+ if (eventForTargeting &&
92
+ Object.values(analytics_core_1.SpecialEventType).includes(eventForTargeting.event_type)) {
93
+ eventForTargeting = undefined;
94
+ }
95
+ // We're setting this on this class because fetching the value from idb
96
+ // is async, we need to access this value synchronously (for record
97
+ // and for getSessionReplayProperties - both synchronous fns)
98
+ _a = this;
99
+ return [4 /*yield*/, (0, targeting_manager_1.evaluateTargetingAndStore)({
100
+ sessionId: this.identifiers.sessionId,
101
+ targetingConfig: this.config.targetingConfig,
102
+ loggerProvider: this.loggerProvider,
103
+ apiKey: this.config.apiKey,
104
+ targetingParams: { userProperties: targetingParams.userProperties, event: eventForTargeting },
105
+ })];
106
+ case 1:
107
+ // We're setting this on this class because fetching the value from idb
108
+ // is async, we need to access this value synchronously (for record
109
+ // and for getSessionReplayProperties - both synchronous fns)
110
+ _a.sessionTargetingMatch = _b.sent();
111
+ _b.label = 2;
112
+ case 2:
113
+ if (!isInit) return [3 /*break*/, 3];
114
+ void this.initialize(true);
115
+ return [3 /*break*/, 5];
116
+ case 3: return [4 /*yield*/, this.recordEvents()];
117
+ case 4:
118
+ _b.sent();
119
+ _b.label = 5;
120
+ case 5: return [2 /*return*/];
121
+ }
122
+ });
123
+ });
124
+ };
68
125
  this.addCustomRRWebEvent = function (eventName, eventData, addStorageInfo) {
69
126
  if (eventData === void 0) { eventData = {}; }
70
127
  if (addStorageInfo === void 0) { addStorageInfo = true; }
@@ -212,7 +269,9 @@ var SessionReplay = /** @class */ (function () {
212
269
  _j.sent();
213
270
  this.loggerProvider.log('Installing @amplitude/session-replay-browser.');
214
271
  this.teardownEventListeners(false);
215
- void this.initialize(true);
272
+ return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options.userProperties }, true)];
273
+ case 12:
274
+ _j.sent();
216
275
  return [2 /*return*/];
217
276
  }
218
277
  });
@@ -221,12 +280,14 @@ var SessionReplay = /** @class */ (function () {
221
280
  SessionReplay.prototype.setSessionId = function (sessionId, deviceId) {
222
281
  return (0, analytics_core_1.returnWrapper)(this.asyncSetSessionId(sessionId, deviceId));
223
282
  };
224
- SessionReplay.prototype.asyncSetSessionId = function (sessionId, deviceId) {
283
+ SessionReplay.prototype.asyncSetSessionId = function (sessionId, deviceId, options) {
225
284
  return tslib_1.__awaiter(this, void 0, void 0, function () {
226
285
  var previousSessionId, deviceIdForReplayId, joinedConfig;
227
286
  return tslib_1.__generator(this, function (_a) {
228
287
  switch (_a.label) {
229
288
  case 0:
289
+ this.sessionTargetingMatch = false;
290
+ this.lastShouldRecordDecision = undefined; // Reset targeting decision for new session
230
291
  previousSessionId = this.identifiers && this.identifiers.sessionId;
231
292
  if (previousSessionId) {
232
293
  this.sendEvents(previousSessionId);
@@ -242,8 +303,9 @@ var SessionReplay = /** @class */ (function () {
242
303
  joinedConfig = (_a.sent()).joinedConfig;
243
304
  this.config = joinedConfig;
244
305
  _a.label = 2;
245
- case 2:
246
- void this.recordEvents();
306
+ case 2: return [4 /*yield*/, this.evaluateTargetingAndCapture({ userProperties: options === null || options === void 0 ? void 0 : options.userProperties })];
307
+ case 3:
308
+ _a.sent();
247
309
  return [2 /*return*/];
248
310
  }
249
311
  });
@@ -330,11 +392,49 @@ var SessionReplay = /** @class */ (function () {
330
392
  this.loggerProvider.log("Opting session ".concat(this.identifiers.sessionId, " out of recording due to optOut config."));
331
393
  return false;
332
394
  }
333
- var isInSample = (0, helpers_1.isSessionInSample)(this.identifiers.sessionId, this.config.sampleRate);
334
- if (!isInSample) {
335
- this.loggerProvider.log("Opting session ".concat(this.identifiers.sessionId, " out of recording due to sample rate."));
395
+ var shouldRecord = false;
396
+ var message = '';
397
+ var matched = false;
398
+ // If targetingConfig exists, we'll use the sessionTargetingMatch to determine whether to record
399
+ // Otherwise, we'll evaluate the session against the overall sample rate
400
+ if (this.config.targetingConfig) {
401
+ if (!this.sessionTargetingMatch) {
402
+ message = "Not capturing replays for session ".concat(this.identifiers.sessionId, " due to not matching targeting conditions.");
403
+ this.loggerProvider.log(message);
404
+ shouldRecord = false;
405
+ matched = false;
406
+ }
407
+ else {
408
+ message = "Capturing replays for session ".concat(this.identifiers.sessionId, " due to matching targeting conditions.");
409
+ this.loggerProvider.log(message);
410
+ shouldRecord = true;
411
+ matched = true;
412
+ }
413
+ }
414
+ else {
415
+ var isInSample = (0, helpers_1.isSessionInSample)(this.identifiers.sessionId, this.config.sampleRate);
416
+ if (!isInSample) {
417
+ message = "Opting session ".concat(this.identifiers.sessionId, " out of recording due to sample rate.");
418
+ this.loggerProvider.log(message);
419
+ shouldRecord = false;
420
+ matched = false;
421
+ }
422
+ else {
423
+ shouldRecord = true;
424
+ matched = true;
425
+ }
336
426
  }
337
- return isInSample;
427
+ // Only send custom rrweb event for targeting decision when the decision changes
428
+ if (this.lastShouldRecordDecision !== shouldRecord && this.config.targetingConfig) {
429
+ void this.addCustomRRWebEvent(constants_1.CustomRRwebEvent.TARGETING_DECISION, {
430
+ message: message,
431
+ sessionId: this.identifiers.sessionId,
432
+ matched: matched,
433
+ targetingParams: this.lastTargetingParams,
434
+ });
435
+ this.lastShouldRecordDecision = shouldRecord;
436
+ }
437
+ return shouldRecord;
338
438
  };
339
439
  SessionReplay.prototype.getBlockSelectors = function () {
340
440
  var _a, _b, _c;
@@ -359,24 +459,37 @@ var SessionReplay = /** @class */ (function () {
359
459
  return maskSelector;
360
460
  };
361
461
  SessionReplay.prototype.getRecordingPlugins = function (loggingConfig) {
362
- var _a;
462
+ var _a, _b, _c, _d, _e, _f;
363
463
  return tslib_1.__awaiter(this, void 0, void 0, function () {
364
- var plugins, getRecordConsolePlugin, error_3;
365
- return tslib_1.__generator(this, function (_b) {
366
- switch (_b.label) {
464
+ var plugins, urlTrackingPlugin, getRecordConsolePlugin, error_3;
465
+ return tslib_1.__generator(this, function (_g) {
466
+ switch (_g.label) {
367
467
  case 0:
368
468
  plugins = [];
369
- if (!((_a = loggingConfig === null || loggingConfig === void 0 ? void 0 : loggingConfig.console) === null || _a === void 0 ? void 0 : _a.enabled)) return [3 /*break*/, 4];
370
- _b.label = 1;
469
+ // Add URL tracking plugin
470
+ try {
471
+ urlTrackingPlugin = (0, url_tracking_plugin_1.createUrlTrackingPlugin)({
472
+ ugcFilterRules: ((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.interactionConfig) === null || _b === void 0 ? void 0 : _b.ugcFilterRules) || [],
473
+ enablePolling: ((_c = this.config) === null || _c === void 0 ? void 0 : _c.enableUrlChangePolling) || false,
474
+ pollingInterval: (_d = this.config) === null || _d === void 0 ? void 0 : _d.urlChangePollingInterval,
475
+ captureDocumentTitle: (_e = this.config) === null || _e === void 0 ? void 0 : _e.captureDocumentTitle,
476
+ });
477
+ plugins.push(urlTrackingPlugin);
478
+ }
479
+ catch (error) {
480
+ this.loggerProvider.warn('Failed to create URL tracking plugin:', error);
481
+ }
482
+ if (!((_f = loggingConfig === null || loggingConfig === void 0 ? void 0 : loggingConfig.console) === null || _f === void 0 ? void 0 : _f.enabled)) return [3 /*break*/, 4];
483
+ _g.label = 1;
371
484
  case 1:
372
- _b.trys.push([1, 3, , 4]);
485
+ _g.trys.push([1, 3, , 4]);
373
486
  return [4 /*yield*/, Promise.resolve().then(function () { return tslib_1.__importStar(require('@amplitude/rrweb-plugin-console-record')); })];
374
487
  case 2:
375
- getRecordConsolePlugin = (_b.sent()).getRecordConsolePlugin;
488
+ getRecordConsolePlugin = (_g.sent()).getRecordConsolePlugin;
376
489
  plugins.push(getRecordConsolePlugin({ level: loggingConfig.console.levels }));
377
490
  return [3 /*break*/, 4];
378
491
  case 3:
379
- error_3 = _b.sent();
492
+ error_3 = _g.sent();
380
493
  this.loggerProvider.warn('Failed to load console plugin:', error_3);
381
494
  return [3 /*break*/, 4];
382
495
  case 4: return [2 /*return*/, plugins.length > 0 ? plugins : undefined];