@aranova/tracking-next 0.22.0 → 0.22.3

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
@@ -1744,6 +1744,9 @@ function buildHeartbeatMetadata(surface, sdkVersion, packageName, triggers, gtag
1744
1744
  var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
1745
1745
  var DEFAULT_MAX_QUEUE_SIZE = 10;
1746
1746
  var HARD_MAX_BATCH = 50;
1747
+ var MAX_RETRY_BACKOFF_MS = 6e4;
1748
+ var MAX_BUFFERED_EVENTS2 = 200;
1749
+ var RETRY_BUFFER_KEY_PREFIX = "aranova_tracking_pending_v1";
1747
1750
  var API_KEY_HEADER = "X-Aranova-Api-Key";
1748
1751
  var SDK_VERSION_HEADER = "X-Aranova-Sdk-Version";
1749
1752
  var SDK_PACKAGE_HEADER = "X-Aranova-Sdk-Package";
@@ -1786,9 +1789,9 @@ function consentSnapshot() {
1786
1789
  }
1787
1790
  }
1788
1791
  async function postWithFetch(url, body, apiKey, identity, keepalive) {
1789
- if (typeof fetch !== "function") return;
1792
+ if (typeof fetch !== "function") return "drop";
1790
1793
  try {
1791
- await fetch(url, {
1794
+ const response = await fetch(url, {
1792
1795
  method: "POST",
1793
1796
  headers: {
1794
1797
  "Content-Type": "application/json",
@@ -1804,9 +1807,49 @@ async function postWithFetch(url, body, apiKey, identity, keepalive) {
1804
1807
  credentials: "omit",
1805
1808
  mode: "cors"
1806
1809
  });
1810
+ if (response.ok) return "ok";
1811
+ if (response.status === 408 || response.status === 429 || response.status >= 500) {
1812
+ return "retry";
1813
+ }
1814
+ return "drop";
1807
1815
  } catch {
1816
+ return "retry";
1808
1817
  }
1809
1818
  }
1819
+ function retryBufferKey(apiKey, endpoint) {
1820
+ return `${RETRY_BUFFER_KEY_PREFIX}:${apiKey}:${endpoint}`;
1821
+ }
1822
+ function readPendingBatches(key) {
1823
+ if (typeof window === "undefined") return [];
1824
+ try {
1825
+ const raw = window.localStorage.getItem(key);
1826
+ if (!raw) return [];
1827
+ const parsed = JSON.parse(raw);
1828
+ if (!Array.isArray(parsed)) return [];
1829
+ return parsed.filter(
1830
+ (entry) => typeof entry === "object" && entry !== null && "session" in entry && Array.isArray(entry.events)
1831
+ );
1832
+ } catch {
1833
+ return [];
1834
+ }
1835
+ }
1836
+ function writePendingBatches(key, batches) {
1837
+ if (typeof window === "undefined") return;
1838
+ try {
1839
+ if (batches.length === 0) window.localStorage.removeItem(key);
1840
+ else window.localStorage.setItem(key, JSON.stringify(batches));
1841
+ } catch {
1842
+ }
1843
+ }
1844
+ function trimToBufferCap(batches) {
1845
+ let total = batches.reduce((sum, batch) => sum + batch.events.length, 0);
1846
+ const trimmed = batches.slice();
1847
+ while (total > MAX_BUFFERED_EVENTS2 && trimmed.length > 1) {
1848
+ const dropped = trimmed.shift();
1849
+ total -= dropped ? dropped.events.length : 0;
1850
+ }
1851
+ return trimmed;
1852
+ }
1810
1853
  var globalClient = null;
1811
1854
  var globalClientKey = null;
1812
1855
  function clientConfigKey(config) {
@@ -1862,6 +1905,10 @@ function createTrackingClient(config) {
1862
1905
  };
1863
1906
  let queue = [];
1864
1907
  let flushTimer = null;
1908
+ const bufferKey = retryBufferKey(config.apiKey, endpointBase);
1909
+ let pending = readPendingBatches(bufferKey);
1910
+ let flushChain = Promise.resolve();
1911
+ let retryAttempt = 0;
1865
1912
  let firstPage = null;
1866
1913
  let initialParams = createEmptyTrackingParams();
1867
1914
  let destroyed = false;
@@ -1898,6 +1945,9 @@ function createTrackingClient(config) {
1898
1945
  if (initialSession.isNew) {
1899
1946
  enqueueHeartbeat();
1900
1947
  }
1948
+ if (pending.length > 0) {
1949
+ scheduleFlush();
1950
+ }
1901
1951
  function buildSessionPayload() {
1902
1952
  const rotated = getOrRotateSessionId();
1903
1953
  if (rotated.isNew && rotated.id !== sessionId) {
@@ -1936,12 +1986,12 @@ function createTrackingClient(config) {
1936
1986
  context
1937
1987
  };
1938
1988
  }
1939
- function scheduleFlush() {
1989
+ function scheduleFlush(delayMs = flushIntervalMs) {
1940
1990
  if (flushTimer !== null || destroyed) return;
1941
1991
  flushTimer = setTimeout(() => {
1942
1992
  flushTimer = null;
1943
1993
  void flush();
1944
- }, flushIntervalMs);
1994
+ }, delayMs);
1945
1995
  }
1946
1996
  function clearScheduledFlush() {
1947
1997
  if (flushTimer !== null) {
@@ -1949,17 +1999,50 @@ function createTrackingClient(config) {
1949
1999
  flushTimer = null;
1950
2000
  }
1951
2001
  }
1952
- async function flush() {
2002
+ function bufferQueued() {
1953
2003
  if (queue.length === 0) return;
1954
2004
  const events = queue.slice(0, HARD_MAX_BATCH);
1955
2005
  queue = queue.slice(events.length);
2006
+ pending = trimToBufferCap([...pending, { session: buildSessionPayload(), events }]);
2007
+ writePendingBatches(bufferKey, pending);
2008
+ }
2009
+ function flush() {
2010
+ flushChain = flushChain.then(runFlush).catch(() => {
2011
+ });
2012
+ return flushChain;
2013
+ }
2014
+ async function runFlush() {
2015
+ if (destroyed) return;
1956
2016
  clearScheduledFlush();
1957
- const body = {
1958
- session: buildSessionPayload(),
1959
- events
1960
- };
1961
- const serialized = JSON.stringify(body);
1962
- await postWithFetch(eventsUrl, serialized, config.apiKey, identityHeaders2, false);
2017
+ bufferQueued();
2018
+ if (pending.length === 0) return;
2019
+ try {
2020
+ while (pending.length > 0) {
2021
+ const batch = pending[0];
2022
+ const body = { session: batch.session, events: batch.events };
2023
+ const outcome = await postWithFetch(
2024
+ eventsUrl,
2025
+ JSON.stringify(body),
2026
+ config.apiKey,
2027
+ identityHeaders2,
2028
+ false
2029
+ );
2030
+ if (outcome === "retry") {
2031
+ retryAttempt += 1;
2032
+ return;
2033
+ }
2034
+ pending = pending.slice(1);
2035
+ writePendingBatches(bufferKey, pending);
2036
+ retryAttempt = 0;
2037
+ }
2038
+ } finally {
2039
+ if (pending.length > 0) {
2040
+ const backoff = Math.min(flushIntervalMs * 2 ** retryAttempt, MAX_RETRY_BACKOFF_MS);
2041
+ scheduleFlush(backoff);
2042
+ } else if (queue.length > 0) {
2043
+ scheduleFlush();
2044
+ }
2045
+ }
1963
2046
  }
1964
2047
  function trackEvent(input) {
1965
2048
  if (destroyed) return;
@@ -1985,16 +2068,14 @@ function createTrackingClient(config) {
1985
2068
  }
1986
2069
  }
1987
2070
  function flushOnUnload() {
1988
- if (queue.length === 0) return;
1989
- const events = queue.slice(0, HARD_MAX_BATCH);
1990
- queue = queue.slice(events.length);
2071
+ bufferQueued();
1991
2072
  clearScheduledFlush();
1992
- const body = {
1993
- session: buildSessionPayload(),
1994
- events
1995
- };
1996
- const serialized = JSON.stringify(body);
1997
- void postWithFetch(eventsUrl, serialized, config.apiKey, identityHeaders2, true);
2073
+ if (pending.length === 0) return;
2074
+ const batch = pending[0];
2075
+ pending = pending.slice(1);
2076
+ writePendingBatches(bufferKey, pending);
2077
+ const body = { session: batch.session, events: batch.events };
2078
+ void postWithFetch(eventsUrl, JSON.stringify(body), config.apiKey, identityHeaders2, true);
1998
2079
  }
1999
2080
  if (typeof window !== "undefined") {
2000
2081
  window.addEventListener("pagehide", flushOnUnload);
@@ -3169,7 +3250,12 @@ var saleCreateSchema = import_zod12.z.object({
3169
3250
  customer_email: customerEmailSchema.nullable().optional(),
3170
3251
  // CASL consent attestation: the customer agreed to receive SMS. Recorded
3171
3252
  // with a timestamp server-side; every SMS send path gates on it.
3172
- sms_consent: import_zod12.z.boolean().default(false)
3253
+ //
3254
+ // Tri-state: omit it (or send null) to let the server apply the business's
3255
+ // configured default-opt-in policy, honouring a returning customer's
3256
+ // remembered preference. Send an explicit boolean to assert consent state
3257
+ // yourself — `false` records a deliberate opt-out.
3258
+ sms_consent: import_zod12.z.boolean().nullable().optional()
3173
3259
  }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
3174
3260
  var saleUpdateSchema = import_zod12.z.object({
3175
3261
  description: import_zod12.z.string().nullable().optional(),
@@ -3181,6 +3267,9 @@ var saleUpdateSchema = import_zod12.z.object({
3181
3267
  items: import_zod12.z.array(saleItemSchema).optional(),
3182
3268
  metadata: metadataSchema.nullable().optional(),
3183
3269
  customer_name: customerNameSchema.nullable().optional(),
3270
+ // Re-attest when changing a filler-looking name; the server clears the
3271
+ // prior attestation whenever `customer_name` changes.
3272
+ customer_name_placeholder_confirmed: import_zod12.z.boolean().optional(),
3184
3273
  customer_phone: customerPhoneSchema.nullable().optional(),
3185
3274
  customer_email: customerEmailSchema.nullable().optional(),
3186
3275
  // NOT NULL server-side: omit to leave unchanged (explicit null is rejected).
@@ -3555,7 +3644,7 @@ function GoogleAdsTracking(props) {
3555
3644
  var import_navigation3 = require("next/navigation");
3556
3645
 
3557
3646
  // package.json
3558
- var version = "0.22.0";
3647
+ var version = "0.22.3";
3559
3648
 
3560
3649
  // src/factory.tsx
3561
3650
  var import_react5 = require("react");