@aranova/tracking-next 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.js CHANGED
@@ -34,9 +34,11 @@ __export(src_exports, {
34
34
  ConsentBanner: () => ConsentBanner,
35
35
  DEFAULT_PHONE_COUNTRY: () => DEFAULT_PHONE_COUNTRY,
36
36
  GoogleAdsTracking: () => GoogleAdsTracking,
37
- PhoneConfigContext: () => PhoneConfigContext,
37
+ NAMED_RANGES: () => NAMED_RANGES,
38
38
  PhoneField: () => PhoneField,
39
+ SUPPORTED_CURRENCIES: () => SUPPORTED_CURRENCIES,
39
40
  TRACKING_PARAM_KEYS: () => TRACKING_PARAM_KEYS,
41
+ TRACKING_RANGES: () => TRACKING_RANGES,
40
42
  captureTrackingParamsFromLocation: () => captureTrackingParamsFromLocation,
41
43
  createSalesClient: () => createSalesClient,
42
44
  createTracking: () => createTracking,
@@ -53,6 +55,11 @@ __export(src_exports, {
53
55
  parsePhone: () => parsePhone,
54
56
  phoneField: () => phoneField,
55
57
  resetConsent: () => resetConsent,
58
+ saleCreateSchema: () => saleCreateSchema,
59
+ saleItemSchema: () => saleItemSchema,
60
+ saleServiceSchema: () => saleServiceSchema,
61
+ saleUpdateSchema: () => saleUpdateSchema,
62
+ salesRequest: () => salesRequest,
56
63
  setConsentState: () => setConsentState,
57
64
  toE164: () => toE164,
58
65
  toMinor: () => toMinor,
@@ -77,24 +84,20 @@ function buildConsentPayload(state) {
77
84
  };
78
85
  }
79
86
  function getConsentState() {
80
- if (typeof window === "undefined")
81
- return "pending";
87
+ if (typeof window === "undefined") return "pending";
82
88
  const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
83
- if (storedState === "granted" || storedState === "denied")
84
- return storedState;
89
+ if (storedState === "granted" || storedState === "denied") return storedState;
85
90
  return "pending";
86
91
  }
87
92
  function setConsentState(state) {
88
- if (typeof window === "undefined")
89
- return;
93
+ if (typeof window === "undefined") return;
90
94
  window.localStorage.setItem(CONSENT_STATE_KEY, state);
91
95
  window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, (/* @__PURE__ */ new Date()).toISOString());
92
96
  if (typeof window.gtag === "function")
93
97
  window.gtag("consent", "update", buildConsentPayload(state));
94
98
  }
95
99
  function resetConsent() {
96
- if (typeof window === "undefined")
97
- return;
100
+ if (typeof window === "undefined") return;
98
101
  window.localStorage.removeItem(CONSENT_STATE_KEY);
99
102
  window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
100
103
  }
@@ -235,18 +238,15 @@ function getTrackingQueryValues(searchParams) {
235
238
  }, {});
236
239
  }
237
240
  function getCookieValueFromDocument(key) {
238
- if (typeof document === "undefined")
239
- return null;
241
+ if (typeof document === "undefined") return null;
240
242
  const cookies = document.cookie ? document.cookie.split("; ") : [];
241
243
  const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));
242
- if (!match)
243
- return null;
244
+ if (!match) return null;
244
245
  const [, rawValue = ""] = match.split("=");
245
246
  return normalizeTrackingCookieValue(decodeURIComponent(rawValue));
246
247
  }
247
248
  function setTrackingCookie(key, value, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
248
- if (typeof document === "undefined")
249
- return;
249
+ if (typeof document === "undefined") return;
250
250
  const encodedValue = encodeURIComponent(value);
251
251
  document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;
252
252
  }
@@ -286,18 +286,15 @@ function writeLocalStorage(key, value) {
286
286
  }
287
287
  }
