@dotcms/analytics 1.2.3 → 1.2.4-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +495 -535
- package/lib/core/dot-analytics.content.js +25 -21
- package/lib/core/plugin/impression/dot-analytics.impression-tracker.js +13 -21
- package/lib/core/shared/constants/dot-analytics.constants.d.ts +19 -0
- package/lib/core/shared/constants/dot-analytics.constants.js +18 -15
- package/lib/core/shared/http/dot-analytics.http.d.ts +1 -1
- package/lib/core/shared/http/dot-analytics.http.js +19 -17
- package/lib/core/shared/models/event.model.d.ts +12 -0
- package/lib/core/shared/queue/dot-analytics.queue.utils.d.ts +4 -0
- package/lib/core/shared/queue/dot-analytics.queue.utils.js +122 -41
- package/lib/core/shared/utils/dot-analytics.utils.d.ts +1 -0
- package/lib/core/shared/utils/dot-analytics.utils.js +113 -76
- package/lib/react/hook/useContentAnalytics.d.ts +1 -2
- package/lib/react/hook/useContentAnalytics.js +35 -23
- package/lib/react/hook/useRouterTracker.d.ts +1 -1
- package/lib/react/hook/useRouterTracker.js +11 -13
- package/package.json +1 -1
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { Analytics as l } from "analytics";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { getUVEState as d } from "../../uve/src/lib/core/core.utils.js";
|
|
3
|
+
import "../../uve/src/internal/constants.js";
|
|
4
|
+
import { dotAnalyticsClickPlugin as y } from "./plugin/click/dot-analytics.click.plugin.js";
|
|
5
|
+
import { dotAnalyticsEnricherPlugin as m } from "./plugin/enricher/dot-analytics.enricher.plugin.js";
|
|
4
6
|
import { dotAnalyticsIdentityPlugin as u } from "./plugin/identity/dot-analytics.identity.plugin.js";
|
|
5
|
-
import { dotAnalyticsImpressionPlugin as
|
|
6
|
-
import { dotAnalytics as
|
|
7
|
-
import { ANALYTICS_WINDOWS_ACTIVE_KEY as
|
|
8
|
-
import { validateAnalyticsConfig as C, getEnhancedTrackingPlugins as
|
|
9
|
-
import { cleanupActivityTracking as
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
7
|
+
import { dotAnalyticsImpressionPlugin as A } from "./plugin/impression/dot-analytics.impression.plugin.js";
|
|
8
|
+
import { dotAnalytics as p } from "./plugin/main/dot-analytics.plugin.js";
|
|
9
|
+
import { ANALYTICS_WINDOWS_ACTIVE_KEY as e, ANALYTICS_WINDOWS_CLEANUP_KEY as f, DotCMSPredefinedEventType as w } from "./shared/constants/dot-analytics.constants.js";
|
|
10
|
+
import { validateAnalyticsConfig as C, isBrowser as g, getEnhancedTrackingPlugins as E } from "./shared/utils/dot-analytics.utils.js";
|
|
11
|
+
import { cleanupActivityTracking as S } from "./plugin/identity/dot-analytics.identity.activity-tracker.js";
|
|
12
|
+
const O = (t) => {
|
|
13
|
+
const r = C(t);
|
|
14
|
+
if (r)
|
|
13
15
|
return console.error(
|
|
14
|
-
`DotCMS Analytics [Core]: Missing ${
|
|
15
|
-
), typeof window < "u" && (window[
|
|
16
|
-
|
|
16
|
+
`DotCMS Analytics [Core]: Missing ${r.join(" and ")} in configuration`
|
|
17
|
+
), typeof window < "u" && (window[e] = !1), null;
|
|
18
|
+
if (g() && d())
|
|
19
|
+
return console.warn("DotCMS Analytics [Core]: Analytics disabled inside UVE editor"), window[e] = !1, null;
|
|
20
|
+
const s = E(
|
|
17
21
|
t,
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
A,
|
|
23
|
+
y
|
|
20
24
|
), i = l({
|
|
21
25
|
app: "dotAnalytics",
|
|
22
26
|
debug: t.debug,
|
|
@@ -25,13 +29,13 @@ const T = (t) => {
|
|
|
25
29
|
// Inject identity context
|
|
26
30
|
...s,
|
|
27
31
|
//Track content impressions & clicks (conditionally loaded)
|
|
28
|
-
|
|
32
|
+
m(),
|
|
29
33
|
// Enrich and clean payload with page, device, utm data and custom data
|
|
30
|
-
|
|
34
|
+
p(t)
|
|
31
35
|
// Send events to server
|
|
32
36
|
]
|
|
33
|
-
}),
|
|
34
|
-
return typeof window < "u" && (window.addEventListener("beforeunload",
|
|
37
|
+
}), a = () => S();
|
|
38
|
+
return typeof window < "u" && (window.addEventListener("beforeunload", a), window[f] = a, window[e] = !0, window.dispatchEvent(new CustomEvent("dotcms:analytics:ready"))), {
|
|
35
39
|
/**
|
|
36
40
|
* Track a page view.
|
|
37
41
|
* Session activity is automatically updated by the identity plugin.
|
|
@@ -74,10 +78,10 @@ const T = (t) => {
|
|
|
74
78
|
name: n,
|
|
75
79
|
...Object.keys(o).length > 0 && { custom: o }
|
|
76
80
|
};
|
|
77
|
-
i.track(
|
|
81
|
+
i.track(w.CONVERSION, c);
|
|
78
82
|
}
|
|
79
83
|
};
|
|
80
84
|
};
|
|
81
85
|
export {
|
|
82
|
-
|
|
86
|
+
O as initializeContentAnalytics
|
|
83
87
|
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { createPluginLogger as p, isBrowser as a, INITIAL_SCAN_DELAY_MS as f, findContentlets as b, extractContentletIdentifier as c, createContentletObserver as v, extractContentletData as I } from "../../shared/utils/dot-analytics.utils.js";
|
|
6
|
-
class E {
|
|
1
|
+
import { getViewportMetrics as m, isElementMeetingVisibilityThreshold as d } from "./dot-analytics.impression.utils.js";
|
|
2
|
+
import { DEFAULT_IMPRESSION_CONFIG as l, IMPRESSION_EVENT_TYPE as u } from "../../shared/constants/dot-analytics.constants.js";
|
|
3
|
+
import { createPluginLogger as g, isBrowser as a, INITIAL_SCAN_DELAY_MS as p, findContentlets as b, extractContentletIdentifier as c, createContentletObserver as f, extractContentletData as v } from "../../shared/utils/dot-analytics.utils.js";
|
|
4
|
+
class T {
|
|
7
5
|
constructor(e) {
|
|
8
|
-
this.observer = null, this.mutationObserver = null, this.elementImpressionStates = /* @__PURE__ */ new Map(), this.sessionTrackedImpressions = /* @__PURE__ */ new Set(), this.currentPagePath = "", this.subscribers = /* @__PURE__ */ new Set(), this.logger =
|
|
6
|
+
this.observer = null, this.mutationObserver = null, this.elementImpressionStates = /* @__PURE__ */ new Map(), this.sessionTrackedImpressions = /* @__PURE__ */ new Set(), this.currentPagePath = "", this.subscribers = /* @__PURE__ */ new Set(), this.logger = g("Impression", e), this.impressionConfig = this.resolveImpressionConfig(e.impressions);
|
|
9
7
|
}
|
|
10
8
|
/**
|
|
11
9
|
* Subscribe to impression events
|
|
@@ -38,15 +36,9 @@ class E {
|
|
|
38
36
|
}
|
|
39
37
|
/** Initializes tracking: sets up observers, finds contentlets, handles visibility/navigation */
|
|
40
38
|
initialize() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
this.initializeIntersectionObserver(), typeof window < "u" && setTimeout(() => {
|
|
47
|
-
this.logger.debug("Running initial scan after timeout..."), this.findAndObserveContentletElements();
|
|
48
|
-
}, f), this.initializeDynamicContentDetector(), this.initializePageVisibilityHandler(), this.initializePageNavigationHandler(), this.logger.info("Impression tracking initialized with config:", this.impressionConfig);
|
|
49
|
-
}
|
|
39
|
+
a() && (this.initializeIntersectionObserver(), typeof window < "u" && setTimeout(() => {
|
|
40
|
+
this.logger.debug("Running initial scan after timeout..."), this.findAndObserveContentletElements();
|
|
41
|
+
}, p), this.initializeDynamicContentDetector(), this.initializePageVisibilityHandler(), this.initializePageNavigationHandler(), this.logger.info("Impression tracking initialized with config:", this.impressionConfig));
|
|
50
42
|
}
|
|
51
43
|
/** Sets up IntersectionObserver with configured visibility threshold */
|
|
52
44
|
initializeIntersectionObserver() {
|
|
@@ -92,7 +84,7 @@ class E {
|
|
|
92
84
|
}
|
|
93
85
|
/** Watches for new contentlets added to DOM (debounced for performance) */
|
|
94
86
|
initializeDynamicContentDetector() {
|
|
95
|
-
a() && (this.mutationObserver =
|
|
87
|
+
a() && (this.mutationObserver = f(() => {
|
|
96
88
|
this.findAndObserveContentletElements();
|
|
97
89
|
}), this.logger.info("MutationObserver enabled for dynamic content detection"));
|
|
98
90
|
}
|
|
@@ -150,7 +142,7 @@ class E {
|
|
|
150
142
|
trackAndSendImpression(e, i) {
|
|
151
143
|
const t = this.elementImpressionStates.get(e);
|
|
152
144
|
if (!t) return;
|
|
153
|
-
const s = t.visibleSince ? Date.now() - t.visibleSince : 0, n =
|
|
145
|
+
const s = t.visibleSince ? Date.now() - t.visibleSince : 0, n = v(i), r = m(i), o = parseInt(i.dataset.dotDomIndex || "-1", 10), h = {
|
|
154
146
|
content: {
|
|
155
147
|
identifier: n.identifier,
|
|
156
148
|
inode: n.inode,
|
|
@@ -162,7 +154,7 @@ class E {
|
|
|
162
154
|
dom_index: o
|
|
163
155
|
}
|
|
164
156
|
};
|
|
165
|
-
this.notifySubscribers(
|
|
157
|
+
this.notifySubscribers(u, h), this.markImpressionAsTracked(e), t.timer = null, t.visibleSince = null, t.tracked = !0, this.observer && this.observer.unobserve(i), this.logger.info(
|
|
166
158
|
`Fired impression for ${e} (dwell: ${s}ms) - element unobserved`,
|
|
167
159
|
n
|
|
168
160
|
);
|
|
@@ -177,7 +169,7 @@ class E {
|
|
|
177
169
|
}
|
|
178
170
|
/** Post-dwell check: verifies element still meets visibility threshold */
|
|
179
171
|
isElementStillVisible(e) {
|
|
180
|
-
return document.visibilityState !== "visible" ? !1 :
|
|
172
|
+
return document.visibilityState !== "visible" ? !1 : d(
|
|
181
173
|
e,
|
|
182
174
|
this.impressionConfig.visibilityThreshold
|
|
183
175
|
);
|
|
@@ -198,5 +190,5 @@ class E {
|
|
|
198
190
|
}
|
|
199
191
|
}
|
|
200
192
|
export {
|
|
201
|
-
|
|
193
|
+
T as DotCMSImpressionTracker
|
|
202
194
|
};
|
|
@@ -156,3 +156,22 @@ export declare const ANALYTICS_WINDOWS_CLEANUP_KEY = "__dotAnalyticsCleanup__";
|
|
|
156
156
|
* @see core-web/libs/sdk/react/src/lib/next/components/Contentlet/Contentlet.tsx
|
|
157
157
|
*/
|
|
158
158
|
export declare const CONTENTLET_CLASS = "dotcms-contentlet";
|
|
159
|
+
/**
|
|
160
|
+
* Queue persistence configuration constants
|
|
161
|
+
* Used for storing events in sessionStorage for traditional page navigations
|
|
162
|
+
*/
|
|
163
|
+
/**
|
|
164
|
+
* Session storage key for persistent tab ID
|
|
165
|
+
* This ID remains constant across page navigations within the same browser tab
|
|
166
|
+
*/
|
|
167
|
+
export declare const TAB_ID_STORAGE_KEY = "dot_analytics_tab_id";
|
|
168
|
+
/**
|
|
169
|
+
* Prefix for queue storage key in sessionStorage
|
|
170
|
+
* Full key format: dot_analytics_queue_{tabId}
|
|
171
|
+
*/
|
|
172
|
+
export declare const QUEUE_STORAGE_KEY_PREFIX = "dot_analytics_queue";
|
|
173
|
+
/**
|
|
174
|
+
* Maximum age in milliseconds for persisted events
|
|
175
|
+
* Events older than this will be discarded (24 hours)
|
|
176
|
+
*/
|
|
177
|
+
export declare const MAX_EVENT_AGE_MS: number;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
const
|
|
1
|
+
const c = "/api/v1/analytics/content/event", o = {
|
|
2
2
|
PAGEVIEW: "pageview",
|
|
3
3
|
CONTENT_IMPRESSION: "content_impression",
|
|
4
4
|
CONTENT_CLICK: "content_click",
|
|
5
5
|
CONVERSION: "conversion"
|
|
6
|
-
},
|
|
6
|
+
}, s = [
|
|
7
7
|
"utm_source",
|
|
8
8
|
"utm_medium",
|
|
9
9
|
"utm_campaign",
|
|
10
10
|
"utm_term",
|
|
11
11
|
"utm_content"
|
|
12
|
-
],
|
|
12
|
+
], I = 30, e = "dot_analytics_session_id", N = "dot_analytics_user_id", _ = 15, E = 5e3, A = ["click"], O = {
|
|
13
13
|
eventBatchSize: _,
|
|
14
14
|
// Max events per batch - auto-sends when reached
|
|
15
15
|
flushInterval: E
|
|
16
16
|
// Time between flushes - sends whatever is queued
|
|
17
|
-
},
|
|
17
|
+
}, L = [
|
|
18
18
|
"title",
|
|
19
19
|
"url",
|
|
20
20
|
"path",
|
|
@@ -28,28 +28,31 @@ const I = "/api/v1/analytics/content/event", c = {
|
|
|
28
28
|
dwellMs: n,
|
|
29
29
|
maxNodes: T,
|
|
30
30
|
throttleMs: S
|
|
31
|
-
}, U = "content_impression",
|
|
31
|
+
}, U = "content_impression", a = "content_click", D = 300, M = "a, button", R = "__dotAnalyticsActive__", l = "__dotAnalyticsCleanup__", F = "dotcms-contentlet", P = "dot_analytics_tab_id", d = "dot_analytics_queue", r = 1440 * 60 * 1e3;
|
|
32
32
|
export {
|
|
33
33
|
A as ACTIVITY_EVENTS,
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
c as ANALYTICS_ENDPOINT,
|
|
35
|
+
L as ANALYTICS_JS_DEFAULT_PROPERTIES,
|
|
36
36
|
R as ANALYTICS_WINDOWS_ACTIVE_KEY,
|
|
37
37
|
l as ANALYTICS_WINDOWS_CLEANUP_KEY,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
M as CLICKABLE_ELEMENTS_SELECTOR,
|
|
39
|
+
a as CLICK_EVENT_TYPE,
|
|
40
|
+
F as CONTENTLET_CLASS,
|
|
41
|
+
D as DEFAULT_CLICK_THROTTLE_MS,
|
|
42
42
|
C as DEFAULT_IMPRESSION_CONFIG,
|
|
43
43
|
n as DEFAULT_IMPRESSION_DWELL_MS,
|
|
44
44
|
T as DEFAULT_IMPRESSION_MAX_NODES,
|
|
45
45
|
i as DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS,
|
|
46
46
|
S as DEFAULT_IMPRESSION_THROTTLE_MS,
|
|
47
47
|
t as DEFAULT_IMPRESSION_VISIBILITY_THRESHOLD,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
O as DEFAULT_QUEUE_CONFIG,
|
|
49
|
+
I as DEFAULT_SESSION_TIMEOUT_MINUTES,
|
|
50
|
+
o as DotCMSPredefinedEventType,
|
|
51
|
+
s as EXPECTED_UTM_KEYS,
|
|
52
52
|
U as IMPRESSION_EVENT_TYPE,
|
|
53
|
+
r as MAX_EVENT_AGE_MS,
|
|
54
|
+
d as QUEUE_STORAGE_KEY_PREFIX,
|
|
53
55
|
e as SESSION_STORAGE_KEY,
|
|
56
|
+
P as TAB_ID_STORAGE_KEY,
|
|
54
57
|
N as USER_ID_KEY
|
|
55
58
|
};
|
|
@@ -6,4 +6,4 @@ import { DotCMSAnalyticsConfig, DotCMSAnalyticsRequestBody } from '../models';
|
|
|
6
6
|
* @param keepalive - Use keepalive mode for page unload scenarios (default: false)
|
|
7
7
|
* @returns A promise that resolves when the request is complete
|
|
8
8
|
*/
|
|
9
|
-
export declare const sendAnalyticsEvent: (payload: DotCMSAnalyticsRequestBody, config: DotCMSAnalyticsConfig, keepalive?: boolean) => Promise<
|
|
9
|
+
export declare const sendAnalyticsEvent: (payload: DotCMSAnalyticsRequestBody, config: DotCMSAnalyticsConfig, keepalive?: boolean) => Promise<boolean>;
|
|
@@ -1,34 +1,36 @@
|
|
|
1
|
-
import { ANALYTICS_ENDPOINT as
|
|
2
|
-
import { createPluginLogger as
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
1
|
+
import { ANALYTICS_ENDPOINT as p } from "../constants/dot-analytics.constants.js";
|
|
2
|
+
import { createPluginLogger as l } from "../utils/dot-analytics.utils.js";
|
|
3
|
+
const m = async (n, i, c = !1) => {
|
|
4
|
+
const e = l("HTTP", i), f = `${i.server}${p}`, g = JSON.stringify(n);
|
|
5
|
+
e.info(`Sending ${n.events.length} event(s)${c ? " (keepalive)" : ""}`, {
|
|
6
6
|
payload: n
|
|
7
7
|
});
|
|
8
8
|
try {
|
|
9
|
-
const
|
|
9
|
+
const r = {
|
|
10
10
|
method: "POST",
|
|
11
11
|
headers: { "Content-Type": "application/json" },
|
|
12
12
|
body: g
|
|
13
13
|
};
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const t = await fetch(
|
|
14
|
+
if (c)
|
|
15
|
+
return r.keepalive = !0, r.credentials = "omit", fetch(f, r).catch((o) => {
|
|
16
|
+
e.error("Keepalive request failed (browser may have ignored it):", o);
|
|
17
|
+
}), !0;
|
|
18
|
+
const t = await fetch(f, r);
|
|
19
19
|
if (!t.ok) {
|
|
20
|
-
const
|
|
20
|
+
const o = t.statusText || "Unknown Error", a = `HTTP ${t.status}: ${o}`;
|
|
21
21
|
try {
|
|
22
22
|
const s = await t.json();
|
|
23
|
-
s.message ?
|
|
23
|
+
s.message ? e.warn(`${s.message} (${a})`) : e.warn(`${a} - No error message in response`);
|
|
24
24
|
} catch (s) {
|
|
25
|
-
|
|
25
|
+
e.warn(`${a} - Failed to parse error response:`, s);
|
|
26
26
|
}
|
|
27
|
+
return !1;
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
return !0;
|
|
30
|
+
} catch (r) {
|
|
31
|
+
return e.error("Error sending event:", r), !1;
|
|
30
32
|
}
|
|
31
33
|
};
|
|
32
34
|
export {
|
|
33
|
-
|
|
35
|
+
m as sendAnalyticsEvent
|
|
34
36
|
};
|
|
@@ -167,3 +167,15 @@ export type DotCMSCustomEvent = DotCMSEventBase<DotCMSCustomEventType, DotCMSCus
|
|
|
167
167
|
* Used primarily for type documentation and validation.
|
|
168
168
|
*/
|
|
169
169
|
export type DotCMSEvent = DotCMSPageViewEvent | DotCMSContentImpressionEvent | DotCMSContentClickEvent | DotCMSConversionEvent | DotCMSCustomEvent;
|
|
170
|
+
/**
|
|
171
|
+
* Structure for persisted queue in sessionStorage.
|
|
172
|
+
* Used to preserve events across traditional page navigations.
|
|
173
|
+
*/
|
|
174
|
+
export interface PersistedQueue {
|
|
175
|
+
/** Unique identifier for this browser tab */
|
|
176
|
+
tabId: string;
|
|
177
|
+
/** Timestamp when the queue was last persisted */
|
|
178
|
+
timestamp: number;
|
|
179
|
+
/** Array of events waiting to be sent */
|
|
180
|
+
events: DotCMSEvent[];
|
|
181
|
+
}
|
|
@@ -23,6 +23,10 @@ export declare const createAnalyticsQueue: (config: DotCMSAnalyticsConfig) => {
|
|
|
23
23
|
/**
|
|
24
24
|
* Clean up queue resources
|
|
25
25
|
* Flushes remaining events and cleans up listeners
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT: Does NOT clear sessionStorage
|
|
28
|
+
* - Storage is cleared only after sendBatch succeeds or in initialize()
|
|
29
|
+
* - This allows events to persist across traditional page navigations
|
|
26
30
|
*/
|
|
27
31
|
cleanup: () => void;
|
|
28
32
|
};
|
|
@@ -1,49 +1,125 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { DEFAULT_QUEUE_CONFIG as
|
|
4
|
-
import { sendAnalyticsEvent as
|
|
5
|
-
import { createPluginLogger as
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
let
|
|
9
|
-
const
|
|
10
|
-
...
|
|
11
|
-
...typeof
|
|
12
|
-
}, g = (e
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import q from "@analytics/queue-utils";
|
|
2
|
+
import F from "@analytics/router-utils";
|
|
3
|
+
import { DEFAULT_QUEUE_CONFIG as x, TAB_ID_STORAGE_KEY as I, MAX_EVENT_AGE_MS as z, QUEUE_STORAGE_KEY_PREFIX as P } from "../constants/dot-analytics.constants.js";
|
|
4
|
+
import { sendAnalyticsEvent as w } from "../http/dot-analytics.http.js";
|
|
5
|
+
import { createPluginLogger as T, safeSessionStorage as d, generateSecureId as U, getAnalyticsContext as D } from "../utils/dot-analytics.utils.js";
|
|
6
|
+
const R = (l) => {
|
|
7
|
+
const n = T("Queue", l);
|
|
8
|
+
let i = null, c = null, f = !1, m = !1, v = typeof window < "u" ? window.location.pathname : "", o = "", a = [];
|
|
9
|
+
const y = {
|
|
10
|
+
...x,
|
|
11
|
+
...typeof l.queue == "object" ? l.queue : {}
|
|
12
|
+
}, g = () => `${P}_${o}`, E = (e) => {
|
|
13
|
+
const t = e;
|
|
14
|
+
if (!t || typeof t != "object" || Array.isArray(t))
|
|
15
|
+
return null;
|
|
16
|
+
if (typeof t.tabId != "string" || typeof t.timestamp != "number" || !Number.isFinite(t.timestamp) || !Array.isArray(t.events))
|
|
17
|
+
return n.warn("Invalid persisted queue: structural mismatch"), null;
|
|
18
|
+
const s = t.events.filter(
|
|
19
|
+
(r) => r && typeof r == "object" && "event_type" in r
|
|
20
|
+
);
|
|
21
|
+
return {
|
|
22
|
+
tabId: t.tabId,
|
|
23
|
+
timestamp: t.timestamp,
|
|
24
|
+
events: s
|
|
25
|
+
};
|
|
26
|
+
}, A = () => {
|
|
27
|
+
try {
|
|
28
|
+
const e = g(), t = d.getItem(e);
|
|
29
|
+
if (!t)
|
|
30
|
+
return n.debug("No persisted queue found"), null;
|
|
31
|
+
const s = JSON.parse(t), r = E(s);
|
|
32
|
+
if (!r)
|
|
33
|
+
return d.removeItem(e), null;
|
|
34
|
+
const u = Date.now() - r.timestamp;
|
|
35
|
+
return u > z ? (n.warn(
|
|
36
|
+
`Persisted events too old (${Math.round(u / 1e3 / 60 / 60)}h), discarding`
|
|
37
|
+
), d.removeItem(e), null) : (n.info(
|
|
38
|
+
`Loaded ${r.events.length} persisted event(s) from storage (age: ${Math.round(u / 1e3)}s)`
|
|
39
|
+
), r);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return n.error("Failed to load persisted queue", e), d.removeItem(g()), null;
|
|
42
|
+
}
|
|
43
|
+
}, h = () => {
|
|
44
|
+
if (a.length !== 0)
|
|
45
|
+
try {
|
|
46
|
+
const e = g(), t = {
|
|
47
|
+
tabId: o,
|
|
48
|
+
timestamp: Date.now(),
|
|
49
|
+
events: a
|
|
50
|
+
};
|
|
51
|
+
d.setItem(e, JSON.stringify(t)), n.debug(`Persisted ${a.length} event(s) to storage`);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
e instanceof Error && e.name === "QuotaExceededError" ? n.warn("sessionStorage quota exceeded, continuing without persistence") : n.error("Failed to persist queue", e);
|
|
54
|
+
}
|
|
55
|
+
}, b = () => {
|
|
56
|
+
try {
|
|
57
|
+
const e = g();
|
|
58
|
+
d.removeItem(e), n.debug("Persisted queue cleared from storage");
|
|
59
|
+
} catch (e) {
|
|
60
|
+
n.error("Failed to clear persisted queue", e);
|
|
61
|
+
}
|
|
62
|
+
}, _ = async (e, t = !0) => {
|
|
63
|
+
if (e.length === 0)
|
|
64
|
+
return !0;
|
|
65
|
+
n.info(`Sending ${e.length} persisted event(s) immediately`);
|
|
66
|
+
const r = { context: D(l), events: e };
|
|
67
|
+
return w(r, l, t);
|
|
68
|
+
}, $ = (e, t) => {
|
|
69
|
+
if (!c) return;
|
|
70
|
+
n.debug(`Sending batch of ${e.length} event(s)`, {
|
|
15
71
|
events: e,
|
|
16
|
-
keepalive:
|
|
17
|
-
}),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
72
|
+
keepalive: f
|
|
73
|
+
}), w({ context: c, events: e }, l, f), a = a.filter(
|
|
74
|
+
(r) => !e.some((u) => u === r)
|
|
75
|
+
), f || (a.length === 0 ? b() : h());
|
|
76
|
+
}, p = () => {
|
|
77
|
+
!i || i.size() === 0 || !c || (n.info(`Flushing ${i.size()} events (page hidden/unload)`), f = !0, i.flush(!0));
|
|
78
|
+
}, S = () => {
|
|
79
|
+
if (n.debug("handleVisibilityChange", document.visibilityState), document.visibilityState === "hidden") {
|
|
80
|
+
if (m) {
|
|
81
|
+
n.debug("Skipping flush (SPA navigation detected), persisting to storage"), h();
|
|
24
82
|
return;
|
|
25
83
|
}
|
|
26
|
-
|
|
27
|
-
} else document.visibilityState === "visible" && (
|
|
84
|
+
p();
|
|
85
|
+
} else document.visibilityState === "visible" && (m = !1);
|
|
28
86
|
};
|
|
29
87
|
return {
|
|
30
88
|
/**
|
|
31
89
|
* Initialize the queue with smart batching
|
|
32
90
|
*/
|
|
33
91
|
initialize: () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
92
|
+
if (typeof window < "u") {
|
|
93
|
+
const t = d.getItem(I);
|
|
94
|
+
if (t)
|
|
95
|
+
o = t, n.debug(`Reusing Tab ID: ${o}`);
|
|
96
|
+
else {
|
|
97
|
+
typeof crypto < "u" && typeof crypto.randomUUID == "function" ? o = crypto.randomUUID() : (o = U("tab"), n.debug("crypto.randomUUID not available, using fallback generator"));
|
|
98
|
+
try {
|
|
99
|
+
d.setItem(I, o);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
n.debug(`Generated new Tab ID: ${o}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const e = A();
|
|
106
|
+
e && e.events.length > 0 && _(e.events, !1).then((t) => {
|
|
107
|
+
t ? b() : n.warn(
|
|
108
|
+
"Failed to send persisted events, keeping in storage for next retry"
|
|
109
|
+
);
|
|
110
|
+
}), i = q(
|
|
111
|
+
(t, s) => {
|
|
112
|
+
$(t);
|
|
37
113
|
},
|
|
38
114
|
{
|
|
39
|
-
max:
|
|
40
|
-
interval:
|
|
115
|
+
max: y.eventBatchSize,
|
|
116
|
+
interval: y.flushInterval,
|
|
41
117
|
throttle: !1
|
|
42
118
|
// Always false - enables both batch size and interval triggers
|
|
43
119
|
}
|
|
44
|
-
), typeof window < "u" && typeof document < "u" && (document.addEventListener("visibilitychange",
|
|
45
|
-
|
|
46
|
-
|
|
120
|
+
), typeof window < "u" && typeof document < "u" && (document.addEventListener("visibilitychange", S), window.addEventListener("pagehide", p), F((t) => {
|
|
121
|
+
m = !0, v = t, n.debug(`SPA navigation detected (${v})`), setTimeout(() => {
|
|
122
|
+
m = !1;
|
|
47
123
|
}, 100);
|
|
48
124
|
}));
|
|
49
125
|
},
|
|
@@ -53,28 +129,33 @@ const L = (u) => {
|
|
|
53
129
|
* - Sends immediately when eventBatchSize reached (with throttle: false)
|
|
54
130
|
* - Sends pending events every flushInterval
|
|
55
131
|
*/
|
|
56
|
-
enqueue: (e,
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
i.
|
|
60
|
-
|
|
132
|
+
enqueue: (e, t) => {
|
|
133
|
+
if (c = t, !i) return;
|
|
134
|
+
a.push(e);
|
|
135
|
+
const s = i.size() + 1, r = y.eventBatchSize, u = s >= r;
|
|
136
|
+
n.debug(
|
|
137
|
+
`Event added. Queue size: ${s}/${r}${u ? " (full, sending...)" : ""}`,
|
|
61
138
|
{ eventType: e.event_type, event: e }
|
|
62
|
-
),
|
|
139
|
+
), i.push(e), h();
|
|
63
140
|
},
|
|
64
141
|
/**
|
|
65
142
|
* Get queue size for debugging
|
|
66
143
|
* Returns the number of events in smartQueue
|
|
67
144
|
*/
|
|
68
|
-
size: () =>
|
|
145
|
+
size: () => i?.size() ?? 0,
|
|
69
146
|
/**
|
|
70
147
|
* Clean up queue resources
|
|
71
148
|
* Flushes remaining events and cleans up listeners
|
|
149
|
+
*
|
|
150
|
+
* IMPORTANT: Does NOT clear sessionStorage
|
|
151
|
+
* - Storage is cleared only after sendBatch succeeds or in initialize()
|
|
152
|
+
* - This allows events to persist across traditional page navigations
|
|
72
153
|
*/
|
|
73
154
|
cleanup: () => {
|
|
74
|
-
|
|
155
|
+
p(), typeof window < "u" && typeof document < "u" && (document.removeEventListener("visibilitychange", S), window.removeEventListener("pagehide", p)), i = null, c = null, f = !1, a = [];
|
|
75
156
|
}
|
|
76
157
|
};
|
|
77
158
|
};
|
|
78
159
|
export {
|
|
79
|
-
|
|
160
|
+
R as createAnalyticsQueue
|
|
80
161
|
};
|
|
@@ -50,6 +50,7 @@ export declare const generateSecureId: (prefix: string) => string;
|
|
|
50
50
|
export declare const safeSessionStorage: {
|
|
51
51
|
getItem: (key: string) => string | null;
|
|
52
52
|
setItem: (key: string, value: string) => void;
|
|
53
|
+
removeItem: (key: string) => void;
|
|
53
54
|
};
|
|
54
55
|
/**
|
|
55
56
|
* Gets or generates a user ID from localStorage.
|