@aranova/tracking-react 0.12.0 → 0.12.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.mjs CHANGED
@@ -16,31 +16,26 @@ function buildConsentPayload(state) {
16
16
  };
17
17
  }
18
18
  function getConsentState() {
19
- if (typeof window === "undefined")
20
- return "pending";
19
+ if (typeof window === "undefined") return "pending";
21
20
  const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
22
- if (storedState === "granted" || storedState === "denied")
23
- return storedState;
21
+ if (storedState === "granted" || storedState === "denied") return storedState;
24
22
  return "pending";
25
23
  }
26
24
  function setConsentState(state) {
27
- if (typeof window === "undefined")
28
- return;
25
+ if (typeof window === "undefined") return;
29
26
  window.localStorage.setItem(CONSENT_STATE_KEY, state);
30
27
  window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, (/* @__PURE__ */ new Date()).toISOString());
31
28
  if (typeof window.gtag === "function")
32
29
  window.gtag("consent", "update", buildConsentPayload(state));
33
30
  }
34
31
  function resetConsent() {
35
- if (typeof window === "undefined")
36
- return;
32
+ if (typeof window === "undefined") return;
37
33
  window.localStorage.removeItem(CONSENT_STATE_KEY);
38
34
  window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
39
35
  }
40
36
  function restoreStoredConsent() {
41
37
  const consentState = getConsentState();
42
- if (consentState === "granted" || consentState === "denied")
43
- setConsentState(consentState);
38
+ if (consentState === "granted" || consentState === "denied") setConsentState(consentState);
44
39
  return consentState;
45
40
  }
46
41
 
@@ -97,8 +92,7 @@ function isValidGtagId(id) {
97
92
  }
98
93
  function ensureGtagFunction() {
99
94
  window.dataLayer = window.dataLayer || [];
100
- if (typeof window.gtag === "function")
101
- return window.gtag;
95
+ if (typeof window.gtag === "function") return window.gtag;
102
96
  window.gtag = (...args) => {
103
97
  window.dataLayer?.push(args);
104
98
  };
@@ -115,17 +109,17 @@ function applyDefaultConsentState() {
115
109
  });
116
110
  }
117
111
  function loadGtagScript(gtagId) {
118
- if (typeof document === "undefined")
119
- return;
112
+ if (typeof document === "undefined") return;
120
113
  const marker = getScriptMarker("gtag-loader");
121
- const existingScript = document.querySelector(`script[${TRACKING_SCRIPT_ATTRIBUTE}="${marker}"]`);
122
- if (existingScript)
123
- return;
114
+ const existingScript = document.querySelector(
115
+ `script[${TRACKING_SCRIPT_ATTRIBUTE}="${marker}"]`
116
+ );
117
+ if (existingScript) return;
124
118
  const script = document.createElement("script");
125
119
  script.async = true;
126
120
  script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;
127
121
  script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);
128
- document.head.appendChild(script);
122
+ document.head.append(script);
129
123
  }
130
124
  function initializeGtag(gtagId) {
131
125
  const gtag = ensureGtagFunction();
@@ -133,21 +127,17 @@ function initializeGtag(gtagId) {
133
127
  gtag("config", gtagId);
134
128
  }
135
129
  function bootstrapGoogleAdsTracking(gtagId) {
136
- if (typeof window === "undefined" || typeof document === "undefined")
137
- return;
138
- if (!isValidGtagId(gtagId))
139
- return;
130
+ if (typeof window === "undefined" || typeof document === "undefined") return;
131
+ if (!isValidGtagId(gtagId)) return;
140
132
  applyDefaultConsentState();
141
133
  loadGtagScript(gtagId);
142
134
  initializeGtag(gtagId);
143
135
  restoreStoredConsent();
144
136
  }
145
137
  function bootstrapMultipleGtags(gtagIds) {
146
- if (typeof window === "undefined" || typeof document === "undefined")
147
- return;
138
+ if (typeof window === "undefined" || typeof document === "undefined") return;
148
139
  const ids = Object.values(gtagIds).filter(isValidGtagId);
149
- if (ids.length === 0)
150
- return;
140
+ if (ids.length === 0) return;
151
141
  applyDefaultConsentState();
152
142
  loadGtagScript(ids[0]);
153
143
  const gtag = ensureGtagFunction();
@@ -199,18 +189,15 @@ function getTrackingQueryValues(searchParams) {
199
189
  }, {});
200
190
  }
201
191
  function getCookieValueFromDocument(key) {
202
- if (typeof document === "undefined")
203
- return null;
192
+ if (typeof document === "undefined") return null;
204
193
  const cookies = document.cookie ? document.cookie.split("; ") : [];
205
194
  const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));
