@sailfish-ai/recorder 1.7.41 → 1.7.46

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 CHANGED
@@ -1,6 +1,7 @@
1
1
  export const DomContentEventId = 24;
2
2
  export const NetworkRequestEventId = 27;
3
3
  export const xSf3RidHeader = "X-Sf3-Rid";
4
+ // Values for DomContentLoadedEvent
4
5
  export const DomContentSource = {
5
6
  loading: 0,
6
7
  contentLoaded: 1,
@@ -8,6 +9,7 @@ export const DomContentSource = {
8
9
  beforeunload: 3,
9
10
  unload: 4,
10
11
  };
12
+ // Values for NetworkRequestEvent
11
13
  export const Loading = "loading";
12
14
  export const Complete = "complete";
13
15
  export const STATIC_EXTENSIONS = [
@@ -1,4 +1,5 @@
1
1
  const DEBUG = import.meta.env.VITE_DEBUG ? import.meta.env.VITE_DEBUG : false;
2
+ // A wrapper around fetch that suppresses connection refused errors
2
3
  export function silentFetch(input, init) {
3
4
  return new Promise((resolve, reject) => {
4
5
  fetch(input, init)
@@ -1,4 +1,21 @@
1
1
  import { createTriageFromRecorder } from "./graphql";
2
+ // TODO - enable configuration by keyboard typing in UI
3
+ const DEFAULT_SHORTCUTS = {
4
+ enabled: false,
5
+ openModalExistingMode: { key: "e", requireCmdCtrl: false },
6
+ openModalCaptureNewMode: { key: "n", requireCmdCtrl: false },
7
+ closeModal: { key: "escape", requireCmdCtrl: false },
8
+ submitReport: { key: "enter", requireCmdCtrl: true },
9
+ startRecording: { key: "r", requireCmdCtrl: false },
10
+ stopRecording: { key: "escape", requireCmdCtrl: true },
11
+ };
12
+ export const ReportIssueContext = {
13
+ shortcuts: { ...DEFAULT_SHORTCUTS },
14
+ resolveSessionId: null,
15
+ apiKey: null,
16
+ backendApi: null,
17
+ triageBaseUrl: "https://app.sailfishqa.com",
18
+ };
2
19
  let modalEl = null;
3
20
  let currentState = {
4
21
  mode: "lookback",
@@ -9,12 +26,6 @@ let recordingStartTime = null;
9
26
  let recordingEndTime = null;
10
27
  let timerInterval = null;
11
28
  let isRecording = false;
12
- export const ReportIssueContext = {
13
- resolveSessionId: null,
14
- apiKey: null,
15
- backendApi: null,
16
- triageBaseUrl: "https://app.sailfishqa.com",
17
- };
18
29
  function isMacPlatform() {
19
30
  // Newer API (Chrome, Edge, Opera)
20
31
  const uaData = navigator.userAgentData;
@@ -33,62 +44,118 @@ function isTypingInInput() {
33
44
  el instanceof HTMLTextAreaElement ||
34
45
  (el instanceof HTMLElement && el.isContentEditable));
35
46
  }
47
+ function formatShortcutKeyLabel(key) {
48
+ const map = {
49
+ escape: "esc",
50
+ };
51
+ return (map[key.toLowerCase()] || key).toUpperCase();
52
+ }
53
+ function getShortcutLabel(config) {
54
+ const parts = [];
55
+ if (config.requireCmdCtrl) {
56
+ parts.push(getShortcutKeyCmdCtrlLabel());
57
+ }
58
+ parts.push(formatShortcutKeyLabel(config.key));
59
+ return parts
60
+ .map((p) => `<span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; padding: 0 4px; font-weight: 500; font-size: 12px; color: #94A3B8; line-height: 16px;">${p}</span>`)
61
+ .join(config.requireCmdCtrl ? " + " : "");
62
+ }
63
+ function mergeShortcutsConfig(userConfig) {
64
+ const merged = { ...DEFAULT_SHORTCUTS };
65
+ if (!userConfig)
66
+ return merged;
67
+ // Handle boolean key separately
68
+ if (typeof userConfig.enabled === "boolean") {
69
+ merged.enabled = userConfig.enabled;
70
+ }
71
+ // Handle object keys explicitly
72
+ const objectKeys = [
73
+ "openModalExistingMode",
74
+ "openModalCaptureNewMode",
75
+ "closeModal",
76
+ "submitReport",
77
+ "startRecording",
78
+ "stopRecording",
79
+ ];
80
+ for (const k of objectKeys) {
81
+ const val = userConfig[k];
82
+ if (val && typeof val === "object") {
83
+ merged[k] = { ...merged[k], ...val };
84
+ merged[k].key = merged[k].key.toLowerCase();
85
+ }
86
+ }
87
+ return merged;
88
+ }
89
+ function getShortcutLabelFromContext(shortcutKey) {
90
+ const config = ReportIssueContext.shortcuts[shortcutKey];
91
+ return getShortcutLabel(config);
92
+ }
36
93
  export function setupIssueReporting(options) {
37
94
  ReportIssueContext.apiKey = options.apiKey;
38
95
  ReportIssueContext.backendApi = options.backendApi;
39
96
  ReportIssueContext.resolveSessionId = options.getSessionId;
40
- // Attach keyboard shortcuts
97
+ ReportIssueContext.shortcuts = mergeShortcutsConfig(options.shortcuts);
98
+ const { shortcuts } = ReportIssueContext;
41
99
  window.addEventListener("keydown", (e) => {
42
100
  const typingInInput = isTypingInInput();
43
101
  const key = e.key.toLowerCase();
44
102
  const isCmdOrCtrl = e.metaKey || e.ctrlKey;
45
- // Shortcuts for modal open
46
- if (!typingInInput && options.enableShortcuts && !isCmdOrCtrl) {
47
- if (key === "e") {
48
- e.preventDefault();
49
- injectModalHTML("lookback");
50
- return;
51
- }
52
- if (key === "n") {
53
- e.preventDefault();
54
- injectModalHTML("startnow");
55
- return;
103
+ const modalOpen = !!document.getElementById("sf-report-issue-modal");
104
+ const enableModeShortcuts = !typingInInput && (shortcuts.enabled || modalOpen);
105
+ const shortcutUsed = (shortcutKey) => key === shortcuts[shortcutKey].key &&
106
+ isCmdOrCtrl === shortcuts[shortcutKey].requireCmdCtrl;
107
+ const openModal = modalOpen
108
+ ? (mode) => {
109
+ setActiveTab(mode);
110
+ updateModeSpecificUI(mode);
56
111
  }
112
+ : injectModalHTML;
113
+ // --- Open modal OR switch mode ---
114
+ if (enableModeShortcuts && shortcutUsed("openModalExistingMode")) {
115
+ e.preventDefault();
116
+ openModal("lookback");
117
+ return;
57
118
  }
58
- // Inside modal actions
59
- const modalOpen = !!document.getElementById("sf-report-issue-modal");
60
- if (!modalOpen && !isRecording)
119
+ if (enableModeShortcuts && shortcutUsed("openModalCaptureNewMode")) {
120
+ e.preventDefault();
121
+ openModal("startnow");
61
122
  return;
62
- // Escape key → close modal (only if not recording and modal is open)
63
- if (!isCmdOrCtrl && key === "escape" && modalOpen && !isRecording) {
123
+ }
124
+ // --- Close modal ---
125
+ if (modalOpen && !isRecording && shortcutUsed("closeModal")) {
64
126
  e.preventDefault();
65
127
  closeModal();
66
128
  return;
67
129
  }
68
- // Cmd/Ctrl + Enter → submit
69
- if (isCmdOrCtrl && key === "enter" && modalOpen) {
130
+ // --- Submit report ---
131
+ if (modalOpen && shortcutUsed("submitReport")) {
70
132
  const submitBtn = document.getElementById("sf-issue-submit-btn");
71
133
  if (submitBtn && !submitBtn.disabled) {
72
134
  e.preventDefault();
73
135
  submitBtn.click();
74
136
  }
137
+ return;
75
138
  }
76
- // Cmd/Ctrl + Esc → stop recording
77
- if (isCmdOrCtrl && key === "escape" && isRecording) {
139
+ // --- Stop recording ---
140
+ if (isRecording &&
141
+ key === shortcuts.stopRecording.key &&
142
+ isCmdOrCtrl === shortcuts.stopRecording.requireCmdCtrl) {
78
143
  e.preventDefault();
79
144
  stopRecording();
145
+ return;
80
146
  }
81
- // "r" to start recording (only in capture-new mode)
82
- if (!typingInInput &&
83
- !isCmdOrCtrl &&
84
- key === "r" &&
85
- modalOpen &&
86
- currentState.mode === "startnow") {
147
+ // --- Start recording ---
148
+ if (modalOpen &&
149
+ currentState.mode === "startnow" &&
150
+ key === shortcuts.startRecording.key &&
151
+ isCmdOrCtrl === shortcuts.startRecording.requireCmdCtrl &&
152
+ !typingInInput) {
87
153
  const recordBtn = document.getElementById("sf-start-recording-btn");
88
154
  if (recordBtn) {
89
155
  e.preventDefault();
90
156
  recordBtn.click();
91
157
  }
158
+ return;
92
159
  }
93
160
  });
94
161
  }
@@ -159,11 +226,11 @@ function injectModalHTML(initialMode = "lookback") {
159
226
  <div id="sf-issue-tabs" style="display:flex; gap:4px; margin-bottom:16px; background:#f1f5f9; padding:6px; border-radius:6px; width: fit-content;">
160
227
  <button id="sf-tab-lookback" data-mode="lookback" class="sf-issue-tab ${!isStartNow ? "active" : ""}"
161
228
  style="padding:6px 12px; border:none; background:${!isStartNow ? "white" : "transparent"}; color: ${!isStartNow ? "#0F172A" : "#64748B"}; border-radius:4px; font-size:14px; cursor:pointer; font-weight:500; height:32px; display: flex; align-items: center; gap: 8px;">
162
- Existing <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 16px; height: 16px; font-size: 12px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">E</span>
229
+ Existing ${getShortcutLabelFromContext("openModalExistingMode")}
163
230
  </button>
164
231
  <button id="sf-tab-startnow" data-mode="startnow" class="sf-issue-tab ${isStartNow ? "active" : ""}"
165
232
  style="padding:6px 12px; border:none; background:${isStartNow ? "white" : "transparent"}; color: ${isStartNow ? "#0F172A" : "#64748B"}; border-radius:4px; font-size:14px; cursor:pointer; font-weight:500; height:32px; display: flex; align-items: center; gap: 8px;">
166
- Capture new <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 16px; height: 16px; font-size: 12px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">N</span>
233
+ Capture new ${getShortcutLabelFromContext("openModalCaptureNewMode")}
167
234
  </button>
168
235
  </div>
169
236
 
@@ -189,7 +256,7 @@ function injectModalHTML(initialMode = "lookback") {
189
256
  <textarea id="sf-issue-description" placeholder="Add description here"
190
257
  style="width:100%; height:80px; padding:8px 12px; font-size:14px;
191
258
  border:1px solid #cbd5e1; border-radius:6px; margin-bottom:20px;
192
- resize:none; outline:none;"></textarea>
259
+ resize:none; outline:none;">${currentState.description}</textarea>
193
260
 
194
261
  <div id="sf-lookback-container" style="display:${isStartNow ? "none" : "block"}; margin-bottom:20px;">
195
262
  <label for="sf-lookback-minutes" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
@@ -261,7 +328,7 @@ function injectModalHTML(initialMode = "lookback") {
261
328
  <div style="width: 14px; height: 14px; background: #fc5555; border-radius: 50%; border: 1px solid #991b1b;"></div>
262
329
  </div>
263
330
  <span>Start Recording</span>
264
- <span style="background: #F1F5F9; border:1px solid #cbd5e1; border-radius: 4px; width: 16px; height: 16px; font-size: 12px; display: flex; align-items: center; justify-content: center; color: #94A3B8; font-weight: 500;">R</span>
331
+ ${getShortcutLabelFromContext("startRecording")}
265
332
  </button>
266
333
  </div>
267
334
 
@@ -270,9 +337,7 @@ function injectModalHTML(initialMode = "lookback") {
270
337
  border-radius:6px; font-size:14px; line-height: 24px; font-weight:500;
271
338
  cursor:${isStartNow ? "not-allowed" : "pointer"}; opacity:${isStartNow ? "0.4" : "1"}; margin-bottom:1px" ${isStartNow ? "disabled" : ""}>
272
339
  Report <span style="margin-left: 8px; display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 12px; line-height:16px;">
273
- <span style="background: #F1F5F9; border-radius: 4px; padding: 0 4px; font-weight: 500;">${getShortcutKeyCmdCtrlLabel()}</span>
274
- +
275
- <span style="background: #F1F5F9; border-radius: 4px; padding: 0 4px; font-weight: 500;">ENTER</span>
340
+ ${getShortcutLabelFromContext("submitReport")}
276
341
  </span>
277
342
  </button>
278
343
  </div>
@@ -487,9 +552,7 @@ function showFloatingTimer() {
487
552
  <div style="display:flex; align-items:center; gap:4px; margin-left:auto; font-size:14px; color:#0F172A;">
488
553
  <span style="color: #71717A;">stop</span>
489
554
  <span style="display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 12px; line-height:16px;">
490
- <span style="background: #F1F5F9; border-radius: 4px; border:1px solid #cbd5e1; padding: 0 4px; font-weight: 500;">${getShortcutKeyCmdCtrlLabel()}</span>
491
- +
492
- <span style="background: #F1F5F9; border-radius: 4px; border:1px solid #cbd5e1; padding: 0 4px; font-weight: 500;">ESC</span>
555
+ ${getShortcutLabelFromContext("stopRecording")}
493
556
  </span>
494
557
  </div>
495
558
  `;
package/dist/index.js CHANGED
@@ -323,6 +323,7 @@ function trackDomainChanges() {
323
323
  timestamp,
324
324
  page_visit_uuid: pageVisitUUID,
325
325
  prev_page_visit_uuid: prevPageVisitUUID,
326
+ session_id: getOrSetSessionId(),
326
327
  },
327
328
  });
328
329
  }
@@ -422,22 +423,46 @@ function storeCredentialsAndConnection({ apiKey, backendApi, }) {
422
423
  sessionStorage.setItem("sailfishBackendApi", backendApi);
423
424
  }
424
425
  // Utility function to match domains or paths with wildcard support
425
- export function matchUrlWithWildcard(url, patterns) {
426
- if (!url || typeof url !== "string") {
427
- throw new Error("Invalid URL input");
428
- }
429
- // Ensure the URL has a protocol. If not, prepend "http://"
430
- const formattedUrl = url.match(/^[a-zA-Z]+:\/\//) ? url : `http://${url}`;
431
- const strippedUrl = formattedUrl.replace(/^[a-zA-Z]+:\/\//, "");
432
- const parsedUrl = new URL("http://" + strippedUrl); // Add a dummy protocol for URL parsing
433
- const { hostname, pathname, port } = parsedUrl;
426
+ export function matchUrlWithWildcard(input, patterns) {
427
+ // Tolerate non-string inputs (Request, URL, etc.); coerce to string when possible.
428
+ let urlStr;
429
+ if (typeof input === "string") {
430
+ urlStr = input;
431
+ }
432
+ else if (typeof URL !== "undefined" && input instanceof URL) {
433
+ urlStr = input.href;
434
+ }
435
+ else if (typeof Request !== "undefined" && input instanceof Request) {
436
+ urlStr = input.url;
437
+ }
438
+ else if (input != null && typeof input.toString === "function") {
439
+ // As per web APIs, fetch/open may accept any object with a stringifier.
440
+ urlStr = input.toString();
441
+ }
442
+ if (!urlStr)
443
+ return false;
444
+ // Use WHATWG URL parsing with a base for relative URLs.
445
+ let parsed;
446
+ try {
447
+ const base = typeof window !== "undefined"
448
+ ? window.location.href
449
+ : "http://localhost/";
450
+ parsed = new URL(urlStr, base);
451
+ }
452
+ catch {
453
+ return false; // If we can't parse, just say "no match" instead of throwing.
454
+ }
455
+ const { hostname, pathname, port, protocol } = parsed;
456
+ // Only match http(s) hosts/paths; ignore others like data:, blob:, about:.
457
+ if (!/^https?:$/.test(protocol))
458
+ return false;
434
459
  // Handle stripping 'www.' and port
435
460
  const domain = hostname.startsWith("www.")
436
461
  ? hostname.slice(4).toLowerCase()
437
462
  : hostname.toLowerCase();
438
463
  return patterns.some((pattern) => {
439
464
  // Strip any protocol and handle port in the pattern if present
440
- const strippedPattern = pattern.replace(/^[a-zA-Z]+:\/\//, "");
465
+ const strippedPattern = String(pattern || "").replace(/^[a-zA-Z]+:\/\//, "");
441
466
  let [patternDomain, patternPath] = strippedPattern.split("/", 2);
442
467
  // Handle port in pattern
443
468
  let patternPort = "";
@@ -453,14 +478,16 @@ export function matchUrlWithWildcard(url, patterns) {
453
478
  // Strip 'www.' from both the input domain and the pattern domain for comparison
454
479
  const strippedDomain = domain.startsWith("www.") ? domain.slice(4) : domain;
455
480
  // If pattern specifies a port, match the exact port
456
- if (patternPort && port !== patternPort) {
481
+ if (patternPort && port !== patternPort)
457
482
  return false;
458
- }
459
- // Handle subdomain wildcard (*.) to match both base and subdomains
460
- if (patternDomain.startsWith("*.") &&
461
- (domain === patternDomain.slice(2) ||
462
- strippedDomain === patternDomain.slice(2))) {
463
- // Check for path match if the pattern includes a path
483
+ // handle patterns like "*.example.com"
484
+ if (patternDomain.startsWith("*.")) {
485
+ const base = patternDomain.slice(2).toLowerCase();
486
+ const isDomainMatch = domain === base ||
487
+ strippedDomain === base ||
488
+ domain.endsWith("." + base);
489
+ if (!isDomainMatch)
490
+ return false;
464
491
  if (patternPath) {
465
492
  const normalizedPatternPath = patternPath
466
493
  .replace(/\*/g, ".*") // Replace '*' with regex to match any characters
@@ -471,9 +498,8 @@ export function matchUrlWithWildcard(url, patterns) {
471
498
  return true; // Domain matched, no path required
472
499
  }
473
500
  // Check if the domain matches (include check for base domain without www)
474
- if (!domainRegex.test(strippedDomain) && !domainRegex.test(domain)) {
501
+ if (!domainRegex.test(strippedDomain) && !domainRegex.test(domain))
475
502
  return false;
476
- }
477
503
  // If there's a path in the pattern, match the path
478
504
  if (patternPath) {
479
505
  const normalizedPatternPath = patternPath
@@ -487,7 +513,14 @@ export function matchUrlWithWildcard(url, patterns) {
487
513
  });
488
514
  }
489
515
  function shouldSkipHeadersPropagation(url, domainsToNotPropagateHeaderTo = []) {
490
- const urlObj = new URL(url, window.location.href);
516
+ let urlObj;
517
+ try {
518
+ urlObj = new URL(typeof url === "string" ? url : String(url?.url ?? url), window.location.href);
519
+ }
520
+ catch {
521
+ // If we cannot parse, play it safe and do NOT inject headers.
522
+ return true;
523
+ }
491
524
  // 1️⃣ STATIC ASSET EXCLUSIONS (by comprehensive file extension list)
492
525
  for (const ext of STATIC_EXTENSIONS) {
493
526
  if (urlObj.pathname.toLowerCase().endsWith(ext)) {
@@ -503,11 +536,6 @@ function shouldSkipHeadersPropagation(url, domainsToNotPropagateHeaderTo = []) {
503
536
  if (matchUrlWithWildcard(url, combinedPatterns)) {
504
537
  return true;
505
538
  }
506
- // 3️⃣ DYNAMIC-FAILURE EXCLUSION (exact host)
507
- const hostname = urlObj.hostname;
508
- if (dynamicExcludedHosts.has(hostname)) {
509
- return true;
510
- }
511
539
  return false;
512
540
  }
513
541
  // Updated XMLHttpRequest interceptor with domain exclusion
@@ -540,10 +568,6 @@ function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo = []) {
540
568
  if (!url) {
541
569
  return originalSend.apply(this, args);
542
570
  }
543
- // parse host+path for exclusion
544
- const urlObj = new URL(url, window.location.href);
545
- const domain = urlObj.hostname;
546
- const hostPath = urlObj.pathname === "/" ? domain : `${domain}${urlObj.pathname}`;
547
571
  // 1️⃣ Skip injection for excluded domains/paths
548
572
  if (shouldSkipHeadersPropagation(url, domainsToNotPropagateHeaderTo)) {
549
573
  return originalSend.apply(this, args);
@@ -584,15 +608,6 @@ function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo = []) {
584
608
  },
585
609
  ...getUrlAndStoredUuids(),
586
610
  });
587
- // 5️⃣ Update dynamic sets
588
- if (success) {
589
- // once any route passes, skip preflight on entire domain
590
- dynamicPassedHosts.add(domain);
591
- }
592
- else {
593
- // only exclude the specific failing path
594
- dynamicExcludedHosts.add(hostPath);
595
- }
596
611
  };
597
612
  // 6️⃣ On successful load
598
613
  this.addEventListener("load", () => {
@@ -622,7 +637,6 @@ function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo = []) {
622
637
  function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
623
638
  const originalFetch = window.fetch;
624
639
  const sessionId = getOrSetSessionId();
625
- const cachedDomains = new Map();
626
640
  window.fetch = new Proxy(originalFetch, {
627
641
  apply: async (target, thisArg, args) => {
628
642
  let input = args[0];
@@ -656,9 +670,6 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
656
670
  if (!sessionId) {
657
671
  return target.apply(thisArg, args);
658
672
  }
659
- const urlObj = new URL(url, window.location.href);
660
- const domain = urlObj.hostname;
661
- const hostPath = urlObj.pathname === "/" ? domain : `${domain}${urlObj.pathname}`;
662
673
  const networkUUID = uuidv4();
663
674
  const urlAndStoredUuids = getUrlAndStoredUuids();
664
675
  const method = init.method || "GET";
@@ -676,13 +687,6 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
676
687
  const status = response.status;
677
688
  const success = response.ok;
678
689
  const error = success ? "" : `Request Error: ${response.statusText}`;
679
- // 5️⃣ Update dynamic sets
680
- if (success) {
681
- dynamicPassedHosts.add(domain);
682
- }
683
- else {
684
- dynamicExcludedHosts.add(hostPath);
685
- }
686
690
  // Emit 'networkRequestFinished' event
687
691
  const eventData = {
688
692
  type: NetworkRequestEventId,
@@ -698,6 +702,7 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
698
702
  error,
699
703
  method,
700
704
  url,
705
+ retry_without_trace_id: isRetry,
701
706
  },
702
707
  ...urlAndStoredUuids,
703
708
  };
@@ -712,11 +717,8 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
712
717
  if (error instanceof TypeError &&
713
718
  error?.message?.toLowerCase().includes(CORS_KEYWORD.toLowerCase())) {
714
719
  // CORS/network failure: exclude just this path
715
- dynamicExcludedHosts.add(hostPath);
716
720
  return target.apply(thisArg, args);
717
721
  }
718
- // On other errors, treat as “passed” so we don’t re-preflight
719
- dynamicPassedHosts.add(domain);
720
722
  const eventData = {
721
723
  type: NetworkRequestEventId,
722
724
  timestamp: endTime,
@@ -761,7 +763,6 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
761
763
  }
762
764
  // Helper to retry a fetch without the X-Sf3-Rid header if the initial attempt fails due to that header
763
765
  async function retryWithoutPropagateHeaders(target, thisArg, args, url) {
764
- const domain = new URL(url).hostname;
765
766
  try {
766
767
  // **Fix:** Properly await and clone the request without the tracing header
767
768
  let input = args[0];
@@ -772,12 +773,6 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
772
773
  retryHeaders.delete(xSf3RidHeader);
773
774
  retryInit.headers = retryHeaders;
774
775
  const response = await target.call(thisArg, input, retryInit);
775
- if (response.ok || !BAD_HTTP_STATUS.includes(response.status)) {
776
- dynamicExcludedHosts.add(domain);
777
- cachedDomains.set(domain, ActionType.IGNORE);
778
- DEBUG &&
779
- console.info(`Retried request to ${url} without ${xSf3RidHeader} succeeded. Added "${domain}" to exclusion list.`);
780
- }
781
776
  return response;
782
777
  }
783
778
  else if (input instanceof Request) {
@@ -787,12 +782,6 @@ function setupFetchInterceptor(domainsToNotPropagateHeadersTo = []) {
787
782
  retryHeaders.delete(xSf3RidHeader);
788
783
  const retryReq = new Request(cloned, { headers: retryHeaders });
789
784
  const response = await target.call(thisArg, retryReq, init);
790
- if (response.ok || !BAD_HTTP_STATUS.includes(response.status)) {
791
- dynamicExcludedHosts.add(domain);
792
- cachedDomains.set(domain, ActionType.IGNORE);
793
- DEBUG &&
794
- console.info(`Retried request to ${url} without ${xSf3RidHeader} succeeded. Added "${domain}" to exclusion list.`);
795
- }
796
785
  return response;
797
786
  }
798
787
  else {
@@ -865,24 +854,6 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
865
854
  initializeConsolePlugin(DEFAULT_CONSOLE_RECORDING_SETTINGS, sessionId);
866
855
  storeCredentialsAndConnection({ apiKey, backendApi });
867
856
  trackDomainChanges();
868
- // ─── Merge stored excludes + passed-in excludes ───
869
- const initialExcludes = new Set(dynamicExcludedHosts);
870
- domainsToNotPropagateHeaderTo.forEach((host) => {
871
- if (host?.trim()) {
872
- dynamicExcludedHosts.add(host.trim());
873
- }
874
- });
875
- const newlyExcluded = Array.from(dynamicExcludedHosts).filter((h) => !initialExcludes.has(h));
876
- if (newlyExcluded.length) {
877
- // single notify of the full updated list
878
- updateExcludedHostsStorageAndBackend(dynamicExcludedHosts);
879
- }
880
- // ─── Merge passed hosts ───
881
- domainsToPropagateHeaderTo.forEach((host) => {
882
- if (host?.trim()) {
883
- dynamicPassedHosts.add(host.trim());
884
- }
885
- });
886
857
  sessionStorage.setItem(SF_API_KEY_FOR_UPDATE, apiKey);
887
858
  sessionStorage.setItem(SF_BACKEND_API, backendApi);
888
859
  // Setup interceptors with custom ignore and propagate domains
@@ -910,6 +881,7 @@ export async function startRecording({ apiKey, backendApi = "https://api-service
910
881
  }
911
882
  }
912
883
  export const initRecorder = async (options) => {
884
+ console.log("Initializing Sailfish Recorder with options:", options);
913
885
  // Only run on the client (browser) environment
914
886
  if (typeof window === "undefined") {
915
887
  return;
@@ -920,7 +892,7 @@ export const initRecorder = async (options) => {
920
892
  apiKey: options.apiKey,
921
893
  backendApi: options.backendApi ?? "https://api-service.sailfishqa.com",
922
894
  getSessionId: () => getOrSetSessionId(),
923
- enableShortcuts: options.enableShortcuts ?? false,
895
+ shortcuts: options.reportIssueShortcuts,
924
896
  });
925
897
  });
926
898
  };