@aranova/tracking-next 0.12.0 → 0.12.2

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
@@ -10,24 +10,20 @@ function buildConsentPayload(state) {
10
10
  };
11
11
  }
12
12
  function getConsentState() {
13
- if (typeof window === "undefined")
14
- return "pending";
13
+ if (typeof window === "undefined") return "pending";
15
14
  const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
16
- if (storedState === "granted" || storedState === "denied")
17
- return storedState;
15
+ if (storedState === "granted" || storedState === "denied") return storedState;
18
16
  return "pending";
19
17
  }
20
18
  function setConsentState(state) {
21
- if (typeof window === "undefined")
22
- return;
19
+ if (typeof window === "undefined") return;
23
20
  window.localStorage.setItem(CONSENT_STATE_KEY, state);
24
21
  window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, (/* @__PURE__ */ new Date()).toISOString());
25
22
  if (typeof window.gtag === "function")
26
23
  window.gtag("consent", "update", buildConsentPayload(state));
27
24
  }
28
25
  function resetConsent() {
29
- if (typeof window === "undefined")
30
- return;
26
+ if (typeof window === "undefined") return;
31
27
  window.localStorage.removeItem(CONSENT_STATE_KEY);
32
28
  window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
33
29
  }
@@ -168,18 +164,15 @@ function getTrackingQueryValues(searchParams) {
168
164
  }, {});
169
165
  }
170
166
  function getCookieValueFromDocument(key) {
171
- if (typeof document === "undefined")
172
- return null;
167
+ if (typeof document === "undefined") return null;
173
168
  const cookies = document.cookie ? document.cookie.split("; ") : [];
174
169
  const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));
175
- if (!match)
176
- return null;
170
+ if (!match) return null;
177
171
  const [, rawValue = ""] = match.split("=");
178
172
  return normalizeTrackingCookieValue(decodeURIComponent(rawValue));
179
173
  }
180
174
  function setTrackingCookie(key, value, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
181
- if (typeof document === "undefined")
182
- return;
175
+ if (typeof document === "undefined") return;
183
176
  const encodedValue = encodeURIComponent(value);
184
177
  document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;
185
178
  }
@@ -219,18 +212,15 @@ function writeLocalStorage(key, value) {
219
212
  }
220
213
  }
221
214
  function getVisitorId() {
222
- if (typeof window === "undefined")
223
- return safeUuid();
215
+ if (typeof window === "undefined") return safeUuid();
224
216
  const existing = readLocalStorage(VISITOR_STORAGE_KEY);
225
- if (existing && existing.length > 0)
226
- return existing;
217
+ if (existing && existing.length > 0) return existing;
227
218
  const fresh = safeUuid();
228
219
  writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
229
220
  return fresh;
230
221
  }