288
288
  function getVisitorId() {
289
- if (typeof window === "undefined")
290
- return safeUuid();
289
+ if (typeof window === "undefined") return safeUuid();
291
290
  const existing = readLocalStorage(VISITOR_STORAGE_KEY);
292
- if (existing && existing.length > 0)
293
- return existing;
291
+ if (existing && existing.length > 0) return existing;
294
292
  const fresh = safeUuid();
295
293
  writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
296
294
  return fresh;
297
295
  }
298
296
  function getOrRotateSessionId(now = Date.now()) {
299
- if (typeof window === "undefined")
300
- return { id: safeUuid(), isNew: true };
297
+ if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
301
298
  const raw = readLocalStorage(SESSION_STORAGE_KEY);
302
299
  if (raw) {
303
300
  try {
@@ -370,8 +367,7 @@ function setLastFiredUrl(url) {
370
367
  writeSessionStorage(LAST_FIRED_URL_STORAGE_KEY, url);
371
368
  }
372
369
  function buildPageViewMetadata(referrerOverride) {
373
- if (typeof window === "undefined" || typeof document === "undefined")
374
- return null;
370
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
375
371
  return pageViewMetadataSchema.parse({
376
372
  page: {
377
373
  title: document.title || null,
@@ -384,8 +380,7 @@ function buildPageViewMetadata(referrerOverride) {
384
380
  });
385
381
  }
386
382
  function fireManualPageView(client) {
387
- if (typeof window === "undefined")
388
- return;
383
+ if (typeof window === "undefined") return;
389
384
  const currentHref = window.location.href;
390
385
  const previousFiredUrl = getLastFiredUrl();
391
386
  const internalReferrer = previousFiredUrl !== null && previousFiredUrl !== currentHref ? previousFiredUrl : null;
@@ -487,8 +482,7 @@ function buildContext(surface, sdkVersion, packageName, environment, activeGtagI
487
482
  };
488
483
  }
489
484
  function readTrackingParams() {
490
- if (typeof window === "undefined")
491
- return createEmptyTrackingParams();
485
+ if (typeof window === "undefined") return createEmptyTrackingParams();
492
486
  try {
493
487
  captureTrackingParamsFromLocation();
494
488
  } catch {
@@ -503,8 +497,7 @@ function consentSnapshot() {
503
497
  }
504
498
  }
505
499
  async function postWithFetch(url, body, apiKey, identity, keepalive) {
506
- if (typeof fetch !== "function")
507
- return;
500
+ if (typeof fetch !== "function") return;
508
501
  try {
509
502
  await fetch(url, {
510
503
  method: "POST",
@@ -564,8 +557,7 @@ function createTrackingClient(config) {
564
557
  const visitorId = getVisitorId();
565
558
  const initialSession = getOrRotateSessionId();
566
559
  let sessionId = initialSession.id;
567
- if (typeof window !== "undefined")
568
- firstPage = window.location.href;
560
+ if (typeof window !== "undefined") firstPage = window.location.href;
569
561
  function enqueueHeartbeat() {
570
562
  const metadata = buildHeartbeatMetadata(
571
563
  config.surface,
@@ -591,7 +583,13 @@ function createTrackingClient(config) {
591
583
  }
592
584
  sessionId = rotated.id;
593
585
  const params = readTrackingParams();
594
- const context = buildContext(config.surface, sdkVersion, packageName, environment, activeGtagIds);
586
+ const context = buildContext(
587
+ config.surface,
588
+ sdkVersion,
589
+ packageName,
590
+ environment,
591
+ activeGtagIds
592
+ );
595
593
  return {
596
594
  session_id: sessionId,
597
595
  visitor_id: visitorId,
@@ -608,8 +606,7 @@ function createTrackingClient(config) {
608
606
  };
609
607
  }
610
608
  function scheduleFlush() {
611
- if (flushTimer !== null || destroyed)
612
- return;
609
+ if (flushTimer !== null || destroyed) return;
613
610
  flushTimer = setTimeout(() => {
614
611
  flushTimer = null;
615
612
  void flush();
@@ -622,8 +619,7 @@ function createTrackingClient(config) {
622
619
  }
623
620
  }
624
621
  async function flush() {
625
- if (queue.length === 0)
626
- return;
622
+ if (queue.length === 0) return;
627
623
  const events = queue.slice(0, HARD_MAX_BATCH);
628
624
  queue = queue.slice(events.length);
629
625
  clearScheduledFlush();
@@ -635,10 +631,8 @@ function createTrackingClient(config) {
635
631
  await postWithFetch(eventsUrl, serialized, config.apiKey, identityHeaders2, false);
636
632
  }
637
633
  function trackEvent(input) {
638
- if (destroyed)
639
- return;
640
- if (!input || typeof input.eventType !== "string" || input.eventType.length === 0)
641
- return;
634
+ if (destroyed) return;
635
+ if (!input || typeof input.eventType !== "string" || input.eventType.length === 0) return;
642
636
  const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
643
637
  queue.push({
644
638
  event_type: input.eventType,
@@ -653,8 +647,7 @@ function createTrackingClient(config) {
653
647
  }
654
648
  }
655
649
  function flushOnUnload() {
656
- if (queue.length === 0)
657
- return;
650
+ if (queue.length === 0) return;
658
651
  const events = queue.slice(0, HARD_MAX_BATCH);
659
652
  queue = queue.slice(events.length);
660
653
  clearScheduledFlush();
@@ -1285,11 +1278,6 @@ function createSalesClient(config) {
1285
1278
  };
1286
1279
  }
1287
1280
 
1288
- // ../tracking-core/src/resources/services.ts
1289
- async function fetchServices(config) {
1290
- return salesRequest(config, "GET", "/services");
1291
- }
1292
-
1293
1281
  // ../tracking-core/src/resources/sales/money.ts
1294
1282
  var MINOR_UNIT_EXPONENT = {
1295
1283
  USD: 2,
@@ -1323,6 +1311,123 @@ function formatDateInTz(iso, timeZone, opts, locale) {
1323
1311
  }).format(date);
1324
1312
  }
1325
1313
 
1314
+ // ../tracking-core/src/resources/sales/schema.ts
1315
+ var import_zod11 = require("zod");
1316
+ var SUPPORTED_CURRENCIES = ["USD", "CAD"];
1317
+ var TRACKING_ENVIRONMENTS = ["production", "development"];
1318
+ var currencySchema = import_zod11.z.enum(SUPPORTED_CURRENCIES);
1319
+ var centsSchema = import_zod11.z.number().int().nonnegative();
1320
+ var quantitySchema = import_zod11.z.string().regex(/^\d+(\.\d{1,3})?$/);
1321
+ var metadataSchema = import_zod11.z.record(import_zod11.z.unknown());
1322
+ var saleItemSchema = import_zod11.z.object({
1323
+ external_item_id: import_zod11.z.string().nullable().optional(),
1324
+ name: import_zod11.z.string().nullable().optional(),
1325
+ category: import_zod11.z.string().nullable().optional(),
1326
+ quantity: quantitySchema,
1327
+ unit_price_cents: centsSchema,
1328
+ // Non-negativity validated on the wire — same contract as the other cents
1329
+ // fields — and backstopped by the DB CHECK.
1330
+ unit_cost_cents: centsSchema.nullable().optional()
1331
+ }).strict();
1332
+ var saleServiceSchema = import_zod11.z.object({
1333
+ service: import_zod11.z.string(),
1334
+ amount_cents: centsSchema
1335
+ }).strict();
1336
+ var customerNameSchema = import_zod11.z.string().max(200);
1337
+ var customerPhoneSchema = import_zod11.z.string().max(64);
1338
+ var customerEmailSchema = import_zod11.z.string().max(320).email();
1339
+ function refineServiceXor(val, ctx, { requireAmount }) {
1340
+ if (val.services != null) {
1341
+ if (val.service != null) {
1342
+ ctx.addIssue({
1343
+ code: import_zod11.z.ZodIssueCode.custom,
1344
+ message: "pass either `service` or `services`, not both",
1345
+ path: ["services"]
1346
+ });
1347
+ }
1348
+ if (val.services.length === 0) {
1349
+ ctx.addIssue({
1350
+ code: import_zod11.z.ZodIssueCode.custom,
1351
+ message: "`services` must not be empty",
1352
+ path: ["services"]
1353
+ });
1354
+ }
1355
+ const keys = val.services.map((s) => s.service);
1356
+ if (new Set(keys).size !== keys.length) {
1357
+ ctx.addIssue({
1358
+ code: import_zod11.z.ZodIssueCode.custom,
1359
+ message: "`services` must not list the same service more than once",
1360
+ path: ["services"]
1361
+ });
1362
+ }
1363
+ if (val.amount_total_cents != null) {
1364
+ const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
1365
+ if (val.amount_total_cents !== sum) {
1366
+ ctx.addIssue({
1367
+ code: import_zod11.z.ZodIssueCode.custom,
1368
+ message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
1369
+ path: ["amount_total_cents"]
1370
+ });
1371
+ }
1372
+ }
1373
+ } else if (requireAmount && val.amount_total_cents == null) {
1374
+ ctx.addIssue({
1375
+ code: import_zod11.z.ZodIssueCode.custom,
1376
+ message: "amount_total_cents is required unless `services` is provided",
1377
+ path: ["amount_total_cents"]
1378
+ });
1379
+ }
1380
+ }
1381
+ var saleCreateSchema = import_zod11.z.object({
1382
+ external_id: import_zod11.z.string().nullable().optional(),
1383
+ description: import_zod11.z.string().nullable().optional(),
1384
+ service: import_zod11.z.string().nullable().optional(),
1385
+ services: import_zod11.z.array(saleServiceSchema).nullable().optional(),
1386
+ currency: currencySchema,
1387
+ // Optional only because the plural `services` form derives it from the sum
1388
+ // (see refineServiceXor); the singular/serviceless path still requires it.
1389
+ amount_total_cents: centsSchema.nullable().optional(),
1390
+ occurred_at: import_zod11.z.string().datetime(),
1391
+ environment: import_zod11.z.enum(TRACKING_ENVIRONMENTS).default("production"),
1392
+ items: import_zod11.z.array(saleItemSchema).default([]),
1393
+ metadata: metadataSchema.nullable().optional(),
1394
+ customer_name: customerNameSchema.nullable().optional(),
1395
+ customer_phone: customerPhoneSchema.nullable().optional(),
1396
+ customer_email: customerEmailSchema.nullable().optional()
1397
+ }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
1398
+ var saleUpdateSchema = import_zod11.z.object({
1399
+ description: import_zod11.z.string().nullable().optional(),
1400
+ service: import_zod11.z.string().nullable().optional(),
1401
+ services: import_zod11.z.array(saleServiceSchema).nullable().optional(),
1402
+ currency: currencySchema.optional(),
1403
+ amount_total_cents: centsSchema.optional(),
1404
+ occurred_at: import_zod11.z.string().datetime().optional(),
1405
+ items: import_zod11.z.array(saleItemSchema).optional(),
1406
+ metadata: metadataSchema.nullable().optional(),
1407
+ customer_name: customerNameSchema.nullable().optional(),
1408
+ customer_phone: customerPhoneSchema.nullable().optional(),
1409
+ customer_email: customerEmailSchema.nullable().optional()
1410
+ }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: false }));
1411
+ var TRACKING_RANGES = ["24h", "7d", "30d"];
1412
+ var NAMED_RANGES = [
1413
+ "today",
1414
+ "yesterday",
1415
+ "wtd",
1416
+ "mtd",
1417
+ "qtd",
1418
+ "ytd",
1419
+ "24h",
1420
+ "7d",
1421
+ "30d",
1422
+ "90d",
1423
+ "custom"
1424
+ ];
1425
+
1426
+ // ../tracking-core/src/resources/services.ts
1427
+ async function fetchServices(config) {
1428
+ return salesRequest(config, "GET", "/services");
1429
+ }
1430
+
1326
1431
  // ../tracking-core/src/phone.ts
1327
1432
  var import_libphonenumber_js = require("libphonenumber-js");
1328
1433
  var DEFAULT_PHONE_COUNTRY = "CA";
@@ -1635,7 +1740,7 @@ function GoogleAdsTracking(props) {
1635
1740
  var import_navigation = require("next/navigation");
1636
1741
 
1637
1742
  // package.json
1638
- var version = "0.12.0";
1743
+ var version = "0.12.1";
1639
1744
 
1640
1745
  // src/factory.tsx
1641
1746
  var import_react4 = require("react");
@@ -1643,9 +1748,19 @@ var import_react4 = require("react");
1643
1748
  // ../tracking-core/src/phone-react.tsx
1644
1749
  var import_react3 = require("react");
1645
1750
  var import_jsx_runtime3 = require("react/jsx-runtime");
1646
- var PhoneConfigContext = (0, import_react3.createContext)(null);
1751
+ var _phoneConfigContext;
1752
+ function phoneConfigContext() {
1753
+ return _phoneConfigContext ?? (_phoneConfigContext = (0, import_react3.createContext)(null));
1754
+ }
1755
+ function PhoneConfigProvider({
1756
+ value,
1757
+ children
1758
+ }) {
1759
+ const Ctx = phoneConfigContext();
1760
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Ctx.Provider, { value, children });
1761
+ }
1647
1762
  function usePhoneConfig() {
1648
- const ctx = (0, import_react3.useContext)(PhoneConfigContext);
1763
+ const ctx = (0, import_react3.useContext)(phoneConfigContext());
1649
1764
  return {
1650
1765
  defaultCountry: ctx?.defaultCountry ?? DEFAULT_PHONE_COUNTRY,
1651
1766
  display: ctx?.display ?? "national"
@@ -1735,7 +1850,7 @@ function createTracking(options) {
1735
1850
  getVisitorId: () => ""
1736
1851
  };
1737
1852
  return {
1738
- TrackingProvider: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PhoneConfigContext.Provider, { value: phone ?? null, children }),
1853
+ TrackingProvider: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PhoneConfigProvider, { value: phone ?? null, children }),
1739
1854
  useTracking: () => noopClient
1740
1855
  };
1741
1856
  }
@@ -1904,7 +2019,7 @@ function createTracking(options) {
1904
2019
  }
1905
2020
  };
1906
2021
  }, []);
1907
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TrackingContext.Provider, { value: client, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PhoneConfigContext.Provider, { value: phone ?? null, children }) });
2022
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TrackingContext.Provider, { value: client, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PhoneConfigProvider, { value: phone ?? null, children }) });
1908
2023
  }
1909
2024
  function useTracking() {
1910
2025
  const client = (0, import_react4.useContext)(TrackingContext);
@@ -1923,9 +2038,11 @@ function createTracking(options) {
1923
2038
  ConsentBanner,
1924
2039
  DEFAULT_PHONE_COUNTRY,
1925
2040
  GoogleAdsTracking,
1926
- PhoneConfigContext,
2041
+ NAMED_RANGES,
1927
2042
  PhoneField,
2043
+ SUPPORTED_CURRENCIES,
1928
2044
  TRACKING_PARAM_KEYS,
2045
+ TRACKING_RANGES,
1929
2046
  captureTrackingParamsFromLocation,
1930
2047
  createSalesClient,
1931
2048
  createTracking,
@@ -1942,6 +2059,11 @@ function createTracking(options) {
1942
2059
  parsePhone,
1943
2060
  phoneField,
1944
2061
  resetConsent,
2062
+ saleCreateSchema,
2063
+ saleItemSchema,
2064
+ saleServiceSchema,
2065
+ saleUpdateSchema,
2066
+ salesRequest,
1945
2067
  setConsentState,
1946
2068
  toE164,
1947
2069
  toMinor,