@aranova/tracking-next 0.27.0 → 1.1.0

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 (73) hide show
  1. package/dist/blog-server.d.mts +2 -1
  2. package/dist/blog-server.d.ts +2 -1
  3. package/dist/blog-server.js +121 -98
  4. package/dist/blog-server.js.map +1 -1
  5. package/dist/blog-server.mjs +86 -63
  6. package/dist/blog-server.mjs.map +1 -1
  7. package/dist/blog.d.mts +651 -1755
  8. package/dist/blog.d.ts +651 -1755
  9. package/dist/blog.js +121 -98
  10. package/dist/blog.js.map +1 -1
  11. package/dist/blog.mjs +86 -63
  12. package/dist/blog.mjs.map +1 -1
  13. package/dist/calendar-server.d.mts +2 -2
  14. package/dist/calendar-server.d.ts +2 -2
  15. package/dist/calendar-server.js +0 -26
  16. package/dist/calendar-server.js.map +1 -1
  17. package/dist/calendar-server.mjs +0 -26
  18. package/dist/calendar-server.mjs.map +1 -1
  19. package/dist/calendar.d.mts +3 -3
  20. package/dist/calendar.d.ts +3 -3
  21. package/dist/calendar.js +227 -126
  22. package/dist/calendar.js.map +1 -1
  23. package/dist/calendar.mjs +216 -125
  24. package/dist/calendar.mjs.map +1 -1
  25. package/dist/client.d.mts +11 -4
  26. package/dist/client.d.ts +11 -4
  27. package/dist/client.js +611 -347
  28. package/dist/client.js.map +1 -1
  29. package/dist/client.mjs +611 -347
  30. package/dist/client.mjs.map +1 -1
  31. package/dist/index.d.mts +9 -8
  32. package/dist/index.d.ts +9 -8
  33. package/dist/index.js +550 -487
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +539 -487
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/ingest-typed-MBuMe9Pn.d.ts +883 -0
  38. package/dist/ingest-typed-UA13xTfI.d.mts +883 -0
  39. package/dist/middleware.js +0 -467
  40. package/dist/middleware.js.map +1 -1
  41. package/dist/middleware.mjs +0 -467
  42. package/dist/middleware.mjs.map +1 -1
  43. package/dist/{phone-utils-BVzSNBf1.d.mts → phone-utils-C0ds6cqZ.d.mts} +24 -78
  44. package/dist/{phone-utils-BVzSNBf1.d.ts → phone-utils-C0ds6cqZ.d.ts} +24 -78
  45. package/dist/phone.d.mts +2 -2
  46. package/dist/phone.d.ts +2 -2
  47. package/dist/phone.js +0 -464
  48. package/dist/phone.js.map +1 -1
  49. package/dist/phone.mjs +0 -464
  50. package/dist/phone.mjs.map +1 -1
  51. package/dist/{sales-IGC2tq4J.d.ts → sales-DQC815f3.d.ts} +90 -329
  52. package/dist/{sales-DQGzXSgL.d.mts → sales-w1reW_Y3.d.mts} +90 -329
  53. package/dist/sales.d.mts +3 -3
  54. package/dist/sales.d.ts +3 -3
  55. package/dist/sales.js +270 -127
  56. package/dist/sales.js.map +1 -1
  57. package/dist/sales.mjs +259 -127
  58. package/dist/sales.mjs.map +1 -1
  59. package/dist/server.d.mts +2 -2
  60. package/dist/server.d.ts +2 -2
  61. package/dist/server.js +0 -467
  62. package/dist/server.js.map +1 -1
  63. package/dist/server.mjs +0 -467
  64. package/dist/server.mjs.map +1 -1
  65. package/dist/{tracking-config-runtime-D7prdTE1.d.mts → tracking-config-runtime-C6H7ofmo.d.mts} +6 -0
  66. package/dist/{tracking-config-runtime-D7prdTE1.d.ts → tracking-config-runtime-C6H7ofmo.d.ts} +6 -0
  67. package/dist/transport-CDmGOiCq.d.ts +253 -0
  68. package/dist/transport-I_cMSFQ6.d.mts +253 -0
  69. package/package.json +2 -2
  70. package/dist/ingest-typed-BxLFlZ7N.d.mts +0 -1371
  71. package/dist/ingest-typed-ByQVRMvP.d.ts +0 -1371
  72. package/dist/transport-C-_rFSR4.d.ts +0 -721
  73. package/dist/transport-yUF5Wize.d.mts +0 -721