231
222
  function getOrRotateSessionId(now = Date.now()) {
232
- if (typeof window === "undefined")
233
- return { id: safeUuid(), isNew: true };
223
+ if (typeof window === "undefined") return { id: safeUuid(), isNew: true };
234
224
  const raw = readLocalStorage(SESSION_STORAGE_KEY);
235
225
  if (raw) {
236
226
  try {
@@ -303,8 +293,7 @@ function setLastFiredUrl(url) {
303
293
  writeSessionStorage(LAST_FIRED_URL_STORAGE_KEY, url);
304
294
  }
305
295
  function buildPageViewMetadata(referrerOverride) {
306
- if (typeof window === "undefined" || typeof document === "undefined")
307
- return null;
296
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
308
297
  return pageViewMetadataSchema.parse({
309
298
  page: {
310
299
  title: document.title || null,
@@ -317,8 +306,7 @@ function buildPageViewMetadata(referrerOverride) {
317
306
  });
318
307
  }
319
308
  function fireManualPageView(client) {
320
- if (typeof window === "undefined")
321
- return;
309
+ if (typeof window === "undefined") return;
322
310
  const currentHref = window.location.href;
323
311
  const previousFiredUrl = getLastFiredUrl();
324
312
  const internalReferrer = previousFiredUrl !== null && previousFiredUrl !== currentHref ? previousFiredUrl : null;
@@ -420,8 +408,7 @@ function buildContext(surface, sdkVersion, packageName, environment, activeGtagI
420
408
  };
421
409
  }
422
410
  function readTrackingParams() {
423
- if (typeof window === "undefined")
424
- return createEmptyTrackingParams();
411
+ if (typeof window === "undefined") return createEmptyTrackingParams();
425
412
  try {
426
413
  captureTrackingParamsFromLocation();
427
414
  } catch {
@@ -436,8 +423,7 @@ function consentSnapshot() {
436
423
  }
437
424
  }
438
425
  async function postWithFetch(url, body, apiKey, identity, keepalive) {
439
- if (typeof fetch !== "function")
440
- return;
426
+ if (typeof fetch !== "function") return;
441
427
  try {
442
428
  await fetch(url, {
443
429
  method: "POST",
@@ -497,8 +483,7 @@ function createTrackingClient(config) {
497
483
  const visitorId = getVisitorId();
498
484
  const initialSession = getOrRotateSessionId();
499
485
  let sessionId = initialSession.id;
500
- if (typeof window !== "undefined")
501
- firstPage = window.location.href;
486
+ if (typeof window !== "undefined") firstPage = window.location.href;
502
487
  function enqueueHeartbeat() {
503
488
  const metadata = buildHeartbeatMetadata(
504
489
  config.surface,
@@ -524,7 +509,13 @@ function createTrackingClient(config) {
524
509
  }
525
510
  sessionId = rotated.id;
526
511
  const params = readTrackingParams();
527
- const context = buildContext(config.surface, sdkVersion, packageName, environment, activeGtagIds);
512
+ const context = buildContext(
513
+ config.surface,
514
+ sdkVersion,
515
+ packageName,
516
+ environment,
517
+ activeGtagIds
518
+ );
528
519
  return {
529
520
  session_id: sessionId,
530
521
  visitor_id: visitorId,
@@ -541,8 +532,7 @@ function createTrackingClient(config) {
541
532
  };
542
533
  }
543
534
  function scheduleFlush() {
544
- if (flushTimer !== null || destroyed)
545
- return;
535
+ if (flushTimer !== null || destroyed) return;
546
536
  flushTimer = setTimeout(() => {
547
537
  flushTimer = null;
548
538
  void flush();
@@ -555,8 +545,7 @@ function createTrackingClient(config) {
555
545
  }
556
546
  }
557
547
  async function flush() {
558
- if (queue.length === 0)
559
- return;
548
+ if (queue.length === 0) return;
560
549
  const events = queue.slice(0, HARD_MAX_BATCH);
561
550
  queue = queue.slice(events.length);
562
551
  clearScheduledFlush();
@@ -568,10 +557,8 @@ function createTrackingClient(config) {
568
557
  await postWithFetch(eventsUrl, serialized, config.apiKey, identityHeaders2, false);
569
558
  }
570
559
  function trackEvent(input) {
571
- if (destroyed)
572
- return;
573
- if (!input || typeof input.eventType !== "string" || input.eventType.length === 0)
574
- return;
560
+ if (destroyed) return;
561
+ if (!input || typeof input.eventType !== "string" || input.eventType.length === 0) return;
575
562
  const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
576
563
  queue.push({
577
564
  event_type: input.eventType,
@@ -586,8 +573,7 @@ function createTrackingClient(config) {
586
573
  }
587
574
  }
588
575
  function flushOnUnload() {
589
- if (queue.length === 0)
590
- return;
576
+ if (queue.length === 0) return;
591
577
  const events = queue.slice(0, HARD_MAX_BATCH);
592
578
  queue = queue.slice(events.length);
593
579
  clearScheduledFlush();
@@ -1218,11 +1204,6 @@ function createSalesClient(config) {
1218
1204
  };
1219
1205
  }
1220
1206
 
1221
- // ../tracking-core/src/resources/services.ts
1222
- async function fetchServices(config) {
1223
- return salesRequest(config, "GET", "/services");
1224
- }
1225
-
1226
1207
  // ../tracking-core/src/resources/sales/money.ts
1227
1208
  var MINOR_UNIT_EXPONENT = {
1228
1209
  USD: 2,
@@ -1256,6 +1237,123 @@ function formatDateInTz(iso, timeZone, opts, locale) {
1256
1237
  }).format(date);
1257
1238
  }
1258
1239
 
1240
+ // ../tracking-core/src/resources/sales/schema.ts
1241
+ import { z as z11 } from "zod";
1242
+ var SUPPORTED_CURRENCIES = ["USD", "CAD"];
1243
+ var TRACKING_ENVIRONMENTS = ["production", "development"];
1244
+ var currencySchema = z11.enum(SUPPORTED_CURRENCIES);
1245
+ var centsSchema = z11.number().int().nonnegative();
1246
+ var quantitySchema = z11.string().regex(/^\d+(\.\d{1,3})?$/);
1247
+ var metadataSchema = z11.record(z11.unknown());
1248
+ var saleItemSchema = z11.object({
1249
+ external_item_id: z11.string().nullable().optional(),
1250
+ name: z11.string().nullable().optional(),
1251
+ category: z11.string().nullable().optional(),
1252
+ quantity: quantitySchema,
1253
+ unit_price_cents: centsSchema,
1254
+ // Non-negativity validated on the wire — same contract as the other cents
1255
+ // fields — and backstopped by the DB CHECK.
1256
+ unit_cost_cents: centsSchema.nullable().optional()
1257
+ }).strict();
1258
+ var saleServiceSchema = z11.object({
1259
+ service: z11.string(),
1260
+ amount_cents: centsSchema
1261
+ }).strict();
1262
+ var customerNameSchema = z11.string().max(200);
1263
+ var customerPhoneSchema = z11.string().max(64);
1264
+ var customerEmailSchema = z11.string().max(320).email();
1265
+ function refineServiceXor(val, ctx, { requireAmount }) {
1266
+ if (val.services != null) {
1267
+ if (val.service != null) {
1268
+ ctx.addIssue({
1269
+ code: z11.ZodIssueCode.custom,
1270
+ message: "pass either `service` or `services`, not both",
1271
+ path: ["services"]
1272
+ });
1273
+ }
1274
+ if (val.services.length === 0) {
1275
+ ctx.addIssue({
1276
+ code: z11.ZodIssueCode.custom,
1277
+ message: "`services` must not be empty",
1278
+ path: ["services"]
1279
+ });
1280
+ }
1281
+ const keys = val.services.map((s) => s.service);
1282
+ if (new Set(keys).size !== keys.length) {
1283
+ ctx.addIssue({
1284
+ code: z11.ZodIssueCode.custom,
1285
+ message: "`services` must not list the same service more than once",
1286
+ path: ["services"]
1287
+ });
1288
+ }
1289
+ if (val.amount_total_cents != null) {
1290
+ const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
1291
+ if (val.amount_total_cents !== sum) {
1292
+ ctx.addIssue({
1293
+ code: z11.ZodIssueCode.custom,
1294
+ message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
1295
+ path: ["amount_total_cents"]
1296
+ });
1297
+ }
1298
+ }
1299
+ } else if (requireAmount && val.amount_total_cents == null) {
1300
+ ctx.addIssue({
1301
+ code: z11.ZodIssueCode.custom,
1302
+ message: "amount_total_cents is required unless `services` is provided",
1303
+ path: ["amount_total_cents"]
1304
+ });
1305
+ }
1306
+ }
1307
+ var saleCreateSchema = z11.object({
1308
+ external_id: z11.string().nullable().optional(),
1309
+ description: z11.string().nullable().optional(),
1310
+ service: z11.string().nullable().optional(),
1311
+ services: z11.array(saleServiceSchema).nullable().optional(),
1312
+ currency: currencySchema,
1313
+ // Optional only because the plural `services` form derives it from the sum
1314
+ // (see refineServiceXor); the singular/serviceless path still requires it.
1315
+ amount_total_cents: centsSchema.nullable().optional(),
1316
+ occurred_at: z11.string().datetime(),
1317
+ environment: z11.enum(TRACKING_ENVIRONMENTS).default("production"),
1318
+ items: z11.array(saleItemSchema).default([]),
1319
+ metadata: metadataSchema.nullable().optional(),
1320
+ customer_name: customerNameSchema.nullable().optional(),
1321
+ customer_phone: customerPhoneSchema.nullable().optional(),
1322
+ customer_email: customerEmailSchema.nullable().optional()
1323
+ }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
1324
+ var saleUpdateSchema = z11.object({
1325
+ description: z11.string().nullable().optional(),
1326
+ service: z11.string().nullable().optional(),
1327
+ services: z11.array(saleServiceSchema).nullable().optional(),
1328
+ currency: currencySchema.optional(),
1329
+ amount_total_cents: centsSchema.optional(),
1330
+ occurred_at: z11.string().datetime().optional(),
1331
+ items: z11.array(saleItemSchema).optional(),
1332
+ metadata: metadataSchema.nullable().optional(),
1333
+ customer_name: customerNameSchema.nullable().optional(),
1334
+ customer_phone: customerPhoneSchema.nullable().optional(),
1335
+ customer_email: customerEmailSchema.nullable().optional()
1336
+ }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: false }));
1337
+ var TRACKING_RANGES = ["24h", "7d", "30d"];
1338
+ var NAMED_RANGES = [
1339
+ "today",
1340
+ "yesterday",
1341
+ "wtd",
1342
+ "mtd",
1343
+ "qtd",
1344
+ "ytd",
1345
+ "24h",
1346
+ "7d",
1347
+ "30d",
1348
+ "90d",
1349
+ "custom"
1350
+ ];
1351
+
1352
+ // ../tracking-core/src/resources/services.ts
1353
+ async function fetchServices(config) {
1354
+ return salesRequest(config, "GET", "/services");
1355
+ }
1356
+
1259
1357
  // ../tracking-core/src/phone.ts
1260
1358
  import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
1261
1359
  var DEFAULT_PHONE_COUNTRY = "CA";
@@ -1546,10 +1644,10 @@ import Script from "next/script";
1546
1644
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1547
1645
  function GoogleAdsTracking(props) {
1548
1646
  const { gtagId, gtagIds } = props;
1549
- const allIds = gtagIds ? Object.values(gtagIds) : gtagId ? [gtagId] : [];
1647
+ const allIds = gtagIds ? Object.values(gtagIds).filter((id) => id != null) : gtagId ? [gtagId] : [];
1550
1648
  if (allIds.length === 0) return null;
1551
1649
  const loaderId = allIds[0];
1552
- const initScript = allIds.length === 1 ? createGtagInitScript(allIds[0]) : createGtagInitScriptMulti(allIds);
1650
+ const initScript = allIds.length === 1 ? createGtagInitScript(loaderId) : createGtagInitScriptMulti(allIds);
1553
1651
  return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1554
1652
  /* @__PURE__ */ jsx2(Script, { id: "consent-default", strategy: "beforeInteractive", children: createConsentDefaultScript() }),
1555
1653
  /* @__PURE__ */ jsx2(
@@ -1568,16 +1666,10 @@ function GoogleAdsTracking(props) {
1568
1666
  import { usePathname, useSearchParams } from "next/navigation";
1569
1667
 
1570
1668
  // package.json
1571
- var version = "0.12.0";
1669
+ var version = "0.12.2";
1572
1670
 
1573
1671
  // src/factory.tsx
1574
- import {
1575
- createContext as createContext2,
1576
- useContext as useContext2,
1577
- useEffect as useEffect3,
1578
- useMemo as useMemo2,
1579
- useRef
1580
- } from "react";
1672
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useMemo as useMemo2, useRef } from "react";
1581
1673
 
1582
1674
  // ../tracking-core/src/phone-react.tsx
1583
1675
  import {
@@ -1589,9 +1681,19 @@ import {
1589
1681
  useState as useState3
1590
1682
  } from "react";
1591
1683
  import { jsx as jsx3 } from "react/jsx-runtime";
1592
- var PhoneConfigContext = createContext(null);
1684
+ var _phoneConfigContext;
1685
+ function phoneConfigContext() {
1686
+ return _phoneConfigContext ?? (_phoneConfigContext = createContext(null));
1687
+ }
1688
+ function PhoneConfigProvider({
1689
+ value,
1690
+ children
1691
+ }) {
1692
+ const Ctx = phoneConfigContext();
1693
+ return /* @__PURE__ */ jsx3(Ctx.Provider, { value, children });
1694
+ }
1593
1695
  function usePhoneConfig() {
1594
- const ctx = useContext(PhoneConfigContext);
1696
+ const ctx = useContext(phoneConfigContext());
1595
1697
  return {
1596
1698
  defaultCountry: ctx?.defaultCountry ?? DEFAULT_PHONE_COUNTRY,
1597
1699
  display: ctx?.display ?? "national"
@@ -1681,7 +1783,7 @@ function createTracking(options) {
1681
1783
  getVisitorId: () => ""
1682
1784
  };
1683
1785
  return {
1684
- TrackingProvider: ({ children }) => /* @__PURE__ */ jsx4(PhoneConfigContext.Provider, { value: phone ?? null, children }),
1786
+ TrackingProvider: ({ children }) => /* @__PURE__ */ jsx4(PhoneConfigProvider, { value: phone ?? null, children }),
1685
1787
  useTracking: () => noopClient
1686
1788
  };
1687
1789
  }
@@ -1850,7 +1952,7 @@ function createTracking(options) {
1850
1952
  }
1851
1953
  };
1852
1954
  }, []);
1853
- return /* @__PURE__ */ jsx4(TrackingContext.Provider, { value: client, children: /* @__PURE__ */ jsx4(PhoneConfigContext.Provider, { value: phone ?? null, children }) });
1955
+ return /* @__PURE__ */ jsx4(TrackingContext.Provider, { value: client, children: /* @__PURE__ */ jsx4(PhoneConfigProvider, { value: phone ?? null, children }) });
1854
1956
  }
1855
1957
  function useTracking() {
1856
1958
  const client = useContext2(TrackingContext);
@@ -1868,9 +1970,11 @@ export {
1868
1970
  ConsentBanner,
1869
1971
  DEFAULT_PHONE_COUNTRY,
1870
1972
  GoogleAdsTracking,
1871
- PhoneConfigContext,
1973
+ NAMED_RANGES,
1872
1974
  PhoneField,
1975
+ SUPPORTED_CURRENCIES,
1873
1976
  TRACKING_PARAM_KEYS,
1977
+ TRACKING_RANGES,
1874
1978
  captureTrackingParamsFromLocation,
1875
1979
  createSalesClient,
1876
1980
  createTracking,
@@ -1887,6 +1991,11 @@ export {
1887
1991
  parsePhone,
1888
1992
  phoneField,
1889
1993
  resetConsent,
1994
+ saleCreateSchema,
1995
+ saleItemSchema,
1996
+ saleServiceSchema,
1997
+ saleUpdateSchema,
1998
+ salesRequest,
1890
1999
  setConsentState,
1891
2000
  toE164,
1892
2001
  toMinor,