206
- if (!match)
207
- return null;
195
+ if (!match) return null;
208
196
  const [, rawValue = ""] = match.split("=");
209
197
  return normalizeTrackingCookieValue(decodeURIComponent(rawValue));
210
198
  }
211
199
  function setTrackingCookie(key, value, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
212
- if (typeof document === "undefined")
213
- return;
200
+ if (typeof document === "undefined") return;
214
201
  const encodedValue = encodeURIComponent(value);
215
202
  document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;
216
203
  }
@@ -250,18 +237,15 @@ function writeLocalStorage(key, value) {
250
237
  }
251
238
  }
252
239
  function getVisitorId() {
253
- if (typeof window === "undefined")
254
- return safeUuid();
240
+ if (typeof window === "undefined") return safeUuid();
255
241
  const existing = readLocalStorage(VISITOR_STORAGE_KEY);
256
- if (existing && existing.length > 0)
257
- return existing;
242
+ if (existing && existing.length > 0) return existing;
258
243
  const fresh = safeUuid();
259
244
  writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
260
245
  return fresh;
261
246
  }
262
247
  function getOrRotateSessionId(now = Date.now()) {
263
- if (typeof window === "undefined")
264
- return { id: safeUuid(), isNew: true };
248
+ if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
265
249
  const raw = readLocalStorage(SESSION_STORAGE_KEY);
266
250
  if (raw) {
267
251
  try {
@@ -334,8 +318,7 @@ function setLastFiredUrl(url) {
334
318
  writeSessionStorage(LAST_FIRED_URL_STORAGE_KEY, url);
335
319
  }
336
320
  function buildPageViewMetadata(referrerOverride) {
337
- if (typeof window === "undefined" || typeof document === "undefined")
338
- return null;
321
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
339
322
  return pageViewMetadataSchema.parse({
340
323
  page: {
341
324
  title: document.title || null,
@@ -348,8 +331,7 @@ function buildPageViewMetadata(referrerOverride) {
348
331
  });
349
332
  }
350
333
  function fireManualPageView(client) {
351
- if (typeof window === "undefined")
352
- return;
334
+ if (typeof window === "undefined") return;
353
335
  const currentHref = window.location.href;
354
336
  const previousFiredUrl = getLastFiredUrl();
355
337
  const internalReferrer = previousFiredUrl !== null && previousFiredUrl !== currentHref ? previousFiredUrl : null;
@@ -385,8 +367,7 @@ function attachAutoPageView(client, options = {}) {
385
367
  let lastPath = window.location.pathname + window.location.search;
386
368
  function maybeFire() {
387
369
  const current = window.location.pathname + window.location.search;
388
- if (current === lastPath)
389
- return;
370
+ if (current === lastPath) return;
390
371
  lastPath = current;
391
372
  fireManualPageView(client);
392
373
  }
@@ -408,8 +389,7 @@ function attachAutoPageView(client, options = {}) {
408
389
  history.replaceState = patchedReplaceState;
409
390
  window.addEventListener("popstate", maybeFire);
410
391
  window.addEventListener("pageshow", handlePageShow);
411
- if (!options.skipInitial)
412
- fireManualPageView(client);
392
+ if (!options.skipInitial) fireManualPageView(client);
413
393
  return () => {
414
394
  history.pushState = originalPushState;
415
395
  history.replaceState = originalReplaceState;
@@ -491,8 +471,7 @@ function buildContext(surface, sdkVersion, packageName, environment, activeGtagI
491
471
  };
492
472
  }
493
473
  function readTrackingParams() {
494
- if (typeof window === "undefined")
495
- return createEmptyTrackingParams();
474
+ if (typeof window === "undefined") return createEmptyTrackingParams();
496
475
  try {
497
476
  captureTrackingParamsFromLocation();
498
477
  } catch {
@@ -507,8 +486,7 @@ function consentSnapshot() {
507
486
  }
508
487
  }
509
488
  async function postWithFetch(url, body, apiKey, identity, keepalive) {
510
- if (typeof fetch !== "function")
511
- return;
489
+ if (typeof fetch !== "function") return;
512
490
  try {
513
491
  await fetch(url, {
514
492
  method: "POST",
@@ -568,8 +546,7 @@ function createTrackingClient(config) {
568
546
  const visitorId = getVisitorId();
569
547
  const initialSession = getOrRotateSessionId();
570
548
  let sessionId = initialSession.id;
571
- if (typeof window !== "undefined")
572
- firstPage = window.location.href;
549
+ if (typeof window !== "undefined") firstPage = window.location.href;
573
550
  function enqueueHeartbeat() {
574
551
  const metadata = buildHeartbeatMetadata(
575
552
  config.surface,
@@ -595,7 +572,13 @@ function createTrackingClient(config) {
595
572
  }
596
573
  sessionId = rotated.id;
597
574
  const params = readTrackingParams();
598
- const context = buildContext(config.surface, sdkVersion, packageName, environment, activeGtagIds);
575
+ const context = buildContext(
576
+ config.surface,
577
+ sdkVersion,
578
+ packageName,
579
+ environment,
580
+ activeGtagIds
581
+ );
599
582
  return {
600
583
  session_id: sessionId,
601
584
  visitor_id: visitorId,
@@ -612,8 +595,7 @@ function createTrackingClient(config) {
612
595
  };
613
596
  }
614
597
  function scheduleFlush() {
615
- if (flushTimer !== null || destroyed)
616
- return;
598
+ if (flushTimer !== null || destroyed) return;
617
599
  flushTimer = setTimeout(() => {
618
600
  flushTimer = null;
619
601
  void flush();
@@ -626,8 +608,7 @@ function createTrackingClient(config) {
626
608
  }
627
609
  }
628
610
  async function flush() {
629
- if (queue.length === 0)
630
- return;
611
+ if (queue.length === 0) return;
631
612
  const events = queue.slice(0, HARD_MAX_BATCH);
632
613
  queue = queue.slice(events.length);
633
614
  clearScheduledFlush();
@@ -639,10 +620,8 @@ function createTrackingClient(config) {
639
620
  await postWithFetch(eventsUrl, serialized, config.apiKey, identityHeaders2, false);
640
621
  }
641
622
  function trackEvent(input) {
642
- if (destroyed)
643
- return;
644
- if (!input || typeof input.eventType !== "string" || input.eventType.length === 0)
645
- return;
623
+ if (destroyed) return;
624
+ if (!input || typeof input.eventType !== "string" || input.eventType.length === 0) return;
646
625
  const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
647
626
  queue.push({
648
627
  event_type: input.eventType,
@@ -657,8 +636,7 @@ function createTrackingClient(config) {
657
636
  }
658
637
  }
659
638
  function flushOnUnload() {
660
- if (queue.length === 0)
661
- return;
639
+ if (queue.length === 0) return;
662
640
  const events = queue.slice(0, HARD_MAX_BATCH);
663
641
  queue = queue.slice(events.length);
664
642
  clearScheduledFlush();
@@ -1424,11 +1402,6 @@ function createSalesClient(config) {
1424
1402
  };
1425
1403
  }
1426
1404
 
1427
- // ../tracking-core/src/resources/services.ts
1428
- async function fetchServices(config) {
1429
- return salesRequest(config, "GET", "/services");
1430
- }
1431
-
1432
1405
  // ../tracking-core/src/resources/sales/money.ts
1433
1406
  var MINOR_UNIT_EXPONENT = {
1434
1407
  USD: 2,
@@ -1462,6 +1435,123 @@ function formatDateInTz(iso, timeZone, opts, locale) {
1462
1435
  }).format(date);
1463
1436
  }
1464
1437
 
1438
+ // ../tracking-core/src/resources/sales/schema.ts
1439
+ import { z as z11 } from "zod";
1440
+ var SUPPORTED_CURRENCIES = ["USD", "CAD"];
1441
+ var TRACKING_ENVIRONMENTS = ["production", "development"];
1442
+ var currencySchema = z11.enum(SUPPORTED_CURRENCIES);
1443
+ var centsSchema = z11.number().int().nonnegative();
1444
+ var quantitySchema = z11.string().regex(/^\d+(\.\d{1,3})?$/);
1445
+ var metadataSchema = z11.record(z11.unknown());
1446
+ var saleItemSchema = z11.object({
1447
+ external_item_id: z11.string().nullable().optional(),
1448
+ name: z11.string().nullable().optional(),
1449
+ category: z11.string().nullable().optional(),
1450
+ quantity: quantitySchema,
1451
+ unit_price_cents: centsSchema,
1452
+ // Non-negativity validated on the wire — same contract as the other cents
1453
+ // fields — and backstopped by the DB CHECK.
1454
+ unit_cost_cents: centsSchema.nullable().optional()
1455
+ }).strict();
1456
+ var saleServiceSchema = z11.object({
1457
+ service: z11.string(),
1458
+ amount_cents: centsSchema
1459
+ }).strict();
1460
+ var customerNameSchema = z11.string().max(200);
1461
+ var customerPhoneSchema = z11.string().max(64);
1462
+ var customerEmailSchema = z11.string().max(320).email();
1463
+ function refineServiceXor(val, ctx, { requireAmount }) {
1464
+ if (val.services != null) {
1465
+ if (val.service != null) {
1466
+ ctx.addIssue({
1467
+ code: z11.ZodIssueCode.custom,
1468
+ message: "pass either `service` or `services`, not both",
1469
+ path: ["services"]
1470
+ });
1471
+ }
1472
+ if (val.services.length === 0) {
1473
+ ctx.addIssue({
1474
+ code: z11.ZodIssueCode.custom,
1475
+ message: "`services` must not be empty",
1476
+ path: ["services"]
1477
+ });
1478
+ }
1479
+ const keys = val.services.map((s) => s.service);
1480
+ if (new Set(keys).size !== keys.length) {
1481
+ ctx.addIssue({
1482
+ code: z11.ZodIssueCode.custom,
1483
+ message: "`services` must not list the same service more than once",
1484
+ path: ["services"]
1485
+ });
1486
+ }
1487
+ if (val.amount_total_cents != null) {
1488
+ const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
1489
+ if (val.amount_total_cents !== sum) {
1490
+ ctx.addIssue({
1491
+ code: z11.ZodIssueCode.custom,
1492
+ message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
1493
+ path: ["amount_total_cents"]
1494
+ });
1495
+ }
1496
+ }
1497
+ } else if (requireAmount && val.amount_total_cents == null) {
1498
+ ctx.addIssue({
1499
+ code: z11.ZodIssueCode.custom,
1500
+ message: "amount_total_cents is required unless `services` is provided",
1501
+ path: ["amount_total_cents"]
1502
+ });
1503
+ }
1504
+ }
1505
+ var saleCreateSchema = z11.object({
1506
+ external_id: z11.string().nullable().optional(),
1507
+ description: z11.string().nullable().optional(),
1508
+ service: z11.string().nullable().optional(),
1509
+ services: z11.array(saleServiceSchema).nullable().optional(),
1510
+ currency: currencySchema,
1511
+ // Optional only because the plural `services` form derives it from the sum
1512
+ // (see refineServiceXor); the singular/serviceless path still requires it.
1513
+ amount_total_cents: centsSchema.nullable().optional(),
1514
+ occurred_at: z11.string().datetime(),
1515
+ environment: z11.enum(TRACKING_ENVIRONMENTS).default("production"),
1516
+ items: z11.array(saleItemSchema).default([]),
1517
+ metadata: metadataSchema.nullable().optional(),
1518
+ customer_name: customerNameSchema.nullable().optional(),
1519
+ customer_phone: customerPhoneSchema.nullable().optional(),
1520
+ customer_email: customerEmailSchema.nullable().optional()
1521
+ }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
1522
+ var saleUpdateSchema = z11.object({
1523
+ description: z11.string().nullable().optional(),
1524
+ service: z11.string().nullable().optional(),
1525
+ services: z11.array(saleServiceSchema).nullable().optional(),
1526
+ currency: currencySchema.optional(),
1527
+ amount_total_cents: centsSchema.optional(),
1528
+ occurred_at: z11.string().datetime().optional(),
1529
+ items: z11.array(saleItemSchema).optional(),
1530
+ metadata: metadataSchema.nullable().optional(),
1531
+ customer_name: customerNameSchema.nullable().optional(),
1532
+ customer_phone: customerPhoneSchema.nullable().optional(),
1533
+ customer_email: customerEmailSchema.nullable().optional()
1534
+ }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: false }));
1535
+ var TRACKING_RANGES = ["24h", "7d", "30d"];
1536
+ var NAMED_RANGES = [
1537
+ "today",
1538
+ "yesterday",
1539
+ "wtd",
1540
+ "mtd",
1541
+ "qtd",
1542
+ "ytd",
1543
+ "24h",
1544
+ "7d",
1545
+ "30d",
1546
+ "90d",
1547
+ "custom"
1548
+ ];
1549
+
1550
+ // ../tracking-core/src/resources/services.ts
1551
+ async function fetchServices(config) {
1552
+ return salesRequest(config, "GET", "/services");
1553
+ }
1554
+
1465
1555
  // ../tracking-core/src/phone.ts
1466
1556
  import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
1467
1557
  var DEFAULT_PHONE_COUNTRY = "CA";
@@ -1747,10 +1837,7 @@ var ANIMATION_KEYFRAMES = `
1747
1837
  import { useEffect as useEffect3, useMemo } from "react";
1748
1838
  function GoogleAdsTracking(props) {
1749
1839
  const { gtagId, gtagIds } = props;
1750
- const gtagIdsKey = useMemo(
1751
- () => gtagIds ? JSON.stringify(gtagIds) : "",
1752
- [gtagIds]
1753
- );
1840
+ const gtagIdsKey = useMemo(() => gtagIds ? JSON.stringify(gtagIds) : "", [gtagIds]);
1754
1841
  useEffect3(() => {
1755
1842
  if (gtagIds && Object.keys(gtagIds).length > 0) {
1756
1843
  bootstrapMultipleGtags(gtagIds);
@@ -1762,15 +1849,10 @@ function GoogleAdsTracking(props) {
1762
1849
  }
1763
1850
 
1764
1851
  // src/factory.tsx
1765
- import {
1766
- createContext as createContext2,
1767
- useContext as useContext2,
1768
- useEffect as useEffect4,
1769
- useMemo as useMemo3
1770
- } from "react";
1852
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo3 } from "react";
1771
1853
 
1772
1854
  // package.json
1773
- var version = "0.12.0";
1855
+ var version = "0.12.1";
1774
1856
 
1775
1857
  // ../tracking-core/src/phone-react.tsx
1776
1858
  import {
@@ -1782,9 +1864,19 @@ import {
1782
1864
  useState as useState3
1783
1865
  } from "react";
1784
1866
  import { jsx as jsx2 } from "react/jsx-runtime";
1785
- var PhoneConfigContext = createContext(null);
1867
+ var _phoneConfigContext;
1868
+ function phoneConfigContext() {
1869
+ return _phoneConfigContext ?? (_phoneConfigContext = createContext(null));
1870
+ }
1871
+ function PhoneConfigProvider({
1872
+ value,
1873
+ children
1874
+ }) {
1875
+ const Ctx = phoneConfigContext();
1876
+ return /* @__PURE__ */ jsx2(Ctx.Provider, { value, children });
1877
+ }
1786
1878
  function usePhoneConfig() {
1787
- const ctx = useContext(PhoneConfigContext);
1879
+ const ctx = useContext(phoneConfigContext());
1788
1880
  return {
1789
1881
  defaultCountry: ctx?.defaultCountry ?? DEFAULT_PHONE_COUNTRY,
1790
1882
  display: ctx?.display ?? "national"
@@ -1877,7 +1969,7 @@ function createTracking(options) {
1877
1969
  return {
1878
1970
  // Still publish phone config so usePhoneField/<PhoneField> work even when
1879
1971
  // tracking is disabled (missing apiKey/endpoint).
1880
- TrackingProvider: ({ children }) => /* @__PURE__ */ jsx3(PhoneConfigContext.Provider, { value: phone ?? null, children }),
1972
+ TrackingProvider: ({ children }) => /* @__PURE__ */ jsx3(PhoneConfigProvider, { value: phone ?? null, children }),
1881
1973
  useTracking: () => noopTyped
1882
1974
  };
1883
1975
  }
@@ -1950,7 +2042,7 @@ function createTracking(options) {
1950
2042
  }
1951
2043
  };
1952
2044
  }, []);
1953
- return /* @__PURE__ */ jsx3(TrackingContext.Provider, { value: client, children: /* @__PURE__ */ jsx3(PhoneConfigContext.Provider, { value: phone ?? null, children }) });
2045
+ return /* @__PURE__ */ jsx3(TrackingContext.Provider, { value: client, children: /* @__PURE__ */ jsx3(PhoneConfigProvider, { value: phone ?? null, children }) });
1954
2046
  }
1955
2047
  function useTracking() {
1956
2048
  const client = useContext2(TrackingContext);
@@ -1968,9 +2060,11 @@ export {
1968
2060
  ConsentBanner,
1969
2061
  DEFAULT_PHONE_COUNTRY,
1970
2062
  GoogleAdsTracking,
1971
- PhoneConfigContext,
2063
+ NAMED_RANGES,
1972
2064
  PhoneField,
2065
+ SUPPORTED_CURRENCIES,
1973
2066
  TRACKING_PARAM_KEYS,
2067
+ TRACKING_RANGES,
1974
2068
  captureTrackingParamsFromLocation,
1975
2069
  createSalesClient,
1976
2070
  createTracking,
@@ -1987,6 +2081,11 @@ export {
1987
2081
  parsePhone,
1988
2082
  phoneField,
1989
2083
  resetConsent,
2084
+ saleCreateSchema,
2085
+ saleItemSchema,
2086
+ saleServiceSchema,
2087
+ saleUpdateSchema,
2088
+ salesRequest,
1990
2089
  setConsentState,
1991
2090
  toE164,
1992
2091
  toMinor,