@aranova/tracking-react 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 (35) hide show
  1. package/dist/client.d.mts +11 -4
  2. package/dist/client.d.ts +11 -4
  3. package/dist/client.js +620 -350
  4. package/dist/client.js.map +1 -1
  5. package/dist/client.mjs +610 -350
  6. package/dist/client.mjs.map +1 -1
  7. package/dist/index.d.mts +7 -6
  8. package/dist/index.d.ts +7 -6
  9. package/dist/index.js +339 -393
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +328 -393
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/ingest-typed-MBuMe9Pn.d.ts +883 -0
  14. package/dist/ingest-typed-UA13xTfI.d.mts +883 -0
  15. package/dist/{phone-utils-BVzSNBf1.d.mts → phone-utils-C0ds6cqZ.d.mts} +24 -78
  16. package/dist/{phone-utils-BVzSNBf1.d.ts → phone-utils-C0ds6cqZ.d.ts} +24 -78
  17. package/dist/phone.d.mts +2 -2
  18. package/dist/phone.d.ts +2 -2
  19. package/dist/phone.js +0 -464
  20. package/dist/phone.js.map +1 -1
  21. package/dist/phone.mjs +0 -464
  22. package/dist/phone.mjs.map +1 -1
  23. package/dist/{sales-D4TB9gRS.d.ts → sales-BuKD_bdo.d.mts} +90 -329
  24. package/dist/{sales-cYjB07y8.d.mts → sales-C3O_xxZ_.d.ts} +90 -329
  25. package/dist/sales.d.mts +3 -3
  26. package/dist/sales.d.ts +3 -3
  27. package/dist/sales.js +270 -127
  28. package/dist/sales.js.map +1 -1
  29. package/dist/sales.mjs +259 -127
  30. package/dist/sales.mjs.map +1 -1
  31. package/dist/{tracking-config-runtime-D7prdTE1.d.mts → tracking-config-runtime-C6H7ofmo.d.mts} +6 -0
  32. package/dist/{tracking-config-runtime-D7prdTE1.d.ts → tracking-config-runtime-C6H7ofmo.d.ts} +6 -0
  33. package/package.json +2 -2
  34. package/dist/ingest-typed-BxLFlZ7N.d.mts +0 -1371
  35. package/dist/ingest-typed-ByQVRMvP.d.ts +0 -1371
