@sailfish-ai/recorder 1.7.20 → 1.7.22
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/dist/constants.js +60 -0
- package/dist/index.js +430 -334
- package/dist/sailfish-recorder.cjs.js +1 -1
- package/dist/sailfish-recorder.cjs.js.br +0 -0
- package/dist/sailfish-recorder.cjs.js.gz +0 -0
- package/dist/sailfish-recorder.es.js +1 -1
- package/dist/sailfish-recorder.es.js.br +0 -0
- package/dist/sailfish-recorder.es.js.gz +0 -0
- package/dist/sailfish-recorder.umd.js +1 -1
- package/dist/sailfish-recorder.umd.js.br +0 -0
- package/dist/sailfish-recorder.umd.js.gz +0 -0
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/index.d.ts +12 -0
- package/package.json +16 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const DEBUG = import.meta.env.VITE_DEBUG ? import.meta.env.VITE_DEBUG : false;
|
|
2
2
|
// import { NetworkRecordOptions } from "@sailfish-rrweb/rrweb-plugin-network-record";
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
|
-
import { NetworkRequestEventId, xSf3RidHeader } from "./constants";
|
|
4
|
+
import { NetworkRequestEventId, STATIC_EXTENSIONS, xSf3RidHeader, } from "./constants";
|
|
5
5
|
import { gatherAndCacheDeviceInfo } from "./deviceInfo";
|
|
6
6
|
import { fetchCaptureSettings, sendDomainsToNotPropagateHeaderTo, startRecordingSession, } from "./graphql";
|
|
7
7
|
import { sendMapUuidIfAvailable } from "./mapUuid";
|
|
@@ -16,19 +16,115 @@ const DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT = [
|
|
|
16
16
|
"*.googleapis.com",
|
|
17
17
|
"*.amazonaws.com", // Exclude AWS S3
|
|
18
18
|
"*.smooch.io", // Exclude smooch-related requests
|
|
19
|
-
|
|
19
|
+
// Exclude zendesk-related requests
|
|
20
|
+
"*.zendesk.com",
|
|
21
|
+
"*.zdassets.com",
|
|
20
22
|
];
|
|
21
23
|
const BAD_HTTP_STATUS = [
|
|
22
24
|
400, // BAD REQUEST
|
|
23
25
|
403, // FORBIDDEN
|
|
24
26
|
];
|
|
25
27
|
const CORS_KEYWORD = "CORS";
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
+
export const STORAGE_VERSION = 1;
|
|
29
|
+
export const DYNAMIC_PASSED_HOSTS_KEY = "dynamicPassedHosts";
|
|
30
|
+
export const DYNAMIC_EXCLUDED_HOSTS_KEY = "dynamicExcludedHosts";
|
|
28
31
|
const SF_API_KEY_FOR_UPDATE = "sailfishApiKey";
|
|
29
32
|
const SF_BACKEND_API = "sailfishBackendApi";
|
|
30
33
|
const INCLUDE = "include";
|
|
31
34
|
const SAME_ORIGIN = "same-origin";
|
|
35
|
+
const ALLOWED_HEADERS_HEADER = "access-control-allow-headers";
|
|
36
|
+
const OPTIONS = "OPTIONS";
|
|
37
|
+
export const dynamicExcludedHosts = new Set();
|
|
38
|
+
export const dynamicPassedHosts = new Set();
|
|
39
|
+
// 1️⃣ dynamicExcludedHosts.add override
|
|
40
|
+
const originalExcludedAdd = dynamicExcludedHosts.add;
|
|
41
|
+
dynamicExcludedHosts.add = (host) => {
|
|
42
|
+
const cleaned = host?.trim();
|
|
43
|
+
if (!cleaned || dynamicExcludedHosts.has(cleaned)) {
|
|
44
|
+
return dynamicExcludedHosts;
|
|
45
|
+
}
|
|
46
|
+
// 1. Add to the excluded Set
|
|
47
|
+
originalExcludedAdd.call(dynamicExcludedHosts, cleaned);
|
|
48
|
+
// 2. If it was previously in passed, remove it
|
|
49
|
+
if (dynamicPassedHosts.has(cleaned)) {
|
|
50
|
+
dynamicPassedHosts.delete(cleaned);
|
|
51
|
+
}
|
|
52
|
+
// 3. Consolidate dynamic exclusions into smart wildcards
|
|
53
|
+
const consolidated = consolidateDynamicExclusions(dynamicExcludedHosts);
|
|
54
|
+
dynamicExcludedHosts.clear();
|
|
55
|
+
for (const p of consolidated) {
|
|
56
|
+
originalExcludedAdd.call(dynamicExcludedHosts, p);
|
|
57
|
+
}
|
|
58
|
+
// 4. Persist wrapper to localStorage
|
|
59
|
+
try {
|
|
60
|
+
// TODO decouple into two logical pieces
|
|
61
|
+
const wrapper = {
|
|
62
|
+
version: STORAGE_VERSION,
|
|
63
|
+
entries: {},
|
|
64
|
+
};
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
for (const p of dynamicExcludedHosts) {
|
|
67
|
+
wrapper.entries[p] = now;
|
|
68
|
+
}
|
|
69
|
+
localStorage.setItem(DYNAMIC_EXCLUDED_HOSTS_KEY, JSON.stringify(wrapper));
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
if (DEBUG)
|
|
73
|
+
console.warn("Persist dynamicExcludedHosts failed:", e);
|
|
74
|
+
}
|
|
75
|
+
// 5. Notify backend of the updated Set
|
|
76
|
+
updateExcludedHostsStorageAndBackend(dynamicExcludedHosts);
|
|
77
|
+
return dynamicExcludedHosts;
|
|
78
|
+
};
|
|
79
|
+
// 2️⃣ dynamicPassedHosts.add override (timestamps refreshed on every call)
|
|
80
|
+
const originalPassedAdd = dynamicPassedHosts.add;
|
|
81
|
+
dynamicPassedHosts.add = (host) => {
|
|
82
|
+
const cleaned = host?.trim();
|
|
83
|
+
if (!cleaned) {
|
|
84
|
+
return dynamicPassedHosts;
|
|
85
|
+
}
|
|
86
|
+
// 1. Persist wrapper to localStorage (update timestamp unconditionally)
|
|
87
|
+
try {
|
|
88
|
+
// TODO decouple into two logical pieces
|
|
89
|
+
const raw = localStorage.getItem(DYNAMIC_PASSED_HOSTS_KEY);
|
|
90
|
+
let wrapper;
|
|
91
|
+
if (!raw) {
|
|
92
|
+
wrapper = { version: STORAGE_VERSION, entries: {} };
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const parsed = JSON.parse(raw);
|
|
96
|
+
if (Array.isArray(parsed)) {
|
|
97
|
+
// migrate old array → wrapper
|
|
98
|
+
wrapper = {
|
|
99
|
+
version: STORAGE_VERSION,
|
|
100
|
+
entries: Object.fromEntries(parsed.map((h) => [h, Date.now()])),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
else if (parsed &&
|
|
104
|
+
typeof parsed === "object" &&
|
|
105
|
+
"entries" in parsed &&
|
|
106
|
+
typeof parsed.entries === "object") {
|
|
107
|
+
wrapper = parsed;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
wrapper = { version: STORAGE_VERSION, entries: {} };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// always set/update the timestamp
|
|
114
|
+
wrapper.entries[cleaned] = Date.now();
|
|
115
|
+
wrapper.version = STORAGE_VERSION;
|
|
116
|
+
localStorage.setItem(DYNAMIC_PASSED_HOSTS_KEY, JSON.stringify(wrapper));
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
if (DEBUG)
|
|
120
|
+
console.warn("Persist dynamicPassedHosts failed:", e);
|
|
121
|
+
}
|
|
122
|
+
// 2. Add to the passed Set if not already present
|
|
123
|
+
if (!dynamicPassedHosts.has(cleaned)) {
|
|
124
|
+
originalPassedAdd.call(dynamicPassedHosts, cleaned);
|
|
125
|
+
}
|
|
126
|
+
return dynamicPassedHosts;
|
|
127
|
+
};
|
|
32
128
|
/**
|
|
33
129
|
* Notify the backend of the updated dynamicExcludedHosts
|
|
34
130
|
*/
|
|
@@ -40,58 +136,114 @@ function updateExcludedHostsStorageAndBackend(dynamicExcludedHosts) {
|
|
|
40
136
|
}
|
|
41
137
|
sendDomainsToNotPropagateHeaderTo(apiKeyForUpdate, [...dynamicExcludedHosts, ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT], apiForUpdate).catch((error) => console.error("Failed to send domains to not propagate header to:", error));
|
|
42
138
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Given a set of excluded hostPaths (like "foo.com/bar", "foo.com/baz", etc.),
|
|
141
|
+
* find any path prefixes under each host that appear ≥ threshold times,
|
|
142
|
+
* and replace their individual entries with a single wildcard pattern
|
|
143
|
+
* (e.g. "foo.com/bar*").
|
|
144
|
+
*/
|
|
145
|
+
export function consolidateDynamicExclusions(hostPathSet, threshold = 3) {
|
|
146
|
+
// 1️⃣ Group by host
|
|
147
|
+
const byHost = {};
|
|
148
|
+
for (const hostPath of hostPathSet) {
|
|
149
|
+
const [host, ...rest] = hostPath.split("/");
|
|
150
|
+
const path = rest.length ? `/${rest.join("/")}` : "/";
|
|
151
|
+
(byHost[host] ??= []).push(path);
|
|
152
|
+
}
|
|
153
|
+
const newSet = new Set();
|
|
154
|
+
for (const host in byHost) {
|
|
155
|
+
const paths = byHost[host];
|
|
156
|
+
if (paths.length < threshold) {
|
|
157
|
+
// not enough entries to bother; keep them as-is
|
|
158
|
+
for (const p of paths)
|
|
159
|
+
newSet.add(`${host}${p}`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const root = { count: 0, children: new Map() };
|
|
163
|
+
for (const p of paths) {
|
|
164
|
+
root.count++;
|
|
165
|
+
const segments = p.split("/").filter(Boolean);
|
|
166
|
+
let node = root;
|
|
167
|
+
for (const seg of segments) {
|
|
168
|
+
if (!node.children.has(seg)) {
|
|
169
|
+
node.children.set(seg, { count: 0, children: new Map() });
|
|
170
|
+
}
|
|
171
|
+
node = node.children.get(seg);
|
|
172
|
+
node.count++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 3️⃣ Walk the trie and pick prefixes where node.count ≥ threshold
|
|
176
|
+
function gather(node, prefixSegments) {
|
|
177
|
+
// if this node covers ≥ threshold of this host’s paths, collapse here
|
|
178
|
+
if (node.count >= threshold && prefixSegments.length > 0) {
|
|
179
|
+
const wildcardPath = "/" + prefixSegments.join("/") + "/*";
|
|
180
|
+
newSet.add(`${host}${wildcardPath}`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// otherwise, recurse into children
|
|
184
|
+
for (const [seg, child] of node.children) {
|
|
185
|
+
gather(child, prefixSegments.concat(seg));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
gather(root, []);
|
|
189
|
+
}
|
|
190
|
+
return newSet;
|
|
191
|
+
}
|
|
192
|
+
// 2️⃣ Load & evict old entries (>7 days) + version check for Excluded
|
|
46
193
|
(() => {
|
|
47
194
|
const stored = localStorage.getItem(DYNAMIC_EXCLUDED_HOSTS_KEY);
|
|
48
|
-
if (stored)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log("Failed to parse dynamicExcludedHosts from storage", e);
|
|
195
|
+
if (!stored)
|
|
196
|
+
return;
|
|
197
|
+
try {
|
|
198
|
+
const wrapper = JSON.parse(stored);
|
|
199
|
+
// if it's from an old version, drop it
|
|
200
|
+
if (wrapper.version !== STORAGE_VERSION) {
|
|
55
201
|
localStorage.removeItem(DYNAMIC_EXCLUDED_HOSTS_KEY);
|
|
202
|
+
return;
|
|
56
203
|
}
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
const valid = {};
|
|
206
|
+
for (const [host, ts] of Object.entries(wrapper.entries)) {
|
|
207
|
+
if (now - ts < 7 * 24 * 60 * 60 * 1000) {
|
|
208
|
+
dynamicExcludedHosts.add(host);
|
|
209
|
+
valid[host] = ts;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
localStorage.setItem(DYNAMIC_EXCLUDED_HOSTS_KEY, JSON.stringify({ version: STORAGE_VERSION, entries: valid }));
|
|
213
|
+
}
|
|
214
|
+
catch (e) {
|
|
215
|
+
if (DEBUG)
|
|
216
|
+
console.warn("Failed to parse dynamicExcludedHosts:", e);
|
|
217
|
+
localStorage.removeItem(DYNAMIC_EXCLUDED_HOSTS_KEY);
|
|
57
218
|
}
|
|
58
219
|
})();
|
|
59
|
-
// Load
|
|
220
|
+
// 3️⃣ Load & evict old entries (>7 days) + version check for Passed
|
|
60
221
|
(() => {
|
|
61
222
|
const stored = localStorage.getItem(DYNAMIC_PASSED_HOSTS_KEY);
|
|
62
|
-
if (stored)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (DEBUG)
|
|
68
|
-
console.log("Failed to parse dynamicPassedHosts from storage", e);
|
|
223
|
+
if (!stored)
|
|
224
|
+
return;
|
|
225
|
+
try {
|
|
226
|
+
const wrapper = JSON.parse(stored);
|
|
227
|
+
if (wrapper.version !== STORAGE_VERSION) {
|
|
69
228
|
localStorage.removeItem(DYNAMIC_PASSED_HOSTS_KEY);
|
|
229
|
+
return;
|
|
70
230
|
}
|
|
231
|
+
const now = Date.now();
|
|
232
|
+
const valid = {};
|
|
233
|
+
for (const [host, ts] of Object.entries(wrapper.entries)) {
|
|
234
|
+
if (now - ts < 7 * 24 * 60 * 60 * 1000) {
|
|
235
|
+
dynamicPassedHosts.add(host);
|
|
236
|
+
valid[host] = ts;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
localStorage.setItem(DYNAMIC_PASSED_HOSTS_KEY, JSON.stringify({ version: STORAGE_VERSION, entries: valid }));
|
|
71
240
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const cleanedHost = host?.trim();
|
|
77
|
-
if (!cleanedHost) {
|
|
78
|
-
return dynamicExcludedHosts;
|
|
241
|
+
catch (e) {
|
|
242
|
+
if (DEBUG)
|
|
243
|
+
console.warn("Failed to parse dynamicPassedHosts:", e);
|
|
244
|
+
localStorage.removeItem(DYNAMIC_PASSED_HOSTS_KEY);
|
|
79
245
|
}
|
|
80
|
-
|
|
81
|
-
localStorage.setItem(DYNAMIC_EXCLUDED_HOSTS_KEY, JSON.stringify(Array.from(dynamicExcludedHosts)));
|
|
82
|
-
updateExcludedHostsStorageAndBackend(dynamicExcludedHosts);
|
|
83
|
-
return dynamicExcludedHosts;
|
|
84
|
-
};
|
|
85
|
-
const originalPassedAdd = dynamicPassedHosts.add;
|
|
86
|
-
dynamicPassedHosts.add = (host) => {
|
|
87
|
-
const cleanedHost = host?.trim();
|
|
88
|
-
if (!cleanedHost) {
|
|
89
|
-
return dynamicPassedHosts;
|
|
90
|
-
}
|
|
91
|
-
originalPassedAdd.call(dynamicPassedHosts, cleanedHost);
|
|
92
|
-
localStorage.setItem(DYNAMIC_PASSED_HOSTS_KEY, JSON.stringify(Array.from(dynamicPassedHosts)));
|
|
93
|
-
return dynamicPassedHosts;
|
|
94
|
-
};
|
|
246
|
+
})();
|
|
95
247
|
const ActionType = {
|
|
96
248
|
PROPAGATE: "propagate",
|
|
97
249
|
IGNORE: "ignore",
|
|
@@ -197,7 +349,10 @@ function getOrSetUserDeviceUuid() {
|
|
|
197
349
|
let userDeviceUuid = localStorage.getItem("sailfishUserDeviceUuid");
|
|
198
350
|
if (!userDeviceUuid) {
|
|
199
351
|
userDeviceUuid = uuidv4();
|
|
200
|
-
|
|
352
|
+
try {
|
|
353
|
+
localStorage.setItem("sailfishUserDeviceUuid", userDeviceUuid);
|
|
354
|
+
}
|
|
355
|
+
catch { }
|
|
201
356
|
}
|
|
202
357
|
return userDeviceUuid;
|
|
203
358
|
}
|
|
@@ -295,65 +450,29 @@ export function matchUrlWithWildcard(url, patterns) {
|
|
|
295
450
|
return true;
|
|
296
451
|
});
|
|
297
452
|
}
|
|
298
|
-
function shouldSkipHeadersPropagation(url,
|
|
299
|
-
const
|
|
453
|
+
function shouldSkipHeadersPropagation(url, domainsToNotPropagateHeaderTo = []) {
|
|
454
|
+
const urlObj = new URL(url, window.location.href);
|
|
455
|
+
// 1️⃣ STATIC ASSET EXCLUSIONS (by comprehensive file extension list)
|
|
456
|
+
for (const ext of STATIC_EXTENSIONS) {
|
|
457
|
+
if (urlObj.pathname.toLowerCase().endsWith(ext)) {
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// 2️⃣ WILDCARD-BASED EXCLUSION (domain + path)
|
|
462
|
+
// Pass patterns like ["*.cdn.com/*", "api.example.com/v1/*"]
|
|
463
|
+
const combinedPatterns = [
|
|
300
464
|
...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
|
|
301
|
-
...
|
|
465
|
+
...domainsToNotPropagateHeaderTo,
|
|
302
466
|
];
|
|
303
|
-
|
|
304
|
-
if (defaultExcluded) {
|
|
467
|
+
if (matchUrlWithWildcard(url, combinedPatterns)) {
|
|
305
468
|
return true;
|
|
306
469
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
* Returns ActionType.PROPAGATE if server responds 2xx, ActionType.IGNORE on error or non-2xx.
|
|
314
|
-
*/
|
|
315
|
-
function performOptionsPreflightForXHR(url, init, xSf3RidHeaderValue, domain) {
|
|
316
|
-
return new Promise((resolve) => {
|
|
317
|
-
const xhr = new XMLHttpRequest();
|
|
318
|
-
xhr.open("OPTIONS", url, true);
|
|
319
|
-
// Mirror credentials
|
|
320
|
-
xhr.withCredentials = init.credentials === INCLUDE;
|
|
321
|
-
// CORS preflight headers
|
|
322
|
-
const method = (init.method || "GET").toUpperCase();
|
|
323
|
-
xhr.setRequestHeader("Access-Control-Request-Method", method);
|
|
324
|
-
const rawHeaders = init.headers instanceof Headers
|
|
325
|
-
? Object.entries(init.headers)
|
|
326
|
-
: init.headers || {};
|
|
327
|
-
const customHeaders = Object.keys(rawHeaders)
|
|
328
|
-
.map((h) => h.toLowerCase())
|
|
329
|
-
.filter((h) => ![
|
|
330
|
-
"accept",
|
|
331
|
-
"content-type",
|
|
332
|
-
"accept-language",
|
|
333
|
-
"content-language",
|
|
334
|
-
].includes(h));
|
|
335
|
-
if (customHeaders.length) {
|
|
336
|
-
xhr.setRequestHeader("Access-Control-Request-Headers", customHeaders.join(","));
|
|
337
|
-
}
|
|
338
|
-
// Add tracing header
|
|
339
|
-
this.setRequestHeader(xSf3RidHeader, xSf3RidHeaderValue);
|
|
340
|
-
xhr.onload = () => {
|
|
341
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
342
|
-
resolve(ActionType.PROPAGATE);
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
DEBUG &&
|
|
346
|
-
console.log(`[XHR Interceptor] OPTIONS returned status ${xhr.status} for ${domain}`);
|
|
347
|
-
resolve(null);
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
xhr.onerror = () => {
|
|
351
|
-
DEBUG &&
|
|
352
|
-
console.log(`[XHR Interceptor] Preflight OPTIONS CORS or network error for ${domain}`);
|
|
353
|
-
resolve(ActionType.IGNORE);
|
|
354
|
-
};
|
|
355
|
-
xhr.send();
|
|
356
|
-
});
|
|
470
|
+
// 3️⃣ DYNAMIC-FAILURE EXCLUSION (exact host)
|
|
471
|
+
const hostname = urlObj.hostname;
|
|
472
|
+
if (dynamicExcludedHosts.has(hostname)) {
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
return false;
|
|
357
476
|
}
|
|
358
477
|
// Updated XMLHttpRequest interceptor with domain exclusion
|
|
359
478
|
function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo = []) {
|
|
@@ -379,120 +498,101 @@ function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo = []) {
|
|
|
379
498
|
this._capturedRequestHeaders = {};
|
|
380
499
|
return originalOpen.apply(this, [method, url, ...args]);
|
|
381
500
|
};
|
|
382
|
-
//
|
|
501
|
+
// 1️⃣ XHR interceptor send()
|
|
383
502
|
XMLHttpRequest.prototype.send = function (...args) {
|
|
384
503
|
const url = this._requestUrl;
|
|
385
|
-
if (!url)
|
|
504
|
+
if (!url) {
|
|
386
505
|
return originalSend.apply(this, args);
|
|
387
|
-
|
|
388
|
-
//
|
|
506
|
+
}
|
|
507
|
+
// parse host+path for exclusion
|
|
508
|
+
const urlObj = new URL(url, window.location.href);
|
|
509
|
+
const domain = urlObj.hostname;
|
|
510
|
+
const hostPath = urlObj.pathname === "/" ? domain : `${domain}${urlObj.pathname}`;
|
|
511
|
+
// 1️⃣ Skip injection for excluded domains/paths
|
|
389
512
|
if (shouldSkipHeadersPropagation(url, domainsToNotPropagateHeaderTo)) {
|
|
390
513
|
return originalSend.apply(this, args);
|
|
391
514
|
}
|
|
515
|
+
// 2️⃣ Prepare header and IDs
|
|
392
516
|
const pageVisitUUID = sessionStorage.getItem("pageVisitUUID");
|
|
393
517
|
const networkUUID = uuidv4();
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
518
|
+
const headerValue = `${sessionId}/${pageVisitUUID}/${networkUUID}`;
|
|
519
|
+
try {
|
|
520
|
+
this.setRequestHeader(xSf3RidHeader, headerValue);
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
console.warn(`Could not set X-Sf3-Rid for ${url}`, e);
|
|
524
|
+
}
|
|
525
|
+
// 3️⃣ Track timing
|
|
526
|
+
const startTime = Date.now();
|
|
527
|
+
let finished = false;
|
|
528
|
+
// 4️⃣ Helper to emit networkRequestFinished
|
|
529
|
+
const emitFinished = (success, status, errorMsg) => {
|
|
530
|
+
if (finished)
|
|
531
|
+
return;
|
|
532
|
+
finished = true;
|
|
533
|
+
const endTime = Date.now();
|
|
534
|
+
sendEvent({
|
|
535
|
+
type: NetworkRequestEventId,
|
|
536
|
+
timestamp: endTime,
|
|
537
|
+
sessionId,
|
|
538
|
+
data: {
|
|
539
|
+
request_id: networkUUID,
|
|
540
|
+
session_id: sessionId,
|
|
541
|
+
timestamp_start: startTime,
|
|
542
|
+
timestamp_end: endTime,
|
|
543
|
+
response_code: status,
|
|
544
|
+
success,
|
|
545
|
+
error: errorMsg,
|
|
546
|
+
method: this._requestMethod,
|
|
547
|
+
url,
|
|
548
|
+
},
|
|
549
|
+
...getUrlAndStoredUuids(),
|
|
550
|
+
});
|
|
551
|
+
// 5️⃣ Update dynamic sets
|
|
552
|
+
if (success) {
|
|
553
|
+
// once any route passes, skip preflight on entire domain
|
|
554
|
+
dynamicPassedHosts.add(domain);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
// only exclude the specific failing path
|
|
558
|
+
dynamicExcludedHosts.add(hostPath);
|
|
403
559
|
}
|
|
404
|
-
// On CORS or network error during send, add domain to passed-hosts list
|
|
405
|
-
this.addEventListener("error", () => {
|
|
406
|
-
dynamicExcludedHosts.add(domain);
|
|
407
|
-
}, { once: true });
|
|
408
|
-
// On successful send (HTTP 2xx), add domain to passed-hosts list
|
|
409
|
-
this.addEventListener("load", () => {
|
|
410
|
-
if (this.status === 0) {
|
|
411
|
-
// status 0 on load is often a CORS‐blocked response
|
|
412
|
-
dynamicExcludedHosts.add(domain);
|
|
413
|
-
}
|
|
414
|
-
if (this.status >= 200 && this.status < 300) {
|
|
415
|
-
dynamicPassedHosts.add(domain);
|
|
416
|
-
}
|
|
417
|
-
}, { once: true });
|
|
418
|
-
return originalSend.apply(this, args);
|
|
419
560
|
};
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
.
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// just return void
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
return proceedSend();
|
|
561
|
+
// 6️⃣ On successful load
|
|
562
|
+
this.addEventListener("load", () => {
|
|
563
|
+
const status = this.status || 0;
|
|
564
|
+
if (status >= 200 && status < 300) {
|
|
565
|
+
emitFinished(true, status, "");
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
const msg = this.statusText || `HTTP ${status}`;
|
|
569
|
+
emitFinished(false, status, msg);
|
|
570
|
+
}
|
|
571
|
+
}, { once: true });
|
|
572
|
+
// 7️⃣ On network/CORS error
|
|
573
|
+
this.addEventListener("error", () => {
|
|
574
|
+
// XHR error events often mean CORS or connectivity; status is usually 0
|
|
575
|
+
const status = this.status || 0;
|
|
576
|
+
const msg = status === 0
|
|
577
|
+
? "Network or CORS failure"
|
|
578
|
+
: this.statusText || `Error ${status}`;
|
|
579
|
+
emitFinished(false, status, msg);
|
|
580
|
+
}, { once: true });
|
|
581
|
+
// 8️⃣ Finally, send the request
|
|
582
|
+
return originalSend.apply(this, args);
|
|
446
583
|
};
|
|
447
584
|
}
|
|
448
|
-
/**
|
|
449
|
-
* Performs an OPTIONS preflight check to decide header propagation.
|
|
450
|
-
* Returns 'propagate' if OPTIONS succeeds, 'ignore' otherwise.
|
|
451
|
-
*/
|
|
452
|
-
async function performOptionsPreflight(target, thisArg, url, init, sessionId, domain) {
|
|
453
|
-
try {
|
|
454
|
-
const headers = new Headers(init.headers || {});
|
|
455
|
-
headers.set(xSf3RidHeader, sessionId);
|
|
456
|
-
const response = await target.call(thisArg, url, {
|
|
457
|
-
method: "OPTIONS",
|
|
458
|
-
headers,
|
|
459
|
-
});
|
|
460
|
-
if (response.ok) {
|
|
461
|
-
return ActionType.PROPAGATE;
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
DEBUG &&
|
|
465
|
-
console.log(`[Fetch Interceptor] OPTIONS returned status ${response.status} for ${domain}`);
|
|
466
|
-
return null;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
catch (error) {
|
|
470
|
-
// Treat fetch errors (likely CORS failures) as ignore
|
|
471
|
-
if (error instanceof TypeError || error?.message?.includes(CORS_KEYWORD)) {
|
|
472
|
-
DEBUG &&
|
|
473
|
-
console.log(`[Fetch Interceptor] Preflight OPTIONS CORS error for ${domain}:`, error);
|
|
474
|
-
return ActionType.IGNORE;
|
|
475
|
-
}
|
|
476
|
-
// Other failures also ignored as some APIs or reverse proxies (e.g. NGINX) don’t route
|
|
477
|
-
// or handle OPTIONS requests, leading to:
|
|
478
|
-
// * 404 Not Found
|
|
479
|
-
// * 405 Method Not Allowed
|
|
480
|
-
// * 500 Internal Server Error
|
|
481
|
-
DEBUG &&
|
|
482
|
-
console.log(`[Fetch Interceptor] Preflight OPTIONS failed for ${domain}:`, error);
|
|
483
|
-
return null;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
585
|
// Updated fetch interceptor with exclusion handling
|
|
487
586
|
function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
|
|
488
587
|
const originalFetch = window.fetch;
|
|
489
588
|
const sessionId = getOrSetSessionId();
|
|
490
|
-
const
|
|
589
|
+
const cachedDomains = new Map();
|
|
491
590
|
window.fetch = new Proxy(originalFetch, {
|
|
492
591
|
apply: async (target, thisArg, args) => {
|
|
493
592
|
let input = args[0];
|
|
494
593
|
let init = args[1] || {};
|
|
495
594
|
let url;
|
|
595
|
+
// 1️⃣ Normalize URL string
|
|
496
596
|
if (typeof input === "string") {
|
|
497
597
|
url = input;
|
|
498
598
|
}
|
|
@@ -503,130 +603,103 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
|
|
|
503
603
|
url = input.href;
|
|
504
604
|
}
|
|
505
605
|
else {
|
|
506
|
-
return target.apply(thisArg, args);
|
|
606
|
+
return target.apply(thisArg, args);
|
|
507
607
|
}
|
|
508
|
-
// Determine the target domain
|
|
509
608
|
const domain = new URL(url, window.location.href).hostname;
|
|
510
|
-
//
|
|
511
|
-
if (cache.has(domain)) {
|
|
512
|
-
const decision = cache.get(domain);
|
|
513
|
-
if (decision === ActionType.IGNORE) {
|
|
514
|
-
return target.apply(thisArg, args);
|
|
515
|
-
}
|
|
516
|
-
if (decision === ActionType.PROPAGATE) {
|
|
517
|
-
return injectHeaderWrapper(target, thisArg, args, input, init, sessionId, url);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
// Check exclusion domains and cache 'ignore'
|
|
609
|
+
// 2️⃣ Skip header injection if excluded
|
|
521
610
|
if (shouldSkipHeadersPropagation(url, domainsToNotPropagateHeadersTo)) {
|
|
522
|
-
cache.set(domain, ActionType.IGNORE);
|
|
523
|
-
return target.apply(thisArg, args);
|
|
524
|
-
}
|
|
525
|
-
let decision = ActionType.PROPAGATE;
|
|
526
|
-
// Check if domain verified before
|
|
527
|
-
if (!dynamicPassedHosts.has(domain)) {
|
|
528
|
-
// Perform OPTIONS preflight to decide header propagation
|
|
529
|
-
const res = await performOptionsPreflight(target, thisArg, url, init, sessionId, domain);
|
|
530
|
-
// Skip the header propagation as OPTIONS return Ignore
|
|
531
|
-
if (res === ActionType.IGNORE) {
|
|
532
|
-
decision = res;
|
|
533
|
-
dynamicExcludedHosts.add(domain);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
cache.set(domain, decision);
|
|
537
|
-
if (decision === ActionType.PROPAGATE) {
|
|
538
|
-
return injectHeaderWrapper(target, thisArg, args, input, init, sessionId, url);
|
|
539
|
-
}
|
|
540
|
-
else {
|
|
541
611
|
return target.apply(thisArg, args);
|
|
542
612
|
}
|
|
613
|
+
// 3️⃣ Delegate to our existing wrapper that injects header,
|
|
614
|
+
// handles retries on BAD_HTTP_STATUS, and updates dynamic sets.
|
|
615
|
+
return injectHeaderWrapper(target, thisArg, args, input, init, sessionId, url);
|
|
543
616
|
},
|
|
544
617
|
});
|
|
545
|
-
//
|
|
618
|
+
// 2️⃣ Fetch interceptor’s injectHeaderWrapper(); emits 'networkRequest' event
|
|
546
619
|
async function injectHeaderWrapper(target, thisArg, args, input, init, sessionId, url) {
|
|
547
|
-
if (sessionId) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
const success = response.ok;
|
|
566
|
-
const error = success ? "" : `Request Error: ${response.statusText}`;
|
|
567
|
-
if (success) {
|
|
568
|
-
(isRetry ? dynamicExcludedHosts : dynamicPassedHosts).add(domain);
|
|
569
|
-
}
|
|
570
|
-
// Emit 'networkRequestFinished' event
|
|
571
|
-
const eventData = {
|
|
572
|
-
type: NetworkRequestEventId,
|
|
573
|
-
timestamp: endTime,
|
|
574
|
-
sessionId,
|
|
575
|
-
data: {
|
|
576
|
-
request_id: networkUUID,
|
|
577
|
-
session_id: sessionId,
|
|
578
|
-
timestamp_start: startTime,
|
|
579
|
-
timestamp_end: endTime,
|
|
580
|
-
response_code: status,
|
|
581
|
-
success,
|
|
582
|
-
error,
|
|
583
|
-
method,
|
|
584
|
-
url,
|
|
585
|
-
},
|
|
586
|
-
...urlAndStoredUuids,
|
|
587
|
-
};
|
|
588
|
-
sendEvent(eventData);
|
|
589
|
-
return response;
|
|
620
|
+
if (!sessionId) {
|
|
621
|
+
return target.apply(thisArg, args);
|
|
622
|
+
}
|
|
623
|
+
const urlObj = new URL(url, window.location.href);
|
|
624
|
+
const domain = urlObj.hostname;
|
|
625
|
+
const hostPath = urlObj.pathname === "/" ? domain : `${domain}${urlObj.pathname}`;
|
|
626
|
+
const networkUUID = uuidv4();
|
|
627
|
+
const urlAndStoredUuids = getUrlAndStoredUuids();
|
|
628
|
+
const method = init.method || "GET";
|
|
629
|
+
const startTime = Date.now();
|
|
630
|
+
try {
|
|
631
|
+
let response = await injectHeader(target, thisArg, input, init, sessionId, urlAndStoredUuids.page_visit_uuid, networkUUID);
|
|
632
|
+
let isRetry = false;
|
|
633
|
+
// Retry logic for 400/403 before logging finished event
|
|
634
|
+
if (BAD_HTTP_STATUS.includes(response.status)) {
|
|
635
|
+
DEBUG && console.log("Perform retry as status was fail:", response);
|
|
636
|
+
response = await retryWithoutPropagateHeaders(target, thisArg, args, url);
|
|
637
|
+
isRetry = true;
|
|
590
638
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
// Since some APIs or reverse proxies (such as NGINX) do not route or handle OPTIONS requests, CORS may occur while the request is being made.
|
|
598
|
-
if (error instanceof TypeError ||
|
|
599
|
-
error?.message?.includes(CORS_KEYWORD)) {
|
|
600
|
-
DEBUG &&
|
|
601
|
-
console.log(`[Fetch Interceptor] CORS error for ${domain}:`, error);
|
|
602
|
-
dynamicExcludedHosts.add(domain);
|
|
603
|
-
return target.apply(thisArg, args);
|
|
604
|
-
}
|
|
639
|
+
const endTime = Date.now();
|
|
640
|
+
const status = response.status;
|
|
641
|
+
const success = response.ok;
|
|
642
|
+
const error = success ? "" : `Request Error: ${response.statusText}`;
|
|
643
|
+
// 5️⃣ Update dynamic sets
|
|
644
|
+
if (success) {
|
|
605
645
|
dynamicPassedHosts.add(domain);
|
|
606
|
-
// Emit 'networkRequestFinished' event with error
|
|
607
|
-
const eventData = {
|
|
608
|
-
type: NetworkRequestEventId,
|
|
609
|
-
timestamp: endTime,
|
|
610
|
-
sessionId,
|
|
611
|
-
data: {
|
|
612
|
-
request_id: networkUUID,
|
|
613
|
-
session_id: sessionId,
|
|
614
|
-
timestamp_start: startTime,
|
|
615
|
-
timestamp_end: endTime,
|
|
616
|
-
response_code: responseCode,
|
|
617
|
-
success,
|
|
618
|
-
error: errorMessage,
|
|
619
|
-
method,
|
|
620
|
-
url,
|
|
621
|
-
},
|
|
622
|
-
...urlAndStoredUuids,
|
|
623
|
-
};
|
|
624
|
-
sendEvent(eventData);
|
|
625
|
-
throw error;
|
|
626
646
|
}
|
|
647
|
+
else {
|
|
648
|
+
dynamicExcludedHosts.add(hostPath);
|
|
649
|
+
}
|
|
650
|
+
// Emit 'networkRequestFinished' event
|
|
651
|
+
const eventData = {
|
|
652
|
+
type: NetworkRequestEventId,
|
|
653
|
+
timestamp: endTime,
|
|
654
|
+
sessionId,
|
|
655
|
+
data: {
|
|
656
|
+
request_id: networkUUID,
|
|
657
|
+
session_id: sessionId,
|
|
658
|
+
timestamp_start: startTime,
|
|
659
|
+
timestamp_end: endTime,
|
|
660
|
+
response_code: status,
|
|
661
|
+
success,
|
|
662
|
+
error,
|
|
663
|
+
method,
|
|
664
|
+
url,
|
|
665
|
+
},
|
|
666
|
+
...urlAndStoredUuids,
|
|
667
|
+
};
|
|
668
|
+
sendEvent(eventData);
|
|
669
|
+
return response;
|
|
627
670
|
}
|
|
628
|
-
|
|
629
|
-
|
|
671
|
+
catch (error) {
|
|
672
|
+
const endTime = Date.now();
|
|
673
|
+
const success = false;
|
|
674
|
+
const responseCode = error.response?.status || 500;
|
|
675
|
+
const errorMessage = error.message || "Fetch request failed";
|
|
676
|
+
if (error instanceof TypeError &&
|
|
677
|
+
error?.message?.toLowerCase().includes(CORS_KEYWORD.toLowerCase())) {
|
|
678
|
+
// CORS/network failure: exclude just this path
|
|
679
|
+
dynamicExcludedHosts.add(hostPath);
|
|
680
|
+
return target.apply(thisArg, args);
|
|
681
|
+
}
|
|
682
|
+
// On other errors, treat as “passed” so we don’t re-preflight
|
|
683
|
+
dynamicPassedHosts.add(domain);
|
|
684
|
+
const eventData = {
|
|
685
|
+
type: NetworkRequestEventId,
|
|
686
|
+
timestamp: endTime,
|
|
687
|
+
sessionId,
|
|
688
|
+
data: {
|
|
689
|
+
request_id: networkUUID,
|
|
690
|
+
session_id: sessionId,
|
|
691
|
+
timestamp_start: startTime,
|
|
692
|
+
timestamp_end: endTime,
|
|
693
|
+
response_code: responseCode,
|
|
694
|
+
success,
|
|
695
|
+
error: errorMessage,
|
|
696
|
+
method,
|
|
697
|
+
url,
|
|
698
|
+
},
|
|
699
|
+
...urlAndStoredUuids,
|
|
700
|
+
};
|
|
701
|
+
sendEvent(eventData);
|
|
702
|
+
throw error;
|
|
630
703
|
}
|
|
631
704
|
}
|
|
632
705
|
// Helper function to inject the X-Sf3-Rid header
|
|
@@ -654,23 +727,44 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
|
|
|
654
727
|
async function retryWithoutPropagateHeaders(target, thisArg, args, url) {
|
|
655
728
|
const domain = new URL(url).hostname;
|
|
656
729
|
try {
|
|
657
|
-
//
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
730
|
+
// **Fix:** Properly await and clone the request without the tracing header
|
|
731
|
+
let input = args[0];
|
|
732
|
+
let init = args[1] || {};
|
|
733
|
+
if (typeof input === "string" || input instanceof URL) {
|
|
734
|
+
const retryInit = { ...init };
|
|
735
|
+
const retryHeaders = new Headers(init.headers || {});
|
|
736
|
+
retryHeaders.delete(xSf3RidHeader);
|
|
737
|
+
retryInit.headers = retryHeaders;
|
|
738
|
+
const response = await target.call(thisArg, input, retryInit);
|
|
739
|
+
if (response.ok || !BAD_HTTP_STATUS.includes(response.status)) {
|
|
740
|
+
dynamicExcludedHosts.add(domain);
|
|
741
|
+
cachedDomains.set(domain, ActionType.IGNORE);
|
|
742
|
+
DEBUG &&
|
|
743
|
+
console.info(`Retried request to ${url} without ${xSf3RidHeader} succeeded. Added "${domain}" to exclusion list.`);
|
|
744
|
+
}
|
|
745
|
+
return response;
|
|
746
|
+
}
|
|
747
|
+
else if (input instanceof Request) {
|
|
748
|
+
const original = input;
|
|
749
|
+
const cloned = original.clone();
|
|
750
|
+
const retryHeaders = new Headers(cloned.headers);
|
|
751
|
+
retryHeaders.delete(xSf3RidHeader);
|
|
752
|
+
const retryReq = new Request(cloned, { headers: retryHeaders });
|
|
753
|
+
const response = await target.call(thisArg, retryReq, init);
|
|
754
|
+
if (response.ok || !BAD_HTTP_STATUS.includes(response.status)) {
|
|
755
|
+
dynamicExcludedHosts.add(domain);
|
|
756
|
+
cachedDomains.set(domain, ActionType.IGNORE);
|
|
757
|
+
DEBUG &&
|
|
758
|
+
console.info(`Retried request to ${url} without ${xSf3RidHeader} succeeded. Added "${domain}" to exclusion list.`);
|
|
759
|
+
}
|
|
760
|
+
return response;
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
// Fallback
|
|
764
|
+
return target.apply(thisArg, args);
|
|
668
765
|
}
|
|
669
|
-
// Return the response from the retry attempt (successful or not)
|
|
670
|
-
return response;
|
|
671
766
|
}
|
|
672
767
|
catch (retryError) {
|
|
673
|
-
// Propagate the failure (no domain added to exclude lists since retry failed)
|
|
674
768
|
DEBUG &&
|
|
675
769
|
console.log(`Retry without ${xSf3RidHeader} for ${url} also failed:`, retryError);
|
|
676
770
|
throw retryError;
|
|
@@ -684,27 +778,29 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
|
|
|
684
778
|
initializeConsolePlugin(DEFAULT_CONSOLE_RECORDING_SETTINGS, sessionId);
|
|
685
779
|
storeCredentialsAndConnection({ apiKey, backendApi });
|
|
686
780
|
trackDomainChanges();
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
|
|
781
|
+
// ─── Merge stored excludes + passed-in excludes ───
|
|
782
|
+
const initialExcludes = new Set(dynamicExcludedHosts);
|
|
783
|
+
domainsToNotPropagateHeaderTo.forEach((host) => {
|
|
784
|
+
if (host?.trim()) {
|
|
785
|
+
dynamicExcludedHosts.add(host.trim());
|
|
786
|
+
}
|
|
690
787
|
});
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
788
|
+
const newlyExcluded = Array.from(dynamicExcludedHosts).filter((h) => !initialExcludes.has(h));
|
|
789
|
+
if (newlyExcluded.length) {
|
|
790
|
+
// single notify of the full updated list
|
|
791
|
+
updateExcludedHostsStorageAndBackend(dynamicExcludedHosts);
|
|
792
|
+
}
|
|
793
|
+
// ─── Merge passed hosts ───
|
|
694
794
|
domainsToPropagateHeaderTo.forEach((host) => {
|
|
695
|
-
|
|
795
|
+
if (host?.trim()) {
|
|
796
|
+
dynamicPassedHosts.add(host.trim());
|
|
797
|
+
}
|
|
696
798
|
});
|
|
697
|
-
// Persist updated included hosts to localStorage
|
|
698
|
-
localStorage.setItem(DYNAMIC_PASSED_HOSTS_KEY, JSON.stringify(Array.from(dynamicPassedHosts)));
|
|
699
|
-
// Non-blocking GraphQL request to send the domains if provided
|
|
700
|
-
if (dynamicExcludedHosts.size > 0) {
|
|
701
|
-
sendDomainsToNotPropagateHeaderTo(apiKey, [...dynamicExcludedHosts, ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT], backendApi).catch((error) => console.error("Failed to send domains to not propagate header to:", error));
|
|
702
|
-
}
|
|
703
799
|
sessionStorage.setItem(SF_API_KEY_FOR_UPDATE, apiKey);
|
|
704
800
|
sessionStorage.setItem(SF_BACKEND_API, backendApi);
|
|
705
801
|
// Setup interceptors with custom ignore and propagate domains
|
|
706
|
-
setupXMLHttpRequestInterceptor(
|
|
707
|
-
setupFetchInterceptor(
|
|
802
|
+
setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo);
|
|
803
|
+
setupFetchInterceptor(domainsToNotPropagateHeaderTo);
|
|
708
804
|
gatherAndCacheDeviceInfo();
|
|
709
805
|
try {
|
|
710
806
|
const captureSettingsResponse = await fetchCaptureSettings(apiKey, backendApi);
|