@aranova/tracking-next 0.24.1 → 0.26.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.
package/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,20 +15,12 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
31
21
  var src_exports = {};
32
22
  __export(src_exports, {
33
- AdPlatformTracking: () => AdPlatformTracking,
23
+ AdPlatformTracking: () => import_client.AdPlatformTracking,
34
24
  AranovaApiError: () => AranovaApiError,
35
25
  BOOKING_SOURCES: () => BOOKING_SOURCES,
36
26
  BOOKING_STATUSES: () => BOOKING_STATUSES,
@@ -38,18 +28,18 @@ __export(src_exports, {
38
28
  CALENDAR_METADATA_SCHEMA_VERSION: () => CALENDAR_METADATA_SCHEMA_VERSION,
39
29
  CALENDAR_SYNC_STATUSES: () => CALENDAR_SYNC_STATUSES,
40
30
  CalendarClientProvider: () => CalendarClientProvider,
41
- ConsentBanner: () => ConsentBanner,
31
+ ConsentBanner: () => import_client.ConsentBanner,
42
32
  DEFAULT_DECLINE_TTL_DAYS: () => DEFAULT_DECLINE_TTL_DAYS,
43
33
  DEFAULT_PHONE_COUNTRY: () => DEFAULT_PHONE_COUNTRY,
44
34
  EVENT_SEMANTICS: () => EVENT_SEMANTICS,
45
35
  GOOGLE_SYNC_STATES: () => GOOGLE_SYNC_STATES,
46
- GoogleAdsTracking: () => GoogleAdsTracking,
36
+ GoogleAdsTracking: () => import_client.GoogleAdsTracking,
47
37
  MAX_ATTENDEES: () => MAX_ATTENDEES,
48
38
  MAX_BOOKING_MINUTES: () => MAX_BOOKING_MINUTES,
49
39
  MAX_REMINDER_MINUTES: () => MAX_REMINDER_MINUTES,
50
40
  MAX_REMINDER_OVERRIDES: () => MAX_REMINDER_OVERRIDES,
51
41
  NAMED_RANGES: () => NAMED_RANGES,
52
- PhoneField: () => PhoneField,
42
+ PhoneField: () => import_client2.PhoneField,
53
43
  SUPPORTED_CURRENCIES: () => SUPPORTED_CURRENCIES,
54
44
  TRACKING_PARAM_KEYS: () => TRACKING_PARAM_KEYS,
55
45
  TRACKING_RANGES: () => TRACKING_RANGES,
@@ -68,7 +58,7 @@ __export(src_exports, {
68
58
  computeSlots: () => computeSlots,
69
59
  createCalendarReadClient: () => createCalendarReadClient,
70
60
  createSalesClient: () => createSalesClient,
71
- createTracking: () => createTracking,
61
+ createTracking: () => import_client.createTracking,
72
62
  createTrackingClientContext: () => createTrackingClientContext,
73
63
  createTrackingEventCreatePayload: () => createTrackingEventCreatePayload,
74
64
  createTrackingSessionUpsertPayload: () => createTrackingSessionUpsertPayload,
@@ -112,14 +102,14 @@ __export(src_exports, {
112
102
  useBookingForm: () => useBookingForm,
113
103
  useCalendarBusy: () => useCalendarBusy,
114
104
  useCalendarClient: () => useCalendarClient,
115
- useConsent: () => useConsent,
116
- useConsentState: () => useConsentState,
117
- useCookiePreferences: () => useCookiePreferences,
118
- useGclid: () => useGclid,
119
- usePhoneConfig: () => usePhoneConfig,
120
- usePhoneField: () => usePhoneField,
105
+ useConsent: () => import_client.useConsent,
106
+ useConsentState: () => import_client.useConsentState,
107
+ useCookiePreferences: () => import_client.useCookiePreferences,
108
+ useGclid: () => import_client.useGclid,
109
+ usePhoneConfig: () => import_client2.usePhoneConfig,
110
+ usePhoneField: () => import_client2.usePhoneField,
121
111
  useSlots: () => useSlots,
122
- useTrackingParams: () => useTrackingParams
112
+ useTrackingParams: () => import_client.useTrackingParams
123
113
  });
124
114
  module.exports = __toCommonJS(src_exports);
125
115
 
@@ -165,44 +155,12 @@ function formatPhoneAsTyped(raw, country) {
165
155
 
166
156
  // ../tracking-core/src/user-data.ts
167
157
  var EMAIL_SHAPE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
168
- var EMAIL_NAME_HINT = /e[-_]?mail/i;
169
- var PHONE_NAME_HINT = /(^|[^a-z])(phone|tel|mobile|cell)/i;
170
158
  var stash = { email: null, phoneNumber: null };
171
159
  function normalizeEmail(raw) {
172
160
  if (typeof raw !== "string") return null;
173
161
  const cleaned = raw.trim().toLowerCase();
174
162
  return EMAIL_SHAPE.test(cleaned) ? cleaned : null;
175
163
  }
176
- function fieldText(field, key) {
177
- const value = field[key];
178
- return typeof value === "string" ? value : "";
179
- }
180
- function extractUserDataFromFormFields(fields, country) {
181
- const result = { email: null, phoneNumber: null };
182
- if (!Array.isArray(fields)) return result;
183
- const passes = [
184
- (field, _hint, type) => fieldText(field, "type").toLowerCase() === type,
185
- (field, hint) => hint.test(fieldText(field, "name")) || hint.test(fieldText(field, "label"))
186
- ];
187
- for (const matches of passes) {
188
- for (const raw of fields) {
189
- if (raw === null || typeof raw !== "object") continue;
190
- const field = raw;
191
- if (typeof field.value !== "string" || field.value.length === 0) continue;
192
- if (result.email === null && matches(field, EMAIL_NAME_HINT, "email")) {
193
- result.email = normalizeEmail(field.value);
194
- }
195
- if (result.phoneNumber === null && matches(field, PHONE_NAME_HINT, "tel")) {
196
- try {
197
- result.phoneNumber = toE164(field.value, country);
198
- } catch {
199
- }
200
- }
201
- }
202
- if (result.email !== null && result.phoneNumber !== null) break;
203
- }
204
- return result;
205
- }
206
164
  function stashUserData(data, country) {
207
165
  const email = normalizeEmail(data.email);
208
166
  let phoneNumber = null;
@@ -218,16 +176,6 @@ function stashUserData(data, country) {
218
176
  phoneNumber: phoneNumber ?? stash.phoneNumber
219
177
  };
220
178
  }
221
- function stashUserDataFromFormFields(fields, country) {
222
- try {
223
- const extracted = extractUserDataFromFormFields(fields, country);
224
- stash = {
225
- email: extracted.email ?? stash.email,
226
- phoneNumber: extracted.phoneNumber ?? stash.phoneNumber
227
- };
228
- } catch {
229
- }
230
- }
231
179
  function clearStashedUserData() {
232
180
  stash = { email: null, phoneNumber: null };
233
181
  }
@@ -368,20 +316,6 @@ function resetConsent() {
368
316
  pushConsentToPlatforms("granted");
369
317
  notifyConsentChanged(DEFAULT_CHOICE);
370
318
  }
371
- function createEffectiveConsentSnippet() {
372
- return `
373
- var aranovaConsent = (function () {
374
- try {
375
- if (window.localStorage.getItem('${CONSENT_STATE_KEY}') === 'denied') {
376
- var exp = window.localStorage.getItem('${CONSENT_EXPIRES_AT_KEY}');
377
- var expMs = exp ? Date.parse(exp) : NaN;
378
- if (!(expMs <= Date.now())) return 'denied';
379
- }
380
- } catch (e) {}
381
- return 'granted';
382
- })();
383
- `.trim();
384
- }
385
319
 
386
320
  // ../tracking-core/src/capabilities.ts
387
321
  var TRACKING_CAPABILITIES = [
@@ -513,12 +447,6 @@ function getCookieValueFromDocument(key) {
513
447
  function setTrackingCookie(key, value, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
514
448
  persistCookieValue(key, value, maxAgeSeconds);
515
449
  }
516
- function mergeTrackingParams(primary, fallback) {
517
- return TRACKING_PARAM_KEYS.reduce((merged, key) => {
518
- merged[key] = primary[key] ?? fallback[key];
519
- return merged;
520
- }, createEmptyTrackingParams());
521
- }
522
450
  function persistTrackingParamsFromSearchParams(searchParams, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
523
451
  const trackingValues = getTrackingQueryValues(searchParams);
524
452
  Object.entries(trackingValues).forEach(([key, value]) => {
@@ -542,30 +470,6 @@ var GTAG_ID_PATTERN = /^[A-Z]{1,3}-[A-Za-z0-9_-]+$/;
542
470
  function isValidGtagId(id) {
543
471
  return GTAG_ID_PATTERN.test(id);
544
472
  }
545
- function createConsentDefaultScript() {
546
- return `
547
- window.dataLayer = window.dataLayer || [];
548
- function gtag(){dataLayer.push(arguments);}
549
- window.gtag = gtag;
550
- ${createEffectiveConsentSnippet()}
551
- gtag('consent', 'default', {
552
- ad_storage: aranovaConsent,
553
- ad_user_data: aranovaConsent,
554
- ad_personalization: aranovaConsent,
555
- analytics_storage: aranovaConsent
556
- });
557
- `.trim();
558
- }
559
- function createGtagInitScript(gtagId) {
560
- if (!isValidGtagId(gtagId)) return "";
561
- return `
562
- window.dataLayer = window.dataLayer || [];
563
- function gtag(){dataLayer.push(arguments);}
564
- window.gtag = gtag;
565
- gtag('js', new Date());
566
- gtag('config', '${gtagId}');
567
- `.trim();
568
- }
569
473
  function ensureGtagFunction() {
570
474
  window.dataLayer = window.dataLayer || [];
571
475
  if (typeof window.gtag === "function") return window.gtag;
@@ -614,80 +518,16 @@ function loadGtagScript(gtagId) {
614
518
  script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);
615
519
  document.head.append(script);
616
520
  }
617
- function createGtagInitScriptMulti(gtagIds) {
618
- const safeIds = gtagIds.filter(isValidGtagId);
619
- const configs = safeIds.map((id) => `gtag('config', '${id}');`).join("\n");
620
- return `
621
- window.dataLayer = window.dataLayer || [];
622
- function gtag(){dataLayer.push(arguments);}
623
- window.gtag = gtag;
624
- gtag('js', new Date());
625
- ${configs}
626
- `.trim();
627
- }
628
521
 
629
522
  // ../tracking-core/src/fbq.ts
630
- var FB_EVENTS_SCRIPT_HOST = "https://connect.facebook.net/en_US/fbevents.js";
631
523
  var FBC_COOKIE = "_fbc";
632
524
  var FBP_COOKIE = "_fbp";
633
- var META_PIXEL_ID_PATTERN = /^\d{15,16}$/;
634
- function isValidMetaPixelId(id) {
635
- return META_PIXEL_ID_PATTERN.test(id);
636
- }
637
- function computeFbSubdomainIndex(hostname) {
638
- const labels = hostname.split(".").filter(Boolean);
639
- return Math.max(0, labels.length - 1);
640
- }
641
- function buildFbc(fbclid, now, hostname) {
642
- const host = hostname ?? (typeof window === "undefined" ? "" : window.location.hostname);
643
- return `fb.${computeFbSubdomainIndex(host)}.${now}.${fbclid}`;
644
- }
645
525
  function getFbcCookie() {
646
526
  return readCookieValue(FBC_COOKIE);
647
527
  }
648
528
  function getFbpCookie() {
649
529
  return readCookieValue(FBP_COOKIE);
650
530
  }
651
- function readFbclidFromUrl() {
652
- if (typeof window === "undefined") return null;
653
- try {
654
- const value = new URL(window.location.href).searchParams.get("fbclid");
655
- return value && value.trim().length > 0 ? value : null;
656
- } catch {
657
- return null;
658
- }
659
- }
660
- function captureFbc(now = typeof Date === "undefined" ? 0 : Date.now()) {
661
- if (typeof window === "undefined") return;
662
- if (getFbcCookie()) return;
663
- const fbclid = readFbclidFromUrl() ?? readCookieValue("fbclid");
664
- if (!fbclid) return;
665
- persistCookieValue(FBC_COOKIE, buildFbc(fbclid, now), TRACKING_COOKIE_MAX_AGE_SECONDS);
666
- }
667
- function fbqStubScript() {
668
- return `
669
- !function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
670
- n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
671
- n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
672
- t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
673
- document,'script','${FB_EVENTS_SCRIPT_HOST}');`.trim();
674
- }
675
- function createMetaConsentDefaultScript() {
676
- return `${fbqStubScript()}
677
- ${createEffectiveConsentSnippet()}
678
- fbq('consent', aranovaConsent === 'denied' ? 'revoke' : 'grant');`;
679
- }
680
- function createMetaPixelInitScript(pixelId) {
681
- if (!isValidMetaPixelId(pixelId)) return "";
682
- return `fbq('init', '${pixelId}');
683
- fbq('track', 'PageView');`;
684
- }
685
- function createMetaPixelInitScriptMulti(pixelIds) {
686
- const inits = pixelIds.filter(isValidMetaPixelId).map((id) => `fbq('init', '${id}');`);
687
- if (inits.length === 0) return "";
688
- return `${inits.join("\n")}
689
- fbq('track', 'PageView');`;
690
- }
691
531
 
692
532
  // ../tracking-core/src/landing.ts
693
533
  var LANDING_STORAGE_KEY = "_aranova_track_landing";
@@ -962,9 +802,9 @@ function resolveConversionConfig(options) {
962
802
  }
963
803
  }
964
804
  function notifyResolved() {
965
- const listeners2 = [...resolveListeners];
805
+ const listeners = [...resolveListeners];
966
806
  resolveListeners.clear();
967
- for (const listener of listeners2) {
807
+ for (const listener of listeners) {
968
808
  try {
969
809
  listener();
970
810
  } catch {
@@ -1529,124 +1369,8 @@ function getTrackingConfigRuntime(ref, fetchImpl) {
1529
1369
  return runtime;
1530
1370
  }
1531
1371
 
1532
- // ../tracking-core/src/resources/conversion-autofire.ts
1533
- function currentPath() {
1534
- return typeof window === "undefined" ? "" : window.location.pathname;
1535
- }
1536
- var MAX_BUFFERED_EVENTS = 50;
1537
- function createConversionAutoFire(store) {
1538
- const pending = [];
1539
- let subscribed = false;
1540
- function fireMatching(eventType, metadata, transactionScope) {
1541
- for (const goal of store.listGoals()) {
1542
- if (goal.kind !== "event" || !goal.firing) continue;
1543
- if (!automaticThresholdMet(goal, eventType, metadata)) continue;
1544
- const transactionId = getAutomaticTransactionId(transactionScope, goal.key, currentPath());
1545
- if ("queueAutomaticEvent" in store) {
1546
- store.fireConversion(goal.key, {
1547
- transactionId
1548
- });
1549
- continue;
1550
- }
1551
- const firing = goal.firing;
1552
- const cents = firing.value_cents ?? null;
1553
- const currency = firing.currency ?? null;
1554
- fireConversionWithConsent({
1555
- sendTo: firing.send_to,
1556
- value: cents != null && currency ? fromMinor(cents, currency) : null,
1557
- currency,
1558
- transactionId
1559
- });
1560
- }
1561
- }
1562
- return {
1563
- onAutomaticEvent(eventType, metadata, transactionScope) {
1564
- if ("queueAutomaticEvent" in store) {
1565
- store.queueAutomaticEvent(eventType, metadata, currentPath(), transactionScope);
1566
- return;
1567
- }
1568
- if (store.isReady()) {
1569
- fireMatching(eventType, metadata, transactionScope);
1570
- return;
1571
- }
1572
- if (pending.length < MAX_BUFFERED_EVENTS) {
1573
- pending.push({ eventType, metadata, transactionScope });
1574
- }
1575
- if (!subscribed) {
1576
- subscribed = true;
1577
- store.onResolve(() => {
1578
- const buffered = pending.splice(0);
1579
- for (const event of buffered) {
1580
- fireMatching(event.eventType, event.metadata, event.transactionScope);
1581
- }
1582
- });
1583
- }
1584
- }
1585
- };
1586
- }
1587
- function withConversionAutoFire(client, autoFire) {
1588
- return {
1589
- ...client,
1590
- trackEvent: (input) => {
1591
- client.trackEvent(input);
1592
- try {
1593
- autoFire.onAutomaticEvent(input.eventType, input.metadata ?? {}, client.getSessionId());
1594
- } catch {
1595
- }
1596
- }
1597
- };
1598
- }
1599
-
1600
1372
  // ../tracking-core/src/session.ts
1601
- var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
1602
- var SESSION_STORAGE_KEY = "aranova_tracking_session";
1603
1373
  var SESSION_IDLE_MS = 30 * 60 * 1e3;
1604
- function safeUuid() {
1605
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
1606
- return crypto.randomUUID();
1607
- return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
1608
- }
1609
- function readLocalStorage(key) {
1610
- try {
1611
- return window.localStorage.getItem(key);
1612
- } catch {
1613
- return null;
1614
- }
1615
- }
1616
- function writeLocalStorage(key, value) {
1617
- try {
1618
- window.localStorage.setItem(key, value);
1619
- } catch {
1620
- }
1621
- }
1622
- function getVisitorId() {
1623
- if (typeof window === "undefined") return safeUuid();
1624
- const existing = readLocalStorage(VISITOR_STORAGE_KEY);
1625
- if (existing && existing.length > 0) return existing;
1626
- const fresh = safeUuid();
1627
- writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
1628
- return fresh;
1629
- }
1630
- function getOrRotateSessionId(now = Date.now()) {
1631
- if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
1632
- const raw = readLocalStorage(SESSION_STORAGE_KEY);
1633
- if (raw) {
1634
- try {
1635
- const parsed = JSON.parse(raw);
1636
- if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
1637
- if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
1638
- const refreshed = { id: parsed.id, last_event_at: now };
1639
- writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
1640
- return { id: parsed.id, isNew: false };
1641
- }
1642
- }
1643
- } catch {
1644
- }
1645
- }
1646
- const fresh = { id: safeUuid(), last_event_at: now };
1647
- writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
1648
- return { id: fresh.id, isNew: true };
1649
- }
1650
1374
 
1651
1375
  // ../tracking-core/src/events/page-view.ts
1652
1376
  var import_zod = require("zod");
@@ -1668,495 +1392,12 @@ var pageViewMetadataSchema = import_zod.z.object({
1668
1392
  }).strict();
1669
1393
  var pageViewConfigSchema = import_zod.z.object({}).strict();
1670
1394
 
1671
- // ../tracking-core/src/page-view.ts
1672
- var LAST_FIRED_URL_STORAGE_KEY = "aranova_tracking_last_fired_url";
1673
- var lastFiredUrl = null;
1674
- var lastFiredUrlHydrated = false;
1675
- function readSessionStorage(key) {
1676
- try {
1677
- if (typeof window === "undefined") return null;
1678
- return window.sessionStorage.getItem(key);
1679
- } catch {
1680
- return null;
1681
- }
1682
- }
1683
- function writeSessionStorage(key, value) {
1684
- try {
1685
- if (typeof window === "undefined") return;
1686
- window.sessionStorage.setItem(key, value);
1687
- } catch {
1688
- }
1689
- }
1690
- function getLastFiredUrl() {
1691
- if (!lastFiredUrlHydrated) {
1692
- lastFiredUrlHydrated = true;
1693
- const stored = readSessionStorage(LAST_FIRED_URL_STORAGE_KEY);
1694
- if (stored !== null) lastFiredUrl = stored;
1695
- }
1696
- return lastFiredUrl;
1697
- }
1698
- function setLastFiredUrl(url) {
1699
- lastFiredUrl = url;
1700
- lastFiredUrlHydrated = true;
1701
- writeSessionStorage(LAST_FIRED_URL_STORAGE_KEY, url);
1702
- }
1703
- function buildPageViewMetadata(referrerOverride) {
1704
- if (typeof window === "undefined" || typeof document === "undefined") return null;
1705
- return pageViewMetadataSchema.parse({
1706
- page: {
1707
- title: document.title || null,
1708
- path: window.location.pathname,
1709
- search: window.location.search,
1710
- hash: window.location.hash
1711
- },
1712
- referrer: referrerOverride !== void 0 ? referrerOverride : document.referrer || null,
1713
- viewport: { w: window.innerWidth, h: window.innerHeight }
1714
- });
1715
- }
1716
- function fireManualPageView(client) {
1717
- if (typeof window === "undefined") return;
1718
- const currentHref = window.location.href;
1719
- const previousFiredUrl = getLastFiredUrl();
1720
- const internalReferrer = previousFiredUrl !== null && previousFiredUrl !== currentHref ? previousFiredUrl : null;
1721
- const externalReferrer = typeof document !== "undefined" ? document.referrer || null : null;
1722
- const referrer = internalReferrer ?? externalReferrer;
1723
- const metadata = buildPageViewMetadata(referrer);
1724
- client.trackEvent({
1725
- eventType: "page_view",
1726
- pageUrl: currentHref,
1727
- metadata
1728
- });
1729
- if (currentHref !== previousFiredUrl) {
1730
- setLastFiredUrl(currentHref);
1731
- }
1732
- }
1733
- function attachBfcacheRestore(client) {
1734
- if (typeof window === "undefined") return () => {
1735
- };
1736
- function handlePageShow(event) {
1737
- if (!event.persisted) return;
1738
- fireManualPageView(client);
1739
- }
1740
- window.addEventListener("pageshow", handlePageShow);
1741
- return () => {
1742
- window.removeEventListener("pageshow", handlePageShow);
1743
- };
1744
- }
1745
-
1746
- // ../tracking-core/src/heartbeat.ts
1747
- function serializeValue(value) {
1748
- if (value instanceof RegExp) return value.source;
1749
- if (Array.isArray(value)) return value.map(serializeValue);
1750
- if (value !== null && typeof value === "object") {
1751
- const out = {};
1752
- for (const [k, v] of Object.entries(value)) {
1753
- out[k] = serializeValue(v);
1754
- }
1755
- return out;
1756
- }
1757
- return value;
1758
- }
1759
- function buildHeartbeatMetadata(surface, sdkVersion, packageName, triggers, gtagIds = null) {
1760
- const automaticNames = triggers ? Object.keys(triggers.automatic) : [];
1761
- const manualNames = triggers?.manual ? Object.keys(triggers.manual) : [];
1762
- let triggerConfig = null;
1763
- if (triggers) {
1764
- const cfg = {};
1765
- for (const [name, config] of Object.entries(triggers.automatic)) {
1766
- const serialized = serializeValue(config);
1767
- if (Object.keys(serialized).length > 0) {
1768
- cfg[name] = serialized;
1769
- }
1770
- }
1771
- if (triggers.manual) {
1772
- for (const [name, config] of Object.entries(triggers.manual)) {
1773
- if (config === void 0) continue;
1774
- const serialized = serializeValue(config);
1775
- if (Object.keys(serialized).length > 0) {
1776
- cfg[name] = serialized;
1777
- }
1778
- }
1779
- }
1780
- if (Object.keys(cfg).length > 0) {
1781
- triggerConfig = cfg;
1782
- }
1783
- }
1784
- return {
1785
- sdk_version: sdkVersion ?? "unknown",
1786
- package_name: packageName,
1787
- surface,
1788
- triggers: {
1789
- automatic: automaticNames,
1790
- manual: manualNames
1791
- },
1792
- trigger_config: triggerConfig,
1793
- configured_gtag_ids: gtagIds
1794
- };
1795
- }
1796
-
1797
1395
  // ../tracking-core/src/ingest.ts
1798
- var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
1799
- var DEFAULT_MAX_QUEUE_SIZE = 10;
1800
- var HARD_MAX_BATCH = 50;
1801
- var MAX_RETRY_BACKOFF_MS = 6e4;
1802
- var MAX_BUFFERED_EVENTS2 = 200;
1803
- var RETRY_BUFFER_KEY_PREFIX = "aranova_tracking_pending_v1";
1804
1396
  var API_KEY_HEADER = "X-Aranova-Api-Key";
1805
1397
  var SDK_VERSION_HEADER = "X-Aranova-Sdk-Version";
1806
1398
  var SDK_PACKAGE_HEADER = "X-Aranova-Sdk-Package";
1807
1399
  var SDK_SURFACE_HEADER = "X-Aranova-Sdk-Surface";
1808
1400
  var SDK_ENVIRONMENT_HEADER = "X-Aranova-Sdk-Environment";
1809
- function buildContext(surface, sdkVersion, packageName, environment, activeGtagIds) {
1810
- return {
1811
- surface,
1812
- sdk_version: sdkVersion,
1813
- package_name: packageName,
1814
- site_origin: typeof window === "undefined" ? null : window.location.origin,
1815
- page_title: typeof document === "undefined" ? null : document.title || null,
1816
- referrer: typeof document === "undefined" ? null : document.referrer || null,
1817
- environment,
1818
- active_gtag_ids: activeGtagIds,
1819
- // Rebuilt per flush (this runs inside the payload builder), so a client
1820
- // constructed later on a deeper route still gets reported.
1821
- capabilities: getRegisteredCapabilities()
1822
- };
1823
- }
1824
- function readTrackingParams() {
1825
- if (typeof window === "undefined") return createEmptyTrackingParams();
1826
- try {
1827
- captureTrackingParamsFromLocation();
1828
- } catch {
1829
- }
1830
- return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
1831
- }
1832
- function consentSnapshot() {
1833
- try {
1834
- const choice = getConsentChoice();
1835
- return {
1836
- state: choice.state,
1837
- source: choice.source,
1838
- updated_at: choice.updatedAt,
1839
- expires_at: choice.expiresAt
1840
- };
1841
- } catch {
1842
- return null;
1843
- }
1844
- }
1845
- async function postWithFetch(url, body, apiKey, identity, keepalive) {
1846
- if (typeof fetch !== "function") return "drop";
1847
- try {
1848
- const response = await fetch(url, {
1849
- method: "POST",
1850
- headers: {
1851
- "Content-Type": "application/json",
1852
- [API_KEY_HEADER]: apiKey,
1853
- [SDK_VERSION_HEADER]: identity.sdkVersion,
1854
- [SDK_PACKAGE_HEADER]: identity.packageName,
1855
- [SDK_SURFACE_HEADER]: identity.surface,
1856
- [SDK_ENVIRONMENT_HEADER]: identity.environment
1857
- },
1858
- body,
1859
- keepalive,
1860
- // CORS is open on the tracking endpoint; never send cookies.
1861
- credentials: "omit",
1862
- mode: "cors"
1863
- });
1864
- if (response.ok) return "ok";
1865
- if (response.status === 408 || response.status === 429 || response.status >= 500) {
1866
- return "retry";
1867
- }
1868
- return "drop";
1869
- } catch {
1870
- return "retry";
1871
- }
1872
- }
1873
- function retryBufferKey(apiKey, endpoint) {
1874
- return `${RETRY_BUFFER_KEY_PREFIX}:${apiKey}:${endpoint}`;
1875
- }
1876
- function readPendingBatches(key) {
1877
- if (typeof window === "undefined") return [];
1878
- try {
1879
- const raw = window.localStorage.getItem(key);
1880
- if (!raw) return [];
1881
- const parsed = JSON.parse(raw);
1882
- if (!Array.isArray(parsed)) return [];
1883
- return parsed.filter(
1884
- (entry) => typeof entry === "object" && entry !== null && "session" in entry && Array.isArray(entry.events)
1885
- );
1886
- } catch {
1887
- return [];
1888
- }
1889
- }
1890
- function writePendingBatches(key, batches) {
1891
- if (typeof window === "undefined") return;
1892
- try {
1893
- if (batches.length === 0) window.localStorage.removeItem(key);
1894
- else window.localStorage.setItem(key, JSON.stringify(batches));
1895
- } catch {
1896
- }
1897
- }
1898
- function trimToBufferCap(batches) {
1899
- let total = batches.reduce((sum, batch) => sum + batch.events.length, 0);
1900
- const trimmed = batches.slice();
1901
- while (total > MAX_BUFFERED_EVENTS2 && trimmed.length > 1) {
1902
- const dropped = trimmed.shift();
1903
- total -= dropped ? dropped.events.length : 0;
1904
- }
1905
- return trimmed;
1906
- }
1907
- var globalClient = null;
1908
- var globalClientKey = null;
1909
- function clientConfigKey(config) {
1910
- return `${config.apiKey}@${config.endpoint}#${config.surface}`;
1911
- }
1912
- function getOrCreateTrackingClient(config) {
1913
- const key = clientConfigKey(config);
1914
- if (globalClient !== null && globalClientKey === key) {
1915
- return globalClient;
1916
- }
1917
- if (globalClient !== null) {
1918
- globalClient.destroy();
1919
- }
1920
- globalClient = createTrackingClient(config);
1921
- globalClientKey = key;
1922
- return globalClient;
1923
- }
1924
- var clientCaptureRegistry = /* @__PURE__ */ new WeakMap();
1925
- function attachClientCapturesOnce(client, build) {
1926
- let entry = clientCaptureRegistry.get(client);
1927
- if (entry === void 0) {
1928
- entry = { detach: build(), refCount: 0 };
1929
- clientCaptureRegistry.set(client, entry);
1930
- }
1931
- entry.refCount += 1;
1932
- let released = false;
1933
- return () => {
1934
- if (released) return;
1935
- released = true;
1936
- const current = clientCaptureRegistry.get(client);
1937
- if (current === void 0) return;
1938
- current.refCount -= 1;
1939
- if (current.refCount <= 0) {
1940
- current.detach();
1941
- clientCaptureRegistry.delete(client);
1942
- }
1943
- };
1944
- }
1945
- function createTrackingClient(config) {
1946
- const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
1947
- const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);
1948
- const sdkVersion = config.sdkVersion ?? null;
1949
- const packageName = config.packageName ?? null;
1950
- const environment = config.environment ?? "production";
1951
- const activeGtagIds = config.activeGtagIds ?? null;
1952
- const endpointBase = config.endpoint.replace(/\/$/, "");
1953
- const eventsUrl = `${endpointBase}/events`;
1954
- const identityHeaders2 = {
1955
- sdkVersion: sdkVersion ?? "",
1956
- packageName: packageName ?? "",
1957
- surface: config.surface,
1958
- environment
1959
- };
1960
- let queue = [];
1961
- let flushTimer = null;
1962
- const bufferKey = retryBufferKey(config.apiKey, endpointBase);
1963
- let pending = readPendingBatches(bufferKey);
1964
- let flushChain = Promise.resolve();
1965
- let retryAttempt = 0;
1966
- let firstPage = null;
1967
- let initialParams = createEmptyTrackingParams();
1968
- let destroyed = false;
1969
- const visitorId = getVisitorId();
1970
- const initialSession = getOrRotateSessionId();
1971
- let sessionId = initialSession.id;
1972
- if (typeof window !== "undefined") {
1973
- firstPage = window.location.href;
1974
- try {
1975
- initialParams = captureTrackingParamsFromLocation();
1976
- } catch {
1977
- }
1978
- getOrCaptureLandingParams(sessionId);
1979
- try {
1980
- captureFbc();
1981
- } catch {
1982
- }
1983
- }
1984
- function enqueueHeartbeat() {
1985
- const metadata = buildHeartbeatMetadata(
1986
- config.surface,
1987
- sdkVersion,
1988
- packageName,
1989
- config.triggers ?? null,
1990
- activeGtagIds
1991
- );
1992
- queue.push({
1993
- event_type: "sdk_heartbeat",
1994
- page_url: typeof window === "undefined" ? null : window.location.href,
1995
- metadata,
1996
- occurred_at: (/* @__PURE__ */ new Date()).toISOString()
1997
- });
1998
- }
1999
- if (initialSession.isNew) {
2000
- enqueueHeartbeat();
2001
- }
2002
- if (pending.length > 0) {
2003
- scheduleFlush();
2004
- }
2005
- function buildSessionPayload() {
2006
- const rotated = getOrRotateSessionId();
2007
- if (rotated.isNew && rotated.id !== sessionId) {
2008
- enqueueHeartbeat();
2009
- }
2010
- sessionId = rotated.id;
2011
- const params = mergeTrackingParams(readTrackingParams(), initialParams);
2012
- const context = buildContext(
2013
- config.surface,
2014
- sdkVersion,
2015
- packageName,
2016
- environment,
2017
- activeGtagIds
2018
- );
2019
- return {
2020
- session_id: sessionId,
2021
- visitor_id: visitorId,
2022
- gclid: params.gclid,
2023
- wbraid: params.wbraid,
2024
- gbraid: params.gbraid,
2025
- ylpcid: params.ylpcid,
2026
- fbclid: params.fbclid,
2027
- fbc: getFbcCookie(),
2028
- fbp: getFbpCookie(),
2029
- utm_source: params.utm_source,
2030
- utm_medium: params.utm_medium,
2031
- utm_campaign: params.utm_campaign,
2032
- utm_term: params.utm_term,
2033
- utm_content: params.utm_content,
2034
- // Landing params for the CURRENT session id — captured on the spot when
2035
- // the session just rotated (the current URL is the rotated session's
2036
- // landing), reused from the stored record otherwise. Keys are omitted
2037
- // entirely when the landing isn't observable (SSR).
2038
- ...buildLandingPayloadFields(sessionId),
2039
- first_page: firstPage,
2040
- consent_state: consentSnapshot(),
2041
- context
2042
- };
2043
- }
2044
- function scheduleFlush(delayMs = flushIntervalMs) {
2045
- if (flushTimer !== null || destroyed) return;
2046
- flushTimer = setTimeout(() => {
2047
- flushTimer = null;
2048
- void flush();
2049
- }, delayMs);
2050
- }
2051
- function clearScheduledFlush() {
2052
- if (flushTimer !== null) {
2053
- clearTimeout(flushTimer);
2054
- flushTimer = null;
2055
- }
2056
- }
2057
- function bufferQueued() {
2058
- if (queue.length === 0) return;
2059
- const events = queue.slice(0, HARD_MAX_BATCH);
2060
- queue = queue.slice(events.length);
2061
- pending = trimToBufferCap([...pending, { session: buildSessionPayload(), events }]);
2062
- writePendingBatches(bufferKey, pending);
2063
- }
2064
- function flush() {
2065
- flushChain = flushChain.then(runFlush).catch(() => {
2066
- });
2067
- return flushChain;
2068
- }
2069
- async function runFlush() {
2070
- if (destroyed) return;
2071
- clearScheduledFlush();
2072
- bufferQueued();
2073
- if (pending.length === 0) return;
2074
- try {
2075
- while (pending.length > 0) {
2076
- const batch = pending[0];
2077
- const body = { session: batch.session, events: batch.events };
2078
- const outcome = await postWithFetch(
2079
- eventsUrl,
2080
- JSON.stringify(body),
2081
- config.apiKey,
2082
- identityHeaders2,
2083
- false
2084
- );
2085
- if (outcome === "retry") {
2086
- retryAttempt += 1;
2087
- return;
2088
- }
2089
- pending = pending.slice(1);
2090
- writePendingBatches(bufferKey, pending);
2091
- retryAttempt = 0;
2092
- }
2093
- } finally {
2094
- if (pending.length > 0) {
2095
- const backoff = Math.min(flushIntervalMs * 2 ** retryAttempt, MAX_RETRY_BACKOFF_MS);
2096
- scheduleFlush(backoff);
2097
- } else if (queue.length > 0) {
2098
- scheduleFlush();
2099
- }
2100
- }
2101
- }
2102
- function trackEvent(input) {
2103
- if (destroyed) return;
2104
- if (!input || typeof input.eventType !== "string" || input.eventType.length === 0) return;
2105
- if (input.eventType === "form_submit" && getConsentState() !== "denied") {
2106
- try {
2107
- const fields = input.metadata?.form?.fields;
2108
- if (fields) stashUserDataFromFormFields(fields, config.phone?.defaultCountry);
2109
- } catch {
2110
- }
2111
- }
2112
- const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
2113
- queue.push({
2114
- event_type: input.eventType,
2115
- page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
2116
- metadata: input.metadata ?? null,
2117
- occurred_at: occurredAt
2118
- });
2119
- if (queue.length >= maxQueueSize) {
2120
- void flush();
2121
- } else {
2122
- scheduleFlush();
2123
- }
2124
- }
2125
- function flushOnUnload() {
2126
- bufferQueued();
2127
- clearScheduledFlush();
2128
- if (pending.length === 0) return;
2129
- const batch = pending[0];
2130
- pending = pending.slice(1);
2131
- writePendingBatches(bufferKey, pending);
2132
- const body = { session: batch.session, events: batch.events };
2133
- void postWithFetch(eventsUrl, JSON.stringify(body), config.apiKey, identityHeaders2, true);
2134
- }
2135
- if (typeof window !== "undefined") {
2136
- window.addEventListener("pagehide", flushOnUnload);
2137
- window.addEventListener("visibilitychange", () => {
2138
- if (document.visibilityState === "hidden") flushOnUnload();
2139
- });
2140
- }
2141
- return {
2142
- trackEvent,
2143
- flush,
2144
- flushBeacon: flushOnUnload,
2145
- getSessionId: () => sessionId,
2146
- getVisitorId: () => visitorId,
2147
- destroy: () => {
2148
- destroyed = true;
2149
- if (queue.length > 0) {
2150
- flushOnUnload();
2151
- }
2152
- clearScheduledFlush();
2153
- queue = [];
2154
- if (typeof window !== "undefined") {
2155
- window.removeEventListener("pagehide", flushOnUnload);
2156
- }
2157
- }
2158
- };
2159
- }
2160
1401
 
2161
1402
  // ../tracking-core/src/events/cta-click.ts
2162
1403
  var import_zod2 = require("zod");
@@ -2485,506 +1726,6 @@ var EVENT_REGISTRY = {
2485
1726
  var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
2486
1727
  var ALL_MANUAL_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "manual").map(([name]) => name);
2487
1728
  var ALL_LEAD_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.semantics.outcomeRole === "lead").map(([name]) => name);
2488
- function getEventDefinition(name) {
2489
- return EVENT_REGISTRY[name];
2490
- }
2491
-
2492
- // ../tracking-core/src/ingest-typed.ts
2493
- function createTypedClient(raw, registry, options = {}) {
2494
- const debug = options.debug ?? false;
2495
- if (registry.manual?.form_submit) registerCapability("form_capture");
2496
- return {
2497
- trackEvent(eventType, metadata, opts) {
2498
- if (debug) {
2499
- const def = getEventDefinition(eventType);
2500
- def.metadataSchema.parse(metadata);
2501
- }
2502
- raw.trackEvent({
2503
- eventType,
2504
- metadata,
2505
- pageUrl: opts?.pageUrl ?? null,
2506
- occurredAt: opts?.occurredAt ?? null
2507
- });
2508
- },
2509
- flush: raw.flush.bind(raw),
2510
- getSessionId: raw.getSessionId.bind(raw),
2511
- getVisitorId: raw.getVisitorId.bind(raw)
2512
- };
2513
- }
2514
-
2515
- // ../tracking-core/src/triggers/time-on-site.ts
2516
- function attachTimeOnSite(client, config) {
2517
- if (typeof window === "undefined" || typeof document === "undefined") {
2518
- return () => {
2519
- };
2520
- }
2521
- const thresholdMs = config.thresholdSeconds * 1e3;
2522
- let accumulatedMs = 0;
2523
- let activeSince = document.visibilityState === "visible" ? Date.now() : null;
2524
- let timer = null;
2525
- let fired = false;
2526
- function fire() {
2527
- if (fired) return;
2528
- fired = true;
2529
- client.trackEvent({
2530
- eventType: "time_on_site",
2531
- metadata: {
2532
- duration_ms: thresholdMs,
2533
- page: { path: window.location.pathname }
2534
- },
2535
- pageUrl: window.location.href,
2536
- occurredAt: null
2537
- });
2538
- }
2539
- function scheduleNext() {
2540
- if (fired || activeSince === null) return;
2541
- const remaining = thresholdMs - accumulatedMs;
2542
- if (remaining <= 0) {
2543
- fire();
2544
- return;
2545
- }
2546
- timer = setTimeout(fire, remaining);
2547
- }
2548
- function clearTimer() {
2549
- if (timer !== null) {
2550
- clearTimeout(timer);
2551
- timer = null;
2552
- }
2553
- }
2554
- function onVisibilityChange() {
2555
- if (fired) return;
2556
- if (document.visibilityState === "hidden") {
2557
- if (activeSince !== null) {
2558
- accumulatedMs += Date.now() - activeSince;
2559
- activeSince = null;
2560
- }
2561
- clearTimer();
2562
- } else {
2563
- activeSince = Date.now();
2564
- scheduleNext();
2565
- }
2566
- }
2567
- document.addEventListener("visibilitychange", onVisibilityChange);
2568
- scheduleNext();
2569
- return () => {
2570
- clearTimer();
2571
- document.removeEventListener("visibilitychange", onVisibilityChange);
2572
- };
2573
- }
2574
-
2575
- // ../tracking-core/src/triggers/navigation.ts
2576
- var listeners = /* @__PURE__ */ new Set();
2577
- var restorePatch = null;
2578
- function notify() {
2579
- for (const listener of listeners) listener();
2580
- }
2581
- function notifyDeferred() {
2582
- setTimeout(notify, 0);
2583
- }
2584
- function installPatch() {
2585
- const originalPushState = history.pushState;
2586
- const originalReplaceState = history.replaceState;
2587
- function patchedPushState(...args) {
2588
- originalPushState.apply(this, args);
2589
- notifyDeferred();
2590
- }
2591
- function patchedReplaceState(...args) {
2592
- originalReplaceState.apply(this, args);
2593
- notifyDeferred();
2594
- }
2595
- history.pushState = patchedPushState;
2596
- history.replaceState = patchedReplaceState;
2597
- window.addEventListener("popstate", notify);
2598
- restorePatch = () => {
2599
- history.pushState = originalPushState;
2600
- history.replaceState = originalReplaceState;
2601
- window.removeEventListener("popstate", notify);
2602
- restorePatch = null;
2603
- };
2604
- }
2605
- function onHistoryChange(listener) {
2606
- if (listeners.size === 0) installPatch();
2607
- listeners.add(listener);
2608
- return () => {
2609
- if (!listeners.delete(listener)) return;
2610
- if (listeners.size === 0) restorePatch?.();
2611
- };
2612
- }
2613
-
2614
- // ../tracking-core/src/triggers/scroll-measurement.ts
2615
- var BOTTOM_EPSILON_PX = 2;
2616
- function measureScrollPercent() {
2617
- const root = document.scrollingElement ?? document.documentElement;
2618
- const scrollHeight = root.scrollHeight;
2619
- const clientHeight = root.clientHeight;
2620
- if (scrollHeight <= 0 || clientHeight <= 0) return null;
2621
- if (scrollHeight <= clientHeight + BOTTOM_EPSILON_PX) return null;
2622
- const maxTop = scrollHeight - clientHeight;
2623
- const scrollTop = Math.min(Math.max(root.scrollTop, 0), maxTop);
2624
- if (scrollTop + clientHeight >= scrollHeight - BOTTOM_EPSILON_PX) return 100;
2625
- return Math.max(1, Math.min(100, Math.round((scrollTop + clientHeight) / scrollHeight * 100)));
2626
- }
2627
- function scheduleBaselineSnapshot(onSnapshot) {
2628
- let rafId = requestAnimationFrame(() => {
2629
- rafId = requestAnimationFrame(() => {
2630
- onSnapshot(measureScrollPercent());
2631
- });
2632
- });
2633
- return () => cancelAnimationFrame(rafId);
2634
- }
2635
- function createBaselineGate() {
2636
- let baselinePercent = null;
2637
- let ready = false;
2638
- let cancelSnapshot = null;
2639
- return {
2640
- rebaseline() {
2641
- cancelSnapshot?.();
2642
- ready = false;
2643
- baselinePercent = null;
2644
- cancelSnapshot = scheduleBaselineSnapshot((b) => {
2645
- baselinePercent = b;
2646
- ready = true;
2647
- });
2648
- },
2649
- cancel() {
2650
- cancelSnapshot?.();
2651
- },
2652
- baseline() {
2653
- return ready ? baselinePercent : null;
2654
- },
2655
- sample() {
2656
- if (!ready) return null;
2657
- if (baselinePercent === null) {
2658
- baselinePercent = measureScrollPercent();
2659
- return null;
2660
- }
2661
- return measureScrollPercent();
2662
- }
2663
- };
2664
- }
2665
-
2666
- // ../tracking-core/src/triggers/scroll-depth.ts
2667
- function attachScrollDepth(client, config) {
2668
- if (typeof window === "undefined" || typeof document === "undefined") {
2669
- return () => {
2670
- };
2671
- }
2672
- const thresholds = new Set(config.thresholds);
2673
- let firedForPath = /* @__PURE__ */ new Set();
2674
- let currentPath2 = window.location.pathname;
2675
- let rafId = null;
2676
- const gate = createBaselineGate();
2677
- function checkThresholds() {
2678
- const percent = gate.sample();
2679
- if (percent === null) return;
2680
- const baseline = gate.baseline();
2681
- if (baseline === null) return;
2682
- for (const threshold of thresholds) {
2683
- if (threshold <= baseline) continue;
2684
- if (percent >= threshold && !firedForPath.has(threshold)) {
2685
- firedForPath.add(threshold);
2686
- client.trackEvent({
2687
- eventType: "scroll_depth",
2688
- metadata: {
2689
- depth_percent: threshold,
2690
- page: { path: currentPath2 }
2691
- },
2692
- pageUrl: window.location.href,
2693
- occurredAt: null
2694
- });
2695
- }
2696
- }
2697
- }
2698
- function onScroll() {
2699
- if (rafId !== null) return;
2700
- rafId = requestAnimationFrame(() => {
2701
- rafId = null;
2702
- checkThresholds();
2703
- });
2704
- }
2705
- function resetIfPathChanged() {
2706
- const newPath = window.location.pathname;
2707
- if (newPath === currentPath2) return;
2708
- currentPath2 = newPath;
2709
- firedForPath = /* @__PURE__ */ new Set();
2710
- gate.rebaseline();
2711
- }
2712
- const unsubscribeNav = onHistoryChange(resetIfPathChanged);
2713
- window.addEventListener("scroll", onScroll, { passive: true });
2714
- gate.rebaseline();
2715
- return () => {
2716
- if (rafId !== null) cancelAnimationFrame(rafId);
2717
- gate.cancel();
2718
- unsubscribeNav();
2719
- window.removeEventListener("scroll", onScroll);
2720
- };
2721
- }
2722
-
2723
- // ../tracking-core/src/triggers/form-start.ts
2724
- function attachFormStart(client, config) {
2725
- if (typeof window === "undefined" || typeof document === "undefined") {
2726
- return () => {
2727
- };
2728
- }
2729
- const selector = config.selector ?? "form";
2730
- let firedForms = /* @__PURE__ */ new Set();
2731
- let currentPath2 = window.location.pathname;
2732
- function getFormKey(form) {
2733
- if (form.id) return `id:${form.id}`;
2734
- const explicitAction = form.getAttribute("action");
2735
- if (explicitAction) return `action:${explicitAction}`;
2736
- const forms = Array.from(document.querySelectorAll(selector));
2737
- return `index:${forms.indexOf(form)}`;
2738
- }
2739
- function onFocusIn(event) {
2740
- const target = event.target;
2741
- if (!(target instanceof HTMLElement)) return;
2742
- const form = target.closest(selector);
2743
- if (!form || form.tagName !== "FORM") return;
2744
- const key = getFormKey(form);
2745
- if (firedForms.has(key)) return;
2746
- firedForms.add(key);
2747
- client.trackEvent({
2748
- eventType: "form_start",
2749
- metadata: {
2750
- form: {
2751
- id: form.id || "",
2752
- action: form.getAttribute("action") ?? null
2753
- },
2754
- page: { path: window.location.pathname }
2755
- },
2756
- pageUrl: window.location.href,
2757
- occurredAt: null
2758
- });
2759
- }
2760
- function resetIfPathChanged() {
2761
- const newPath = window.location.pathname;
2762
- if (newPath === currentPath2) return;
2763
- currentPath2 = newPath;
2764
- firedForms = /* @__PURE__ */ new Set();
2765
- }
2766
- const originalPushState = history.pushState.bind(history);
2767
- const originalReplaceState = history.replaceState.bind(history);
2768
- function patchedPushState(...args) {
2769
- originalPushState(...args);
2770
- setTimeout(resetIfPathChanged, 0);
2771
- }
2772
- function patchedReplaceState(...args) {
2773
- originalReplaceState(...args);
2774
- setTimeout(resetIfPathChanged, 0);
2775
- }
2776
- history.pushState = patchedPushState;
2777
- history.replaceState = patchedReplaceState;
2778
- window.addEventListener("popstate", resetIfPathChanged);
2779
- document.addEventListener("focusin", onFocusIn);
2780
- return () => {
2781
- history.pushState = originalPushState;
2782
- history.replaceState = originalReplaceState;
2783
- window.removeEventListener("popstate", resetIfPathChanged);
2784
- document.removeEventListener("focusin", onFocusIn);
2785
- };
2786
- }
2787
-
2788
- // ../tracking-core/src/triggers/page-exit.ts
2789
- var MIN_SEGMENT_MS = 50;
2790
- function attachPageExit(client) {
2791
- if (typeof window === "undefined" || typeof document === "undefined") {
2792
- return () => {
2793
- };
2794
- }
2795
- let currentPath2 = window.location.pathname;
2796
- let activeSince = document.visibilityState === "visible" ? Date.now() : null;
2797
- let accumulatedMs = 0;
2798
- let maxScrollPercent = null;
2799
- let rafId = null;
2800
- const gate = createBaselineGate();
2801
- function onScroll() {
2802
- if (rafId !== null) return;
2803
- rafId = requestAnimationFrame(() => {
2804
- rafId = null;
2805
- const percent = gate.sample();
2806
- if (percent !== null && (maxScrollPercent === null || percent > maxScrollPercent)) {
2807
- maxScrollPercent = percent;
2808
- }
2809
- });
2810
- }
2811
- function settledDwellMs() {
2812
- let total = accumulatedMs;
2813
- if (activeSince !== null) {
2814
- total += Date.now() - activeSince;
2815
- }
2816
- return Math.max(0, Math.round(total));
2817
- }
2818
- function emitSegment(path, flush) {
2819
- const dwell = settledDwellMs();
2820
- if (dwell < MIN_SEGMENT_MS) return;
2821
- client.trackEvent({
2822
- eventType: "page_exit",
2823
- metadata: {
2824
- dwell_ms: dwell,
2825
- max_scroll_percent: maxScrollPercent,
2826
- // Lets the backend tell "scrolled to the bottom" apart from "the page
2827
- // was barely scrollable". null = unscrollable page or the segment
2828
- // ended before the post-paint snapshot landed. For pages that grew
2829
- // after the snapshot this is the first-scroll position, not the
2830
- // at-load fraction (see BaselineGate.baseline).
2831
- scroll_baseline_percent: gate.baseline(),
2832
- page: { path }
2833
- },
2834
- pageUrl: window.location.href,
2835
- occurredAt: null
2836
- });
2837
- if (flush) {
2838
- client.flushBeacon();
2839
- }
2840
- accumulatedMs = 0;
2841
- activeSince = null;
2842
- }
2843
- function onNavigate() {
2844
- const newPath = window.location.pathname;
2845
- if (newPath === currentPath2) return;
2846
- emitSegment(currentPath2, false);
2847
- currentPath2 = newPath;
2848
- maxScrollPercent = null;
2849
- gate.rebaseline();
2850
- accumulatedMs = 0;
2851
- activeSince = document.visibilityState === "visible" ? Date.now() : null;
2852
- }
2853
- function onVisibilityChange() {
2854
- if (document.visibilityState === "hidden") {
2855
- emitSegment(currentPath2, true);
2856
- } else {
2857
- activeSince = Date.now();
2858
- }
2859
- }
2860
- function onPageHide() {
2861
- emitSegment(currentPath2, true);
2862
- }
2863
- const unsubscribeNav = onHistoryChange(onNavigate);
2864
- window.addEventListener("scroll", onScroll, { passive: true });
2865
- document.addEventListener("visibilitychange", onVisibilityChange);
2866
- window.addEventListener("pagehide", onPageHide);
2867
- gate.rebaseline();
2868
- return () => {
2869
- if (rafId !== null) cancelAnimationFrame(rafId);
2870
- gate.cancel();
2871
- unsubscribeNav();
2872
- window.removeEventListener("scroll", onScroll);
2873
- document.removeEventListener("visibilitychange", onVisibilityChange);
2874
- window.removeEventListener("pagehide", onPageHide);
2875
- };
2876
- }
2877
-
2878
- // ../tracking-core/src/triggers/cta-click-capture.ts
2879
- var DEFAULT_CTA_SELECTOR = "[data-aranova-cta]";
2880
- var CTA_NAME_MAX_LENGTH = 120;
2881
- function describeElement(el) {
2882
- const tag = el.tagName.toLowerCase();
2883
- return el.id ? `${tag}#${el.id}` : tag;
2884
- }
2885
- function resolveCtaName(el) {
2886
- const explicit = el.getAttribute("data-aranova-cta");
2887
- if (explicit && explicit.trim().length > 0) return explicit.trim();
2888
- const text = (el.textContent ?? "").trim().replaceAll(/\s+/g, " ");
2889
- if (text.length > 0) return text.slice(0, CTA_NAME_MAX_LENGTH);
2890
- return describeElement(el);
2891
- }
2892
- function attachCtaClickCapture(client, config) {
2893
- if (typeof window === "undefined" || typeof document === "undefined") {
2894
- return () => {
2895
- };
2896
- }
2897
- const autoCapture = config.autoCapture;
2898
- if (!autoCapture) {
2899
- return () => {
2900
- };
2901
- }
2902
- registerCapability("cta_click_capture");
2903
- const selector = autoCapture.selector ?? DEFAULT_CTA_SELECTOR;
2904
- function onClick(event) {
2905
- const target = event.target;
2906
- if (!(target instanceof Element)) return;
2907
- let matched = null;
2908
- try {
2909
- matched = target.closest(selector);
2910
- } catch {
2911
- return;
2912
- }
2913
- if (matched === null) return;
2914
- const href = matched instanceof HTMLAnchorElement ? matched.href || null : matched.getAttribute("href");
2915
- client.trackEvent({
2916
- eventType: "cta_click",
2917
- metadata: {
2918
- cta_name: resolveCtaName(matched),
2919
- page: { path: window.location.pathname },
2920
- section: matched.getAttribute("data-aranova-section"),
2921
- destination_url: href,
2922
- href,
2923
- element: describeElement(matched)
2924
- },
2925
- pageUrl: window.location.href,
2926
- occurredAt: null
2927
- });
2928
- }
2929
- document.addEventListener("click", onClick, true);
2930
- return () => {
2931
- document.removeEventListener("click", onClick, true);
2932
- };
2933
- }
2934
-
2935
- // ../tracking-core/src/triggers/phone-click-capture.ts
2936
- var DEFAULT_TEL_SELECTOR = 'a[href^="tel:"]';
2937
- function safeDecodeURIComponent(value) {
2938
- try {
2939
- return decodeURIComponent(value);
2940
- } catch {
2941
- return value;
2942
- }
2943
- }
2944
- function resolvePhoneNumber(el) {
2945
- const href = el instanceof HTMLAnchorElement ? el.href : el.getAttribute("href") ?? "";
2946
- const raw = safeDecodeURIComponent(href.replace(/^tel:/i, "").split(";")[0]).trim();
2947
- return toE164(raw) ?? raw;
2948
- }
2949
- function attachPhoneClickCapture(client, config) {
2950
- if (typeof window === "undefined" || typeof document === "undefined") {
2951
- return () => {
2952
- };
2953
- }
2954
- const autoCapture = config.autoCapture;
2955
- if (!autoCapture) {
2956
- return () => {
2957
- };
2958
- }
2959
- registerCapability("phone_click_capture");
2960
- const selector = autoCapture.selector ?? DEFAULT_TEL_SELECTOR;
2961
- function onClick(event) {
2962
- const target = event.target;
2963
- if (!(target instanceof Element)) return;
2964
- let matched = null;
2965
- try {
2966
- matched = target.closest(selector);
2967
- } catch {
2968
- return;
2969
- }
2970
- if (matched === null) return;
2971
- const metadata = {
2972
- phone_number: resolvePhoneNumber(matched),
2973
- page: { path: window.location.pathname },
2974
- section: matched.getAttribute("data-aranova-section")
2975
- };
2976
- client.trackEvent({
2977
- eventType: "phone_click",
2978
- metadata,
2979
- pageUrl: window.location.href,
2980
- occurredAt: null
2981
- });
2982
- }
2983
- document.addEventListener("click", onClick, true);
2984
- return () => {
2985
- document.removeEventListener("click", onClick, true);
2986
- };
2987
- }
2988
1729
 
2989
1730
  // ../tracking-core/src/resources/http/errors.ts
2990
1731
  var AranovaApiError = class extends Error {
@@ -3355,634 +2096,8 @@ function phoneField(name, raw, country) {
3355
2096
  return { name, type: "phone", value: toE164(raw, country) };
3356
2097
  }
3357
2098
 
3358
- // src/ConsentBanner.tsx
3359
- var import_react2 = require("react");
3360
-
3361
- // src/hooks.ts
3362
- var import_react = require("react");
3363
- function useGclid() {
3364
- const [gclid, setGclid] = (0, import_react.useState)(null);
3365
- (0, import_react.useEffect)(() => {
3366
- setGclid(getCookieValueFromDocument("gclid"));
3367
- }, []);
3368
- return gclid;
3369
- }
3370
- function useTrackingParams() {
3371
- const [trackingParams, setTrackingParams] = (0, import_react.useState)(createEmptyTrackingParams());
3372
- (0, import_react.useEffect)(() => {
3373
- setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
3374
- }, []);
3375
- return trackingParams;
3376
- }
3377
- var DEFAULT_CHOICE2 = {
3378
- state: "granted",
3379
- source: "default",
3380
- updatedAt: null,
3381
- expiresAt: null
3382
- };
3383
- function useCookiePreferences(options) {
3384
- registerCapability("consent_controls");
3385
- const [choice, setChoice] = (0, import_react.useState)(DEFAULT_CHOICE2);
3386
- const ttlDays = options?.declineTtlDays;
3387
- (0, import_react.useEffect)(() => {
3388
- const sync = () => setChoice(getConsentChoice());
3389
- sync();
3390
- const handleStorage = (event) => {
3391
- if (event.key === null || event.key === CONSENT_STATE_KEY || event.key === CONSENT_EXPIRES_AT_KEY || event.key === CONSENT_TIMESTAMP_KEY)
3392
- sync();
3393
- };
3394
- window.addEventListener("storage", handleStorage);
3395
- const unsubscribe = onConsentChange(sync);
3396
- return () => {
3397
- window.removeEventListener("storage", handleStorage);
3398
- unsubscribe();
3399
- };
3400
- }, []);
3401
- const optOutAction = (0, import_react.useCallback)(() => {
3402
- optOut(ttlDays != null ? { declineTtlDays: ttlDays } : void 0);
3403
- }, [ttlDays]);
3404
- const optInAction = (0, import_react.useCallback)(() => {
3405
- optIn();
3406
- }, []);
3407
- const reset = (0, import_react.useCallback)(() => {
3408
- resetConsent();
3409
- }, []);
3410
- return {
3411
- state: choice.state,
3412
- source: choice.source,
3413
- isDefault: choice.source === "default",
3414
- isGranted: choice.state === "granted",
3415
- isDenied: choice.state === "denied",
3416
- updatedAt: choice.updatedAt,
3417
- expiresAt: choice.expiresAt,
3418
- optOut: optOutAction,
3419
- optIn: optInAction,
3420
- reset
3421
- };
3422
- }
3423
- function useConsentState() {
3424
- return useConsent().state;
3425
- }
3426
- function useConsent() {
3427
- const {
3428
- state,
3429
- isGranted,
3430
- isDenied,
3431
- optIn: accept,
3432
- optOut: decline,
3433
- reset
3434
- } = useCookiePreferences();
3435
- return {
3436
- state,
3437
- isPending: false,
3438
- isGranted,
3439
- isDenied,
3440
- accept,
3441
- decline,
3442
- reset
3443
- };
3444
- }
3445
-
3446
- // src/ConsentBanner.tsx
3447
- var import_jsx_runtime = require("react/jsx-runtime");
3448
- var LIGHT_THEME = {
3449
- background: "#ffffff",
3450
- border: "#e5e7eb",
3451
- text: "#111827",
3452
- mutedText: "#4b5563",
3453
- acceptBg: "#111827",
3454
- acceptText: "#ffffff",
3455
- declineBg: "transparent",
3456
- declineText: "#111827",
3457
- declineBorder: "#d1d5db",
3458
- shadow: "0 -4px 16px -2px rgba(15, 23, 42, 0.08), 0 -2px 6px -1px rgba(15, 23, 42, 0.04)",
3459
- linkColor: "#1f2937"
3460
- };
3461
- var DARK_THEME = {
3462
- background: "#0f172a",
3463
- border: "#1e293b",
3464
- text: "#f1f5f9",
3465
- mutedText: "#cbd5e1",
3466
- acceptBg: "#f1f5f9",
3467
- acceptText: "#0f172a",
3468
- declineBg: "transparent",
3469
- declineText: "#f1f5f9",
3470
- declineBorder: "#334155",
3471
- shadow: "0 -4px 16px -2px rgba(0, 0, 0, 0.5), 0 -2px 6px -1px rgba(0, 0, 0, 0.3)",
3472
- linkColor: "#e2e8f0"
3473
- };
3474
- function useResolvedTheme(theme) {
3475
- const [prefersDark, setPrefersDark] = (0, import_react2.useState)(false);
3476
- (0, import_react2.useEffect)(() => {
3477
- if (theme !== "auto" || typeof window === "undefined" || !window.matchMedia) return;
3478
- const mql = window.matchMedia("(prefers-color-scheme: dark)");
3479
- setPrefersDark(mql.matches);
3480
- const onChange = (e) => setPrefersDark(e.matches);
3481
- mql.addEventListener("change", onChange);
3482
- return () => mql.removeEventListener("change", onChange);
3483
- }, [theme]);
3484
- if (theme === "dark") return DARK_THEME;
3485
- if (theme === "auto" && prefersDark) return DARK_THEME;
3486
- return LIGHT_THEME;
3487
- }
3488
- var DEFAULT_MESSAGE = "We use cookies to understand ad performance and improve how our marketing works across visits. You can accept or decline this tracking.";
3489
- function ConsentBanner({
3490
- message,
3491
- title,
3492
- acceptLabel = "Accept",
3493
- declineLabel = "Decline",
3494
- policyHref,
3495
- policyLabel = "Learn more",
3496
- onAccept,
3497
- onDecline,
3498
- position = "bottom",
3499
- theme = "light",
3500
- className,
3501
- style
3502
- } = {}) {
3503
- const { isPending, accept, decline } = useConsent();
3504
- const tokens = useResolvedTheme(theme);
3505
- const [hasMounted, setHasMounted] = (0, import_react2.useState)(false);
3506
- (0, import_react2.useEffect)(() => {
3507
- setHasMounted(true);
3508
- }, []);
3509
- if (!hasMounted || !isPending) return null;
3510
- const wrapperStyle = {
3511
- position: "fixed",
3512
- left: 0,
3513
- right: 0,
3514
- [position]: 0,
3515
- zIndex: 2147483640,
3516
- background: tokens.background,
3517
- color: tokens.text,
3518
- borderTop: position === "bottom" ? `1px solid ${tokens.border}` : "none",
3519
- borderBottom: position === "top" ? `1px solid ${tokens.border}` : "none",
3520
- boxShadow: tokens.shadow,
3521
- padding: "16px 20px",
3522
- boxSizing: "border-box",
3523
- animation: `${ANIMATION_NAME}-${position} 200ms ease-out`,
3524
- fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
3525
- ...style
3526
- };
3527
- const innerStyle = {
3528
- maxWidth: 1100,
3529
- margin: "0 auto",
3530
- display: "flex",
3531
- flexWrap: "wrap",
3532
- gap: 16,
3533
- alignItems: "center",
3534
- justifyContent: "space-between"
3535
- };
3536
- const messageStyle = {
3537
- flex: "1 1 320px",
3538
- margin: 0,
3539
- fontSize: 14,
3540
- lineHeight: 1.5,
3541
- color: tokens.mutedText
3542
- };
3543
- const titleStyle = {
3544
- margin: "0 0 4px 0",
3545
- fontSize: 14,
3546
- fontWeight: 600,
3547
- color: tokens.text
3548
- };
3549
- const actionsStyle = {
3550
- display: "flex",
3551
- gap: 8,
3552
- flexShrink: 0
3553
- };
3554
- const buttonBase = {
3555
- appearance: "none",
3556
- fontFamily: "inherit",
3557
- fontSize: 14,
3558
- fontWeight: 500,
3559
- padding: "8px 16px",
3560
- borderRadius: 6,
3561
- cursor: "pointer",
3562
- border: "1px solid transparent",
3563
- transition: "opacity 120ms ease"
3564
- };
3565
- const declineStyle = {
3566
- ...buttonBase,
3567
- background: tokens.declineBg,
3568
- color: tokens.declineText,
3569
- borderColor: tokens.declineBorder
3570
- };
3571
- const acceptStyle = {
3572
- ...buttonBase,
3573
- background: tokens.acceptBg,
3574
- color: tokens.acceptText
3575
- };
3576
- const linkStyle = {
3577
- color: tokens.linkColor,
3578
- textDecoration: "underline",
3579
- textUnderlineOffset: 2
3580
- };
3581
- const handleAccept = () => {
3582
- accept();
3583
- onAccept?.();
3584
- };
3585
- const handleDecline = () => {
3586
- decline();
3587
- onDecline?.();
3588
- };
3589
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3590
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: ANIMATION_KEYFRAMES }),
3591
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3592
- "div",
3593
- {
3594
- role: "dialog",
3595
- "aria-live": "polite",
3596
- "aria-label": "Cookie consent",
3597
- className,
3598
- style: wrapperStyle,
3599
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: innerStyle, children: [
3600
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "1 1 320px" }, children: [
3601
- title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: titleStyle, children: title }) : null,
3602
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: messageStyle, children: [
3603
- message ?? DEFAULT_MESSAGE,
3604
- policyHref ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3605
- " ",
3606
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: policyHref, style: linkStyle, children: policyLabel })
3607
- ] }) : null
3608
- ] })
3609
- ] }),
3610
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: actionsStyle, children: [
3611
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: handleDecline, style: declineStyle, children: declineLabel }),
3612
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: handleAccept, style: acceptStyle, children: acceptLabel })
3613
- ] })
3614
- ] })
3615
- }
3616
- )
3617
- ] });
3618
- }
3619
- var ANIMATION_NAME = "aranova-consent-banner";
3620
- var ANIMATION_KEYFRAMES = `
3621
- @keyframes ${ANIMATION_NAME}-bottom {
3622
- from { transform: translateY(100%); opacity: 0; }
3623
- to { transform: translateY(0); opacity: 1; }
3624
- }
3625
- @keyframes ${ANIMATION_NAME}-top {
3626
- from { transform: translateY(-100%); opacity: 0; }
3627
- to { transform: translateY(0); opacity: 1; }
3628
- }
3629
- `;
3630
-
3631
- // src/AdPlatformTracking.tsx
3632
- var import_script = __toESM(require("next/script"));
3633
- var import_react3 = require("react");
3634
- var import_jsx_runtime2 = require("react/jsx-runtime");
3635
- function AdPlatformTracking({
3636
- gtagId,
3637
- gtagIds,
3638
- trackingConfig,
3639
- standalonePageView = false,
3640
- metaPixelId,
3641
- metaPixelIds
3642
- }) {
3643
- const trackingConfigKey2 = trackingConfig ? `${resolveTrackingConfigUrl(trackingConfig)}:${trackingConfig.businessId}:${trackingConfig.environment}` : "";
3644
- (0, import_react3.useEffect)(() => {
3645
- if (!trackingConfig) return;
3646
- const runtime = getTrackingConfigRuntime(trackingConfig);
3647
- if (standalonePageView) runtime.queuePageView();
3648
- else runtime.start();
3649
- }, [trackingConfigKey2, standalonePageView]);
3650
- const gtagAll = trackingConfig ? [] : gtagIds ? Object.values(gtagIds).filter((id) => id != null) : gtagId ? [gtagId] : [];
3651
- const metaAll = metaPixelIds ? Object.values(metaPixelIds).filter((id) => id != null) : metaPixelId ? [metaPixelId] : [];
3652
- if (trackingConfig || gtagAll.length > 0) registerCapability("ad_tags_google");
3653
- if (metaAll.length > 0) registerCapability("ad_tags_meta");
3654
- const gtagLoaderId = gtagAll[0];
3655
- const gtagInitScript = gtagAll.length === 1 ? createGtagInitScript(gtagAll[0]) : createGtagInitScriptMulti(gtagAll);
3656
- const metaInitScript = metaAll.length === 1 ? createMetaPixelInitScript(metaAll[0]) : createMetaPixelInitScriptMulti(metaAll);
3657
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
3658
- gtagLoaderId ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
3659
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_script.default, { id: "consent-default", strategy: "beforeInteractive", children: createConsentDefaultScript() }),
3660
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3661
- import_script.default,
3662
- {
3663
- src: `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gtagLoaderId)}`,
3664
- strategy: "afterInteractive"
3665
- }
3666
- ),
3667
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_script.default, { id: "gtag-init", strategy: "afterInteractive", children: gtagInitScript })
3668
- ] }) : null,
3669
- metaAll.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
3670
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_script.default, { id: "meta-pixel-base", strategy: "afterInteractive", children: createMetaConsentDefaultScript() }),
3671
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_script.default, { id: "meta-pixel-init", strategy: "afterInteractive", children: metaInitScript })
3672
- ] }) : null
3673
- ] });
3674
- }
3675
-
3676
- // src/GoogleAdsTracking.tsx
3677
- var import_script2 = __toESM(require("next/script"));
3678
- var import_jsx_runtime3 = require("react/jsx-runtime");
3679
- function GoogleAdsTracking(props) {
3680
- const { gtagId, gtagIds } = props;
3681
- const allIds = gtagIds ? Object.values(gtagIds).filter((id) => id != null) : gtagId ? [gtagId] : [];
3682
- if (allIds.length === 0) return null;
3683
- const loaderId = allIds[0];
3684
- const initScript = allIds.length === 1 ? createGtagInitScript(loaderId) : createGtagInitScriptMulti(allIds);
3685
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
3686
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_script2.default, { id: "consent-default", strategy: "beforeInteractive", children: createConsentDefaultScript() }),
3687
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3688
- import_script2.default,
3689
- {
3690
- src: `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(loaderId)}`,
3691
- strategy: "afterInteractive"
3692
- }
3693
- ),
3694
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_script2.default, { id: "gtag-init", strategy: "afterInteractive", children: initScript })
3695
- ] });
3696
- }
3697
-
3698
- // src/factory.tsx
3699
- var import_navigation3 = require("next/navigation");
3700
-
3701
- // package.json
3702
- var version = "0.24.1";
3703
-
3704
- // src/factory.tsx
3705
- var import_react5 = require("react");
3706
-
3707
- // ../tracking-core/src/phone-react.tsx
3708
- var import_react4 = require("react");
3709
- var import_jsx_runtime4 = require("react/jsx-runtime");
3710
- var _phoneConfigContext;
3711
- function phoneConfigContext() {
3712
- return _phoneConfigContext ?? (_phoneConfigContext = (0, import_react4.createContext)(null));
3713
- }
3714
- function PhoneConfigProvider({
3715
- value,
3716
- children
3717
- }) {
3718
- const Ctx = phoneConfigContext();
3719
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Ctx.Provider, { value, children });
3720
- }
3721
- function usePhoneConfig() {
3722
- const ctx = (0, import_react4.useContext)(phoneConfigContext());
3723
- return {
3724
- defaultCountry: ctx?.defaultCountry ?? DEFAULT_PHONE_COUNTRY,
3725
- display: ctx?.display ?? "national"
3726
- };
3727
- }
3728
- function usePhoneField(opts = {}) {
3729
- registerCapability("phone_fields");
3730
- const cfg = usePhoneConfig();
3731
- const country = opts.country ?? cfg.defaultCountry;
3732
- const display = opts.display ?? cfg.display;
3733
- const { onValueChange } = opts;
3734
- const [value, setValue] = (0, import_react4.useState)(() => formatPhoneAsTyped(opts.defaultValue ?? "", country));
3735
- const [touched, setTouched] = (0, import_react4.useState)(false);
3736
- const parsed = (0, import_react4.useMemo)(() => parsePhone(value, country), [value, country]);
3737
- const onChange = (0, import_react4.useCallback)(
3738
- (event) => {
3739
- const next = formatPhoneAsTyped(event.target.value, country);
3740
- setValue(next);
3741
- onValueChange?.(parsePhone(next, country).e164);
3742
- },
3743
- [country, onValueChange]
3744
- );
3745
- const onBlur = (0, import_react4.useCallback)(
3746
- (_event) => {
3747
- setTouched(true);
3748
- setValue((current) => {
3749
- const p = parsePhone(current, country);
3750
- return p.isValid ? formatPhone(current, display, country) : current;
3751
- });
3752
- },
3753
- [country, display]
3754
- );
3755
- const error = touched && value.length > 0 && !parsed.isValid ? "Enter a valid phone number" : null;
3756
- return {
3757
- value,
3758
- e164: parsed.e164,
3759
- isValid: parsed.isValid,
3760
- error,
3761
- parsed,
3762
- inputProps: { value, onChange, onBlur, type: "tel", inputMode: "tel", autoComplete: "tel" }
3763
- };
3764
- }
3765
- var PhoneField = (0, import_react4.forwardRef)(function PhoneField2({ country, value, defaultValue, onChange, onE164Change, ...rest }, ref) {
3766
- const cfg = usePhoneConfig();
3767
- const resolvedCountry = country ?? cfg.defaultCountry;
3768
- const isControlled = value !== void 0;
3769
- const [internal, setInternal] = (0, import_react4.useState)(
3770
- () => formatPhoneAsTyped(defaultValue ?? "", resolvedCountry)
3771
- );
3772
- const handleChange = (event) => {
3773
- const formatted = formatPhoneAsTyped(event.target.value, resolvedCountry);
3774
- event.target.value = formatted;
3775
- onE164Change?.(parsePhone(formatted, resolvedCountry).e164);
3776
- if (!isControlled) setInternal(formatted);
3777
- onChange?.(event);
3778
- };
3779
- const shown = isControlled ? formatPhoneAsTyped(value, resolvedCountry) : internal;
3780
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3781
- "input",
3782
- {
3783
- ...rest,
3784
- ref,
3785
- type: "tel",
3786
- inputMode: "tel",
3787
- autoComplete: "tel",
3788
- value: shown,
3789
- onChange: handleChange
3790
- }
3791
- );
3792
- });
3793
-
3794
- // src/factory.tsx
3795
- var import_jsx_runtime5 = require("react/jsx-runtime");
3796
- function PageViewTracker({ fire }) {
3797
- const pathname = (0, import_navigation3.usePathname)();
3798
- const searchParams = (0, import_navigation3.useSearchParams)();
3799
- (0, import_react5.useEffect)(() => {
3800
- fire();
3801
- }, [pathname, searchParams, fire]);
3802
- return null;
3803
- }
3804
- function createTracking(options) {
3805
- const {
3806
- apiKey,
3807
- endpoint,
3808
- triggers,
3809
- environment,
3810
- gtagIds,
3811
- debug,
3812
- phone,
3813
- conversionConfig,
3814
- trackingConfig
3815
- } = options;
3816
- registerCapability("base_tracking");
3817
- if (trackingConfig) registerCapability("conversion_goals_auto");
3818
- if (!apiKey || !endpoint) {
3819
- if (apiKey || endpoint) {
3820
- console.warn(
3821
- "[AranovaTracking] createTracking() requires both `apiKey` and `endpoint`. Tracking is disabled for this session."
3822
- );
3823
- }
3824
- const noopClient = {
3825
- trackEvent: () => {
3826
- },
3827
- flush: async () => {
3828
- },
3829
- getSessionId: () => "",
3830
- getVisitorId: () => ""
3831
- };
3832
- return {
3833
- TrackingProvider: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PhoneConfigProvider, { value: phone ?? null, children }),
3834
- useTracking: () => noopClient
3835
- };
3836
- }
3837
- const TrackingContext = (0, import_react5.createContext)(null);
3838
- const specificPageVisitConfig = triggers.automatic.specific_page_visit ?? null;
3839
- const timeOnSiteConfig = triggers.automatic.time_on_site ?? null;
3840
- function TrackingProvider({ children }) {
3841
- const pathname = (0, import_navigation3.usePathname)();
3842
- const rawClient = (0, import_react5.useMemo)(
3843
- () => getOrCreateTrackingClient({
3844
- apiKey,
3845
- endpoint,
3846
- surface: "next",
3847
- packageName: "@aranova/tracking-next",
3848
- sdkVersion: version,
3849
- triggers,
3850
- environment,
3851
- activeGtagIds: gtagIds,
3852
- debug
3853
- }),
3854
- []
3855
- );
3856
- const conversionStore = (0, import_react5.useMemo)(
3857
- () => trackingConfig ? getTrackingConfigRuntime(trackingConfig) : conversionConfig ? resolveConversionConfig({
3858
- cdnUrl: conversionConfig.cdnUrl,
3859
- baked: conversionConfig.baked
3860
- }) : null,
3861
- []
3862
- );
3863
- const wrapClient = (0, import_react5.useMemo)(() => {
3864
- const autoFire = conversionStore ? createConversionAutoFire(conversionStore) : null;
3865
- return (c) => autoFire ? withConversionAutoFire(c, autoFire) : c;
3866
- }, [conversionStore]);
3867
- const client = (0, import_react5.useMemo)(
3868
- () => createTypedClient(wrapClient(rawClient), triggers, { debug }),
3869
- [rawClient, wrapClient]
3870
- );
3871
- (0, import_react5.useEffect)(() => {
3872
- if (trackingConfig && conversionStore && "start" in conversionStore) {
3873
- conversionStore.start();
3874
- }
3875
- }, [conversionStore]);
3876
- const firePageView = (0, import_react5.useCallback)(() => {
3877
- fireManualPageView(wrapClient(rawClient));
3878
- if (trackingConfig) getTrackingConfigRuntime(trackingConfig).queuePageView();
3879
- }, [rawClient, wrapClient]);
3880
- const firedPagesRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
3881
- (0, import_react5.useEffect)(() => {
3882
- if (!specificPageVisitConfig || pathname === null) return;
3883
- const detectorClient = wrapClient(rawClient);
3884
- for (const { name, pathPattern } of specificPageVisitConfig.pages) {
3885
- if (!pathPattern.test(pathname)) continue;
3886
- const key = `${name}:${pathname}`;
3887
- if (firedPagesRef.current.has(key)) continue;
3888
- firedPagesRef.current.add(key);
3889
- detectorClient.trackEvent({
3890
- eventType: "specific_page_visit",
3891
- metadata: {
3892
- page_name: name,
3893
- page: { path: pathname }
3894
- },
3895
- pageUrl: null,
3896
- occurredAt: null
3897
- });
3898
- }
3899
- }, [pathname, rawClient, wrapClient]);
3900
- const multiPageSessionConfig = triggers.automatic.multi_page_session ?? null;
3901
- const mpsLastPathRef = (0, import_react5.useRef)(null);
3902
- (0, import_react5.useEffect)(() => {
3903
- if (!multiPageSessionConfig || pathname === null) return;
3904
- if (pathname === mpsLastPathRef.current) return;
3905
- mpsLastPathRef.current = pathname;
3906
- const MPS_PATHS_KEY = "aranova_tracking_mps_paths";
3907
- const MPS_SESSION_KEY = "aranova_tracking_mps_session";
3908
- const MPS_FIRED_KEY = "aranova_tracking_mps_fired";
3909
- try {
3910
- const store = window.sessionStorage;
3911
- const currentSession = getOrRotateSessionId().id;
3912
- const storedSession = store.getItem(MPS_SESSION_KEY);
3913
- if (storedSession !== currentSession) {
3914
- store.setItem(MPS_SESSION_KEY, currentSession);
3915
- store.removeItem(MPS_PATHS_KEY);
3916
- store.removeItem(MPS_FIRED_KEY);
3917
- }
3918
- if (store.getItem(MPS_FIRED_KEY) === "1") return;
3919
- const raw = store.getItem(MPS_PATHS_KEY);
3920
- const paths = raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
3921
- paths.add(pathname);
3922
- store.setItem(MPS_PATHS_KEY, JSON.stringify([...paths]));
3923
- if (paths.size >= multiPageSessionConfig.pageThreshold) {
3924
- store.setItem(MPS_FIRED_KEY, "1");
3925
- wrapClient(rawClient).trackEvent({
3926
- eventType: "multi_page_session",
3927
- metadata: {
3928
- page_count: paths.size,
3929
- page: { path: pathname }
3930
- },
3931
- pageUrl: null,
3932
- occurredAt: null
3933
- });
3934
- }
3935
- } catch {
3936
- }
3937
- }, [multiPageSessionConfig, pathname, rawClient, wrapClient]);
3938
- (0, import_react5.useEffect)(() => {
3939
- return attachClientCapturesOnce(rawClient, () => {
3940
- const detachers = [];
3941
- const detectorClient = wrapClient(rawClient);
3942
- detachers.push(attachPageExit(detectorClient));
3943
- detachers.push(attachBfcacheRestore(detectorClient));
3944
- if (timeOnSiteConfig) {
3945
- detachers.push(attachTimeOnSite(detectorClient, timeOnSiteConfig));
3946
- }
3947
- const scrollDepth = triggers.automatic.scroll_depth;
3948
- if (scrollDepth) {
3949
- detachers.push(attachScrollDepth(detectorClient, scrollDepth));
3950
- }
3951
- const formStart = triggers.automatic.form_start;
3952
- if (formStart) {
3953
- detachers.push(attachFormStart(detectorClient, formStart));
3954
- }
3955
- const ctaClick = triggers.manual?.cta_click;
3956
- if (ctaClick) {
3957
- detachers.push(attachCtaClickCapture(detectorClient, ctaClick));
3958
- }
3959
- const phoneClick = triggers.manual?.phone_click;
3960
- if (phoneClick) {
3961
- detachers.push(attachPhoneClickCapture(detectorClient, phoneClick));
3962
- }
3963
- return () => {
3964
- for (let i = detachers.length - 1; i >= 0; i--) {
3965
- detachers[i]();
3966
- }
3967
- };
3968
- });
3969
- }, [rawClient, wrapClient]);
3970
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(TrackingContext.Provider, { value: client, children: [
3971
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react5.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PageViewTracker, { fire: firePageView }) }),
3972
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PhoneConfigProvider, { value: phone ?? null, children })
3973
- ] });
3974
- }
3975
- function useTracking() {
3976
- const client = (0, import_react5.useContext)(TrackingContext);
3977
- if (client === null) {
3978
- throw new Error(
3979
- "useTracking must be called inside a <TrackingProvider> returned by createTracking()"
3980
- );
3981
- }
3982
- return client;
3983
- }
3984
- return { TrackingProvider, useTracking };
3985
- }
2099
+ // src/index.ts
2100
+ var import_client = require("@aranova/tracking-next/client");
3986
2101
 
