@levo-so/insights 0.1.82 → 0.1.85

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,3 +1,7 @@
1
+ /**
2
+ * Runtime configuration for the insights module.
3
+ * Set once on init and updated on SPA navigations (page_id).
4
+ */
1
5
  interface IInsightsConfig {
2
6
  workspace: string | null;
3
7
  page_id: string | null;
@@ -1,18 +1,18 @@
1
1
  import { ILevoAudience } from '../types/analytics';
2
+ /**
3
+ * An event pending delivery in the queue.
4
+ * Carries only the data captured at track time; store context (session, workspace, etc.)
5
+ * is merged in at flush time by {@link flushEventQueue}.
6
+ */
2
7
  type IQueuedEvent = Partial<ILevoAudience.CollectInput> & {
8
+ /** The event name (e.g. "page.view", "element.click"). */
3
9
  event: string;
10
+ /** ISO 8601 timestamp when the event was recorded. */
4
11
  created_at: string;
5
12
  };
6
13
  declare const $eventQueue: import('nanostores').PreinitializedWritableAtom<IQueuedEvent[]> & object;
7
14
  declare const $lifetimePageCount: import('nanostores').PreinitializedWritableAtom<number> & object;
8
15
  declare const $totalEventCount: import('nanostores').ReadableAtom<number>;
9
- /**
10
- * Initialize a listener that flushes the event queue immediately
11
- * when the session becomes identified.
12
- *
13
- * @returns Unsubscribe function
14
- */
15
- declare const initSessionFlushListener: () => () => void;
16
16
  declare const getEventQueue: () => IQueuedEvent[];
17
17
  /**
18
18
  * Flush all queued events to the server.
@@ -42,4 +42,5 @@ declare const resetRateLimit: () => void;
42
42
  * Clear all pending events and reset state.
43
43
  */
44
44
  export declare const clearEventQueue: () => void;
45
- export { $eventQueue, $lifetimePageCount, $totalEventCount, queueEvent, resetRateLimit, initSessionFlushListener, getEventQueue, flushEventQueue, type IQueuedEvent, };
45
+ export type { IQueuedEvent };
46
+ export { $eventQueue, $lifetimePageCount, $totalEventCount, queueEvent, resetRateLimit, getEventQueue, flushEventQueue, };
@@ -1,10 +1,22 @@
1
1
  import { IIdentity } from '../types/identity';
2
2
  interface ISession {
3
+ /**
4
+ * True once /welcome has succeeded for this browser session.
5
+ * Persisted in sessionStorage (not localStorage) so it resets when the browser
6
+ * session ends — matching the lifetime of the HttpOnly proxy cookie.
7
+ */
3
8
  isIdentified: boolean;
9
+ /**
10
+ * True while a /welcome request is in flight.
11
+ * Always reset to false on page load; no fetch survives a reload, so a
12
+ * persisted true is always a stale lock from a previous page load or crash.
13
+ */
4
14
  isIdentifying: boolean;
5
15
  sessionId: string | null;
6
16
  deviceId: string | null;
17
+ /** Direct-mode JWT. Stored in localStorage; survives browser restarts. */
7
18
  deviceToken: string | null;
19
+ /** Direct-mode JWT. Stored in localStorage; survives browser restarts. */
8
20
  sessionToken: string | null;
9
21
  allTabs: string[];
10
22
  mode: "proxy" | "direct";
@@ -12,10 +24,17 @@ interface ISession {
12
24
  identity: IIdentity | null;
13
25
  }
14
26
  declare const $session: import('nanostores').PreinitializedWritableAtom<ISession> & object;
27
+ /** Calls cb immediately if the session is already loaded, otherwise queues it. */
15
28
  declare const onSessionReady: (cb: () => void) => void;
16
29
  declare const isSessionReady: () => boolean;
17
30
  declare const getSession: () => ISession;
18
31
  declare const updateSession: (v: Partial<ISession>) => void;
19
32
  export { $session, getSession, updateSession, onSessionReady, isSessionReady, type ISession };
33
+ /**
34
+ * True when the analytics instance has everything it needs to call /welcome and
35
+ * start tracking: a resolved base URL (from detectMode) and identity data (from
36
+ * getUserProperties + getReferrer). Both are set asynchronously on init and may
37
+ * arrive in either order — this computed fires once both are present.
38
+ */
20
39
  declare const $isContextReady: import('nanostores').ReadableAtom<boolean>;
21
40
  export { $isContextReady };
package/dist/stores.js CHANGED
@@ -1,14 +1,2 @@
1
- import { $ as s, a as t, b as $, c as i, d as o, e as c, f as n, g as l, h as r, i as f, j as u } from "./textSelection-N3vefCQW.js";
2
- export {
3
- s as $activity,
4
- t as $config,
5
- $ as $currentTab,
6
- i as $eventQueue,
7
- o as $isContextReady,
8
- c as $lifetimePageCount,
9
- n as $pageLifecycle,
10
- l as $scrollBehavior,
11
- r as $selectedText,
12
- f as $session,
13
- u as $totalEventCount
14
- };
1
+ import { C as e, _ as t, a as n, d as r, g as i, h as a, k as o, p as s, r as c, t as l, w as u } from "./textSelection-DJ23SrA-.js";
2
+ export { s as $activity, o as $config, n as $currentTab, a as $eventQueue, e as $isContextReady, i as $lifetimePageCount, r as $pageLifecycle, c as $scrollBehavior, l as $selectedText, u as $session, t as $totalEventCount };
@@ -0,0 +1,558 @@
1
+ import { atom as e, computed as t, map as n } from "nanostores";
2
+ import { onUserActivity as r } from "@analytics/activity-utils";
3
+ import { getLevoError as i } from "@levo-so/core";
4
+ import { debounce as a } from "lodash-es";
5
+ //#region src/stores/config.ts
6
+ var o = n({
7
+ workspace: null,
8
+ page_id: null,
9
+ site_id: null,
10
+ debug: !1
11
+ }), s = () => o.get(), c = (e) => o.set({
12
+ ...s(),
13
+ ...e
14
+ }), l = (e, t) => o.setKey(e, t), u = ({ deviceToken: e, sessionToken: t, mode: n }) => {
15
+ let r = {};
16
+ return n === "direct" && (e && (r["Levo-Audience-Device"] = e), t && (r["Levo-Audience-Session"] = t)), r;
17
+ }, d = {
18
+ identify: "lock:lv_aud_identify",
19
+ open_tabs: "lv_insights_ot",
20
+ current_tab: "lv_insights_ct",
21
+ all_tabs: "lv_insights_at",
22
+ device_token: "lv_aud_device",
23
+ session_token: "lv_aud_session",
24
+ mode: "lv_aud_mode",
25
+ session: "lv_insights_session",
26
+ session_flags: "lv_insights_sf"
27
+ }, f = () => {
28
+ if (typeof window > "u" || !("localStorage" in window)) return !1;
29
+ try {
30
+ let e = "__levo_test__";
31
+ return window.localStorage.setItem(e, "test"), window.localStorage.removeItem(e), !0;
32
+ } catch {
33
+ return !1;
34
+ }
35
+ }, p = {
36
+ isIdentified: !1,
37
+ isIdentifying: !1,
38
+ sessionId: null,
39
+ deviceId: null,
40
+ deviceToken: null,
41
+ sessionToken: null,
42
+ allTabs: [],
43
+ mode: "proxy",
44
+ insightsBaseUrl: null,
45
+ identity: null
46
+ }, m = JSON.stringify, h = (e) => {
47
+ if (!e) return p;
48
+ try {
49
+ return JSON.parse(e);
50
+ } catch {
51
+ return p;
52
+ }
53
+ }, g = e(p), _ = g.set, v = !1, y = [], b = () => {
54
+ v || (v = !0, y.forEach((e) => {
55
+ e();
56
+ }), y.length = 0);
57
+ }, ee = (e) => {
58
+ v ? e() : y.push(e);
59
+ };
60
+ if (f()) {
61
+ let e = () => {
62
+ try {
63
+ let e = sessionStorage.getItem(d.session_flags);
64
+ if (!e) return { isIdentified: !1 };
65
+ let t = JSON.parse(e);
66
+ return t.v === 1 ? { isIdentified: t.isIdentified ?? !1 } : { isIdentified: !1 };
67
+ } catch {
68
+ return { isIdentified: !1 };
69
+ }
70
+ }, t = (e) => {
71
+ try {
72
+ sessionStorage.setItem(d.session_flags, m({
73
+ v: 1,
74
+ isIdentified: e
75
+ }));
76
+ } catch {}
77
+ }, n = localStorage.getItem(d.session), r = e();
78
+ _({
79
+ ...p,
80
+ ...h(n),
81
+ isIdentified: r.isIdentified,
82
+ isIdentifying: !1
83
+ }), b(), g.set = (e) => {
84
+ _(e);
85
+ try {
86
+ localStorage.setItem(d.session, m(e));
87
+ } catch {}
88
+ t(e.isIdentified);
89
+ }, window.addEventListener("storage", (e) => {
90
+ if (e.key === d.session) {
91
+ let n = h(e.newValue);
92
+ _(n), t(n.isIdentified);
93
+ }
94
+ });
95
+ } else if (typeof BroadcastChannel < "u") {
96
+ let e = new BroadcastChannel(d.session);
97
+ e.onmessage = (t) => {
98
+ let { type: n, value: r } = t.data;
99
+ n === "UPDATE" && r ? _(r) : n === "QUERY" ? v && e.postMessage({
100
+ type: "RESPONSE",
101
+ value: g.get()
102
+ }) : n === "RESPONSE" && r && (_(r), b());
103
+ }, g.set = (t) => {
104
+ _(t), e.postMessage({
105
+ type: "UPDATE",
106
+ value: t
107
+ });
108
+ }, e.postMessage({ type: "QUERY" }), setTimeout(() => {
109
+ b();
110
+ }, 150);
111
+ } else b();
112
+ var x = () => g.get(), S = (e) => {
113
+ g.set({
114
+ ...x(),
115
+ ...e
116
+ });
117
+ }, te = t(g, (e) => e.insightsBaseUrl !== null && e.identity !== null), ne = 10, re = 2e3, C = 500, w = e([]), T = e(0), E = t(w, (e) => e.length ?? 0), D = e(null), O = () => w.get(), ie = () => E.get(), k = (e) => {
118
+ 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 } = x();
119
+ if (e.length === 0 || !a || !p || !t) return;
120
+ let m = e.map((e) => ({
121
+ workspace_id: t,
122
+ site_id: r ?? void 0,
123
+ session_id: c ?? void 0,
124
+ device_id: l ?? void 0,
125
+ identifier: i ?? void 0,
126
+ hostname: window.location.hostname,
127
+ pathname: window.location.pathname,
128
+ page_title: document.title,
129
+ url: window.location.href,
130
+ tab_id: "",
131
+ tab_count: 0,
132
+ ...e,
133
+ event: e.event,
134
+ created_at: e.created_at,
135
+ resource: e.resource ?? "page",
136
+ properties: e.properties ?? {}
137
+ }));
138
+ if (w.set([]), n) {
139
+ console.log("[@levo-so/insights] Debug mode enabled. Events batch:", m);
140
+ return;
141
+ }
142
+ let h = `${p}/v1/insights/event/bulk?workspace=${t}`, g = {
143
+ "Content-Type": "application/json",
144
+ ...u({
145
+ deviceToken: f,
146
+ sessionToken: d,
147
+ mode: o
148
+ })
149
+ };
150
+ o === "proxy" && navigator.sendBeacon && navigator.sendBeacon(h, JSON.stringify(m)) || fetch(h, {
151
+ method: "POST",
152
+ body: JSON.stringify(m),
153
+ headers: g,
154
+ keepalive: !0,
155
+ credentials: o === "proxy" ? "include" : "omit"
156
+ }).catch(() => {});
157
+ }, A = () => {
158
+ if (D.get()) return;
159
+ let e = setTimeout(() => {
160
+ k(O()), D.set(null);
161
+ }, re);
162
+ D.set(e);
163
+ }, j = () => {
164
+ let e = D.get();
165
+ e && (clearTimeout(e), D.set(null));
166
+ }, ae = (e) => {
167
+ let { debug: t } = s(), n = T.get();
168
+ if (n >= C) {
169
+ t && console.warn(`[@levo-so/core] Event limit (${C}) reached for this page`);
170
+ return;
171
+ }
172
+ T.set(n + 1);
173
+ let r = w.get();
174
+ w.set([...r, e]);
175
+ let { isIdentified: i } = x();
176
+ ie() >= ne ? (j(), k(O())) : i && A();
177
+ }, oe = () => T.set(0), M = g.get().isIdentified;
178
+ g.subscribe((e) => {
179
+ e.isIdentified && !M && w.get().length > 0 && (e.insightsBaseUrl ? k(O()) : A()), M = e.isIdentified;
180
+ });
181
+ var se = () => {
182
+ w.set([]), T.set(0), j();
183
+ }, N = n({
184
+ status: "active",
185
+ seconds: 0
186
+ }), ce = (e) => {
187
+ if (typeof window > "u") return () => {};
188
+ let t = r({
189
+ timeout: 6e4,
190
+ throttle: 1e3,
191
+ onIdle: (e) => {
192
+ N.set({
193
+ status: "idle",
194
+ seconds: e
195
+ });
196
+ },
197
+ onWakeUp: (e) => {
198
+ N.set({
199
+ status: "active",
200
+ seconds: e
201
+ });
202
+ }
203
+ }), n = N.listen((t) => {
204
+ t.seconds > 0 && (t.status === "idle" ? e.track("user.idle", { seconds_active: t.seconds }) : e.track("user.active", { seconds_idle: t.seconds }));
205
+ });
206
+ return () => {
207
+ n(), t.disable(), N.set({
208
+ status: "active",
209
+ seconds: 0
210
+ });
211
+ };
212
+ }, le = () => N.get(), P = /* @__PURE__ */ function(e) {
213
+ return e.ACTIVE = "active", e.PASSIVE = "passive", e.HIDDEN = "hidden", e.FROZEN = "frozen", e.TERMINATED = "terminated", e;
214
+ }(P || {}), F = e(P.ACTIVE), ue = () => typeof document > "u" ? P.ACTIVE : document.visibilityState === "hidden" ? P.HIDDEN : document.hasFocus() ? P.ACTIVE : P.PASSIVE, de = (e) => {
215
+ if (typeof window > "u") return () => {};
216
+ let t = !1, n = (n) => {
217
+ if (t) return;
218
+ t = !0;
219
+ let r = le(), a = {};
220
+ r.seconds > 0 && (r.status === "idle" ? a.seconds_idle = r.seconds : a.seconds_active = r.seconds);
221
+ try {
222
+ e.track("page.bounce", {
223
+ resource: "page",
224
+ ...a,
225
+ ...n
226
+ }, {}, () => k(O()));
227
+ } catch (e) {
228
+ console.error("[insights] Failed to send bounce event", i(e));
229
+ }
230
+ }, r = () => {
231
+ let e = performance.getEntriesByType("navigation")?.[0], t = e?.type === "navigate";
232
+ if (!t) try {
233
+ let e = document.activeElement;
234
+ e?.tagName === "A" && e.href && new URL(e.href).origin !== window.location.origin && (t = !0);
235
+ } catch (e) {
236
+ console.warn("[insights] Failed to parse link destination", i(e));
237
+ }
238
+ n({
239
+ is_navigating_away: t,
240
+ navigation_type: e?.type,
241
+ trigger: "beforeunload"
242
+ });
243
+ }, a = (e) => {
244
+ switch (e.type) {
245
+ case "pageshow":
246
+ case "resume":
247
+ case "blur":
248
+ case "focus":
249
+ case "visibilitychange":
250
+ F.set(ue());
251
+ break;
252
+ case "freeze":
253
+ F.set(P.FROZEN);
254
+ break;
255
+ case "pagehide":
256
+ F.set(e.persisted ? P.FROZEN : P.TERMINATED);
257
+ break;
258
+ }
259
+ }, s = [
260
+ "pageshow",
261
+ "pagehide",
262
+ "focus",
263
+ "blur",
264
+ "visibilitychange",
265
+ "resume",
266
+ "freeze"
267
+ ];
268
+ s.forEach((e) => {
269
+ window.addEventListener(e, a, { capture: !0 });
270
+ }), window.addEventListener("beforeunload", r);
271
+ let c = F.listen((e) => {
272
+ e === P.FROZEN || e === P.TERMINATED ? n({
273
+ state: e,
274
+ trigger: "lifecycle"
275
+ }) : e === P.HIDDEN && k(O());
276
+ }), l = o.get().page_id, u = o.listen((e) => {
277
+ e.page_id !== l && (t = !1, l = e.page_id);
278
+ });
279
+ return () => {
280
+ s.forEach((e) => {
281
+ window.removeEventListener(e, a, { capture: !0 });
282
+ }), window.removeEventListener("beforeunload", r), c(), u(), t = !1, F.set(P.ACTIVE);
283
+ };
284
+ }, I = (e) => Object.prototype.toString.call(e) == "[object String]", fe = (e, t) => e.indexOf(t) !== -1, L = (e) => e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""), R = (e) => e === void 0, pe = (e) => e === null, z = (e) => R(e) || pe(e), B = (e) => !!e && e?.nodeType === 3;
285
+ function me(e) {
286
+ return !!e && e?.nodeType === 1;
287
+ }
288
+ function he(e, t) {
289
+ return !!e && !!e?.tagName && e?.tagName.toLowerCase() === t.toLowerCase();
290
+ }
291
+ function ge(e) {
292
+ return e ? L(e).split(/\s+/) : [];
293
+ }
294
+ function _e(e) {
295
+ let t = "";
296
+ switch (typeof e?.className) {
297
+ case "string":
298
+ t = e?.className;
299
+ break;
300
+ case "object":
301
+ t = (e?.className && "baseVal" in e.className ? (e?.className).baseVal : null) || e?.getAttribute("class") || "";
302
+ break;
303
+ default: t = "";
304
+ }
305
+ return ge(t);
306
+ }
307
+ var V = (e, t = 255) => z(e) ? "" : L(e).split(/(\s+)/).filter((e) => q(e)).join("").replace(/[\r\n]/g, " ").replace(/[ ]+/g, " ").substring(0, t), H = [
308
+ "span",
309
+ "strong",
310
+ "em",
311
+ "i",
312
+ "b",
313
+ "mark",
314
+ "code",
315
+ "small",
316
+ "sub",
317
+ "sup",
318
+ "label"
319
+ ], U = (e) => {
320
+ let t = "";
321
+ if (be(e) && e?.childNodes && e?.childNodes.length) {
322
+ for (let n of e.childNodes) B(n) && n.textContent && (t += V(n.textContent) ?? "");
323
+ if (!L(t)) {
324
+ let n = e.querySelectorAll(H.join(", "));
325
+ for (let e of n) {
326
+ let n = e.textContent;
327
+ n && (t += `${V(n)} `);
328
+ }
329
+ }
330
+ if (!L(t) && e instanceof HTMLElement) {
331
+ let n = e.innerText;
332
+ n && (t = V(n.substring(0, 500)));
333
+ }
334
+ }
335
+ return L(t);
336
+ }, W = (e) => R(e.target) ? e?.srcElement || null : e?.composedPath?.()[0] || e?.target || null, ve = (e, t) => {
337
+ if (!window || !e || he(e, "html") || !me(e)) return !1;
338
+ let n = window.getComputedStyle(e);
339
+ if (n && n.getPropertyValue("cursor") === "pointer" && t.type === "click") return !0;
340
+ switch (e?.tagName.toLowerCase()) {
341
+ case "html": return !1;
342
+ case "form": return t.type === "submit";
343
+ case "input":
344
+ case "select":
345
+ case "textarea": return ["change", "click"].includes(t.type);
346
+ default: return t.type === "click";
347
+ }
348
+ }, ye = new Set([
349
+ "password",
350
+ "passwd",
351
+ "pwd",
352
+ "creditcard",
353
+ "cardnumber",
354
+ "ccnum",
355
+ "ccnumber",
356
+ "cvv",
357
+ "cvc",
358
+ "csc",
359
+ "securitycode",
360
+ "ssn",
361
+ "socialsecurity",
362
+ "routingnumber",
363
+ "accountnumber",
364
+ "bankaccount",
365
+ "secretkey",
366
+ "apikey",
367
+ "accesstoken"
368
+ ]);
369
+ function be(e) {
370
+ if (fe(_e(e), "ph-include")) return !0;
371
+ let t = e.type || "";
372
+ if (I(t)) switch (t.toLowerCase()) {
373
+ case "hidden": return !1;
374
+ case "password": return !1;
375
+ }
376
+ let n = e.name || e?.id || "";
377
+ if (I(n)) {
378
+ let e = n.toLowerCase().replace(/[^a-z]/g, "");
379
+ if (ye.has(e)) return !1;
380
+ }
381
+ return !0;
382
+ }
383
+ var G = "(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})", xe = RegExp(`^(?:${G})$`), Se = new RegExp(G), K = "\\d{3}-?\\d{2}-?\\d{4}", Ce = RegExp(`^(${K})$`), we = RegExp(`(${K})`), q = (e, t = !0) => !(z(e) || I(e) && (e = L(e), (t ? xe : Se).test((e || "").replace(/[- ]/g, "")) || (t ? Ce : we).test(e))), Te = (e) => I(e) ? e.substring(0, 10) === "_ngcontent" || e.substring(0, 7) === "_nghost" : !1, J = (e) => {
384
+ let t = U(e);
385
+ return t = `${t} ${Y(e)}`.trim(), q(t) ? t : "";
386
+ };
387
+ function Y(e) {
388
+ let t = "";
389
+ if (e?.childNodes.length) for (let n of e.childNodes) {
390
+ let e = n.tagName?.toLowerCase();
391
+ if (n && e && H.includes(e)) try {
392
+ let e = U(n);
393
+ t = `${t} ${e}`.trim(), n?.childNodes.length && (t = `${t} ${Y(n)}`.trim());
394
+ } catch {}
395
+ }
396
+ return t;
397
+ }
398
+ var Ee = (e) => {
399
+ let t = e.tagName?.toLowerCase(), n = t === "a" || t === "button" ? J(e) : U(e);
400
+ if (n) return {
401
+ text: n,
402
+ source: "text"
403
+ };
404
+ let r = e.getAttribute("aria-label");
405
+ if (r) return {
406
+ text: V(r),
407
+ source: "aria-label"
408
+ };
409
+ let i = e.getAttribute("title");
410
+ if (i) return {
411
+ text: V(i),
412
+ source: "title"
413
+ };
414
+ let a = e.getAttribute("data-track-label");
415
+ if (a) return {
416
+ text: V(a),
417
+ source: "data-track-label"
418
+ };
419
+ let o = e.querySelector("img[alt], svg[aria-label]");
420
+ if (o) {
421
+ let e = o.getAttribute("alt") || o.getAttribute("aria-label");
422
+ if (e) return {
423
+ text: V(e),
424
+ source: "alt"
425
+ };
426
+ }
427
+ let s = e.getAttribute("placeholder");
428
+ if (s) return {
429
+ text: V(s),
430
+ source: "placeholder"
431
+ };
432
+ if (e instanceof HTMLElement && e.innerText) {
433
+ let t = e.innerText.substring(0, 500);
434
+ if (t.trim()) return {
435
+ text: V(t),
436
+ source: "innerText"
437
+ };
438
+ }
439
+ return {
440
+ text: "",
441
+ source: "none"
442
+ };
443
+ }, De = [
444
+ "input",
445
+ "textarea",
446
+ "select"
447
+ ], X = (e) => {
448
+ let t = e?.tagName?.toLowerCase(), n = { element: t }, r = Ee(e);
449
+ n.text = r.text, n.text_source = r.source;
450
+ let i = e.getAttribute("aria-label");
451
+ i && (n.aria_label = V(i));
452
+ let a = e.getAttribute("role");
453
+ a && (n.role = a);
454
+ let o = {};
455
+ for (let t of e.attributes) (t.name.startsWith("data-track-") || t.name.startsWith("data-analytics-")) && (o[t.name] = V(t.value));
456
+ if (Object.keys(o).length && (n.tracking_data = o), n.attributes = Array.from(e.attributes).reduce((e, t) => {
457
+ if (q(t.value) && !Te(t.name)) {
458
+ if (t.name === "class" || t.name.startsWith("data-track-") || t.name.startsWith("data-analytics-")) return e;
459
+ e[t.name] = typeof t?.value == "string" ? V(t.value) : t.value;
460
+ }
461
+ return e;
462
+ }, {}), De.includes(t) || e?.getAttribute("contenteditable") === "true") {
463
+ let t;
464
+ 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 ? V(t, 1024) : t);
465
+ let r = "";
466
+ n?.attributes.id && (r = V(document.querySelector(`label[for="${n?.attributes.id}"]`)?.textContent?.trim() || ""));
467
+ let i = e.closest("label");
468
+ r ||= V(i?.textContent?.trim() || ""), r && (n.label = r);
469
+ }
470
+ return n;
471
+ }, Oe = (e, t) => {
472
+ let n = W(e);
473
+ B(n) && (n = n.parentNode || null);
474
+ let r = ["copy", "cut"].includes(t);
475
+ if (n && (r || ve(n, e))) {
476
+ let t = X(n);
477
+ if (r) {
478
+ let n = V(window?.getSelection()?.toString()), r = e?.type || "clipboard";
479
+ t.copy_text = n ?? "", t.copy_type = r ?? "";
480
+ }
481
+ return t;
482
+ }
483
+ return null;
484
+ }, Z = e(null), ke = (e) => {
485
+ if (typeof window > "u") return () => {};
486
+ let t = sessionStorage.getItem(d.current_tab), { allTabs: n } = g.get();
487
+ t && n.includes(t) && (t = null), t || (t = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, sessionStorage.setItem(d.current_tab, t));
488
+ let r = t;
489
+ Z.set(r), n.includes(r) || S({ allTabs: [...n, r] });
490
+ let i = () => {
491
+ if (!r) return;
492
+ let { allTabs: e } = g.get();
493
+ e.includes(r) && S({ allTabs: e.filter((e) => e !== r) });
494
+ };
495
+ return window.addEventListener("beforeunload", i), () => {
496
+ window.removeEventListener("beforeunload", i), i(), Z.set(null);
497
+ };
498
+ }, Ae = () => Z.get(), Q = n({
499
+ depth: 0,
500
+ milestone: 0,
501
+ speedCategory: "read",
502
+ direction: "down"
503
+ }), je = (e) => {
504
+ if (typeof window > "u") return () => {};
505
+ let t = {
506
+ prevMilestone: -1,
507
+ prevScrollY: 0,
508
+ prevTime: Date.now()
509
+ }, n = () => {
510
+ let e = window.scrollY, n = document.documentElement.scrollHeight - window.innerHeight;
511
+ if (n <= 0) return;
512
+ let r = Math.round(e / n * 100), i = Math.max(0, Math.min(100, r)), a = Math.floor(i / 5) * 5, { prevScrollY: o, prevTime: s } = t, c = Date.now(), l = (c - s) / 1e3, u = Math.abs(e - o), d = l > 0 ? u / l : 0, f = i > t.prevScrollY / n * 100 ? "down" : "up", p = d > 300 ? "scan" : "read";
513
+ Q.set({
514
+ depth: i,
515
+ milestone: a,
516
+ speedCategory: p,
517
+ direction: f
518
+ }), t = {
519
+ prevMilestone: a,
520
+ prevScrollY: e,
521
+ prevTime: c
522
+ };
523
+ }, r = a(n, 250);
524
+ window.addEventListener("scroll", r);
525
+ let i = 0, o = Q.listen((t) => {
526
+ t.milestone !== i && (i = t.milestone, e.track("page.scroll", {
527
+ depth: t.milestone,
528
+ speed_category: t.speedCategory,
529
+ direction: t.direction
530
+ }));
531
+ });
532
+ return typeof window.requestIdleCallback == "function" ? window.requestIdleCallback(() => n()) : setTimeout(n, 0), () => {
533
+ window.removeEventListener("scroll", r), r.cancel(), o(), Q.set({
534
+ depth: 0,
535
+ milestone: 0,
536
+ speedCategory: "read",
537
+ direction: "down"
538
+ });
539
+ };
540
+ }, $ = e(null), Me = (e) => {
541
+ if (typeof window > "u") return () => {};
542
+ let t = a(() => {
543
+ let e = window.getSelection();
544
+ if (e && e.rangeCount > 0) {
545
+ let t = e.toString().trim();
546
+ t && $.set(t);
547
+ }
548
+ }, 1e3);
549
+ document.addEventListener("selectionchange", t);
550
+ let n = $.listen((t) => {
551
+ t && e.track("page.selection", { text: V(t) });
552
+ });
553
+ return () => {
554
+ document.removeEventListener("selectionchange", t), t.cancel(), n(), $.set(null);
555
+ };
556
+ };
557
+ //#endregion
558
+ export { s as A, te as C, S as D, ee as E, l as M, u as O, oe as S, x as T, E as _, Z as a, O as b, X as c, F as d, de as f, T as g, w as h, je as i, c as j, o as k, W as l, ce as m, Me as n, Ae as o, N as p, Q as r, ke as s, $ as t, Oe as u, se as v, g as w, ae as x, k as y };
@@ -8,7 +8,7 @@ export interface IInsightOptions {
8
8
  directInsightsUrl?: string;
9
9
  /** Insights mode: 'auto' (default) detects via ping, 'proxy' forces reverse proxy, 'direct' forces direct API */
10
10
  mode?: "auto" | "proxy" | "direct";
11
- /** Timeout for proxy ping detection in ms (default: 3000) */
11
+ /** Timeout for proxy ping detection in ms (default: 1000) */
12
12
  pingTimeout?: number;
13
13
  debug?: boolean;
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@levo-so/insights",
3
- "version": "0.1.82",
3
+ "version": "0.1.85",
4
4
  "author": "Levo Engineering <devs@theinternetfolks.com>",
5
5
  "description": "Levo analytics",
6
6
  "type": "module",
@@ -27,9 +27,8 @@
27
27
  "@playwright/test": "1.58.2",
28
28
  "@types/lodash-es": "4.17.12",
29
29
  "typescript": "5.9.3",
30
- "vite": "7.2.4",
31
- "vite-plugin-dts": "4.1.0",
32
- "vite-plugin-externalize-deps": "0.10.0",
30
+ "vite": "8.0.0",
31
+ "vite-plugin-dts": "4.5.4",
33
32
  "@levo/ts-config": "0.0.0",
34
33
  "@levo-so/core": "0.1.82"
35
34
  },