@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.
- package/README.md +13 -3
- package/lib/cjs/config/joined-config.d.ts.map +1 -1
- package/lib/cjs/config/joined-config.js +10 -3
- package/lib/cjs/config/joined-config.js.map +1 -1
- package/lib/cjs/config/local-config.d.ts +3 -0
- package/lib/cjs/config/local-config.d.ts.map +1 -1
- package/lib/cjs/config/local-config.js +4 -1
- package/lib/cjs/config/local-config.js.map +1 -1
- package/lib/cjs/config/types.d.ts +30 -0
- package/lib/cjs/config/types.d.ts.map +1 -1
- package/lib/cjs/config/types.js.map +1 -1
- package/lib/cjs/constants.d.ts +3 -1
- package/lib/cjs/constants.d.ts.map +1 -1
- package/lib/cjs/constants.js +3 -1
- package/lib/cjs/constants.js.map +1 -1
- package/lib/cjs/helpers.d.ts +7 -0
- package/lib/cjs/helpers.d.ts.map +1 -1
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +2 -2
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/plugins/index.d.ts +2 -0
- package/lib/cjs/plugins/index.d.ts.map +1 -0
- package/lib/cjs/plugins/index.js +5 -0
- package/lib/cjs/plugins/index.js.map +1 -0
- package/lib/cjs/plugins/url-tracking-plugin.d.ts +56 -0
- package/lib/cjs/plugins/url-tracking-plugin.d.ts.map +1 -0
- package/lib/cjs/plugins/url-tracking-plugin.js +187 -0
- package/lib/cjs/plugins/url-tracking-plugin.js.map +1 -0
- package/lib/cjs/session-replay-factory.d.ts.map +1 -1
- package/lib/cjs/session-replay-factory.js +1 -0
- package/lib/cjs/session-replay-factory.js.map +1 -1
- package/lib/cjs/session-replay.d.ts +12 -3
- package/lib/cjs/session-replay.d.ts.map +1 -1
- package/lib/cjs/session-replay.js +132 -19
- package/lib/cjs/session-replay.js.map +1 -1
- package/lib/cjs/targeting/targeting-idb-store.d.ts +38 -0
- package/lib/cjs/targeting/targeting-idb-store.d.ts.map +1 -0
- package/lib/cjs/targeting/targeting-idb-store.js +146 -0
- package/lib/cjs/targeting/targeting-idb-store.js.map +1 -0
- package/lib/cjs/targeting/targeting-manager.d.ts +11 -0
- package/lib/cjs/targeting/targeting-manager.d.ts.map +1 -0
- package/lib/cjs/targeting/targeting-manager.js +60 -0
- package/lib/cjs/targeting/targeting-manager.js.map +1 -0
- package/lib/cjs/typings/session-replay.d.ts +2 -0
- package/lib/cjs/typings/session-replay.d.ts.map +1 -1
- package/lib/cjs/typings/session-replay.js.map +1 -1
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/cjs/version.js.map +1 -1
- package/lib/esm/config/joined-config.d.ts.map +1 -1
- package/lib/esm/config/joined-config.js +10 -3
- package/lib/esm/config/joined-config.js.map +1 -1
- package/lib/esm/config/local-config.d.ts +3 -0
- package/lib/esm/config/local-config.d.ts.map +1 -1
- package/lib/esm/config/local-config.js +5 -2
- package/lib/esm/config/local-config.js.map +1 -1
- package/lib/esm/config/types.d.ts +30 -0
- package/lib/esm/config/types.d.ts.map +1 -1
- package/lib/esm/config/types.js.map +1 -1
- package/lib/esm/constants.d.ts +3 -1
- package/lib/esm/constants.d.ts.map +1 -1
- package/lib/esm/constants.js +2 -0
- package/lib/esm/constants.js.map +1 -1
- package/lib/esm/helpers.d.ts +7 -0
- package/lib/esm/helpers.d.ts.map +1 -1
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +1 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/plugins/index.d.ts +2 -0
- package/lib/esm/plugins/index.d.ts.map +1 -0
- package/lib/esm/plugins/index.js +2 -0
- package/lib/esm/plugins/index.js.map +1 -0
- package/lib/esm/plugins/url-tracking-plugin.d.ts +56 -0
- package/lib/esm/plugins/url-tracking-plugin.d.ts.map +1 -0
- package/lib/esm/plugins/url-tracking-plugin.js +183 -0
- package/lib/esm/plugins/url-tracking-plugin.js.map +1 -0
- package/lib/esm/session-replay-factory.d.ts.map +1 -1
- package/lib/esm/session-replay-factory.js +1 -0
- package/lib/esm/session-replay-factory.js.map +1 -1
- package/lib/esm/session-replay.d.ts +12 -3
- package/lib/esm/session-replay.d.ts.map +1 -1
- package/lib/esm/session-replay.js +133 -20
- package/lib/esm/session-replay.js.map +1 -1
- package/lib/esm/targeting/targeting-idb-store.d.ts +38 -0
- package/lib/esm/targeting/targeting-idb-store.d.ts.map +1 -0
- package/lib/esm/targeting/targeting-idb-store.js +143 -0
- package/lib/esm/targeting/targeting-idb-store.js.map +1 -0
- package/lib/esm/targeting/targeting-manager.d.ts +11 -0
- package/lib/esm/targeting/targeting-manager.d.ts.map +1 -0
- package/lib/esm/targeting/targeting-manager.js +56 -0
- package/lib/esm/targeting/targeting-manager.js.map +1 -0
- package/lib/esm/typings/session-replay.d.ts +2 -0
- package/lib/esm/typings/session-replay.d.ts.map +1 -1
- package/lib/esm/typings/session-replay.js.map +1 -1
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/esm/version.js.map +1 -1
- package/lib/scripts/observers-min.js +1 -1
- package/lib/scripts/observers-min.js.gz +0 -0
- package/lib/scripts/observers-min.js.map +1 -1
- package/lib/scripts/session-replay-browser-esm.js +1 -1
- package/lib/scripts/session-replay-browser-esm.js.gz +0 -0
- package/lib/scripts/session-replay-browser-min.js +1 -1
- package/lib/scripts/session-replay-browser-min.js.gz +0 -0
- package/lib/scripts/session-replay-browser-min.js.map +1 -1
- package/lib/scripts/session-replay-min.js +1 -1
- package/lib/scripts/session-replay-min.js.gz +0 -0
- package/lib/scripts/session-replay-min.js.map +1 -1
- package/lib/scripts/targeting-min.js +2 -0
- package/lib/scripts/targeting-min.js.gz +0 -0
- package/lib/scripts/targeting-min.js.map +1 -0
- 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;;
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
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 (
|
|
363
|
-
switch (
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
|
|
482
|
+
_g.trys.push([1, 3, , 4]);
|
|
370
483
|
return [4 /*yield*/, import('@amplitude/rrweb-plugin-console-record')];
|
|
371
484
|
case 2:
|
|
372
|
-
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 =
|
|
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];
|