@xenia-martech/sdk-js 1.2.0-dev.2 → 1.2.0-dev.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.
Files changed (82) hide show
  1. package/README.md +112 -3
  2. package/dist/{actions-BcdlokGO.d.cts → actions-C18Kv-4w.d.cts} +50 -2
  3. package/dist/{actions-BcdlokGO.d.ts → actions-C18Kv-4w.d.ts} +50 -2
  4. package/dist/{api-CktVQ6Gl.d.ts → api-0IycTYJR.d.cts} +4 -2
  5. package/dist/{api-BEPW0Uan.d.cts → api-COXWLL5L.d.ts} +4 -2
  6. package/dist/{chunk-3PYPPIUM.cjs → chunk-2DR5AVPX.cjs} +11 -46
  7. package/dist/chunk-2DR5AVPX.cjs.map +1 -0
  8. package/dist/chunk-BQ5PJCZ7.js +179 -0
  9. package/dist/chunk-BQ5PJCZ7.js.map +1 -0
  10. package/dist/{chunk-DWFVDNU5.js → chunk-GWRDZIVA.js} +5 -37
  11. package/dist/chunk-GWRDZIVA.js.map +1 -0
  12. package/dist/{chunk-7NGJC7CF.js → chunk-LW5AEJ6K.js} +20 -183
  13. package/dist/chunk-LW5AEJ6K.js.map +1 -0
  14. package/dist/chunk-N5M4PI7C.cjs +192 -0
  15. package/dist/chunk-N5M4PI7C.cjs.map +1 -0
  16. package/dist/chunk-OMMIRSPK.js +336 -0
  17. package/dist/chunk-OMMIRSPK.js.map +1 -0
  18. package/dist/chunk-QEYONKWY.js +42 -0
  19. package/dist/chunk-QEYONKWY.js.map +1 -0
  20. package/dist/{chunk-IBNIRT7J.cjs → chunk-RWE2OCQL.cjs} +20 -194
  21. package/dist/chunk-RWE2OCQL.cjs.map +1 -0
  22. package/dist/chunk-WHGK55JP.cjs +46 -0
  23. package/dist/chunk-WHGK55JP.cjs.map +1 -0
  24. package/dist/chunk-YLMMTKQL.cjs +358 -0
  25. package/dist/chunk-YLMMTKQL.cjs.map +1 -0
  26. package/dist/client/index.cjs +320 -31
  27. package/dist/client/index.cjs.map +1 -1
  28. package/dist/client/index.d.cts +15 -4
  29. package/dist/client/index.d.ts +15 -4
  30. package/dist/client/index.js +320 -31
  31. package/dist/client/index.js.map +1 -1
  32. package/dist/client/react/index.cjs +731 -34
  33. package/dist/client/react/index.cjs.map +1 -1
  34. package/dist/client/react/index.d.cts +32 -3
  35. package/dist/client/react/index.d.ts +32 -3
  36. package/dist/client/react/index.js +732 -37
  37. package/dist/client/react/index.js.map +1 -1
  38. package/dist/cms/contentful.cjs +2 -1
  39. package/dist/cms/contentful.cjs.map +1 -1
  40. package/dist/cms/contentful.d.cts +2 -1
  41. package/dist/cms/contentful.d.ts +2 -1
  42. package/dist/cms/contentful.js +2 -1
  43. package/dist/cms/contentful.js.map +1 -1
  44. package/dist/consent-Cemn91j3.d.ts +248 -0
  45. package/dist/consent-DgWdjsSy.d.cts +248 -0
  46. package/dist/{cookie-names-CLqtmGBM.d.ts → cookie-names-BvUaW3uo.d.ts} +1 -1
  47. package/dist/{cookie-names-BACctagz.d.cts → cookie-names-CnL65Oly.d.cts} +1 -1
  48. package/dist/edge/index.cjs +280 -0
  49. package/dist/edge/index.cjs.map +1 -0
  50. package/dist/edge/index.d.cts +61 -0
  51. package/dist/edge/index.d.ts +61 -0
  52. package/dist/edge/index.js +191 -0
  53. package/dist/edge/index.js.map +1 -0
  54. package/dist/index.cjs +41 -40
  55. package/dist/index.d.cts +5 -4
  56. package/dist/index.d.ts +5 -4
  57. package/dist/index.js +2 -1
  58. package/dist/next/index.cjs +320 -15
  59. package/dist/next/index.cjs.map +1 -1
  60. package/dist/next/index.d.cts +180 -6
  61. package/dist/next/index.d.ts +180 -6
  62. package/dist/next/index.js +309 -9
  63. package/dist/next/index.js.map +1 -1
  64. package/dist/personalize-content-c2DDAV6g.d.ts +140 -0
  65. package/dist/personalize-content-ghZPKlf1.d.cts +140 -0
  66. package/dist/server/index.cjs +34 -32
  67. package/dist/server/index.cjs.map +1 -1
  68. package/dist/server/index.d.cts +6 -5
  69. package/dist/server/index.d.ts +6 -5
  70. package/dist/server/index.js +5 -3
  71. package/dist/server/index.js.map +1 -1
  72. package/dist/{personalize-content-Clc1oMYY.d.ts → types-DgVHS4Qw.d.cts} +7 -127
  73. package/dist/{personalize-content-Clc1oMYY.d.cts → types-DgVHS4Qw.d.ts} +7 -127
  74. package/dist/xenia-sdk.iife.js +3 -3
  75. package/dist/xenia-sdk.iife.js.map +1 -1
  76. package/dist/{xenia-server-o_wDOcei.d.cts → xenia-server-C3O-cWQT.d.cts} +3 -2
  77. package/dist/{xenia-server-BWhWD0La.d.ts → xenia-server-DV8Sjmoo.d.ts} +3 -2
  78. package/package.json +14 -1
  79. package/dist/chunk-3PYPPIUM.cjs.map +0 -1
  80. package/dist/chunk-7NGJC7CF.js.map +0 -1
  81. package/dist/chunk-DWFVDNU5.js.map +0 -1
  82. package/dist/chunk-IBNIRT7J.cjs.map +0 -1
