@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,583 +0,0 @@
1
- import { getLevoError as z } from "@levo-so/core";
2
- import { map as B, computed as J, atom as b } from "nanostores";
3
- import { onUserActivity as de } from "@analytics/activity-utils";
4
- import { debounce as K } from "lodash-es";
5
- const x = B({
6
- workspace: null,
7
- page_id: null,
8
- site_id: null,
9
- debug: !1
10
- }), U = () => x.get(), ze = (e) => x.set({ ...U(), ...e }), Ve = (e, t) => x.setKey(e, t), fe = ({
11
- deviceToken: e,
12
- sessionToken: t,
13
- mode: n
14
- }) => {
15
- const i = {};
16
- return n === "direct" && (e && (i["Levo-Audience-Device"] = e), t && (i["Levo-Audience-Session"] = t)), i;
17
- }, v = {
18
- /** Legacy: Lock key for identify operation (no longer used internally) */
19
- identify: "lock:lv_aud_identify",
20
- /** Key for tracking open tab count */
21
- open_tabs: "lv_insights_ot",
22
- /** Key for current tab ID (sessionStorage) */
23
- current_tab: "lv_insights_ct",
24
- /** Key for all tabs registry */
25
- all_tabs: "lv_insights_at",
26
- /** Device JWT for direct mode (1yr expiry, rotated on /welcome, analytics-only scope) */
27
- device_token: "lv_aud_device",
28
- /** Session JWT for direct mode (1yr expiry, rotated on /welcome, analytics-only scope) */
29
- session_token: "lv_aud_session",
30
- /** Cached insights mode ('proxy' | 'direct') */
31
- mode: "lv_aud_mode",
32
- /** Key to store session data **/
33
- session: "lv_insights_session"
34
- }, ge = () => {
35
- if (typeof window > "u" || !("localStorage" in window))
36
- return !1;
37
- try {
38
- const e = "__levo_test__";
39
- return window.localStorage.setItem(e, "test"), window.localStorage.removeItem(e), !0;
40
- } catch {
41
- return !1;
42
- }
43
- }, M = {
44
- isIdentified: !1,
45
- isIdentifying: !1,
46
- sessionId: null,
47
- deviceId: null,
48
- deviceToken: null,
49
- sessionToken: null,
50
- allTabs: [],
51
- mode: "proxy",
52
- insightsBaseUrl: null,
53
- identity: null
54
- }, pe = JSON.stringify, V = (e) => {
55
- if (!e) return M;
56
- try {
57
- return JSON.parse(e);
58
- } catch {
59
- return M;
60
- }
61
- }, p = b(M), y = p.set;
62
- let N = !1;
63
- const O = [], L = () => {
64
- N || (N = !0, O.forEach((e) => {
65
- e();
66
- }), O.length = 0);
67
- }, Qe = (e) => {
68
- N ? e() : O.push(e);
69
- };
70
- if (ge()) {
71
- const e = localStorage.getItem(v.session);
72
- e && y(V(e)), L(), p.set = (t) => {
73
- y(t);
74
- try {
75
- localStorage.setItem(v.session, pe(t));
76
- } catch {
77
- }
78
- }, window.addEventListener("storage", (t) => {
79
- t.key === v.session && y(V(t.newValue));
80
- });
81
- } else if (typeof BroadcastChannel < "u") {
82
- const e = new BroadcastChannel(v.session);
83
- e.onmessage = (t) => {
84
- const { type: n, value: i } = t.data;
85
- n === "UPDATE" && i ? y(i) : n === "QUERY" ? N && e.postMessage({ type: "RESPONSE", value: p.get() }) : n === "RESPONSE" && i && (y(i), L());
86
- }, p.set = (t) => {
87
- y(t), e.postMessage({ type: "UPDATE", value: t });
88
- }, e.postMessage({ type: "QUERY" }), setTimeout(() => {
89
- L();
90
- }, 150);
91
- } else
92
- L();
93
- const H = () => p.get(), Q = (e) => {
94
- p.set({ ...H(), ...e });
95
- }, We = J(
96
- p,
97
- (e) => e.insightsBaseUrl !== null && e.identity !== null
98
- ), me = 10, he = 2e3, W = 500, h = b([]), I = b(0), be = J(h, (e) => e.length ?? 0), T = b(null), Je = () => {
99
- let e = !1;
100
- return p.subscribe((t) => {
101
- if (t.isIdentified && !e) {
102
- e = !0;
103
- const n = h.get();
104
- n.length > 0 && C(n);
105
- }
106
- });
107
- }, A = () => h.get(), ye = () => be.get(), C = (e) => {
108
- const { workspace: t, debug: n, site_id: i, page_id: o } = U(), { isIdentified: r, mode: c, sessionId: u, deviceId: a, sessionToken: s, deviceToken: d, insightsBaseUrl: f } = H();
109
- if (e.length === 0 || !r || !f || !t)
110
- return;
111
- const g = e.map(
112
- (m) => ({
113
- // Default values from stores
114
- workspace_id: t,
115
- site_id: i ?? void 0,
116
- session_id: u ?? void 0,
117
- device_id: a ?? void 0,
118
- identifier: o ?? void 0,
119
- hostname: window.location.hostname,
120
- pathname: window.location.pathname,
121
- page_title: document.title,
122
- url: window.location.href,
123
- tab_id: "",
124
- tab_count: 0,
125
- // Event values override defaults (includes tab_id, tab_count captured at track time)
126
- ...m,
127
- // Ensure required fields have values
128
- event: m.event,
129
- created_at: m.created_at,
130
- resource: m.resource ?? "page",
131
- properties: m.properties ?? {}
132
- })
133
- );
134
- if (h.set([]), n) {
135
- console.log("[@levo-so/insights] Debug mode enabled. Events batch:", g);
136
- return;
137
- }
138
- const k = `${f}/v1/insights/event/bulk?workspace=${t}`, E = {
139
- "Content-Type": "application/json",
140
- ...fe({ deviceToken: d, sessionToken: s, mode: c })
141
- };
142
- c === "proxy" && navigator.sendBeacon && navigator.sendBeacon(k, JSON.stringify(g)) || fetch(k, {
143
- method: "POST",
144
- body: JSON.stringify(g),
145
- headers: E,
146
- keepalive: !0,
147
- credentials: c === "proxy" ? "include" : "omit"
148
- }).catch(() => {
149
- });
150
- }, ve = () => {
151
- if (T.get()) return;
152
- const e = setTimeout(() => {
153
- C(A()), T.set(null);
154
- }, he);
155
- T.set(e);
156
- }, G = () => {
157
- const e = T.get();
158
- e && (clearTimeout(e), T.set(null));
159
- }, Ke = (e) => {
160
- const { debug: t } = U(), n = I.get();
161
- if (n >= W) {
162
- t && console.warn(`[@levo-so/core] Event limit (${W}) reached for this page`);
163
- return;
164
- }
165
- I.set(n + 1);
166
- const i = h.get();
167
- h.set([...i, e]);
168
- const { isIdentified: o } = H();
169
- ye() >= me ? (G(), C(A())) : o && ve();
170
- }, Ge = () => I.set(0), Xe = () => {
171
- h.set([]), I.set(0), G();
172
- }, _ = B({
173
- status: "active",
174
- seconds: 0
175
- }), Ze = (e) => {
176
- if (typeof window > "u") return () => {
177
- };
178
- const i = de({
179
- timeout: 6e4,
180
- throttle: 1e3,
181
- onIdle: (r) => {
182
- _.set({ status: "idle", seconds: r });
183
- },
184
- onWakeUp: (r) => {
185
- _.set({ status: "active", seconds: r });
186
- }
187
- }), o = _.listen((r) => {
188
- r.seconds > 0 && (r.status === "idle" ? e.track("user.idle", {
189
- seconds_active: r.seconds
190
- }) : e.track("user.active", {
191
- seconds_idle: r.seconds
192
- }));
193
- });
194
- return () => {
195
- o(), i.disable(), _.set({ status: "active", seconds: 0 });
196
- };
197
- }, we = () => _.get(), S = b(
198
- "active"
199
- /* ACTIVE */
200
- ), Ee = () => typeof document > "u" ? "active" : document.visibilityState === "hidden" ? "hidden" : document.hasFocus() ? "active" : "passive", et = (e) => {
201
- if (typeof window > "u") return () => {
202
- };
203
- let t = !1;
204
- const n = (s) => {
205
- if (t) return;
206
- t = !0;
207
- const d = we(), f = {};
208
- d.seconds > 0 && (d.status === "idle" ? f.seconds_idle = d.seconds : f.seconds_active = d.seconds);
209
- try {
210
- e.track("page.bounce", {
211
- resource: "page",
212
- ...f,
213
- ...s
214
- }), C(A());
215
- } catch (g) {
216
- console.error("[insights] Failed to send bounce event", z(g));
217
- }
218
- }, i = () => {
219
- const s = performance.getEntriesByType("navigation")?.[0];
220
- let d = s?.type === "navigate";
221
- if (!d)
222
- try {
223
- const f = document.activeElement;
224
- f?.tagName === "A" && f.href && new URL(f.href).origin !== window.location.origin && (d = !0);
225
- } catch (f) {
226
- console.warn("[insights] Failed to parse link destination", z(f));
227
- }
228
- n({
229
- is_navigating_away: d,
230
- navigation_type: s?.type,
231
- trigger: "beforeunload"
232
- });
233
- }, o = (s) => {
234
- switch (s.type) {
235
- case "pageshow":
236
- case "resume":
237
- case "blur":
238
- case "focus":
239
- case "visibilitychange":
240
- S.set(Ee());
241
- break;
242
- case "freeze":
243
- S.set(
244
- "frozen"
245
- /* FROZEN */
246
- );
247
- break;
248
- case "pagehide":
249
- S.set(
250
- s.persisted ? "frozen" : "terminated"
251
- /* TERMINATED */
252
- );
253
- break;
254
- }
255
- }, r = ["pageshow", "pagehide", "focus", "blur", "visibilitychange", "resume", "freeze"];
256
- r.forEach((s) => {
257
- window.addEventListener(s, o, { capture: !0 });
258
- }), window.addEventListener("beforeunload", i);
259
- const c = S.listen((s) => {
260
- s === "frozen" || s === "terminated" ? n({
261
- state: s,
262
- trigger: "lifecycle"
263
- }) : s === "hidden" && C(A());
264
- });
265
- let u = x.get().page_id;
266
- const a = x.listen((s) => {
267
- s.page_id !== u && (t = !1, u = s.page_id);
268
- });
269
- return () => {
270
- r.forEach((s) => {
271
- window.removeEventListener(s, o, { capture: !0 });
272
- }), window.removeEventListener("beforeunload", i), c(), a(), t = !1, S.set(
273
- "active"
274
- /* ACTIVE */
275
- );
276
- };
277
- }, R = (e) => Object.prototype.toString.call(e) == "[object String]", Se = (e, t) => e.indexOf(t) !== -1, w = (e) => e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""), X = (e) => e === void 0, _e = (e) => e === null, Z = (e) => X(e) || _e(e), ee = (e) => !!e && e?.nodeType === 3;
278
- function Te(e) {
279
- return !!e && e?.nodeType === 1;
280
- }
281
- function xe(e, t) {
282
- return !!e && !!e?.tagName && e?.tagName.toLowerCase() === t.toLowerCase();
283
- }
284
- function Ce(e) {
285
- return e ? w(e).split(/\s+/) : [];
286
- }
287
- function ke(e) {
288
- let t = "";
289
- switch (typeof e?.className) {
290
- case "string":
291
- t = e?.className;
292
- break;
293
- // TODO: when is this ever used?
294
- case "object":
295
- t = (e?.className && "baseVal" in e.className ? (e?.className).baseVal : null) || e?.getAttribute("class") || "";
296
- break;
297
- default:
298
- t = "";
299
- }
300
- return Ce(t);
301
- }
302
- const l = (e, t = 255) => Z(e) ? "" : w(e).split(/(\s+)/).filter((n) => Y(n)).join("").replace(/[\r\n]/g, " ").replace(/[ ]+/g, " ").substring(0, t), te = ["span", "strong", "em", "i", "b", "mark", "code", "small", "sub", "sup", "label"], F = (e) => {
303
- let t = "";
304
- if (Ae(e) && e?.childNodes && e?.childNodes.length) {
305
- for (const n of e.childNodes)
306
- ee(n) && n.textContent && (t += l(n.textContent) ?? "");
307
- if (!w(t)) {
308
- const n = e.querySelectorAll(te.join(", "));
309
- for (const i of n) {
310
- const o = i.textContent;
311
- o && (t += `${l(o)} `);
312
- }
313
- }
314
- if (!w(t) && e instanceof HTMLElement) {
315
- const n = e.innerText;
316
- n && (t = l(n.substring(0, 500)));
317
- }
318
- }
319
- return w(t);
320
- }, Le = (e) => X(e.target) ? e?.srcElement || null : e?.target?.shadowRoot ? e?.composedPath()?.[0] || null : e?.target || null, Ne = (e, t) => {
321
- if (!window || !e || xe(e, "html") || !Te(e))
322
- return !1;
323
- const n = window.getComputedStyle(e);
324
- if (n && n.getPropertyValue("cursor") === "pointer" && t.type === "click")
325
- return !0;
326
- switch (e?.tagName.toLowerCase()) {
327
- case "html":
328
- return !1;
329
- case "form":
330
- return t.type === "submit";
331
- case "input":
332
- case "select":
333
- case "textarea":
334
- return ["change", "click"].includes(t.type);
335
- default:
336
- return t.type === "click";
337
- }
338
- }, Ie = /* @__PURE__ */ new Set([
339
- "password",
340
- "passwd",
341
- "pwd",
342
- "creditcard",
343
- "cardnumber",
344
- "ccnum",
345
- "ccnumber",
346
- "cvv",
347
- "cvc",
348
- "csc",
349
- "securitycode",
350
- "ssn",
351
- "socialsecurity",
352
- "routingnumber",
353
- "accountnumber",
354
- "bankaccount",
355
- "secretkey",
356
- "apikey",
357
- "accesstoken"
358
- ]);
359
- function Ae(e) {
360
- if (Se(ke(e), "ph-include"))
361
- return !0;
362
- const t = e.type || "";
363
- if (R(t))
364
- switch (t.toLowerCase()) {
365
- case "hidden":
366
- return !1;
367
- case "password":
368
- return !1;
369
- }
370
- const n = e.name || e?.id || "";
371
- if (R(n)) {
372
- const i = n.toLowerCase().replace(/[^a-z]/g, "");
373
- if (Ie.has(i))
374
- return !1;
375
- }
376
- return !0;
377
- }
378
- const ne = "(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})", Re = new RegExp(`^(?:${ne})$`), $e = new RegExp(ne), se = "\\d{3}-?\\d{2}-?\\d{4}", De = new RegExp(`^(${se})$`), Me = new RegExp(`(${se})`), Y = (e, t = !0) => !(Z(e) || R(e) && (e = w(e), (t ? Re : $e).test((e || "").replace(/[- ]/g, "")) || (t ? De : Me).test(e))), Oe = (e) => R(e) ? e.substring(0, 10) === "_ngcontent" || e.substring(0, 7) === "_nghost" : !1, Pe = (e) => {
379
- let t = F(e);
380
- return t = `${t} ${ie(e)}`.trim(), Y(t) ? t : "";
381
- };
382
- function ie(e) {
383
- let t = "";
384
- if (e?.childNodes.length)
385
- for (const n of e.childNodes) {
386
- const i = n.tagName?.toLowerCase();
387
- if (n && i && te.includes(i))
388
- try {
389
- const o = F(n);
390
- t = `${t} ${o}`.trim(), n?.childNodes.length && (t = `${t} ${ie(n)}`.trim());
391
- } catch {
392
- }
393
- }
394
- return t;
395
- }
396
- const Be = (e) => {
397
- const t = e.tagName?.toLowerCase(), n = t === "a" || t === "button" ? Pe(e) : F(e);
398
- if (n) return { text: n, source: "text" };
399
- const i = e.getAttribute("aria-label");
400
- if (i) return { text: l(i), source: "aria-label" };
401
- const o = e.getAttribute("title");
402
- if (o) return { text: l(o), source: "title" };
403
- const r = e.getAttribute("data-track-label");
404
- if (r) return { text: l(r), source: "data-track-label" };
405
- const c = e.querySelector("img[alt], svg[aria-label]");
406
- if (c) {
407
- const a = c.getAttribute("alt") || c.getAttribute("aria-label");
408
- if (a) return { text: l(a), source: "alt" };
409
- }
410
- const u = e.getAttribute("placeholder");
411
- if (u) return { text: l(u), source: "placeholder" };
412
- if (e instanceof HTMLElement && e.innerText) {
413
- const a = e.innerText.substring(0, 500);
414
- if (a.trim())
415
- return { text: l(a), source: "innerText" };
416
- }
417
- return { text: "", source: "none" };
418
- }, Ue = ["input", "textarea", "select"], He = (e) => {
419
- const t = e?.tagName?.toLowerCase(), n = {
420
- element: t
421
- }, i = Be(e);
422
- n.text = i.text, n.text_source = i.source;
423
- const o = e.getAttribute("aria-label");
424
- o && (n.aria_label = l(o));
425
- const r = e.getAttribute("role");
426
- r && (n.role = r);
427
- const c = {};
428
- for (const a of e.attributes)
429
- (a.name.startsWith("data-track-") || a.name.startsWith("data-analytics-")) && (c[a.name] = l(a.value));
430
- Object.keys(c).length && (n.tracking_data = c);
431
- const u = Array.from(e.attributes).reduce(
432
- (a, s) => {
433
- if (Y(s.value) && !Oe(s.name)) {
434
- if (s.name === "class" || s.name.startsWith("data-track-") || s.name.startsWith("data-analytics-"))
435
- return a;
436
- a[s.name] = typeof s?.value == "string" ? l(s.value) : s.value;
437
- }
438
- return a;
439
- },
440
- {}
441
- );
442
- if (n.attributes = u, Ue.includes(t) || e?.getAttribute("contenteditable") === "true") {
443
- let a;
444
- e.matches('input[type="checkbox"], input[type="radio"]') ? a = e.checked : e.matches("input, select, textarea") && !e.matches('input[type="password"]') && (a = e.value), a !== void 0 && (n.value = typeof a == "string" || a === null ? l(a, 1024) : a);
445
- let s = "";
446
- n?.attributes.id && (s = l(
447
- document.querySelector(`label[for="${n?.attributes.id}"]`)?.textContent?.trim() || ""
448
- ));
449
- const d = e.closest("label");
450
- s || (s = l(d?.textContent?.trim() || "")), s && (n.label = s);
451
- }
452
- return n;
453
- }, tt = (e, t) => {
454
- let n = Le(e);
455
- ee(n) && (n = n.parentNode || null);
456
- const i = ["copy", "cut"].includes(t);
457
- if (n && (i || Ne(n, e))) {
458
- const o = He(n);
459
- if (i) {
460
- const r = l(window?.getSelection()?.toString()), c = e?.type || "clipboard";
461
- o.copy_text = r ?? "", o.copy_type = c ?? "";
462
- }
463
- return o;
464
- }
465
- return null;
466
- }, P = b(null), nt = (e) => {
467
- if (typeof window > "u") return () => {
468
- };
469
- let t = sessionStorage.getItem(v.current_tab);
470
- const { allTabs: n } = p.get();
471
- t && n.includes(t) && (t = null), t || (t = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, sessionStorage.setItem(v.current_tab, t));
472
- const i = t;
473
- P.set(i), n.includes(i) || Q({ allTabs: [...n, i] });
474
- const o = () => {
475
- if (!i) return;
476
- const { allTabs: r } = p.get();
477
- if (r.includes(i)) {
478
- const c = r.filter((u) => u !== i);
479
- Q({ allTabs: c });
480
- }
481
- };
482
- return window.addEventListener("beforeunload", o), () => {
483
- window.removeEventListener("beforeunload", o), o(), P.set(null);
484
- };
485
- }, st = () => P.get(), $ = B({
486
- depth: 0,
487
- milestone: 0,
488
- speedCategory: "read",
489
- direction: "down"
490
- }), it = (e) => {
491
- if (typeof window > "u") return () => {
492
- };
493
- let t = {
494
- // Use -1 to ensure first milestone (even 0) is tracked
495
- prevScrollY: 0,
496
- prevTime: Date.now()
497
- };
498
- const n = 5, i = 300, o = 250, r = () => {
499
- const s = window.scrollY, d = document.documentElement.scrollHeight, f = window.innerHeight, g = d - f;
500
- if (g <= 0) return;
501
- const k = Math.round(s / g * 100), E = Math.max(0, Math.min(100, k)), m = Math.floor(E / n) * n, { prevScrollY: oe, prevTime: re } = t, j = Date.now(), q = (j - re) / 1e3, ae = Math.abs(s - oe), ce = q > 0 ? ae / q : 0, le = E > t.prevScrollY / g * 100 ? "down" : "up", ue = ce > i ? "scan" : "read";
502
- $.set({
503
- depth: E,
504
- milestone: m,
505
- speedCategory: ue,
506
- direction: le
507
- }), t = {
508
- prevMilestone: m,
509
- prevScrollY: s,
510
- prevTime: j
511
- };
512
- }, c = K(r, o);
513
- window.addEventListener("scroll", c);
514
- let u = 0;
515
- const a = $.listen((s) => {
516
- s.milestone !== u && (u = s.milestone, e.track("page.scroll", {
517
- depth: s.milestone,
518
- speed_category: s.speedCategory,
519
- direction: s.direction
520
- }));
521
- });
522
- return typeof window.requestIdleCallback == "function" ? window.requestIdleCallback(() => r()) : setTimeout(r, 0), () => {
523
- window.removeEventListener("scroll", c), c.cancel(), a(), $.set({
524
- depth: 0,
525
- milestone: 0,
526
- speedCategory: "read",
527
- direction: "down"
528
- });
529
- };
530
- }, D = b(null), ot = (e) => {
531
- if (typeof window > "u") return () => {
532
- };
533
- const n = K(() => {
534
- const o = window.getSelection();
535
- if (o && o.rangeCount > 0) {
536
- const r = o.toString().trim();
537
- r && D.set(r);
538
- }
539
- }, 1e3);
540
- document.addEventListener("selectionchange", n);
541
- const i = D.listen((o) => {
542
- o && e.track("page.selection", {
543
- text: l(o)
544
- });
545
- });
546
- return () => {
547
- document.removeEventListener("selectionchange", n), n.cancel(), i(), D.set(null);
548
- };
549
- };
550
- export {
551
- _ as $,
552
- Ze as A,
553
- et as B,
554
- nt as C,
555
- it as D,
556
- ot as E,
557
- x as a,
558
- P as b,
559
- h as c,
560
- We as d,
561
- I as e,
562
- S as f,
563
- $ as g,
564
- D as h,
565
- p as i,
566
- be as j,
567
- He as k,
568
- tt as l,
569
- U as m,
570
- H as n,
571
- st as o,
572
- fe as p,
573
- Ke as q,
574
- Ge as r,
575
- ze as s,
576
- Ve as t,
577
- Q as u,
578
- Qe as v,
579
- C as w,
580
- A as x,
581
- Xe as y,
582
- Je as z
583
- };
@@ -1,20 +0,0 @@
1
- /**
2
- * @example
3
- *
4
- * const lock = new LocalStorageLock("myResource");
5
- * if (lock.acquireLock()) {
6
- * console.log("Lock acquired");
7
- * // Do some work...
8
- * lock.releaseLock();
9
- * } else {
10
- * console.log("Lock is already held by another instance");
11
- * }
12
- *
13
- */
14
- export declare class LocalStorageLock {
15
- private key;
16
- constructor(lockKey: string);
17
- acquireLock(expiryMs?: number): boolean;
18
- releaseLock(): void;
19
- isLocked(expiryMs?: number): boolean;
20
- }