@levo-so/insights 0.1.109 → 0.1.111

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.
@@ -5,12 +5,10 @@
5
5
  export declare const STORAGE_KEYS: {
6
6
  /** Legacy: Lock key for identify operation (no longer used internally) */
7
7
  readonly identify: "lock:lv_aud_identify";
8
- /** Key for tracking open tab count */
8
+ /** Namespace prefix for per-tab heartbeat keys (`${open_tabs}:${tabId}`) — see tracking/currentTab.ts */
9
9
  readonly open_tabs: "lv_insights_ot";
10
10
  /** Key for current tab ID (sessionStorage) */
11
11
  readonly current_tab: "lv_insights_ct";
12
- /** Key for all tabs registry */
13
- readonly all_tabs: "lv_insights_at";
14
12
  /** Device JWT for direct mode (1yr expiry, rotated on /welcome, analytics-only scope) */
15
13
  readonly device_token: "lv_aud_device";
16
14
  /** Session JWT for direct mode (1yr expiry, rotated on /welcome, analytics-only scope) */
@@ -25,4 +23,13 @@ export declare const STORAGE_KEYS: {
25
23
  * ends, preventing stale isIdentified from blocking /welcome across restarts.
26
24
  */
27
25
  readonly session_flags: "lv_insights_sf";
26
+ /**
27
+ * sessionStorage key for the last non-direct attribution (channel/referrer/UTM)
28
+ * of the CURRENT browsing session. Used to carry the original source forward when
29
+ * the visitor-source library would otherwise report "internalLink" for same-site
30
+ * continued browsing after the server cuts a session at its 30-min idle threshold.
31
+ * Session-scoped on purpose — see utils/getReferrer.ts (a long-lived store would
32
+ * mis-attribute a fresh visit days later).
33
+ */
34
+ readonly attribution: "lv_insights_attr";
28
35
  };
package/dist/index.js CHANGED
@@ -1,24 +1,24 @@
1
- import { A as e, C as t, D as n, E as r, F as i, M as a, N as o, P as s, S as c, T as l, _ as u, c as d, f, g as p, h as m, i as h, j as g, k as _, l as v, m as y, n as b, o as x, s as S, u as C, v as w, w as T } from "./textSelection-eSp_MmZH.js";
2
- import { AnalyticsEvents as E, getLevoError as D } from "@levo-so/core";
3
- import { getVisitorSource as O } from "@analytics/visitor-source";
4
- import k from "get-user-locale";
5
- import { Analytics as A } from "analytics";
1
+ import { A as e, C as t, D as n, E as r, F as i, I as a, L as o, M as s, N as c, O as l, P as u, T as d, _ as f, c as p, d as m, g as h, h as g, i as _, j as v, l as y, n as b, o as x, p as S, s as C, u as w, v as T, w as E, y as ee } from "./textSelection-D9P2LBjT.js";
2
+ import { AnalyticsEvents as D, getLevoError as O } from "@levo-so/core";
3
+ import { getVisitorSource as k } from "@analytics/visitor-source";
4
+ import A from "get-user-locale";
5
+ import { Analytics as j } from "analytics";
6
6
  //#region src/utils/rageclick.ts
7
- var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
8
- let r = P[P.length - 1];
9
- if (r && Math.abs(e - r.x) + Math.abs(t - r.y) < j && n - r.timestamp < M) {
10
- if (P.push({
7
+ var M = 30, N = 1e3, P = 3, F = [], I = (e, t, n) => {
8
+ let r = F[F.length - 1];
9
+ if (r && Math.abs(e - r.x) + Math.abs(t - r.y) < M && n - r.timestamp < N) {
10
+ if (F.push({
11
11
  x: e,
12
12
  y: t,
13
13
  timestamp: n
14
- }), P.length >= N) return P = [], !0;
15
- } else P = [{
14
+ }), F.length >= P) return F = [], !0;
15
+ } else F = [{
16
16
  x: e,
17
17
  y: t,
18
18
  timestamp: n
19
19
  }];
20
20
  return !1;
21
- }, I = (e) => {
21
+ }, L = (e) => {
22
22
  let t = e, n = 0;
23
23
  for (; t && n < 10;) {
24
24
  let e = t.getAttribute?.("href");
@@ -26,8 +26,8 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
26
26
  t = t.parentElement, n++;
27
27
  }
28
28
  return null;
29
- }, L = (e) => {
30
- let t = e.closest("a[href]"), n = t?.getAttribute("href") || I(e);
29
+ }, R = (e) => {
30
+ let t = e.closest("a[href]"), n = t?.getAttribute("href") || L(e);
31
31
  if (!n) return {
32
32
  type: null,
33
33
  href: null
@@ -54,15 +54,15 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
54
54
  href: n
55
55
  };
56
56
  } catch (e) {
57
- return console.warn("[insights] Failed to classify link", D(e)), {
57
+ return console.warn("[insights] Failed to classify link", O(e)), {
58
58
  type: "internal",
59
59
  href: n
60
60
  };
61
61
  }
62
- }, R = (e) => {
62
+ }, z = (e) => {
63
63
  if (typeof window > "u") return () => {};
64
64
  let t = 0, n = null, r = (e, t = !1, n) => {
65
- let r = n ?? v(e), i = "clientX" in e ? e.clientX : e.changedTouches?.[0]?.clientX ?? 0, a = "clientY" in e ? e.clientY : e.changedTouches?.[0]?.clientY ?? 0, o = {
65
+ let r = n ?? w(e), i = "clientX" in e ? e.clientX : e.changedTouches?.[0]?.clientX ?? 0, a = "clientY" in e ? e.clientY : e.changedTouches?.[0]?.clientY ?? 0, o = {
66
66
  hostname: window.location.hostname,
67
67
  pathname: window.location.pathname,
68
68
  page_title: document.title,
@@ -71,9 +71,9 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
71
71
  event_type: t ? "dblclick" : "click",
72
72
  button_type: "button" in e ? e.button : 0
73
73
  };
74
- return r instanceof HTMLElement && (o.rage_click = F(i, a, Date.now()), o = {
74
+ return r instanceof HTMLElement && (o.rage_click = I(i, a, Date.now()), o = {
75
75
  ...o,
76
- ...d(r)
76
+ ...y(r)
77
77
  }), o;
78
78
  }, i = (n, i, a) => {
79
79
  try {
@@ -86,25 +86,25 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
86
86
  is_navigation: !0
87
87
  });
88
88
  } catch (e) {
89
- console.error("[insights] Failed to track navigation click", D(e));
89
+ console.error("[insights] Failed to track navigation click", O(e));
90
90
  }
91
91
  }, a = (n, i = !1, a) => {
92
92
  try {
93
- let o = a ?? v(n);
93
+ let o = a ?? w(n);
94
94
  if (!o || Date.now() - t < 300) return;
95
- let s = r(n, i, o), c = L(o);
95
+ let s = r(n, i, o), c = R(o);
96
96
  e.track("page.click", {
97
97
  ...s,
98
98
  href: c.href || void 0,
99
99
  link_type: c.type || void 0
100
100
  });
101
101
  } catch (e) {
102
- console.error("[insights] Failed to track click", D(e));
102
+ console.error("[insights] Failed to track click", O(e));
103
103
  }
104
104
  }, o = (e) => {
105
- let t = v(e);
105
+ let t = w(e);
106
106
  if (!t) return;
107
- let r = L(t);
107
+ let r = R(t);
108
108
  if (r.type === "external" || r.type === "download") {
109
109
  i(e, r, t);
110
110
  return;
@@ -117,13 +117,13 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
117
117
  a(e, !1, t), n = null;
118
118
  }, 250);
119
119
  }, s = (e) => {
120
- n &&= (clearTimeout(n), null), a(e, !0, v(e) ?? void 0);
120
+ n &&= (clearTimeout(n), null), a(e, !0, w(e) ?? void 0);
121
121
  }, c = (n) => {
122
122
  try {
123
- let i = v(n);
123
+ let i = w(n);
124
124
  if (!i) return;
125
125
  t = Date.now();
126
- let a = L(i);
126
+ let a = R(i);
127
127
  if (a.type === "external" || a.type === "download") {
128
128
  let t = r(n, !1, i);
129
129
  e.track("page.click", {
@@ -143,52 +143,52 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
143
143
  is_touch: !0
144
144
  });
145
145
  } catch (e) {
146
- console.error("[insights] Failed to track touch", D(e));
146
+ console.error("[insights] Failed to track touch", O(e));
147
147
  }
148
148
  };
149
149
  return document.addEventListener("click", o), document.addEventListener("dblclick", s), document.addEventListener("touchend", c, { passive: !0 }), () => {
150
150
  document.removeEventListener("click", o), document.removeEventListener("dblclick", s), document.removeEventListener("touchend", c), n && clearTimeout(n);
151
151
  };
152
- }, z = (e) => {
152
+ }, B = (e) => {
153
153
  if (typeof window > "u") return () => {};
154
154
  let t = (t) => (n) => {
155
155
  try {
156
- let r = C(n, t);
156
+ let r = m(n, t);
157
157
  r && e.track("page.copy", r);
158
158
  } catch (e) {
159
- console.error(`[insights] Failed to track clipboard ${t}`, D(e));
159
+ console.error(`[insights] Failed to track clipboard ${t}`, O(e));
160
160
  }
161
161
  }, n = t("copy"), r = t("cut");
162
162
  return document.addEventListener("copy", n, { capture: !0 }), document.addEventListener("cut", r, { capture: !0 }), () => {
163
163
  document.removeEventListener("copy", n, { capture: !0 }), document.removeEventListener("cut", r, { capture: !0 });
164
164
  };
165
- }, B = (e) => {
165
+ }, V = (e) => {
166
166
  if (typeof window > "u") return () => {};
167
167
  let t = (t) => (n) => {
168
168
  try {
169
- let r = C(n, t);
169
+ let r = m(n, t);
170
170
  if (r) {
171
171
  let n = t === "submit" ? "form.submit" : "form.change";
172
172
  e.track(n, r);
173
173
  }
174
174
  } catch (e) {
175
- console.error(`[insights] Failed to track form ${t}`, D(e));
175
+ console.error(`[insights] Failed to track form ${t}`, O(e));
176
176
  }
177
177
  }, n = t("change"), r = t("submit");
178
178
  return document.addEventListener("change", n, { capture: !0 }), document.addEventListener("submit", r, { capture: !0 }), () => {
179
179
  document.removeEventListener("change", n, { capture: !0 }), document.removeEventListener("submit", r, { capture: !0 });
180
180
  };
181
- }, V = (e) => {
181
+ }, H = (e) => {
182
182
  if (typeof window > "u") return () => {};
183
- let { page_id: t } = a.get(), n = t || "", r = typeof window < "u" ? window.location.href : "", i = () => {
183
+ let { page_id: t } = u.get(), n = t || "", r = typeof window < "u" ? window.location.href : "", i = () => {
184
184
  try {
185
185
  e.page({ resource: "page" });
186
186
  } catch (e) {
187
- console.error("[insights] Failed to send page view", D(e));
187
+ console.error("[insights] Failed to send page view", O(e));
188
188
  }
189
189
  };
190
190
  if (t) {
191
- let e = a.listen((e, t, r) => {
191
+ let e = u.listen((e, t, r) => {
192
192
  let { page_id: a } = e;
193
193
  r === "page_id" && a && n !== a && (n = a, i());
194
194
  });
@@ -209,7 +209,7 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
209
209
  window.removeEventListener("popstate", e), window.history.pushState = t;
210
210
  };
211
211
  }
212
- }, H = async (e, t) => {
212
+ }, U = async (e, t) => {
213
213
  let n = null;
214
214
  try {
215
215
  let r = new AbortController();
@@ -223,7 +223,7 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
223
223
  } finally {
224
224
  n && clearTimeout(n);
225
225
  }
226
- }, U = async (e) => {
226
+ }, W = async (e) => {
227
227
  let { mode: t = "auto", insightsUrl: n, directInsightsUrl: r, pingTimeout: i = 1e3, debug: a = !1 } = e;
228
228
  if (a) return {
229
229
  mode: "proxy",
@@ -238,7 +238,7 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
238
238
  mode: "proxy",
239
239
  url: n ?? null
240
240
  };
241
- let [s, c] = await Promise.all([n ? H(`${n}/ping`, o) : Promise.resolve(!1), r ? H(`${r}/ping`, o) : Promise.resolve(!1)]);
241
+ let [s, c] = await Promise.all([n ? U(`${n}/ping`, o) : Promise.resolve(!1), r ? U(`${r}/ping`, o) : Promise.resolve(!1)]);
242
242
  return s ? {
243
243
  mode: "proxy",
244
244
  url: n ?? null
@@ -249,21 +249,61 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
249
249
  mode: "proxy",
250
250
  url: n ?? null
251
251
  };
252
- }, W = () => {
253
- if (typeof document > "u") return {};
254
- let { type: e, referrer: t, data: n = {} } = O({
252
+ }, G = () => {
253
+ try {
254
+ let e = window.sessionStorage.getItem(s.attribution);
255
+ if (!e) return;
256
+ let t = JSON.parse(e);
257
+ return {
258
+ type: t.type,
259
+ referrer: t.referrer,
260
+ data: t.data
261
+ };
262
+ } catch {
263
+ return;
264
+ }
265
+ }, K = (e) => {
266
+ try {
267
+ window.sessionStorage.setItem(s.attribution, JSON.stringify(e));
268
+ } catch {}
269
+ }, q = () => {
270
+ if (typeof document > "u") return {
271
+ type: "",
272
+ referrer: {},
273
+ data: {},
274
+ raw: ""
275
+ };
276
+ let { type: e, referrer: t, data: n = {} } = k({
255
277
  referrer: document.referrer,
256
278
  currentUrl: window.location.href,
257
279
  mapper: null
258
280
  }), r = Object.keys(n);
259
281
  for (let e of r) n[e] && Array.isArray(n[e]) && n[e].length > 0 && (n[e] = n[e]?.[0]);
260
- return {
282
+ if (e === "internalLink") {
283
+ let e = G();
284
+ return e ? {
285
+ type: e.type,
286
+ referrer: e.referrer ?? {},
287
+ data: e.data ?? {},
288
+ raw: document.referrer
289
+ } : {
290
+ type: "direct",
291
+ referrer: t ?? {},
292
+ data: n,
293
+ raw: document.referrer
294
+ };
295
+ }
296
+ return e && e !== "direct" && K({
261
297
  type: e,
262
298
  referrer: t,
299
+ data: n
300
+ }), {
301
+ type: e,
302
+ referrer: t ?? {},
263
303
  data: n,
264
304
  raw: document.referrer
265
305
  };
266
- }, G = () => new Promise((e, t) => {
306
+ }, J = () => new Promise((e, t) => {
267
307
  let n = "Unknown";
268
308
  function r(t) {
269
309
  e({
@@ -360,12 +400,12 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
360
400
  o() ? (n = "Safari", f()) : s() ? (n = i(), g()) : c() ? (n = "Firefox", _()) : l() ? (n = "Internet Explorer", v()) : t(/* @__PURE__ */ Error("detectIncognito cannot determine the browser"));
361
401
  }
362
402
  y();
363
- }), K = () => {
403
+ }), Y = () => {
364
404
  let e = document.createElement("canvas"), t = e?.getContext("webgl") || e?.getContext("experimental-webgl");
365
405
  if (!t) return null;
366
406
  let n = t?.getExtension("WEBGL_debug_renderer_info");
367
407
  return n ? t?.getParameter(n.UNMASKED_RENDERER_WEBGL) : null;
368
- }, q = async () => {
408
+ }, te = async () => {
369
409
  let e = "", t = "", n = !1, r = !1, i = {};
370
410
  if (!window) return {
371
411
  locale: e,
@@ -375,7 +415,7 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
375
415
  properties: i
376
416
  };
377
417
  let [a, o, s] = await Promise.allSettled([
378
- G(),
418
+ J(),
379
419
  navigator.getBattery?.() ?? Promise.reject(),
380
420
  "mediaDevices" in navigator ? navigator.mediaDevices.enumerateDevices() : Promise.reject()
381
421
  ]);
@@ -387,7 +427,7 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
387
427
  t = Intl.DateTimeFormat().resolvedOptions().timeZone;
388
428
  } catch {}
389
429
  try {
390
- e = k();
430
+ e = A();
391
431
  } catch {}
392
432
  let c = navigator, l = c.connection ?? c.mozConnection ?? c.webkitConnection;
393
433
  if (l && (i.network_type = l.effectiveType), i.width = window?.innerWidth, i.height = window?.innerHeight, i.pixel_ratio = window?.devicePixelRatio, screen && (i.screen_width = screen?.width, i.screen_height = screen?.height, i.color_depth = screen?.colorDepth, i.orientation = screen?.orientation?.type), o.status === "fulfilled") {
@@ -398,129 +438,129 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
398
438
  let e = s.value;
399
439
  i.camera_supported = e.some((e) => e?.kind === "videoinput"), i.microphone_supported = e.some((e) => e?.kind === "audioinput");
400
440
  }
401
- return i.gpu = K(), {
441
+ return i.gpu = Y(), {
402
442
  locale: e,
403
443
  timezone: t,
404
444
  dark_mode: n,
405
445
  private_mode: r,
406
446
  properties: i
407
447
  };
408
- }, J = (e, t) => {
448
+ }, X = (e, t) => {
409
449
  if (e.get()) return t(), () => {};
410
450
  let n = e.listen((e) => {
411
451
  e && (n(), t());
412
452
  });
413
453
  return n;
414
- }, Y = (n, i, a) => {
415
- let { debug: s } = o();
416
- if (!n) return null;
417
- let c = {
418
- name: n.workspace,
454
+ }, ne = (t, r, a) => {
455
+ let { debug: o } = i();
456
+ if (!t) return null;
457
+ let s = {
458
+ name: t.workspace,
419
459
  track: ({ payload: e }) => {
420
460
  if (!a()) return;
421
461
  let t = e?.properties || {};
422
- i(e.event, t);
462
+ r(e.event, t);
423
463
  },
424
464
  page: ({ payload: e }) => {
425
- a() && (r(), i("page.view", {
465
+ a() && (n(), r("page.view", {
426
466
  ...e?.properties || {},
427
467
  resource: "page"
428
468
  }));
429
469
  },
430
- identify: ({ payload: r }) => {
470
+ identify: ({ payload: n }) => {
431
471
  if (!a()) return;
432
- let { workspace: i, site_id: s, debug: c } = o(), { mode: l, isIdentified: u, isIdentifying: d, sessionToken: f, deviceToken: p, insightsBaseUrl: m, identity: h } = _(), v = x(), y = _().allTabs.length;
433
- if (!m || !h) return;
472
+ let { workspace: r, site_id: o, debug: s } = i(), { mode: l, isIdentified: u, isIdentifying: f, sessionToken: p, deviceToken: m, insightsBaseUrl: h, identity: g } = e(), _ = x(), y = C();
473
+ if (!h || !g) return;
434
474
  let b = {
435
- private_mode: r.traits?.private_mode ?? h.privateMode ?? !1,
436
- dark_mode: r.traits?.dark_mode ?? h.darkMode ?? !1,
437
- timezone: r.traits?.timezone ?? h.timezone ?? "",
438
- locale: r.traits?.locale ?? h.locale ?? "",
439
- referrer: r.traits?.referrer ?? h.referrer ?? {
475
+ private_mode: n.traits?.private_mode ?? g.privateMode ?? !1,
476
+ dark_mode: n.traits?.dark_mode ?? g.darkMode ?? !1,
477
+ timezone: n.traits?.timezone ?? g.timezone ?? "",
478
+ locale: n.traits?.locale ?? g.locale ?? "",
479
+ referrer: n.traits?.referrer ?? g.referrer ?? {
440
480
  type: "",
441
481
  referrer: {},
442
482
  data: {}
443
483
  },
444
484
  properties: {
445
- ...h.properties,
446
- ...r.traits?.properties
485
+ ...g.properties,
486
+ ...n.traits?.properties
447
487
  },
448
- workspace_id: i ?? "",
449
- site_id: s ?? void 0,
488
+ workspace_id: r ?? "",
489
+ site_id: o ?? void 0,
450
490
  hostname: window.location.hostname,
451
491
  pathname: window.location.pathname,
452
492
  page_title: document.title,
453
493
  url: window.location.href,
454
494
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
455
- tab_id: v ?? "",
495
+ tab_id: _ ?? "",
456
496
  tab_count: y ?? 0
457
497
  };
458
- if (c && console.log("[@levo-so/insights] Identification triggered:", {
498
+ if (s && console.log("[@levo-so/insights] Identification triggered:", {
459
499
  isIdentified: u,
460
- isIdentifying: d,
500
+ isIdentifying: f,
461
501
  welcomeInput: b
462
- }), u || d) return;
463
- if (e({ isIdentifying: !0 }), c) {
464
- console.log("[@levo-so/insights] Debug mode enabled. Mocking session identification."), e({
502
+ }), u || f) return;
503
+ if (v({ isIdentifying: !0 }), s) {
504
+ console.log("[@levo-so/insights] Debug mode enabled. Mocking session identification."), v({
465
505
  isIdentified: !0,
466
506
  isIdentifying: !1,
467
507
  sessionId: "debug-session",
468
508
  deviceId: "debug-device"
469
- }), t(T());
509
+ }), E(d());
470
510
  return;
471
511
  }
472
512
  let S = {
473
513
  Accept: "application/json",
474
514
  "Content-Type": "application/json",
475
- ...g({
476
- sessionToken: f,
477
- deviceToken: p,
515
+ ...c({
516
+ sessionToken: p,
517
+ deviceToken: m,
478
518
  mode: l
479
519
  })
480
520
  };
481
- n.fetch.url(m, !0).url("/v1/insights/event/welcome").options({
521
+ t.fetch.url(h, !0).url("/v1/insights/event/welcome").options({
482
522
  keepalive: !0,
483
523
  credentials: l === "proxy" ? "include" : "omit"
484
- }).headers(S).post(b).json().then((n) => {
524
+ }).headers(S).post(b).json().then((t) => {
485
525
  if (!a()) {
486
- e({ isIdentifying: !1 });
526
+ v({ isIdentifying: !1 });
487
527
  return;
488
528
  }
489
- let { isIdentified: r } = _();
490
- if (r) e({ isIdentifying: !1 });
529
+ let { isIdentified: n } = e();
530
+ if (n) v({ isIdentifying: !1 });
491
531
  else {
492
- let r = {
532
+ let e = {
493
533
  isIdentified: !0,
494
534
  isIdentifying: !1
495
535
  };
496
- if (n?.content?.data?.session && (r.sessionId = n?.content?.data?.session), n?.content?.data?.device && (r.deviceId = n?.content?.data?.device), l === "direct" && n?.content?.meta) {
497
- let e = n.content.meta;
498
- e.device_token && (r.deviceToken = e.device_token), e.session_token && (r.sessionToken = e.session_token);
536
+ if (t?.content?.data?.session && (e.sessionId = t?.content?.data?.session), t?.content?.data?.device && (e.deviceId = t?.content?.data?.device), l === "direct" && t?.content?.meta) {
537
+ let n = t.content.meta;
538
+ n.device_token && (e.deviceToken = n.device_token), n.session_token && (e.sessionToken = n.session_token);
499
539
  }
500
- e(r), t(T());
540
+ v(e), E(d());
501
541
  }
502
- }).catch((t) => {
503
- _().isIdentified || n.logger?.error("/welcome failed with error", { exception: D(t) }), e({ isIdentifying: !1 });
542
+ }).catch((n) => {
543
+ e().isIdentified || t.logger?.error("/welcome failed with error", { exception: O(n) }), v({ isIdentifying: !1 });
504
544
  });
505
545
  }
506
546
  };
507
- return A({
508
- app: n.workspace || "",
509
- debug: !!s,
510
- plugins: [c]
547
+ return j({
548
+ app: t.workspace || "",
549
+ debug: !!o,
550
+ plugins: [s]
511
551
  });
512
- }, X = null, Z = !1, Q = [], $ = (r, a) => {
513
- let { insightsUrl: d = null, debug: g = !1, site: v = null } = a;
514
- s({
515
- workspace: r.workspace,
516
- debug: g,
517
- site_id: v,
518
- insightsUrl: d ?? null
552
+ }, Z = null, Q = !1, $ = [], re = (n, s) => {
553
+ let { insightsUrl: c = null, debug: u = !1, site: m = null } = s;
554
+ a({
555
+ workspace: n.workspace,
556
+ debug: u,
557
+ site_id: m,
558
+ insightsUrl: c ?? null
519
559
  });
520
- let C = async (e, t) => {
560
+ let y = async (e, t) => {
521
561
  if (typeof window > "u") return;
522
- let { page_id: n } = o(), { allTabs: r } = _(), i = x(), a = r.length;
523
- l({
562
+ let { page_id: n } = i(), a = x(), o = C();
563
+ r({
524
564
  event: e,
525
565
  version: t?.version || 1,
526
566
  properties: {
@@ -534,82 +574,82 @@ var j = 30, M = 1e3, N = 3, P = [], F = (e, t, n) => {
534
574
  page_title: void 0,
535
575
  created_at: void 0
536
576
  },
537
- hostname: window.location.hostname,
538
- pathname: window.location.pathname,
539
577
  url: t?.url ?? window.location.href,
540
578
  page_title: t?.page_title ?? document.title,
541
579
  resource: t?.resource ?? "page",
542
- identifier: t?.id ?? n ?? void 0,
580
+ identifier: t?.identifier || n || void 0,
543
581
  created_at: t?.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
544
- tab_id: i ?? "",
545
- tab_count: a
582
+ hostname: window.location.hostname,
583
+ pathname: window.location.pathname,
584
+ tab_id: a ?? "",
585
+ tab_count: o
546
586
  });
547
- }, E = (e) => {
548
- !X || !Z || X.identify("", { withLock: e?.withLock ?? !0 });
587
+ }, w = (e) => {
588
+ !Z || !Q || Z.identify("", { withLock: e?.withLock ?? !0 });
549
589
  }, D = (e) => {
550
- let { page_id: t } = o();
551
- t !== e && i("page_id", e);
552
- }, O = () => Promise.allSettled([q(), Promise.resolve().then(() => W())]).then(([t, n]) => {
553
- let r = t.status === "fulfilled" ? t.value : null, i = n.status === "fulfilled" ? n.value : null, a = i?.type ? {
554
- type: i.type,
555
- referrer: i.referrer,
556
- data: i.data,
557
- raw: i.raw ?? ""
590
+ let { page_id: t } = i();
591
+ t !== e && o("page_id", e);
592
+ }, O = () => Promise.allSettled([te(), Promise.resolve().then(() => q())]).then(([e, t]) => {
593
+ let n = e.status === "fulfilled" ? e.value : null, r = t.status === "fulfilled" ? t.value : null, i = r?.type ? {
594
+ type: r.type,
595
+ referrer: r.referrer,
596
+ data: r.data,
597
+ raw: r.raw ?? ""
558
598
  } : null;
559
- e({ identity: {
560
- locale: r?.locale ?? null,
561
- timezone: r?.timezone ?? null,
562
- darkMode: r?.dark_mode ?? null,
563
- privateMode: r?.private_mode ?? null,
564
- referrer: a,
565
- properties: r?.properties ?? null
599
+ v({ identity: {
600
+ locale: n?.locale ?? null,
601
+ timezone: n?.timezone ?? null,
602
+ darkMode: n?.dark_mode ?? null,
603
+ privateMode: n?.private_mode ?? null,
604
+ referrer: i,
605
+ properties: n?.properties ?? null
566
606
  } });
567
607
  }), k = () => {
568
- let { insightsBaseUrl: t } = _();
569
- return t ? Promise.resolve() : U(a).then(({ mode: t, url: n }) => {
570
- Z && n && e({
571
- mode: t,
572
- insightsBaseUrl: n
608
+ let { insightsBaseUrl: t } = e();
609
+ return t ? Promise.resolve() : W(s).then(({ mode: e, url: t }) => {
610
+ Q && t && v({
611
+ mode: e,
612
+ insightsBaseUrl: t
573
613
  });
574
614
  }).catch(() => {
575
- Z && d && e({
615
+ Q && c && v({
576
616
  mode: "proxy",
577
- insightsBaseUrl: d
617
+ insightsBaseUrl: c
578
618
  });
579
619
  });
580
620
  };
581
621
  return {
582
622
  init: (t = {}) => {
583
623
  if (typeof window > "u") throw Error("[@levo-so/insights] init() should only be called on the client side.");
584
- let { pageId: i } = t;
585
- i && D(i), !Z && (r.NODE_ENV === "development" && !g || (Z = !0, Q.push(J(n, () => {
586
- if (!Z) return;
587
- let t = _();
588
- t.mode === "direct" && t.isIdentified && (!t.sessionToken || !t.deviceToken) && e({ isIdentified: !1 }), O().finally(() => {
589
- Z && u.set(!0);
624
+ let { pageId: r } = t;
625
+ r && D(r), !Q && (n.NODE_ENV === "development" && !u || (Q = !0, $.push(X(l, () => {
626
+ if (!Q) return;
627
+ let t = e();
628
+ t.mode === "direct" && t.isIdentified && (!t.sessionToken || !t.deviceToken) && v({ isIdentified: !1 }), O().finally(() => {
629
+ Q && T.set(!0);
590
630
  });
591
- })), d && Q.push(J(n, () => {
592
- Z && k().finally(() => {
593
- Z && m.set(!0);
631
+ })), c && $.push(X(l, () => {
632
+ Q && k().finally(() => {
633
+ Q && h.set(!0);
594
634
  });
595
- })), Q.push(J(p, () => {
596
- if (!Z || X) return;
597
- let e = Y(r, C, () => X === e);
598
- e && (X = e, Q.push(y(e), f(e), R(e), z(e), S(e), B(e), V(e), h(e), b(e)));
599
- })), Q.push(J(w, () => {
600
- !Z || !X || (E(), X.page({ resource: "page" }));
635
+ })), $.push(X(f, () => {
636
+ if (!Q || Z) return;
637
+ let e = ne(n, y, () => Z === e);
638
+ e && (Z = e, $.push(g(e), S(e), z(e), B(e), p(e), V(e), H(e), _(e), b(e)));
639
+ })), $.push(X(ee, () => {
640
+ !Q || !Z || (w(), Z.page({ resource: "page" }));
601
641
  }))));
602
642
  },
603
643
  destroy: () => {
604
- Z && (t(T()), c(), Q.forEach((e) => {
644
+ Q && (E(d()), t(), $.forEach((e) => {
605
645
  e();
606
- }), Q.length = 0, X = null, Z = !1, u.set(!1), m.set(!1));
646
+ }), $.length = 0, Z = null, Q = !1, T.set(!1), h.set(!1));
607
647
  },
608
- track: C,
648
+ track: y,
609
649
  get instance() {
610
- return X;
650
+ return Z;
611
651
  }
612
652
  };
613
653
  };
614
654
  //#endregion
615
- export { E as AnalyticsEvents, $ as createLevoInsights };
655
+ export { D as AnalyticsEvents, re as createLevoInsights };
@@ -18,7 +18,6 @@ interface ISession {
18
18
  deviceToken: string | null;
19
19
  /** Direct-mode JWT. Stored in localStorage; survives browser restarts. */
20
20
  sessionToken: string | null;
21
- allTabs: string[];
22
21
  mode: "proxy" | "direct";
23
22
  insightsBaseUrl: string | null;
24
23
  identity: IIdentity | null;
package/dist/stores.js CHANGED
@@ -1,2 +1,2 @@
1
- import { M as e, O as t, a as n, b as r, d as i, g as a, p as o, r as s, t as c, x as l, y as u } from "./textSelection-eSp_MmZH.js";
2
- export { o as $activity, e as $config, n as $currentTab, u as $eventQueue, a as $isIdentityContextReady, r as $lifetimePageCount, i as $pageLifecycle, s as $scrollBehavior, c as $selectedText, t as $session, l as $totalEventCount };
1
+ import { P as e, S as t, _ as n, a as r, b as i, f as a, k as o, m as s, r as c, t as l, x as u } from "./textSelection-D9P2LBjT.js";
2
+ export { s as $activity, e as $config, r as $currentTab, i as $eventQueue, n as $isIdentityContextReady, u as $lifetimePageCount, a as $pageLifecycle, c as $scrollBehavior, l as $selectedText, o as $session, t as $totalEventCount };
@@ -19,12 +19,12 @@ var o = n({
19
19
  identify: "lock:lv_aud_identify",
20
20
  open_tabs: "lv_insights_ot",
21
21
  current_tab: "lv_insights_ct",
22
- all_tabs: "lv_insights_at",
23
22
  device_token: "lv_aud_device",
24
23
  session_token: "lv_aud_session",
25
24
  mode: "lv_aud_mode",
26
25
  session: "lv_insights_session",
27
- session_flags: "lv_insights_sf"
26
+ session_flags: "lv_insights_sf",
27
+ attribution: "lv_insights_attr"
28
28
  }, f = () => {
29
29
  if (typeof window > "u" || !("localStorage" in window)) return !1;
30
30
  try {
@@ -40,7 +40,6 @@ var o = n({
40
40
  deviceId: null,
41
41
  deviceToken: null,
42
42
  sessionToken: null,
43
- allTabs: [],
44
43
  mode: "proxy",
45
44
  insightsBaseUrl: null,
46
45
  identity: null
@@ -106,12 +105,12 @@ if (f()) {
106
105
  y();
107
106
  }, 150);
108
107
  } else y();
109
- var b = () => g.get(), x = (e) => {
108
+ var b = () => g.get(), ee = (e) => {
110
109
  g.set({
111
110
  ...b(),
112
111
  ...e
113
112
  });
114
- }, ee = 10, te = 2e3, S = 500, C = e([]), w = e(0), T = t(C, (e) => e.length ?? 0), E = e(null), D = () => C.get(), ne = () => T.get(), O = (e) => {
113
+ }, te = 10, ne = 2e3, x = 1e3, S = e([]), C = e(0), w = t(S, (e) => e.length ?? 0), T = e(null), E = () => S.get(), re = () => w.get(), D = (e) => {
115
114
  let { workspace: t, debug: n, site_id: r, page_id: i } = s(), { isIdentified: a, mode: o, sessionId: c, deviceId: l, sessionToken: d, deviceToken: f, insightsBaseUrl: p } = b();
116
115
  if (e.length === 0 || !a || !p || !t) return;
117
116
  let m = e.map((e) => ({
@@ -132,7 +131,7 @@ var b = () => g.get(), x = (e) => {
132
131
  resource: e.resource ?? "page",
133
132
  properties: e.properties ?? {}
134
133
  }));
135
- if (C.set([]), n) {
134
+ if (S.set([]), n) {
136
135
  console.log("[@levo-so/insights] Debug mode enabled. Events batch:", m);
137
136
  return;
138
137
  }
@@ -157,16 +156,16 @@ var b = () => g.get(), x = (e) => {
157
156
  }).catch((e) => {
158
157
  console.warn("[@levo-so/insights] Event flush failed, batch lost:", e);
159
158
  });
160
- }, k = () => {
161
- if (E.get()) return;
159
+ }, O = () => {
160
+ if (T.get()) return;
162
161
  let e = setTimeout(() => {
163
- O(D()), E.set(null);
164
- }, te);
165
- E.set(e);
166
- }, A = () => {
167
- let e = E.get();
168
- e && (clearTimeout(e), E.set(null));
169
- }, re = (e) => {
162
+ D(E()), T.set(null);
163
+ }, ne);
164
+ T.set(e);
165
+ }, k = () => {
166
+ let e = T.get();
167
+ e && (clearTimeout(e), T.set(null));
168
+ }, ie = (e) => {
170
169
  let { debug: t, insightsUrl: n } = s();
171
170
  if (!n) {
172
171
  t && console.warn("[@levo-so/insights] Event dropped — insightsUrl not configured.", {
@@ -175,70 +174,70 @@ var b = () => g.get(), x = (e) => {
175
174
  });
176
175
  return;
177
176
  }
178
- let r = w.get();
179
- if (r >= S) {
180
- t && console.warn(`[@levo-so/core] Event limit (${S}) reached for this page`);
177
+ let r = C.get();
178
+ if (r >= x) {
179
+ t && console.warn(`[@levo-so/core] Event limit (${x}) reached for this page`);
181
180
  return;
182
181
  }
183
- w.set(r + 1);
184
- let i = C.get();
185
- C.set([...i, e]);
182
+ C.set(r + 1);
183
+ let i = S.get();
184
+ S.set([...i, e]);
186
185
  let { isIdentified: a } = b();
187
- ne() >= ee ? (A(), O(D())) : a && k();
188
- }, ie = () => w.set(0), j = g.get().isIdentified;
186
+ re() >= te ? (k(), D(E())) : a && O();
187
+ }, ae = () => C.set(0), oe = g.get().isIdentified;
189
188
  g.subscribe((e) => {
190
- e.isIdentified && !j && C.get().length > 0 && (e.insightsBaseUrl ? O(D()) : k()), j = e.isIdentified;
189
+ e.isIdentified && !oe && S.get().length > 0 && (e.insightsBaseUrl ? D(E()) : O()), oe = e.isIdentified;
191
190
  });
192
- var M = g.get().insightsBaseUrl;
191
+ var se = g.get().insightsBaseUrl;
193
192
  g.subscribe((e) => {
194
- e.insightsBaseUrl && !M && e.isIdentified && C.get().length > 0 && O(D()), M = e.insightsBaseUrl;
193
+ e.insightsBaseUrl && !se && e.isIdentified && S.get().length > 0 && D(E()), se = e.insightsBaseUrl;
195
194
  });
196
- var ae = () => {
197
- C.set([]), w.set(0), A();
198
- }, N = e(!1), oe = e(!1), se = t([v, N], (e, t) => e && t), ce = t([se, oe], (e, t) => e && t), P = n({
195
+ var ce = () => {
196
+ S.set([]), C.set(0), k();
197
+ }, le = e(!1), A = e(!1), j = t([v, le], (e, t) => e && t), ue = t([j, A], (e, t) => e && t), M = n({
199
198
  status: "active",
200
199
  seconds: 0
201
- }), le = (e) => {
200
+ }), de = (e) => {
202
201
  if (typeof window > "u") return () => {};
203
202
  let t = r({
204
203
  timeout: 6e4,
205
204
  throttle: 1e3,
206
205
  onIdle: (e) => {
207
- P.set({
206
+ M.set({
208
207
  status: "idle",
209
208
  seconds: e
210
209
  });
211
210
  },
212
211
  onWakeUp: (e) => {
213
- P.set({
212
+ M.set({
214
213
  status: "active",
215
214
  seconds: e
216
215
  });
217
216
  }
218
- }), n = P.listen((t) => {
217
+ }), n = M.listen((t) => {
219
218
  t.seconds > 0 && (t.status === "idle" ? e.track("user.idle", { seconds_active: t.seconds }) : e.track("user.active", { seconds_idle: t.seconds }));
220
219
  });
221
220
  return () => {
222
- n(), t.disable(), P.set({
221
+ n(), t.disable(), M.set({
223
222
  status: "active",
224
223
  seconds: 0
225
224
  });
226
225
  };
227
- }, ue = () => P.get(), F = /* @__PURE__ */ function(e) {
226
+ }, fe = () => M.get(), N = /* @__PURE__ */ function(e) {
228
227
  return e.ACTIVE = "active", e.PASSIVE = "passive", e.HIDDEN = "hidden", e.FROZEN = "frozen", e.TERMINATED = "terminated", e;
229
- }(F || {}), I = e(F.ACTIVE), de = () => typeof document > "u" ? F.ACTIVE : document.visibilityState === "hidden" ? F.HIDDEN : document.hasFocus() ? F.ACTIVE : F.PASSIVE, fe = (e) => {
228
+ }(N || {}), P = e(N.ACTIVE), pe = () => typeof document > "u" ? N.ACTIVE : document.visibilityState === "hidden" ? N.HIDDEN : document.hasFocus() ? N.ACTIVE : N.PASSIVE, me = (e) => {
230
229
  if (typeof window > "u") return () => {};
231
230
  let t = !1, n = (n) => {
232
231
  if (t) return;
233
232
  t = !0;
234
- let r = ue(), a = {};
235
- r.seconds > 0 && (r.status === "idle" ? a.seconds_idle = r.seconds : a.seconds_active = r.seconds);
233
+ let r = fe(), a = {};
234
+ r.seconds > 0 && (r.status === "idle" ? a.seconds_active = r.seconds : a.seconds_idle = r.seconds);
236
235
  try {
237
236
  e.track("page.bounce", {
238
237
  resource: "page",
239
238
  ...a,
240
239
  ...n
241
- }, {}, () => O(D()));
240
+ }, {}, () => D(E()));
242
241
  } catch (e) {
243
242
  console.error("[insights] Failed to send bounce event", i(e));
244
243
  }
@@ -262,13 +261,13 @@ var ae = () => {
262
261
  case "blur":
263
262
  case "focus":
264
263
  case "visibilitychange":
265
- I.set(de());
264
+ P.set(pe());
266
265
  break;
267
266
  case "freeze":
268
- I.set(F.FROZEN);
267
+ P.set(N.FROZEN);
269
268
  break;
270
269
  case "pagehide":
271
- I.set(e.persisted ? F.FROZEN : F.TERMINATED);
270
+ P.set(e.persisted ? N.FROZEN : N.TERMINATED);
272
271
  break;
273
272
  }
274
273
  }, s = [
@@ -283,32 +282,32 @@ var ae = () => {
283
282
  s.forEach((e) => {
284
283
  window.addEventListener(e, a, { capture: !0 });
285
284
  }), window.addEventListener("beforeunload", r);
286
- let c = I.listen((e) => {
287
- e === F.FROZEN || e === F.TERMINATED ? n({
285
+ let c = P.listen((e) => {
286
+ e === N.FROZEN || e === N.TERMINATED ? n({
288
287
  state: e,
289
288
  trigger: "lifecycle"
290
- }) : e === F.HIDDEN && O(D());
289
+ }) : e === N.HIDDEN && D(E());
291
290
  }), l = o.get().page_id, u = o.listen((e) => {
292
291
  e.page_id !== l && (t = !1, l = e.page_id);
293
- }), d = w.listen((e) => {
292
+ }), d = C.listen((e) => {
294
293
  e === 0 && (t = !1);
295
294
  });
296
295
  return () => {
297
296
  s.forEach((e) => {
298
297
  window.removeEventListener(e, a, { capture: !0 });
299
- }), window.removeEventListener("beforeunload", r), c(), u(), d(), t = !1, I.set(F.ACTIVE);
298
+ }), window.removeEventListener("beforeunload", r), c(), u(), d(), t = !1, P.set(N.ACTIVE);
300
299
  };
301
- }, L = (e) => Object.prototype.toString.call(e) == "[object String]", pe = (e, t) => e.indexOf(t) !== -1, R = (e) => e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""), z = (e) => e === void 0, me = (e) => e === null, B = (e) => z(e) || me(e), V = (e) => !!e && e?.nodeType === 3;
302
- function he(e) {
300
+ }, F = (e) => Object.prototype.toString.call(e) == "[object String]", he = (e, t) => e.indexOf(t) !== -1, I = (e) => e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""), L = (e) => e === void 0, ge = (e) => e === null, R = (e) => L(e) || ge(e), z = (e) => !!e && e?.nodeType === 3;
301
+ function _e(e) {
303
302
  return !!e && e?.nodeType === 1;
304
303
  }
305
- function ge(e, t) {
304
+ function ve(e, t) {
306
305
  return !!e && !!e?.tagName && e?.tagName.toLowerCase() === t.toLowerCase();
307
306
  }
308
- function _e(e) {
309
- return e ? R(e).split(/\s+/) : [];
307
+ function ye(e) {
308
+ return e ? I(e).split(/\s+/) : [];
310
309
  }
311
- function ve(e) {
310
+ function be(e) {
312
311
  let t = "";
313
312
  switch (typeof e?.className) {
314
313
  case "string":
@@ -319,9 +318,9 @@ function ve(e) {
319
318
  break;
320
319
  default: t = "";
321
320
  }
322
- return _e(t);
321
+ return ye(t);
323
322
  }
324
- var H = (e, t = 255) => B(e) ? "" : R(e).split(/(\s+)/).filter((e) => J(e)).join("").replace(/[\r\n]/g, " ").replace(/[ ]+/g, " ").substring(0, t), U = [
323
+ var B = (e, t = 255) => R(e) ? "" : I(e).split(/(\s+)/).filter((e) => K(e)).join("").replace(/[\r\n]/g, " ").replace(/[ ]+/g, " ").substring(0, t), V = [
325
324
  "span",
326
325
  "strong",
327
326
  "em",
@@ -333,25 +332,25 @@ var H = (e, t = 255) => B(e) ? "" : R(e).split(/(\s+)/).filter((e) => J(e)).join
333
332
  "sub",
334
333
  "sup",
335
334
  "label"
336
- ], W = (e) => {
335
+ ], H = (e) => {
337
336
  let t = "";
338
- if (xe(e) && e?.childNodes && e?.childNodes.length) {
339
- for (let n of e.childNodes) V(n) && n.textContent && (t += H(n.textContent) ?? "");
340
- if (!R(t)) {
341
- let n = e.querySelectorAll(U.join(", "));
337
+ if (Ce(e) && e?.childNodes && e?.childNodes.length) {
338
+ for (let n of e.childNodes) z(n) && n.textContent && (t += B(n.textContent) ?? "");
339
+ if (!I(t)) {
340
+ let n = e.querySelectorAll(V.join(", "));
342
341
  for (let e of n) {
343
342
  let n = e.textContent;
344
- n && (t += `${H(n)} `);
343
+ n && (t += `${B(n)} `);
345
344
  }
346
345
  }
347
- if (!R(t) && e instanceof HTMLElement) {
346
+ if (!I(t) && e instanceof HTMLElement) {
348
347
  let n = e.innerText;
349
- n && (t = H(n.substring(0, 500)));
348
+ n && (t = B(n.substring(0, 500)));
350
349
  }
351
350
  }
352
- return R(t);
353
- }, G = (e) => z(e.target) ? e?.srcElement || null : e?.composedPath?.()[0] || e?.target || null, ye = (e, t) => {
354
- if (!window || !e || ge(e, "html") || !he(e)) return !1;
351
+ return I(t);
352
+ }, U = (e) => L(e.target) ? e?.srcElement || null : e?.composedPath?.()[0] || e?.target || null, xe = (e, t) => {
353
+ if (!window || !e || ve(e, "html") || !_e(e)) return !1;
355
354
  let n = window.getComputedStyle(e);
356
355
  if (n && n.getPropertyValue("cursor") === "pointer" && t.type === "click") return !0;
357
356
  switch (e?.tagName.toLowerCase()) {
@@ -362,7 +361,7 @@ var H = (e, t = 255) => B(e) ? "" : R(e).split(/(\s+)/).filter((e) => J(e)).join
362
361
  case "textarea": return ["change", "click"].includes(t.type);
363
362
  default: return t.type === "click";
364
363
  }
365
- }, be = new Set([
364
+ }, Se = new Set([
366
365
  "password",
367
366
  "passwd",
368
367
  "pwd",
@@ -383,73 +382,73 @@ var H = (e, t = 255) => B(e) ? "" : R(e).split(/(\s+)/).filter((e) => J(e)).join
383
382
  "apikey",
384
383
  "accesstoken"
385
384
  ]);
386
- function xe(e) {
387
- if (pe(ve(e), "ph-include")) return !0;
385
+ function Ce(e) {
386
+ if (he(be(e), "ph-include")) return !0;
388
387
  let t = e.type || "";
389
- if (L(t)) switch (t.toLowerCase()) {
388
+ if (F(t)) switch (t.toLowerCase()) {
390
389
  case "hidden": return !1;
391
390
  case "password": return !1;
392
391
  }
393
392
  let n = e.name || e?.id || "";
394
- if (L(n)) {
393
+ if (F(n)) {
395
394
  let e = n.toLowerCase().replace(/[^a-z]/g, "");
396
- if (be.has(e)) return !1;
395
+ if (Se.has(e)) return !1;
397
396
  }
398
397
  return !0;
399
398
  }
400
- var K = "(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11})", Se = RegExp(`^(?:${K})$`), Ce = new RegExp(K), q = "\\d{3}-?\\d{2}-?\\d{4}", we = RegExp(`^(${q})$`), Te = RegExp(`(${q})`), J = (e, t = !0) => !(B(e) || L(e) && (e = R(e), (t ? Se : Ce).test((e || "").replace(/[- ]/g, "")) || (t ? we : Te).test(e))), Ee = (e) => L(e) ? e.substring(0, 10) === "_ngcontent" || e.substring(0, 7) === "_nghost" : !1, De = (e) => {
401
- let t = W(e);
402
- return t = `${t} ${Y(e)}`.trim(), J(t) ? t : "";
399
+ var W = "(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11})", we = RegExp(`^(?:${W})$`), Te = new RegExp(W), G = "\\d{3}-?\\d{2}-?\\d{4}", Ee = RegExp(`^(${G})$`), De = RegExp(`(${G})`), K = (e, t = !0) => !(R(e) || F(e) && (e = I(e), (t ? we : Te).test((e || "").replace(/[- ]/g, "")) || (t ? Ee : De).test(e))), Oe = (e) => F(e) ? e.substring(0, 10) === "_ngcontent" || e.substring(0, 7) === "_nghost" : !1, ke = (e) => {
400
+ let t = H(e);
401
+ return t = `${t} ${q(e)}`.trim(), K(t) ? t : "";
403
402
  };
404
- function Y(e) {
403
+ function q(e) {
405
404
  let t = "";
406
405
  if (e?.childNodes.length) for (let n of e.childNodes) {
407
406
  let e = n.tagName?.toLowerCase();
408
- if (n && e && U.includes(e)) try {
409
- let e = W(n);
410
- t = `${t} ${e}`.trim(), n?.childNodes.length && (t = `${t} ${Y(n)}`.trim());
407
+ if (n && e && V.includes(e)) try {
408
+ let e = H(n);
409
+ t = `${t} ${e}`.trim(), n?.childNodes.length && (t = `${t} ${q(n)}`.trim());
411
410
  } catch {}
412
411
  }
413
412
  return t;
414
413
  }
415
- var Oe = (e) => {
416
- let t = e.tagName?.toLowerCase(), n = t === "a" || t === "button" ? De(e) : W(e);
414
+ var Ae = (e) => {
415
+ let t = e.tagName?.toLowerCase(), n = t === "a" || t === "button" ? ke(e) : H(e);
417
416
  if (n) return {
418
417
  text: n,
419
418
  source: "text"
420
419
  };
421
420
  let r = e.getAttribute("aria-label");
422
421
  if (r) return {
423
- text: H(r),
422
+ text: B(r),
424
423
  source: "aria-label"
425
424
  };
426
425
  let i = e.getAttribute("title");
427
426
  if (i) return {
428
- text: H(i),
427
+ text: B(i),
429
428
  source: "title"
430
429
  };
431
430
  let a = e.getAttribute("data-track-label");
432
431
  if (a) return {
433
- text: H(a),
432
+ text: B(a),
434
433
  source: "data-track-label"
435
434
  };
436
435
  let o = e.querySelector("img[alt], svg[aria-label]");
437
436
  if (o) {
438
437
  let e = o.getAttribute("alt") || o.getAttribute("aria-label");
439
438
  if (e) return {
440
- text: H(e),
439
+ text: B(e),
441
440
  source: "alt"
442
441
  };
443
442
  }
444
443
  let s = e.getAttribute("placeholder");
445
444
  if (s) return {
446
- text: H(s),
445
+ text: B(s),
447
446
  source: "placeholder"
448
447
  };
449
448
  if (e instanceof HTMLElement && e.innerText) {
450
449
  let t = e.innerText.substring(0, 500);
451
450
  if (t.trim()) return {
452
- text: H(t),
451
+ text: B(t),
453
452
  source: "innerText"
454
453
  };
455
454
  }
@@ -457,76 +456,108 @@ var Oe = (e) => {
457
456
  text: "",
458
457
  source: "none"
459
458
  };
460
- }, ke = [
459
+ }, je = [
461
460
  "input",
462
461
  "textarea",
463
462
  "select"
464
- ], X = (e) => {
465
- let t = e?.tagName?.toLowerCase(), n = { element: t }, r = Oe(e);
463
+ ], J = (e) => {
464
+ let t = e?.tagName?.toLowerCase(), n = { element: t }, r = Ae(e);
466
465
  n.text = r.text, n.text_source = r.source;
467
466
  let i = e.getAttribute("aria-label");
468
- i && (n.aria_label = H(i));
467
+ i && (n.aria_label = B(i));
469
468
  let a = e.getAttribute("role");
470
469
  a && (n.role = a);
471
470
  let o = {};
472
- for (let t of e.attributes) (t.name.startsWith("data-track-") || t.name.startsWith("data-analytics-")) && (o[t.name] = H(t.value));
471
+ for (let t of e.attributes) (t.name.startsWith("data-track-") || t.name.startsWith("data-analytics-")) && (o[t.name] = B(t.value));
473
472
  if (Object.keys(o).length && (n.tracking_data = o), n.attributes = Array.from(e.attributes).reduce((e, t) => {
474
- if (J(t.value) && !Ee(t.name)) {
473
+ if (K(t.value) && !Oe(t.name)) {
475
474
  if (t.name === "class" || t.name.startsWith("data-track-") || t.name.startsWith("data-analytics-")) return e;
476
- e[t.name] = typeof t?.value == "string" ? H(t.value) : t.value;
475
+ e[t.name] = typeof t?.value == "string" ? B(t.value) : t.value;
477
476
  }
478
477
  return e;
479
- }, {}), ke.includes(t) || e?.getAttribute("contenteditable") === "true") {
478
+ }, {}), je.includes(t) || e?.getAttribute("contenteditable") === "true") {
480
479
  let t;
481
- e.matches("input[type=\"checkbox\"], input[type=\"radio\"]") ? t = e.checked : e.matches("input, select, textarea") && !e.matches("input[type=\"password\"]") && (t = e.value), t !== void 0 && (n.value = typeof t == "string" || t === null ? H(t, 1024) : t);
480
+ e.matches("input[type=\"checkbox\"], input[type=\"radio\"]") ? t = e.checked : e.matches("input, select, textarea") && !e.matches("input[type=\"password\"]") && (t = e.value), t !== void 0 && (n.value = typeof t == "string" || t === null ? B(t, 1024) : t);
482
481
  let r = "";
483
- n?.attributes.id && (r = H(document.querySelector(`label[for="${n?.attributes.id}"]`)?.textContent?.trim() || ""));
482
+ n?.attributes.id && (r = B(document.querySelector(`label[for="${n?.attributes.id}"]`)?.textContent?.trim() || ""));
484
483
  let i = e.closest("label");
485
- r ||= H(i?.textContent?.trim() || ""), r && (n.label = r);
484
+ r ||= B(i?.textContent?.trim() || ""), r && (n.label = r);
486
485
  }
487
486
  return n;
488
- }, Ae = (e, t) => {
489
- let n = G(e);
490
- V(n) && (n = n.parentNode || null);
487
+ }, Me = (e, t) => {
488
+ let n = U(e);
489
+ z(n) && (n = n.parentNode || null);
491
490
  let r = ["copy", "cut"].includes(t);
492
- if (n && (r || ye(n, e))) {
493
- let t = X(n);
491
+ if (n && (r || xe(n, e))) {
492
+ let t = J(n);
494
493
  if (r) {
495
- let n = H(window?.getSelection()?.toString()), r = e?.type || "clipboard";
494
+ let n = B(window?.getSelection()?.toString()), r = e?.type || "clipboard";
496
495
  t.copy_text = n ?? "", t.copy_type = r ?? "";
497
496
  }
498
497
  return t;
499
498
  }
500
499
  return null;
501
- }, Z = e(null), je = (e) => {
500
+ }, Y = e(null), Ne = 3e3, Pe = 1e4, Fe = `${d.open_tabs}:`, X = (e) => `${Fe}${e}`, Ie = (e) => {
501
+ try {
502
+ localStorage.setItem(X(e), String(Date.now()));
503
+ } catch {}
504
+ }, Z = (e) => {
505
+ try {
506
+ localStorage.removeItem(X(e));
507
+ } catch {}
508
+ }, Le = (e) => {
509
+ try {
510
+ let t = Number(localStorage.getItem(X(e)));
511
+ return Number.isFinite(t) && Date.now() - t <= Pe;
512
+ } catch {
513
+ return !1;
514
+ }
515
+ }, Re = () => {
516
+ try {
517
+ let e = Date.now(), t = 0, n = [];
518
+ for (let r = 0; r < localStorage.length; r++) {
519
+ let i = localStorage.key(r);
520
+ if (!i || !i.startsWith(Fe)) continue;
521
+ let a = Number(localStorage.getItem(i));
522
+ Number.isFinite(a) && e - a <= Pe ? t += 1 : n.push(i);
523
+ }
524
+ for (let e of n) try {
525
+ localStorage.removeItem(e);
526
+ } catch {}
527
+ return Math.max(1, t);
528
+ } catch {
529
+ return 1;
530
+ }
531
+ }, ze = 1, Be = () => {
532
+ ze = Re();
533
+ }, Ve = (e) => {
502
534
  if (typeof window > "u") return () => {};
503
535
  let t = null;
504
536
  try {
505
537
  t = sessionStorage.getItem(d.current_tab);
506
538
  } catch {}
507
- let { allTabs: n } = g.get();
508
- if (t && n.includes(t) && (t = null), !t) {
539
+ if (t && Le(t) && (t = null), !t) {
509
540
  t = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
510
541
  try {
511
542
  sessionStorage.setItem(d.current_tab, t);
512
543
  } catch {}
513
544
  }
514
- let r = t;
515
- Z.set(r), n.includes(r) || x({ allTabs: [...n, r] });
516
- let i = () => {
517
- if (!r) return;
518
- let { allTabs: e } = g.get();
519
- e.includes(r) && x({ allTabs: e.filter((e) => e !== r) });
545
+ let n = t;
546
+ Y.set(n), Ie(n), Be();
547
+ let r = window.setInterval(() => {
548
+ Ie(n), Be();
549
+ }, Ne), i = (e) => {
550
+ e.persisted || Z(n);
520
551
  };
521
- return window.addEventListener("beforeunload", i), () => {
522
- window.removeEventListener("beforeunload", i), i(), Z.set(null);
552
+ return window.addEventListener("beforeunload", i), window.addEventListener("pagehide", i), () => {
553
+ window.clearInterval(r), window.removeEventListener("beforeunload", i), window.removeEventListener("pagehide", i), Z(n), Y.set(null);
523
554
  };
524
- }, Me = () => Z.get(), Q = n({
555
+ }, He = () => Y.get(), Ue = () => ze, Q = n({
525
556
  depth: 0,
526
557
  milestone: 0,
527
558
  speedCategory: "read",
528
559
  direction: "down"
529
- }), Ne = (e) => {
560
+ }), We = (e) => {
530
561
  if (typeof window > "u") return () => {};
531
562
  let t = {
532
563
  prevMilestone: -1,
@@ -563,7 +594,7 @@ var Oe = (e) => {
563
594
  direction: "down"
564
595
  });
565
596
  };
566
- }, $ = e(null), Pe = (e) => {
597
+ }, $ = e(null), Ge = (e) => {
567
598
  if (typeof window > "u") return () => {};
568
599
  let t = a(() => {
569
600
  let e = window.getSelection();
@@ -574,11 +605,11 @@ var Oe = (e) => {
574
605
  }, 1e3);
575
606
  document.addEventListener("selectionchange", t);
576
607
  let n = $.listen((t) => {
577
- t && e.track("page.selection", { text: H(t) });
608
+ t && e.track("page.selection", { text: B(t) });
578
609
  });
579
610
  return () => {
580
611
  document.removeEventListener("selectionchange", t), t.cancel(), n(), $.set(null);
581
612
  };
582
613
  };
583
614
  //#endregion
584
- export { x as A, O as C, v as D, ie as E, l as F, o as M, s as N, g as O, c as P, ae as S, re as T, N as _, Z as a, w as b, X as c, I as d, fe as f, se as g, oe as h, Ne as i, u as j, b as k, G as l, le as m, Pe as n, Me as o, P as p, Q as r, je as s, $ as t, Ae as u, ce as v, D as w, T as x, C as y };
615
+ export { b as A, ce as C, ae as D, ie as E, s as F, c as I, l as L, d as M, u as N, v as O, o as P, w as S, E as T, j as _, Y as a, S as b, Ve as c, Me as d, P as f, A as g, de as h, We as i, ee as j, g as k, J as l, M as m, Ge as n, He as o, me as p, Q as r, Ue as s, $ as t, U as u, le as v, D as w, C as x, ue as y };
@@ -6,11 +6,12 @@ import { AnalyticsInstance } from 'analytics';
6
6
  declare const $currentTab: import('nanostores').PreinitializedWritableAtom<string | null> & object;
7
7
  /**
8
8
  * Initialize current tab tracking.
9
- * Assumes the session store is already synchronized (isReady).
10
9
  *
11
10
  * @param _instance - Analytics instance
12
11
  * @returns Cleanup function
13
12
  */
14
13
  declare const initCurrentTabTracker: (_instance: AnalyticsInstance) => () => void;
15
14
  declare const getCurrentTab: () => string | null;
16
- export { $currentTab, initCurrentTabTracker, getCurrentTab };
15
+ /** Number of tabs currently open for this site (live heartbeats, cached snapshot). */
16
+ declare const getTabCount: () => number;
17
+ export { $currentTab, initCurrentTabTracker, getCurrentTab, getTabCount };
@@ -1,3 +1,4 @@
1
+ import { IIdentityReferrer } from '../types/identity';
1
2
  /**
2
3
  * Extract referrer information for the current page.
3
4
  *
@@ -9,16 +10,21 @@
9
10
  * Handles array-to-string conversion for UTM params that may come
10
11
  * as arrays from the parser.
11
12
  *
13
+ * GA-parity (last-non-direct-click): getVisitorSource returns `internalLink` whenever
14
+ * document.referrer is the SAME site — which happens when the server cuts a session
15
+ * (30-min inactivity) and the visitor keeps browsing. Google Analytics has no
16
+ * `internalLink` channel; it carries the ORIGINAL source forward. So we remember the
17
+ * last real (non-direct, non-internal) channel and restore it in place of
18
+ * `internalLink`, falling back to `direct` when there is no prior source.
19
+ *
20
+ * Example: land via google (`search`) → idle 31 min → keep clicking → the server cuts
21
+ * a new session and document.referrer is the prior same-site page (`internalLink`).
22
+ * We report `search` (matching GA) instead of inventing an `internalLink` channel.
23
+ *
24
+ * Returns one concrete shape for every branch (SSR early-return, internalLink
25
+ * restore, normal). `type` is "" when there is no referrer, so the consumer's
26
+ * `referrer?.type` guard still treats it as "no referrer".
27
+ *
12
28
  * @returns Referrer information object
13
29
  */
14
- export declare const getReferrer: () => {
15
- type?: undefined;
16
- referrer?: undefined;
17
- data?: undefined;
18
- raw?: undefined;
19
- } | {
20
- type: string;
21
- referrer: Record<string, string>;
22
- data: Record<string, string>;
23
- raw: string;
24
- };
30
+ export declare const getReferrer: () => IIdentityReferrer;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@levo-so/insights",
3
3
  "description": "Levo analytics",
4
- "version": "0.1.109",
4
+ "version": "0.1.111",
5
5
  "author": "Levo Engineering <devs@theinternetfolks.com>",
6
6
  "dependencies": {
7
7
  "@analytics/activity-utils": "0.2.2",
@@ -17,8 +17,8 @@
17
17
  "typescript": "5.9.3",
18
18
  "vite": "8.0.8",
19
19
  "vite-plugin-dts": "4.5.4",
20
- "@levo-so/core": "0.1.109",
21
- "@levo/ts-config": "0.0.0"
20
+ "@levo/ts-config": "0.0.0",
21
+ "@levo-so/core": "0.1.111"
22
22
  },
23
23
  "exports": {
24
24
  ".": {
@@ -38,7 +38,7 @@
38
38
  "main": "./dist/index.js",
39
39
  "module": "./dist/index.js",
40
40
  "peerDependencies": {
41
- "@levo-so/core": "0.1.109"
41
+ "@levo-so/core": "0.1.111"
42
42
  },
43
43
  "publishConfig": {
44
44
  "access": "public"