@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,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":"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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"}
|
|
@@ -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
|
|
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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"}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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 (
|
|
366
|
-
switch (
|
|
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
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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 = (
|
|
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 =
|
|
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];
|