@sailfish-ai/recorder 1.7.34 → 1.7.41

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.
@@ -1,42 +1,100 @@
1
- import { openDB } from 'idb';
2
- const DB_NAME = 'leapsEventDB';
3
- const STORE_NAME = 'recordingEvents';
4
- const dbPromise = openDB(DB_NAME, 1, {
5
- upgrade(db) {
6
- if (!db.objectStoreNames.contains(STORE_NAME)) {
7
- db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
1
+ // lib/safe-idb.ts
2
+ // SSR/Edge-safe IndexedDB helpers with zero top-level access to `indexedDB`.
3
+ // On the server (or any env without IDB), functions are no-ops / return safe defaults.
4
+ const DB_NAME = "leapsEventDB";
5
+ const STORE_NAME = "recordingEvents";
6
+ const DB_VERSION = 1;
7
+ // Cached open promise; never created on SSR
8
+ let _dbPromise = null;
9
+ // Narrow, safe feature check (no throws in weird runtimes)
10
+ function hasIndexedDB() {
11
+ return typeof globalThis !== "undefined" && !!globalThis.indexedDB;
12
+ }
13
+ // Open the DB lazily and only on the client.
14
+ function openDb() {
15
+ if (!hasIndexedDB())
16
+ return Promise.resolve(null);
17
+ if (_dbPromise)
18
+ return _dbPromise;
19
+ _dbPromise = new Promise((resolve) => {
20
+ try {
21
+ const req = globalThis.indexedDB.open(DB_NAME, DB_VERSION);
22
+ req.onupgradeneeded = () => {
23
+ const db = req.result;
24
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
25
+ db.createObjectStore(STORE_NAME, {
26
+ keyPath: "id",
27
+ autoIncrement: true,
28
+ });
29
+ }
30
+ };
31
+ req.onsuccess = () => resolve(req.result);
32
+ req.onerror = () => resolve(null); // fail closed → treat as unavailable
33
+ req.onblocked = () => {
34
+ // Best effort: resolve null to avoid hanging builds if something odd blocks upgrades
35
+ resolve(null);
36
+ };
37
+ }
38
+ catch {
39
+ // Some exotic environments can still throw on open()
40
+ resolve(null);
8
41
  }
9
- },
10
- });
42
+ });
43
+ return _dbPromise;
44
+ }
45
+ function withStore(mode, fn) {
46
+ return openDb().then((db) => {
47
+ if (!db)
48
+ return null;
49
+ return new Promise((resolve) => {
50
+ try {
51
+ const tx = db.transaction(STORE_NAME, mode);
52
+ const store = tx.objectStore(STORE_NAME);
53
+ Promise.resolve(fn(store))
54
+ .then((result) => {
55
+ tx.oncomplete = () => resolve(result);
56
+ tx.onerror = () => resolve(null);
57
+ // If fn() already did async work, ensure we complete; otherwise completing will call resolve above
58
+ })
59
+ .catch(() => resolve(null));
60
+ }
61
+ catch {
62
+ resolve(null);
63
+ }
64
+ });
65
+ });
66
+ }
67
+ // ── Public API (SSR-safe) ──────────────────────────────────────────────────────
11
68
  export async function saveEventToIDB(event) {
12
- const db = await dbPromise;
13
- const tx = db.transaction(STORE_NAME, 'readwrite');
14
- await tx.store.add({ timestamp: Date.now(), data: event });
15
- await tx.done;
69
+ await withStore("readwrite", (store) => {
70
+ store.add({ timestamp: Date.now(), data: event });
71
+ });
16
72
  }
17
73
  export async function saveEventsToIDB(events) {
18
- const db = await dbPromise;
19
- const tx = db.transaction(STORE_NAME, 'readwrite');
20
- for (const event of events) {
21
- await tx.store.add({ timestamp: Date.now(), data: event });
22
- }
23
- await tx.done;
74
+ await withStore("readwrite", async (store) => {
75
+ for (const event of events) {
76
+ store.add({ timestamp: Date.now(), data: event });
77
+ }
78
+ });
24
79
  }
