@tracelog/lib 2.5.1 → 2.6.0-rc.91.5

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 CHANGED
@@ -131,6 +131,7 @@ tracelog.destroy();
131
131
  | `setCustomHeaders(provider)` | Add custom HTTP headers to requests (see [Custom Headers](#custom-headers)) |
132
132
  | `removeCustomHeaders()` | Remove custom headers provider |
133
133
  | `isInitialized()` | Check initialization status |
134
+ | `getSessionId()` | Get current session ID (or null) |
134
135
  | `setQaMode(enabled)` | Enable/disable QA mode (console logging) |
135
136
  | `destroy()` | Stop tracking and cleanup |
136
137
 
@@ -879,6 +880,24 @@ await tracelog.init({ /* same config */ });
879
880
 
880
881
  **→ [Full Error Handling Reference](./API_REFERENCE.md#error-handling)**
881
882
 
883
+ ### Session Continuity (External Redirects)
884
+
885
+ TraceLog automatically preserves sessions across external redirects (payment processors, OAuth flows, etc.) with zero developer action. Session data is mirrored to `sessionStorage` alongside `localStorage`, so when a user returns from an external site and `localStorage` is empty, the session is recovered from `sessionStorage` transparently.
886
+
887
+ ```typescript
888
+ // No special handling needed before redirect
889
+ window.location.href = paymentUrl;
890
+
891
+ // On the confirmation page, init() automatically recovers the session
892
+ const { sessionId } = await tracelog.init({ /* same config */ });
893
+ tracelog.event('purchase', { orderId: '12345', amount: 99.99 });
894
+ // Same session as before the redirect
895
+ ```
896
+
897
+ - Automatic: no API calls or developer action required
898
+ - `sessionStorage` mirror survives same-tab navigation (cleared on tab close)
899
+ - Session timeout still applies (expired sessions are not recovered)
900
+
882
901
  ---
883
902
 
884
903
  ## Privacy & Security
@@ -80,7 +80,7 @@ const m = {
80
80
  /<embed\b[^>]*>/gi,
81
81
  /<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi
82
82
  ], y = "tlog", G = `${y}:qa_mode`, Oe = `${y}:uid`, Je = "tlog_mode", Pe = "qa", De = "qa_off", It = (s) => s ? `${y}:${s}:queue` : `${y}:queue`, wt = (s) => s ? `${y}:${s}:session` : `${y}:session`, yt = (s) => s ? `${y}:${s}:broadcast` : `${y}:broadcast`, Ve = (s, e) => `${y}:${s}:session_counts:${e}`, ke = 10080 * 60 * 1e3, Ue = `${y}:session_counts_last_cleanup`, He = 3600 * 1e3;
83
- var $ = /* @__PURE__ */ ((s) => (s.Localhost = "localhost:8080", s.Fail = "localhost:9999", s))($ || {}), A = /* @__PURE__ */ ((s) => (s.Mobile = "mobile", s.Tablet = "tablet", s.Desktop = "desktop", s.Unknown = "unknown", s))(A || {}), me = /* @__PURE__ */ ((s) => (s.EVENT = "event", s.QUEUE = "queue", s))(me || {});
83
+ var $ = /* @__PURE__ */ ((s) => (s.Localhost = "localhost:8080", s.Fail = "localhost:9999", s))($ || {}), L = /* @__PURE__ */ ((s) => (s.Mobile = "mobile", s.Tablet = "tablet", s.Desktop = "desktop", s.Unknown = "unknown", s))(L || {}), me = /* @__PURE__ */ ((s) => (s.EVENT = "event", s.QUEUE = "queue", s))(me || {});
84
84
  class N extends Error {
85
85
  constructor(e, t) {
86
86
  super(e), this.statusCode = t, this.name = "PermanentError", Error.captureStackTrace && Error.captureStackTrace(this, N);
@@ -123,7 +123,7 @@ class hs extends Q {
123
123
  super(e, "INITIALIZATION_TIMEOUT", r), this.timeoutMs = t;
124
124
  }
125
125
  }
126
- const Ze = "background: #ff9800; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;", et = "background: #9e9e9e; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;", Lt = "background: #d32f2f; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;", At = (s, e) => {
126
+ const Ze = "background: #ff9800; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;", et = "background: #9e9e9e; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;", At = "background: #d32f2f; color: white; font-weight: bold; padding: 2px 8px; border-radius: 3px;", Lt = (s, e) => {
127
127
  if (e) {
128
128
  if (e instanceof Error) {
129
129
  const t = e.message.replace(/\s+at\s+.*$/gm, "").replace(/\s*\([^()]+:\d+:\d+\)/g, "");
@@ -151,12 +151,12 @@ const Ze = "background: #ff9800; color: white; font-weight: bold; padding: 2px 8
151
151
  return !1;
152
152
  }
153
153
  }, l = (s, e, t) => {
154
- const { error: r, data: n, showToClient: i = !1, style: o, visibility: a } = t ?? {}, c = r ? At(e, r) : `[TraceLog] ${e}`, u = s === "error" ? "error" : s === "warn" ? "warn" : "log";
154
+ const { error: r, data: n, showToClient: i = !1, style: o, visibility: a } = t ?? {}, c = r ? Lt(e, r) : `[TraceLog] ${e}`, u = s === "error" ? "error" : s === "warn" ? "warn" : "log";
155
155
  if (!Ct(a, i))
156
156
  return;
157
- const g = Rt(a, o), p = n !== void 0 ? ge(n) : void 0;
158
- Nt(u, c, g, p);
159
- }, Ct = (s, e) => s === "critical" ? !0 : s === "qa" || e ? Mt() : !1, Rt = (s, e) => e !== void 0 && e !== "" ? e : s === "critical" ? Lt : "", Nt = (s, e, t, r) => {
157
+ const E = Rt(a, o), p = n !== void 0 ? ge(n) : void 0;
158
+ Nt(u, c, E, p);
159
+ }, Ct = (s, e) => s === "critical" ? !0 : s === "qa" || e ? Mt() : !1, Rt = (s, e) => e !== void 0 && e !== "" ? e : s === "critical" ? At : "", Nt = (s, e, t, r) => {
160
160
  const n = t !== void 0 && t !== "", i = n ? `%c${e}` : e;
161
161
  r !== void 0 ? n ? console[s](i, t, r) : console[s](i, r) : n ? console[s](i, t) : console[s](i);
162
162
  }, ge = (s) => {
@@ -204,13 +204,13 @@ const Ot = () => {
204
204
  const s = navigator;
205
205
  if (s.userAgentData != null && typeof s.userAgentData.mobile == "boolean") {
206
206
  const c = s.userAgentData.platform;
207
- return c != null && c !== "" && /ipad|tablet/i.test(c) ? A.Tablet : s.userAgentData.mobile ? A.Mobile : A.Desktop;
207
+ return c != null && c !== "" && /ipad|tablet/i.test(c) ? L.Tablet : s.userAgentData.mobile ? L.Mobile : L.Desktop;
208
208
  }
209
209
  Ot();
210
210
  const e = window.innerWidth, t = Ee?.matches ?? !1, r = tt?.matches ?? !1, n = "ontouchstart" in window || navigator.maxTouchPoints > 0, i = navigator.userAgent.toLowerCase(), o = /mobile|android|iphone|ipod|blackberry|iemobile|opera mini/.test(i), a = /tablet|ipad|android(?!.*mobile)/.test(i);
211
- return e <= 767 || o && n ? A.Mobile : e >= 768 && e <= 1024 || a || t && r && n ? A.Tablet : A.Desktop;
211
+ return e <= 767 || o && n ? L.Mobile : e >= 768 && e <= 1024 || a || t && r && n ? L.Tablet : L.Desktop;
212
212
  } catch (s) {
213
- return l("debug", "Device detection failed, defaulting to desktop", { error: s }), A.Desktop;
213
+ return l("debug", "Device detection failed, defaulting to desktop", { error: s }), L.Desktop;
214
214
  }
215
215
  }, kt = () => {
216
216
  try {
@@ -222,7 +222,7 @@ const Ot = () => {
222
222
  };
223
223
  } catch (s) {
224
224
  return l("debug", "Device info detection failed, using defaults", { error: s }), {
225
- type: A.Desktop,
225
+ type: L.Desktop,
226
226
  os: se,
227
227
  browser: se
228
228
  };
@@ -290,7 +290,7 @@ const Ot = () => {
290
290
  default:
291
291
  return We;
292
292
  }
293
- }, Bt = 1e3, Wt = 50, Gt = "2.5.0", Xt = Gt, nt = () => typeof window < "u" && typeof sessionStorage < "u", Qt = () => {
293
+ }, Bt = 1e3, Wt = 50, Gt = "2.6.0", Xt = Gt, nt = () => typeof window < "u" && typeof sessionStorage < "u", Qt = () => {
294
294
  try {
295
295
  const s = new URLSearchParams(window.location.search);
296
296
  s.delete(Je);
@@ -959,6 +959,15 @@ class ze extends w {
959
959
  lastMetadataTimestamp = 0;
960
960
  fetchCredentials;
961
961
  pendingControllers = /* @__PURE__ */ new Set();
962
+ /**
963
+ * Counts consecutive fetch() rejections where no HTTP response was received
964
+ * (DNS failure, connection refused, etc.). Resets on success.
965
+ * When this reaches MAX_CONSECUTIVE_NETWORK_FAILURES the circuit opens and
966
+ * further send attempts are skipped until CIRCUIT_BREAKER_COOLDOWN_MS elapses,
967
+ * at which point a single probe request is allowed (half-open state).
968
+ */
969
+ consecutiveNetworkFailures = 0;
970
+ circuitOpenedAt = 0;
962
971
  /**
963
972
  * Creates a SenderManager instance.
964
973
  *
@@ -1181,8 +1190,16 @@ class ze extends w {
1181
1190
  this.clearPersistedEvents();
1182
1191
  return;
1183
1192
  }
1184
- const r = this.createRecoveryBody(t);
1185
- await this.send(r) ? (this.clearPersistedEvents(), e?.onSuccess?.(t.events.length, t.events, r)) : e?.onFailure?.();
1193
+ const r = t.recoveryFailures, n = typeof r == "number" && Number.isFinite(r) && r >= 0 ? r : 0;
1194
+ if (n >= 3) {
1195
+ l(
1196
+ "debug",
1197
+ `Discarding persisted events after ${n} failed recovery attempts${this.integrationId ? ` [${this.integrationId}]` : ""}`
1198
+ ), this.clearPersistedEvents(), e?.onFailure?.();
1199
+ return;
1200
+ }
1201
+ const i = this.createRecoveryBody(t);
1202
+ await this.send(i) ? (this.clearPersistedEvents(), e?.onSuccess?.(t.events.length, t.events, i)) : (this.persistEventsWithFailureCount(i, n + 1, !0), e?.onFailure?.());
1186
1203
  } catch (t) {
1187
1204
  if (t instanceof N) {
1188
1205
  this.logPermanentError("Permanent error during recovery, clearing persisted events", t), this.clearPersistedEvents(), e?.onFailure?.();
@@ -1367,40 +1384,53 @@ class ze extends w {
1367
1384
  return l("debug", `Success mode: simulating successful send${this.integrationId ? ` [${this.integrationId}]` : ""}`, {
1368
1385
  data: { events: r.events.length }
1369
1386
  }), !0;
1387
+ if (this.consecutiveNetworkFailures >= 3) {
1388
+ const c = Date.now() - this.circuitOpenedAt;
1389
+ if (c < 12e4)
1390
+ return l("debug", `Network circuit open, skipping send${this.integrationId ? ` [${this.integrationId}]` : ""}`, {
1391
+ data: {
1392
+ consecutiveNetworkFailures: this.consecutiveNetworkFailures,
1393
+ cooldownRemainingMs: 12e4 - c
1394
+ }
1395
+ }), !1;
1396
+ }
1370
1397
  const { url: n, payload: i } = this.prepareRequest(r);
1371
- let o = !0;
1372
- for (let a = 1; a <= 3; a++)
1398
+ let o = !0, a = !1;
1399
+ for (let c = 1; c <= 3; c++)
1373
1400
  try {
1374
- return (await this.sendWithTimeout(n, i)).ok ? (a > 1 && l(
1401
+ return (await this.sendWithTimeout(n, i)).ok ? (c > 1 && l(
1375
1402
  "info",
1376
- `Send succeeded after ${a - 1} retry attempt(s)${this.integrationId ? ` [${this.integrationId}]` : ""}`,
1403
+ `Send succeeded after ${c - 1} retry attempt(s)${this.integrationId ? ` [${this.integrationId}]` : ""}`,
1377
1404
  {
1378
- data: { events: r.events.length, attempt: a }
1405
+ data: { events: r.events.length, attempt: c }
1379
1406
  }
1380
- ), !0) : !1;
1381
- } catch (c) {
1382
- const u = a === 3;
1383
- if (c instanceof N)
1384
- throw c;
1385
- if (c instanceof O || (o = !1), l(
1386
- u ? "error" : "warn",
1387
- `Send attempt ${a} failed${this.integrationId ? ` [${this.integrationId}]` : ""}${u ? " (all retries exhausted)" : ", will retry"}`,
1407
+ ), this.consecutiveNetworkFailures = 0, this.circuitOpenedAt = 0, !0) : !1;
1408
+ } catch (u) {
1409
+ const g = c === 3;
1410
+ if (u instanceof N)
1411
+ throw this.consecutiveNetworkFailures = 0, this.circuitOpenedAt = 0, u;
1412
+ if (u instanceof O || (o = !1), u instanceof TypeError || (a = !0), l(
1413
+ g ? "error" : "warn",
1414
+ `Send attempt ${c} failed${this.integrationId ? ` [${this.integrationId}]` : ""}${g ? " (all retries exhausted)" : ", will retry"}`,
1388
1415
  {
1389
- error: c,
1416
+ error: u,
1390
1417
  data: {
1391
1418
  events: e.events.length,
1392
1419
  url: n.replace(/\/\/[^/]+/, "//[DOMAIN]"),
1393
- attempt: a,
1420
+ attempt: c,
1394
1421
  maxAttempts: 3
1395
1422
  }
1396
1423
  }
1397
- ), !u) {
1398
- await this.backoffDelay(a);
1424
+ ), !g) {
1425
+ await this.backoffDelay(c);
1399
1426
  continue;
1400
1427
  }
1401
1428
  if (o)
1402
1429
  throw new O("All retry attempts timed out (server likely received the request)");
1403
- return !1;
1430
+ return a ? (this.consecutiveNetworkFailures = 0, this.circuitOpenedAt = 0) : (this.consecutiveNetworkFailures = Math.min(
1431
+ this.consecutiveNetworkFailures + 1,
1432
+ 3
1433
+ ), this.consecutiveNetworkFailures >= 3 && (this.circuitOpenedAt = Date.now())), !1;
1404
1434
  }
1405
1435
  return !1;
1406
1436
  }
@@ -1589,8 +1619,8 @@ class ze extends w {
1589
1619
  * @private
1590
1620
  */
1591
1621
  createRecoveryBody(e) {
1592
- const { timestamp: t, ...r } = e;
1593
- return r;
1622
+ const { timestamp: t, recoveryFailures: r, ...n } = e;
1623
+ return n;
1594
1624
  }
1595
1625
  /**
1596
1626
  * Persists failed events to localStorage for next-page-load recovery.
@@ -1610,26 +1640,44 @@ class ze extends w {
1610
1640
  * @private
1611
1641
  */
1612
1642
  persistEvents(e) {
1643
+ return this.persistEventsWithFailureCount(e, 0);
1644
+ }
1645
+ /**
1646
+ * Persists failed events to localStorage, recording how many consecutive
1647
+ * cross-session recovery attempts have already been made for this batch.
1648
+ *
1649
+ * When `recoveryFailures` reaches MAX_RECOVERY_FAILURES on the next page load,
1650
+ * the batch is discarded rather than retried, preventing an infinite persistence
1651
+ * loop caused by a permanently unreachable backend URL.
1652
+ *
1653
+ * @param body - EventsQueue to persist
1654
+ * @param recoveryFailures - Number of failed recovery attempts already made
1655
+ * @param skipThrottle - Bypass the multi-tab throttle (used during recovery re-persistence)
1656
+ * @returns `true` on successful persistence or throttled write, `false` on error
1657
+ * @private
1658
+ */
1659
+ persistEventsWithFailureCount(e, t, r = !1) {
1613
1660
  try {
1614
- const t = this.getPersistedData();
1615
- if (t && t.timestamp) {
1616
- const i = Date.now() - t.timestamp;
1617
- if (i < 1e3)
1661
+ const n = this.getPersistedData();
1662
+ if (!r && n && n.timestamp) {
1663
+ const a = Date.now() - n.timestamp;
1664
+ if (a < 1e3)
1618
1665
  return l(
1619
1666
  "debug",
1620
1667
  `Skipping persistence, another tab recently persisted events${this.integrationId ? ` [${this.integrationId}]` : ""}`,
1621
1668
  {
1622
- data: { timeSinceExisting: i }
1669
+ data: { timeSinceExisting: a }
1623
1670
  }
1624
1671
  ), !0;
1625
1672
  }
1626
- const r = {
1673
+ const i = {
1627
1674
  ...e,
1628
- timestamp: Date.now()
1629
- }, n = this.getQueueStorageKey();
1630
- return this.storeManager.setItem(n, JSON.stringify(r)), !!this.storeManager.getItem(n);
1631
- } catch (t) {
1632
- return l("debug", `Failed to persist events${this.integrationId ? ` [${this.integrationId}]` : ""}`, { error: t }), !1;
1675
+ timestamp: Date.now(),
1676
+ ...t > 0 && { recoveryFailures: t }
1677
+ }, o = this.getQueueStorageKey();
1678
+ return this.storeManager.setItem(o, JSON.stringify(i)), !!this.storeManager.getItem(o);
1679
+ } catch (n) {
1680
+ return l("debug", `Failed to persist events${this.integrationId ? ` [${this.integrationId}]` : ""}`, { error: n }), !1;
1633
1681
  }
1634
1682
  }
1635
1683
  clearPersistedEvents() {
@@ -1973,7 +2021,7 @@ class fr extends w {
1973
2021
  web_vitals: a,
1974
2022
  error_data: c,
1975
2023
  viewport_data: u,
1976
- page_view: S
2024
+ page_view: g
1977
2025
  }) {
1978
2026
  if (!e) {
1979
2027
  l("error", "Event type is required - event will be ignored");
@@ -1985,8 +2033,8 @@ class fr extends w {
1985
2033
  });
1986
2034
  return;
1987
2035
  }
1988
- const g = this.get("sessionId");
1989
- if (!g) {
2036
+ const E = this.get("sessionId");
2037
+ if (!E) {
1990
2038
  this.pendingEventsBuffer.length >= 100 && (this.pendingEventsBuffer.shift(), l("debug", "Pending events buffer full - dropping oldest event", {
1991
2039
  data: { maxBufferSize: 100 }
1992
2040
  })), this.pendingEventsBuffer.push({
@@ -1999,35 +2047,35 @@ class fr extends w {
1999
2047
  web_vitals: a,
2000
2048
  error_data: c,
2001
2049
  viewport_data: u,
2002
- page_view: S
2050
+ page_view: g
2003
2051
  });
2004
2052
  return;
2005
2053
  }
2006
- this.lastSessionId !== g && (this.lastSessionId = g, this.sessionEventCounts = this.loadSessionCounts(g));
2054
+ this.lastSessionId !== E && (this.lastSessionId = E, this.sessionEventCounts = this.loadSessionCounts(E));
2007
2055
  const p = e === d.SESSION_START;
2008
2056
  if (p && l("debug", "Processing SESSION_START event", {
2009
- data: { sessionId: g }
2057
+ data: { sessionId: E }
2010
2058
  }), !p && !this.checkRateLimit())
2011
2059
  return;
2012
- const E = e;
2060
+ const S = e;
2013
2061
  if (!p) {
2014
2062
  if (this.sessionEventCounts.total >= 1e3) {
2015
2063
  l("warn", "Session event limit reached", {
2016
2064
  data: {
2017
- type: E,
2065
+ type: S,
2018
2066
  total: this.sessionEventCounts.total,
2019
2067
  limit: 1e3
2020
2068
  }
2021
2069
  });
2022
2070
  return;
2023
2071
  }
2024
- const T = this.getTypeLimitForEvent(E);
2072
+ const T = this.getTypeLimitForEvent(S);
2025
2073
  if (T) {
2026
- const le = this.sessionEventCounts[E];
2074
+ const le = this.sessionEventCounts[S];
2027
2075
  if (le !== void 0 && le >= T) {
2028
2076
  l("warn", "Session event type limit reached", {
2029
2077
  data: {
2030
- type: E,
2078
+ type: S,
2031
2079
  count: le,
2032
2080
  limit: T
2033
2081
  }
@@ -2036,13 +2084,13 @@ class fr extends w {
2036
2084
  }
2037
2085
  }
2038
2086
  }
2039
- if (E === d.CUSTOM && o?.name) {
2087
+ if (S === d.CUSTOM && o?.name) {
2040
2088
  const T = this.get("config")?.maxSameEventPerMinute ?? 60;
2041
2089
  if (!this.checkPerEventRateLimit(o.name, T))
2042
2090
  return;
2043
2091
  }
2044
- const Ne = E === d.SESSION_START, K = t || this.get("pageUrl"), F = this.buildEventPayload({
2045
- type: E,
2092
+ const Ne = S === d.SESSION_START, K = t || this.get("pageUrl"), F = this.buildEventPayload({
2093
+ type: S,
2046
2094
  page_url: K,
2047
2095
  from_page_url: r,
2048
2096
  scroll_data: n,
@@ -2051,7 +2099,7 @@ class fr extends w {
2051
2099
  web_vitals: a,
2052
2100
  error_data: c,
2053
2101
  viewport_data: u,
2054
- page_view: S
2102
+ page_view: g
2055
2103
  });
2056
2104
  if (F && !(!p && !this.shouldSample())) {
2057
2105
  if (Ne) {
@@ -2070,7 +2118,7 @@ class fr extends w {
2070
2118
  }
2071
2119
  if (!this.isDuplicateEvent(F)) {
2072
2120
  if (this.get("mode") === re.QA) {
2073
- if (E === d.CUSTOM && o) {
2121
+ if (S === d.CUSTOM && o) {
2074
2122
  l("info", `Custom Event: ${o.name}`, {
2075
2123
  visibility: "qa",
2076
2124
  data: {
@@ -2080,7 +2128,7 @@ class fr extends w {
2080
2128
  }), this.emitEvent(F);
2081
2129
  return;
2082
2130
  }
2083
- if (E === d.VIEWPORT_VISIBLE && u) {
2131
+ if (S === d.VIEWPORT_VISIBLE && u) {
2084
2132
  const T = u.name || u.id || u.selector;
2085
2133
  l("info", `Viewport Visible: ${T}`, {
2086
2134
  visibility: "qa",
@@ -2096,7 +2144,7 @@ class fr extends w {
2096
2144
  }
2097
2145
  }
2098
2146
  if (this.addToQueue(F), !p) {
2099
- this.sessionEventCounts.total++, this.sessionEventCounts[E] !== void 0 && this.sessionEventCounts[E]++;
2147
+ this.sessionEventCounts.total++, this.sessionEventCounts[S] !== void 0 && this.sessionEventCounts[S]++;
2100
2148
  const T = this.get("sessionId");
2101
2149
  T && this.saveSessionCountsDebounced && this.saveSessionCountsDebounced(T);
2102
2150
  }
@@ -2463,9 +2511,9 @@ class fr extends w {
2463
2511
  ...e.page_view && { page_view: e.page_view },
2464
2512
  ...o && { utm: o }
2465
2513
  };
2466
- const c = this.get("collectApiUrls"), u = !!c?.custom, S = !!c?.saas, g = u || S, p = u && S, E = this.transformers.beforeSend;
2467
- if (E && (!g || u && !p)) {
2468
- const K = at(a, E, "EventManager");
2514
+ const c = this.get("collectApiUrls"), u = !!c?.custom, g = !!c?.saas, E = u || g, p = u && g, S = this.transformers.beforeSend;
2515
+ if (S && (!E || u && !p)) {
2516
+ const K = at(a, S, "EventManager");
2469
2517
  if (K === null)
2470
2518
  return null;
2471
2519
  a = K;
@@ -2857,18 +2905,28 @@ class Er extends w {
2857
2905
  }
2858
2906
  loadStoredSession() {
2859
2907
  const e = this.getSessionStorageKey(), t = this.storageManager.getItem(e);
2860
- if (!t)
2861
- return null;
2862
- try {
2863
- const r = JSON.parse(t);
2864
- return !r.id || typeof r.lastActivity != "number" ? null : r;
2865
- } catch {
2866
- return this.storageManager.removeItem(e), null;
2867
- }
2908
+ if (t !== null)
2909
+ try {
2910
+ const n = JSON.parse(t);
2911
+ if (n.id && typeof n.lastActivity == "number")
2912
+ return n;
2913
+ } catch {
2914
+ this.storageManager.removeItem(e);
2915
+ }
2916
+ const r = this.storageManager.getSessionItem(e);
2917
+ if (r !== null)
2918
+ try {
2919
+ const n = JSON.parse(r);
2920
+ if (n.id && typeof n.lastActivity == "number")
2921
+ return n;
2922
+ } catch {
2923
+ this.storageManager.removeSessionItem(e);
2924
+ }
2925
+ return null;
2868
2926
  }
2869
2927
  saveStoredSession(e) {
2870
- const t = this.getSessionStorageKey();
2871
- this.storageManager.setItem(t, JSON.stringify(e));
2928
+ const t = this.getSessionStorageKey(), r = JSON.stringify(e);
2929
+ this.storageManager.setItem(t, r), this.storageManager.setSessionItem(t, r);
2872
2930
  }
2873
2931
  getSessionStorageKey() {
2874
2932
  return wt(this.getProjectId());
@@ -2896,7 +2954,8 @@ class Er extends w {
2896
2954
  * 11. Sets up lifecycle listeners (visibilitychange, beforeunload)
2897
2955
  *
2898
2956
  * **Session Recovery**:
2899
- * - Checks localStorage for existing session
2957
+ * - Checks localStorage for existing session (primary)
2958
+ * - Falls back to sessionStorage mirror (survives external redirects)
2900
2959
  * - Recovers if session exists and is recent (within timeout window)
2901
2960
  * - NO SESSION_START event if session recovered
2902
2961
  *
@@ -3309,14 +3368,14 @@ class Tr extends w {
3309
3368
  return;
3310
3369
  const o = this.findTrackingElement(n), a = this.getRelevantClickElement(n), c = this.calculateClickCoordinates(t, n);
3311
3370
  if (o) {
3312
- const S = this.extractTrackingData(o);
3313
- if (S) {
3314
- const g = this.createCustomEventData(S);
3371
+ const g = this.extractTrackingData(o);
3372
+ if (g) {
3373
+ const E = this.createCustomEventData(g);
3315
3374
  this.eventManager.track({
3316
3375
  type: d.CUSTOM,
3317
3376
  custom_event: {
3318
- name: g.name,
3319
- ...g.value && { metadata: { value: g.value } }
3377
+ name: E.name,
3378
+ ...E.value && { metadata: { value: E.value } }
3320
3379
  }
3321
3380
  });
3322
3381
  }
@@ -3654,8 +3713,8 @@ class vr extends w {
3654
3713
  this.get("suppressNextScroll") || (a.firstScrollEventTime === null && (a.firstScrollEventTime = Date.now()), this.clearContainerTimer(a), a.debounceTimer = window.setTimeout(() => {
3655
3714
  const u = this.calculateScrollData(a);
3656
3715
  if (u) {
3657
- const S = Date.now();
3658
- this.processScrollEvent(a, u, S);
3716
+ const g = Date.now();
3717
+ this.processScrollEvent(a, u, g);
3659
3718
  }
3660
3719
  a.debounceTimer = null;
3661
3720
  }, 250));
@@ -3715,14 +3774,14 @@ class vr extends w {
3715
3774
  const { element: t, lastScrollPos: r, lastEventTime: n } = e, i = this.getScrollTop(t), o = Date.now(), a = Math.abs(i - r);
3716
3775
  if (a < 10 || t === window && !this.isWindowScrollable())
3717
3776
  return null;
3718
- const c = this.getViewportHeight(t), u = this.getScrollHeight(t), S = this.getScrollDirection(i, r), g = this.calculateScrollDepth(i, u, c);
3777
+ const c = this.getViewportHeight(t), u = this.getScrollHeight(t), g = this.getScrollDirection(i, r), E = this.calculateScrollDepth(i, u, c);
3719
3778
  let p;
3720
3779
  n > 0 ? p = o - n : e.firstScrollEventTime !== null ? p = o - e.firstScrollEventTime : p = 250;
3721
- const E = Math.round(a / p * 1e3);
3722
- return g > e.maxDepthReached && (e.maxDepthReached = g), e.lastScrollPos = i, {
3723
- depth: g,
3724
- direction: S,
3725
- velocity: E,
3780
+ const S = Math.round(a / p * 1e3);
3781
+ return E > e.maxDepthReached && (e.maxDepthReached = E), e.lastScrollPos = i, {
3782
+ depth: E,
3783
+ direction: g,
3784
+ velocity: S,
3726
3785
  max_depth_reached: e.maxDepthReached
3727
3786
  };
3728
3787
  }
@@ -4797,7 +4856,7 @@ class br extends w {
4797
4856
  }
4798
4857
  const V = [], M = [];
4799
4858
  let D = null, h = null, R = !1, v = !1, P = null;
4800
- const Lr = async (s) => typeof window > "u" || typeof document > "u" ? { sessionId: "" } : (v = !1, window.__traceLogDisabled === !0 ? { sessionId: "" } : h ? { sessionId: h.getSessionId() ?? "" } : (R && P || (R = !0, P = (async () => {
4859
+ const Ar = async (s) => typeof window > "u" || typeof document > "u" ? { sessionId: "" } : (v = !1, window.__traceLogDisabled === !0 ? { sessionId: "" } : h ? { sessionId: h.getSessionId() ?? "" } : (R && P || (R = !0, P = (async () => {
4801
4860
  try {
4802
4861
  const e = ir(s ?? {}), t = new br();
4803
4862
  try {
@@ -4825,7 +4884,7 @@ const Lr = async (s) => typeof window > "u" || typeof document > "u" ? { session
4825
4884
  } finally {
4826
4885
  R = !1, P = null;
4827
4886
  }
4828
- })()), P)), Ar = (s, e) => {
4887
+ })()), P)), Lr = (s, e) => {
4829
4888
  if (!(typeof window > "u" || typeof document > "u")) {
4830
4889
  if (!h)
4831
4890
  throw new Error("[TraceLog] TraceLog not initialized. Please call init() first.");
@@ -4932,8 +4991,8 @@ const Nr = (s) => {
4932
4991
  h.mergeGlobalMetadata(s);
4933
4992
  }
4934
4993
  }, ms = {
4935
- init: Lr,
4936
- event: Ar,
4994
+ init: Ar,
4995
+ event: Lr,
4937
4996
  on: Mr,
4938
4997
  off: Cr,
4939
4998
  setTransformer: Rr,
@@ -5032,8 +5091,8 @@ var _e, C, X, ct, ne, ut = -1, k = function(s) {
5032
5091
  var t, r = _("CLS", 0), n = 0, i = [], o = function(c) {
5033
5092
  c.forEach((function(u) {
5034
5093
  if (!u.hadRecentInput) {
5035
- var S = i[0], g = i[i.length - 1];
5036
- n && u.startTime - g.startTime < 1e3 && u.startTime - S.startTime < 5e3 ? (n += u.value, i.push(u)) : (n = u.value, i = [u]);
5094
+ var g = i[0], E = i[i.length - 1];
5095
+ n && u.startTime - E.startTime < 1e3 && u.startTime - g.startTime < 5e3 ? (n += u.value, i.push(u)) : (n = u.value, i = [u]);
5037
5096
  }
5038
5097
  })), n > r.value && (r.value = n, r.entries = i, t());
5039
5098
  }, a = x("layout-shift", o);
@@ -5053,23 +5112,23 @@ var _e, C, X, ct, ne, ut = -1, k = function(s) {
5053
5112
  return _e ? ht : performance.interactionCount || 0;
5054
5113
  }, Wr = function() {
5055
5114
  "interactionCount" in performance || _e || (_e = x("event", Br, { type: "event", buffered: !0, durationThreshold: 0 }));
5056
- }, L = [], te = /* @__PURE__ */ new Map(), mt = 0, Gr = function() {
5057
- var s = Math.min(L.length - 1, Math.floor((ft() - mt) / 50));
5058
- return L[s];
5115
+ }, A = [], te = /* @__PURE__ */ new Map(), mt = 0, Gr = function() {
5116
+ var s = Math.min(A.length - 1, Math.floor((ft() - mt) / 50));
5117
+ return A[s];
5059
5118
  }, Xr = [], Qr = function(s) {
5060
5119
  if (Xr.forEach((function(n) {
5061
5120
  return n(s);
5062
5121
  })), s.interactionId || s.entryType === "first-input") {
5063
- var e = L[L.length - 1], t = te.get(s.interactionId);
5064
- if (t || L.length < 10 || s.duration > e.latency) {
5122
+ var e = A[A.length - 1], t = te.get(s.interactionId);
5123
+ if (t || A.length < 10 || s.duration > e.latency) {
5065
5124
  if (t) s.duration > t.latency ? (t.entries = [s], t.latency = s.duration) : s.duration === t.latency && s.startTime === t.entries[0].startTime && t.entries.push(s);
5066
5125
  else {
5067
5126
  var r = { id: s.interactionId, latency: s.duration, entries: [s] };
5068
- te.set(r.id, r), L.push(r);
5127
+ te.set(r.id, r), A.push(r);
5069
5128
  }
5070
- L.sort((function(n, i) {
5129
+ A.sort((function(n, i) {
5071
5130
  return i.latency - n.latency;
5072
- })), L.length > 10 && L.splice(10).forEach((function(n) {
5131
+ })), A.length > 10 && A.splice(10).forEach((function(n) {
5073
5132
  return te.delete(n.id);
5074
5133
  }));
5075
5134
  }
@@ -5091,7 +5150,7 @@ var _e, C, X, ct, ne, ut = -1, k = function(s) {
5091
5150
  r = I(s, n, ye, e.reportAllChanges), o && (o.observe({ type: "first-input", buffered: !0 }), j((function() {
5092
5151
  i(o.takeRecords()), r(!0);
5093
5152
  })), k((function() {
5094
- mt = ft(), L.length = 0, te.clear(), n = _("INP"), r = I(s, n, ye, e.reportAllChanges);
5153
+ mt = ft(), A.length = 0, te.clear(), n = _("INP"), r = I(s, n, ye, e.reportAllChanges);
5095
5154
  })));
5096
5155
  })));
5097
5156
  }, be = [2500, 4e3], fe = {}, zr = function(s, e) {
@@ -5117,7 +5176,7 @@ var _e, C, X, ct, ne, ut = -1, k = function(s) {
5117
5176
  }));
5118
5177
  }
5119
5178
  }));
5120
- }, Le = [800, 1800], Kr = function s(e) {
5179
+ }, Ae = [800, 1800], Kr = function s(e) {
5121
5180
  document.prerendering ? z((function() {
5122
5181
  return s(e);
5123
5182
  })) : document.readyState !== "complete" ? addEventListener("load", (function() {
@@ -5125,11 +5184,11 @@ var _e, C, X, ct, ne, ut = -1, k = function(s) {
5125
5184
  }), !0) : setTimeout(e, 0);
5126
5185
  }, Yr = function(s, e) {
5127
5186
  e = e || {};
5128
- var t = _("TTFB"), r = I(s, t, Le, e.reportAllChanges);
5187
+ var t = _("TTFB"), r = I(s, t, Ae, e.reportAllChanges);
5129
5188
  Kr((function() {
5130
5189
  var n = Me();
5131
5190
  n && (t.value = Math.max(n.responseStart - oe(), 0), t.entries = [n], r(!0), k((function() {
5132
- t = _("TTFB", 0), (r = I(s, t, Le, e.reportAllChanges))(!0);
5191
+ t = _("TTFB", 0), (r = I(s, t, Ae, e.reportAllChanges))(!0);
5133
5192
  })));
5134
5193
  }));
5135
5194
  }, W = { passive: !0, capture: !0 }, qr = /* @__PURE__ */ new Date(), qe = function(s, e) {
@@ -5159,18 +5218,18 @@ var _e, C, X, ct, ne, ut = -1, k = function(s) {
5159
5218
  ["mousedown", "keydown", "touchstart", "pointerdown"].forEach((function(e) {
5160
5219
  return s(e, Jr, W);
5161
5220
  }));
5162
- }, Ae = [100, 300], Zr = function(s, e) {
5221
+ }, Le = [100, 300], Zr = function(s, e) {
5163
5222
  e = e || {}, z((function() {
5164
5223
  var t, r = Re(), n = _("FID"), i = function(c) {
5165
5224
  c.startTime < r.firstHiddenTime && (n.value = c.processingStart - c.startTime, n.entries.push(c), t(!0));
5166
5225
  }, o = function(c) {
5167
5226
  c.forEach(i);
5168
5227
  }, a = x("first-input", o);
5169
- t = I(s, n, Ae, e.reportAllChanges), a && (j(ae((function() {
5228
+ t = I(s, n, Le, e.reportAllChanges), a && (j(ae((function() {
5170
5229
  o(a.takeRecords()), a.disconnect();
5171
5230
  }))), k((function() {
5172
5231
  var c;
5173
- n = _("FID"), t = I(s, n, Ae, e.reportAllChanges), ne = [], X = -1, C = null, St(addEventListener), c = i, ne.push(c), Et();
5232
+ n = _("FID"), t = I(s, n, Le, e.reportAllChanges), ne = [], X = -1, C = null, St(addEventListener), c = i, ne.push(c), Et();
5174
5233
  })));
5175
5234
  }));
5176
5235
  };
@@ -5178,10 +5237,10 @@ const es = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
5178
5237
  __proto__: null,
5179
5238
  CLSThresholds: we,
5180
5239
  FCPThresholds: Ie,
5181
- FIDThresholds: Ae,
5240
+ FIDThresholds: Le,
5182
5241
  INPThresholds: ye,
5183
5242
  LCPThresholds: be,
5184
- TTFBThresholds: Le,
5243
+ TTFBThresholds: Ae,
5185
5244
  onCLS: $r,
5186
5245
  onFCP: dt,
5187
5246
  onFID: Zr,
@@ -5193,7 +5252,7 @@ export {
5193
5252
  f as AppConfigValidationError,
5194
5253
  ts as DEFAULT_SESSION_TIMEOUT,
5195
5254
  Se as DEFAULT_WEB_VITALS_MODE,
5196
- A as DeviceType,
5255
+ L as DeviceType,
5197
5256
  me as EmitterEvent,
5198
5257
  B as ErrorType,
5199
5258
  d as EventType,