@aranova/tracking-react 0.21.0 → 0.22.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
@@ -250,6 +250,43 @@ function resetConsent() {
250
250
  notifyConsentChanged(DEFAULT_CHOICE);
251
251
  }
252
252
 
253
+ // ../tracking-core/src/capabilities.ts
254
+ var TRACKING_CAPABILITIES = [
255
+ "ad_tags_google",
256
+ "ad_tags_meta",
257
+ "base_tracking",
258
+ "blog_rendering",
259
+ "calendar_read",
260
+ "calendar_write",
261
+ "consent_controls",
262
+ "conversion_goals_auto",
263
+ "conversion_goals_manual",
264
+ "cta_click_capture",
265
+ "form_capture",
266
+ "phone_click_capture",
267
+ "phone_fields"
268
+ ];
269
+ var CAPABILITY_DOM_ATTRIBUTE = "data-aranova-capability";
270
+ var registered = /* @__PURE__ */ new Set();
271
+ function registerCapability(capability) {
272
+ registered.add(capability);
273
+ }
274
+ function getRegisteredCapabilities() {
275
+ const all = new Set(registered);
276
+ for (const marker of readDomMarkers()) all.add(marker);
277
+ return Array.from(all).sort();
278
+ }
279
+ function readDomMarkers() {
280
+ if (typeof document === "undefined") return [];
281
+ const known = new Set(TRACKING_CAPABILITIES);
282
+ const found = [];
283
+ for (const node of document.querySelectorAll(`[${CAPABILITY_DOM_ATTRIBUTE}]`)) {
284
+ const value = node.getAttribute(CAPABILITY_DOM_ATTRIBUTE);
285
+ if (value && known.has(value)) found.push(value);
286
+ }
287
+ return found;
288
+ }
289
+
253
290
  // ../tracking-core/src/tracking.ts
254
291
  var TRACKING_COOKIE_MAX_AGE_SECONDS = 7776e3;
255
292
  var TRACKING_PARAM_KEYS = [
@@ -623,7 +660,8 @@ function createTrackingClientContext(surface, input = {}) {
623
660
  page_title: input.pageTitle ?? (typeof document === "undefined" ? null : document.title || null),
624
661
  referrer: input.referrer ?? (typeof document === "undefined" ? null : document.referrer || null),
625
662
  environment: input.environment ?? "production",
626
- active_gtag_ids: input.activeGtagIds ?? null
663
+ active_gtag_ids: input.activeGtagIds ?? null,
664
+ capabilities: getRegisteredCapabilities()
627
665
  };
628
666
  }