3987
2102
  // ../tracking-core/src/resources/calendar/transport.ts
3988
2103
  async function calendarRequest(config, method, path, body) {
@@ -4306,21 +2421,21 @@ function groupSlotsByDay(slots, timeZone) {
4306
2421
  }
4307
2422
 
4308
2423
  // ../tracking-core/src/calendar-react.tsx
4309
- var import_react6 = require("react");
4310
- var import_jsx_runtime6 = require("react/jsx-runtime");
2424
+ var import_react = require("react");
2425
+ var import_jsx_runtime = require("react/jsx-runtime");
4311
2426
  var _calendarClientContext;
4312
2427
  function calendarClientContext() {
4313
- return _calendarClientContext ?? (_calendarClientContext = (0, import_react6.createContext)(null));
2428
+ return _calendarClientContext ?? (_calendarClientContext = (0, import_react.createContext)(null));
4314
2429
  }
4315
2430
  function CalendarClientProvider({
4316
2431
  value,
4317
2432
  children
4318
2433
  }) {
4319
2434
  const Ctx = calendarClientContext();
4320
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Ctx.Provider, { value, children });
2435
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ctx.Provider, { value, children });
4321
2436
  }
4322
2437
  function useCalendarClient() {
4323
- const client = (0, import_react6.useContext)(calendarClientContext());
2438
+ const client = (0, import_react.useContext)(calendarClientContext());
4324
2439
  if (!client) {
4325
2440
  throw new Error(
4326
2441
  "useCalendarClient requires <CalendarClientProvider>. Wire it via createTracking({ calendar })."
@@ -4330,12 +2445,12 @@ function useCalendarClient() {
4330
2445
  }
4331
2446
  function useCalendarBusy(calendarKey, window2) {
4332
2447
  const client = useCalendarClient();
4333
- const [busy, setBusy] = (0, import_react6.useState)([]);
4334
- const [timeZone, setTimeZone] = (0, import_react6.useState)(resolveBrowserTimeZone());
4335
- const [isLoading, setIsLoading] = (0, import_react6.useState)(true);
4336
- const [error, setError] = (0, import_react6.useState)(null);
4337
- const [nonce, setNonce] = (0, import_react6.useState)(0);
4338
- (0, import_react6.useEffect)(() => {
2448
+ const [busy, setBusy] = (0, import_react.useState)([]);
2449
+ const [timeZone, setTimeZone] = (0, import_react.useState)(resolveBrowserTimeZone());
2450
+ const [isLoading, setIsLoading] = (0, import_react.useState)(true);
2451
+ const [error, setError] = (0, import_react.useState)(null);
2452
+ const [nonce, setNonce] = (0, import_react.useState)(0);
2453
+ (0, import_react.useEffect)(() => {
4339
2454
  const controller = new AbortController();
4340
2455
  setIsLoading(true);
4341
2456
  client.busy(calendarKey, { since: window2.since, until: window2.until }).then((response) => {
@@ -4351,22 +2466,22 @@ function useCalendarBusy(calendarKey, window2) {
4351
2466
  });
4352
2467
  return () => controller.abort();
4353
2468
  }, [client, calendarKey, window2.since, window2.until, nonce]);
4354
- const refetch = (0, import_react6.useCallback)(() => setNonce((value) => value + 1), []);
2469
+ const refetch = (0, import_react.useCallback)(() => setNonce((value) => value + 1), []);
4355
2470
  return { busy, timeZone, isLoading, error, refetch };
4356
2471
  }
4357
2472
  function useSlots(busy, timeZone, options) {
4358
2473
  const zone = options.timeZone ?? timeZone;
4359
- return (0, import_react6.useMemo)(() => {
2474
+ return (0, import_react.useMemo)(() => {
4360
2475
  const { slots, nextAvailableAt } = computeSlots({ ...options, busy, timeZone: zone });
4361
2476
  return { slots, days: groupSlotsByDay(slots, zone), nextAvailableAt };
4362
2477
  }, [busy, zone, options]);
4363
2478
  }
4364
2479
  function useBookingForm(options = {}) {
4365
2480
  const endpoint = options.endpoint ?? "/api/aranova/calendar/bookings";
4366
- const [status, setStatus] = (0, import_react6.useState)("idle");
4367
- const [error, setError] = (0, import_react6.useState)(null);
4368
- const [booking, setBooking] = (0, import_react6.useState)(null);
4369
- const submit = (0, import_react6.useCallback)(
2481
+ const [status, setStatus] = (0, import_react.useState)("idle");
2482
+ const [error, setError] = (0, import_react.useState)(null);
2483
+ const [booking, setBooking] = (0, import_react.useState)(null);
2484
+ const submit = (0, import_react.useCallback)(
4370
2485
  async (input) => {
4371
2486
  setStatus("submitting");
4372
2487
  setError(null);
@@ -4391,13 +2506,16 @@ function useBookingForm(options = {}) {
4391
2506
  },
4392
2507
  [endpoint, options]
4393
2508
  );
4394
- const reset = (0, import_react6.useCallback)(() => {
2509
+ const reset = (0, import_react.useCallback)(() => {
4395
2510
  setStatus("idle");
4396
2511
  setError(null);
4397
2512
  setBooking(null);
4398
2513
  }, []);
4399
2514
  return { status, error, booking, submit, reset };
4400
2515
  }
2516
+
2517
+ // src/index.ts
2518
+ var import_client2 = require("@aranova/tracking-next/client");
4401
2519
  // Annotate the CommonJS export names for ESM import in node:
4402
2520
  0 && (module.exports = {
4403
2521
  AdPlatformTracking,