@aranova/tracking-next 0.17.3 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -35,6 +35,7 @@ __export(src_exports, {
35
35
  ConsentBanner: () => ConsentBanner,
36
36
  DEFAULT_DECLINE_TTL_DAYS: () => DEFAULT_DECLINE_TTL_DAYS,
37
37
  DEFAULT_PHONE_COUNTRY: () => DEFAULT_PHONE_COUNTRY,
38
+ EVENT_SEMANTICS: () => EVENT_SEMANTICS,
38
39
  GoogleAdsTracking: () => GoogleAdsTracking,
39
40
  NAMED_RANGES: () => NAMED_RANGES,
40
41
  PhoneField: () => PhoneField,
@@ -55,6 +56,8 @@ __export(src_exports, {
55
56
  fromMinor: () => fromMinor,
56
57
  getConsentChoice: () => getConsentChoice,
57
58
  getConsentState: () => getConsentState,
59
+ getEventSemantics: () => getEventSemantics,
60
+ getTrackingConfigRuntime: () => getTrackingConfigRuntime,
58
61
  onConsentChange: () => onConsentChange,
59
62
  optIn: () => optIn,
60
63
  optOut: () => optOut,
@@ -302,6 +305,11 @@ function captureTrackingParamsFromLocation(url = typeof window === "undefined" ?
302
305
  }
303
306
 
304
307
  // ../tracking-core/src/gtag.ts
308
+ var GTAG_SCRIPT_HOST = "https://www.googletagmanager.com/gtag/js";
309
+ var TRACKING_SCRIPT_ATTRIBUTE = "data-aranova-tracking";
310
+ function getScriptMarker(id) {
311
+ return `aranova-${id}`;
312
+ }
305
313
  var GTAG_ID_PATTERN = /^[A-Z]{1,3}-[A-Za-z0-9_-]+$/;
306
314
  function isValidGtagId(id) {
307
315
  return GTAG_ID_PATTERN.test(id);
@@ -330,6 +338,15 @@ gtag('js', new Date());
330
338
  gtag('config', '${gtagId}');
331
339
  `.trim();
332
340
  }
341
+ function ensureGtagFunction() {
342
+ window.dataLayer = window.dataLayer || [];
343
+ if (typeof window.gtag === "function") return window.gtag;
344
+ function gtag() {
345
+ window.dataLayer?.push(arguments);
346
+ }
347
+ window.gtag = gtag;
348
+ return window.gtag;
349
+ }
333
350
  var SEND_TO_RE = /^AW-[A-Za-z0-9]+\/[A-Za-z0-9_-]+$/;
334
351
  function isValidSendTo(sendTo) {
335
352
  return SEND_TO_RE.test(sendTo);
@@ -348,6 +365,27 @@ function fireGtagConversion(input) {
348
365
  return false;
349
366
  }
350
367
  }
368
+ function applyDefaultConsentState() {
369
+ const gtag = ensureGtagFunction();
370
+ gtag(
371
+ "consent",
372
+ "default",
373
+ buildConsentPayload(getConsentState() === "denied" ? "denied" : "granted")
374
+ );
375
+ }
376
+ function loadGtagScript(gtagId) {
377
+ if (typeof document === "undefined") return;
378
+ const marker = getScriptMarker("gtag-loader");
379
+ const existingScript = document.querySelector(
380
+ `script[${TRACKING_SCRIPT_ATTRIBUTE}="${marker}"]`
381
+ );
382
+ if (existingScript) return;
383
+ const script = document.createElement("script");
384
+ script.async = true;
385
+ script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;
386
+ script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);
387
+ document.head.append(script);
388
+ }
351
389
  function createGtagInitScriptMulti(gtagIds) {
352
390
  const safeIds = gtagIds.filter(isValidGtagId);
353
391
  const configs = safeIds.map((id) => `gtag('config', '${id}');`).join("\n");
@@ -634,6 +672,7 @@ function parseConversionConfig(raw) {
634
672
  business_id: typeof obj.business_id === "string" ? obj.business_id : void 0,
635
673
  customer_id: typeof obj.customer_id === "string" ? obj.customer_id : null,
636
674
  environment: typeof obj.environment === "string" ? obj.environment : void 0,
675
+ google_tracking_state: obj.google_tracking_state === "active" ? "active" : "disabled",
637
676
  gtag_ids: isStringMap(obj.gtag_ids) ? obj.gtag_ids : {},
638
677
  meta_pixel_ids: isStringMap(obj.meta_pixel_ids) ? obj.meta_pixel_ids : {},
639
678
  services,
@@ -769,6 +808,283 @@ function formatDateInTz(iso, timeZone, opts, locale) {
769
808
  }).format(date);
770
809
  }
771
810
 
811
+ // ../tracking-core/src/resources/tracking-config-runtime.ts
812
+ var CACHE_PREFIX2 = "_aranova_cfg_runtime_";
813
+ var AUTHORITY_TTL_MS = 6e4;
814
+ var runtimes = /* @__PURE__ */ new Map();
815
+ var configuredIds = /* @__PURE__ */ new Set();
816
+ var scriptLoad = null;
817
+ var jsInitialized = false;
818
+ function cacheKey2(url) {
819
+ return `${CACHE_PREFIX2}${url}`;
820
+ }
821
+ function readCache2(url) {
822
+ if (typeof window === "undefined") return null;
823
+ try {
824
+ const raw = window.sessionStorage.getItem(cacheKey2(url));
825
+ if (!raw) return null;
826
+ const parsed = JSON.parse(raw);
827
+ const config = parseConversionConfig(parsed.config);
828
+ if (!config) return null;
829
+ return {
830
+ etag: typeof parsed.etag === "string" ? parsed.etag : null,
831
+ config
832
+ };
833
+ } catch {
834
+ return null;
835
+ }
836
+ }
837
+ function writeCache2(url, entry) {
838
+ if (typeof window === "undefined") return;
839
+ try {
840
+ window.sessionStorage.setItem(cacheKey2(url), JSON.stringify(entry));
841
+ } catch {
842
+ }
843
+ }
844
+ function isTombstone(config) {
845
+ return config.google_tracking_state === "disabled" || Object.keys(config.gtag_ids).length === 0;
846
+ }
847
+ function validateConfig(config, ref) {
848
+ return config.business_id === ref.businessId && config.environment === ref.environment && (config.google_tracking_state === "active" || config.google_tracking_state === "disabled");
849
+ }
850
+ function pageViewSnapshot() {
851
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
852
+ return {
853
+ href: window.location.href,
854
+ title: document.title || null,
855
+ referrer: document.referrer || null
856
+ };
857
+ }
858
+ function ensureScript(gtagId) {
859
+ if (typeof window === "undefined" || typeof document === "undefined") return Promise.resolve();
860
+ applyDefaultConsentState();
861
+ loadGtagScript(gtagId);
862
+ if (scriptLoad) return scriptLoad;
863
+ scriptLoad = new Promise((resolve) => {
864
+ const script = document.querySelector(
865
+ 'script[data-aranova-tracking="aranova-gtag-loader"]'
866
+ );
867
+ if (!script) {
868
+ resolve();
869
+ return;
870
+ }
871
+ if (script.dataset.loaded === "true") {
872
+ resolve();
873
+ return;
874
+ }
875
+ window.setTimeout(resolve, 0);
876
+ script.addEventListener(
877
+ "load",
878
+ () => {
879
+ script.dataset.loaded = "true";
880
+ resolve();
881
+ },
882
+ { once: true }
883
+ );
884
+ script.addEventListener("error", () => resolve(), { once: true });
885
+ });
886
+ return scriptLoad;
887
+ }
888
+ var TrackingConfigRuntime = class {
889
+ constructor(ref, fetchImpl = globalThis.fetch) {
890
+ this.ref = ref;
891
+ this.fetchImpl = fetchImpl;
892
+ this.current = null;
893
+ this.etag = null;
894
+ this.stateValue = "unconfirmed";
895
+ this.confirmedAt = 0;
896
+ this.inFlight = null;
897
+ this.conversionQueue = [];
898
+ this.automaticQueue = [];
899
+ this.pageQueue = [];
900
+ this.listeners = /* @__PURE__ */ new Set();
901
+ const cached = readCache2(ref.cdnUrl);
902
+ this.current = cached?.config ?? null;
903
+ this.etag = cached?.etag ?? null;
904
+ void this.revalidate();
905
+ if (typeof window !== "undefined") {
906
+ window.addEventListener("visibilitychange", () => {
907
+ if (document.visibilityState === "visible") void this.revalidate();
908
+ });
909
+ window.addEventListener("focus", () => void this.revalidate());
910
+ }
911
+ }
912
+ state() {
913
+ return this.stateValue;
914
+ }
915
+ config() {
916
+ return this.stateValue === "active" || this.stateValue === "tombstone" ? this.current : null;
917
+ }
918
+ __unsafeExpireAuthorityForTests() {
919
+ this.confirmedAt = 0;
920
+ }
921
+ subscribe(listener) {
922
+ this.listeners.add(listener);
923
+ return () => this.listeners.delete(listener);
924
+ }
925
+ async ensureAuthority() {
926
+ if (this.stateValue !== "unconfirmed" && Date.now() - this.confirmedAt < AUTHORITY_TTL_MS) {
927
+ return true;
928
+ }
929
+ await this.revalidate();
930
+ return this.stateValue !== "unconfirmed" && Date.now() - this.confirmedAt < AUTHORITY_TTL_MS;
931
+ }
932
+ async revalidate() {
933
+ if (typeof window === "undefined" || !this.fetchImpl) return;
934
+ if (this.inFlight) return this.inFlight;
935
+ this.inFlight = this.revalidateNow().finally(() => {
936
+ this.inFlight = null;
937
+ });
938
+ return this.inFlight;
939
+ }
940
+ queuePageView(snapshot = pageViewSnapshot()) {
941
+ if (!snapshot) return;
942
+ this.pageQueue.push(snapshot);
943
+ void this.flush();
944
+ }
945
+ fireConversion(key, options) {
946
+ this.conversionQueue.push({ key, ...options });
947
+ void this.flush();
948
+ }
949
+ queueAutomaticEvent(eventType, metadata, transactionPath) {
950
+ this.automaticQueue.push({ eventType, metadata, transactionPath });
951
+ void this.flush();
952
+ }
953
+ listGoals() {
954
+ return this.current?.goals ?? [];
955
+ }
956
+ async revalidateNow() {
957
+ try {
958
+ const headers = {};
959
+ if (this.etag) headers["If-None-Match"] = this.etag;
960
+ const response = await this.fetchImpl(this.ref.cdnUrl, {
961
+ method: "GET",
962
+ headers,
963
+ cache: "no-cache"
964
+ });
965
+ if (response.status === 304 && this.current && validateConfig(this.current, this.ref)) {
966
+ this.confirm(this.current, this.etag);
967
+ await this.flush();
968
+ return;
969
+ }
970
+ if (!response.ok) {
971
+ this.expireAuthority();
972
+ return;
973
+ }
974
+ const next = parseConversionConfig(await response.json());
975
+ if (!next || !validateConfig(next, this.ref)) {
976
+ this.expireAuthority();
977
+ return;
978
+ }
979
+ if (this.current && next.config_version < this.current.config_version) {
980
+ this.expireAuthority();
981
+ return;
982
+ }
983
+ this.confirm(next, response.headers.get("ETag"));
984
+ await this.flush();
985
+ } catch {
986
+ this.expireAuthority();
987
+ }
988
+ }
989
+ expireAuthority() {
990
+ if (this.stateValue !== "unconfirmed") this.stateValue = "unconfirmed";
991
+ }
992
+ confirm(config, etag) {
993
+ this.current = config;
994
+ this.etag = etag;
995
+ this.confirmedAt = Date.now();
996
+ this.stateValue = isTombstone(config) ? "tombstone" : "active";
997
+ writeCache2(this.ref.cdnUrl, { etag, config });
998
+ if (this.stateValue === "tombstone") {
999
+ this.conversionQueue.length = 0;
1000
+ this.automaticQueue.length = 0;
1001
+ this.pageQueue.length = 0;
1002
+ }
1003
+ for (const listener of this.listeners) listener();
1004
+ }
1005
+ async flush() {
1006
+ if (!await this.ensureAuthority()) return;
1007
+ if (this.stateValue !== "active" || !this.current) return;
1008
+ const ids = Object.values(this.current.gtag_ids).filter(
1009
+ (id) => typeof id === "string" && isValidGtagId(id)
1010
+ );
1011
+ if (ids.length === 0) return;
1012
+ await ensureScript(ids[0]);
1013
+ const gtag = window.gtag;
1014
+ if (typeof gtag !== "function") return;
1015
+ if (!jsInitialized) {
1016
+ gtag("js", /* @__PURE__ */ new Date());
1017
+ jsInitialized = true;
1018
+ }
1019
+ for (const id of ids) {
1020
+ if (configuredIds.has(id)) continue;
1021
+ gtag("config", id, { send_page_view: false });
1022
+ configuredIds.add(id);
1023
+ }
1024
+ while (this.pageQueue.length) {
1025
+ const page = this.pageQueue.shift();
1026
+ gtag("event", "page_view", {
1027
+ page_location: page.href,
1028
+ page_title: page.title ?? void 0,
1029
+ page_referrer: page.referrer ?? void 0
1030
+ });
1031
+ }
1032
+ while (this.automaticQueue.length) {
1033
+ const event = this.automaticQueue.shift();
1034
+ for (const goal of this.current.goals) {
1035
+ if (goal.kind !== "event" || !goal.firing) continue;
1036
+ if (!automaticThresholdMet(goal, event.eventType, event.metadata)) continue;
1037
+ this.conversionQueue.push({
1038
+ key: goal.key,
1039
+ transactionId: `auto:${goal.key}:${event.transactionPath}`
1040
+ });
1041
+ }
1042
+ }
1043
+ while (this.conversionQueue.length) {
1044
+ const item = this.conversionQueue.shift();
1045
+ const goal = this.current.goals.find((g) => g.key === item.key);
1046
+ const firing = goal?.firing;
1047
+ if (!firing || getConsentState() === "denied") continue;
1048
+ const currency = item.currency ?? firing.currency ?? null;
1049
+ const value = item.value ?? (firing.value_cents != null && currency ? fromMinor(firing.value_cents, currency) : null);
1050
+ fireGtagConversion({
1051
+ sendTo: firing.send_to,
1052
+ value,
1053
+ currency,
1054
+ transactionId: item.transactionId ?? null
1055
+ });
1056
+ }
1057
+ }
1058
+ };
1059
+ function automaticThresholdMet(goal, eventType, metadata) {
1060
+ const t = goal.trigger;
1061
+ if (!t || t.event_type !== eventType) return false;
1062
+ switch (eventType) {
1063
+ case "scroll_depth":
1064
+ return typeof metadata.depth_percent === "number" && t.threshold_percent != null && metadata.depth_percent >= t.threshold_percent;
1065
+ case "time_on_site":
1066
+ return typeof metadata.duration_ms === "number" && t.threshold_seconds != null && metadata.duration_ms >= t.threshold_seconds * 1e3;
1067
+ case "multi_page_session":
1068
+ return typeof metadata.page_count === "number" && t.page_threshold != null && metadata.page_count >= t.page_threshold;
1069
+ case "specific_page_visit":
1070
+ return typeof metadata.page_name === "string" && metadata.page_name === t.page_name;
1071
+ case "page_view":
1072
+ case "form_start":
1073
+ case "phone_click":
1074
+ return true;
1075
+ default:
1076
+ return false;
1077
+ }
1078
+ }
1079
+ function getTrackingConfigRuntime(ref, fetchImpl) {
1080
+ const key = `${ref.cdnUrl}|${ref.businessId}|${ref.environment}`;
1081
+ const existing = runtimes.get(key);
1082
+ if (existing) return existing;
1083
+ const runtime = new TrackingConfigRuntime(ref, fetchImpl ?? globalThis.fetch);
1084
+ runtimes.set(key, runtime);
1085
+ return runtime;
1086
+ }
1087
+
772
1088
  // ../tracking-core/src/resources/conversion-autofire.ts
773
1089
  function thresholdMet(goal, eventType, metadata) {
774
1090
  const t = goal.trigger;
@@ -801,6 +1117,12 @@ function createConversionAutoFire(store) {
801
1117
  for (const goal of store.listGoals()) {
802
1118
  if (goal.kind !== "event" || !goal.firing) continue;
803
1119
  if (!thresholdMet(goal, eventType, metadata)) continue;
1120
+ if ("queueAutomaticEvent" in store) {
1121
+ store.fireConversion(goal.key, {
1122
+ transactionId: `auto:${goal.key}:${currentPath()}`
1123
+ });
1124
+ continue;
1125
+ }
804
1126
  const firing = goal.firing;
805
1127
  const cents = firing.value_cents ?? null;
806
1128
  const currency = firing.currency ?? null;
@@ -816,6 +1138,10 @@ function createConversionAutoFire(store) {
816
1138
  }
817
1139
  return {
818
1140
  onAutomaticEvent(eventType, metadata) {
1141
+ if ("queueAutomaticEvent" in store) {
1142
+ store.queueAutomaticEvent(eventType, metadata, currentPath());
1143
+ return;
1144
+ }
819
1145
  if (store.isReady()) {
820
1146
  fireMatching(eventType, metadata);
821
1147
  return;
@@ -1495,69 +1821,154 @@ var timeOnSiteConfigSchema = import_zod11.z.object({
1495
1821
  thresholdSeconds: import_zod11.z.number().int().positive()
1496
1822
  }).strict();
1497
1823
 
1824
+ // ../tracking-core/src/events/semantics.ts
1825
+ var EVENT_SEMANTICS = {
1826
+ page_view: {
1827
+ label: "Page view",
1828
+ category: "page",
1829
+ outcomeRole: "navigation",
1830
+ clientVisibility: "simple"
1831
+ },
1832
+ time_on_site: {
1833
+ label: "Time on site",
1834
+ category: "engagement",
1835
+ outcomeRole: "engagement",
1836
+ clientVisibility: "detailed"
1837
+ },
1838
+ specific_page_visit: {
1839
+ label: "Key page visit",
1840
+ category: "engagement",
1841
+ outcomeRole: "engagement",
1842
+ clientVisibility: "detailed"
1843
+ },
1844
+ scroll_depth: {
1845
+ label: "Scroll depth",
1846
+ category: "engagement",
1847
+ outcomeRole: "engagement",
1848
+ clientVisibility: "detailed"
1849
+ },
1850
+ multi_page_session: {
1851
+ label: "Multi-page session",
1852
+ category: "engagement",
1853
+ outcomeRole: "engagement",
1854
+ clientVisibility: "detailed"
1855
+ },
1856
+ form_start: {
1857
+ label: "Form started",
1858
+ category: "engagement",
1859
+ outcomeRole: "engagement",
1860
+ clientVisibility: "simple"
1861
+ },
1862
+ sdk_heartbeat: {
1863
+ label: "SDK heartbeat",
1864
+ category: "system",
1865
+ outcomeRole: "diagnostic",
1866
+ clientVisibility: "hidden"
1867
+ },
1868
+ page_exit: {
1869
+ label: "Page exit",
1870
+ category: "engagement",
1871
+ outcomeRole: "diagnostic",
1872
+ clientVisibility: "detailed"
1873
+ },
1874
+ form_submit: {
1875
+ label: "Form submitted",
1876
+ category: "lead",
1877
+ outcomeRole: "lead",
1878
+ clientVisibility: "simple"
1879
+ },
1880
+ phone_click: {
1881
+ label: "Phone click",
1882
+ category: "lead",
1883
+ outcomeRole: "lead",
1884
+ clientVisibility: "simple"
1885
+ },
1886
+ cta_click: {
1887
+ label: "CTA click",
1888
+ category: "engagement",
1889
+ outcomeRole: "engagement",
1890
+ clientVisibility: "simple"
1891
+ }
1892
+ };
1893
+ function getEventSemantics(eventName) {
1894
+ return eventName in EVENT_SEMANTICS ? EVENT_SEMANTICS[eventName] : null;
1895
+ }
1896
+
1498
1897
  // ../tracking-core/src/events/registry.ts
1499
1898
  var EVENT_REGISTRY = {
1500
1899
  // --- automatic triggers ---
1501
1900
  page_view: {
1502
1901
  kind: "automatic",
1902
+ semantics: EVENT_SEMANTICS.page_view,
1503
1903
  metadataSchema: pageViewMetadataSchema,
1504
1904
  configSchema: pageViewConfigSchema
1505
1905
  },
1506
1906
  time_on_site: {
1507
1907
  kind: "automatic",
1908
+ semantics: EVENT_SEMANTICS.time_on_site,
1508
1909
  metadataSchema: timeOnSiteMetadataSchema,
1509
1910
  configSchema: timeOnSiteConfigSchema
1510
1911
  },
1511
1912
  specific_page_visit: {
1512
1913
  kind: "automatic",
1914
+ semantics: EVENT_SEMANTICS.specific_page_visit,
1513
1915
  metadataSchema: specificPageVisitMetadataSchema,
1514
1916
  configSchema: specificPageVisitConfigSchema
1515
1917
  },
1516
1918
  scroll_depth: {
1517
1919
  kind: "automatic",
1920
+ semantics: EVENT_SEMANTICS.scroll_depth,
1518
1921
  metadataSchema: scrollDepthMetadataSchema,
1519
1922
  configSchema: scrollDepthConfigSchema
1520
1923
  },
1521
1924
  multi_page_session: {
1522
1925
  kind: "automatic",
1926
+ semantics: EVENT_SEMANTICS.multi_page_session,
1523
1927
  metadataSchema: multiPageSessionMetadataSchema,
1524
1928
  configSchema: multiPageSessionConfigSchema
1525
1929
  },
1526
1930
  form_start: {
1527
1931
  kind: "automatic",
1932
+ semantics: EVENT_SEMANTICS.form_start,
1528
1933
  metadataSchema: formStartMetadataSchema,
1529
1934
  configSchema: formStartConfigSchema
1530
1935
  },
1531
1936
  // --- SDK-internal automatic (not consumer-configurable) ---
1532
1937
  sdk_heartbeat: {
1533
1938
  kind: "automatic",
1939
+ semantics: EVENT_SEMANTICS.sdk_heartbeat,
1534
1940
  metadataSchema: sdkHeartbeatMetadataSchema,
1535
1941
  configSchema: sdkHeartbeatConfigSchema
1536
1942
  },
1537
1943
  page_exit: {
1538
1944
  kind: "automatic",
1945
+ semantics: EVENT_SEMANTICS.page_exit,
1539
1946
  metadataSchema: pageExitMetadataSchema,
1540
1947
  configSchema: pageExitConfigSchema
1541
1948
  },
1542
1949
  // --- manual triggers ---
1543
1950
  form_submit: {
1544
1951
  kind: "manual",
1952
+ semantics: EVENT_SEMANTICS.form_submit,
1545
1953
  metadataSchema: formSubmitMetadataSchema,
1546
1954
  configSchema: formSubmitConfigSchema
1547
1955
  },
1548
1956
  phone_click: {
1549
1957
  kind: "manual",
1958
+ semantics: EVENT_SEMANTICS.phone_click,
1550
1959
  metadataSchema: phoneClickMetadataSchema,
1551
1960
  configSchema: phoneClickConfigSchema
1552
1961
  },
1553
1962
  cta_click: {
1554
1963
  kind: "manual",
1964
+ semantics: EVENT_SEMANTICS.cta_click,
1555
1965
  metadataSchema: ctaClickMetadataSchema,
1556
1966
  configSchema: ctaClickConfigSchema
1557
1967
  }
1558
1968
  };
1559
1969
  var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
1560
1970
  var ALL_MANUAL_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "manual").map(([name]) => name);
1971
+ var ALL_LEAD_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.semantics.outcomeRole === "lead").map(([name]) => name);
1561
1972
  function getEventDefinition(name) {
1562
1973
  return EVENT_REGISTRY[name];
1563
1974
  }
@@ -2155,6 +2566,14 @@ function fireRecordedConversions(firing, input, recorded, sale, currency) {
2155
2566
  const txnBase = input.external_id ?? sale.id;
2156
2567
  for (const item of recorded) {
2157
2568
  if (!item.service) continue;
2569
+ if ("fireConversion" in firing) {
2570
+ firing.fireConversion(item.service, {
2571
+ value: item.amount_cents != null ? fromMinor(item.amount_cents, currency) : void 0,
2572
+ currency,
2573
+ transactionId: `${txnBase}:${item.service}`
2574
+ });
2575
+ continue;
2576
+ }
2158
2577
  const config = firing.getFiring(item.service);
2159
2578
  if (!config) continue;
2160
2579
  const cents = item.amount_cents ?? config.value_cents ?? null;
@@ -2197,6 +2616,14 @@ function createSalesClient(config) {
2197
2616
  // recordSale is the intent-revealing alias — same behavior, clearer call site.
2198
2617
  recordSale: record,
2199
2618
  trackConversion(key, options) {
2619
+ if (config.firing && "fireConversion" in config.firing) {
2620
+ config.firing.fireConversion(key, {
2621
+ value: options?.value ?? void 0,
2622
+ currency: options?.currency ?? config.defaultCurrency ?? void 0,
2623
+ transactionId: options?.transactionId ?? null
2624
+ });
2625
+ return;
2626
+ }
2200
2627
  const firing = config.firing?.getFiring(key);
2201
2628
  if (!firing) return;
2202
2629
  const currency = firing.currency ?? options?.currency ?? config.defaultCurrency ?? null;
@@ -2702,14 +3129,24 @@ var ANIMATION_KEYFRAMES = `
2702
3129
 
2703
3130
  // src/AdPlatformTracking.tsx
2704
3131
  var import_script = __toESM(require("next/script"));
3132
+ var import_react3 = require("react");
2705
3133
  var import_jsx_runtime2 = require("react/jsx-runtime");
2706
3134
  function AdPlatformTracking({
2707
3135
  gtagId,
2708
3136
  gtagIds,
3137
+ trackingConfig,
3138
+ standalonePageView = false,
2709
3139
  metaPixelId,
2710
3140
  metaPixelIds
2711
3141
  }) {
2712
- const gtagAll = gtagIds ? Object.values(gtagIds).filter((id) => id != null) : gtagId ? [gtagId] : [];
3142
+ const trackingConfigKey = trackingConfig ? `${trackingConfig.cdnUrl}:${trackingConfig.businessId}:${trackingConfig.environment}` : "";
3143
+ (0, import_react3.useEffect)(() => {
3144
+ if (!trackingConfig) return;
3145
+ const runtime = getTrackingConfigRuntime(trackingConfig);
3146
+ if (standalonePageView) runtime.queuePageView();
3147
+ else void runtime.revalidate();
3148
+ }, [trackingConfigKey, standalonePageView]);
3149
+ const gtagAll = trackingConfig ? [] : gtagIds ? Object.values(gtagIds).filter((id) => id != null) : gtagId ? [gtagId] : [];
2713
3150
  const metaAll = metaPixelIds ? Object.values(metaPixelIds).filter((id) => id != null) : metaPixelId ? [metaPixelId] : [];
2714
3151
  const gtagLoaderId = gtagAll[0];
2715
3152
  const gtagInitScript = gtagAll.length === 1 ? createGtagInitScript(gtagAll[0]) : createGtagInitScriptMulti(gtagAll);
@@ -2759,17 +3196,17 @@ function GoogleAdsTracking(props) {
2759
3196
  var import_navigation3 = require("next/navigation");
2760
3197
 
2761
3198
  // package.json
2762
- var version = "0.17.3";
3199
+ var version = "0.18.1";
2763
3200
 
2764
3201
  // src/factory.tsx
2765
- var import_react4 = require("react");
3202
+ var import_react5 = require("react");
2766
3203
 
2767
3204
  // ../tracking-core/src/phone-react.tsx
2768
- var import_react3 = require("react");
3205
+ var import_react4 = require("react");
2769
3206
  var import_jsx_runtime4 = require("react/jsx-runtime");
2770
3207
  var _phoneConfigContext;
2771
3208
  function phoneConfigContext() {
2772
- return _phoneConfigContext ?? (_phoneConfigContext = (0, import_react3.createContext)(null));
3209
+ return _phoneConfigContext ?? (_phoneConfigContext = (0, import_react4.createContext)(null));
2773
3210
  }
2774
3211
  function PhoneConfigProvider({
2775
3212
  value,
@@ -2779,7 +3216,7 @@ function PhoneConfigProvider({
2779
3216
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Ctx.Provider, { value, children });
2780
3217
  }
2781
3218
  function usePhoneConfig() {
2782
- const ctx = (0, import_react3.useContext)(phoneConfigContext());
3219
+ const ctx = (0, import_react4.useContext)(phoneConfigContext());
2783
3220
  return {
2784
3221
  defaultCountry: ctx?.defaultCountry ?? DEFAULT_PHONE_COUNTRY,
2785
3222
  display: ctx?.display ?? "national"
@@ -2790,10 +3227,10 @@ function usePhoneField(opts = {}) {
2790
3227
  const country = opts.country ?? cfg.defaultCountry;
2791
3228
  const display = opts.display ?? cfg.display;
2792
3229
  const { onValueChange } = opts;
2793
- const [value, setValue] = (0, import_react3.useState)(() => formatPhoneAsTyped(opts.defaultValue ?? "", country));
2794
- const [touched, setTouched] = (0, import_react3.useState)(false);
2795
- const parsed = (0, import_react3.useMemo)(() => parsePhone(value, country), [value, country]);
2796
- const onChange = (0, import_react3.useCallback)(
3230
+ const [value, setValue] = (0, import_react4.useState)(() => formatPhoneAsTyped(opts.defaultValue ?? "", country));
3231
+ const [touched, setTouched] = (0, import_react4.useState)(false);
3232
+ const parsed = (0, import_react4.useMemo)(() => parsePhone(value, country), [value, country]);
3233
+ const onChange = (0, import_react4.useCallback)(
2797
3234
  (event) => {
2798
3235
  const next = formatPhoneAsTyped(event.target.value, country);
2799
3236
  setValue(next);
@@ -2801,7 +3238,7 @@ function usePhoneField(opts = {}) {
2801
3238
  },
2802
3239
  [country, onValueChange]
2803
3240
  );
2804
- const onBlur = (0, import_react3.useCallback)(
3241
+ const onBlur = (0, import_react4.useCallback)(
2805
3242
  (_event) => {
2806
3243
  setTouched(true);
2807
3244
  setValue((current) => {
@@ -2821,11 +3258,11 @@ function usePhoneField(opts = {}) {
2821
3258
  inputProps: { value, onChange, onBlur, type: "tel", inputMode: "tel", autoComplete: "tel" }
2822
3259
  };
2823
3260
  }
2824
- var PhoneField = (0, import_react3.forwardRef)(function PhoneField2({ country, value, defaultValue, onChange, onE164Change, ...rest }, ref) {
3261
+ var PhoneField = (0, import_react4.forwardRef)(function PhoneField2({ country, value, defaultValue, onChange, onE164Change, ...rest }, ref) {
2825
3262
  const cfg = usePhoneConfig();
2826
3263
  const resolvedCountry = country ?? cfg.defaultCountry;
2827
3264
  const isControlled = value !== void 0;
2828
- const [internal, setInternal] = (0, import_react3.useState)(
3265
+ const [internal, setInternal] = (0, import_react4.useState)(
2829
3266
  () => formatPhoneAsTyped(defaultValue ?? "", resolvedCountry)
2830
3267
  );
2831
3268
  const handleChange = (event) => {
@@ -2855,13 +3292,23 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
2855
3292
  function PageViewTracker({ fire }) {
2856
3293
  const pathname = (0, import_navigation3.usePathname)();
2857
3294
  const searchParams = (0, import_navigation3.useSearchParams)();
2858
- (0, import_react4.useEffect)(() => {
3295
+ (0, import_react5.useEffect)(() => {
2859
3296
  fire();
2860
3297
  }, [pathname, searchParams, fire]);
2861
3298
  return null;
2862
3299
  }
2863
3300
  function createTracking(options) {
2864
- const { apiKey, endpoint, triggers, environment, gtagIds, debug, phone, conversionConfig } = options;
3301
+ const {
3302
+ apiKey,
3303
+ endpoint,
3304
+ triggers,
3305
+ environment,
3306
+ gtagIds,
3307
+ debug,
3308
+ phone,
3309
+ conversionConfig,
3310
+ trackingConfig
3311
+ } = options;
2865
3312
  if (!apiKey || !endpoint) {
2866
3313
  if (apiKey || endpoint) {
2867
3314
  console.warn(
@@ -2881,12 +3328,12 @@ function createTracking(options) {
2881
3328
  useTracking: () => noopClient
2882
3329
  };
2883
3330
  }
2884
- const TrackingContext = (0, import_react4.createContext)(null);
3331
+ const TrackingContext = (0, import_react5.createContext)(null);
2885
3332
  const specificPageVisitConfig = triggers.automatic.specific_page_visit ?? null;
2886
3333
  const timeOnSiteConfig = triggers.automatic.time_on_site ?? null;
2887
3334
  function TrackingProvider({ children }) {
2888
3335
  const pathname = (0, import_navigation3.usePathname)();
2889
- const client = (0, import_react4.useMemo)(
3336
+ const client = (0, import_react5.useMemo)(
2890
3337
  () => createTypedClient(
2891
3338
  getOrCreateTrackingClient({
2892
3339
  apiKey,
@@ -2904,18 +3351,18 @@ function createTracking(options) {
2904
3351
  ),
2905
3352
  []
2906
3353
  );
2907
- const conversionStore = (0, import_react4.useMemo)(
2908
- () => conversionConfig ? resolveConversionConfig({
3354
+ const conversionStore = (0, import_react5.useMemo)(
3355
+ () => trackingConfig ? getTrackingConfigRuntime(trackingConfig) : conversionConfig ? resolveConversionConfig({
2909
3356
  cdnUrl: conversionConfig.cdnUrl,
2910
3357
  baked: conversionConfig.baked
2911
3358
  }) : null,
2912
3359
  []
2913
3360
  );
2914
- const wrapClient = (0, import_react4.useMemo)(() => {
3361
+ const wrapClient = (0, import_react5.useMemo)(() => {
2915
3362
  const autoFire = conversionStore ? createConversionAutoFire(conversionStore) : null;
2916
3363
  return (c) => autoFire ? withConversionAutoFire(c, autoFire) : c;
2917
3364
  }, [conversionStore]);
2918
- const firePageView = (0, import_react4.useCallback)(() => {
3365
+ const firePageView = (0, import_react5.useCallback)(() => {
2919
3366
  const rawClient = getOrCreateTrackingClient({
2920
3367
  apiKey,
2921
3368
  endpoint,
@@ -2927,9 +3374,10 @@ function createTracking(options) {
2927
3374
  debug
2928
3375
  });
2929
3376
  fireManualPageView(wrapClient(rawClient));
3377
+ if (trackingConfig) getTrackingConfigRuntime(trackingConfig).queuePageView();
2930
3378
  }, [wrapClient]);
2931
- const firedPagesRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
2932
- (0, import_react4.useEffect)(() => {
3379
+ const firedPagesRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
3380
+ (0, import_react5.useEffect)(() => {
2933
3381
  if (!specificPageVisitConfig || pathname === null) return;
2934
3382
  const rawClient = getOrCreateTrackingClient({
2935
3383
  apiKey,
@@ -2959,8 +3407,8 @@ function createTracking(options) {
2959
3407
  }
2960
3408
  }, [pathname]);
2961
3409
  const multiPageSessionConfig = triggers.automatic.multi_page_session ?? null;
2962
- const mpsLastPathRef = (0, import_react4.useRef)(null);
2963
- (0, import_react4.useEffect)(() => {
3410
+ const mpsLastPathRef = (0, import_react5.useRef)(null);
3411
+ (0, import_react5.useEffect)(() => {
2964
3412
  if (!multiPageSessionConfig || pathname === null) return;
2965
3413
  if (pathname === mpsLastPathRef.current) return;
2966
3414
  mpsLastPathRef.current = pathname;
@@ -3007,7 +3455,7 @@ function createTracking(options) {
3007
3455
  } catch {
3008
3456
  }
3009
3457
  }, [pathname]);
3010
- (0, import_react4.useEffect)(() => {
3458
+ (0, import_react5.useEffect)(() => {
3011
3459
  const rawClient = getOrCreateTrackingClient({
3012
3460
  apiKey,
3013
3461
  endpoint,
@@ -3050,12 +3498,12 @@ function createTracking(options) {
3050
3498
  });
3051
3499
  }, []);
3052
3500
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(TrackingContext.Provider, { value: client, children: [
3053
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PageViewTracker, { fire: firePageView }) }),
3501
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react5.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PageViewTracker, { fire: firePageView }) }),
3054
3502
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PhoneConfigProvider, { value: phone ?? null, children })
3055
3503
  ] });
3056
3504
  }
3057
3505
  function useTracking() {
3058
- const client = (0, import_react4.useContext)(TrackingContext);
3506
+ const client = (0, import_react5.useContext)(TrackingContext);
3059
3507
  if (client === null) {
3060
3508
  throw new Error(
3061
3509
  "useTracking must be called inside a <TrackingProvider> returned by createTracking()"
@@ -3072,6 +3520,7 @@ function createTracking(options) {
3072
3520
  ConsentBanner,
3073
3521
  DEFAULT_DECLINE_TTL_DAYS,
3074
3522
  DEFAULT_PHONE_COUNTRY,
3523
+ EVENT_SEMANTICS,
3075
3524
  GoogleAdsTracking,
3076
3525
  NAMED_RANGES,
3077
3526
  PhoneField,
@@ -3092,6 +3541,8 @@ function createTracking(options) {
3092
3541
  fromMinor,
3093
3542
  getConsentChoice,
3094
3543
  getConsentState,
3544
+ getEventSemantics,
3545
+ getTrackingConfigRuntime,
3095
3546
  onConsentChange,
3096
3547
  optIn,
3097
3548
  optOut,