@@ -548,7 +548,7 @@ var XeniaApi = class {
548
548
  * ({@link personalizeContent}) calls this once per round with the runtime
549
549
  * targets it found; callers rarely need it directly.
550
550
  */
551
- async resolveEntries(entryIds, opts = {}) {
551
+ async resolveEntries(entryIds, opts) {
552
552
  const { data } = await this.http.request({
553
553
  method: "POST",
554
554
  path: "/api/personalization/resolve-entries",
@@ -556,7 +556,8 @@ var XeniaApi = class {
556
556
  body: compact({
557
557
  entryIds,
558
558
  data: opts.data,
559
- locale: opts.locale
559
+ locale: opts.locale,
560
+ contentKey: opts.contentKey
560
561
  })
561
562
  });
562
563
  return data;
@@ -680,7 +681,7 @@ var DEFAULT_TIMEOUT_MS = 300;
680
681
  var DEFAULT_MAX_ROUNDS = 3;
681
682
  var copyOf = (v) => v && typeof v === "object" && typeof structuredClone === "function" ? structuredClone(v) : v;
682
683
  async function personalizeContent(payload, options) {
683
- const { cms } = options;
684
+ const cms = scopedAdapter(options.cms, options.contentKey);
684
685
  const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
685
686
  const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
686
687
  const applied = [];
@@ -879,7 +880,8 @@ function withDeadline(promise, deadline) {
879
880
  })
880
881
  ]).finally(() => clearTimeout(timer));
881
882
  }
882
- function pendingTargets(payload, cms) {
883
+ function pendingTargets(payload, adapter, contentKey) {
884
+ const cms = scopedAdapter(adapter, contentKey);
883
885
  try {
884
886
  let content = typeof structuredClone === "function" ? structuredClone(payload) : payload;
885
887
  content = cms.prepare ? cms.prepare(content) : content;
@@ -890,6 +892,15 @@ function pendingTargets(payload, cms) {
890
892
  return [];
891
893
  }
892
894
  }
895
+ function scopedAdapter(cms, contentKey) {
896
+ return {
897
+ ...cms,
898
+ marker(entry) {
899
+ const m = cms.marker(entry);
900
+ return m && contentKey && m.keys?.includes(contentKey) ? m : null;
901
+ }
902
+ };
903
+ }
893
904
  async function personalizePage(ref, options) {
894
905
  let page;
895
906
  try {
@@ -1079,17 +1090,22 @@ function parseDataAttribute(value) {
1079
1090
  return { eventId, interactionType };
1080
1091
  }
1081
1092
  function readSlotMeta(el) {
1082
- const contentKey = el.getAttribute("data-xenia-content-key");
1083
- if (!contentKey) return null;
1084
- const contentEntryId = el.getAttribute("data-xenia-content-entry-id");
1085
- const baseEntryId = el.getAttribute("data-xenia-base-entry-id");
1086
- const personalized = el.getAttribute("data-xenia-personalized");
1087
- return {
1088
- contentKey,
1089
- contentEntryId: contentEntryId ?? void 0,
1090
- baseEntryId: baseEntryId ?? void 0,
1091
- personalized: personalized == null ? void 0 : personalized === "true"
1092
- };
1093
+ const attr = (name) => el.getAttribute(`data-xenia-${name}`) ?? void 0;
1094
+ const contentKey = attr("content-key");
1095
+ const contentEntryId = attr("content-entry-id");
1096
+ if (!contentKey && !contentEntryId) return null;
1097
+ const personalized = attr("personalized");
1098
+ const meta2 = {};
1099
+ const slot = attr("slot");
1100
+ if (slot) meta2.slot = slot;
1101
+ if (contentKey) meta2.contentKey = contentKey;
1102
+ if (contentEntryId) meta2.contentEntryId = contentEntryId;
1103
+ const baseEntryId = attr("base-entry-id");
1104
+ if (baseEntryId) meta2.baseEntryId = baseEntryId;
1105
+ if (personalized != null) meta2.personalized = personalized === "true";
1106
+ const bucket = attr("bucket");
1107
+ if (bucket) meta2.bucket = bucket;
1108
+ return meta2;
1093
1109
  }
1094
1110
  var AutoTracker = class {
1095
1111
  opts;
@@ -1150,8 +1166,12 @@ var AutoTracker = class {
1150
1166
  if (this.opts.fireSlot) {
1151
1167
  const slot = target.closest("[data-xenia-slot]");
1152
1168
  if (slot) {
1153
- const meta = readSlotMeta(slot);
1154
- if (meta && !this.throttled(slot)) this.opts.fireSlot("click", meta);
1169
+ const meta2 = readSlotMeta(slot);
1170
+ const found = target.closest("a, button");
1171
+ const control = found && slot.contains(found) ? found : null;
1172
+ if (!control && this.opts.slotClicks === "controls") return;
1173
+ const label = control ? (control.textContent ?? "").trim().slice(0, 60) : "";
1174
+ if (meta2 && !this.throttled(slot)) this.opts.fireSlot("click", label ? { ...meta2, label } : meta2);
1155
1175
  }
1156
1176
  }
1157
1177
  };
@@ -1159,24 +1179,24 @@ var AutoTracker = class {
1159
1179
  }
1160
1180
  observeSlot(el) {
1161
1181
  if (!this.opts.fireSlot || this.slotObservers.has(el)) return;
1162
- const meta = readSlotMeta(el);
1163
- if (!meta) return;
1182
+ const meta2 = readSlotMeta(el);
1183
+ if (!meta2) return;
1164
1184
  const fireSlot = this.opts.fireSlot;
1165
1185
  const { threshold, rootMargin } = this.opts.viewport;
1166
1186
  const observer = new IntersectionObserver(
1167
1187
  (entries) => {
1168
1188
  for (const entry of entries) {
1169
- if (!entry.isIntersecting) continue;
1170
- if (this.slotImpressed.has(el)) continue;
1189
+ const inView = entry.isIntersecting && (entry.intersectionRatio >= threshold || entry.intersectionRect.height >= window.innerHeight / 2);
1190
+ if (!inView || this.slotImpressed.has(el)) continue;
1171
1191
  this.slotImpressed.add(el);
1172
- fireSlot("impression", meta);
1192
+ fireSlot("impression", meta2);
1173
1193
  observer.disconnect();
1174
1194
  this.slotObservers.delete(el);
1175
1195
  }
1176
1196
  },
1177
- { threshold, rootMargin }
1197
+ { threshold: [.../* @__PURE__ */ new Set([0.1, 0.25, threshold])].sort(), rootMargin }
1178
1198
  );
1179
- observer.observe(el);
1199
+ observer.observe(boxOf(el));
1180
1200
  this.slotObservers.set(el, observer);
1181
1201
  }
1182
1202
  observeViewport(el, eventId) {
@@ -1230,6 +1250,235 @@ var AutoTracker = class {
1230
1250
  this.mutationObserver.observe(document.body, { childList: true, subtree: true });
1231
1251
  }
1232
1252
  };
1253
+ function boxOf(el) {
1254
+ if (typeof getComputedStyle !== "function") return el;
1255
+ return getComputedStyle(el).display === "contents" ? el.firstElementChild ?? el : el;
1256
+ }
1257
+
1258
+ // src/edge/trail.ts
1259
+ var MAX_TRAIL = 8;
1260
+ var TRAIL_MAX_AGE_S = 30 * 60;
1261
+ function trailCookieName(prefix = DEFAULT_PREFIX) {
1262
+ return `${prefix}_trail`;
1263
+ }
1264
+ var decode = (v) => {
1265
+ try {
1266
+ return decodeURIComponent(v);
1267
+ } catch {
1268
+ return v;
1269
+ }
1270
+ };
1271
+ function readTrail(value) {
1272
+ if (!value) return [];
1273
+ try {
1274
+ const parsed = JSON.parse(decode(value));
1275
+ if (!Array.isArray(parsed)) return [];
1276
+ return parsed.filter((t) => Array.isArray(t) && typeof t[0] === "string" && typeof t[1] === "number").slice(0, MAX_TRAIL).map(
1277
+ ([path, at, seconds]) => typeof seconds === "number" && seconds >= 0 && seconds <= 86400 ? { path, at, seconds } : { path, at }
1278
+ );
1279
+ } catch {
1280
+ return [];
1281
+ }
1282
+ }
1283
+ function withView(trail, path, now = Date.now()) {
1284
+ if (trail[0]?.path === path) return trail;
1285
+ const earlier = trail.find((t) => t.path === path);
1286
+ return [{ path, at: earlier?.at ?? now }, ...trail.filter((t) => t.path !== path)].slice(0, MAX_TRAIL);
1287
+ }
1288
+ function trailCookieValue(trail) {
1289
+ return JSON.stringify(
1290
+ trail.slice(0, MAX_TRAIL).map((t) => t.seconds === void 0 ? [t.path, t.at] : [t.path, t.at, t.seconds])
1291
+ );
1292
+ }
1293
+
1294
+ // src/client/page-tracking.ts
1295
+ var SETTLE_MS = 3e3;
1296
+ var POLL_MS = 50;
1297
+ var MIN_VISIT_MS = 250;
1298
+ var listeners = /* @__PURE__ */ new Set();
1299
+ var restore = null;
1300
+ function watchNavigation(listener) {
1301
+ listeners.add(listener);
1302
+ if (!restore) {
1303
+ const { pushState, replaceState } = history;
1304
+ const notify = () => listeners.forEach((l) => {
1305
+ try {
1306
+ l();
1307
+ } catch {
1308
+ }
1309
+ });
1310
+ const ownPush = function(...args) {
1311
+ pushState.apply(this, args);
1312
+ notify();
1313
+ };
1314
+ const ownReplace = function(...args) {
1315
+ replaceState.apply(this, args);
1316
+ notify();
1317
+ };
1318
+ history.pushState = ownPush;
1319
+ history.replaceState = ownReplace;
1320
+ window.addEventListener("popstate", notify);
1321
+ restore = () => {
1322
+ if (history.pushState !== ownPush || history.replaceState !== ownReplace) return false;
1323
+ history.pushState = pushState;
1324
+ history.replaceState = replaceState;
1325
+ window.removeEventListener("popstate", notify);
1326
+ return true;
1327
+ };
1328
+ }
1329
+ return () => {
1330
+ listeners.delete(listener);
1331
+ if (!listeners.size && restore?.()) restore = null;
1332
+ };
1333
+ }
1334
+ var meta = (name) => document.querySelector(`meta[name="${name}"]`)?.content || void 0;
1335
+ function startPageTracking(client, opts) {
1336
+ let path = location.pathname;
1337
+ let lastTitle = null;
1338
+ let timer;
1339
+ let view = { path, since: visibleNow(), sent: false };
1340
+ function visibleNow() {
1341
+ return document.visibilityState === "visible" ? Date.now() : null;
1342
+ }
1343
+ function sendView() {
1344
+ clearTimeout(timer);
1345
+ const started = Date.now();
1346
+ const tick = () => {
1347
+ const settled = document.title !== "" && document.title !== lastTitle;
1348
+ if (!settled && Date.now() - started < SETTLE_MS) {
1349
+ timer = setTimeout(tick, POLL_MS);
1350
+ return;
1351
+ }
1352
+ const params = new URLSearchParams(location.search);
1353
+ const searchTerm = (params.get("utm_term") ?? params.get("q") ?? "").slice(0, 120);
1354
+ const category = meta("xenia-category");
1355
+ lastTitle = document.title;
1356
+ client.track("pageView", {
1357
+ title: (meta("xenia-title") ?? document.title).slice(0, 160),
1358
+ ...searchTerm ? { searchTerm } : {},
1359
+ ...category ? { category } : {}
1360
+ });
1361
+ };
1362
+ timer = setTimeout(tick, 0);
1363
+ }
1364
+ function leave() {
1365
+ if (view.sent || view.since === null || Date.now() - view.since < MIN_VISIT_MS) return;
1366
+ view.sent = true;
1367
+ client.track("page_leave", { path: view.path, durationMs: Date.now() - view.since });
1368
+ }
1369
+ const onNavigate = () => {
1370
+ if (location.pathname === path) return;
1371
+ if (opts.pageLeave) leave();
1372
+ path = location.pathname;
1373
+ view = { path, since: visibleNow(), sent: false };
1374
+ if (opts.pageViews) sendView();
1375
+ };
1376
+ const onVisibility = () => {
1377
+ if (document.visibilityState === "hidden") leave();
1378
+ else if (view.since === null) view.since = Date.now();
1379
+ };
1380
+ const unwatch = watchNavigation(onNavigate);
1381
+ if (opts.pageLeave) document.addEventListener("visibilitychange", onVisibility);
1382
+ if (opts.pageViews) sendView();
1383
+ return () => {
1384
+ clearTimeout(timer);
1385
+ unwatch();
1386
+ document.removeEventListener("visibilitychange", onVisibility);
1387
+ };
1388
+ }
1389
+
1390
+ // src/client/engagement.ts
1391
+ var rootOf = (opt, fallback) => typeof opt === "object" && opt.root || fallback;
1392
+ function readCookie(name) {
1393
+ return document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`))?.[1];
1394
+ }
1395
+ function writeCookie(name, value) {
1396
+ try {
1397
+ document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${TRAIL_MAX_AGE_S}; samesite=lax`;
1398
+ } catch {
1399
+ }
1400
+ }
1401
+ function sectionOf(link) {
1402
+ if (!link || link.origin !== location.origin) return void 0;
1403
+ return link.pathname.split("/").filter(Boolean)[0];
1404
+ }
1405
+ function cardImage(target, root) {
1406
+ let el = target;
1407
+ for (let depth = 0; el && depth < 5 && el !== root; depth++, el = el.parentElement) {
1408
+ const imgs = [...el.querySelectorAll("img")].filter((i) => i.alt.trim());
1409
+ if (!imgs.length) continue;
1410
+ return new Set(imgs.map((i) => i.alt.trim())).size === 1 ? imgs[0] : null;
1411
+ }
1412
+ return null;
1413
+ }
1414
+ function startEngagement(client, opts) {
1415
+ const trailName = trailCookieName(opts.prefix);
1416
+ const clicksName = `${opts.prefix}_clicks`;
1417
+ const visibleNow = () => document.visibilityState === "visible" ? Date.now() : null;
1418
+ let current = { path: location.pathname, since: visibleNow(), stamped: false };
1419
+ const stamp = () => {
1420
+ if (current.stamped || !opts.trail || !opts.analyticsAllowed() || current.since === null) return;
1421
+ current.stamped = true;
1422
+ const seconds = Math.round((Date.now() - current.since) / 100) / 10;
1423
+ const trail = readTrail(readCookie(trailName));
1424
+ const i = trail.findIndex((t) => t.path === current.path);
1425
+ if (i < 0) return;
1426
+ trail[i] = { ...trail[i], seconds };
1427
+ writeCookie(trailName, trailCookieValue(trail));
1428
+ };
1429
+ const onNavigate = () => {
1430
+ if (location.pathname === current.path) return;
1431
+ stamp();
1432
+ current = { path: location.pathname, since: visibleNow(), stamped: false };
1433
+ if (!opts.trail || !opts.analyticsAllowed()) return;
1434
+ const trail = readTrail(readCookie(trailName));
1435
+ const next = withView(trail, current.path);
1436
+ if (next !== trail) writeCookie(trailName, trailCookieValue(next));
1437
+ };
1438
+ const onVisible = () => {
1439
+ if (document.visibilityState === "visible" && current.since === null) current.since = Date.now();
1440
+ };
1441
+ const onLeaveClick = (e) => {
1442
+ const link = e.target?.closest?.("a[href]");
1443
+ if (link && link.origin === location.origin && link.pathname !== location.pathname) stamp();
1444
+ };
1445
+ const bump = () => {
1446
+ if (!opts.analyticsAllowed()) return;
1447
+ writeCookie(clicksName, String((Number(readCookie(clicksName)) || 0) + 1));
1448
+ };
1449
+ const navRoot = rootOf(opts.navClicks, "header");
1450
+ const contentRoot = rootOf(opts.contentClicks, "main");
1451
+ const onClick = (e) => {
1452
+ const target = e.target;
1453
+ if (!target?.closest) return;
1454
+ const nav = opts.navClicks ? target.closest(`${navRoot} a[href]`) : null;
1455
+ if (nav) {
1456
+ const category2 = sectionOf(nav);
1457
+ if (!category2) return;
1458
+ client.track("nav_click", { category: category2, label: (nav.textContent ?? "").trim().slice(0, 60) });
1459
+ bump();
1460
+ return;
1461
+ }
1462
+ const root = opts.contentClicks ? target.closest(contentRoot) : null;
1463
+ if (!root) return;
1464
+ const img = target.closest("img") ?? cardImage(target, root);
1465
+ const label = (img?.alt ?? "").trim().slice(0, 160);
1466
+ if (!label) return;
1467
+ const category = sectionOf(target.closest("a[href]"));
1468
+ client.track("content_click", category ? { label, category } : { label });
1469
+ bump();
1470
+ };
1471
+ const unwatch = watchNavigation(onNavigate);
1472
+ window.addEventListener("click", onLeaveClick, { capture: true });
1473
+ document.addEventListener("visibilitychange", onVisible);
1474
+ if (opts.contentClicks || opts.navClicks) document.addEventListener("click", onClick, { capture: true });
1475
+ return () => {
1476
+ unwatch();
1477
+ window.removeEventListener("click", onLeaveClick, { capture: true });
1478
+ document.removeEventListener("visibilitychange", onVisible);
1479
+ document.removeEventListener("click", onClick, { capture: true });
1480
+ };
1481
+ }
1233
1482
 
1234
1483
  // src/server/cookies.ts
1235
1484
  function parseCookies(header) {
@@ -1240,7 +1489,12 @@ function parseCookies(header) {
1240
1489
  if (idx === -1) continue;
1241
1490
  const key = part.slice(0, idx).trim();
1242
1491
  const value = part.slice(idx + 1).trim();
1243
- if (key) out[key] = decodeURIComponent(value);
1492
+ if (!key) continue;
1493
+ try {
1494
+ out[key] = decodeURIComponent(value);
1495
+ } catch {
1496
+ out[key] = value;
1497
+ }
1244
1498
  }
1245
1499
  return out;
1246
1500
  }
@@ -1314,6 +1568,14 @@ var Identity = class {
1314
1568
  const storageKey = this.key("device_id");
1315
1569
  try {
1316
1570
  const cached = localStorage.getItem(storageKey);
1571
+ const fromServer = this.cookie?.authoritative ? this.readVisitorCookie() : null;
1572
+ const effective = localStorage.getItem(this.key("visitor_id")) ?? cached;
1573
+ if (cached && fromServer && fromServer !== effective) {
1574
+ this.deviceId = fromServer;
1575
+ localStorage.setItem(storageKey, fromServer);
1576
+ if (localStorage.getItem(this.key("visitor_id"))) localStorage.setItem(this.key("visitor_id"), fromServer);
1577
+ return fromServer;
1578
+ }
1317
1579
  if (cached) {
1318
1580
  this.deviceId = cached;
1319
1581
  this.writeVisitorCookie(this.getVisitorId() ?? cached);
@@ -1493,7 +1755,7 @@ function resolveCookieConfig(opt) {
1493
1755
  const o = opt === true ? {} : opt;
1494
1756
  const sameSite = o.sameSite ?? "Lax";
1495
1757
  const secure = sameSite === "None" ? true : o.secure ?? false;
1496
- return { sameSite, secure, maxAgeDays: o.maxAgeDays ?? 365, domain: o.domain };
1758
+ return { sameSite, secure, maxAgeDays: o.maxAgeDays ?? 365, domain: o.domain, authoritative: o.authoritative ?? false };
1497
1759
  }
1498
1760
 
1499
1761
  // src/client/widgets/form.ts
@@ -2177,6 +2439,8 @@ var XeniaClient = class {
2177
2439
  renderer;
2178
2440
  analyticsQueue;
2179
2441
  tracker = null;
2442
+ stopPageTracking = null;
2443
+ stopEngagement = null;
2180
2444
  /** The in-flight (or finished) boot; shared so concurrent `init()` calls start one tracker. */
2181
2445
  booting = null;
2182
2446
  /** Bumped by `destroy()`, so a boot still waiting on the device id knows to stop. */
@@ -2228,17 +2492,21 @@ var XeniaClient = class {
2228
2492
  rootMargin: this.options.viewport?.rootMargin,
2229
2493
  triggerOnce: this.options.viewport?.triggerOnce ?? true
2230
2494
  },
2495
+ slotClicks: this.options.slotClicks,
2231
2496
  // Declarative `data-xenia` elements fire journey triggers (immediate).
2232
2497
  fire: (eventId, interaction) => this.track(eventId, { interactionType: interaction }, { trigger: true }),
2233
2498
  // `[data-xenia-slot]` elements fire analytics-only personalization events.
2234
- fireSlot: (interaction, meta) => {
2499
+ fireSlot: (interaction, meta2) => {
2235
2500
  this.track(
2236
2501
  "customEvent",
2237
2502
  {
2238
2503
  customEventName: interaction === "impression" ? "personalization_impression" : "personalization_click",
2239
- contentKey: meta.contentKey,
2240
- contentEntryId: meta.contentEntryId ?? null,
2241
- baseEntryId: meta.baseEntryId ?? null
2504
+ ...meta2.contentKey ? { contentKey: meta2.contentKey } : {},
2505
+ contentEntryId: meta2.contentEntryId ?? null,
2506
+ baseEntryId: meta2.baseEntryId ?? null,
2507
+ ...meta2.bucket ? { bucket: meta2.bucket } : {},
2508
+ ...meta2.slot ? { slot: meta2.slot } : {},
2509
+ ...meta2.label ? { label: meta2.label } : {}
2242
2510
  },
2243
2511
  { trigger: false }
2244
2512
  );
@@ -2246,11 +2514,32 @@ var XeniaClient = class {
2246
2514
  });
2247
2515
  this.tracker.start();
2248
2516
  }
2517
+ const { pageViews = false, pageLeave = false } = this.options;
2518
+ if (pageViews || pageLeave) {
2519
+ this.stopPageTracking = startPageTracking({ track: (name, props) => this.track(name, props) }, { pageViews, pageLeave });
2520
+ }
2521
+ const { trail = false, contentClicks = false, navClicks = false } = this.options;
2522
+ if (trail || contentClicks || navClicks) {
2523
+ this.stopEngagement = startEngagement(
2524
+ { track: (name, props) => this.track(name, props) },
2525
+ {
2526
+ trail,
2527
+ contentClicks,
2528
+ navClicks,
2529
+ prefix: this.options.prefix ?? "xenia",
2530
+ analyticsAllowed: () => this.analyticsAllowed() && !this.dntBlocked()
2531
+ }
2532
+ );
2533
+ }
2249
2534
  }
2250
2535
  /** Tear down listeners, observers, and injected UI. */
2251
2536
  destroy() {
2252
2537
  this.tracker?.stop();
2253
2538
  this.tracker = null;
2539
+ this.stopPageTracking?.();
2540
+ this.stopPageTracking = null;
2541
+ this.stopEngagement?.();
2542
+ this.stopEngagement = null;
2254
2543
  this.renderer.destroy();
2255
2544
  this.analyticsQueue.stop();
2256
2545
  this.booting = null;
@@ -2401,7 +2690,7 @@ var XeniaClient = class {
2401
2690
  return personalizePage(ref, { ...opts, resolve: this.entryResolver(opts) });
2402
2691
  }
2403
2692
  entryResolver(opts) {
2404
- const resolveOpts = this.withVisitor({ data: opts.data, locale: opts.locale });
2693
+ const resolveOpts = { ...this.withVisitor({ data: opts.data, locale: opts.locale }), contentKey: opts.contentKey };
2405
2694
  return async (ids) => (await this.api.resolveEntries(ids, resolveOpts)).decisions ?? [];
2406
2695
  }
2407
2696
  /** The SDK-owned visitor anchor: the `identify()`/`config.visitorId` id, else the device id. */