package/dist/client.js CHANGED
@@ -308,6 +308,7 @@ var TRACKING_CAPABILITIES = [
308
308
  "consent_controls",
309
309
  "conversion_goals_auto",
310
310
  "conversion_goals_manual",
311
+ "cross_site_linker",
311
312
  "cta_click_capture",
312
313
  "form_capture",
313
314
  "phone_click_capture",
@@ -334,6 +335,9 @@ function readDomMarkers() {
334
335
  return found;
335
336
  }
336
337
 
338
+ // ../tracking-core/src/cross-site-link.ts
339
+ var z = __toESM(require("zod/mini"));
340
+
337
341
  // ../tracking-core/src/tracking.ts
338
342
  var TRACKING_COOKIE_MAX_AGE_SECONDS = 7776e3;
339
343
  var TRACKING_PARAM_KEYS = [
@@ -626,10 +630,10 @@ function readStoredRecord() {
626
630
  return null;
627
631
  }
628
632
  }
629
- function writeRecord(record) {
630
- memoryRecord = record;
633
+ function writeRecord(record3) {
634
+ memoryRecord = record3;
631
635
  try {
632
- window.localStorage.setItem(LANDING_STORAGE_KEY, JSON.stringify(record));
636
+ window.localStorage.setItem(LANDING_STORAGE_KEY, JSON.stringify(record3));
633
637
  } catch {
634
638
  }
635
639
  }
@@ -651,9 +655,9 @@ function getOrCaptureLandingParams(sessionId, url) {
651
655
  if (memoryRecord && memoryRecord.session_id === sessionId) {
652
656
  return memoryRecord.params;
653
657
  }
654
- const record = { session_id: sessionId, params: captureFromUrl(url) };
655
- writeRecord(record);
656
- return record.params;
658
+ const record3 = { session_id: sessionId, params: captureFromUrl(url) };
659
+ writeRecord(record3);
660
+ return record3.params;
657
661
  }
658
662
  function buildLandingPayloadFields(sessionId, override) {
659
663
  const params = override ?? (typeof window === "undefined" ? null : getOrCaptureLandingParams(sessionId));
@@ -672,6 +676,244 @@ function buildLandingPayloadFields(sessionId, override) {
672
676
  };
673
677
  }
674
678
 
679
+ // ../tracking-core/src/session.ts
680
+ var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
681
+ var SESSION_STORAGE_KEY = "aranova_tracking_session";
682
+ var SESSION_IDLE_MS = 30 * 60 * 1e3;
683
+ function safeUuid() {
684
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
685
+ return crypto.randomUUID();
686
+ return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
687
+ }
688
+ function readLocalStorage(key) {
689
+ try {
690
+ return window.localStorage.getItem(key);
691
+ } catch {
692
+ return null;
693
+ }
694
+ }
695
+ function writeLocalStorage(key, value) {
696
+ try {
697
+ window.localStorage.setItem(key, value);
698
+ } catch {
699
+ }
700
+ }
701
+ function getVisitorId() {
702
+ if (typeof window === "undefined") return safeUuid();
703
+ const existing = readLocalStorage(VISITOR_STORAGE_KEY);
704
+ if (existing && existing.length > 0) return existing;
705
+ const fresh = safeUuid();
706
+ writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
707
+ return fresh;
708
+ }
709
+ function adoptVisitorId(visitorId) {
710
+ if (typeof window === "undefined" || visitorId.length === 0) return false;
711
+ const existing = readLocalStorage(VISITOR_STORAGE_KEY);
712
+ if (existing && existing.length > 0) return false;
713
+ writeLocalStorage(VISITOR_STORAGE_KEY, visitorId);
714
+ return readLocalStorage(VISITOR_STORAGE_KEY) === visitorId;
715
+ }
716
+ function getOrRotateSessionId(now = Date.now()) {
717
+ if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
718
+ const raw = readLocalStorage(SESSION_STORAGE_KEY);
719
+ if (raw) {
720
+ try {
721
+ const parsed = JSON.parse(raw);
722
+ if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
723
+ if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
724
+ const refreshed = { id: parsed.id, last_event_at: now };
725
+ writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
726
+ return { id: parsed.id, isNew: false };
727
+ }
728
+ }
729
+ } catch {
730
+ }
731
+ }
732
+ const fresh = { id: safeUuid(), last_event_at: now };
733
+ writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
734
+ return { id: fresh.id, isNew: true };
735
+ }
736
+
737
+ // ../tracking-core/src/cross-site-link.ts
738
+ var LINK_PARAM = "_arv";
739
+ var LINK_TOKEN_VERSION = "1";
740
+ var LINK_TOKEN_TTL_MS = 5 * 60 * 1e3;
741
+ var FUTURE_SKEW_MS = 60 * 1e3;
742
+ var CARRIED_KEYS = {
743
+ g: "gclid",
744
+ w: "wbraid",
745
+ gb: "gbraid",
746
+ f: "fbclid",
747
+ y: "ylpcid"
748
+ };
749
+ var CARRIED_ENTRIES = Object.entries(CARRIED_KEYS);
750
+ var identifier = z.string().check(z.minLength(1), z.maxLength(128));
751
+ var optionalIdentifier = z.optional(identifier);
752
+ var linkTokenSchema = z.object({
753
+ v: identifier,
754
+ s: identifier,
755
+ // Epoch ms in base36.
756
+ t: z.string().check(z.regex(/^[0-9a-z]{1,12}$/)),
757
+ g: optionalIdentifier,
758
+ w: optionalIdentifier,
759
+ gb: optionalIdentifier,
760
+ f: optionalIdentifier,
761
+ fc: optionalIdentifier,
762
+ y: optionalIdentifier
763
+ });
764
+ function toBase64Url(input) {
765
+ const bytes = new TextEncoder().encode(input);
766
+ let binary = "";
767
+ for (const byte of bytes) binary += String.fromCharCode(byte);
768
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
769
+ }
770
+ function fromBase64Url(input) {
771
+ try {
772
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
773
+ const binary = atob(padded + "=".repeat((4 - padded.length % 4) % 4));
774
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
775
+ return new TextDecoder().decode(bytes);
776
+ } catch {
777
+ return null;
778
+ }
779
+ }
780
+ function encodeLinkToken(payload) {
781
+ const params = new URLSearchParams();
782
+ for (const [key, value] of Object.entries(payload)) {
783
+ if (typeof value === "string" && value.length > 0) params.set(key, value);
784
+ }
785
+ return `${LINK_TOKEN_VERSION}.${toBase64Url(params.toString())}`;
786
+ }
787
+ function decodeLinkToken(raw) {
788
+ const dot = raw.indexOf(".");
789
+ if (dot <= 0 || raw.slice(0, dot) !== LINK_TOKEN_VERSION) return null;
790
+ const decoded = fromBase64Url(raw.slice(dot + 1));
791
+ if (decoded === null) return null;
792
+ const params = new URLSearchParams(decoded);
793
+ const candidate = {};
794
+ params.forEach((value, key) => {
795
+ candidate[key] = value;
796
+ });
797
+ const result = linkTokenSchema.safeParse(candidate);
798
+ return result.success ? result.data : null;
799
+ }
800
+ function payloadToLink(payload) {
801
+ const carried = {};
802
+ for (const [wire, param] of CARRIED_ENTRIES) {
803
+ const value = payload[wire];
804
+ if (value) carried[param] = value;
805
+ }
806
+ return {
807
+ visitorId: payload.v,
808
+ sessionId: payload.s,
809
+ linkedAt: Number.parseInt(payload.t, 36),
810
+ carried,
811
+ fbc: payload.fc ?? null
812
+ };
813
+ }
814
+ function stripWww(hostname) {
815
+ return hostname.toLowerCase().replace(/^www\./, "");
816
+ }
817
+ function isSiblingHost(hostname, sibling) {
818
+ const host = stripWww(hostname);
819
+ const target = stripWww(sibling);
820
+ return host === target || host.endsWith(`.${target}`);
821
+ }
822
+ function resolveCrossSiteLink(url, linkedHostnames, now = Date.now()) {
823
+ if (typeof window === "undefined" || linkedHostnames.length === 0) return null;
824
+ let target;
825
+ try {
826
+ target = new URL(url, window.location.href);
827
+ } catch {
828
+ return null;
829
+ }
830
+ if (target.protocol !== "http:" && target.protocol !== "https:") return null;
831
+ const here = window.location.hostname;
832
+ if (here && isSiblingHost(target.hostname, here)) return null;
833
+ const hostname = linkedHostnames.find((sibling) => isSiblingHost(target.hostname, sibling));
834
+ if (hostname === void 0) return null;
835
+ const consent = getConsentChoice();
836
+ if (consent.state === "denied" && consent.source === "explicit") {
837
+ return { hostname, decorated: url };
838
+ }
839
+ target.searchParams.set(LINK_PARAM, buildOutboundLinkToken(now));
840
+ return { hostname, decorated: target.href };
841
+ }
842
+ function stripLinkParam(url) {
843
+ try {
844
+ const parsed = new URL(url, typeof window === "undefined" ? void 0 : window.location.href);
845
+ if (!parsed.searchParams.has(LINK_PARAM)) return url;
846
+ parsed.searchParams.delete(LINK_PARAM);
847
+ return parsed.href;
848
+ } catch {
849
+ return url;
850
+ }
851
+ }
852
+ function stripLinkParamFromSearch(search) {
853
+ if (!search.includes(LINK_PARAM)) return search;
854
+ const params = new URLSearchParams(search);
855
+ if (!params.has(LINK_PARAM)) return search;
856
+ params.delete(LINK_PARAM);
857
+ const rest = params.toString();
858
+ return rest.length > 0 ? `?${rest}` : "";
859
+ }
860
+ function readCarried() {
861
+ const out = {};
862
+ for (const [wire, param] of CARRIED_ENTRIES) {
863
+ const value = readCookieValue(param);
864
+ if (value) out[wire] = value;
865
+ }
866
+ return out;
867
+ }
868
+ function buildOutboundLinkToken(now = Date.now()) {
869
+ const fbc = getFbcCookie();
870
+ return encodeLinkToken({
871
+ v: getVisitorId(),
872
+ s: getOrRotateSessionId(now).id,
873
+ t: now.toString(36),
874
+ ...readCarried(),
875
+ ...fbc ? { fc: fbc } : {}
876
+ });
877
+ }
878
+ function decorateCrossSiteUrl(url, linkedHostnames, now = Date.now()) {
879
+ return resolveCrossSiteLink(url, linkedHostnames, now)?.decorated ?? url;
880
+ }
881
+ function consumeInboundLink(href = typeof window === "undefined" ? "" : window.location.href, now = Date.now()) {
882
+ if (typeof window === "undefined" || !href) return null;
883
+ let raw;
884
+ try {
885
+ raw = new URL(href, window.location.href).searchParams.get(LINK_PARAM);
886
+ } catch {
887
+ return null;
888
+ }
889
+ if (!raw) return null;
890
+ const payload = decodeLinkToken(raw);
891
+ if (payload === null) return { outcome: "invalid", link: null, adoptedVisitor: false };
892
+ const link = payloadToLink(payload);
893
+ const age = now - link.linkedAt;
894
+ if (!Number.isFinite(age) || age < -FUTURE_SKEW_MS) {
895
+ return { outcome: "invalid", link: null, adoptedVisitor: false };
896
+ }
897
+ if (age > LINK_TOKEN_TTL_MS) {
898
+ return { outcome: "stale", link: null, adoptedVisitor: false };
899
+ }
900
+ const adoptedVisitor = adoptVisitorId(link.visitorId);
901
+ for (const [param, value] of Object.entries(link.carried)) {
902
+ if (!readCookieValue(param)) setTrackingCookie(param, value);
903
+ }
904
+ if (link.fbc && !getFbcCookie()) persistCookieValue(FBC_COOKIE, link.fbc);
905
+ return { outcome: "linked", link, adoptedVisitor };
906
+ }
907
+ function stripLinkParamFromLocation() {
908
+ if (typeof window === "undefined" || typeof history === "undefined") return;
909
+ const clean = stripLinkParam(window.location.href);
910
+ if (clean === window.location.href) return;
911
+ try {
912
+ history.replaceState(null, "", clean);
913
+ } catch {
914
+ }
915
+ }
916
+
675
917
  // ../tracking-core/src/resources/conversion-firing.ts
