@aranova/tracking-react 1.3.0 → 1.4.0

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
@@ -666,6 +666,10 @@ function fireConversionWithConsent(input, options) {
666
666
  }
667
667
 
668
668
  // ../tracking-core/src/resources/conversion-config.ts
669
+ var BOOKING_MODES = ["live", "test", "simulated"];
670
+ function isBookingMode(value) {
671
+ return BOOKING_MODES.includes(value);
672
+ }
669
673
  function isStringMap(value) {
670
674
  return typeof value === "object" && value !== null && Object.values(value).every((v) => typeof v === "string");
671
675
  }
@@ -737,7 +741,8 @@ function parseIntegrations(value) {
737
741
  config_version: typeof b.config_version === "number" ? b.config_version : null,
738
742
  accepts_bookings: b.accepts_bookings === true,
739
743
  rules: parseBookingRules(b.rules),
740
- options: Array.isArray(b.options) ? b.options.flatMap(parseBookingOption) : []
744
+ options: Array.isArray(b.options) ? b.options.flatMap(parseBookingOption) : [],
745
+ ...isBookingMode(b.mode) ? { mode: b.mode } : {}
741
746
  }
742
747
  }
743
748
  };
@@ -1444,7 +1449,8 @@ function getBookingIntegration(integrations) {
1444
1449
  timezone: booking.timezone,
1445
1450
  configVersion: booking.config_version,
1446
1451
  rules,
1447
- options: booking.options.map((option) => ({ key: option.key, label: option.label }))
1452
+ options: booking.options.map((option) => ({ key: option.key, label: option.label })),
1453
+ mode: booking.mode ?? "live"
1448
1454
  };
1449
1455
  }
