@dotcms/analytics 1.2.3-next.3 → 1.2.3-next.4

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.
@@ -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 I = "/api/v1/analytics/content/event", c = {
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
- }, o = [
6
+ }, s = [
7
7
  "utm_source",
8
8
  "utm_medium",
9
9
  "utm_campaign",
10
10
  "utm_term",
11
11
  "utm_content"
12
- ], s = 30, e = "dot_analytics_session_id", N = "dot_analytics_user_id", _ = 15, E = 5e3, A = ["click"], L = {
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
- }, O = [
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", D = "content_click", M = 300, a = "a, button", R = "__dotAnalyticsActive__", l = "__dotAnalyticsCleanup__", r = "dotcms-contentlet";
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
- I as ANALYTICS_ENDPOINT,
35
- O as ANALYTICS_JS_DEFAULT_PROPERTIES,
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
- a as CLICKABLE_ELEMENTS_SELECTOR,
39
- D as CLICK_EVENT_TYPE,
40
- r as CONTENTLET_CLASS,
41
- M as DEFAULT_CLICK_THROTTLE_MS,
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
- L as DEFAULT_QUEUE_CONFIG,
49
- s as DEFAULT_SESSION_TIMEOUT_MINUTES,
50
- c as DotCMSPredefinedEventType,
51
- o as EXPECTED_UTM_KEYS,
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<void>;
9
+ export declare const sendAnalyticsEvent: (payload: DotCMSAnalyticsRequestBody, config: DotCMSAnalyticsConfig, keepalive?: boolean) => Promise<boolean>;
@@ -1,34 +1,36 @@
1
- import { ANALYTICS_ENDPOINT as f } from "../constants/dot-analytics.constants.js";
2
- import { createPluginLogger as T } from "../utils/dot-analytics.utils.js";
3
- const h = async (n, a, i = !1) => {
4
- const r = T("HTTP", a), c = `${a.server}${f}`, g = JSON.stringify(n);
5
- r.info(`Sending ${n.events.length} event(s)${i ? " (keepalive)" : ""}`, {
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 e = {
9
+ const r = {
10
10
  method: "POST",
11
11
  headers: { "Content-Type": "application/json" },
12
12
  body: g
13
13
  };
14
- if (i) {
15
- e.keepalive = !0, e.credentials = "omit", fetch(c, e);
16
- return;
17
- }
18
- const t = await fetch(c, e);
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 p = t.statusText || "Unknown Error", o = `HTTP ${t.status}: ${p}`;
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 ? r.warn(`${s.message} (${o})`) : r.warn(`${o} - No error message in response`);
23
+ s.message ? e.warn(`${s.message} (${a})`) : e.warn(`${a} - No error message in response`);
24
24
  } catch (s) {
25
- r.warn(`${o} - Failed to parse error response:`, s);
25
+ e.warn(`${a} - Failed to parse error response:`, s);
26
26
  }
27
+ return !1;
27
28
  }
28
- } catch (e) {
29
- r.error("Error sending event:", e);
29
+ return !0;
30
+ } catch (r) {
31
+ return e.error("Error sending event:", r), !1;
30
32
  }
31
33
  };
32
34
  export {
33
- h as sendAnalyticsEvent
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 m from "@analytics/queue-utils";
2
- import v from "@analytics/router-utils";
3
- import { DEFAULT_QUEUE_CONFIG as y } from "../constants/dot-analytics.constants.js";
4
- import { sendAnalyticsEvent as b } from "../http/dot-analytics.http.js";
5
- import { createPluginLogger as w } from "../utils/dot-analytics.utils.js";
6
- const L = (u) => {
7
- const i = w("Queue", u);
8
- let t = null, n = null, o = !1, d = !1, f = typeof window < "u" ? window.location.pathname : "";
9
- const a = {
10
- ...y,
11
- ...typeof u.queue == "object" ? u.queue : {}
12
- }, g = (e, s) => {
13
- if (!n) return;
14
- i.debug(`Sending batch of ${e.length} event(s)`, {
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: o
17
- }), b({ context: n, events: e }, u, o);
18
- }, l = () => {
19
- !t || t.size() === 0 || !n || (i.info(`Flushing ${t.size()} events (page hidden/unload)`), o = !0, t.flush(!0));
20
- }, c = () => {
21
- if (i.debug("handleVisibilityChange", document.visibilityState), document.visibilityState === "hidden") {
22
- if (d) {
23
- i.debug("Skipping flush (SPA navigation detected)");
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
- l();
27
- } else document.visibilityState === "visible" && (d = !1);
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
- t = m(
35
- (e, s) => {
36
- g(e);
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: a.eventBatchSize,
40
- interval: a.flushInterval,
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", c), window.addEventListener("pagehide", l), v((e) => {
45
- d = !0, f = e, i.debug(`SPA navigation detected (${f})`), setTimeout(() => {
46
- d = !1;
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, s) => {
57
- if (n = s, !t) return;
58
- const r = t.size() + 1, p = a.eventBatchSize, h = r >= p;
59
- i.debug(
60
- `Event added. Queue size: ${r}/${p}${h ? " (full, sending...)" : ""}`,
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
- ), t.push(e);
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: () => t?.size() ?? 0,
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
- l(), typeof window < "u" && typeof document < "u" && (document.removeEventListener("visibilitychange", c), window.removeEventListener("pagehide", l)), t = null, n = null, o = !1;
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
- L as createAnalyticsQueue
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.
@@ -1,6 +1,6 @@
1
- import { ANALYTICS_JS_DEFAULT_PROPERTIES as _, SESSION_STORAGE_KEY as g, DEFAULT_SESSION_TIMEOUT_MINUTES as p, USER_ID_KEY as m, DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS as T, EXPECTED_UTM_KEYS as D, CONTENTLET_CLASS as h } from "../constants/dot-analytics.constants.js";
2
- import { DotLogger as E } from "../dot-analytics.logger.js";
3
- function $(t) {
1
+ import { ANALYTICS_JS_DEFAULT_PROPERTIES as p, SESSION_STORAGE_KEY as g, DEFAULT_SESSION_TIMEOUT_MINUTES as T, USER_ID_KEY as f, DEFAULT_IMPRESSION_MUTATION_OBSERVER_DEBOUNCE_MS as I, EXPECTED_UTM_KEYS as D, CONTENTLET_CLASS as h } from "../constants/dot-analytics.constants.js";
2
+ import { DotLogger as y } from "../dot-analytics.logger.js";
3
+ function P(t) {
4
4
  const e = [];
5
5
  return t.siteAuth?.trim() || e.push('"siteAuth"'), t.server?.trim() || e.push('"server"'), e.length > 0 ? e : null;
6
6
  }
@@ -8,7 +8,7 @@ let d = null, u = null;
8
8
  const l = (t) => {
9
9
  const e = Date.now(), n = Math.random().toString(36).substr(2, 9), s = Math.random().toString(36).substr(2, 9);
10
10
  return `${t}_${e}_${n}${s}`;
11
- }, f = {
11
+ }, w = {
12
12
  getItem: (t) => {
13
13
  try {
14
14
  return localStorage.getItem(t);
@@ -19,20 +19,49 @@ const l = (t) => {
19
19
  setItem: (t, e) => {
20
20
  try {
21
21
  localStorage.setItem(t, e);
22
+ } catch (n) {
23
+ throw console.warn(`DotCMS Analytics [Core]: Could not save ${t} to localStorage`), n;
24
+ }
25
+ }
26
+ }, R = {
27
+ getItem: (t) => {
28
+ try {
29
+ return sessionStorage.getItem(t);
30
+ } catch {
31
+ return null;
32
+ }
33
+ },
34
+ setItem: (t, e) => {
35
+ try {
36
+ sessionStorage.setItem(t, e);
37
+ } catch (n) {
38
+ throw console.warn(`DotCMS Analytics [Core]: Could not save ${t} to sessionStorage`), n;
39
+ }
40
+ },
41
+ removeItem: (t) => {
42
+ try {
43
+ sessionStorage.removeItem(t);
22
44
  } catch {
23
- console.warn(`DotCMS Analytics [Core]: Could not save ${t} to localStorage`);
45
+ console.warn(`DotCMS Analytics [Core]: Could not remove ${t} from sessionStorage`);
24
46
  }
25
47
  }
26
- }, I = () => {
27
- let t = f.getItem(m);
28
- return t || (t = l("user"), f.setItem(m, t)), t;
29
- }, y = (t) => {
48
+ }, E = () => {
49
+ let t = w.getItem(f);
50
+ if (!t) {
51
+ t = l("user");
52
+ try {
53
+ w.setItem(f, t);
54
+ } catch {
55
+ }
56
+ }
57
+ return t;
58
+ }, C = (t) => {
30
59
  const e = new Date(t), n = /* @__PURE__ */ new Date(), s = new Date(
31
60
  e.getUTCFullYear(),
32
61
  e.getUTCMonth(),
33
62
  e.getUTCDate()
34
- ), r = new Date(n.getUTCFullYear(), n.getUTCMonth(), n.getUTCDate());
35
- return s.getTime() !== r.getTime();
63
+ ), o = new Date(n.getUTCFullYear(), n.getUTCMonth(), n.getUTCDate());
64
+ return s.getTime() !== o.getTime();
36
65
  }, v = () => {
37
66
  const t = Date.now();
38
67
  if (typeof window > "u")
@@ -40,16 +69,16 @@ const l = (t) => {
40
69
  try {
41
70
  const e = sessionStorage.getItem(g);
42
71
  if (e) {
43
- const { sessionId: r, startTime: o, lastActivity: a } = JSON.parse(e), i = !y(o), c = t - a < p * 60 * 1e3;
44
- if (i && c)
72
+ const { sessionId: o, startTime: r, lastActivity: c } = JSON.parse(e), a = !C(r), i = t - c < T * 60 * 1e3;
73
+ if (a && i)
45
74
  return sessionStorage.setItem(
46
75
  g,
47
76
  JSON.stringify({
48
- sessionId: r,
49
- startTime: o,
77
+ sessionId: o,
78
+ startTime: r,
50
79
  lastActivity: t
51
80
  })
52
- ), r;
81
+ ), o;
53
82
  }
54
83
  const n = l("session"), s = {
55
84
  sessionId: n,
@@ -60,58 +89,58 @@ const l = (t) => {
60
89
  } catch {
61
90
  return l("session_fallback");
62
91
  }
63
- }, P = (t) => {
64
- const e = v(), n = I(), s = C();
92
+ }, x = (t) => {
93
+ const e = v(), n = E(), s = A();
65
94
  return {
66
95
  site_auth: t.siteAuth,
67
96
  session_id: e,
68
97
  user_id: n,
69
98
  device: s
70
99
  };
71
- }, w = () => d || (d = {
100
+ }, _ = () => d || (d = {
72
101
  user_language: navigator.language,
73
102
  doc_encoding: document.characterSet || document.charset,
74
103
  screen_resolution: typeof screen < "u" && screen.width && screen.height ? `${screen.width}x${screen.height}` : ""
75
- }, d), C = () => {
76
- const t = w(), e = window.innerWidth || document.documentElement.clientWidth || 0, n = window.innerHeight || document.documentElement.clientHeight || 0;
104
+ }, d), A = () => {
105
+ const t = _(), e = window.innerWidth || document.documentElement.clientWidth || 0, n = window.innerHeight || document.documentElement.clientHeight || 0;
77
106
  return {
78
107
  screen_resolution: t.screen_resolution ?? "",
79
108
  language: t.user_language ?? "",
80
109
  viewport_width: String(e),
81
110
  viewport_height: String(n)
82
111
  };
83
- }, A = (t) => {
112
+ }, b = (t) => {
84
113
  const e = t.search;
85
114
  if (u && u.search === e)
86
115
  return u.params;
87
116
  const n = new URLSearchParams(e), s = {};
88
- return D.forEach((r) => {
89
- const o = n.get(r);
90
- if (o) {
91
- const a = r.replace("utm_", "");
92
- s[a] = o;
117
+ return D.forEach((o) => {
118
+ const r = n.get(o);
119
+ if (r) {
120
+ const c = o.replace("utm_", "");
121
+ s[c] = r;
93
122
  }
94
123
  }), u = { search: e, params: s }, s;
95
124
  }, O = () => {
96
125
  try {
97
- const t = (/* @__PURE__ */ new Date()).getTimezoneOffset(), e = t > 0 ? "-" : "+", n = Math.abs(t), s = Math.floor(n / 60), r = n % 60;
98
- return `${e}${s.toString().padStart(2, "0")}:${r.toString().padStart(2, "0")}`;
126
+ const t = (/* @__PURE__ */ new Date()).getTimezoneOffset(), e = t > 0 ? "-" : "+", n = Math.abs(t), s = Math.floor(n / 60), o = n % 60;
127
+ return `${e}${s.toString().padStart(2, "0")}:${o.toString().padStart(2, "0")}`;
99
128
  } catch {
100
129
  return "+00:00";
101
130
  }
102
- }, N = () => {
131
+ }, M = () => {
103
132
  try {
104
- const t = /* @__PURE__ */ new Date(), e = O(), n = t.getFullYear(), s = (t.getMonth() + 1).toString().padStart(2, "0"), r = t.getDate().toString().padStart(2, "0"), o = t.getHours().toString().padStart(2, "0"), a = t.getMinutes().toString().padStart(2, "0"), i = t.getSeconds().toString().padStart(2, "0");
105
- return `${n}-${s}-${r}T${o}:${a}:${i}${e}`;
133
+ const t = /* @__PURE__ */ new Date(), e = O(), n = t.getFullYear(), s = (t.getMonth() + 1).toString().padStart(2, "0"), o = t.getDate().toString().padStart(2, "0"), r = t.getHours().toString().padStart(2, "0"), c = t.getMinutes().toString().padStart(2, "0"), a = t.getSeconds().toString().padStart(2, "0");
134
+ return `${n}-${s}-${o}T${r}:${c}:${a}${e}`;
106
135
  } catch {
107
136
  return (/* @__PURE__ */ new Date()).toISOString();
108
137
  }
109
- }, R = (t, e = typeof window < "u" ? window.location : {}) => {
110
- const n = N(), s = w(), { properties: r } = t, o = {};
111
- Object.keys(r).forEach((c) => {
112
- _.includes(c) || (o[c] = r[c]);
138
+ }, Y = (t, e = typeof window < "u" ? window.location : {}) => {
139
+ const n = M(), s = _(), { properties: o } = t, r = {};
140
+ Object.keys(o).forEach((i) => {
141
+ p.includes(i) || (r[i] = o[i]);
113
142
  });
114
- const a = {
143
+ const c = {
115
144
  url: e.href,
116
145
  doc_encoding: s.doc_encoding,
117
146
  doc_hash: e.hash,
@@ -119,28 +148,28 @@ const l = (t) => {
119
148
  doc_search: e.search,
120
149
  doc_host: e.hostname,
121
150
  doc_path: e.pathname,
122
- title: r.title ?? document?.title
123
- }, i = A(e);
151
+ title: o.title ?? document?.title
152
+ }, a = b(e);
124
153
  return {
125
154
  ...t,
126
- page: a,
127
- ...Object.keys(i).length > 0 && { utm: i },
155
+ page: c,
156
+ ...Object.keys(a).length > 0 && { utm: a },
128
157
  // Only include custom if there are user-provided properties
129
- ...Object.keys(o).length > 0 && { custom: o },
158
+ ...Object.keys(r).length > 0 && { custom: r },
130
159
  local_time: n
131
160
  };
132
161
  };
133
- function b(t, e) {
162
+ function L(t, e) {
134
163
  let n = 0;
135
164
  return (...s) => {
136
- const r = Date.now();
137
- r - n >= e && (t(...s), n = r);
165
+ const o = Date.now();
166
+ o - n >= e && (t(...s), n = o);
138
167
  };
139
168
  }
140
- function x(t) {
169
+ function B(t) {
141
170
  return t.dataset.dotIdentifier || null;
142
171
  }
143
- function Y(t) {
172
+ function F(t) {
144
173
  return {
145
174
  identifier: t.dataset.dotIdentifier || "",
146
175
  inode: t.dataset.dotInode || "",
@@ -149,51 +178,59 @@ function Y(t) {
149
178
  baseType: t.dataset.dotBasetype || ""
150
179
  };
151
180
  }
152
- const B = 100, M = () => typeof window < "u" && typeof document < "u", F = () => Array.from(document.querySelectorAll(`.${h}`)), k = (t, e = T) => {
153
- const n = b(t, e), s = new MutationObserver((r) => {
181
+ const z = 100, N = () => typeof window < "u" && typeof document < "u", H = () => Array.from(document.querySelectorAll(`.${h}`)), J = (t, e = I) => {
182
+ const n = L(t, e), s = new MutationObserver((r) => {
154
183
  r.some((a) => a.addedNodes.length === 0 && a.removedNodes.length === 0 ? !1 : [
155
184
  ...Array.from(a.addedNodes),
156
185
  ...Array.from(a.removedNodes)
157
- ].some((c) => {
158
- if (c.nodeType !== Node.ELEMENT_NODE)
186
+ ].some((m) => {
187
+ if (m.nodeType !== Node.ELEMENT_NODE)
159
188
  return !1;
160
- const S = c;
189
+ const S = m;
161
190
  return S.classList?.contains(h) ? !0 : S.querySelector?.(`.${h}`) !== null;
162
191
  })) && n();
163
- });
164
- return s.observe(document.body, {
192
+ }), o = document.body || document.documentElement;
193
+ return o ? s.observe(o, {
165
194
  childList: !0,
166
195
  subtree: !0,
167
196
  attributes: !1,
168
197
  characterData: !1
198
+ }) : window.addEventListener("DOMContentLoaded", () => {
199
+ document.body && s.observe(document.body, {
200
+ childList: !0,
201
+ subtree: !0,
202
+ attributes: !1,
203
+ characterData: !1
204
+ });
169
205
  }), s;
170
- }, z = (t) => {
171
- M() && (window.addEventListener("beforeunload", t), window.addEventListener("pagehide", t));
172
- }, H = (t, e) => {
206
+ }, K = (t) => {
207
+ N() && (window.addEventListener("beforeunload", t), window.addEventListener("pagehide", t));
208
+ }, j = (t, e) => {
173
209
  const n = e.logLevel ?? (e.debug ? "debug" : "warn");
174
- return new E("Analytics", t, n);
175
- }, J = (t, e, n) => [
210
+ return new y("Analytics", t, n);
211
+ }, W = (t, e, n) => [
176
212
  t.impressions && e(t),
177
213
  t.clicks && n(t)
178
214
  ].filter(Boolean);
179
215
  export {
180
- B as INITIAL_SCAN_DELAY_MS,
181
- k as createContentletObserver,
182
- H as createPluginLogger,
183
- b as createThrottle,
184
- R as enrichPagePayloadOptimized,
185
- Y as extractContentletData,
186
- x as extractContentletIdentifier,
187
- A as extractUTMParameters,
188
- F as findContentlets,
216
+ z as INITIAL_SCAN_DELAY_MS,
217
+ J as createContentletObserver,
218
+ j as createPluginLogger,
219
+ L as createThrottle,
220
+ Y as enrichPagePayloadOptimized,
221
+ F as extractContentletData,
222
+ B as extractContentletIdentifier,
223
+ b as extractUTMParameters,
224
+ H as findContentlets,
189
225
  l as generateSecureId,
190
- P as getAnalyticsContext,
191
- C as getDeviceDataForContext,
192
- J as getEnhancedTrackingPlugins,
193
- N as getLocalTime,
226
+ x as getAnalyticsContext,
227
+ A as getDeviceDataForContext,
228
+ W as getEnhancedTrackingPlugins,
229
+ M as getLocalTime,
194
230
  v as getSessionId,
195
- I as getUserId,
196
- M as isBrowser,
197
- z as setupPluginCleanup,
198
- $ as validateAnalyticsConfig
231
+ E as getUserId,
232
+ N as isBrowser,
233
+ R as safeSessionStorage,
234
+ K as setupPluginCleanup,
235
+ P as validateAnalyticsConfig
199
236
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotcms/analytics",
3
- "version": "1.2.3-next.3",
3
+ "version": "1.2.3-next.4",
4
4
  "description": "Official JavaScript library for Content Analytics with DotCMS.",
5
5
  "repository": {
6
6
  "type": "git",