package/dist/client.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  "use client";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/client.ts
@@ -284,6 +294,7 @@ var TRACKING_CAPABILITIES = [
284
294
  "consent_controls",
285
295
  "conversion_goals_auto",
286
296
  "conversion_goals_manual",
297
+ "cross_site_linker",
287
298
  "cta_click_capture",
288
299
  "form_capture",
289
300
  "phone_click_capture",
@@ -310,6 +321,9 @@ function readDomMarkers() {
310
321
  return found;
311
322
  }
312
323
 
324
+ // ../tracking-core/src/cross-site-link.ts
325
+ var z = __toESM(require("zod/mini"));
326
+
313
327
  // ../tracking-core/src/tracking.ts
314
328
  var TRACKING_COOKIE_MAX_AGE_SECONDS = 7776e3;
315
329
  var TRACKING_PARAM_KEYS = [
@@ -631,10 +645,10 @@ function readStoredRecord() {
631
645
  return null;
632
646
  }
633
647
  }
634
- function writeRecord(record) {
635
- memoryRecord = record;
648
+ function writeRecord(record3) {
649
+ memoryRecord = record3;
636
650
  try {
637
- window.localStorage.setItem(LANDING_STORAGE_KEY, JSON.stringify(record));
651
+ window.localStorage.setItem(LANDING_STORAGE_KEY, JSON.stringify(record3));
638
652
  } catch {
639
653
  }
640
654
  }
@@ -656,9 +670,9 @@ function getOrCaptureLandingParams(sessionId, url) {
656
670
  if (memoryRecord && memoryRecord.session_id === sessionId) {
657
671
  return memoryRecord.params;
658
672
  }
659
- const record = { session_id: sessionId, params: captureFromUrl(url) };
660
- writeRecord(record);
661
- return record.params;
673
+ const record3 = { session_id: sessionId, params: captureFromUrl(url) };
674
+ writeRecord(record3);
675
+ return record3.params;
662
676
  }
663
677
  function buildLandingPayloadFields(sessionId, override) {
664
678
  const params = override ?? (typeof window === "undefined" ? null : getOrCaptureLandingParams(sessionId));
@@ -677,6 +691,244 @@ function buildLandingPayloadFields(sessionId, override) {
677
691
  };
678
692
  }
679
693
 
694
+ // ../tracking-core/src/session.ts
695
+ var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
696
+ var SESSION_STORAGE_KEY = "aranova_tracking_session";
697
+ var SESSION_IDLE_MS = 30 * 60 * 1e3;
698
+ function safeUuid() {
699
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
700
+ return crypto.randomUUID();
701
+ return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
702
+ }
703
+ function readLocalStorage(key) {
704
+ try {
705
+ return window.localStorage.getItem(key);
706
+ } catch {
707
+ return null;
708
+ }
709
+ }
710
+ function writeLocalStorage(key, value) {
711
+ try {
712
+ window.localStorage.setItem(key, value);
713
+ } catch {
714
+ }
715
+ }
716
+ function getVisitorId() {
717
+ if (typeof window === "undefined") return safeUuid();
718
+ const existing = readLocalStorage(VISITOR_STORAGE_KEY);
719
+ if (existing && existing.length > 0) return existing;
720
+ const fresh = safeUuid();
721
+ writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
722
+ return fresh;
723
+ }
724
+ function adoptVisitorId(visitorId) {
725
+ if (typeof window === "undefined" || visitorId.length === 0) return false;
726
+ const existing = readLocalStorage(VISITOR_STORAGE_KEY);
727
+ if (existing && existing.length > 0) return false;
728
+ writeLocalStorage(VISITOR_STORAGE_KEY, visitorId);
729
+ return readLocalStorage(VISITOR_STORAGE_KEY) === visitorId;
730
+ }
731
+ function getOrRotateSessionId(now = Date.now()) {
732
+ if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
733
+ const raw = readLocalStorage(SESSION_STORAGE_KEY);
734
+ if (raw) {
735
+ try {
736
+ const parsed = JSON.parse(raw);
737
+ if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
738
+ if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
739
+ const refreshed = { id: parsed.id, last_event_at: now };
740
+ writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
741
+ return { id: parsed.id, isNew: false };
742
+ }
743
+ }
744
+ } catch {
745
+ }
746
+ }
747
+ const fresh = { id: safeUuid(), last_event_at: now };
748
+ writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
749
+ return { id: fresh.id, isNew: true };
750
+ }
751
+
752
+ // ../tracking-core/src/cross-site-link.ts
753
+ var LINK_PARAM = "_arv";
754
+ var LINK_TOKEN_VERSION = "1";
755
+ var LINK_TOKEN_TTL_MS = 5 * 60 * 1e3;
756
+ var FUTURE_SKEW_MS = 60 * 1e3;
757
+ var CARRIED_KEYS = {
758
+ g: "gclid",
759
+ w: "wbraid",
760
+ gb: "gbraid",
761
+ f: "fbclid",
762
+ y: "ylpcid"
763
+ };
764
+ var CARRIED_ENTRIES = Object.entries(CARRIED_KEYS);
765
+ var identifier = z.string().check(z.minLength(1), z.maxLength(128));
766
+ var optionalIdentifier = z.optional(identifier);
767
+ var linkTokenSchema = z.object({
768
+ v: identifier,
769
+ s: identifier,
770
+ // Epoch ms in base36.
771
+ t: z.string().check(z.regex(/^[0-9a-z]{1,12}$/)),
772
+ g: optionalIdentifier,
773
+ w: optionalIdentifier,
774
+ gb: optionalIdentifier,
775
+ f: optionalIdentifier,
776
+ fc: optionalIdentifier,
777
+ y: optionalIdentifier
778
+ });
779
+ function toBase64Url(input) {
780
+ const bytes = new TextEncoder().encode(input);
781
+ let binary = "";
782
+ for (const byte of bytes) binary += String.fromCharCode(byte);
783
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
784
+ }
785
+ function fromBase64Url(input) {
786
+ try {
787
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
788
+ const binary = atob(padded + "=".repeat((4 - padded.length % 4) % 4));
789
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
790
+ return new TextDecoder().decode(bytes);
791
+ } catch {
792
+ return null;
793
+ }
794
+ }
795
+ function encodeLinkToken(payload) {
796
+ const params = new URLSearchParams();
797
+ for (const [key, value] of Object.entries(payload)) {
798
+ if (typeof value === "string" && value.length > 0) params.set(key, value);
799
+ }
800
+ return `${LINK_TOKEN_VERSION}.${toBase64Url(params.toString())}`;
801
+ }
802
+ function decodeLinkToken(raw) {
803
+ const dot = raw.indexOf(".");
804
+ if (dot <= 0 || raw.slice(0, dot) !== LINK_TOKEN_VERSION) return null;
805
+ const decoded = fromBase64Url(raw.slice(dot + 1));
806
+ if (decoded === null) return null;
807
+ const params = new URLSearchParams(decoded);
808
+ const candidate = {};
809
+ params.forEach((value, key) => {
810
+ candidate[key] = value;
811
+ });
812
+ const result = linkTokenSchema.safeParse(candidate);
813
+ return result.success ? result.data : null;
814
+ }
815
+ function payloadToLink(payload) {
816
+ const carried = {};
817
+ for (const [wire, param] of CARRIED_ENTRIES) {
818
+ const value = payload[wire];
819
+ if (value) carried[param] = value;
820
+ }
821
+ return {
822
+ visitorId: payload.v,
823
+ sessionId: payload.s,
824
+ linkedAt: Number.parseInt(payload.t, 36),
825
+ carried,
826
+ fbc: payload.fc ?? null
827
+ };
828
+ }
829
+ function stripWww(hostname) {
830
+ return hostname.toLowerCase().replace(/^www\./, "");
831
+ }
832
+ function isSiblingHost(hostname, sibling) {
833
+ const host = stripWww(hostname);
834
+ const target = stripWww(sibling);
835
+ return host === target || host.endsWith(`.${target}`);
836
+ }
837
+ function resolveCrossSiteLink(url, linkedHostnames, now = Date.now()) {
838
+ if (typeof window === "undefined" || linkedHostnames.length === 0) return null;
839
+ let target;
840
+ try {
841
+ target = new URL(url, window.location.href);
842
+ } catch {
843
+ return null;
844
+ }
845
+ if (target.protocol !== "http:" && target.protocol !== "https:") return null;
846
+ const here = window.location.hostname;
847
+ if (here && isSiblingHost(target.hostname, here)) return null;
848
+ const hostname = linkedHostnames.find((sibling) => isSiblingHost(target.hostname, sibling));
849
+ if (hostname === void 0) return null;
850
+ const consent = getConsentChoice();
851
+ if (consent.state === "denied" && consent.source === "explicit") {
852
+ return { hostname, decorated: url };
853
+ }
854
+ target.searchParams.set(LINK_PARAM, buildOutboundLinkToken(now));
855
+ return { hostname, decorated: target.href };
856
+ }
857
+ function stripLinkParam(url) {
858
+ try {
859
+ const parsed = new URL(url, typeof window === "undefined" ? void 0 : window.location.href);
860
+ if (!parsed.searchParams.has(LINK_PARAM)) return url;
861
+ parsed.searchParams.delete(LINK_PARAM);
862
+ return parsed.href;
863
+ } catch {
864
+ return url;
865
+ }
866
+ }
867
+ function stripLinkParamFromSearch(search) {
868
+ if (!search.includes(LINK_PARAM)) return search;
869
+ const params = new URLSearchParams(search);
870
+ if (!params.has(LINK_PARAM)) return search;
871
+ params.delete(LINK_PARAM);
872
+ const rest = params.toString();
873
+ return rest.length > 0 ? `?${rest}` : "";
874
+ }
875
+ function readCarried() {
876
+ const out = {};
877
+ for (const [wire, param] of CARRIED_ENTRIES) {
878
+ const value = readCookieValue(param);
879
+ if (value) out[wire] = value;
880
+ }
881
+ return out;
882
+ }
883
+ function buildOutboundLinkToken(now = Date.now()) {
884
+ const fbc = getFbcCookie();
885
+ return encodeLinkToken({
886
+ v: getVisitorId(),
887
+ s: getOrRotateSessionId(now).id,
888
+ t: now.toString(36),
889
+ ...readCarried(),
890
+ ...fbc ? { fc: fbc } : {}
891
+ });
892
+ }
893
+ function decorateCrossSiteUrl(url, linkedHostnames, now = Date.now()) {
894
+ return resolveCrossSiteLink(url, linkedHostnames, now)?.decorated ?? url;
895
+ }
896
+ function consumeInboundLink(href = typeof window === "undefined" ? "" : window.location.href, now = Date.now()) {
897
+ if (typeof window === "undefined" || !href) return null;
898
+ let raw;
899
+ try {
900
+ raw = new URL(href, window.location.href).searchParams.get(LINK_PARAM);
901
+ } catch {
902
+ return null;
903
+ }
904
+ if (!raw) return null;
905
+ const payload = decodeLinkToken(raw);
906
+ if (payload === null) return { outcome: "invalid", link: null, adoptedVisitor: false };
907
+ const link = payloadToLink(payload);
908
+ const age = now - link.linkedAt;
909
+ if (!Number.isFinite(age) || age < -FUTURE_SKEW_MS) {
910
+ return { outcome: "invalid", link: null, adoptedVisitor: false };
911
+ }
912
+ if (age > LINK_TOKEN_TTL_MS) {
913
+ return { outcome: "stale", link: null, adoptedVisitor: false };
914
+ }
915
+ const adoptedVisitor = adoptVisitorId(link.visitorId);
916
+ for (const [param, value] of Object.entries(link.carried)) {
917
+ if (!readCookieValue(param)) setTrackingCookie(param, value);
918
+ }
919
+ if (link.fbc && !getFbcCookie()) persistCookieValue(FBC_COOKIE, link.fbc);
920
+ return { outcome: "linked", link, adoptedVisitor };
921
+ }
922
+ function stripLinkParamFromLocation() {
923
+ if (typeof window === "undefined" || typeof history === "undefined") return;
924
+ const clean = stripLinkParam(window.location.href);
925
+ if (clean === window.location.href) return;
926
+ try {
927
+ history.replaceState(null, "", clean);
928
+ } catch {
929
+ }
930
+ }
931
+
680
932
  // ../tracking-core/src/resources/conversion-firing.ts
681
933
  var DEDUP_PREFIX = "_aranova_conv_";
