@xenia-martech/sdk-js 1.2.0-dev.3 → 1.2.0-dev.5
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 +106 -2
- package/dist/{actions-Bhji05Pe.d.cts → actions-C18Kv-4w.d.cts} +35 -0
- package/dist/{actions-Bhji05Pe.d.ts → actions-C18Kv-4w.d.ts} +35 -0
- package/dist/{api-ByoECRhR.d.ts → api-0IycTYJR.d.cts} +1 -1
- package/dist/{api-BqSvWgBZ.d.cts → api-COXWLL5L.d.ts} +1 -1
- package/dist/{chunk-TRRMMPBH.cjs → chunk-2DR5AVPX.cjs} +10 -45
- package/dist/chunk-2DR5AVPX.cjs.map +1 -0
- package/dist/chunk-BQ5PJCZ7.js +179 -0
- package/dist/chunk-BQ5PJCZ7.js.map +1 -0
- package/dist/{chunk-INM2SPH4.js → chunk-GWRDZIVA.js} +4 -36
- package/dist/chunk-GWRDZIVA.js.map +1 -0
- package/dist/{chunk-TGG5Y4QD.js → chunk-LW5AEJ6K.js} +5 -179
- package/dist/chunk-LW5AEJ6K.js.map +1 -0
- package/dist/chunk-N5M4PI7C.cjs +192 -0
- package/dist/chunk-N5M4PI7C.cjs.map +1 -0
- package/dist/chunk-OMMIRSPK.js +336 -0
- package/dist/chunk-OMMIRSPK.js.map +1 -0
- package/dist/chunk-QEYONKWY.js +42 -0
- package/dist/chunk-QEYONKWY.js.map +1 -0
- package/dist/{chunk-YZG6JR4E.cjs → chunk-RWE2OCQL.cjs} +5 -190
- package/dist/chunk-RWE2OCQL.cjs.map +1 -0
- package/dist/chunk-WHGK55JP.cjs +46 -0
- package/dist/chunk-WHGK55JP.cjs.map +1 -0
- package/dist/chunk-YLMMTKQL.cjs +358 -0
- package/dist/chunk-YLMMTKQL.cjs.map +1 -0
- package/dist/client/index.cjs +304 -26
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +15 -4
- package/dist/client/index.d.ts +15 -4
- package/dist/client/index.js +304 -26
- package/dist/client/index.js.map +1 -1
- package/dist/client/react/index.cjs +714 -26
- package/dist/client/react/index.cjs.map +1 -1
- package/dist/client/react/index.d.cts +32 -3
- package/dist/client/react/index.d.ts +32 -3
- package/dist/client/react/index.js +715 -29
- package/dist/client/react/index.js.map +1 -1
- package/dist/cms/contentful.d.cts +2 -1
- package/dist/cms/contentful.d.ts +2 -1
- package/dist/consent-Cemn91j3.d.ts +248 -0
- package/dist/consent-DgWdjsSy.d.cts +248 -0
- package/dist/{cookie-names-Bxd-VNyJ.d.ts → cookie-names-BvUaW3uo.d.ts} +1 -1
- package/dist/{cookie-names-RXVfGYPE.d.cts → cookie-names-CnL65Oly.d.cts} +1 -1
- package/dist/edge/index.cjs +280 -0
- package/dist/edge/index.cjs.map +1 -0
- package/dist/edge/index.d.cts +61 -0
- package/dist/edge/index.d.ts +61 -0
- package/dist/edge/index.js +191 -0
- package/dist/edge/index.js.map +1 -0
- package/dist/index.cjs +41 -40
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -1
- package/dist/next/index.cjs +331 -15
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +180 -6
- package/dist/next/index.d.ts +180 -6
- package/dist/next/index.js +320 -9
- package/dist/next/index.js.map +1 -1
- package/dist/personalize-content-c2DDAV6g.d.ts +140 -0
- package/dist/personalize-content-ghZPKlf1.d.cts +140 -0
- package/dist/server/index.cjs +34 -32
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +6 -5
- package/dist/server/index.d.ts +6 -5
- package/dist/server/index.js +5 -3
- package/dist/server/index.js.map +1 -1
- package/dist/{personalize-content-Bf6YZdkZ.d.cts → types-DgVHS4Qw.d.cts} +7 -138
- package/dist/{personalize-content-Bf6YZdkZ.d.ts → types-DgVHS4Qw.d.ts} +7 -138
- package/dist/xenia-sdk.iife.js +3 -3
- package/dist/xenia-sdk.iife.js.map +1 -1
- package/dist/{xenia-server-MlI3Khya.d.cts → xenia-server-C3O-cWQT.d.cts} +3 -2
- package/dist/{xenia-server-CcjQenru.d.ts → xenia-server-DV8Sjmoo.d.ts} +3 -2
- package/package.json +14 -1
- package/dist/chunk-INM2SPH4.js.map +0 -1
- package/dist/chunk-TGG5Y4QD.js.map +0 -1
- package/dist/chunk-TRRMMPBH.cjs.map +0 -1
- package/dist/chunk-YZG6JR4E.cjs.map +0 -1
package/dist/client/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as InteractionType, X as XeniaConfig, a as XeniaEvent, M as MessageCta, b as MessageTemplateFn, c as MessageComponentRenderer, W as WidgetFormSubmit, d as WidgetSubmitResponse, e as XeniaAction, f as MessagePlacement, C as ConsentState } from '../actions-
|
|
2
|
-
export { A as AnalyticsContext, g as AnalyticsEventName, h as AnalyticsIdentity, i as AnalyticsIngestResponse, j as AppliedSwap, k as ClientEvent, l as CmsAdapter, m as ContentSwap, n as IdentifyRequest, o as IdentifyResponse, p as Identity, J as JsonValue, q as MessageMountContext, r as MessageRenderContext, P as PagePersonalizeOptions, s as PageRef, t as PersonalizationSwap, R as ResolveBatchItem, u as ResolveBatchResult, v as ResolveOptions, w as ResolveResult, T as TrackOptions, x as WebhookResponse, y as WidgetPrize, z as WidgetSegment, B as WidgetType, D as XeniaClient, E as XeniaClientOptions, F as XeniaMarker, G as createXeniaClient, H as extractContentSwaps, K as parseActions, L as pendingTargets, N as personalizeContent, O as personalizePage, Q as spliceSlots } from '../actions-
|
|
1
|
+
import { I as InteractionType, X as XeniaConfig, a as XeniaEvent, M as MessageCta, b as MessageTemplateFn, c as MessageComponentRenderer, W as WidgetFormSubmit, d as WidgetSubmitResponse, e as XeniaAction, f as MessagePlacement, C as ConsentState } from '../actions-C18Kv-4w.cjs';
|
|
2
|
+
export { A as AnalyticsContext, g as AnalyticsEventName, h as AnalyticsIdentity, i as AnalyticsIngestResponse, j as AppliedSwap, k as ClientEvent, l as CmsAdapter, m as ContentSwap, n as IdentifyRequest, o as IdentifyResponse, p as Identity, J as JsonValue, q as MessageMountContext, r as MessageRenderContext, P as PagePersonalizeOptions, s as PageRef, t as PersonalizationSwap, R as ResolveBatchItem, u as ResolveBatchResult, v as ResolveOptions, w as ResolveResult, T as TrackOptions, x as WebhookResponse, y as WidgetPrize, z as WidgetSegment, B as WidgetType, D as XeniaClient, E as XeniaClientOptions, F as XeniaMarker, G as createXeniaClient, H as extractContentSwaps, K as parseActions, L as pendingTargets, N as personalizeContent, O as personalizePage, Q as spliceSlots } from '../actions-C18Kv-4w.cjs';
|
|
3
3
|
|
|
4
4
|
interface Parsed {
|
|
5
5
|
eventId: string;
|
|
@@ -9,10 +9,16 @@ interface Parsed {
|
|
|
9
9
|
declare function parseDataAttribute(value: string): Parsed | null;
|
|
10
10
|
/** Personalization slot metadata read from `data-xenia-*` attributes. */
|
|
11
11
|
interface SlotMeta {
|
|
12
|
-
|
|
12
|
+
/** The `data-xenia-slot` value: which section of the page this is. */
|
|
13
|
+
slot?: string;
|
|
14
|
+
contentKey?: string;
|
|
13
15
|
contentEntryId?: string;
|
|
14
16
|
baseEntryId?: string;
|
|
15
17
|
personalized?: boolean;
|
|
18
|
+
/** `data-xenia-bucket`, e.g. `personalized` — stored by Xenia as the slot event's bucket. */
|
|
19
|
+
bucket?: string;
|
|
20
|
+
/** Click only: the clicked link's or button's text (≤60 chars). */
|
|
21
|
+
label?: string;
|
|
16
22
|
}
|
|
17
23
|
interface AutoTrackOptions {
|
|
18
24
|
prefix: string;
|
|
@@ -30,8 +36,13 @@ interface AutoTrackOptions {
|
|
|
30
36
|
* scanning is skipped entirely.
|
|
31
37
|
*/
|
|
32
38
|
fireSlot?: (interaction: "impression" | "click", meta: SlotMeta) => void;
|
|
39
|
+
/** Which clicks in a slot count: `any` (default) or only a link's or button's (`controls`). */
|
|
40
|
+
slotClicks?: "any" | "controls";
|
|
33
41
|
}
|
|
34
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Read the resolve metadata attributes off a `[data-xenia-slot]` element. `null` when it carries
|
|
44
|
+
* neither a content key nor an entry id (not a resolved slot).
|
|
45
|
+
*/
|
|
35
46
|
declare function readSlotMeta(el: Element): SlotMeta | null;
|
|
36
47
|
/**
|
|
37
48
|
* Wires declarative `data-xenia` auto-tracking: delegated clicks, an
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as InteractionType, X as XeniaConfig, a as XeniaEvent, M as MessageCta, b as MessageTemplateFn, c as MessageComponentRenderer, W as WidgetFormSubmit, d as WidgetSubmitResponse, e as XeniaAction, f as MessagePlacement, C as ConsentState } from '../actions-
|
|
2
|
-
export { A as AnalyticsContext, g as AnalyticsEventName, h as AnalyticsIdentity, i as AnalyticsIngestResponse, j as AppliedSwap, k as ClientEvent, l as CmsAdapter, m as ContentSwap, n as IdentifyRequest, o as IdentifyResponse, p as Identity, J as JsonValue, q as MessageMountContext, r as MessageRenderContext, P as PagePersonalizeOptions, s as PageRef, t as PersonalizationSwap, R as ResolveBatchItem, u as ResolveBatchResult, v as ResolveOptions, w as ResolveResult, T as TrackOptions, x as WebhookResponse, y as WidgetPrize, z as WidgetSegment, B as WidgetType, D as XeniaClient, E as XeniaClientOptions, F as XeniaMarker, G as createXeniaClient, H as extractContentSwaps, K as parseActions, L as pendingTargets, N as personalizeContent, O as personalizePage, Q as spliceSlots } from '../actions-
|
|
1
|
+
import { I as InteractionType, X as XeniaConfig, a as XeniaEvent, M as MessageCta, b as MessageTemplateFn, c as MessageComponentRenderer, W as WidgetFormSubmit, d as WidgetSubmitResponse, e as XeniaAction, f as MessagePlacement, C as ConsentState } from '../actions-C18Kv-4w.js';
|
|
2
|
+
export { A as AnalyticsContext, g as AnalyticsEventName, h as AnalyticsIdentity, i as AnalyticsIngestResponse, j as AppliedSwap, k as ClientEvent, l as CmsAdapter, m as ContentSwap, n as IdentifyRequest, o as IdentifyResponse, p as Identity, J as JsonValue, q as MessageMountContext, r as MessageRenderContext, P as PagePersonalizeOptions, s as PageRef, t as PersonalizationSwap, R as ResolveBatchItem, u as ResolveBatchResult, v as ResolveOptions, w as ResolveResult, T as TrackOptions, x as WebhookResponse, y as WidgetPrize, z as WidgetSegment, B as WidgetType, D as XeniaClient, E as XeniaClientOptions, F as XeniaMarker, G as createXeniaClient, H as extractContentSwaps, K as parseActions, L as pendingTargets, N as personalizeContent, O as personalizePage, Q as spliceSlots } from '../actions-C18Kv-4w.js';
|
|
3
3
|
|
|
4
4
|
interface Parsed {
|
|
5
5
|
eventId: string;
|
|
@@ -9,10 +9,16 @@ interface Parsed {
|
|
|
9
9
|
declare function parseDataAttribute(value: string): Parsed | null;
|
|
10
10
|
/** Personalization slot metadata read from `data-xenia-*` attributes. */
|
|
11
11
|
interface SlotMeta {
|
|
12
|
-
|
|
12
|
+
/** The `data-xenia-slot` value: which section of the page this is. */
|
|
13
|
+
slot?: string;
|
|
14
|
+
contentKey?: string;
|
|
13
15
|
contentEntryId?: string;
|
|
14
16
|
baseEntryId?: string;
|
|
15
17
|
personalized?: boolean;
|
|
18
|
+
/** `data-xenia-bucket`, e.g. `personalized` — stored by Xenia as the slot event's bucket. */
|
|
19
|
+
bucket?: string;
|
|
20
|
+
/** Click only: the clicked link's or button's text (≤60 chars). */
|
|
21
|
+
label?: string;
|
|
16
22
|
}
|
|
17
23
|
interface AutoTrackOptions {
|
|
18
24
|
prefix: string;
|
|
@@ -30,8 +36,13 @@ interface AutoTrackOptions {
|
|
|
30
36
|
* scanning is skipped entirely.
|
|
31
37
|
*/
|
|
32
38
|
fireSlot?: (interaction: "impression" | "click", meta: SlotMeta) => void;
|
|
39
|
+
/** Which clicks in a slot count: `any` (default) or only a link's or button's (`controls`). */
|
|
40
|
+
slotClicks?: "any" | "controls";
|
|
33
41
|
}
|
|
34
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Read the resolve metadata attributes off a `[data-xenia-slot]` element. `null` when it carries
|
|
44
|
+
* neither a content key nor an entry id (not a resolved slot).
|
|
45
|
+
*/
|
|
35
46
|
declare function readSlotMeta(el: Element): SlotMeta | null;
|
|
36
47
|
/**
|
|
37
48
|
* Wires declarative `data-xenia` auto-tracking: delegated clicks, an
|
package/dist/client/index.js
CHANGED
|
@@ -1088,17 +1088,22 @@ function parseDataAttribute(value) {
|
|
|
1088
1088
|
return { eventId, interactionType };
|
|
1089
1089
|
}
|
|
1090
1090
|
function readSlotMeta(el) {
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
const contentEntryId =
|
|
1094
|
-
|
|
1095
|
-
const personalized =
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1091
|
+
const attr = (name) => el.getAttribute(`data-xenia-${name}`) ?? void 0;
|
|
1092
|
+
const contentKey = attr("content-key");
|
|
1093
|
+
const contentEntryId = attr("content-entry-id");
|
|
1094
|
+
if (!contentKey && !contentEntryId) return null;
|
|
1095
|
+
const personalized = attr("personalized");
|
|
1096
|
+
const meta2 = {};
|
|
1097
|
+
const slot = attr("slot");
|
|
1098
|
+
if (slot) meta2.slot = slot;
|
|
1099
|
+
if (contentKey) meta2.contentKey = contentKey;
|
|
1100
|
+
if (contentEntryId) meta2.contentEntryId = contentEntryId;
|
|
1101
|
+
const baseEntryId = attr("base-entry-id");
|
|
1102
|
+
if (baseEntryId) meta2.baseEntryId = baseEntryId;
|
|
1103
|
+
if (personalized != null) meta2.personalized = personalized === "true";
|
|
1104
|
+
const bucket = attr("bucket");
|
|
1105
|
+
if (bucket) meta2.bucket = bucket;
|
|
1106
|
+
return meta2;
|
|
1102
1107
|
}
|
|
1103
1108
|
var AutoTracker = class {
|
|
1104
1109
|
opts;
|
|
@@ -1159,8 +1164,12 @@ var AutoTracker = class {
|
|
|
1159
1164
|
if (this.opts.fireSlot) {
|
|
1160
1165
|
const slot = target.closest("[data-xenia-slot]");
|
|
1161
1166
|
if (slot) {
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1167
|
+
const meta2 = readSlotMeta(slot);
|
|
1168
|
+
const found = target.closest("a, button");
|
|
1169
|
+
const control = found && slot.contains(found) ? found : null;
|
|
1170
|
+
if (!control && this.opts.slotClicks === "controls") return;
|
|
1171
|
+
const label = control ? (control.textContent ?? "").trim().slice(0, 60) : "";
|
|
1172
|
+
if (meta2 && !this.throttled(slot)) this.opts.fireSlot("click", label ? { ...meta2, label } : meta2);
|
|
1164
1173
|
}
|
|
1165
1174
|
}
|
|
1166
1175
|
};
|
|
@@ -1168,24 +1177,24 @@ var AutoTracker = class {
|
|
|
1168
1177
|
}
|
|
1169
1178
|
observeSlot(el) {
|
|
1170
1179
|
if (!this.opts.fireSlot || this.slotObservers.has(el)) return;
|
|
1171
|
-
const
|
|
1172
|
-
if (!
|
|
1180
|
+
const meta2 = readSlotMeta(el);
|
|
1181
|
+
if (!meta2) return;
|
|
1173
1182
|
const fireSlot = this.opts.fireSlot;
|
|
1174
1183
|
const { threshold, rootMargin } = this.opts.viewport;
|
|
1175
1184
|
const observer = new IntersectionObserver(
|
|
1176
1185
|
(entries) => {
|
|
1177
1186
|
for (const entry of entries) {
|
|
1178
|
-
|
|
1179
|
-
if (this.slotImpressed.has(el)) continue;
|
|
1187
|
+
const inView = entry.isIntersecting && (entry.intersectionRatio >= threshold || entry.intersectionRect.height >= window.innerHeight / 2);
|
|
1188
|
+
if (!inView || this.slotImpressed.has(el)) continue;
|
|
1180
1189
|
this.slotImpressed.add(el);
|
|
1181
|
-
fireSlot("impression",
|
|
1190
|
+
fireSlot("impression", meta2);
|
|
1182
1191
|
observer.disconnect();
|
|
1183
1192
|
this.slotObservers.delete(el);
|
|
1184
1193
|
}
|
|
1185
1194
|
},
|
|
1186
|
-
{ threshold, rootMargin }
|
|
1195
|
+
{ threshold: [.../* @__PURE__ */ new Set([0.1, 0.25, threshold])].sort(), rootMargin }
|
|
1187
1196
|
);
|
|
1188
|
-
observer.observe(el);
|
|
1197
|
+
observer.observe(boxOf(el));
|
|
1189
1198
|
this.slotObservers.set(el, observer);
|
|
1190
1199
|
}
|
|
1191
1200
|
observeViewport(el, eventId) {
|
|
@@ -1239,6 +1248,235 @@ var AutoTracker = class {
|
|
|
1239
1248
|
this.mutationObserver.observe(document.body, { childList: true, subtree: true });
|
|
1240
1249
|
}
|
|
1241
1250
|
};
|
|
1251
|
+
function boxOf(el) {
|
|
1252
|
+
if (typeof getComputedStyle !== "function") return el;
|
|
1253
|
+
return getComputedStyle(el).display === "contents" ? el.firstElementChild ?? el : el;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// src/edge/trail.ts
|
|
1257
|
+
var MAX_TRAIL = 8;
|
|
1258
|
+
var TRAIL_MAX_AGE_S = 30 * 60;
|
|
1259
|
+
function trailCookieName(prefix = DEFAULT_PREFIX) {
|
|
1260
|
+
return `${prefix}_trail`;
|
|
1261
|
+
}
|
|
1262
|
+
var decode = (v) => {
|
|
1263
|
+
try {
|
|
1264
|
+
return decodeURIComponent(v);
|
|
1265
|
+
} catch {
|
|
1266
|
+
return v;
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
function readTrail(value) {
|
|
1270
|
+
if (!value) return [];
|
|
1271
|
+
try {
|
|
1272
|
+
const parsed = JSON.parse(decode(value));
|
|
1273
|
+
if (!Array.isArray(parsed)) return [];
|
|
1274
|
+
return parsed.filter((t) => Array.isArray(t) && typeof t[0] === "string" && typeof t[1] === "number").slice(0, MAX_TRAIL).map(
|
|
1275
|
+
([path, at, seconds]) => typeof seconds === "number" && seconds >= 0 && seconds <= 86400 ? { path, at, seconds } : { path, at }
|
|
1276
|
+
);
|
|
1277
|
+
} catch {
|
|
1278
|
+
return [];
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
function withView(trail, path, now = Date.now()) {
|
|
1282
|
+
if (trail[0]?.path === path) return trail;
|
|
1283
|
+
const earlier = trail.find((t) => t.path === path);
|
|
1284
|
+
return [{ path, at: earlier?.at ?? now }, ...trail.filter((t) => t.path !== path)].slice(0, MAX_TRAIL);
|
|
1285
|
+
}
|
|
1286
|
+
function trailCookieValue(trail) {
|
|
1287
|
+
return JSON.stringify(
|
|
1288
|
+
trail.slice(0, MAX_TRAIL).map((t) => t.seconds === void 0 ? [t.path, t.at] : [t.path, t.at, t.seconds])
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// src/client/page-tracking.ts
|
|
1293
|
+
var SETTLE_MS = 3e3;
|
|
1294
|
+
var POLL_MS = 50;
|
|
1295
|
+
var MIN_VISIT_MS = 250;
|
|
1296
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
1297
|
+
var restore = null;
|
|
1298
|
+
function watchNavigation(listener) {
|
|
1299
|
+
listeners.add(listener);
|
|
1300
|
+
if (!restore) {
|
|
1301
|
+
const { pushState, replaceState } = history;
|
|
1302
|
+
const notify = () => listeners.forEach((l) => {
|
|
1303
|
+
try {
|
|
1304
|
+
l();
|
|
1305
|
+
} catch {
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
const ownPush = function(...args) {
|
|
1309
|
+
pushState.apply(this, args);
|
|
1310
|
+
notify();
|
|
1311
|
+
};
|
|
1312
|
+
const ownReplace = function(...args) {
|
|
1313
|
+
replaceState.apply(this, args);
|
|
1314
|
+
notify();
|
|
1315
|
+
};
|
|
1316
|
+
history.pushState = ownPush;
|
|
1317
|
+
history.replaceState = ownReplace;
|
|
1318
|
+
window.addEventListener("popstate", notify);
|
|
1319
|
+
restore = () => {
|
|
1320
|
+
if (history.pushState !== ownPush || history.replaceState !== ownReplace) return false;
|
|
1321
|
+
history.pushState = pushState;
|
|
1322
|
+
history.replaceState = replaceState;
|
|
1323
|
+
window.removeEventListener("popstate", notify);
|
|
1324
|
+
return true;
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
return () => {
|
|
1328
|
+
listeners.delete(listener);
|
|
1329
|
+
if (!listeners.size && restore?.()) restore = null;
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
var meta = (name) => document.querySelector(`meta[name="${name}"]`)?.content || void 0;
|
|
1333
|
+
function startPageTracking(client, opts) {
|
|
1334
|
+
let path = location.pathname;
|
|
1335
|
+
let lastTitle = null;
|
|
1336
|
+
let timer;
|
|
1337
|
+
let view = { path, since: visibleNow(), sent: false };
|
|
1338
|
+
function visibleNow() {
|
|
1339
|
+
return document.visibilityState === "visible" ? Date.now() : null;
|
|
1340
|
+
}
|
|
1341
|
+
function sendView() {
|
|
1342
|
+
clearTimeout(timer);
|
|
1343
|
+
const started = Date.now();
|
|
1344
|
+
const tick = () => {
|
|
1345
|
+
const settled = document.title !== "" && document.title !== lastTitle;
|
|
1346
|
+
if (!settled && Date.now() - started < SETTLE_MS) {
|
|
1347
|
+
timer = setTimeout(tick, POLL_MS);
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
const params = new URLSearchParams(location.search);
|
|
1351
|
+
const searchTerm = (params.get("utm_term") ?? params.get("q") ?? "").slice(0, 120);
|
|
1352
|
+
const category = meta("xenia-category");
|
|
1353
|
+
lastTitle = document.title;
|
|
1354
|
+
client.track("pageView", {
|
|
1355
|
+
title: (meta("xenia-title") ?? document.title).slice(0, 160),
|
|
1356
|
+
...searchTerm ? { searchTerm } : {},
|
|
1357
|
+
...category ? { category } : {}
|
|
1358
|
+
});
|
|
1359
|
+
};
|
|
1360
|
+
timer = setTimeout(tick, 0);
|
|
1361
|
+
}
|
|
1362
|
+
function leave() {
|
|
1363
|
+
if (view.sent || view.since === null || Date.now() - view.since < MIN_VISIT_MS) return;
|
|
1364
|
+
view.sent = true;
|
|
1365
|
+
client.track("page_leave", { path: view.path, durationMs: Date.now() - view.since });
|
|
1366
|
+
}
|
|
1367
|
+
const onNavigate = () => {
|
|
1368
|
+
if (location.pathname === path) return;
|
|
1369
|
+
if (opts.pageLeave) leave();
|
|
1370
|
+
path = location.pathname;
|
|
1371
|
+
view = { path, since: visibleNow(), sent: false };
|
|
1372
|
+
if (opts.pageViews) sendView();
|
|
1373
|
+
};
|
|
1374
|
+
const onVisibility = () => {
|
|
1375
|
+
if (document.visibilityState === "hidden") leave();
|
|
1376
|
+
else if (view.since === null) view.since = Date.now();
|
|
1377
|
+
};
|
|
1378
|
+
const unwatch = watchNavigation(onNavigate);
|
|
1379
|
+
if (opts.pageLeave) document.addEventListener("visibilitychange", onVisibility);
|
|
1380
|
+
if (opts.pageViews) sendView();
|
|
1381
|
+
return () => {
|
|
1382
|
+
clearTimeout(timer);
|
|
1383
|
+
unwatch();
|
|
1384
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// src/client/engagement.ts
|
|
1389
|
+
var rootOf = (opt, fallback) => typeof opt === "object" && opt.root || fallback;
|
|
1390
|
+
function readCookie(name) {
|
|
1391
|
+
return document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`))?.[1];
|
|
1392
|
+
}
|
|
1393
|
+
function writeCookie(name, value) {
|
|
1394
|
+
try {
|
|
1395
|
+
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${TRAIL_MAX_AGE_S}; samesite=lax`;
|
|
1396
|
+
} catch {
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
function sectionOf(link) {
|
|
1400
|
+
if (!link || link.origin !== location.origin) return void 0;
|
|
1401
|
+
return link.pathname.split("/").filter(Boolean)[0];
|
|
1402
|
+
}
|
|
1403
|
+
function cardImage(target, root) {
|
|
1404
|
+
let el = target;
|
|
1405
|
+
for (let depth = 0; el && depth < 5 && el !== root; depth++, el = el.parentElement) {
|
|
1406
|
+
const imgs = [...el.querySelectorAll("img")].filter((i) => i.alt.trim());
|
|
1407
|
+
if (!imgs.length) continue;
|
|
1408
|
+
return new Set(imgs.map((i) => i.alt.trim())).size === 1 ? imgs[0] : null;
|
|
1409
|
+
}
|
|
1410
|
+
return null;
|
|
1411
|
+
}
|
|
1412
|
+
function startEngagement(client, opts) {
|
|
1413
|
+
const trailName = trailCookieName(opts.prefix);
|
|
1414
|
+
const clicksName = `${opts.prefix}_clicks`;
|
|
1415
|
+
const visibleNow = () => document.visibilityState === "visible" ? Date.now() : null;
|
|
1416
|
+
let current = { path: location.pathname, since: visibleNow(), stamped: false };
|
|
1417
|
+
const stamp = () => {
|
|
1418
|
+
if (current.stamped || !opts.trail || !opts.analyticsAllowed() || current.since === null) return;
|
|
1419
|
+
current.stamped = true;
|
|
1420
|
+
const seconds = Math.round((Date.now() - current.since) / 100) / 10;
|
|
1421
|
+
const trail = readTrail(readCookie(trailName));
|
|
1422
|
+
const i = trail.findIndex((t) => t.path === current.path);
|
|
1423
|
+
if (i < 0) return;
|
|
1424
|
+
trail[i] = { ...trail[i], seconds };
|
|
1425
|
+
writeCookie(trailName, trailCookieValue(trail));
|
|
1426
|
+
};
|
|
1427
|
+
const onNavigate = () => {
|
|
1428
|
+
if (location.pathname === current.path) return;
|
|
1429
|
+
stamp();
|
|
1430
|
+
current = { path: location.pathname, since: visibleNow(), stamped: false };
|
|
1431
|
+
if (!opts.trail || !opts.analyticsAllowed()) return;
|
|
1432
|
+
const trail = readTrail(readCookie(trailName));
|
|
1433
|
+
const next = withView(trail, current.path);
|
|
1434
|
+
if (next !== trail) writeCookie(trailName, trailCookieValue(next));
|
|
1435
|
+
};
|
|
1436
|
+
const onVisible = () => {
|
|
1437
|
+
if (document.visibilityState === "visible" && current.since === null) current.since = Date.now();
|
|
1438
|
+
};
|
|
1439
|
+
const onLeaveClick = (e) => {
|
|
1440
|
+
const link = e.target?.closest?.("a[href]");
|
|
1441
|
+
if (link && link.origin === location.origin && link.pathname !== location.pathname) stamp();
|
|
1442
|
+
};
|
|
1443
|
+
const bump = () => {
|
|
1444
|
+
if (!opts.analyticsAllowed()) return;
|
|
1445
|
+
writeCookie(clicksName, String((Number(readCookie(clicksName)) || 0) + 1));
|
|
1446
|
+
};
|
|
1447
|
+
const navRoot = rootOf(opts.navClicks, "header");
|
|
1448
|
+
const contentRoot = rootOf(opts.contentClicks, "main");
|
|
1449
|
+
const onClick = (e) => {
|
|
1450
|
+
const target = e.target;
|
|
1451
|
+
if (!target?.closest) return;
|
|
1452
|
+
const nav = opts.navClicks ? target.closest(`${navRoot} a[href]`) : null;
|
|
1453
|
+
if (nav) {
|
|
1454
|
+
const category2 = sectionOf(nav);
|
|
1455
|
+
if (!category2) return;
|
|
1456
|
+
client.track("nav_click", { category: category2, label: (nav.textContent ?? "").trim().slice(0, 60) });
|
|
1457
|
+
bump();
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
const root = opts.contentClicks ? target.closest(contentRoot) : null;
|
|
1461
|
+
if (!root) return;
|
|
1462
|
+
const img = target.closest("img") ?? cardImage(target, root);
|
|
1463
|
+
const label = (img?.alt ?? "").trim().slice(0, 160);
|
|
1464
|
+
if (!label) return;
|
|
1465
|
+
const category = sectionOf(target.closest("a[href]"));
|
|
1466
|
+
client.track("content_click", category ? { label, category } : { label });
|
|
1467
|
+
bump();
|
|
1468
|
+
};
|
|
1469
|
+
const unwatch = watchNavigation(onNavigate);
|
|
1470
|
+
window.addEventListener("click", onLeaveClick, { capture: true });
|
|
1471
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
1472
|
+
if (opts.contentClicks || opts.navClicks) document.addEventListener("click", onClick, { capture: true });
|
|
1473
|
+
return () => {
|
|
1474
|
+
unwatch();
|
|
1475
|
+
window.removeEventListener("click", onLeaveClick, { capture: true });
|
|
1476
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
1477
|
+
document.removeEventListener("click", onClick, { capture: true });
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1242
1480
|
|
|
1243
1481
|
// src/server/cookies.ts
|
|
1244
1482
|
function parseCookies(header) {
|
|
@@ -1249,7 +1487,12 @@ function parseCookies(header) {
|
|
|
1249
1487
|
if (idx === -1) continue;
|
|
1250
1488
|
const key = part.slice(0, idx).trim();
|
|
1251
1489
|
const value = part.slice(idx + 1).trim();
|
|
1252
|
-
if (key)
|
|
1490
|
+
if (!key) continue;
|
|
1491
|
+
try {
|
|
1492
|
+
out[key] = decodeURIComponent(value);
|
|
1493
|
+
} catch {
|
|
1494
|
+
out[key] = value;
|
|
1495
|
+
}
|
|
1253
1496
|
}
|
|
1254
1497
|
return out;
|
|
1255
1498
|
}
|
|
@@ -1323,6 +1566,14 @@ var Identity = class {
|
|
|
1323
1566
|
const storageKey = this.key("device_id");
|
|
1324
1567
|
try {
|
|
1325
1568
|
const cached = localStorage.getItem(storageKey);
|
|
1569
|
+
const fromServer = this.cookie?.authoritative ? this.readVisitorCookie() : null;
|
|
1570
|
+
const effective = localStorage.getItem(this.key("visitor_id")) ?? cached;
|
|
1571
|
+
if (cached && fromServer && fromServer !== effective) {
|
|
1572
|
+
this.deviceId = fromServer;
|
|
1573
|
+
localStorage.setItem(storageKey, fromServer);
|
|
1574
|
+
if (localStorage.getItem(this.key("visitor_id"))) localStorage.setItem(this.key("visitor_id"), fromServer);
|
|
1575
|
+
return fromServer;
|
|
1576
|
+
}
|
|
1326
1577
|
if (cached) {
|
|
1327
1578
|
this.deviceId = cached;
|
|
1328
1579
|
this.writeVisitorCookie(this.getVisitorId() ?? cached);
|
|
@@ -1502,7 +1753,7 @@ function resolveCookieConfig(opt) {
|
|
|
1502
1753
|
const o = opt === true ? {} : opt;
|
|
1503
1754
|
const sameSite = o.sameSite ?? "Lax";
|
|
1504
1755
|
const secure = sameSite === "None" ? true : o.secure ?? false;
|
|
1505
|
-
return { sameSite, secure, maxAgeDays: o.maxAgeDays ?? 365, domain: o.domain };
|
|
1756
|
+
return { sameSite, secure, maxAgeDays: o.maxAgeDays ?? 365, domain: o.domain, authoritative: o.authoritative ?? false };
|
|
1506
1757
|
}
|
|
1507
1758
|
|
|
1508
1759
|
// src/client/widgets/form.ts
|
|
@@ -2186,6 +2437,8 @@ var XeniaClient = class {
|
|
|
2186
2437
|
renderer;
|
|
2187
2438
|
analyticsQueue;
|
|
2188
2439
|
tracker = null;
|
|
2440
|
+
stopPageTracking = null;
|
|
2441
|
+
stopEngagement = null;
|
|
2189
2442
|
/** The in-flight (or finished) boot; shared so concurrent `init()` calls start one tracker. */
|
|
2190
2443
|
booting = null;
|
|
2191
2444
|
/** Bumped by `destroy()`, so a boot still waiting on the device id knows to stop. */
|
|
@@ -2237,17 +2490,21 @@ var XeniaClient = class {
|
|
|
2237
2490
|
rootMargin: this.options.viewport?.rootMargin,
|
|
2238
2491
|
triggerOnce: this.options.viewport?.triggerOnce ?? true
|
|
2239
2492
|
},
|
|
2493
|
+
slotClicks: this.options.slotClicks,
|
|
2240
2494
|
// Declarative `data-xenia` elements fire journey triggers (immediate).
|
|
2241
2495
|
fire: (eventId, interaction) => this.track(eventId, { interactionType: interaction }, { trigger: true }),
|
|
2242
2496
|
// `[data-xenia-slot]` elements fire analytics-only personalization events.
|
|
2243
|
-
fireSlot: (interaction,
|
|
2497
|
+
fireSlot: (interaction, meta2) => {
|
|
2244
2498
|
this.track(
|
|
2245
2499
|
"customEvent",
|
|
2246
2500
|
{
|
|
2247
2501
|
customEventName: interaction === "impression" ? "personalization_impression" : "personalization_click",
|
|
2248
|
-
contentKey:
|
|
2249
|
-
contentEntryId:
|
|
2250
|
-
baseEntryId:
|
|
2502
|
+
...meta2.contentKey ? { contentKey: meta2.contentKey } : {},
|
|
2503
|
+
contentEntryId: meta2.contentEntryId ?? null,
|
|
2504
|
+
baseEntryId: meta2.baseEntryId ?? null,
|
|
2505
|
+
...meta2.bucket ? { bucket: meta2.bucket } : {},
|
|
2506
|
+
...meta2.slot ? { slot: meta2.slot } : {},
|
|
2507
|
+
...meta2.label ? { label: meta2.label } : {}
|
|
2251
2508
|
},
|
|
2252
2509
|
{ trigger: false }
|
|
2253
2510
|
);
|
|
@@ -2255,11 +2512,32 @@ var XeniaClient = class {
|
|
|
2255
2512
|
});
|
|
2256
2513
|
this.tracker.start();
|
|
2257
2514
|
}
|
|
2515
|
+
const { pageViews = false, pageLeave = false } = this.options;
|
|
2516
|
+
if (pageViews || pageLeave) {
|
|
2517
|
+
this.stopPageTracking = startPageTracking({ track: (name, props) => this.track(name, props) }, { pageViews, pageLeave });
|
|
2518
|
+
}
|
|
2519
|
+
const { trail = false, contentClicks = false, navClicks = false } = this.options;
|
|
2520
|
+
if (trail || contentClicks || navClicks) {
|
|
2521
|
+
this.stopEngagement = startEngagement(
|
|
2522
|
+
{ track: (name, props) => this.track(name, props) },
|
|
2523
|
+
{
|
|
2524
|
+
trail,
|
|
2525
|
+
contentClicks,
|
|
2526
|
+
navClicks,
|
|
2527
|
+
prefix: this.options.prefix ?? "xenia",
|
|
2528
|
+
analyticsAllowed: () => this.analyticsAllowed() && !this.dntBlocked()
|
|
2529
|
+
}
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2258
2532
|
}
|
|
2259
2533
|
/** Tear down listeners, observers, and injected UI. */
|
|
2260
2534
|
destroy() {
|
|
2261
2535
|
this.tracker?.stop();
|
|
2262
2536
|
this.tracker = null;
|
|
2537
|
+
this.stopPageTracking?.();
|
|
2538
|
+
this.stopPageTracking = null;
|
|
2539
|
+
this.stopEngagement?.();
|
|
2540
|
+
this.stopEngagement = null;
|
|
2263
2541
|
this.renderer.destroy();
|
|
2264
2542
|
this.analyticsQueue.stop();
|
|
2265
2543
|
this.booting = null;
|