629
667
  function createTrackingSessionUpsertPayload(trackingParams, input, context) {
@@ -1641,6 +1679,9 @@ function buildHeartbeatMetadata(surface, sdkVersion, packageName, triggers, gtag
1641
1679
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
1642
1680
  var DEFAULT_MAX_QUEUE_SIZE = 10;
1643
1681
  var HARD_MAX_BATCH = 50;
1682
+ var MAX_RETRY_BACKOFF_MS = 6e4;
1683
+ var MAX_BUFFERED_EVENTS2 = 200;
1684
+ var RETRY_BUFFER_KEY_PREFIX = "aranova_tracking_pending_v1";
1644
1685
  var API_KEY_HEADER = "X-Aranova-Api-Key";
1645
1686
  var SDK_VERSION_HEADER = "X-Aranova-Sdk-Version";
1646
1687
  var SDK_PACKAGE_HEADER = "X-Aranova-Sdk-Package";
@@ -1655,7 +1696,10 @@ function buildContext(surface, sdkVersion, packageName, environment, activeGtagI
1655
1696
  page_title: typeof document === "undefined" ? null : document.title || null,
1656
1697
  referrer: typeof document === "undefined" ? null : document.referrer || null,
1657
1698
  environment,
1658
- active_gtag_ids: activeGtagIds
1699
+ active_gtag_ids: activeGtagIds,
1700
+ // Rebuilt per flush (this runs inside the payload builder), so a client
1701
+ // constructed later on a deeper route still gets reported.
1702
+ capabilities: getRegisteredCapabilities()
1659
1703
  };
1660
1704
  }
1661
1705
  function readTrackingParams() {
@@ -1680,9 +1724,9 @@ function consentSnapshot() {
1680
1724
  }
1681
1725
  }
1682
1726
  async function postWithFetch(url, body, apiKey, identity, keepalive) {
1683
- if (typeof fetch !== "function") return;
1727
+ if (typeof fetch !== "function") return "drop";
1684
1728
  try {
1685
- await fetch(url, {
1729
+ const response = await fetch(url, {
1686
1730
  method: "POST",
1687
1731
  headers: {
1688
1732
  "Content-Type": "application/json",
@@ -1698,9 +1742,49 @@ async function postWithFetch(url, body, apiKey, identity, keepalive) {
1698
1742
  credentials: "omit",
1699
1743
  mode: "cors"
1700
1744
  });
1745
+ if (response.ok) return "ok";
1746
+ if (response.status === 408 || response.status === 429 || response.status >= 500) {
1747
+ return "retry";
1748
+ }
1749
+ return "drop";
1701
1750
  } catch {
1751
+ return "retry";
1702
1752
  }
1703
1753
  }
1754
+ function retryBufferKey(apiKey, endpoint) {
1755
+ return `${RETRY_BUFFER_KEY_PREFIX}:${apiKey}:${endpoint}`;
1756
+ }
1757
+ function readPendingBatches(key) {
1758
+ if (typeof window === "undefined") return [];
1759
+ try {
1760
+ const raw = window.localStorage.getItem(key);
1761
+ if (!raw) return [];
1762
+ const parsed = JSON.parse(raw);
1763
+ if (!Array.isArray(parsed)) return [];
1764
+ return parsed.filter(
1765
+ (entry) => typeof entry === "object" && entry !== null && "session" in entry && Array.isArray(entry.events)
1766
+ );
1767
+ } catch {
1768
+ return [];
1769
+ }
1770
+ }
1771
+ function writePendingBatches(key, batches) {
1772
+ if (typeof window === "undefined") return;
1773
+ try {
1774
+ if (batches.length === 0) window.localStorage.removeItem(key);
1775
+ else window.localStorage.setItem(key, JSON.stringify(batches));
1776
+ } catch {
1777
+ }
1778
+ }
1779
+ function trimToBufferCap(batches) {
1780
+ let total = batches.reduce((sum, batch) => sum + batch.events.length, 0);
1781
+ const trimmed = batches.slice();
1782
+ while (total > MAX_BUFFERED_EVENTS2 && trimmed.length > 1) {
1783
+ const dropped = trimmed.shift();
1784
+ total -= dropped ? dropped.events.length : 0;
1785
+ }
1786
+ return trimmed;
1787
+ }
1704
1788
  var globalClient = null;
1705
1789
  var globalClientKey = null;
1706
1790
  function clientConfigKey(config) {
@@ -1756,6 +1840,10 @@ function createTrackingClient(config) {
1756
1840
  };
1757
1841
  let queue = [];
1758
1842
  let flushTimer = null;
1843
+ const bufferKey = retryBufferKey(config.apiKey, endpointBase);
1844
+ let pending = readPendingBatches(bufferKey);
1845
+ let flushChain = Promise.resolve();
1846
+ let retryAttempt = 0;
1759
1847
  let firstPage = null;
1760
1848
  let initialParams = createEmptyTrackingParams();
1761
1849
  let destroyed = false;
@@ -1792,6 +1880,9 @@ function createTrackingClient(config) {
1792
1880
  if (initialSession.isNew) {
1793
1881
  enqueueHeartbeat();
1794
1882
  }
1883
+ if (pending.length > 0) {
1884
+ scheduleFlush();
1885
+ }
1795
1886
  function buildSessionPayload() {
1796
1887
  const rotated = getOrRotateSessionId();
1797
1888
  if (rotated.isNew && rotated.id !== sessionId) {
@@ -1830,12 +1921,12 @@ function createTrackingClient(config) {
1830
1921
  context
1831
1922
  };
1832
1923
  }
1833
- function scheduleFlush() {
1924
+ function scheduleFlush(delayMs = flushIntervalMs) {
1834
1925
  if (flushTimer !== null || destroyed) return;
1835
1926
  flushTimer = setTimeout(() => {
1836
1927
  flushTimer = null;
1837
1928
  void flush();
1838
- }, flushIntervalMs);
1929
+ }, delayMs);
1839
1930
  }
1840
1931
  function clearScheduledFlush() {
1841
1932
  if (flushTimer !== null) {
@@ -1843,17 +1934,50 @@ function createTrackingClient(config) {
1843
1934
  flushTimer = null;
1844
1935
  }
1845
1936
  }
1846
- async function flush() {
1937
+ function bufferQueued() {
1847
1938
  if (queue.length === 0) return;
1848
1939
  const events = queue.slice(0, HARD_MAX_BATCH);
1849
1940
  queue = queue.slice(events.length);
1941
+ pending = trimToBufferCap([...pending, { session: buildSessionPayload(), events }]);
1942
+ writePendingBatches(bufferKey, pending);
1943
+ }
1944
+ function flush() {
1945
+ flushChain = flushChain.then(runFlush).catch(() => {
1946
+ });
1947
+ return flushChain;
1948
+ }
1949
+ async function runFlush() {
1950
+ if (destroyed) return;
1850
1951
  clearScheduledFlush();
1851
- const body = {
1852
- session: buildSessionPayload(),
1853
- events
1854
- };
1855
- const serialized = JSON.stringify(body);
1856
- await postWithFetch(eventsUrl, serialized, config.apiKey, identityHeaders2, false);
1952
+ bufferQueued();
1953
+ if (pending.length === 0) return;
1954
+ try {
1955
+ while (pending.length > 0) {
1956
+ const batch = pending[0];
1957
+ const body = { session: batch.session, events: batch.events };
1958
+ const outcome = await postWithFetch(
1959
+ eventsUrl,
1960
+ JSON.stringify(body),
1961
+ config.apiKey,
1962
+ identityHeaders2,
1963
+ false
1964
+ );
1965
+ if (outcome === "retry") {
1966
+ retryAttempt += 1;
1967
+ return;
1968
+ }
1969
+ pending = pending.slice(1);
1970
+ writePendingBatches(bufferKey, pending);
1971
+ retryAttempt = 0;
1972
+ }
1973
+ } finally {
1974
+ if (pending.length > 0) {
1975
+ const backoff = Math.min(flushIntervalMs * 2 ** retryAttempt, MAX_RETRY_BACKOFF_MS);
1976
+ scheduleFlush(backoff);
1977
+ } else if (queue.length > 0) {
1978
+ scheduleFlush();
1979
+ }
1980
+ }
1857
1981
  }
1858
1982
  function trackEvent(input) {
1859
1983
  if (destroyed) return;
@@ -1879,16 +2003,14 @@ function createTrackingClient(config) {
1879
2003
  }
1880
2004
  }
1881
2005
  function flushOnUnload() {
1882
- if (queue.length === 0) return;
1883
- const events = queue.slice(0, HARD_MAX_BATCH);
1884
- queue = queue.slice(events.length);
2006
+ bufferQueued();
1885
2007
  clearScheduledFlush();
1886
- const body = {
1887
- session: buildSessionPayload(),
1888
- events
1889
- };
1890
- const serialized = JSON.stringify(body);
1891
- void postWithFetch(eventsUrl, serialized, config.apiKey, identityHeaders2, true);
2008
+ if (pending.length === 0) return;
2009
+ const batch = pending[0];
2010
+ pending = pending.slice(1);
2011
+ writePendingBatches(bufferKey, pending);
2012
+ const body = { session: batch.session, events: batch.events };
2013
+ void postWithFetch(eventsUrl, JSON.stringify(body), config.apiKey, identityHeaders2, true);
1892
2014
  }
1893
2015
  if (typeof window !== "undefined") {
1894
2016
  window.addEventListener("pagehide", flushOnUnload);
@@ -2248,8 +2370,9 @@ function getEventDefinition(name) {
2248
2370
  }
2249
2371
 
2250
2372
  // ../tracking-core/src/ingest-typed.ts
2251
- function createTypedClient(raw, _registry, options = {}) {
2373
+ function createTypedClient(raw, registry, options = {}) {
2252
2374
  const debug = options.debug ?? false;
2375
+ if (registry.manual?.form_submit) registerCapability("form_capture");
2253
2376
  return {
2254
2377
  trackEvent(eventType, metadata, opts) {
2255
2378
  if (debug) {
@@ -2791,6 +2914,7 @@ function attachCtaClickCapture(client, config) {
2791
2914
  return () => {
2792
2915
  };
2793
2916
  }
2917
+ registerCapability("cta_click_capture");
2794
2918
  const selector = autoCapture.selector ?? DEFAULT_CTA_SELECTOR;
2795
2919
  function onClick(event) {
2796
2920
  const target = event.target;
@@ -2847,6 +2971,7 @@ function attachPhoneClickCapture(client, config) {
2847
2971
  return () => {
2848
2972
  };
2849
2973
  }
2974
+ registerCapability("phone_click_capture");
2850
2975
  const selector = autoCapture.selector ?? DEFAULT_TEL_SELECTOR;
2851
2976
  function onClick(event) {
2852
2977
  const target = event.target;
@@ -2963,6 +3088,7 @@ function fireRecordedConversions(firing, input, recorded, sale, currency) {
2963
3088
  }
2964
3089
  }
2965
3090
  function createSalesClient(config) {
3091
+ if (config.firing) registerCapability("conversion_goals_manual");
2966
3092
  async function record(input) {
2967
3093
  const currency = input.currency ?? config.defaultCurrency;
2968
3094
  if (!currency) {
@@ -3206,6 +3332,9 @@ var saleUpdateSchema = z12.object({
3206
3332
  items: z12.array(saleItemSchema).optional(),
3207
3333
  metadata: metadataSchema.nullable().optional(),
3208
3334
  customer_name: customerNameSchema.nullable().optional(),
3335
+ // Re-attest when changing a filler-looking name; the server clears the
3336
+ // prior attestation whenever `customer_name` changes.
3337
+ customer_name_placeholder_confirmed: z12.boolean().optional(),
3209
3338
  customer_phone: customerPhoneSchema.nullable().optional(),
3210
3339
  customer_email: customerEmailSchema.nullable().optional(),
3211
3340
  // NOT NULL server-side: omit to leave unchanged (explicit null is rejected).
@@ -3258,6 +3387,7 @@ var DEFAULT_CHOICE2 = {
3258
3387
  expiresAt: null
3259
3388
  };
3260
3389
  function useCookiePreferences(options) {
3390
+ registerCapability("consent_controls");
3261
3391
  const [choice, setChoice] = useState(DEFAULT_CHOICE2);
3262
3392
  const ttlDays = options?.declineTtlDays;
3263
3393
  useEffect(() => {
@@ -3520,6 +3650,12 @@ function AdPlatformTracking({
3520
3650
  () => metaPixelIds ? JSON.stringify(metaPixelIds) : "",
3521
3651
  [metaPixelIds]
3522
3652
  );
3653
+ if (trackingConfig || gtagId || gtagIds && Object.keys(gtagIds).length > 0) {
3654
+ registerCapability("ad_tags_google");
3655
+ }
3656
+ if (metaPixelId || metaPixelIds && Object.keys(metaPixelIds).length > 0) {
3657
+ registerCapability("ad_tags_meta");
3658
+ }
3523
3659
  useEffect3(() => {
3524
3660
  if (trackingConfig) {
3525
3661
  const runtime = getTrackingConfigRuntime(trackingConfig);
@@ -3560,7 +3696,7 @@ function GoogleAdsTracking(props) {
3560
3696
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect5, useMemo as useMemo4 } from "react";
3561
3697
 
3562
3698
  // package.json
3563
- var version = "0.21.0";
3699
+ var version = "0.22.2";
3564
3700
 
3565
3701
  // ../tracking-core/src/phone-react.tsx
3566
3702
  import {
@@ -3591,6 +3727,7 @@ function usePhoneConfig() {
3591
3727
  };
3592
3728
  }
3593
3729
  function usePhoneField(opts = {}) {
3730
+ registerCapability("phone_fields");
3594
3731
  const cfg = usePhoneConfig();
3595
3732
  const country = opts.country ?? cfg.defaultCountry;
3596
3733
  const display = opts.display ?? cfg.display;
@@ -3676,6 +3813,8 @@ function createTracking(options) {
3676
3813
  conversionConfig,
3677
3814
  trackingConfig
3678
3815
  } = options;
3816
+ registerCapability("base_tracking");
3817
+ if (trackingConfig) registerCapability("conversion_goals_auto");
3679
3818
  if (!apiKey || !endpoint) {
3680
3819
  if (apiKey || endpoint) {
3681
3820
  console.warn(