682
934
  function dedupKey(input) {
@@ -783,7 +1035,8 @@ function parseConversionConfig(raw) {
783
1035
  gtag_ids: isStringMap(obj.gtag_ids) ? obj.gtag_ids : {},
784
1036
  meta_pixel_ids: isStringMap(obj.meta_pixel_ids) ? obj.meta_pixel_ids : {},
785
1037
  services,
786
- goals
1038
+ goals,
1039
+ linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : []
787
1040
  };
788
1041
  }
789
1042
  var CACHE_PREFIX = "_aranova_cfg_";
@@ -867,6 +1120,7 @@ function resolveConversionConfig(options) {
867
1120
  getFiring: (key) => goalsByKey.get(key)?.firing ?? null,
868
1121
  getGoal: (key) => goalsByKey.get(key) ?? null,
869
1122
  listGoals: () => [...goalsByKey.values()],
1123
+ linkedHostnames: () => current?.linked_hostnames ?? [],
870
1124
  current: () => current,
871
1125
  isReady: () => current !== null,
872
1126
  onResolve: (listener) => {
@@ -1051,7 +1305,7 @@ function validateConfig(config, ref) {
1051
1305
  function pageViewSnapshot() {
1052
1306
  if (typeof window === "undefined" || typeof document === "undefined") return null;
1053
1307
  return {
1054
- href: window.location.href,
1308
+ href: stripLinkParam(window.location.href),
1055
1309
  title: document.title || null,
1056
1310
  referrer: document.referrer || null
1057
1311
  };
@@ -1202,6 +1456,10 @@ var TrackingConfigRuntime = class {
1202
1456
  listGoals() {
1203
1457
  return this.current?.goals ?? [];
1204
1458
  }
1459
+ /** Sibling hostnames for the linker — read from the cached object even while unconfirmed. */
1460
+ linkedHostnames() {
1461
+ return this.current?.linked_hostnames ?? [];
1462
+ }
1205
1463
  async revalidateNow() {
1206
1464
  try {
1207
1465
  const headers = {};
@@ -1439,76 +1697,32 @@ function withConversionAutoFire(client, autoFire) {
1439
1697
  };
1440
1698
  }
1441
1699
 
1442
- // ../tracking-core/src/session.ts
1443
- var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
1444
- var SESSION_STORAGE_KEY = "aranova_tracking_session";
1445
- var SESSION_IDLE_MS = 30 * 60 * 1e3;
1446
- function safeUuid() {
1447
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
1448
- return crypto.randomUUID();
1449
- return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
1450
- }
1451
- function readLocalStorage(key) {
1452
- try {
1453
- return window.localStorage.getItem(key);
1454
- } catch {
1455
- return null;
1456
- }
1457
- }
1458
- function writeLocalStorage(key, value) {
1459
- try {
1460
- window.localStorage.setItem(key, value);
1461
- } catch {
1462
- }
1463
- }
1464
- function getVisitorId() {
1465
- if (typeof window === "undefined") return safeUuid();
1466
- const existing = readLocalStorage(VISITOR_STORAGE_KEY);
1467
- if (existing && existing.length > 0) return existing;
1468
- const fresh = safeUuid();
1469
- writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
1470
- return fresh;
1471
- }
1472
- function getOrRotateSessionId(now = Date.now()) {
1473
- if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
1474
- const raw = readLocalStorage(SESSION_STORAGE_KEY);
1475
- if (raw) {
1476
- try {
1477
- const parsed = JSON.parse(raw);
1478
- if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
1479
- if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
1480
- const refreshed = { id: parsed.id, last_event_at: now };
1481
- writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
1482
- return { id: parsed.id, isNew: false };
1483
- }
1484
- }
1485
- } catch {
1486
- }
1487
- }
1488
- const fresh = { id: safeUuid(), last_event_at: now };
1489
- writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
1490
- return { id: fresh.id, isNew: true };
1491
- }
1700
+ // ../tracking-core/src/page-view.ts
1701
+ var import_mini = require("zod/mini");
1492
1702
 
1493
1703
  // ../tracking-core/src/events/page-view.ts
1494
- var import_zod = require("zod");
1495
- var pageViewMetadataSchema = import_zod.z.object({
1496
- page: import_zod.z.object({
1497
- title: import_zod.z.string().nullable(),
1498
- path: import_zod.z.string(),
1499
- search: import_zod.z.string(),
1500
- hash: import_zod.z.string()
1501
- }).strict(),
1502
- referrer: import_zod.z.string().nullable(),
1503
- // `.nullable().optional()` — absent (undefined) OR explicit null OR a
1704
+ var z2 = __toESM(require("zod/mini"));
1705
+ var pageViewMetadataSchema = z2.strictObject({
1706
+ page: z2.strictObject({
1707
+ title: z2.nullable(z2.string()),
1708
+ path: z2.string(),
1709
+ search: z2.string(),
1710
+ hash: z2.string()
1711
+ }),
1712
+ referrer: z2.nullable(z2.string()),
1713
+ // Optional + nullable — absent (undefined) OR explicit null OR a
1504
1714
  // real viewport object. Mirrors Pydantic's `_Viewport | None = None`
1505
1715
  // on the backend side so the drift test stays clean.
1506
- viewport: import_zod.z.object({
1507
- w: import_zod.z.number(),
1508
- h: import_zod.z.number()
1509
- }).strict().nullable().optional()
1510
- }).strict();
1511
- var pageViewConfigSchema = import_zod.z.object({}).strict();
1716
+ viewport: z2.optional(
1717
+ z2.nullable(
1718
+ z2.strictObject({
1719
+ w: z2.number(),
1720
+ h: z2.number()
1721
+ })
1722
+ )
1723
+ )
1724
+ });
1725
+ var pageViewConfigSchema = z2.strictObject({});
1512
1726
 
1513
1727
  // ../tracking-core/src/page-view.ts
1514
1728
  var LAST_FIRED_URL_STORAGE_KEY = "aranova_tracking_last_fired_url";
@@ -1544,11 +1758,11 @@ function setLastFiredUrl(url) {
1544
1758
  }
1545
1759
  function buildPageViewMetadata(referrerOverride) {
1546
1760
  if (typeof window === "undefined" || typeof document === "undefined") return null;
1547
- return pageViewMetadataSchema.parse({
1761
+ return (0, import_mini.parse)(pageViewMetadataSchema, {
1548
1762
  page: {
1549
1763
  title: document.title || null,
1550
1764
  path: window.location.pathname,
1551
- search: window.location.search,
1765
+ search: stripLinkParamFromSearch(window.location.search),
1552
1766
  hash: window.location.hash
1553
1767
  },
1554
1768
  referrer: referrerOverride !== void 0 ? referrerOverride : document.referrer || null,
@@ -1847,11 +2061,19 @@ function createTrackingClient(config) {
1847
2061
  let firstPage = null;
1848
2062
  let initialParams = createEmptyTrackingParams();
1849
2063
  let destroyed = false;
2064
+ let inboundLink = null;
2065
+ if (typeof window !== "undefined") {
2066
+ try {
2067
+ inboundLink = consumeInboundLink();
2068
+ } catch {
2069
+ }
2070
+ }
2071
+ let originLink = inboundLink?.link ?? null;
1850
2072
  const visitorId = getVisitorId();
1851
2073
  const initialSession = getOrRotateSessionId();
1852
2074
  let sessionId = initialSession.id;
1853
2075
  if (typeof window !== "undefined") {
1854
- firstPage = window.location.href;
2076
+ firstPage = stripLinkParam(window.location.href);
1855
2077
  try {
1856
2078
  initialParams = captureTrackingParamsFromLocation();
1857
2079
  } catch {
@@ -1862,25 +2084,50 @@ function createTrackingClient(config) {
1862
2084
  } catch {
1863
2085
  }
1864
2086
  }
1865
- function enqueueHeartbeat() {
1866
- const metadata = buildHeartbeatMetadata(
1867
- config.surface,
1868
- sdkVersion,
1869
- packageName,
1870
- config.triggers ?? null,
1871
- activeGtagIds,
1872
- config.configSiteId ?? null
1873
- );
2087
+ function currentPageUrl() {
2088
+ return typeof window === "undefined" ? null : stripLinkParam(window.location.href);
2089
+ }
2090
+ function enqueueInternal(eventType, metadata, pageUrl) {
1874
2091
  queue.push({
1875
- event_type: "sdk_heartbeat",
1876
- page_url: typeof window === "undefined" ? null : window.location.href,
2092
+ event_type: eventType,
2093
+ page_url: pageUrl,
1877
2094
  metadata,
1878
2095
  occurred_at: (/* @__PURE__ */ new Date()).toISOString()
1879
2096
  });
1880
2097
  }
2098
+ function enqueueHeartbeat() {
2099
+ enqueueInternal(
2100
+ "sdk_heartbeat",
2101
+ buildHeartbeatMetadata(
2102
+ config.surface,
2103
+ sdkVersion,
2104
+ packageName,
2105
+ config.triggers ?? null,
2106
+ activeGtagIds,
2107
+ config.configSiteId ?? null
2108
+ ),
2109
+ currentPageUrl()
2110
+ );
2111
+ }
1881
2112
  if (initialSession.isNew) {
1882
2113
  enqueueHeartbeat();
1883
2114
  }
2115
+ if (inboundLink !== null && typeof window !== "undefined") {
2116
+ const link = inboundLink.link;
2117
+ enqueueInternal(
2118
+ "site_link_arrival",
2119
+ {
2120
+ outcome: inboundLink.outcome,
2121
+ origin_session_id: link?.sessionId ?? null,
2122
+ origin_visitor_id: link?.visitorId ?? null,
2123
+ transit_ms: link ? Math.max(0, Date.now() - link.linkedAt) : null,
2124
+ adopted_visitor: inboundLink.adoptedVisitor,
2125
+ page: { path: window.location.pathname }
2126
+ },
2127
+ firstPage
2128
+ );
2129
+ scheduleFlush();
2130
+ }
1884
2131
  if (pending.length > 0) {
1885
2132
  scheduleFlush();
1886
2133
  }
@@ -1888,6 +2135,7 @@ function createTrackingClient(config) {
1888
2135
  const rotated = getOrRotateSessionId();
1889
2136
  if (rotated.isNew && rotated.id !== sessionId) {
1890
2137
  enqueueHeartbeat();
2138
+ originLink = null;
1891
2139
  }
1892
2140
  sessionId = rotated.id;
1893
2141
  const params = mergeTrackingParams(readTrackingParams(), initialParams);
@@ -1918,6 +2166,11 @@ function createTrackingClient(config) {
1918
2166
  // landing), reused from the stored record otherwise. Keys are omitted
1919
2167
  // entirely when the landing isn't observable (SSR).
1920
2168
  ...buildLandingPayloadFields(sessionId),
2169
+ ...originLink ? {
2170
+ origin_session_id: originLink.sessionId,
2171
+ origin_visitor_id: originLink.visitorId,
2172
+ origin_linked_at: new Date(originLink.linkedAt).toISOString()
2173
+ } : {},
1921
2174
  first_page: firstPage,
1922
2175
  consent_state: consentSnapshot(),
1923
2176
  context
@@ -1994,7 +2247,8 @@ function createTrackingClient(config) {
1994
2247
  const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
1995
2248
  queue.push({
1996
2249
  event_type: input.eventType,
1997
- page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
2250
+ // A caller's href still carries `_arv` until the surface strips it after hydration.
2251
+ page_url: input.pageUrl != null ? stripLinkParam(input.pageUrl) : currentPageUrl(),
1998
2252
  metadata: input.metadata ?? null,
1999
2253
  occurred_at: occurredAt
2000
2254
  });
@@ -2040,186 +2294,222 @@ function createTrackingClient(config) {
2040
2294
  };
2041
2295
  }
2042
2296
 
2297
+ // ../tracking-core/src/ingest-typed.ts
2298
+ var import_mini2 = require("zod/mini");
2299
+
2043
2300
  // ../tracking-core/src/events/cta-click.ts
2044
- var import_zod2 = require("zod");
2045
- var ctaClickMetadataSchema = import_zod2.z.object({
2046
- cta_name: import_zod2.z.string(),
2047
- page: import_zod2.z.object({
2048
- path: import_zod2.z.string()
2049
- }).strict(),
2050
- section: import_zod2.z.string().nullable().optional(),
2051
- destination_url: import_zod2.z.string().nullable().optional(),
2301
+ var z3 = __toESM(require("zod/mini"));
2302
+ var ctaClickMetadataSchema = z3.strictObject({
2303
+ cta_name: z3.string(),
2304
+ page: z3.strictObject({
2305
+ path: z3.string()
2306
+ }),
2307
+ section: z3.optional(z3.nullable(z3.string())),
2308
+ destination_url: z3.optional(z3.nullable(z3.string())),
2052
2309
  // Set by auto-capture (and available to manual callers): the link target
2053
2310
  // and a short element descriptor (tag#id) for tying clicks to specific UI.
2054
- href: import_zod2.z.string().nullable().optional(),
2055
- element: import_zod2.z.string().nullable().optional()
2056
- }).strict();
2057
- var ctaClickConfigSchema = import_zod2.z.object({
2058
- autoCapture: import_zod2.z.object({
2059
- selector: import_zod2.z.string().optional()
2060
- }).strict().optional()
2061
- }).strict();
2311
+ href: z3.optional(z3.nullable(z3.string())),
2312
+ element: z3.optional(z3.nullable(z3.string()))
2313
+ });
2314
+ var ctaClickConfigSchema = z3.strictObject({
2315
+ autoCapture: z3.optional(
2316
+ z3.strictObject({
2317
+ selector: z3.optional(z3.string())
2318
+ })
2319
+ )
2320
+ });
2062
2321
 
2063
2322
  // ../tracking-core/src/events/sdk-heartbeat.ts
2064
- var import_zod3 = require("zod");
2065
- var sdkHeartbeatTriggersSchema = import_zod3.z.object({
2066
- automatic: import_zod3.z.array(import_zod3.z.string()),
2067
- manual: import_zod3.z.array(import_zod3.z.string())
2068
- }).strict();
2069
- var sdkHeartbeatMetadataSchema = import_zod3.z.object({
2070
- sdk_version: import_zod3.z.string(),
2071
- package_name: import_zod3.z.string().nullable(),
2072
- surface: import_zod3.z.enum(["next", "react", "script"]),
2323
+ var z4 = __toESM(require("zod/mini"));
2324
+ var sdkHeartbeatTriggersSchema = z4.strictObject({
2325
+ automatic: z4.array(z4.string()),
2326
+ manual: z4.array(z4.string())
2327
+ });
2328
+ var sdkHeartbeatMetadataSchema = z4.strictObject({
2329
+ sdk_version: z4.string(),
2330
+ package_name: z4.nullable(z4.string()),
2331
+ surface: z4.enum(["next", "react", "script"]),
2073
2332
  triggers: sdkHeartbeatTriggersSchema,
2074
- trigger_config: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown())).nullable().optional(),
2075
- configured_gtag_ids: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.string()).nullable().optional(),
2333
+ trigger_config: z4.optional(z4.nullable(z4.record(z4.string(), z4.record(z4.string(), z4.unknown())))),
2334
+ configured_gtag_ids: z4.optional(z4.nullable(z4.record(z4.string(), z4.string()))),
2076
2335
  // Site whose config object the SDK resolves; null on a legacy business ref.
2077
- config_site_id: import_zod3.z.string().uuid().nullable().optional()
2078
- }).strict();
2079
- var sdkHeartbeatConfigSchema = import_zod3.z.object({}).strict();
2336
+ config_site_id: z4.optional(z4.nullable(z4.uuid()))
2337
+ });
2338
+ var sdkHeartbeatConfigSchema = z4.strictObject({});
2080
2339
 
2081
2340
  // ../tracking-core/src/events/form-start.ts
2082
- var import_zod4 = require("zod");
2083
- var formStartMetadataSchema = import_zod4.z.object({
2084
- form: import_zod4.z.object({
2085
- id: import_zod4.z.string(),
2086
- action: import_zod4.z.string().nullable()
2087
- }).strict(),
2088
- page: import_zod4.z.object({
2089
- path: import_zod4.z.string()
2090
- }).strict()
2091
- }).strict();
2092
- var formStartConfigSchema = import_zod4.z.object({
2093
- selector: import_zod4.z.string().optional()
2094
- }).strict();
2341
+ var z5 = __toESM(require("zod/mini"));
2342
+ var formStartMetadataSchema = z5.strictObject({
2343
+ form: z5.strictObject({
2344
+ id: z5.string(),
2345
+ action: z5.nullable(z5.string())
2346
+ }),
2347
+ page: z5.strictObject({
2348
+ path: z5.string()
2349
+ })
2350
+ });
2351
+ var formStartConfigSchema = z5.strictObject({
2352
+ selector: z5.optional(z5.string())
2353
+ });
2095
2354
 
2096
2355
  // ../tracking-core/src/events/form-submit.ts
2097
- var import_zod5 = require("zod");
2098
- var jsonValueSchema = import_zod5.z.lazy(
2099
- () => import_zod5.z.union([
2100
- import_zod5.z.string(),
2101
- import_zod5.z.number().finite(),
2102
- import_zod5.z.boolean(),
2103
- import_zod5.z.null(),
2104
- import_zod5.z.array(jsonValueSchema),
2105
- import_zod5.z.record(jsonValueSchema)
2356
+ var z6 = __toESM(require("zod/mini"));
2357
+ var jsonValueSchema = z6.lazy(
2358
+ () => z6.union([
2359
+ z6.string(),
2360
+ z6.number(),
2361
+ z6.boolean(),
2362
+ z6.null(),
2363
+ z6.array(jsonValueSchema),
2364
+ z6.record(z6.string(), jsonValueSchema)
2106
2365
  ])
2107
- );
2108
- var formSubmitMetadataSchema = import_zod5.z.object({
2109
- form: import_zod5.z.object({
2110
- id: import_zod5.z.string(),
2111
- action: import_zod5.z.string().nullable(),
2112
- fields: import_zod5.z.array(
2113
- import_zod5.z.object({
2114
- name: import_zod5.z.string(),
2115
- type: import_zod5.z.string(),
2116
- label: import_zod5.z.string().nullable(),
2117
- value: jsonValueSchema
2118
- }).strict()
2119
- ).optional()
2120
- }).strict(),
2121
- page: import_zod5.z.object({
2122
- path: import_zod5.z.string()
2123
- }).strict()
2124
- }).strict();
2125
- var formSubmitConfigSchema = import_zod5.z.object({}).strict();
2366
+ ).register(z6.globalRegistry, { id: "JsonSerializableValue" });
2367
+ var formSubmitMetadataSchema = z6.strictObject({
2368
+ form: z6.strictObject({
2369
+ id: z6.string(),
2370
+ action: z6.nullable(z6.string()),
2371
+ fields: z6.optional(
2372
+ z6.array(
2373
+ z6.strictObject({
2374
+ name: z6.string(),
2375
+ type: z6.string(),
2376
+ label: z6.nullable(z6.string()),
2377
+ value: jsonValueSchema
2378
+ })
2379
+ )
2380
+ )
2381
+ }),
2382
+ page: z6.strictObject({
2383
+ path: z6.string()
2384
+ })
2385
+ });
2386
+ var formSubmitConfigSchema = z6.strictObject({});
2126
2387
 
2127
2388
  // ../tracking-core/src/events/multi-page-session.ts
2128
- var import_zod6 = require("zod");
2129
- var multiPageSessionMetadataSchema = import_zod6.z.object({
2130
- page_count: import_zod6.z.number().int().min(2),
2131
- page: import_zod6.z.object({
2132
- path: import_zod6.z.string()
2133
- }).strict()
2134
- }).strict();
2135
- var multiPageSessionConfigSchema = import_zod6.z.object({
2136
- pageThreshold: import_zod6.z.number().int().min(2)
2137
- }).strict();
2389
+ var z7 = __toESM(require("zod/mini"));
2390
+ var multiPageSessionMetadataSchema = z7.strictObject({
2391
+ page_count: z7.int().check(z7.gte(2)),
2392
+ page: z7.strictObject({
2393
+ path: z7.string()
2394
+ })
2395
+ });
2396
+ var multiPageSessionConfigSchema = z7.strictObject({
2397
+ pageThreshold: z7.int().check(z7.gte(2))
2398
+ });
2138
2399
 
2139
2400
  // ../tracking-core/src/events/page-exit.ts
2140
- var import_zod7 = require("zod");
2141
- var pageExitMetadataSchema = import_zod7.z.object({
2142
- dwell_ms: import_zod7.z.number().int().min(0),
2401
+ var z8 = __toESM(require("zod/mini"));
2402
+ var pageExitMetadataSchema = z8.strictObject({
2403
+ dwell_ms: z8.int().check(z8.gte(0)),
2143
2404
  // null = left without any scroll signal; floor is 0 so a valid 0% is never
2144
2405
  // rejected (a single bad field 422s the whole keepalive beacon batch).
2145
- max_scroll_percent: import_zod7.z.number().int().min(0).max(100).nullable(),
2406
+ max_scroll_percent: z8.nullable(z8.int().check(z8.gte(0), z8.lte(100))),
2146
2407
  // The gating baseline: the fraction of the page visible at load with
2147
2408
  // zero scrolling — or, for pages that grew after the post-paint snapshot
2148
2409
  // (skeleton/streaming renders), the first-scroll position that
2149
2410
  // established it. null = page had no scrollable range, or the segment
2150
- // ended before the snapshot. `.optional()` is load-bearing: SDK builds
2411
+ // ended before the snapshot. Optionality is load-bearing: SDK builds
2151
2412
  // predating this field keep POSTing page_exit without the key —
2152
2413
  // requiring it would 422 whole keepalive beacon batches.
2153
- scroll_baseline_percent: import_zod7.z.number().int().min(0).max(100).nullable().optional(),
2154
- page: import_zod7.z.object({
2155
- path: import_zod7.z.string()
2156
- }).strict()
2157
- }).strict();
2158
- var pageExitConfigSchema = import_zod7.z.object({}).strict();
2414
+ scroll_baseline_percent: z8.optional(z8.nullable(z8.int().check(z8.gte(0), z8.lte(100)))),
2415
+ page: z8.strictObject({
2416
+ path: z8.string()
2417
+ })
2418
+ });
2419
+ var pageExitConfigSchema = z8.strictObject({});
2159
2420
 
2160
2421
  // ../tracking-core/src/events/phone-click.ts
2161
- var import_zod8 = require("zod");
2162
- var phoneClickMetadataSchema = import_zod8.z.object({
2163
- phone_number: import_zod8.z.string(),
2164
- page: import_zod8.z.object({
2165
- path: import_zod8.z.string()
2166
- }).strict(),
2167
- section: import_zod8.z.string().nullable().optional()
2168
- }).strict();
2169
- var phoneClickConfigSchema = import_zod8.z.object({
2170
- autoCapture: import_zod8.z.object({
2171
- selector: import_zod8.z.string().optional()
2172
- }).strict().optional()
2173
- }).strict();
2422
+ var z9 = __toESM(require("zod/mini"));
2423
+ var phoneClickMetadataSchema = z9.strictObject({
2424
+ phone_number: z9.string(),
2425
+ page: z9.strictObject({
2426
+ path: z9.string()
2427
+ }),
2428
+ section: z9.optional(z9.nullable(z9.string()))
2429
+ });
2430
+ var phoneClickConfigSchema = z9.strictObject({
2431
+ autoCapture: z9.optional(
2432
+ z9.strictObject({
2433
+ selector: z9.optional(z9.string())
2434
+ })
2435
+ )
2436
+ });
2174
2437
 
2175
2438
  // ../tracking-core/src/events/scroll-depth.ts
2176
- var import_zod9 = require("zod");
2177
- var scrollDepthMetadataSchema = import_zod9.z.object({
2178
- depth_percent: import_zod9.z.number().int().min(1).max(100),
2179
- page: import_zod9.z.object({
2180
- path: import_zod9.z.string()
2181
- }).strict()
2182
- }).strict();
2183
- var scrollDepthConfigSchema = import_zod9.z.object({
2184
- thresholds: import_zod9.z.array(import_zod9.z.number().int().min(1).max(100)).min(1)
2185
- }).strict();
2439
+ var z10 = __toESM(require("zod/mini"));
2440
+ var scrollDepthMetadataSchema = z10.strictObject({
2441
+ depth_percent: z10.int().check(z10.gte(1), z10.lte(100)),
2442
+ page: z10.strictObject({
2443
+ path: z10.string()
2444
+ })
2445
+ });
2446
+ var scrollDepthConfigSchema = z10.strictObject({
2447
+ thresholds: z10.array(z10.int().check(z10.gte(1), z10.lte(100))).check(z10.minLength(1))
2448
+ });
2186
2449
 
2187
2450
  // ../tracking-core/src/events/specific-page-visit.ts
2188
- var import_zod10 = require("zod");
2451
+ var z11 = __toESM(require("zod/mini"));
2189
2452
  var PAGE_IDENTITY_MAX_LENGTH = 64;
2190
2453
  var PAGE_IDENTITY_PATTERN = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
2191
- var pageIdentitySchema = import_zod10.z.string().max(PAGE_IDENTITY_MAX_LENGTH).regex(PAGE_IDENTITY_PATTERN, {
2192
- message: "page identity must be lowercase snake_case, start with a letter, and contain at most 64 characters"
2193
- });
2454
+ var pageIdentitySchema = z11.string().check(
2455
+ z11.maxLength(PAGE_IDENTITY_MAX_LENGTH),
2456
+ z11.regex(PAGE_IDENTITY_PATTERN, {
2457
+ message: "page identity must be lowercase snake_case, start with a letter, and contain at most 64 characters"
2458
+ })
2459
+ );
2194
2460
  var specificPageNameSchema = pageIdentitySchema;
2195
- var specificPageVisitMetadataSchema = import_zod10.z.object({
2461
+ var specificPageVisitMetadataSchema = z11.strictObject({
2196
2462
  page_name: specificPageNameSchema,
2197
- page: import_zod10.z.object({
2198
- path: import_zod10.z.string()
2199
- }).strict()
2200
- }).strict();
2201
- var specificPageVisitConfigSchema = import_zod10.z.object({
2202
- pages: import_zod10.z.array(
2203
- import_zod10.z.object({
2463
+ page: z11.strictObject({
2464
+ path: z11.string()
2465
+ })
2466
+ });
2467
+ var specificPageVisitConfigSchema = z11.strictObject({
2468
+ pages: z11.array(
2469
+ z11.strictObject({
2204
2470
  name: specificPageNameSchema,
2205
- pathPattern: import_zod10.z.custom((value) => value instanceof RegExp, {
2471
+ pathPattern: z11.custom((value) => value instanceof RegExp, {
2206
2472
  message: "pathPattern must be a RegExp"
2207
2473
  })
2208
- }).strict()
2209
- ).min(1)
2210
- }).strict();
2474
+ })
2475
+ ).check(z11.minLength(1))
2476
+ });
2477
+
2478
+ // ../tracking-core/src/events/site-link.ts
2479
+ var z12 = __toESM(require("zod/mini"));
2480
+ var siteLinkClickMetadataSchema = z12.strictObject({
2481
+ destination_host: z12.string(),
2482
+ destination_url: z12.string(),
2483
+ page: z12.strictObject({
2484
+ path: z12.string()
2485
+ })
2486
+ });
2487
+ var siteLinkClickConfigSchema = z12.strictObject({});
2488
+ var siteLinkArrivalMetadataSchema = z12.strictObject({
2489
+ outcome: z12.enum(["linked", "stale", "invalid"]),
2490
+ origin_session_id: z12.optional(z12.nullable(z12.string())),
2491
+ origin_visitor_id: z12.optional(z12.nullable(z12.string())),
2492
+ // Milliseconds between the origin click and this page load.
2493
+ transit_ms: z12.optional(z12.nullable(z12.number())),
2494
+ // True when this browser had no visitor id and took the origin's.
2495
+ adopted_visitor: z12.boolean(),
2496
+ page: z12.strictObject({
2497
+ path: z12.string()
2498
+ })
2499
+ });
2500
+ var siteLinkArrivalConfigSchema = z12.strictObject({});
2211
2501
 
2212
2502
  // ../tracking-core/src/events/time-on-site.ts
2213
- var import_zod11 = require("zod");
2214
- var timeOnSiteMetadataSchema = import_zod11.z.object({
2215
- duration_ms: import_zod11.z.number().int().nonnegative(),
2216
- page: import_zod11.z.object({
2217
- path: import_zod11.z.string()
2218
- }).strict()
2219
- }).strict();
2220
- var timeOnSiteConfigSchema = import_zod11.z.object({
2221
- thresholdSeconds: import_zod11.z.number().int().positive()
2222
- }).strict();
2503
+ var z13 = __toESM(require("zod/mini"));
2504
+ var timeOnSiteMetadataSchema = z13.strictObject({
2505
+ duration_ms: z13.int().check(z13.nonnegative()),
2506
+ page: z13.strictObject({
2507
+ path: z13.string()
2508
+ })
2509
+ });
2510
+ var timeOnSiteConfigSchema = z13.strictObject({
2511
+ thresholdSeconds: z13.int().check(z13.positive())
2512
+ });
2223
2513
 
2224
2514
  // ../tracking-core/src/events/semantics.ts
2225
2515
  var EVENT_SEMANTICS = {
@@ -2288,6 +2578,18 @@ var EVENT_SEMANTICS = {
2288
2578
  category: "engagement",
2289
2579
  outcomeRole: "engagement",
2290
2580
  clientVisibility: "simple"
2581
+ },
2582
+ site_link_click: {
2583
+ label: "Left for sibling site",
2584
+ category: "page",
2585
+ outcomeRole: "navigation",
2586
+ clientVisibility: "detailed"
2587
+ },
2588
+ site_link_arrival: {
2589
+ label: "Arrived from sibling site",
2590
+ category: "page",
2591
+ outcomeRole: "navigation",
2592
+ clientVisibility: "simple"
2291
2593
  }
2292
2594
  };
2293
2595
 
@@ -2343,6 +2645,20 @@ var EVENT_REGISTRY = {
2343
2645
  metadataSchema: pageExitMetadataSchema,
2344
2646
  configSchema: pageExitConfigSchema
2345
2647
  },
2648
+ // Cross-site linkage: emitted by the linker (roster comes from the site
2649
+ // config, not the trigger registry), so neither is consumer-configurable.
2650
+ site_link_click: {
2651
+ kind: "automatic",
2652
+ semantics: EVENT_SEMANTICS.site_link_click,
2653
+ metadataSchema: siteLinkClickMetadataSchema,
2654
+ configSchema: siteLinkClickConfigSchema
2655
+ },
2656
+ site_link_arrival: {
2657
+ kind: "automatic",
2658
+ semantics: EVENT_SEMANTICS.site_link_arrival,
2659
+ metadataSchema: siteLinkArrivalMetadataSchema,
2660
+ configSchema: siteLinkArrivalConfigSchema
2661
+ },
2346
2662
  // --- manual triggers ---
2347
2663
  form_submit: {
2348
2664
  kind: "manual",
@@ -2378,7 +2694,7 @@ function createTypedClient(raw, registry, options = {}) {
2378
2694
  trackEvent(eventType, metadata, opts) {
2379
2695
  if (debug) {
2380
2696
  const def = getEventDefinition(eventType);
2381
- def.metadataSchema.parse(metadata);
2697
+ (0, import_mini2.parse)(def.metadataSchema, metadata);
2382
2698
  }
2383
2699
  raw.trackEvent({
2384
2700
  eventType,
@@ -2948,6 +3264,57 @@ function attachCtaClickCapture(client, config) {
2948
3264
  };
2949
3265
  }
2950
3266
 
3267
+ // ../tracking-core/src/triggers/site-link-capture.ts
3268
+ function anchorFrom(target) {
3269
+ if (!(target instanceof Element)) return null;
3270
+ const anchor = target.closest("a[href]");
3271
+ return anchor instanceof HTMLAnchorElement ? anchor : null;
3272
+ }
3273
+ function opensTheLink(event) {
3274
+ return event.button === 0 || event.button === 1;
3275
+ }
3276
+ function attachSiteLinkCapture(client, options) {
3277
+ if (typeof window === "undefined" || typeof document === "undefined") {
3278
+ return () => {
3279
+ };
3280
+ }
3281
+ registerCapability("cross_site_linker");
3282
+ function hopFrom(event) {
3283
+ const anchor = anchorFrom(event.target);
3284
+ if (anchor === null) return null;
3285
+ const link = resolveCrossSiteLink(anchor.href, options.linkedHostnames());
3286
+ return link === null ? null : { anchor, link };
3287
+ }
3288
+ function onPointerDown(event) {
3289
+ if (!opensTheLink(event)) return;
3290
+ const hop = hopFrom(event);
3291
+ if (hop !== null) hop.anchor.href = hop.link.decorated;
3292
+ }
3293
+ function onClick(event) {
3294
+ if (!opensTheLink(event)) return;
3295
+ const hop = hopFrom(event);
3296
+ if (hop === null) return;
3297
+ hop.anchor.href = hop.link.decorated;
3298
+ client.trackEvent({
3299
+ eventType: "site_link_click",
3300
+ metadata: {
3301
+ destination_host: hop.anchor.hostname,
3302
+ destination_url: stripLinkParam(hop.anchor.href),
3303
+ page: { path: window.location.pathname }
3304
+ },
3305
+ pageUrl: window.location.href,
3306
+ occurredAt: null
3307
+ });
3308
+ client.flushBeacon();
3309
+ }
3310
+ document.addEventListener("pointerdown", onPointerDown, true);
3311
+ document.addEventListener("click", onClick, true);
3312
+ return () => {
3313
+ document.removeEventListener("pointerdown", onPointerDown, true);
3314
+ document.removeEventListener("click", onClick, true);
3315
+ };
3316
+ }
3317
+
2951
3318
  // ../tracking-core/src/triggers/phone-click-capture.ts
2952
3319
  var DEFAULT_TEL_SELECTOR = 'a[href^="tel:"]';
2953
3320
  function safeDecodeURIComponent(value) {
@@ -3002,117 +3369,6 @@ function attachPhoneClickCapture(client, config) {
3002
3369
  };
3003
3370
  }
3004
3371
 
3005
- // ../tracking-core/src/resources/sales/schema.ts
3006
- var import_zod12 = require("zod");
3007
- var SUPPORTED_CURRENCIES = ["USD", "CAD"];
3008
- var TRACKING_ENVIRONMENTS = ["production", "development"];
3009
- var currencySchema = import_zod12.z.enum(SUPPORTED_CURRENCIES);
3010
- var centsSchema = import_zod12.z.number().int().nonnegative();
3011
- var quantitySchema = import_zod12.z.string().regex(/^\d+(\.\d{1,3})?$/);
3012
- var metadataSchema = import_zod12.z.record(import_zod12.z.unknown());
3013
- var saleItemSchema = import_zod12.z.object({
3014
- external_item_id: import_zod12.z.string().nullable().optional(),
3015
- name: import_zod12.z.string().nullable().optional(),
3016
- category: import_zod12.z.string().nullable().optional(),
3017
- quantity: quantitySchema,
3018
- unit_price_cents: centsSchema,
3019
- // Non-negativity validated on the wire — same contract as the other cents
3020
- // fields — and backstopped by the DB CHECK.
3021
- unit_cost_cents: centsSchema.nullable().optional()
3022
- }).strict();
3023
- var saleServiceSchema = import_zod12.z.object({
3024
- service: import_zod12.z.string(),
3025
- amount_cents: centsSchema
3026
- }).strict();
3027
- var customerNameSchema = import_zod12.z.string().max(200);
3028
- var customerPhoneSchema = import_zod12.z.string().max(64);
3029
- var customerEmailSchema = import_zod12.z.string().max(320).email();
3030
- function refineServiceXor(val, ctx, { requireAmount }) {
3031
- if (val.services != null) {
3032
- if (val.service != null) {
3033
- ctx.addIssue({
3034
- code: import_zod12.z.ZodIssueCode.custom,
3035
- message: "pass either `service` or `services`, not both",
3036
- path: ["services"]
3037
- });
3038
- }
3039
- if (val.services.length === 0) {
3040
- ctx.addIssue({
3041
- code: import_zod12.z.ZodIssueCode.custom,
3042
- message: "`services` must not be empty",
3043
- path: ["services"]
3044
- });
3045
- }
3046
- const keys = val.services.map((s) => s.service);
3047
- if (new Set(keys).size !== keys.length) {
3048
- ctx.addIssue({
3049
- code: import_zod12.z.ZodIssueCode.custom,
3050
- message: "`services` must not list the same service more than once",
3051
- path: ["services"]
3052
- });
3053
- }
3054
- if (val.amount_total_cents != null) {
3055
- const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
3056
- if (val.amount_total_cents !== sum) {
3057
- ctx.addIssue({
3058
- code: import_zod12.z.ZodIssueCode.custom,
3059
- message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
3060
- path: ["amount_total_cents"]
3061
- });
3062
- }
3063
- }
3064
- } else if (requireAmount && val.amount_total_cents == null) {
3065
- ctx.addIssue({
3066
- code: import_zod12.z.ZodIssueCode.custom,
3067
- message: "amount_total_cents is required unless `services` is provided",
3068
- path: ["amount_total_cents"]
3069
- });
3070
- }
3071
- }
3072
- var saleCreateSchema = import_zod12.z.object({
3073
- external_id: import_zod12.z.string().nullable().optional(),
3074
- description: import_zod12.z.string().nullable().optional(),
3075
- service: import_zod12.z.string().nullable().optional(),
3076
- services: import_zod12.z.array(saleServiceSchema).nullable().optional(),
3077
- currency: currencySchema,
3078
- // Optional only because the plural `services` form derives it from the sum
3079
- // (see refineServiceXor); the singular/serviceless path still requires it.
3080
- amount_total_cents: centsSchema.nullable().optional(),
3081
- occurred_at: import_zod12.z.string().datetime(),
3082
- environment: import_zod12.z.enum(TRACKING_ENVIRONMENTS).default("production"),
3083
- items: import_zod12.z.array(saleItemSchema).default([]),
3084
- metadata: metadataSchema.nullable().optional(),
3085
- customer_name: customerNameSchema.nullable().optional(),
3086
- customer_phone: customerPhoneSchema.nullable().optional(),
3087
- customer_email: customerEmailSchema.nullable().optional(),
3088
- // CASL consent attestation: the customer agreed to receive SMS. Recorded
3089
- // with a timestamp server-side; every SMS send path gates on it.
3090
- //
3091
- // Tri-state: omit it (or send null) to let the server apply the business's
3092
- // configured default-opt-in policy, honouring a returning customer's
3093
- // remembered preference. Send an explicit boolean to assert consent state
3094
- // yourself — `false` records a deliberate opt-out.
3095
- sms_consent: import_zod12.z.boolean().nullable().optional()
3096
- }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
3097
- var saleUpdateSchema = import_zod12.z.object({
3098
- description: import_zod12.z.string().nullable().optional(),
3099
- service: import_zod12.z.string().nullable().optional(),
3100
- services: import_zod12.z.array(saleServiceSchema).nullable().optional(),
3101
- currency: currencySchema.optional(),
3102
- amount_total_cents: centsSchema.optional(),
3103
- occurred_at: import_zod12.z.string().datetime().optional(),
3104
- items: import_zod12.z.array(saleItemSchema).optional(),
3105
- metadata: metadataSchema.nullable().optional(),
3106
- customer_name: customerNameSchema.nullable().optional(),
3107
- // Re-attest when changing a filler-looking name; the server clears the
3108
- // prior attestation whenever `customer_name` changes.
3109
- customer_name_placeholder_confirmed: import_zod12.z.boolean().optional(),
3110
- customer_phone: customerPhoneSchema.nullable().optional(),
3111
- customer_email: customerEmailSchema.nullable().optional(),
3112
- // NOT NULL server-side: omit to leave unchanged (explicit null is rejected).
3113
- sms_consent: import_zod12.z.boolean().optional()
3114
- }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: false }));
3115
-
3116
3372
  // src/hooks.ts
3117
3373
  function useGclid() {
3118
3374
  const [gclid, setGclid] = (0, import_react.useState)(null);
@@ -3444,7 +3700,7 @@ function GoogleAdsTracking(props) {
3444
3700
  var import_react6 = require("react");
3445
3701
 
3446
3702
  // package.json
3447
- var version = "0.27.0";
3703
+ var version = "1.1.0";
3448
3704
 
3449
3705
  // ../tracking-core/src/phone-react.tsx
3450
3706
  var import_react5 = require("react");
@@ -3567,7 +3823,8 @@ function createTracking(options) {
3567
3823
  // Still publish phone config so usePhoneField/<PhoneField> work even when
3568
3824
  // tracking is disabled (missing apiKey/endpoint).
3569
3825
  TrackingProvider: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PhoneConfigProvider, { value: phone ?? null, children }),
3570
- useTracking: () => noopTyped
3826
+ useTracking: () => noopTyped,
3827
+ decorateUrl: (url) => url
3571
3828
  };
3572
3829
  }
3573
3830
  const TrackingContext = (0, import_react6.createContext)(null);
@@ -3586,8 +3843,8 @@ function createTracking(options) {
3586
3843
  // eslint-disable-next-line react-hooks/exhaustive-deps -- gtagIdsKey is stable proxy
3587
3844
  [gtagId, gtagIdsKey]
3588
3845
  );
3589
- const rawClient = (0, import_react6.useMemo)(
3590
- () => getOrCreateTrackingClient({
3846
+ const rawClient = (0, import_react6.useMemo)(() => {
3847
+ const created = getOrCreateTrackingClient({
3591
3848
  apiKey,
3592
3849
  endpoint,
3593
3850
  surface: "react",
@@ -3598,9 +3855,10 @@ function createTracking(options) {
3598
3855
  activeGtagIds: resolvedGtagIds,
3599
3856
  configSiteId: trackingConfig?.siteId ?? null,
3600
3857
  debug
3601
- }),
3602
- [resolvedGtagIds]
3603
- );
3858
+ });
3859
+ stripLinkParamFromLocation();
3860
+ return created;
3861
+ }, [resolvedGtagIds]);
3604
3862
  const conversionStore = (0, import_react6.useMemo)(
3605
3863
  () => trackingConfig ? getTrackingConfigRuntime(trackingConfig) : conversionConfig ? resolveConversionConfig({
3606
3864
  cdnUrl: conversionConfig.cdnUrl,
@@ -3654,6 +3912,11 @@ function createTracking(options) {
3654
3912
  if (timeOnSite) {
3655
3913
  detachers.push(attachTimeOnSite(detectorClient, timeOnSite));
3656
3914
  }
3915
+ detachers.push(
3916
+ attachSiteLinkCapture(detectorClient, {
3917
+ linkedHostnames: () => conversionStore?.linkedHostnames() ?? []
3918
+ })
3919
+ );
3657
3920
  const specificPageVisit = triggers.automatic.specific_page_visit;
3658
3921
  if (specificPageVisit) {
3659
3922
  detachers.push(attachSpecificPageVisit(detectorClient, specificPageVisit));
@@ -3696,7 +3959,14 @@ function createTracking(options) {
3696
3959
  }
3697
3960
  return client;
3698
3961
  }
3699
- return { TrackingProvider, useTracking };
3962
+ return {
3963
+ TrackingProvider,
3964
+ useTracking,
3965
+ decorateUrl: (url) => decorateCrossSiteUrl(
3966
+ url,
3967
+ trackingConfig ? getTrackingConfigRuntime(trackingConfig).linkedHostnames() : []
3968
+ )
3969
+ };
3700
3970
  }
3701
3971
  // Annotate the CommonJS export names for ESM import in node:
3702
3972
  0 && (module.exports = {