676
918
  var DEDUP_PREFIX = "_aranova_conv_";
677
919
  function dedupKey(input) {
@@ -778,7 +1020,8 @@ function parseConversionConfig(raw) {
778
1020
  gtag_ids: isStringMap(obj.gtag_ids) ? obj.gtag_ids : {},
779
1021
  meta_pixel_ids: isStringMap(obj.meta_pixel_ids) ? obj.meta_pixel_ids : {},
780
1022
  services,
781
- goals
1023
+ goals,
1024
+ linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : []
782
1025
  };
783
1026
  }
784
1027
  var CACHE_PREFIX = "_aranova_cfg_";
@@ -862,6 +1105,7 @@ function resolveConversionConfig(options) {
862
1105
  getFiring: (key) => goalsByKey.get(key)?.firing ?? null,
863
1106
  getGoal: (key) => goalsByKey.get(key) ?? null,
864
1107
  listGoals: () => [...goalsByKey.values()],
1108
+ linkedHostnames: () => current?.linked_hostnames ?? [],
865
1109
  current: () => current,
866
1110
  isReady: () => current !== null,
867
1111
  onResolve: (listener) => {
@@ -1046,7 +1290,7 @@ function validateConfig(config, ref) {
1046
1290
  function pageViewSnapshot() {
1047
1291
  if (typeof window === "undefined" || typeof document === "undefined") return null;
1048
1292
  return {
1049
- href: window.location.href,
1293
+ href: stripLinkParam(window.location.href),
1050
1294
  title: document.title || null,
1051
1295
  referrer: document.referrer || null
1052
1296
  };
@@ -1197,6 +1441,10 @@ var TrackingConfigRuntime = class {
1197
1441
  listGoals() {
1198
1442
  return this.current?.goals ?? [];
1199
1443
  }
1444
+ /** Sibling hostnames for the linker — read from the cached object even while unconfirmed. */
1445
+ linkedHostnames() {
1446
+ return this.current?.linked_hostnames ?? [];
1447
+ }
1200
1448
  async revalidateNow() {
1201
1449
  try {
1202
1450
  const headers = {};
@@ -1434,76 +1682,32 @@ function withConversionAutoFire(client, autoFire) {
1434
1682
  };
1435
1683
  }
1436
1684
 
1437
- // ../tracking-core/src/session.ts
1438
- var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
1439
- var SESSION_STORAGE_KEY = "aranova_tracking_session";
1440
- var SESSION_IDLE_MS = 30 * 60 * 1e3;
1441
- function safeUuid() {
1442
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
1443
- return crypto.randomUUID();
1444
- return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
1445
- }
1446
- function readLocalStorage(key) {
1447
- try {
1448
- return window.localStorage.getItem(key);
1449
- } catch {
1450
- return null;
1451
- }
1452
- }
1453
- function writeLocalStorage(key, value) {
1454
- try {
1455
- window.localStorage.setItem(key, value);
1456
- } catch {
1457
- }
1458
- }
1459
- function getVisitorId() {
1460
- if (typeof window === "undefined") return safeUuid();
1461
- const existing = readLocalStorage(VISITOR_STORAGE_KEY);
1462
- if (existing && existing.length > 0) return existing;
1463
- const fresh = safeUuid();
1464
- writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
1465
- return fresh;
1466
- }
1467
- function getOrRotateSessionId(now = Date.now()) {
1468
- if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
1469
- const raw = readLocalStorage(SESSION_STORAGE_KEY);
1470
- if (raw) {
1471
- try {
1472
- const parsed = JSON.parse(raw);
1473
- if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
1474
- if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
1475
- const refreshed = { id: parsed.id, last_event_at: now };
1476
- writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
1477
- return { id: parsed.id, isNew: false };
1478
- }
1479
- }
1480
- } catch {
1481
- }
1482
- }
1483
- const fresh = { id: safeUuid(), last_event_at: now };
1484
- writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
1485
- return { id: fresh.id, isNew: true };
1486
- }
1685
+ // ../tracking-core/src/page-view.ts
1686
+ var import_mini = require("zod/mini");
1487
1687
 
1488
1688
  // ../tracking-core/src/events/page-view.ts
1489
- var import_zod = require("zod");
1490
- var pageViewMetadataSchema = import_zod.z.object({
1491
- page: import_zod.z.object({
1492
- title: import_zod.z.string().nullable(),
1493
- path: import_zod.z.string(),
1494
- search: import_zod.z.string(),
1495
- hash: import_zod.z.string()
1496
- }).strict(),
1497
- referrer: import_zod.z.string().nullable(),
1498
- // `.nullable().optional()` — absent (undefined) OR explicit null OR a
1689
+ var z2 = __toESM(require("zod/mini"));
1690
+ var pageViewMetadataSchema = z2.strictObject({
1691
+ page: z2.strictObject({
1692
+ title: z2.nullable(z2.string()),
1693
+ path: z2.string(),
1694
+ search: z2.string(),
1695
+ hash: z2.string()
1696
+ }),
1697
+ referrer: z2.nullable(z2.string()),
1698
+ // Optional + nullable — absent (undefined) OR explicit null OR a
1499
1699
  // real viewport object. Mirrors Pydantic's `_Viewport | None = None`
1500
1700
  // on the backend side so the drift test stays clean.
1501
- viewport: import_zod.z.object({
1502
- w: import_zod.z.number(),
1503
- h: import_zod.z.number()
1504
- }).strict().nullable().optional()
1505
- }).strict();
1506
- var pageViewConfigSchema = import_zod.z.object({}).strict();
1701
+ viewport: z2.optional(
1702
+ z2.nullable(
1703
+ z2.strictObject({
1704
+ w: z2.number(),
1705
+ h: z2.number()
1706
+ })
1707
+ )
1708
+ )
1709
+ });
1710
+ var pageViewConfigSchema = z2.strictObject({});
1507
1711
 
1508
1712
  // ../tracking-core/src/page-view.ts
1509
1713
  var LAST_FIRED_URL_STORAGE_KEY = "aranova_tracking_last_fired_url";
@@ -1539,11 +1743,11 @@ function setLastFiredUrl(url) {
1539
1743
  }
1540
1744
  function buildPageViewMetadata(referrerOverride) {
1541
1745
  if (typeof window === "undefined" || typeof document === "undefined") return null;
1542
- return pageViewMetadataSchema.parse({
1746
+ return (0, import_mini.parse)(pageViewMetadataSchema, {
1543
1747
  page: {
1544
1748
  title: document.title || null,
1545
1749
  path: window.location.pathname,
1546
- search: window.location.search,
1750
+ search: stripLinkParamFromSearch(window.location.search),
1547
1751
  hash: window.location.hash
1548
1752
  },
1549
1753
  referrer: referrerOverride !== void 0 ? referrerOverride : document.referrer || null,
@@ -1804,11 +2008,19 @@ function createTrackingClient(config) {
1804
2008
  let firstPage = null;
1805
2009
  let initialParams = createEmptyTrackingParams();
1806
2010
  let destroyed = false;
2011
+ let inboundLink = null;
2012
+ if (typeof window !== "undefined") {
2013
+ try {
2014
+ inboundLink = consumeInboundLink();
2015
+ } catch {
2016
+ }
2017
+ }
2018
+ let originLink = inboundLink?.link ?? null;
1807
2019
  const visitorId = getVisitorId();
1808
2020
  const initialSession = getOrRotateSessionId();
1809
2021
  let sessionId = initialSession.id;
1810
2022
  if (typeof window !== "undefined") {
1811
- firstPage = window.location.href;
2023
+ firstPage = stripLinkParam(window.location.href);
1812
2024
  try {
1813
2025
  initialParams = captureTrackingParamsFromLocation();
1814
2026
  } catch {
@@ -1819,25 +2031,50 @@ function createTrackingClient(config) {
1819
2031
  } catch {
1820
2032
  }
1821
2033
  }
1822
- function enqueueHeartbeat() {
1823
- const metadata = buildHeartbeatMetadata(
1824
- config.surface,
1825
- sdkVersion,
1826
- packageName,
1827
- config.triggers ?? null,
1828
- activeGtagIds,
1829
- config.configSiteId ?? null
1830
- );
2034
+ function currentPageUrl() {
2035
+ return typeof window === "undefined" ? null : stripLinkParam(window.location.href);
2036
+ }
2037
+ function enqueueInternal(eventType, metadata, pageUrl) {
1831
2038
  queue.push({
1832
- event_type: "sdk_heartbeat",
1833
- page_url: typeof window === "undefined" ? null : window.location.href,
2039
+ event_type: eventType,
2040
+ page_url: pageUrl,
1834
2041
  metadata,
1835
2042
  occurred_at: (/* @__PURE__ */ new Date()).toISOString()
1836
2043
  });
1837
2044
  }
2045
+ function enqueueHeartbeat() {
2046
+ enqueueInternal(
2047
+ "sdk_heartbeat",
2048
+ buildHeartbeatMetadata(
2049
+ config.surface,
2050
+ sdkVersion,
2051
+ packageName,
2052
+ config.triggers ?? null,
2053
+ activeGtagIds,
2054
+ config.configSiteId ?? null
2055
+ ),
2056
+ currentPageUrl()
2057
+ );
2058
+ }
1838
2059
  if (initialSession.isNew) {
1839
2060
  enqueueHeartbeat();
1840
2061
  }
2062
+ if (inboundLink !== null && typeof window !== "undefined") {
2063
+ const link = inboundLink.link;
2064
+ enqueueInternal(
2065
+ "site_link_arrival",
2066
+ {
2067
+ outcome: inboundLink.outcome,
2068
+ origin_session_id: link?.sessionId ?? null,
2069
+ origin_visitor_id: link?.visitorId ?? null,
2070
+ transit_ms: link ? Math.max(0, Date.now() - link.linkedAt) : null,
2071
+ adopted_visitor: inboundLink.adoptedVisitor,
2072
+ page: { path: window.location.pathname }
2073
+ },
2074
+ firstPage
2075
+ );
2076
+ scheduleFlush();
2077
+ }
1841
2078
  if (pending.length > 0) {
1842
2079
  scheduleFlush();
1843
2080
  }
@@ -1845,6 +2082,7 @@ function createTrackingClient(config) {
1845
2082
  const rotated = getOrRotateSessionId();
1846
2083
  if (rotated.isNew && rotated.id !== sessionId) {
1847
2084
  enqueueHeartbeat();
2085
+ originLink = null;
1848
2086
  }
1849
2087
  sessionId = rotated.id;
1850
2088
  const params = mergeTrackingParams(readTrackingParams(), initialParams);
@@ -1875,6 +2113,11 @@ function createTrackingClient(config) {
1875
2113
  // landing), reused from the stored record otherwise. Keys are omitted
1876
2114
  // entirely when the landing isn't observable (SSR).
1877
2115
  ...buildLandingPayloadFields(sessionId),
2116
+ ...originLink ? {
2117
+ origin_session_id: originLink.sessionId,
2118
+ origin_visitor_id: originLink.visitorId,
2119
+ origin_linked_at: new Date(originLink.linkedAt).toISOString()
2120
+ } : {},
1878
2121
  first_page: firstPage,
1879
2122
  consent_state: consentSnapshot(),
1880
2123
  context
@@ -1951,7 +2194,8 @@ function createTrackingClient(config) {
1951
2194
  const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
1952
2195
  queue.push({
1953
2196
  event_type: input.eventType,
1954
- page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
2197
+ // A caller's href still carries `_arv` until the surface strips it after hydration.
2198
+ page_url: input.pageUrl != null ? stripLinkParam(input.pageUrl) : currentPageUrl(),
1955
2199
  metadata: input.metadata ?? null,
1956
2200
  occurred_at: occurredAt
1957
2201
  });
@@ -1997,186 +2241,222 @@ function createTrackingClient(config) {
1997
2241
  };
1998
2242
  }
1999
2243
 
2244
+ // ../tracking-core/src/ingest-typed.ts
2245
+ var import_mini2 = require("zod/mini");
2246
+
2000
2247
  // ../tracking-core/src/events/cta-click.ts
2001
- var import_zod2 = require("zod");
2002
- var ctaClickMetadataSchema = import_zod2.z.object({
2003
- cta_name: import_zod2.z.string(),
2004
- page: import_zod2.z.object({
2005
- path: import_zod2.z.string()
2006
- }).strict(),
2007
- section: import_zod2.z.string().nullable().optional(),
2008
- destination_url: import_zod2.z.string().nullable().optional(),
2248
+ var z3 = __toESM(require("zod/mini"));
2249
+ var ctaClickMetadataSchema = z3.strictObject({
2250
+ cta_name: z3.string(),
2251
+ page: z3.strictObject({
2252
+ path: z3.string()
2253
+ }),
2254
+ section: z3.optional(z3.nullable(z3.string())),
2255
+ destination_url: z3.optional(z3.nullable(z3.string())),
2009
2256
  // Set by auto-capture (and available to manual callers): the link target
2010
2257
  // and a short element descriptor (tag#id) for tying clicks to specific UI.
2011
- href: import_zod2.z.string().nullable().optional(),
2012
- element: import_zod2.z.string().nullable().optional()
2013
- }).strict();
2014
- var ctaClickConfigSchema = import_zod2.z.object({
2015
- autoCapture: import_zod2.z.object({
2016
- selector: import_zod2.z.string().optional()
2017
- }).strict().optional()
2018
- }).strict();
2258
+ href: z3.optional(z3.nullable(z3.string())),
2259
+ element: z3.optional(z3.nullable(z3.string()))
2260
+ });
2261
+ var ctaClickConfigSchema = z3.strictObject({
2262
+ autoCapture: z3.optional(
2263
+ z3.strictObject({
2264
+ selector: z3.optional(z3.string())
2265
+ })
2266
+ )
2267
+ });
2019
2268
 
2020
2269
  // ../tracking-core/src/events/sdk-heartbeat.ts
2021
- var import_zod3 = require("zod");
2022
- var sdkHeartbeatTriggersSchema = import_zod3.z.object({
2023
- automatic: import_zod3.z.array(import_zod3.z.string()),
2024
- manual: import_zod3.z.array(import_zod3.z.string())
2025
- }).strict();
2026
- var sdkHeartbeatMetadataSchema = import_zod3.z.object({
2027
- sdk_version: import_zod3.z.string(),
2028
- package_name: import_zod3.z.string().nullable(),
2029
- surface: import_zod3.z.enum(["next", "react", "script"]),
2270
+ var z4 = __toESM(require("zod/mini"));
2271
+ var sdkHeartbeatTriggersSchema = z4.strictObject({
2272
+ automatic: z4.array(z4.string()),
2273
+ manual: z4.array(z4.string())
2274
+ });
2275
+ var sdkHeartbeatMetadataSchema = z4.strictObject({
2276
+ sdk_version: z4.string(),
2277
+ package_name: z4.nullable(z4.string()),
2278
+ surface: z4.enum(["next", "react", "script"]),
2030
2279
  triggers: sdkHeartbeatTriggersSchema,
2031
- trigger_config: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown())).nullable().optional(),
2032
- configured_gtag_ids: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.string()).nullable().optional(),
2280
+ trigger_config: z4.optional(z4.nullable(z4.record(z4.string(), z4.record(z4.string(), z4.unknown())))),
2281
+ configured_gtag_ids: z4.optional(z4.nullable(z4.record(z4.string(), z4.string()))),
2033
2282
  // Site whose config object the SDK resolves; null on a legacy business ref.
2034
- config_site_id: import_zod3.z.string().uuid().nullable().optional()
2035
- }).strict();
2036
- var sdkHeartbeatConfigSchema = import_zod3.z.object({}).strict();
2283
+ config_site_id: z4.optional(z4.nullable(z4.uuid()))
2284
+ });
2285
+ var sdkHeartbeatConfigSchema = z4.strictObject({});
2037
2286
 
2038
2287
  // ../tracking-core/src/events/form-start.ts
2039
- var import_zod4 = require("zod");
2040
- var formStartMetadataSchema = import_zod4.z.object({
2041
- form: import_zod4.z.object({
2042
- id: import_zod4.z.string(),
2043
- action: import_zod4.z.string().nullable()
2044
- }).strict(),
2045
- page: import_zod4.z.object({
2046
- path: import_zod4.z.string()
2047
- }).strict()
2048
- }).strict();
2049
- var formStartConfigSchema = import_zod4.z.object({
2050
- selector: import_zod4.z.string().optional()
2051
- }).strict();
2288
+ var z5 = __toESM(require("zod/mini"));
2289
+ var formStartMetadataSchema = z5.strictObject({
2290
+ form: z5.strictObject({
2291
+ id: z5.string(),
2292
+ action: z5.nullable(z5.string())
2293
+ }),
2294
+ page: z5.strictObject({
2295
+ path: z5.string()
2296
+ })
2297
+ });
2298
+ var formStartConfigSchema = z5.strictObject({
2299
+ selector: z5.optional(z5.string())
2300
+ });
2052
2301
 
2053
2302
  // ../tracking-core/src/events/form-submit.ts
2054
- var import_zod5 = require("zod");
2055
- var jsonValueSchema = import_zod5.z.lazy(
2056
- () => import_zod5.z.union([
2057
- import_zod5.z.string(),
2058
- import_zod5.z.number().finite(),
2059
- import_zod5.z.boolean(),
2060
- import_zod5.z.null(),
2061
- import_zod5.z.array(jsonValueSchema),
2062
- import_zod5.z.record(jsonValueSchema)
2303
+ var z6 = __toESM(require("zod/mini"));
2304
+ var jsonValueSchema = z6.lazy(
2305
+ () => z6.union([
2306
+ z6.string(),
2307
+ z6.number(),
2308
+ z6.boolean(),
2309
+ z6.null(),
2310
+ z6.array(jsonValueSchema),
2311
+ z6.record(z6.string(), jsonValueSchema)
2063
2312
  ])
2064
- );
2065
- var formSubmitMetadataSchema = import_zod5.z.object({
2066
- form: import_zod5.z.object({
2067
- id: import_zod5.z.string(),
2068
- action: import_zod5.z.string().nullable(),
2069
- fields: import_zod5.z.array(
2070
- import_zod5.z.object({
2071
- name: import_zod5.z.string(),
2072
- type: import_zod5.z.string(),
2073
- label: import_zod5.z.string().nullable(),
2074
- value: jsonValueSchema
2075
- }).strict()
2076
- ).optional()
2077
- }).strict(),
2078
- page: import_zod5.z.object({
2079
- path: import_zod5.z.string()
2080
- }).strict()
2081
- }).strict();
2082
- var formSubmitConfigSchema = import_zod5.z.object({}).strict();
2313
+ ).register(z6.globalRegistry, { id: "JsonSerializableValue" });
2314
+ var formSubmitMetadataSchema = z6.strictObject({
2315
+ form: z6.strictObject({
2316
+ id: z6.string(),
2317
+ action: z6.nullable(z6.string()),
2318
+ fields: z6.optional(
2319
+ z6.array(
2320
+ z6.strictObject({
2321
+ name: z6.string(),
2322
+ type: z6.string(),
2323
+ label: z6.nullable(z6.string()),
2324
+ value: jsonValueSchema
2325
+ })
2326
+ )
2327
+ )
2328
+ }),
2329
+ page: z6.strictObject({
2330
+ path: z6.string()
2331
+ })
2332
+ });
2333
+ var formSubmitConfigSchema = z6.strictObject({});
2083
2334
 
2084
2335
  // ../tracking-core/src/events/multi-page-session.ts
2085
- var import_zod6 = require("zod");
2086
- var multiPageSessionMetadataSchema = import_zod6.z.object({
2087
- page_count: import_zod6.z.number().int().min(2),
2088
- page: import_zod6.z.object({
2089
- path: import_zod6.z.string()
2090
- }).strict()
2091
- }).strict();
2092
- var multiPageSessionConfigSchema = import_zod6.z.object({
2093
- pageThreshold: import_zod6.z.number().int().min(2)
2094
- }).strict();
2336
+ var z7 = __toESM(require("zod/mini"));
2337
+ var multiPageSessionMetadataSchema = z7.strictObject({
2338
+ page_count: z7.int().check(z7.gte(2)),
2339
+ page: z7.strictObject({
2340
+ path: z7.string()
2341
+ })
2342
+ });
2343
+ var multiPageSessionConfigSchema = z7.strictObject({
2344
+ pageThreshold: z7.int().check(z7.gte(2))
2345
+ });
2095
2346
 
2096
2347
  // ../tracking-core/src/events/page-exit.ts
2097
- var import_zod7 = require("zod");
2098
- var pageExitMetadataSchema = import_zod7.z.object({
2099
- dwell_ms: import_zod7.z.number().int().min(0),
2348
+ var z8 = __toESM(require("zod/mini"));
2349
+ var pageExitMetadataSchema = z8.strictObject({
2350
+ dwell_ms: z8.int().check(z8.gte(0)),
2100
2351
  // null = left without any scroll signal; floor is 0 so a valid 0% is never
2101
2352
  // rejected (a single bad field 422s the whole keepalive beacon batch).
2102
- max_scroll_percent: import_zod7.z.number().int().min(0).max(100).nullable(),
2353
+ max_scroll_percent: z8.nullable(z8.int().check(z8.gte(0), z8.lte(100))),
2103
2354
  // The gating baseline: the fraction of the page visible at load with
2104
2355
  // zero scrolling — or, for pages that grew after the post-paint snapshot
2105
2356
  // (skeleton/streaming renders), the first-scroll position that
2106
2357
  // established it. null = page had no scrollable range, or the segment
2107
- // ended before the snapshot. `.optional()` is load-bearing: SDK builds
2358
+ // ended before the snapshot. Optionality is load-bearing: SDK builds
2108
2359
  // predating this field keep POSTing page_exit without the key —
2109
2360
  // requiring it would 422 whole keepalive beacon batches.
2110
- scroll_baseline_percent: import_zod7.z.number().int().min(0).max(100).nullable().optional(),
2111
- page: import_zod7.z.object({
2112
- path: import_zod7.z.string()
2113
- }).strict()
2114
- }).strict();
2115
- var pageExitConfigSchema = import_zod7.z.object({}).strict();
2361
+ scroll_baseline_percent: z8.optional(z8.nullable(z8.int().check(z8.gte(0), z8.lte(100)))),
2362
+ page: z8.strictObject({
2363
+ path: z8.string()
2364
+ })
2365
+ });
2366
+ var pageExitConfigSchema = z8.strictObject({});
2116
2367
 
2117
2368
  // ../tracking-core/src/events/phone-click.ts
2118
- var import_zod8 = require("zod");
2119
- var phoneClickMetadataSchema = import_zod8.z.object({
2120
- phone_number: import_zod8.z.string(),
2121
- page: import_zod8.z.object({
2122
- path: import_zod8.z.string()
2123
- }).strict(),
2124
- section: import_zod8.z.string().nullable().optional()
2125
- }).strict();
2126
- var phoneClickConfigSchema = import_zod8.z.object({
2127
- autoCapture: import_zod8.z.object({
2128
- selector: import_zod8.z.string().optional()
2129
- }).strict().optional()
2130
- }).strict();
2369
+ var z9 = __toESM(require("zod/mini"));
2370
+ var phoneClickMetadataSchema = z9.strictObject({
2371
+ phone_number: z9.string(),
2372
+ page: z9.strictObject({
2373
+ path: z9.string()
2374
+ }),
2375
+ section: z9.optional(z9.nullable(z9.string()))
2376
+ });
2377
+ var phoneClickConfigSchema = z9.strictObject({
2378
+ autoCapture: z9.optional(
2379
+ z9.strictObject({
2380
+ selector: z9.optional(z9.string())
2381
+ })
2382
+ )
2383
+ });
2131
2384
 
2132
2385
  // ../tracking-core/src/events/scroll-depth.ts
2133
- var import_zod9 = require("zod");
2134
- var scrollDepthMetadataSchema = import_zod9.z.object({
2135
- depth_percent: import_zod9.z.number().int().min(1).max(100),
2136
- page: import_zod9.z.object({
2137
- path: import_zod9.z.string()
2138
- }).strict()
2139
- }).strict();
2140
- var scrollDepthConfigSchema = import_zod9.z.object({
2141
- thresholds: import_zod9.z.array(import_zod9.z.number().int().min(1).max(100)).min(1)
2142
- }).strict();
2386
+ var z10 = __toESM(require("zod/mini"));
2387
+ var scrollDepthMetadataSchema = z10.strictObject({
2388
+ depth_percent: z10.int().check(z10.gte(1), z10.lte(100)),
2389
+ page: z10.strictObject({
2390
+ path: z10.string()
2391
+ })
2392
+ });
2393
+ var scrollDepthConfigSchema = z10.strictObject({
2394
+ thresholds: z10.array(z10.int().check(z10.gte(1), z10.lte(100))).check(z10.minLength(1))
2395
+ });
2143
2396
 
2144
2397
  // ../tracking-core/src/events/specific-page-visit.ts
2145
- var import_zod10 = require("zod");
2398
+ var z11 = __toESM(require("zod/mini"));
2146
2399
  var PAGE_IDENTITY_MAX_LENGTH = 64;
2147
2400
  var PAGE_IDENTITY_PATTERN = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
2148
- var pageIdentitySchema = import_zod10.z.string().max(PAGE_IDENTITY_MAX_LENGTH).regex(PAGE_IDENTITY_PATTERN, {
2149
- message: "page identity must be lowercase snake_case, start with a letter, and contain at most 64 characters"
2150
- });
2401
+ var pageIdentitySchema = z11.string().check(
2402
+ z11.maxLength(PAGE_IDENTITY_MAX_LENGTH),
2403
+ z11.regex(PAGE_IDENTITY_PATTERN, {
2404
+ message: "page identity must be lowercase snake_case, start with a letter, and contain at most 64 characters"
2405
+ })
2406
+ );
2151
2407
  var specificPageNameSchema = pageIdentitySchema;
2152
- var specificPageVisitMetadataSchema = import_zod10.z.object({
2408
+ var specificPageVisitMetadataSchema = z11.strictObject({
2153
2409
  page_name: specificPageNameSchema,
2154
- page: import_zod10.z.object({
2155
- path: import_zod10.z.string()
2156
- }).strict()
2157
- }).strict();
2158
- var specificPageVisitConfigSchema = import_zod10.z.object({
2159
- pages: import_zod10.z.array(
2160
- import_zod10.z.object({
2410
+ page: z11.strictObject({
2411
+ path: z11.string()
2412
+ })
2413
+ });
2414
+ var specificPageVisitConfigSchema = z11.strictObject({
2415
+ pages: z11.array(
2416
+ z11.strictObject({
2161
2417
  name: specificPageNameSchema,
2162
- pathPattern: import_zod10.z.custom((value) => value instanceof RegExp, {
2418
+ pathPattern: z11.custom((value) => value instanceof RegExp, {
2163
2419
  message: "pathPattern must be a RegExp"
2164
2420
  })
2165
- }).strict()
2166
- ).min(1)
2167
- }).strict();
2421
+ })
2422
+ ).check(z11.minLength(1))
2423
+ });
2424
+
2425
+ // ../tracking-core/src/events/site-link.ts
2426
+ var z12 = __toESM(require("zod/mini"));
2427
+ var siteLinkClickMetadataSchema = z12.strictObject({
2428
+ destination_host: z12.string(),
2429
+ destination_url: z12.string(),
2430
+ page: z12.strictObject({
2431
+ path: z12.string()
2432
+ })
2433
+ });
2434
+ var siteLinkClickConfigSchema = z12.strictObject({});
2435
+ var siteLinkArrivalMetadataSchema = z12.strictObject({
2436
+ outcome: z12.enum(["linked", "stale", "invalid"]),
2437
+ origin_session_id: z12.optional(z12.nullable(z12.string())),
2438
+ origin_visitor_id: z12.optional(z12.nullable(z12.string())),
2439
+ // Milliseconds between the origin click and this page load.
2440
+ transit_ms: z12.optional(z12.nullable(z12.number())),
2441
+ // True when this browser had no visitor id and took the origin's.
2442
+ adopted_visitor: z12.boolean(),
2443
+ page: z12.strictObject({
2444
+ path: z12.string()
2445
+ })
2446
+ });
2447
+ var siteLinkArrivalConfigSchema = z12.strictObject({});
2168
2448
 
2169
2449
  // ../tracking-core/src/events/time-on-site.ts
2170
- var import_zod11 = require("zod");
2171
- var timeOnSiteMetadataSchema = import_zod11.z.object({
2172
- duration_ms: import_zod11.z.number().int().nonnegative(),
2173
- page: import_zod11.z.object({
2174
- path: import_zod11.z.string()
2175
- }).strict()
2176
- }).strict();
2177
- var timeOnSiteConfigSchema = import_zod11.z.object({
2178
- thresholdSeconds: import_zod11.z.number().int().positive()
2179
- }).strict();
2450
+ var z13 = __toESM(require("zod/mini"));
2451
+ var timeOnSiteMetadataSchema = z13.strictObject({
2452
+ duration_ms: z13.int().check(z13.nonnegative()),
2453
+ page: z13.strictObject({
2454
+ path: z13.string()
2455
+ })
2456
+ });
2457
+ var timeOnSiteConfigSchema = z13.strictObject({
2458
+ thresholdSeconds: z13.int().check(z13.positive())
2459
+ });
2180
2460
 
2181
2461
  // ../tracking-core/src/events/semantics.ts
2182
2462
  var EVENT_SEMANTICS = {
@@ -2245,6 +2525,18 @@ var EVENT_SEMANTICS = {
2245
2525
  category: "engagement",
2246
2526
  outcomeRole: "engagement",
2247
2527
  clientVisibility: "simple"
2528
+ },
2529
+ site_link_click: {
2530
+ label: "Left for sibling site",
2531
+ category: "page",
2532
+ outcomeRole: "navigation",
2533
+ clientVisibility: "detailed"
2534
+ },
2535
+ site_link_arrival: {
2536
+ label: "Arrived from sibling site",
2537
+ category: "page",
2538
+ outcomeRole: "navigation",
2539
+ clientVisibility: "simple"
2248
2540
  }
2249
2541
  };
2250
2542
 
@@ -2300,6 +2592,20 @@ var EVENT_REGISTRY = {
2300
2592
  metadataSchema: pageExitMetadataSchema,
2301
2593
  configSchema: pageExitConfigSchema
2302
2594
  },
2595
+ // Cross-site linkage: emitted by the linker (roster comes from the site
2596
+ // config, not the trigger registry), so neither is consumer-configurable.
2597
+ site_link_click: {
2598
+ kind: "automatic",
2599
+ semantics: EVENT_SEMANTICS.site_link_click,
2600
+ metadataSchema: siteLinkClickMetadataSchema,
2601
+ configSchema: siteLinkClickConfigSchema
2602
+ },
2603
+ site_link_arrival: {
2604
+ kind: "automatic",
2605
+ semantics: EVENT_SEMANTICS.site_link_arrival,
2606
+ metadataSchema: siteLinkArrivalMetadataSchema,
2607
+ configSchema: siteLinkArrivalConfigSchema
2608
+ },
2303
2609
  // --- manual triggers ---
2304
2610
  form_submit: {
2305
2611
  kind: "manual",
@@ -2335,7 +2641,7 @@ function createTypedClient(raw, registry, options = {}) {
2335
2641
  trackEvent(eventType, metadata, opts) {
2336
2642
  if (debug) {
2337
2643
  const def = getEventDefinition(eventType);
2338
- def.metadataSchema.parse(metadata);
2644
+ (0, import_mini2.parse)(def.metadataSchema, metadata);
2339
2645
  }
2340
2646
  raw.trackEvent({
2341
2647
  eventType,
@@ -2770,6 +3076,57 @@ function attachCtaClickCapture(client, config) {
2770
3076
  };
2771
3077
  }
2772
3078
 
3079
+ // ../tracking-core/src/triggers/site-link-capture.ts
3080
+ function anchorFrom(target) {
3081
+ if (!(target instanceof Element)) return null;
3082
+ const anchor = target.closest("a[href]");
3083
+ return anchor instanceof HTMLAnchorElement ? anchor : null;
3084
+ }
3085
+ function opensTheLink(event) {
3086
+ return event.button === 0 || event.button === 1;
3087
+ }
3088
+ function attachSiteLinkCapture(client, options) {
3089
+ if (typeof window === "undefined" || typeof document === "undefined") {
3090
+ return () => {
3091
+ };
3092
+ }
3093
+ registerCapability("cross_site_linker");
3094
+ function hopFrom(event) {
3095
+ const anchor = anchorFrom(event.target);
3096
+ if (anchor === null) return null;
3097
+ const link = resolveCrossSiteLink(anchor.href, options.linkedHostnames());
3098
+ return link === null ? null : { anchor, link };
3099
+ }
3100
+ function onPointerDown(event) {
3101
+ if (!opensTheLink(event)) return;
3102
+ const hop = hopFrom(event);
3103
+ if (hop !== null) hop.anchor.href = hop.link.decorated;
3104
+ }
3105
+ function onClick(event) {
3106
+ if (!opensTheLink(event)) return;
3107
+ const hop = hopFrom(event);
3108
+ if (hop === null) return;
3109
+ hop.anchor.href = hop.link.decorated;
3110
+ client.trackEvent({
3111
+ eventType: "site_link_click",
3112
+ metadata: {
3113
+ destination_host: hop.anchor.hostname,
3114
+ destination_url: stripLinkParam(hop.anchor.href),
3115
+ page: { path: window.location.pathname }
3116
+ },
3117
+ pageUrl: window.location.href,
3118
+ occurredAt: null
3119
+ });
3120
+ client.flushBeacon();
3121
+ }
3122
+ document.addEventListener("pointerdown", onPointerDown, true);
3123
+ document.addEventListener("click", onClick, true);
3124
+ return () => {
3125
+ document.removeEventListener("pointerdown", onPointerDown, true);
3126
+ document.removeEventListener("click", onClick, true);
3127
+ };
3128
+ }
3129
+
2773
3130
  // ../tracking-core/src/triggers/phone-click-capture.ts
2774
3131
  var DEFAULT_TEL_SELECTOR = 'a[href^="tel:"]';
2775
3132
  function safeDecodeURIComponent(value) {
@@ -2824,117 +3181,6 @@ function attachPhoneClickCapture(client, config) {
2824
3181
  };
2825
3182
  }
2826
3183
 
2827
- // ../tracking-core/src/resources/sales/schema.ts
2828
- var import_zod12 = require("zod");
2829
- var SUPPORTED_CURRENCIES = ["USD", "CAD"];
2830
- var TRACKING_ENVIRONMENTS = ["production", "development"];
2831
- var currencySchema = import_zod12.z.enum(SUPPORTED_CURRENCIES);
2832
- var centsSchema = import_zod12.z.number().int().nonnegative();
2833
- var quantitySchema = import_zod12.z.string().regex(/^\d+(\.\d{1,3})?$/);
2834
- var metadataSchema = import_zod12.z.record(import_zod12.z.unknown());
2835
- var saleItemSchema = import_zod12.z.object({
2836
- external_item_id: import_zod12.z.string().nullable().optional(),
2837
- name: import_zod12.z.string().nullable().optional(),
2838
- category: import_zod12.z.string().nullable().optional(),
2839
- quantity: quantitySchema,
2840
- unit_price_cents: centsSchema,
2841
- // Non-negativity validated on the wire — same contract as the other cents
2842
- // fields — and backstopped by the DB CHECK.
2843
- unit_cost_cents: centsSchema.nullable().optional()
2844
- }).strict();
2845
- var saleServiceSchema = import_zod12.z.object({
2846
- service: import_zod12.z.string(),
2847
- amount_cents: centsSchema
2848
- }).strict();
2849
- var customerNameSchema = import_zod12.z.string().max(200);
2850
- var customerPhoneSchema = import_zod12.z.string().max(64);
2851
- var customerEmailSchema = import_zod12.z.string().max(320).email();
2852
- function refineServiceXor(val, ctx, { requireAmount }) {
2853
- if (val.services != null) {
2854
- if (val.service != null) {
2855
- ctx.addIssue({
2856
- code: import_zod12.z.ZodIssueCode.custom,
2857
- message: "pass either `service` or `services`, not both",
2858
- path: ["services"]
2859
- });
2860
- }
2861
- if (val.services.length === 0) {
2862
- ctx.addIssue({
2863
- code: import_zod12.z.ZodIssueCode.custom,
2864
- message: "`services` must not be empty",
2865
- path: ["services"]
2866
- });
2867
- }
2868
- const keys = val.services.map((s) => s.service);
2869
- if (new Set(keys).size !== keys.length) {
2870
- ctx.addIssue({
2871
- code: import_zod12.z.ZodIssueCode.custom,
2872
- message: "`services` must not list the same service more than once",
2873
- path: ["services"]
2874
- });
2875
- }
2876
- if (val.amount_total_cents != null) {
2877
- const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
2878
- if (val.amount_total_cents !== sum) {
2879
- ctx.addIssue({
2880
- code: import_zod12.z.ZodIssueCode.custom,
2881
- message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
2882
- path: ["amount_total_cents"]
2883
- });
2884
- }
2885
- }
2886
- } else if (requireAmount && val.amount_total_cents == null) {
2887
- ctx.addIssue({
2888
- code: import_zod12.z.ZodIssueCode.custom,
2889
- message: "amount_total_cents is required unless `services` is provided",
2890
- path: ["amount_total_cents"]
2891
- });
2892
- }
2893
- }
2894
- var saleCreateSchema = import_zod12.z.object({
2895
- external_id: import_zod12.z.string().nullable().optional(),
2896
- description: import_zod12.z.string().nullable().optional(),
2897
- service: import_zod12.z.string().nullable().optional(),
2898
- services: import_zod12.z.array(saleServiceSchema).nullable().optional(),
2899
- currency: currencySchema,
2900
- // Optional only because the plural `services` form derives it from the sum
2901
- // (see refineServiceXor); the singular/serviceless path still requires it.
2902
- amount_total_cents: centsSchema.nullable().optional(),
2903
- occurred_at: import_zod12.z.string().datetime(),
2904
- environment: import_zod12.z.enum(TRACKING_ENVIRONMENTS).default("production"),
2905
- items: import_zod12.z.array(saleItemSchema).default([]),
2906
- metadata: metadataSchema.nullable().optional(),
2907
- customer_name: customerNameSchema.nullable().optional(),
2908
- customer_phone: customerPhoneSchema.nullable().optional(),
2909
- customer_email: customerEmailSchema.nullable().optional(),
2910
- // CASL consent attestation: the customer agreed to receive SMS. Recorded
2911
- // with a timestamp server-side; every SMS send path gates on it.
2912
- //
2913
- // Tri-state: omit it (or send null) to let the server apply the business's
2914
- // configured default-opt-in policy, honouring a returning customer's
2915
- // remembered preference. Send an explicit boolean to assert consent state
2916
- // yourself — `false` records a deliberate opt-out.
2917
- sms_consent: import_zod12.z.boolean().nullable().optional()
2918
- }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
2919
- var saleUpdateSchema = import_zod12.z.object({
2920
- description: import_zod12.z.string().nullable().optional(),
2921
- service: import_zod12.z.string().nullable().optional(),
2922
- services: import_zod12.z.array(saleServiceSchema).nullable().optional(),
2923
- currency: currencySchema.optional(),
2924
- amount_total_cents: centsSchema.optional(),
2925
- occurred_at: import_zod12.z.string().datetime().optional(),
2926
- items: import_zod12.z.array(saleItemSchema).optional(),
2927
- metadata: metadataSchema.nullable().optional(),
2928
- customer_name: customerNameSchema.nullable().optional(),
2929
- // Re-attest when changing a filler-looking name; the server clears the
2930
- // prior attestation whenever `customer_name` changes.
2931
- customer_name_placeholder_confirmed: import_zod12.z.boolean().optional(),
2932
- customer_phone: customerPhoneSchema.nullable().optional(),
2933
- customer_email: customerEmailSchema.nullable().optional(),
2934
- // NOT NULL server-side: omit to leave unchanged (explicit null is rejected).
2935
- sms_consent: import_zod12.z.boolean().optional()
2936
- }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: false }));
2937
-
2938
3184
  // src/hooks.ts
2939
3185
  function useGclid() {
2940
3186
  const [gclid, setGclid] = (0, import_react.useState)(null);
@@ -3275,7 +3521,7 @@ function GoogleAdsTracking(props) {
3275
3521
  var import_navigation3 = require("next/navigation");
3276
3522
 
3277
3523
  // package.json
3278
- var version = "0.27.0";
3524
+ var version = "1.1.0";
3279
3525
 
3280
3526
  // src/factory.tsx
3281
3527
  var import_react5 = require("react");
@@ -3372,9 +3618,16 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
3372
3618
  function PageViewTracker({ fire }) {
3373
3619
  const pathname = (0, import_navigation3.usePathname)();
3374
3620
  const searchParams = (0, import_navigation3.useSearchParams)();
3621
+ const search = stripLinkParamFromSearch(`?${searchParams.toString()}`);
3622
+ const carriesToken = searchParams.has(LINK_PARAM);
3375
3623
  (0, import_react5.useEffect)(() => {
3376
3624
  fire();
3377
- }, [pathname, searchParams, fire]);
3625
+ }, [pathname, search, fire]);
3626
+ (0, import_react5.useEffect)(() => {
3627
+ if (!carriesToken) return;
3628
+ const timer = window.setTimeout(stripLinkParamFromLocation, 0);
3629
+ return () => window.clearTimeout(timer);
3630
+ }, [carriesToken]);
3378
3631
  return null;
3379
3632
  }
3380
3633
  function createTracking(options) {
@@ -3407,9 +3660,11 @@ function createTracking(options) {
3407
3660
  };
3408
3661
  return {
3409
3662
  TrackingProvider: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PhoneConfigProvider, { value: phone ?? null, children }),
3410
- useTracking: () => noopClient
3663
+ useTracking: () => noopClient,
3664
+ decorateUrl: (url) => url
3411
3665
  };
3412
3666
  }
3667
+ const linkedHostnames = () => trackingConfig ? getTrackingConfigRuntime(trackingConfig).linkedHostnames() : [];
3413
3668
  const TrackingContext = (0, import_react5.createContext)(null);
3414
3669
  const specificPageVisitConfig = triggers.automatic.specific_page_visit ?? null;
3415
3670
  const timeOnSiteConfig = triggers.automatic.time_on_site ?? null;
@@ -3521,6 +3776,11 @@ function createTracking(options) {
3521
3776
  if (timeOnSiteConfig) {
3522
3777
  detachers.push(attachTimeOnSite(detectorClient, timeOnSiteConfig));
3523
3778
  }
3779
+ detachers.push(
3780
+ attachSiteLinkCapture(detectorClient, {
3781
+ linkedHostnames: () => conversionStore?.linkedHostnames() ?? []
3782
+ })
3783
+ );
3524
3784
  const scrollDepth = triggers.automatic.scroll_depth;
3525
3785
  if (scrollDepth) {
3526
3786
  detachers.push(attachScrollDepth(detectorClient, scrollDepth));
@@ -3543,7 +3803,7 @@ function createTracking(options) {
3543
3803
  }
3544
3804
  };
3545
3805
  });
3546
- }, [rawClient, wrapClient]);
3806
+ }, [conversionStore, rawClient, wrapClient]);
3547
3807
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(TrackingContext.Provider, { value: client, children: [
3548
3808
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react5.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PageViewTracker, { fire: firePageView }) }),
3549
3809
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PhoneConfigProvider, { value: phone ?? null, children })
@@ -3558,7 +3818,11 @@ function createTracking(options) {
3558
3818
  }
3559
3819
  return client;
3560
3820
  }
3561
- return { TrackingProvider, useTracking };
3821
+ return {
3822
+ TrackingProvider,
3823
+ useTracking,
3824
+ decorateUrl: (url) => decorateCrossSiteUrl(url, linkedHostnames())
3825
+ };
3562
3826
  }
3563
3827
  // Annotate the CommonJS export names for ESM import in node:
3564
3828
  0 && (module.exports = {