25
80
  export async function getAllIndexedEvents() {
26
- const db = await dbPromise;
27
- return await db.getAll(STORE_NAME);
81
+ const result = await withStore("readonly", (store) => {
82
+ return new Promise((resolve) => {
83
+ const req = store.getAll();
84
+ req.onsuccess = () => resolve(req.result);
85
+ req.onerror = () => resolve([]);
86
+ });
87
+ });
88
+ return result ?? []; // SSR/Edge → []
28
89
  }
29
90
  export async function deleteEventById(id) {
30
- const db = await dbPromise;
31
- const tx = db.transaction(STORE_NAME, 'readwrite');
32
- await tx.store.delete(id);
33
- await tx.done;
91
+ await withStore("readwrite", (store) => {
92
+ store.delete(id);
93
+ });
34
94
  }
35
95
  export async function deleteEventsByIds(ids) {
36
- const db = await dbPromise;
37
- const tx = db.transaction(STORE_NAME, 'readwrite');
38
- for (const id of ids) {
39
- await tx.store.delete(id);
40
- }
41
- await tx.done;
96
+ await withStore("readwrite", async (store) => {
97
+ for (const id of ids)
98
+ store.delete(id);
99
+ });
42
100
  }
package/dist/graphql.js CHANGED
@@ -44,7 +44,7 @@ export function fetchCaptureSettings(apiKey, backendApi) {
44
44
  export function startRecordingSession(apiKey, recordingId, backendApi, serviceIdentifier, serviceVersion, mapUuid, gitSha, library, serviceAdditionalMetadata) {
45
45
  // These must be sent as None/Null for now (not user-provided)
46
46
  const startRecordingFilePath = null;
47
- const startRecordingFileNumber = null;
47
+ const startRecordingLineNumber = null;
48
48
  return sendGraphQLRequest("StartSession", `mutation StartSession(
49
49
  $apiKey: UUID!,
50
50
  $recordingSessionId: UUID!,
@@ -55,7 +55,7 @@ export function startRecordingSession(apiKey, recordingId, backendApi, serviceId
55
55
  $library: String,
56
56
  $serviceAdditionalMetadata: JSON,
57
57
  $startRecordingFilePath: String,
58
- $startRecordingFileNumber: Int
58
+ $startRecordingLineNumber: Int
59
59
  ) {
60
60
  startRecordingSession(
61
61
  companyApiKey: $apiKey,
@@ -67,7 +67,7 @@ export function startRecordingSession(apiKey, recordingId, backendApi, serviceId
67
67
  library: $library,
68
68
  serviceAdditionalMetadata: $serviceAdditionalMetadata,
69
69
  startRecordingFilePath: $startRecordingFilePath,
70
- startRecordingFileNumber: $startRecordingFileNumber
70
+ startRecordingLineNumber: $startRecordingLineNumber
71
71
  ) {
72
72
  id
73
73
  }
@@ -82,7 +82,7 @@ export function startRecordingSession(apiKey, recordingId, backendApi, serviceId
82
82
  library,
83
83
  serviceAdditionalMetadata,
84
84
  startRecordingFilePath,
85
- startRecordingFileNumber,
85
+ startRecordingLineNumber,
86
86
  });
87
87
  }
88
88
  export function sendDomainsToNotPropagateHeaderTo(apiKey, domains, backendApi) {
@@ -9,10 +9,12 @@ let recordingStartTime = null;
9
9
  let recordingEndTime = null;
10
10
  let timerInterval = null;
11
11
  let isRecording = false;
12
- let resolveSessionId = null;
13
- let apiKey = null;
14
- let backendApi = null;
15
- let triageBaseUrl = "https://app.sailfishqa.com";
12
+ export const ReportIssueContext = {
13
+ resolveSessionId: null,
14
+ apiKey: null,
15
+ backendApi: null,
16
+ triageBaseUrl: "https://app.sailfishqa.com",
17
+ };
16
18
  function isMacPlatform() {
17
19
  // Newer API (Chrome, Edge, Opera)
18
20
  const uaData = navigator.userAgentData;
@@ -32,9 +34,9 @@ function isTypingInInput() {
32
34
  (el instanceof HTMLElement && el.isContentEditable));
33
35
  }
34
36
  export function setupIssueReporting(options) {
35
- apiKey = options.apiKey;
36
- backendApi = options.backendApi;
37
- resolveSessionId = options.getSessionId;
37
+ ReportIssueContext.apiKey = options.apiKey;
38
+ ReportIssueContext.backendApi = options.backendApi;
39
+ ReportIssueContext.resolveSessionId = options.getSessionId;
38
40
  // Attach keyboard shortcuts
39
41
  window.addEventListener("keydown", (e) => {
40
42
  const typingInInput = isTypingInInput();
@@ -91,10 +93,10 @@ export function setupIssueReporting(options) {
91
93
  });
92
94
  }
93
95
  function getSessionIdSafely() {
94
- if (!resolveSessionId) {
96
+ if (!ReportIssueContext.resolveSessionId) {
95
97
  throw new Error("getSessionId not defined");
96
98
  }
97
- return resolveSessionId();
99
+ return ReportIssueContext.resolveSessionId();
98
100
  }
99
101
  export function openReportIssueModal(customBaseUrl) {
100
102
  if (isRecording) {
@@ -105,7 +107,8 @@ export function openReportIssueModal(customBaseUrl) {
105
107
  if (modalEl)
106
108
  document.body.appendChild(modalEl);
107
109
  // Store custom base URL globally for reuse
108
- triageBaseUrl = customBaseUrl ?? "https://app.sailfishqa.com";
110
+ ReportIssueContext.triageBaseUrl =
111
+ customBaseUrl ?? "https://app.sailfishqa.com";
109
112
  }
110
113
  function closeModal() {
111
114
  // ✅ Explicitly blur the focused element
@@ -266,7 +269,7 @@ function injectModalHTML(initialMode = "lookback") {
266
269
  style="background: #295DBF; color:white; border:none; padding:8px 16px;
267
270
  border-radius:6px; font-size:14px; line-height: 24px; font-weight:500;
268
271
  cursor:${isStartNow ? "not-allowed" : "pointer"}; opacity:${isStartNow ? "0.4" : "1"}; margin-bottom:1px" ${isStartNow ? "disabled" : ""}>
269
- Create Triage <span style="margin-left: 8px; display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 12px; line-height:16px;">
272
+ Report <span style="margin-left: 8px; display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 12px; line-height:16px;">
270
273
  <span style="background: #F1F5F9; border-radius: 4px; padding: 0 4px; font-weight: 500;">${getShortcutKeyCmdCtrlLabel()}</span>
271
274
  +
272
275
  <span style="background: #F1F5F9; border-radius: 4px; padding: 0 4px; font-weight: 500;">ENTER</span>
@@ -561,7 +564,7 @@ function reopenModalAfterStop() {
561
564
  async function createTriage(startTimestamp, endTimestamp, description) {
562
565
  try {
563
566
  showTriageStatusModal(true);
564
- const response = await createTriageFromRecorder(apiKey, backendApi, getSessionIdSafely(), startTimestamp, endTimestamp, description);
567
+ const response = await createTriageFromRecorder(ReportIssueContext.apiKey, ReportIssueContext.backendApi, getSessionIdSafely(), startTimestamp, endTimestamp, description);
565
568
  const triageId = response?.data?.createTriageFromRecorder?.id;
566
569
  if (triageId) {
567
570
  showTriageStatusModal(false, triageId);
@@ -578,10 +581,20 @@ async function createTriage(startTimestamp, endTimestamp, description) {
578
581
  }
579
582
  function showTriageStatusModal(isLoading, triageId) {
580
583
  removeExistingModals();
581
- const triageUrl = triageId ? `${triageBaseUrl}/triage/${triageId}` : "";
584
+ const triageUrl = triageId
585
+ ? `${ReportIssueContext.triageBaseUrl}/triage/${triageId}?from=inAppReportIssue`
586
+ : "";
582
587
  const container = document.createElement("div");
583
588
  container.id = "sf-triage-status-modal";
584
- const statusTitle = isLoading ? "Creating Triage..." : "Triage created!";
589
+ Object.assign(container.style, {
590
+ position: "fixed",
591
+ inset: "0",
592
+ zIndex: "9998",
593
+ display: "flex",
594
+ alignItems: "center",
595
+ justifyContent: "center",
596
+ });
597
+ const statusTitle = isLoading ? "Reporting Issue..." : "Issue reported!";
585
598
  const statusSubtitle = isLoading
586
599
  ? `<p style="font-size:14px; color:#64748b; line-height:20px;">This may take ~10 seconds</p>`
587
600
  : "";
@@ -601,10 +614,10 @@ function showTriageStatusModal(isLoading, triageId) {
601
614
  : "";
602
615
  container.innerHTML = `
603
616
  <div style="position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:9998;"></div>
604
- <div style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
605
- background:#fff; padding:24px; border-radius:12px; width:300px; max-width:90%;
606
- z-index:9999; font-family:sans-serif; box-shadow:0 4px 20px rgba(0,0,0,0.15);">
607
-
617
+ <div id="sf-triage-card"
618
+ style="position:relative; background:#fff; padding:24px; border-radius:12px; width:300px; max-width:90%;
619
+ font-family:sans-serif; box-shadow:0 4px 20px rgba(0,0,0,0.15);
620
+ z-index:9999; opacity:1; transition:opacity 300ms ease;">
608
621
  <div style="position:absolute; top:24px; right:48px;">${copiedStatus}</div>
609
622
 
610
623
  <button id="sf-triage-modal-close"
@@ -615,7 +628,7 @@ function showTriageStatusModal(isLoading, triageId) {
615
628
  </svg>
616
629
  </button>
617
630
 
618
- <h2 style="font-size:18px; font-weight:600; margin-bottom:${isLoading ? 8 : 40}px; line-height: 28px;">${statusTitle}</h2>
631
+ <h2 style="font-size:18px; font-weight:600; margin-bottom:${isLoading ? 8 : 40}px; line-height:28px;">${statusTitle}</h2>
619
632
  ${statusSubtitle}
620
633
  ${spinner}
621
634
 
@@ -628,7 +641,7 @@ function showTriageStatusModal(isLoading, triageId) {
628
641
  <button id="sf-view-triage-btn"
629
642
  style="display:flex; align-items:center; gap:8px; background:#295dbf; border:none;
630
643
  padding:8px 16px; border-radius:6px; font-size:14px; color:white; cursor:pointer;">
631
- View Triage
644
+ View
632
645
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
633
646
  <path d="M12 8.66667V12.6667C12 13.0203 11.8595 13.3594 11.6095 13.6095C11.3594 13.8595 11.0203 14 10.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V5.33333C2 4.97971 2.14048 4.64057 2.39052 4.39052C2.64057 4.14048 2.97971 4 3.33333 4H7.33333" stroke="white" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
634
647
  <path d="M10 2H14V6" stroke="white" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
@@ -642,10 +655,12 @@ function showTriageStatusModal(isLoading, triageId) {
642
655
  </style>
643
656
  `;
644
657
  document.body.appendChild(container);
658
+ const card = container.querySelector("#sf-triage-card");
659
+ // Close handler with fade only on card
645
660
  document
646
661
  .getElementById("sf-triage-modal-close")
647
662
  ?.addEventListener("click", () => {
648
- container.remove();
663
+ fadeCardAndRemove(container, card, 300);
649
664
  });
650
665
  const copyBtn = document.getElementById("sf-copy-triage-link");
651
666
  const viewBtn = document.getElementById("sf-view-triage-btn");
@@ -672,8 +687,17 @@ function showTriageStatusModal(isLoading, triageId) {
672
687
  window.open(triageUrl, "_blank");
673
688
  }
674
689
  });
690
+ // Auto-hide after success
691
+ setTimeout(() => fadeCardAndRemove(container, card, 300), 10000);
675
692
  }
676
693
  }
694
+ function fadeCardAndRemove(container, card, durationMs = 300) {
695
+ card.style.opacity = "0"; // fade only the card
696
+ card.addEventListener("transitionend", () => container.remove(), {
697
+ once: true,
698
+ });
699
+ setTimeout(() => container.remove(), durationMs + 100); // fallback
700
+ }
677
701
  function removeExistingModals() {
678
702
  document.getElementById("sf-report-issue-modal")?.remove();
679
703
  document.getElementById("sf-triage-status-modal")?.remove();
package/dist/index.js CHANGED
@@ -1,4 +1,11 @@
1
1
  const DEBUG = import.meta.env.VITE_DEBUG ? import.meta.env.VITE_DEBUG : false;
2
+ // ── SSR guards ─────────────────────────────────────────
3
+ const HAS_WINDOW = typeof globalThis !== "undefined" &&
4
+ typeof globalThis.window !== "undefined";
5
+ const HAS_DOCUMENT = typeof globalThis !== "undefined" &&
6
+ typeof globalThis.document !== "undefined";
7
+ const HAS_LOCAL_STORAGE = typeof globalThis !== "undefined" && "localStorage" in globalThis;
8
+ const HAS_SESSION_STORAGE = typeof globalThis !== "undefined" && "sessionStorage" in globalThis;
2
9
  // import { NetworkRecordOptions } from "@sailfish-rrweb/rrweb-plugin-network-record";
3
10
  import { v4 as uuidv4 } from "uuid";
4
11
  import { NetworkRequestEventId, STATIC_EXTENSIONS, xSf3RidHeader, } from "./constants";
@@ -57,16 +64,18 @@ dynamicExcludedHosts.add = (host) => {
57
64
  }
58
65
  // 4. Persist wrapper to localStorage
59
66
  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;
67
+ if (HAS_LOCAL_STORAGE) {
68
+ // TODO decouple into two logical pieces
69
+ const wrapper = {
70
+ version: STORAGE_VERSION,
71
+ entries: {},
72
+ };
73
+ const now = Date.now();
74
+ for (const p of dynamicExcludedHosts) {
75
+ wrapper.entries[p] = now;
76
+ }
77
+ localStorage.setItem(DYNAMIC_EXCLUDED_HOSTS_KEY, JSON.stringify(wrapper));
68
78
  }
69
- localStorage.setItem(DYNAMIC_EXCLUDED_HOSTS_KEY, JSON.stringify(wrapper));
70
79
  }
71
80
  catch (e) {
72
81
  if (DEBUG)
@@ -85,35 +94,37 @@ dynamicPassedHosts.add = (host) => {
85
94
  }
86
95
  // 1. Persist wrapper to localStorage (update timestamp unconditionally)
87
96
  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;
97
+ if (HAS_LOCAL_STORAGE) {
98
+ // TODO decouple into two logical pieces
99
+ const raw = localStorage.getItem(DYNAMIC_PASSED_HOSTS_KEY);
100
+ let wrapper;
101
+ if (!raw) {
102
+ wrapper = { version: STORAGE_VERSION, entries: {} };
108
103
  }
109
104
  else {
110
- wrapper = { version: STORAGE_VERSION, entries: {} };
105
+ const parsed = JSON.parse(raw);
106
+ if (Array.isArray(parsed)) {
107
+ // migrate old array → wrapper
108
+ wrapper = {
109
+ version: STORAGE_VERSION,
110
+ entries: Object.fromEntries(parsed.map((h) => [h, Date.now()])),
111
+ };
112
+ }
113
+ else if (parsed &&
114
+ typeof parsed === "object" &&
115
+ "entries" in parsed &&
116
+ typeof parsed.entries === "object") {
117
+ wrapper = parsed;
118
+ }
119
+ else {
120
+ wrapper = { version: STORAGE_VERSION, entries: {} };
121
+ }
111
122
  }
123
+ // always set/update the timestamp
124
+ wrapper.entries[cleaned] = Date.now();
125
+ wrapper.version = STORAGE_VERSION;
126
+ localStorage.setItem(DYNAMIC_PASSED_HOSTS_KEY, JSON.stringify(wrapper));
112
127
  }
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
128
  }
118
129
  catch (e) {
119
130
  if (DEBUG)
@@ -129,6 +140,8 @@ dynamicPassedHosts.add = (host) => {
129
140
  * Notify the backend of the updated dynamicExcludedHosts
130
141
  */
131
142
  function updateExcludedHostsStorageAndBackend(dynamicExcludedHosts) {
143
+ if (!HAS_SESSION_STORAGE)
144
+ return;
132
145
  const apiKeyForUpdate = sessionStorage.getItem(SF_API_KEY_FOR_UPDATE) || "";
133
146
  const apiForUpdate = sessionStorage.getItem(SF_BACKEND_API) || "";
134
147
  if (!apiForUpdate) {
@@ -190,60 +203,64 @@ export function consolidateDynamicExclusions(hostPathSet, threshold = 3) {
190
203
  return newSet;
191
204
  }
192
205
  // 2️⃣ Load & evict old entries (>7 days) + version check for Excluded
193
- (() => {
194
- const stored = localStorage.getItem(DYNAMIC_EXCLUDED_HOSTS_KEY);
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) {
201
- localStorage.removeItem(DYNAMIC_EXCLUDED_HOSTS_KEY);
206
+ if (HAS_LOCAL_STORAGE) {
207
+ (() => {
208
+ const stored = localStorage.getItem(DYNAMIC_EXCLUDED_HOSTS_KEY);
209
+ if (!stored)
202
210
  return;
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;
211
+ try {
212
+ const wrapper = JSON.parse(stored);
213
+ // if it's from an old version, drop it
214
+ if (wrapper.version !== STORAGE_VERSION) {
215
+ localStorage.removeItem(DYNAMIC_EXCLUDED_HOSTS_KEY);
216
+ return;
210
217
  }
218
+ const now = Date.now();
219
+ const valid = {};
220
+ for (const [host, ts] of Object.entries(wrapper.entries)) {
221
+ if (now - ts < 7 * 24 * 60 * 60 * 1000) {
222
+ dynamicExcludedHosts.add(host);
223
+ valid[host] = ts;
224
+ }
225
+ }
226
+ localStorage.setItem(DYNAMIC_EXCLUDED_HOSTS_KEY, JSON.stringify({ version: STORAGE_VERSION, entries: valid }));
211
227
  }
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);
218
- }
219
- })();
228
+ catch (e) {
229
+ if (DEBUG)
230
+ console.warn("Failed to parse dynamicExcludedHosts:", e);
231
+ localStorage.removeItem(DYNAMIC_EXCLUDED_HOSTS_KEY);
232
+ }
233
+ })();
234
+ }
220
235
  // 3️⃣ Load & evict old entries (>7 days) + version check for Passed
221
- (() => {
222
- const stored = localStorage.getItem(DYNAMIC_PASSED_HOSTS_KEY);
223
- if (!stored)
224
- return;
225
- try {
226
- const wrapper = JSON.parse(stored);
227
- if (wrapper.version !== STORAGE_VERSION) {
228
- localStorage.removeItem(DYNAMIC_PASSED_HOSTS_KEY);
236
+ if (HAS_LOCAL_STORAGE) {
237
+ (() => {
238
+ const stored = localStorage.getItem(DYNAMIC_PASSED_HOSTS_KEY);
239
+ if (!stored)
229
240
  return;
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;
241
+ try {
242
+ const wrapper = JSON.parse(stored);
243
+ if (wrapper.version !== STORAGE_VERSION) {
244
+ localStorage.removeItem(DYNAMIC_PASSED_HOSTS_KEY);
245
+ return;
246
+ }
247
+ const now = Date.now();
248
+ const valid = {};
249
+ for (const [host, ts] of Object.entries(wrapper.entries)) {
250
+ if (now - ts < 7 * 24 * 60 * 60 * 1000) {
251
+ dynamicPassedHosts.add(host);
252
+ valid[host] = ts;
253
+ }
237
254
  }
255
+ localStorage.setItem(DYNAMIC_PASSED_HOSTS_KEY, JSON.stringify({ version: STORAGE_VERSION, entries: valid }));
238
256
  }
239
- localStorage.setItem(DYNAMIC_PASSED_HOSTS_KEY, JSON.stringify({ version: STORAGE_VERSION, entries: valid }));
240
- }
241
- catch (e) {
242
- if (DEBUG)
243
- console.warn("Failed to parse dynamicPassedHosts:", e);
244
- localStorage.removeItem(DYNAMIC_PASSED_HOSTS_KEY);
245
- }
246
- })();
257
+ catch (e) {
258
+ if (DEBUG)
259
+ console.warn("Failed to parse dynamicPassedHosts:", e);
260
+ localStorage.removeItem(DYNAMIC_PASSED_HOSTS_KEY);
261
+ }
262
+ })();
263
+ }
247
264
  const ActionType = {
248
265
  PROPAGATE: "propagate",
249
266
  IGNORE: "ignore",
@@ -266,7 +283,7 @@ export const DEFAULT_CONSOLE_RECORDING_SETTINGS = {
266
283
  stringifyOptions: {
267
284
  stringLengthLimit: 1000,
268
285
  numOfKeysLimit: 20,
269
- depthOfLimit: 1,
286
+ depthOfLimit: 4,
270
287
  },
271
288
  logger: "console",
272
289
  };
@@ -342,15 +359,25 @@ function sendTimeZone() {
342
359
  sendMessage(message);
343
360
  }
344
361
  // Send standard information like userDeviceUuid and timeZone
345
- sendUserDeviceUuid();
346
- sendTimeZone();
362
+ if (HAS_WINDOW) {
363
+ sendUserDeviceUuid();
364
+ sendTimeZone();
365
+ }
347
366
  // Function to get or set the device & program UUID in localStorage
348
367
  function getOrSetUserDeviceUuid() {
349
- let userDeviceUuid = localStorage.getItem("sailfishUserDeviceUuid");
368
+ let userDeviceUuid = null;
369
+ if (HAS_LOCAL_STORAGE) {
370
+ try {
371
+ userDeviceUuid = localStorage.getItem("sailfishUserDeviceUuid");
372
+ }
373
+ catch { }
374
+ }
350
375
  if (!userDeviceUuid) {
351
376
  userDeviceUuid = uuidv4();
352
377
  try {
353
- localStorage.setItem("sailfishUserDeviceUuid", userDeviceUuid);
378
+ if (HAS_LOCAL_STORAGE) {
379
+ localStorage.setItem("sailfishUserDeviceUuid", userDeviceUuid);
380
+ }
354
381
  }
355
382
  catch { }
356
383
  }
@@ -359,6 +386,8 @@ function getOrSetUserDeviceUuid() {
359
386
  // Storing the sailfishSessionId in window.name, as window.name retains its value after a page refresh
360
387
  // but resets when a new tab (including a duplicated tab) is opened.
361
388
  function getOrSetSessionId() {
389
+ if (!HAS_WINDOW)
390
+ return uuidv4();
362
391
  if (!window.name) {
363
392
  window.name = uuidv4();
364
393
  }
@@ -371,17 +400,24 @@ function handleVisibilityChange() {
371
400
  }
372
401
  }
373
402
  function clearPageVisitUuid() {
403
+ if (!HAS_SESSION_STORAGE)
404
+ return;
374
405
  sessionStorage.removeItem("pageVisitUUID");
375
406
  sessionStorage.removeItem("prevPageVisitUUID");
376
407
  }
377
408
  // Initialize event listeners for visibility change and page unload
378
- document.addEventListener("visibilitychange", handleVisibilityChange);
379
- // Clearing window.name on refresh to generate and retain a new sessionId
380
- window.addEventListener("beforeunload", () => {
381
- window.name = "";
382
- clearPageVisitUuid();
383
- });
409
+ if (HAS_DOCUMENT) {
410
+ document.addEventListener("visibilitychange", handleVisibilityChange);
411
+ }
412
+ if (HAS_WINDOW) {
413
+ window.addEventListener("beforeunload", () => {
414
+ window.name = "";
415
+ clearPageVisitUuid();
416
+ });
417
+ }
384
418
  function storeCredentialsAndConnection({ apiKey, backendApi, }) {
419
+ if (!HAS_SESSION_STORAGE)
420
+ return;
385
421
  sessionStorage.setItem("sailfishApiKey", apiKey);
386
422
  sessionStorage.setItem("sailfishBackendApi", backendApi);
387
423
  }