@aranova/tracking-react 0.15.0 → 0.16.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
@@ -1285,6 +1285,7 @@ function createTrackingClient(config) {
1285
1285
  return {
1286
1286
  trackEvent,
1287
1287
  flush,
1288
+ flushBeacon: flushOnUnload,
1288
1289
  getSessionId: () => sessionId,
1289
1290
  getVisitorId: () => visitorId,
1290
1291
  destroy: () => {
@@ -1309,9 +1310,17 @@ var ctaClickMetadataSchema = z2.object({
1309
1310
  path: z2.string()
1310
1311
  }).strict(),
1311
1312
  section: z2.string().nullable().optional(),
1312
- destination_url: z2.string().nullable().optional()
1313
+ destination_url: z2.string().nullable().optional(),
1314
+ // Set by auto-capture (and available to manual callers): the link target
1315
+ // and a short element descriptor (tag#id) for tying clicks to specific UI.
1316
+ href: z2.string().nullable().optional(),
1317
+ element: z2.string().nullable().optional()
1318
+ }).strict();
1319
+ var ctaClickConfigSchema = z2.object({
1320
+ autoCapture: z2.object({
1321
+ selector: z2.string().optional()
1322
+ }).strict().optional()
1313
1323
  }).strict();
1314
- var ctaClickConfigSchema = z2.object({}).strict();
1315
1324
 
1316
1325
  // ../tracking-core/src/events/sdk-heartbeat.ts
1317
1326
  import { z as z3 } from "zod";
@@ -1387,31 +1396,44 @@ var multiPageSessionConfigSchema = z6.object({
1387
1396
  pageThreshold: z6.number().int().min(2)
1388
1397
  }).strict();
1389
1398
 
1390
- // ../tracking-core/src/events/phone-click.ts
1399
+ // ../tracking-core/src/events/page-exit.ts
1391
1400
  import { z as z7 } from "zod";
1392
- var phoneClickMetadataSchema = z7.object({
1393
- phone_number: z7.string(),
1401
+ var pageExitMetadataSchema = z7.object({
1402
+ dwell_ms: z7.number().int().min(0),
1403
+ // null = left without any scroll signal; floor is 0 so a valid 0% is never
1404
+ // rejected (a single bad field 422s the whole keepalive beacon batch).
1405
+ max_scroll_percent: z7.number().int().min(0).max(100).nullable(),
1394
1406
  page: z7.object({
1395
1407
  path: z7.string()
1396
- }).strict(),
1397
- section: z7.string().nullable().optional()
1408
+ }).strict()
1398
1409
  }).strict();
1399
- var phoneClickConfigSchema = z7.object({}).strict();
1410
+ var pageExitConfigSchema = z7.object({}).strict();
1400
1411
 
1401
- // ../tracking-core/src/events/scroll-depth.ts
1412
+ // ../tracking-core/src/events/phone-click.ts
1402
1413
  import { z as z8 } from "zod";
1403
- var scrollDepthMetadataSchema = z8.object({
1404
- depth_percent: z8.number().int().min(1).max(100),
1414
+ var phoneClickMetadataSchema = z8.object({
1415
+ phone_number: z8.string(),
1405
1416
  page: z8.object({
1406
1417
  path: z8.string()
1418
+ }).strict(),
1419
+ section: z8.string().nullable().optional()
1420
+ }).strict();
1421
+ var phoneClickConfigSchema = z8.object({}).strict();
1422
+
1423
+ // ../tracking-core/src/events/scroll-depth.ts
1424
+ import { z as z9 } from "zod";
1425
+ var scrollDepthMetadataSchema = z9.object({
1426
+ depth_percent: z9.number().int().min(1).max(100),
1427
+ page: z9.object({
1428
+ path: z9.string()
1407
1429
  }).strict()
1408
1430
  }).strict();
1409
- var scrollDepthConfigSchema = z8.object({
1410
- thresholds: z8.array(z8.number().int().min(1).max(100)).min(1)
1431
+ var scrollDepthConfigSchema = z9.object({
1432
+ thresholds: z9.array(z9.number().int().min(1).max(100)).min(1)
1411
1433
  }).strict();
1412
1434
 
1413
1435
  // ../tracking-core/src/events/specific-page-visit.ts
1414
- import { z as z9 } from "zod";
1436
+ import { z as z10 } from "zod";
1415
1437
  var SPECIFIC_PAGE_NAMES = [
1416
1438
  "contact_page",
1417
1439
  "about_page",
@@ -1422,18 +1444,18 @@ var SPECIFIC_PAGE_NAMES = [
1422
1444
  "faq_page",
1423
1445
  "testimonials_page"
1424
1446
  ];
1425
- var specificPageNameSchema = z9.enum(SPECIFIC_PAGE_NAMES);
1426
- var specificPageVisitMetadataSchema = z9.object({
1447
+ var specificPageNameSchema = z10.enum(SPECIFIC_PAGE_NAMES);
1448
+ var specificPageVisitMetadataSchema = z10.object({
1427
1449
  page_name: specificPageNameSchema,
1428
- page: z9.object({
1429
- path: z9.string()
1450
+ page: z10.object({
1451
+ path: z10.string()
1430
1452
  }).strict()
1431
1453
  }).strict();
1432
- var specificPageVisitConfigSchema = z9.object({
1433
- pages: z9.array(
1434
- z9.object({
1454
+ var specificPageVisitConfigSchema = z10.object({
1455
+ pages: z10.array(
1456
+ z10.object({
1435
1457
  name: specificPageNameSchema,
1436
- pathPattern: z9.custom((value) => value instanceof RegExp, {
1458
+ pathPattern: z10.custom((value) => value instanceof RegExp, {
1437
1459
  message: "pathPattern must be a RegExp"
1438
1460
  })
1439
1461
  }).strict()
@@ -1441,15 +1463,15 @@ var specificPageVisitConfigSchema = z9.object({
1441
1463
  }).strict();
1442
1464
 
1443
1465
  // ../tracking-core/src/events/time-on-site.ts
1444
- import { z as z10 } from "zod";
1445
- var timeOnSiteMetadataSchema = z10.object({
1446
- duration_ms: z10.number().int().nonnegative(),
1447
- page: z10.object({
1448
- path: z10.string()
1466
+ import { z as z11 } from "zod";
1467
+ var timeOnSiteMetadataSchema = z11.object({
1468
+ duration_ms: z11.number().int().nonnegative(),
1469
+ page: z11.object({
1470
+ path: z11.string()
1449
1471
  }).strict()
1450
1472
  }).strict();
1451
- var timeOnSiteConfigSchema = z10.object({
1452
- thresholdSeconds: z10.number().int().positive()
1473
+ var timeOnSiteConfigSchema = z11.object({
1474
+ thresholdSeconds: z11.number().int().positive()
1453
1475
  }).strict();
1454
1476
 
1455
1477
  // ../tracking-core/src/events/registry.ts
@@ -1491,6 +1513,11 @@ var EVENT_REGISTRY = {
1491
1513
  metadataSchema: sdkHeartbeatMetadataSchema,
1492
1514
  configSchema: sdkHeartbeatConfigSchema
1493
1515
  },
1516
+ page_exit: {
1517
+ kind: "automatic",
1518
+ metadataSchema: pageExitMetadataSchema,
1519
+ configSchema: pageExitConfigSchema
1520
+ },
1494
1521
  // --- manual triggers ---
1495
1522
  form_submit: {
1496
1523
  kind: "manual",
@@ -1869,6 +1896,163 @@ function attachFormStart(client, config) {
1869
1896
  };
1870
1897
  }
1871
1898
 
1899
+ // ../tracking-core/src/triggers/page-exit.ts
1900
+ function attachPageExit(client) {
1901
+ if (typeof window === "undefined" || typeof document === "undefined") {
1902
+ return () => {
1903
+ };
1904
+ }
1905
+ let currentPath2 = window.location.pathname;
1906
+ let activeSince = document.visibilityState === "visible" ? Date.now() : null;
1907
+ let accumulatedMs = 0;
1908
+ let maxScrollPercent = null;
1909
+ let rafId = null;
1910
+ function getScrollPercent() {
1911
+ const doc = document.documentElement;
1912
+ const scrollTop = window.scrollY || doc.scrollTop;
1913
+ const scrollHeight = doc.scrollHeight;
1914
+ const clientHeight = doc.clientHeight;
1915
+ if (scrollHeight <= clientHeight) return 100;
1916
+ return Math.round((scrollTop + clientHeight) / scrollHeight * 100);
1917
+ }
1918
+ function onScroll() {
1919
+ if (rafId !== null) return;
1920
+ rafId = requestAnimationFrame(() => {
1921
+ rafId = null;
1922
+ const percent = getScrollPercent();
1923
+ if (percent >= 1 && (maxScrollPercent === null || percent > maxScrollPercent)) {
1924
+ maxScrollPercent = Math.min(percent, 100);
1925
+ }
1926
+ });
1927
+ }
1928
+ function settledDwellMs() {
1929
+ let total = accumulatedMs;
1930
+ if (activeSince !== null) {
1931
+ total += Date.now() - activeSince;
1932
+ }
1933
+ return Math.max(0, Math.round(total));
1934
+ }
1935
+ function emitSegment(path, flush) {
1936
+ const dwell = settledDwellMs();
1937
+ if (dwell === 0) return;
1938
+ client.trackEvent({
1939
+ eventType: "page_exit",
1940
+ metadata: {
1941
+ dwell_ms: dwell,
1942
+ max_scroll_percent: maxScrollPercent,
1943
+ page: { path }
1944
+ },
1945
+ pageUrl: window.location.href,
1946
+ occurredAt: null
1947
+ });
1948
+ if (flush) {
1949
+ client.flushBeacon();
1950
+ }
1951
+ accumulatedMs = 0;
1952
+ activeSince = document.visibilityState === "visible" ? Date.now() : null;
1953
+ }
1954
+ function onNavigate() {
1955
+ const newPath = window.location.pathname;
1956
+ if (newPath === currentPath2) return;
1957
+ emitSegment(currentPath2, false);
1958
+ currentPath2 = newPath;
1959
+ maxScrollPercent = null;
1960
+ accumulatedMs = 0;
1961
+ activeSince = document.visibilityState === "visible" ? Date.now() : null;
1962
+ }
1963
+ function onVisibilityChange() {
1964
+ if (document.visibilityState === "hidden") {
1965
+ emitSegment(currentPath2, true);
1966
+ } else {
1967
+ activeSince = Date.now();
1968
+ }
1969
+ }
1970
+ function onPageHide() {
1971
+ emitSegment(currentPath2, true);
1972
+ }
1973
+ const originalPushState = history.pushState.bind(history);
1974
+ const originalReplaceState = history.replaceState.bind(history);
1975
+ function patchedPushState(...args) {
1976
+ originalPushState(...args);
1977
+ setTimeout(onNavigate, 0);
1978
+ }
1979
+ function patchedReplaceState(...args) {
1980
+ originalReplaceState(...args);
1981
+ setTimeout(onNavigate, 0);
1982
+ }
1983
+ history.pushState = patchedPushState;
1984
+ history.replaceState = patchedReplaceState;
1985
+ window.addEventListener("popstate", onNavigate);
1986
+ window.addEventListener("scroll", onScroll, { passive: true });
1987
+ document.addEventListener("visibilitychange", onVisibilityChange);
1988
+ window.addEventListener("pagehide", onPageHide);
1989
+ return () => {
1990
+ if (rafId !== null) cancelAnimationFrame(rafId);
1991
+ history.pushState = originalPushState;
1992
+ history.replaceState = originalReplaceState;
1993
+ window.removeEventListener("popstate", onNavigate);
1994
+ window.removeEventListener("scroll", onScroll);
1995
+ document.removeEventListener("visibilitychange", onVisibilityChange);
1996
+ window.removeEventListener("pagehide", onPageHide);
1997
+ };
1998
+ }
1999
+
2000
+ // ../tracking-core/src/triggers/cta-click-capture.ts
2001
+ var DEFAULT_CTA_SELECTOR = "[data-aranova-cta]";
2002
+ var CTA_NAME_MAX_LENGTH = 120;
2003
+ function describeElement(el) {
2004
+ const tag = el.tagName.toLowerCase();
2005
+ return el.id ? `${tag}#${el.id}` : tag;
2006
+ }
2007
+ function resolveCtaName(el) {
2008
+ const explicit = el.getAttribute("data-aranova-cta");
2009
+ if (explicit && explicit.trim().length > 0) return explicit.trim();
2010
+ const text = (el.textContent ?? "").trim().replace(/\s+/g, " ");
2011
+ if (text.length > 0) return text.slice(0, CTA_NAME_MAX_LENGTH);
2012
+ return describeElement(el);
2013
+ }
2014
+ function attachCtaClickCapture(client, config) {
2015
+ if (typeof window === "undefined" || typeof document === "undefined") {
2016
+ return () => {
2017
+ };
2018
+ }
2019
+ const autoCapture = config.autoCapture;
2020
+ if (!autoCapture) {
2021
+ return () => {
2022
+ };
2023
+ }
2024
+ const selector = autoCapture.selector ?? DEFAULT_CTA_SELECTOR;
2025
+ function onClick(event) {
2026
+ const target = event.target;
2027
+ if (!(target instanceof Element)) return;
2028
+ let matched = null;
2029
+ try {
2030
+ matched = target.closest(selector);
2031
+ } catch {
2032
+ return;
2033
+ }
2034
+ if (matched === null) return;
2035
+ const href = matched instanceof HTMLAnchorElement ? matched.href || null : matched.getAttribute("href");
2036
+ client.trackEvent({
2037
+ eventType: "cta_click",
2038
+ metadata: {
2039
+ cta_name: resolveCtaName(matched),
2040
+ page: { path: window.location.pathname },
2041
+ section: matched.getAttribute("data-aranova-section"),
2042
+ destination_url: href,
2043
+ href,
2044
+ element: describeElement(matched)
2045
+ },
2046
+ pageUrl: window.location.href,
2047
+ occurredAt: null
2048
+ });
2049
+ }
2050
+ document.addEventListener("click", onClick, true);
2051
+ return () => {
2052
+ document.removeEventListener("click", onClick, true);
2053
+ };
2054
+ }
2055
+
1872
2056
  // ../tracking-core/src/resources/sales/errors.ts
1873
2057
  var AranovaApiError = class extends Error {
1874
2058
  constructor(message, options) {
@@ -2075,42 +2259,42 @@ function createSalesClient(config) {
2075
2259
  }
2076
2260
 
2077
2261
  // ../tracking-core/src/resources/sales/schema.ts
2078
- import { z as z11 } from "zod";
2262
+ import { z as z12 } from "zod";
2079
2263
  var SUPPORTED_CURRENCIES = ["USD", "CAD"];
2080
2264
  var TRACKING_ENVIRONMENTS = ["production", "development"];
2081
- var currencySchema = z11.enum(SUPPORTED_CURRENCIES);
2082
- var centsSchema = z11.number().int().nonnegative();
2083
- var quantitySchema = z11.string().regex(/^\d+(\.\d{1,3})?$/);
2084
- var metadataSchema = z11.record(z11.unknown());
2085
- var saleItemSchema = z11.object({
2086
- external_item_id: z11.string().nullable().optional(),
2087
- name: z11.string().nullable().optional(),
2088
- category: z11.string().nullable().optional(),
2265
+ var currencySchema = z12.enum(SUPPORTED_CURRENCIES);
2266
+ var centsSchema = z12.number().int().nonnegative();
2267
+ var quantitySchema = z12.string().regex(/^\d+(\.\d{1,3})?$/);
2268
+ var metadataSchema = z12.record(z12.unknown());
2269
+ var saleItemSchema = z12.object({
2270
+ external_item_id: z12.string().nullable().optional(),
2271
+ name: z12.string().nullable().optional(),
2272
+ category: z12.string().nullable().optional(),
2089
2273
  quantity: quantitySchema,
2090
2274
  unit_price_cents: centsSchema,
2091
2275
  // Non-negativity validated on the wire — same contract as the other cents
2092
2276
  // fields — and backstopped by the DB CHECK.
2093
2277
  unit_cost_cents: centsSchema.nullable().optional()
2094
2278
  }).strict();
2095
- var saleServiceSchema = z11.object({
2096
- service: z11.string(),
2279
+ var saleServiceSchema = z12.object({
2280
+ service: z12.string(),
2097
2281
  amount_cents: centsSchema
2098
2282
  }).strict();
2099
- var customerNameSchema = z11.string().max(200);
2100
- var customerPhoneSchema = z11.string().max(64);
2101
- var customerEmailSchema = z11.string().max(320).email();
2283
+ var customerNameSchema = z12.string().max(200);
2284
+ var customerPhoneSchema = z12.string().max(64);
2285
+ var customerEmailSchema = z12.string().max(320).email();
2102
2286
  function refineServiceXor(val, ctx, { requireAmount }) {
2103
2287
  if (val.services != null) {
2104
2288
  if (val.service != null) {
2105
2289
  ctx.addIssue({
2106
- code: z11.ZodIssueCode.custom,
2290
+ code: z12.ZodIssueCode.custom,
2107
2291
  message: "pass either `service` or `services`, not both",
2108
2292
  path: ["services"]
2109
2293
  });
2110
2294
  }
2111
2295
  if (val.services.length === 0) {
2112
2296
  ctx.addIssue({
2113
- code: z11.ZodIssueCode.custom,
2297
+ code: z12.ZodIssueCode.custom,
2114
2298
  message: "`services` must not be empty",
2115
2299
  path: ["services"]
2116
2300
  });
@@ -2118,7 +2302,7 @@ function refineServiceXor(val, ctx, { requireAmount }) {
2118
2302
  const keys = val.services.map((s) => s.service);
2119
2303
  if (new Set(keys).size !== keys.length) {
2120
2304
  ctx.addIssue({
2121
- code: z11.ZodIssueCode.custom,
2305
+ code: z12.ZodIssueCode.custom,
2122
2306
  message: "`services` must not list the same service more than once",
2123
2307
  path: ["services"]
2124
2308
  });
@@ -2127,7 +2311,7 @@ function refineServiceXor(val, ctx, { requireAmount }) {
2127
2311
  const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
2128
2312
  if (val.amount_total_cents !== sum) {
2129
2313
  ctx.addIssue({
2130
- code: z11.ZodIssueCode.custom,
2314
+ code: z12.ZodIssueCode.custom,
2131
2315
  message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
2132
2316
  path: ["amount_total_cents"]
2133
2317
  });
@@ -2135,37 +2319,37 @@ function refineServiceXor(val, ctx, { requireAmount }) {
2135
2319
  }
2136
2320
  } else if (requireAmount && val.amount_total_cents == null) {
2137
2321
  ctx.addIssue({
2138
- code: z11.ZodIssueCode.custom,
2322
+ code: z12.ZodIssueCode.custom,
2139
2323
  message: "amount_total_cents is required unless `services` is provided",
2140
2324
  path: ["amount_total_cents"]
2141
2325
  });
2142
2326
  }
2143
2327
  }
2144
- var saleCreateSchema = z11.object({
2145
- external_id: z11.string().nullable().optional(),
2146
- description: z11.string().nullable().optional(),
2147
- service: z11.string().nullable().optional(),
2148
- services: z11.array(saleServiceSchema).nullable().optional(),
2328
+ var saleCreateSchema = z12.object({
2329
+ external_id: z12.string().nullable().optional(),
2330
+ description: z12.string().nullable().optional(),
2331
+ service: z12.string().nullable().optional(),
2332
+ services: z12.array(saleServiceSchema).nullable().optional(),
2149
2333
  currency: currencySchema,
2150
2334
  // Optional only because the plural `services` form derives it from the sum
2151
2335
  // (see refineServiceXor); the singular/serviceless path still requires it.
2152
2336
  amount_total_cents: centsSchema.nullable().optional(),
2153
- occurred_at: z11.string().datetime(),
2154
- environment: z11.enum(TRACKING_ENVIRONMENTS).default("production"),
2155
- items: z11.array(saleItemSchema).default([]),
2337
+ occurred_at: z12.string().datetime(),
2338
+ environment: z12.enum(TRACKING_ENVIRONMENTS).default("production"),
2339
+ items: z12.array(saleItemSchema).default([]),
2156
2340
  metadata: metadataSchema.nullable().optional(),
2157
2341
  customer_name: customerNameSchema.nullable().optional(),
2158
2342
  customer_phone: customerPhoneSchema.nullable().optional(),
2159
2343
  customer_email: customerEmailSchema.nullable().optional()
2160
2344
  }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
2161
- var saleUpdateSchema = z11.object({
2162
- description: z11.string().nullable().optional(),
2163
- service: z11.string().nullable().optional(),
2164
- services: z11.array(saleServiceSchema).nullable().optional(),
2345
+ var saleUpdateSchema = z12.object({
2346
+ description: z12.string().nullable().optional(),
2347
+ service: z12.string().nullable().optional(),
2348
+ services: z12.array(saleServiceSchema).nullable().optional(),
2165
2349
  currency: currencySchema.optional(),
2166
2350
  amount_total_cents: centsSchema.optional(),
2167
- occurred_at: z11.string().datetime().optional(),
2168
- items: z11.array(saleItemSchema).optional(),
2351
+ occurred_at: z12.string().datetime().optional(),
2352
+ items: z12.array(saleItemSchema).optional(),
2169
2353
  metadata: metadataSchema.nullable().optional(),
2170
2354
  customer_name: customerNameSchema.nullable().optional(),
2171
2355
  customer_phone: customerPhoneSchema.nullable().optional(),
@@ -2553,7 +2737,7 @@ function GoogleAdsTracking(props) {
2553
2737
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect5, useMemo as useMemo4 } from "react";
2554
2738
 
2555
2739
  // package.json
2556
- var version = "0.15.0";
2740
+ var version = "0.16.1";
2557
2741
 
2558
2742
  // ../tracking-core/src/phone-react.tsx
2559
2743
  import {
@@ -2741,6 +2925,7 @@ function createTracking(options) {
2741
2925
  const detectorClient = conversionStore ? withConversionAutoFire(rawClient, createConversionAutoFire(conversionStore)) : rawClient;
2742
2926
  detachers.push(attachAutoPageView(detectorClient));
2743
2927
  detachers.push(attachBfcacheRestore(detectorClient));
2928
+ detachers.push(attachPageExit(detectorClient));
2744
2929
  const timeOnSite = triggers.automatic.time_on_site;
2745
2930
  if (timeOnSite) {
2746
2931
  detachers.push(attachTimeOnSite(detectorClient, timeOnSite));
@@ -2761,6 +2946,10 @@ function createTracking(options) {
2761
2946
  if (formStart) {
2762
2947
  detachers.push(attachFormStart(detectorClient, formStart));
2763
2948
  }
2949
+ const ctaClick = triggers.manual?.cta_click;
2950
+ if (ctaClick) {
2951
+ detachers.push(attachCtaClickCapture(detectorClient, ctaClick));
2952
+ }
2764
2953
  return () => {
2765
2954
  for (let i = detachers.length - 1; i >= 0; i--) {
2766
2955
  detachers[i]();