1450
1456
  function camelRules(rules) {
@@ -1685,6 +1691,7 @@ async function apiRequest(config, method, path, body, extraHeaders) {
1685
1691
  var salesRequest = apiRequest;
1686
1692
 
1687
1693
  // ../tracking-core/src/resources/sales/bookings.ts
1694
+ var BOOKING_SIMULATE_HEADER = "X-Aranova-Simulate";
1688
1695
  var BOOKING_ERROR_CODES = {
1689
1696
  unavailable: "booking_unavailable",
1690
1697
  stale: "booking_config_stale",
@@ -1699,10 +1706,11 @@ function sleep(ms) {
1699
1706
  });
1700
1707
  }
1701
1708
  function outcomeOf(body) {
1709
+ const mode = body.mode ?? "live";
1702
1710
  if (body.status === "created")
1703
- return { status: "created", id: body.id, requestCode: body.request_code };
1704
- if (body.status === "pending") return { status: "pending", id: body.id };
1705
- return { status: "failed", id: body.id, fallbackUrl: body.fallback_url };
1711
+ return { status: "created", id: body.id, requestCode: body.request_code, mode };
1712
+ if (body.status === "pending") return { status: "pending", id: body.id, mode };
1713
+ return { status: "failed", id: body.id, fallbackUrl: body.fallback_url, mode };
1706
1714
  }
1707
1715
  function formSubmitPayload(input, formId, labels) {
1708
1716
  const vehicle = input.vehicle ? `${input.vehicle.year} ${input.vehicle.make} ${input.vehicle.model}` : "";
@@ -1744,9 +1752,29 @@ function formSubmitPayload(input, formId, labels) {
1744
1752
  function runtimeOf(firing) {
1745
1753
  return firing && "integrations" in firing ? firing : null;
1746
1754
  }
1755
+ function isLocalUrl(url) {
1756
+ try {
1757
+ return ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname);
1758
+ } catch {
1759
+ return false;
1760
+ }
1761
+ }
1762
+ function bookingSetupMismatch(endpoint, runtime) {
1763
+ if (!runtime || !isLocalUrl(endpoint)) return null;
1764
+ const configUrl = resolveTrackingConfigUrl(runtime.ref);
1765
+ if (isLocalUrl(configUrl)) return null;
1766
+ return `[aranova] Bookings go to the local API (${endpoint}) but the runtime config comes from ${configUrl}. Set NEXT_PUBLIC_ARANOVA_CDN_BASE_URL to the local bucket (http://localhost:9100/aranova-demos) so both come from one backend.`;
1767
+ }
1747
1768
  function createSalesBookingsClient(config, firing) {
1748
1769
  const submittedForms = /* @__PURE__ */ new Set();
1749
1770
  const staleAttempts = /* @__PURE__ */ new Set();
1771
+ let isSetupChecked = false;
1772
+ function warnOnSetupMismatch(runtime) {
1773
+ if (isSetupChecked) return;
1774
+ isSetupChecked = true;
1775
+ const warning = bookingSetupMismatch(config.endpoint, runtime);
1776
+ if (warning) console.warn(warning);
1777
+ }
1750
1778
  function handoff(input) {
1751
1779
  const booking = getBookingIntegration(runtimeOf(firing)?.integrations());
1752
1780
  if (!booking) return null;
@@ -1757,14 +1785,18 @@ function createSalesBookingsClient(config, firing) {
1757
1785
  email: input.email
1758
1786
  });
1759
1787
  }
1788
+ function failedBeforeSend(input) {
1789
+ const mode = getBookingIntegration(runtimeOf(firing)?.integrations())?.mode ?? "live";
1790
+ return { status: "failed", id: null, fallbackUrl: handoff(input), mode };
1791
+ }
1760
1792
  function withHandoff(outcome, input) {
1761
1793
  if (outcome.status === "failed" && !outcome.fallbackUrl) {
1762
1794
  return { ...outcome, fallbackUrl: handoff(input) };
1763
1795
  }
1764
1796
  return outcome;
1765
1797
  }
1766
- async function poll(id) {
1767
- let outcome = { status: "pending", id };
1798
+ async function poll(id, mode) {
1799
+ let outcome = { status: "pending", id, mode };
1768
1800
  for (const delay of POLL_DELAYS_MS) {
1769
1801
  await sleep(delay);
1770
1802
  try {
@@ -1779,12 +1811,10 @@ function createSalesBookingsClient(config, firing) {
1779
1811
  }
1780
1812
  async function rejected(error, input, attemptKey, runtime) {
1781
1813
  if (!(error instanceof AranovaApiError)) {
1782
- return { status: "failed", id: null, fallbackUrl: handoff(input) };
1814
+ return failedBeforeSend(input);
1783
1815
  }
1784
1816
  if (error.code === BOOKING_ERROR_CODES.stale) {
1785
- if (staleAttempts.has(attemptKey)) {
1786
- return { status: "failed", id: null, fallbackUrl: handoff(input) };
1787
- }
1817
+ if (staleAttempts.has(attemptKey)) return failedBeforeSend(input);
1788
1818
  staleAttempts.add(attemptKey);
1789
1819
  await runtime?.revalidate();
1790
1820
  return { status: "stale", message: error.message };
@@ -1800,15 +1830,16 @@ function createSalesBookingsClient(config, firing) {
1800
1830
  if (error.code === "validation_error") {
1801
1831
  return { status: "invalid", message: INVALID_DETAILS, reasons: [] };
1802
1832
  }
1803
- return { status: "failed", id: null, fallbackUrl: handoff(input) };
1833
+ return failedBeforeSend(input);
1804
1834
  }
1805
1835
  return {
1806
- async submit(input, { attemptKey, formSubmit }) {
1836
+ async submit(input, { attemptKey, formSubmit, simulate }) {
1807
1837
  registerCapability("booking_integration");
1808
1838
  const runtime = runtimeOf(firing);
1839
+ warnOnSetupMismatch(runtime);
1809
1840
  const booking = getBookingIntegration(runtime?.integrations());
1810
1841
  if (!booking?.acceptsBookings || booking.configVersion === null) {
1811
- return { status: "failed", id: null, fallbackUrl: handoff(input) };
1842
+ return failedBeforeSend(input);
1812
1843
  }
1813
1844
  if (formSubmit && !submittedForms.has(attemptKey)) {
1814
1845
  submittedForms.add(attemptKey);
@@ -1838,12 +1869,22 @@ function createSalesBookingsClient(config, firing) {
1838
1869
  };
1839
1870
  let response;
1840
1871
  try {
1841
- response = await salesRequest(config, "POST", "/bookings", body);
1872
+ const headers = simulate ? { [BOOKING_SIMULATE_HEADER]: simulate } : void 0;
1873
+ response = await salesRequest(
1874
+ config,
1875
+ "POST",
1876
+ "/bookings",
1877
+ body,
1878
+ headers
1879
+ );
1842
1880
  } catch (error) {
1843
1881
  return rejected(error, input, attemptKey, runtime);
1844
1882
  }
1845
1883
  const outcome = outcomeOf(response);
1846
- return withHandoff(outcome.status === "pending" ? await poll(outcome.id) : outcome, input);
1884
+ return withHandoff(
1885
+ outcome.status === "pending" ? await poll(outcome.id, outcome.mode) : outcome,
1886
+ input
1887
+ );
1847
1888
  },
1848
1889
  async status(id) {
1849
1890
  return outcomeOf(