@aranova/tracking-react 1.2.5 → 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/README.md +77 -0
- package/dist/{tracking-config-runtime-C6H7ofmo.d.ts → bookings-CqCuuyP6.d.mts} +165 -1
- package/dist/{tracking-config-runtime-C6H7ofmo.d.mts → bookings-DIu1ho6v.d.ts} +165 -1
- package/dist/client.d.mts +58 -5
- package/dist/client.d.ts +58 -5
- package/dist/client.js +287 -11
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +282 -11
- package/dist/client.mjs.map +1 -1
- package/dist/form-submit-BLb28aCk.d.mts +75 -0
- package/dist/form-submit-BLb28aCk.d.ts +75 -0
- package/dist/index.d.mts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +470 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +465 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{ingest-typed-CUpdCtmM.d.ts → ingest-typed-D2S3Kb8m.d.ts} +83 -2
- package/dist/{ingest-typed-oAwnKqH2.d.mts → ingest-typed-D7UZr0EM.d.mts} +83 -2
- package/dist/{phone-utils-qLfVpQI7.d.mts → phone-utils-CM8eUPz8.d.mts} +2 -74
- package/dist/{phone-utils-qLfVpQI7.d.ts → phone-utils-l8ey3Zdm.d.ts} +2 -74
- package/dist/phone.d.mts +2 -1
- package/dist/phone.d.ts +2 -1
- package/dist/{sales-C3O_xxZ_.d.ts → sales-CxJVthx_.d.mts} +23 -2
- package/dist/{sales-BuKD_bdo.d.mts → sales-hlcVTJSS.d.ts} +23 -2
- package/dist/sales.d.mts +3 -2
- package/dist/sales.d.ts +3 -2
- package/dist/sales.js +386 -38
- package/dist/sales.js.map +1 -1
- package/dist/sales.mjs +384 -38
- package/dist/sales.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AdPlatformTracking: () => import_client.AdPlatformTracking,
|
|
34
34
|
AranovaApiError: () => AranovaApiError,
|
|
35
|
+
BookingHandoffLink: () => import_client3.BookingHandoffLink,
|
|
35
36
|
ConsentBanner: () => import_client.ConsentBanner,
|
|
36
37
|
DEFAULT_DECLINE_TTL_DAYS: () => DEFAULT_DECLINE_TTL_DAYS,
|
|
37
38
|
DEFAULT_PHONE_COUNTRY: () => DEFAULT_PHONE_COUNTRY,
|
|
@@ -39,9 +40,12 @@ __export(src_exports, {
|
|
|
39
40
|
GoogleAdsTracking: () => import_client.GoogleAdsTracking,
|
|
40
41
|
NAMED_RANGES: () => NAMED_RANGES,
|
|
41
42
|
PhoneField: () => import_client2.PhoneField,
|
|
43
|
+
SALE_SOURCES: () => SALE_SOURCES,
|
|
42
44
|
SUPPORTED_CURRENCIES: () => SUPPORTED_CURRENCIES,
|
|
43
45
|
TRACKING_PARAM_KEYS: () => TRACKING_PARAM_KEYS,
|
|
44
46
|
TRACKING_RANGES: () => TRACKING_RANGES,
|
|
47
|
+
bookableSlots: () => bookableSlots,
|
|
48
|
+
buildBookingHandoffUrl: () => buildBookingHandoffUrl,
|
|
45
49
|
captureTrackingParamsFromLocation: () => captureTrackingParamsFromLocation,
|
|
46
50
|
clearStashedUserData: () => clearStashedUserData,
|
|
47
51
|
createSalesClient: () => createSalesClient,
|
|
@@ -56,10 +60,12 @@ __export(src_exports, {
|
|
|
56
60
|
formatPhone: () => formatPhone,
|
|
57
61
|
formatPhoneAsTyped: () => formatPhoneAsTyped,
|
|
58
62
|
fromMinor: () => fromMinor,
|
|
63
|
+
getBookingIntegration: () => getBookingIntegration,
|
|
59
64
|
getConsentChoice: () => getConsentChoice,
|
|
60
65
|
getConsentState: () => getConsentState,
|
|
61
66
|
getEventSemantics: () => getEventSemantics,
|
|
62
67
|
getTrackingConfigRuntime: () => getTrackingConfigRuntime,
|
|
68
|
+
newBookingAttemptKey: () => newBookingAttemptKey,
|
|
63
69
|
onConsentChange: () => onConsentChange,
|
|
64
70
|
optIn: () => optIn,
|
|
65
71
|
optOut: () => optOut,
|
|
@@ -74,9 +80,14 @@ __export(src_exports, {
|
|
|
74
80
|
saleUpdateSchema: () => saleUpdateSchema,
|
|
75
81
|
salesRequest: () => salesRequest,
|
|
76
82
|
setConsentState: () => setConsentState,
|
|
83
|
+
shopToday: () => shopToday,
|
|
77
84
|
stashUserData: () => stashUserData,
|
|
78
85
|
toE164: () => toE164,
|
|
79
86
|
toMinor: () => toMinor,
|
|
87
|
+
useBookingHandoffUrl: () => import_client3.useBookingHandoffUrl,
|
|
88
|
+
useBookingIntegration: () => import_client3.useBookingIntegration,
|
|
89
|
+
useBookingSlots: () => import_client3.useBookingSlots,
|
|
90
|
+
useBookingSubmit: () => import_client3.useBookingSubmit,
|
|
80
91
|
useConsent: () => import_client.useConsent,
|
|
81
92
|
useConsentState: () => import_client.useConsentState,
|
|
82
93
|
useCookiePreferences: () => import_client.useCookiePreferences,
|
|
@@ -298,6 +309,8 @@ var TRACKING_CAPABILITIES = [
|
|
|
298
309
|
"ad_tags_meta",
|
|
299
310
|
"base_tracking",
|
|
300
311
|
"blog_rendering",
|
|
312
|
+
"booking_handoff",
|
|
313
|
+
"booking_integration",
|
|
301
314
|
"calendar_read",
|
|
302
315
|
"calendar_write",
|
|
303
316
|
"consent_controls",
|
|
@@ -577,6 +590,42 @@ function buildLandingPayloadFields(sessionId, override) {
|
|
|
577
590
|
};
|
|
578
591
|
}
|
|
579
592
|
|
|
593
|
+
// ../tracking-core/src/uuid.ts
|
|
594
|
+
function uuidV4() {
|
|
595
|
+
const cryptoApi = globalThis.crypto;
|
|
596
|
+
if (typeof cryptoApi?.randomUUID === "function") return cryptoApi.randomUUID();
|
|
597
|
+
const bytes = new Uint8Array(16);
|
|
598
|
+
if (typeof cryptoApi?.getRandomValues === "function") cryptoApi.getRandomValues(bytes);
|
|
599
|
+
else for (let i = 0; i < 16; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
600
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
601
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
602
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
603
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ../tracking-core/src/session.ts
|
|
607
|
+
var SESSION_STORAGE_KEY = "aranova_tracking_session";
|
|
608
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
609
|
+
function readLocalStorage(key) {
|
|
610
|
+
try {
|
|
611
|
+
return window.localStorage.getItem(key);
|
|
612
|
+
} catch {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function peekSessionId(now = Date.now()) {
|
|
617
|
+
if (typeof window === "undefined") return null;
|
|
618
|
+
const raw = readLocalStorage(SESSION_STORAGE_KEY);
|
|
619
|
+
if (!raw) return null;
|
|
620
|
+
try {
|
|
621
|
+
const parsed = JSON.parse(raw);
|
|
622
|
+
if (typeof parsed.id !== "string" || typeof parsed.last_event_at !== "number") return null;
|
|
623
|
+
return now - parsed.last_event_at <= SESSION_IDLE_MS ? parsed.id : null;
|
|
624
|
+
} catch {
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
580
629
|
// ../tracking-core/src/cross-site-link.ts
|
|
581
630
|
var LINK_PARAM = "_arv";
|
|
582
631
|
var LINK_TOKEN_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -705,6 +754,10 @@ function fireConversionWithConsent(input, options) {
|
|
|
705
754
|
}
|
|
706
755
|
|
|
707
756
|
// ../tracking-core/src/resources/conversion-config.ts
|
|
757
|
+
var BOOKING_MODES = ["live", "test", "simulated"];
|
|
758
|
+
function isBookingMode(value) {
|
|
759
|
+
return BOOKING_MODES.includes(value);
|
|
760
|
+
}
|
|
708
761
|
function isStringMap(value) {
|
|
709
762
|
return typeof value === "object" && value !== null && Object.values(value).every((v) => typeof v === "string");
|
|
710
763
|
}
|
|
@@ -718,6 +771,70 @@ function parseFiring(value) {
|
|
|
718
771
|
currency: typeof f.currency === "string" ? f.currency : null
|
|
719
772
|
};
|
|
720
773
|
}
|
|
774
|
+
function isNumberList(value) {
|
|
775
|
+
return Array.isArray(value) && value.every((v) => typeof v === "number");
|
|
776
|
+
}
|
|
777
|
+
function isStringList(value) {
|
|
778
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
779
|
+
}
|
|
780
|
+
function parseWindowsMap(value) {
|
|
781
|
+
const windowsByKey = {};
|
|
782
|
+
if (!value || typeof value !== "object") return windowsByKey;
|
|
783
|
+
for (const [key, windows] of Object.entries(value)) {
|
|
784
|
+
if (!windows || typeof windows !== "object") continue;
|
|
785
|
+
const w = windows;
|
|
786
|
+
windowsByKey[key] = {
|
|
787
|
+
morning: isNumberList(w.morning) ? w.morning : [],
|
|
788
|
+
afternoon: isNumberList(w.afternoon) ? w.afternoon : []
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
return windowsByKey;
|
|
792
|
+
}
|
|
793
|
+
function parseBookingRules(value) {
|
|
794
|
+
if (!value || typeof value !== "object") return null;
|
|
795
|
+
const r = value;
|
|
796
|
+
if (typeof r.min_days_ahead !== "number" || typeof r.max_days_ahead !== "number") return null;
|
|
797
|
+
if (!r.hours || typeof r.hours !== "object") return null;
|
|
798
|
+
const hours = parseWindowsMap(r.hours);
|
|
799
|
+
return {
|
|
800
|
+
min_days_ahead: r.min_days_ahead,
|
|
801
|
+
max_days_ahead: r.max_days_ahead,
|
|
802
|
+
hours,
|
|
803
|
+
closed_dates: isStringList(r.closed_dates) ? r.closed_dates : [],
|
|
804
|
+
special_hours: parseWindowsMap(r.special_hours),
|
|
805
|
+
vehicle_required: r.vehicle_required === true,
|
|
806
|
+
drop_off_options: isStringList(r.drop_off_options) ? r.drop_off_options : []
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function parseBookingOption(value) {
|
|
810
|
+
if (!value || typeof value !== "object") return [];
|
|
811
|
+
const o = value;
|
|
812
|
+
if (typeof o.key !== "string" || !o.key || typeof o.label !== "string") return [];
|
|
813
|
+
return [{ key: o.key, label: o.label }];
|
|
814
|
+
}
|
|
815
|
+
function parseIntegrations(value) {
|
|
816
|
+
if (!value || typeof value !== "object") return {};
|
|
817
|
+
const autoleap = value.autoleap;
|
|
818
|
+
if (!autoleap || typeof autoleap !== "object") return {};
|
|
819
|
+
const booking = autoleap.booking;
|
|
820
|
+
if (!booking || typeof booking !== "object") return {};
|
|
821
|
+
const b = booking;
|
|
822
|
+
if (typeof b.fallback_url !== "string" || !b.fallback_url) return {};
|
|
823
|
+
return {
|
|
824
|
+
autoleap: {
|
|
825
|
+
booking: {
|
|
826
|
+
enabled: b.enabled === true,
|
|
827
|
+
fallback_url: b.fallback_url,
|
|
828
|
+
timezone: typeof b.timezone === "string" ? b.timezone : "UTC",
|
|
829
|
+
config_version: typeof b.config_version === "number" ? b.config_version : null,
|
|
830
|
+
accepts_bookings: b.accepts_bookings === true,
|
|
831
|
+
rules: parseBookingRules(b.rules),
|
|
832
|
+
options: Array.isArray(b.options) ? b.options.flatMap(parseBookingOption) : [],
|
|
833
|
+
...isBookingMode(b.mode) ? { mode: b.mode } : {}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
721
838
|
function parseTrigger(value) {
|
|
722
839
|
if (!value || typeof value !== "object") return null;
|
|
723
840
|
const t = value;
|
|
@@ -777,7 +894,8 @@ function parseConversionConfig(raw) {
|
|
|
777
894
|
meta_pixel_ids: isStringMap(obj.meta_pixel_ids) ? obj.meta_pixel_ids : {},
|
|
778
895
|
services,
|
|
779
896
|
goals,
|
|
780
|
-
linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : []
|
|
897
|
+
linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : [],
|
|
898
|
+
integrations: parseIntegrations(obj.integrations)
|
|
781
899
|
};
|
|
782
900
|
}
|
|
783
901
|
var CACHE_PREFIX = "_aranova_cfg_";
|
|
@@ -1224,6 +1342,13 @@ var TrackingConfigRuntime = class {
|
|
|
1224
1342
|
linkedHostnames() {
|
|
1225
1343
|
return this.current?.linked_hostnames ?? [];
|
|
1226
1344
|
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Integration blocks, from the cached object even while unconfirmed: the booking fallback
|
|
1347
|
+
* link is safe to show stale. Same reference until the next adopt, for `useSyncExternalStore`.
|
|
1348
|
+
*/
|
|
1349
|
+
integrations() {
|
|
1350
|
+
return this.current?.integrations ?? null;
|
|
1351
|
+
}
|
|
1227
1352
|
async revalidateNow() {
|
|
1228
1353
|
try {
|
|
1229
1354
|
const headers = {};
|
|
@@ -1393,6 +1518,106 @@ function getTrackingConfigRuntime(ref, fetchImpl) {
|
|
|
1393
1518
|
return runtime;
|
|
1394
1519
|
}
|
|
1395
1520
|
|
|
1521
|
+
// ../../contracts/autoleap-booking-links.json
|
|
1522
|
+
var autoleap_booking_links_default = {
|
|
1523
|
+
hosts: ["myalp.io"]
|
|
1524
|
+
};
|
|
1525
|
+
|
|
1526
|
+
// ../tracking-core/src/integrations-public.ts
|
|
1527
|
+
var HANDOFF_HOSTS = new Set(autoleap_booking_links_default.hosts);
|
|
1528
|
+
function getBookingIntegration(integrations) {
|
|
1529
|
+
const booking = integrations?.autoleap?.booking;
|
|
1530
|
+
if (!booking) return null;
|
|
1531
|
+
const rules = booking.rules ? camelRules(booking.rules) : null;
|
|
1532
|
+
return {
|
|
1533
|
+
provider: "autoleap",
|
|
1534
|
+
enabled: booking.enabled,
|
|
1535
|
+
fallbackUrl: booking.fallback_url,
|
|
1536
|
+
acceptsBookings: booking.accepts_bookings && rules !== null && booking.config_version !== null,
|
|
1537
|
+
timezone: booking.timezone,
|
|
1538
|
+
configVersion: booking.config_version,
|
|
1539
|
+
rules,
|
|
1540
|
+
options: booking.options.map((option) => ({ key: option.key, label: option.label })),
|
|
1541
|
+
mode: booking.mode ?? "live"
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
function camelRules(rules) {
|
|
1545
|
+
return {
|
|
1546
|
+
minDaysAhead: rules.min_days_ahead,
|
|
1547
|
+
maxDaysAhead: rules.max_days_ahead,
|
|
1548
|
+
hours: rules.hours,
|
|
1549
|
+
closedDates: rules.closed_dates,
|
|
1550
|
+
specialHours: rules.special_hours,
|
|
1551
|
+
vehicleRequired: rules.vehicle_required,
|
|
1552
|
+
dropOffOptions: rules.drop_off_options
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
var WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
1556
|
+
function shopToday(timeZone, now = /* @__PURE__ */ new Date()) {
|
|
1557
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
1558
|
+
timeZone,
|
|
1559
|
+
year: "numeric",
|
|
1560
|
+
month: "2-digit",
|
|
1561
|
+
day: "2-digit"
|
|
1562
|
+
}).formatToParts(now);
|
|
1563
|
+
const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
1564
|
+
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
1565
|
+
}
|
|
1566
|
+
function addDays(isoDate, days) {
|
|
1567
|
+
const [year, month, day] = isoDate.split("-").map(Number);
|
|
1568
|
+
const date = new Date(Date.UTC(year, month - 1, day + days));
|
|
1569
|
+
return date.toISOString().slice(0, 10);
|
|
1570
|
+
}
|
|
1571
|
+
function bookableSlots(rules, { timeZone, now = /* @__PURE__ */ new Date() }) {
|
|
1572
|
+
const today = shopToday(timeZone, now);
|
|
1573
|
+
const days = [];
|
|
1574
|
+
const closed = new Set(rules.closedDates);
|
|
1575
|
+
for (let offset = rules.minDaysAhead; offset <= rules.maxDaysAhead; offset += 1) {
|
|
1576
|
+
const date = addDays(today, offset);
|
|
1577
|
+
if (closed.has(date)) continue;
|
|
1578
|
+
const weekday = WEEKDAYS[(/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay()];
|
|
1579
|
+
const windows = rules.specialHours[date] ?? rules.hours[weekday];
|
|
1580
|
+
if (!windows || windows.morning.length === 0 && windows.afternoon.length === 0) continue;
|
|
1581
|
+
days.push({
|
|
1582
|
+
date,
|
|
1583
|
+
weekday,
|
|
1584
|
+
morning: [...windows.morning],
|
|
1585
|
+
afternoon: [...windows.afternoon]
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
return days;
|
|
1589
|
+
}
|
|
1590
|
+
function buildBookingHandoffUrl(fallbackUrl, prefill = {}) {
|
|
1591
|
+
let url;
|
|
1592
|
+
try {
|
|
1593
|
+
url = new URL(fallbackUrl);
|
|
1594
|
+
} catch {
|
|
1595
|
+
return null;
|
|
1596
|
+
}
|
|
1597
|
+
if (url.protocol !== "https:" || !HANDOFF_HOSTS.has(url.hostname) || url.username || url.password || url.port) {
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1600
|
+
const [firstName, lastName] = prefill.firstName?.trim() ? [prefill.firstName, prefill.lastName] : splitFullName(prefill.name);
|
|
1601
|
+
const values = [
|
|
1602
|
+
["firstName", firstName?.trim()],
|
|
1603
|
+
["lastName", lastName?.trim()],
|
|
1604
|
+
["phone", prefill.phone ? nationalDigits(prefill.phone) : ""],
|
|
1605
|
+
["email", prefill.email?.trim()]
|
|
1606
|
+
];
|
|
1607
|
+
for (const [key, value] of values) {
|
|
1608
|
+
if (value) url.searchParams.set(key, value);
|
|
1609
|
+
}
|
|
1610
|
+
return url.toString();
|
|
1611
|
+
}
|
|
1612
|
+
function splitFullName(name) {
|
|
1613
|
+
const [first = "", ...rest] = (name ?? "").trim().split(/\s+/);
|
|
1614
|
+
return [first, rest.join(" ")];
|
|
1615
|
+
}
|
|
1616
|
+
function nationalDigits(phone) {
|
|
1617
|
+
const parsed = parsePhone(phone);
|
|
1618
|
+
return (parsed.isValid ? parsed.national : phone).replace(/\D/g, "");
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1396
1621
|
// ../tracking-core/src/events/semantics.ts
|
|
1397
1622
|
var EVENT_SEMANTICS = {
|
|
1398
1623
|
page_view: {
|
|
@@ -1499,6 +1724,7 @@ var AranovaApiError = class extends Error {
|
|
|
1499
1724
|
this.status = options.status;
|
|
1500
1725
|
this.code = options.code;
|
|
1501
1726
|
this.requestId = options.requestId;
|
|
1727
|
+
this.context = options.context;
|
|
1502
1728
|
}
|
|
1503
1729
|
};
|
|
1504
1730
|
|
|
@@ -1525,19 +1751,24 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
1525
1751
|
if (!response.ok) {
|
|
1526
1752
|
let detail;
|
|
1527
1753
|
let code;
|
|
1754
|
+
let context;
|
|
1528
1755
|
try {
|
|
1529
1756
|
const parsed = await response.json();
|
|
1530
1757
|
if (parsed && typeof parsed === "object") {
|
|
1531
1758
|
const record3 = parsed;
|
|
1532
1759
|
if (typeof record3.detail === "string") detail = record3.detail;
|
|
1533
1760
|
if (typeof record3.code === "string") code = record3.code;
|
|
1761
|
+
if (record3.context && typeof record3.context === "object") {
|
|
1762
|
+
context = record3.context;
|
|
1763
|
+
}
|
|
1534
1764
|
}
|
|
1535
1765
|
} catch {
|
|
1536
1766
|
}
|
|
1537
1767
|
throw new AranovaApiError(detail ?? response.statusText ?? "Request failed", {
|
|
1538
1768
|
status: response.status,
|
|
1539
1769
|
code,
|
|
1540
|
-
requestId: response.headers.get("x-request-id") ?? void 0
|
|
1770
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
1771
|
+
context
|
|
1541
1772
|
});
|
|
1542
1773
|
}
|
|
1543
1774
|
if (response.status === 204) return void 0;
|
|
@@ -1547,6 +1778,214 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
1547
1778
|
// ../tracking-core/src/resources/sales/transport.ts
|
|
1548
1779
|
var salesRequest = apiRequest;
|
|
1549
1780
|
|
|
1781
|
+
// ../tracking-core/src/resources/sales/bookings.ts
|
|
1782
|
+
var BOOKING_SIMULATE_HEADER = "X-Aranova-Simulate";
|
|
1783
|
+
var BOOKING_ERROR_CODES = {
|
|
1784
|
+
unavailable: "booking_unavailable",
|
|
1785
|
+
stale: "booking_config_stale",
|
|
1786
|
+
rejected: "booking_rejected"
|
|
1787
|
+
};
|
|
1788
|
+
var POLL_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
1789
|
+
var INVALID_DETAILS = "Please check your details and try again.";
|
|
1790
|
+
var newBookingAttemptKey = uuidV4;
|
|
1791
|
+
function sleep(ms) {
|
|
1792
|
+
return new Promise((resolve) => {
|
|
1793
|
+
setTimeout(resolve, ms);
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
function outcomeOf(body) {
|
|
1797
|
+
const mode = body.mode ?? "live";
|
|
1798
|
+
if (body.status === "created")
|
|
1799
|
+
return { status: "created", id: body.id, requestCode: body.request_code, mode };
|
|
1800
|
+
if (body.status === "pending") return { status: "pending", id: body.id, mode };
|
|
1801
|
+
return { status: "failed", id: body.id, fallbackUrl: body.fallback_url, mode };
|
|
1802
|
+
}
|
|
1803
|
+
function formSubmitPayload(input, formId, labels) {
|
|
1804
|
+
const vehicle = input.vehicle ? `${input.vehicle.year} ${input.vehicle.make} ${input.vehicle.model}` : "";
|
|
1805
|
+
const field = (name, type, label, value) => ({
|
|
1806
|
+
name,
|
|
1807
|
+
type,
|
|
1808
|
+
label,
|
|
1809
|
+
value
|
|
1810
|
+
});
|
|
1811
|
+
return {
|
|
1812
|
+
form: {
|
|
1813
|
+
id: formId,
|
|
1814
|
+
action: "/tracking/bookings",
|
|
1815
|
+
fields: [
|
|
1816
|
+
field("first_name", "text", "First name", input.firstName),
|
|
1817
|
+
field("last_name", "text", "Last name", input.lastName),
|
|
1818
|
+
// E.164 on the wire: it keys the customer and enhanced-conversion matching.
|
|
1819
|
+
field("phone", "tel", "Phone", toE164(input.phone) ?? input.phone),
|
|
1820
|
+
field("email", "email", "Email", input.email ?? ""),
|
|
1821
|
+
field(
|
|
1822
|
+
"service",
|
|
1823
|
+
"select",
|
|
1824
|
+
"Service",
|
|
1825
|
+
input.services.map((key) => labels.get(key) ?? key).join(", ")
|
|
1826
|
+
),
|
|
1827
|
+
field("vehicle", "text", "Vehicle", vehicle),
|
|
1828
|
+
field(
|
|
1829
|
+
"start",
|
|
1830
|
+
"text",
|
|
1831
|
+
"Requested time",
|
|
1832
|
+
`${input.startDate} ${String(input.startHour).padStart(2, "0")}:00`
|
|
1833
|
+
),
|
|
1834
|
+
field("note", "textarea", "Notes", input.notes ?? "")
|
|
1835
|
+
]
|
|
1836
|
+
},
|
|
1837
|
+
page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
function runtimeOf(firing) {
|
|
1841
|
+
return firing && "integrations" in firing ? firing : null;
|
|
1842
|
+
}
|
|
1843
|
+
function isLocalUrl(url) {
|
|
1844
|
+
try {
|
|
1845
|
+
return ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname);
|
|
1846
|
+
} catch {
|
|
1847
|
+
return false;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
function bookingSetupMismatch(endpoint, runtime) {
|
|
1851
|
+
if (!runtime || !isLocalUrl(endpoint)) return null;
|
|
1852
|
+
const configUrl = resolveTrackingConfigUrl(runtime.ref);
|
|
1853
|
+
if (isLocalUrl(configUrl)) return null;
|
|
1854
|
+
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.`;
|
|
1855
|
+
}
|
|
1856
|
+
function createSalesBookingsClient(config, firing) {
|
|
1857
|
+
const submittedForms = /* @__PURE__ */ new Set();
|
|
1858
|
+
const staleAttempts = /* @__PURE__ */ new Set();
|
|
1859
|
+
let isSetupChecked = false;
|
|
1860
|
+
function warnOnSetupMismatch(runtime) {
|
|
1861
|
+
if (isSetupChecked) return;
|
|
1862
|
+
isSetupChecked = true;
|
|
1863
|
+
const warning = bookingSetupMismatch(config.endpoint, runtime);
|
|
1864
|
+
if (warning) console.warn(warning);
|
|
1865
|
+
}
|
|
1866
|
+
function handoff(input) {
|
|
1867
|
+
const booking = getBookingIntegration(runtimeOf(firing)?.integrations());
|
|
1868
|
+
if (!booking) return null;
|
|
1869
|
+
return buildBookingHandoffUrl(booking.fallbackUrl, {
|
|
1870
|
+
firstName: input.firstName,
|
|
1871
|
+
lastName: input.lastName,
|
|
1872
|
+
phone: input.phone,
|
|
1873
|
+
email: input.email
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
function failedBeforeSend(input) {
|
|
1877
|
+
const mode = getBookingIntegration(runtimeOf(firing)?.integrations())?.mode ?? "live";
|
|
1878
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input), mode };
|
|
1879
|
+
}
|
|
1880
|
+
function withHandoff(outcome, input) {
|
|
1881
|
+
if (outcome.status === "failed" && !outcome.fallbackUrl) {
|
|
1882
|
+
return { ...outcome, fallbackUrl: handoff(input) };
|
|
1883
|
+
}
|
|
1884
|
+
return outcome;
|
|
1885
|
+
}
|
|
1886
|
+
async function poll(id, mode) {
|
|
1887
|
+
let outcome = { status: "pending", id, mode };
|
|
1888
|
+
for (const delay of POLL_DELAYS_MS) {
|
|
1889
|
+
await sleep(delay);
|
|
1890
|
+
try {
|
|
1891
|
+
const path = `/bookings/${encodeURIComponent(id)}`;
|
|
1892
|
+
outcome = outcomeOf(await salesRequest(config, "GET", path));
|
|
1893
|
+
} catch {
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1896
|
+
if (outcome.status !== "pending") return outcome;
|
|
1897
|
+
}
|
|
1898
|
+
return outcome;
|
|
1899
|
+
}
|
|
1900
|
+
async function rejected(error, input, attemptKey, runtime) {
|
|
1901
|
+
if (!(error instanceof AranovaApiError)) {
|
|
1902
|
+
return failedBeforeSend(input);
|
|
1903
|
+
}
|
|
1904
|
+
if (error.code === BOOKING_ERROR_CODES.stale) {
|
|
1905
|
+
if (staleAttempts.has(attemptKey)) return failedBeforeSend(input);
|
|
1906
|
+
staleAttempts.add(attemptKey);
|
|
1907
|
+
await runtime?.revalidate();
|
|
1908
|
+
return { status: "stale", message: error.message };
|
|
1909
|
+
}
|
|
1910
|
+
if (error.code === BOOKING_ERROR_CODES.rejected) {
|
|
1911
|
+
const reasons = error.context?.reasons;
|
|
1912
|
+
return {
|
|
1913
|
+
status: "invalid",
|
|
1914
|
+
message: error.message,
|
|
1915
|
+
reasons: Array.isArray(reasons) ? reasons.map(String) : []
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
if (error.code === "validation_error") {
|
|
1919
|
+
return { status: "invalid", message: INVALID_DETAILS, reasons: [] };
|
|
1920
|
+
}
|
|
1921
|
+
return failedBeforeSend(input);
|
|
1922
|
+
}
|
|
1923
|
+
return {
|
|
1924
|
+
async submit(input, { attemptKey, formSubmit, simulate }) {
|
|
1925
|
+
registerCapability("booking_integration");
|
|
1926
|
+
const runtime = runtimeOf(firing);
|
|
1927
|
+
warnOnSetupMismatch(runtime);
|
|
1928
|
+
const booking = getBookingIntegration(runtime?.integrations());
|
|
1929
|
+
if (!booking?.acceptsBookings || booking.configVersion === null) {
|
|
1930
|
+
return failedBeforeSend(input);
|
|
1931
|
+
}
|
|
1932
|
+
if (formSubmit && !submittedForms.has(attemptKey)) {
|
|
1933
|
+
submittedForms.add(attemptKey);
|
|
1934
|
+
const labels = new Map(booking.options.map((option) => [option.key, option.label]));
|
|
1935
|
+
formSubmit.tracking.trackEvent(
|
|
1936
|
+
"form_submit",
|
|
1937
|
+
formSubmitPayload(input, formSubmit.formId, labels)
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
const body = {
|
|
1941
|
+
idempotency_key: attemptKey,
|
|
1942
|
+
// The tracking client's own visit, so the booking and its form_submit share one session.
|
|
1943
|
+
session_id: formSubmit?.tracking.getSessionId() ?? peekSessionId() ?? `no-session-${attemptKey}`,
|
|
1944
|
+
config_version: booking.configVersion,
|
|
1945
|
+
start_date: input.startDate,
|
|
1946
|
+
start_hour: input.startHour,
|
|
1947
|
+
first_name: input.firstName.trim(),
|
|
1948
|
+
last_name: input.lastName.trim(),
|
|
1949
|
+
phone: input.phone,
|
|
1950
|
+
email: input.email?.trim() || null,
|
|
1951
|
+
vehicle: input.vehicle ?? null,
|
|
1952
|
+
drop_off: input.dropOff ?? null,
|
|
1953
|
+
services: input.services,
|
|
1954
|
+
notes: input.notes?.trim() || null,
|
|
1955
|
+
marketing_sms_consent: input.marketingSmsConsent === true,
|
|
1956
|
+
page_path: typeof window === "undefined" ? null : window.location.pathname
|
|
1957
|
+
};
|
|
1958
|
+
let response;
|
|
1959
|
+
try {
|
|
1960
|
+
const headers = simulate ? { [BOOKING_SIMULATE_HEADER]: simulate } : void 0;
|
|
1961
|
+
response = await salesRequest(
|
|
1962
|
+
config,
|
|
1963
|
+
"POST",
|
|
1964
|
+
"/bookings",
|
|
1965
|
+
body,
|
|
1966
|
+
headers
|
|
1967
|
+
);
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
return rejected(error, input, attemptKey, runtime);
|
|
1970
|
+
}
|
|
1971
|
+
const outcome = outcomeOf(response);
|
|
1972
|
+
return withHandoff(
|
|
1973
|
+
outcome.status === "pending" ? await poll(outcome.id, outcome.mode) : outcome,
|
|
1974
|
+
input
|
|
1975
|
+
);
|
|
1976
|
+
},
|
|
1977
|
+
async status(id) {
|
|
1978
|
+
return outcomeOf(
|
|
1979
|
+
await salesRequest(
|
|
1980
|
+
config,
|
|
1981
|
+
"GET",
|
|
1982
|
+
`/bookings/${encodeURIComponent(id)}`
|
|
1983
|
+
)
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1550
1989
|
// ../tracking-core/src/resources/sales/client.ts
|
|
1551
1990
|
function fireRecordedConversions(firing, input, recorded, sale, currency) {
|
|
1552
1991
|
if (!firing) return;
|
|
@@ -1721,7 +2160,8 @@ function createSalesClient(config) {
|
|
|
1721
2160
|
async config() {
|
|
1722
2161
|
return salesRequest(config, "GET", "/business/config");
|
|
1723
2162
|
}
|
|
1724
|
-
}
|
|
2163
|
+
},
|
|
2164
|
+
bookings: createSalesBookingsClient(config, config.firing)
|
|
1725
2165
|
};
|
|
1726
2166
|
}
|
|
1727
2167
|
|
|
@@ -1730,6 +2170,12 @@ var z3 = __toESM(require("zod/mini"));
|
|
|
1730
2170
|
|
|
1731
2171
|
// ../tracking-core/src/resources/sales/generated/zod.gen.ts
|
|
1732
2172
|
var z2 = __toESM(require("zod/mini"));
|
|
2173
|
+
var zAutoLeapSaleFacts = z2.object({
|
|
2174
|
+
first_order: z2.boolean(),
|
|
2175
|
+
fleet_customer: z2.boolean(),
|
|
2176
|
+
invoice_code: z2.string(),
|
|
2177
|
+
invoice_id: z2.string()
|
|
2178
|
+
});
|
|
1733
2179
|
var zErrorCode = z2.enum([
|
|
1734
2180
|
"missing_credentials",
|
|
1735
2181
|
"invalid_api_key",
|
|
@@ -1757,6 +2203,9 @@ var zErrorCode = z2.enum([
|
|
|
1757
2203
|
"booking_not_reschedulable",
|
|
1758
2204
|
"manage_token_invalid",
|
|
1759
2205
|
"google_error",
|
|
2206
|
+
"booking_unavailable",
|
|
2207
|
+
"booking_config_stale",
|
|
2208
|
+
"booking_rejected",
|
|
1760
2209
|
"calendar_not_owned",
|
|
1761
2210
|
"calendar_is_sandbox",
|
|
1762
2211
|
"sandbox_exists",
|
|
@@ -1798,6 +2247,7 @@ var zSaleServiceInputSchema = z2.strictObject({
|
|
|
1798
2247
|
amount_cents: z2.int().check(z2.gte(0)),
|
|
1799
2248
|
service: z2.string()
|
|
1800
2249
|
});
|
|
2250
|
+
var zSaleSource = z2.enum(["api", "admin", "autoleap"]);
|
|
1801
2251
|
var zSmsAutomationState = z2.enum([
|
|
1802
2252
|
"needs_check",
|
|
1803
2253
|
"failed",
|
|
@@ -1829,7 +2279,8 @@ var zSmsWithheldReason = z2.enum([
|
|
|
1829
2279
|
"number_not_active",
|
|
1830
2280
|
"a2p_registration_required",
|
|
1831
2281
|
"alert_recipient_cannot_receive_sms",
|
|
1832
|
-
"line_type_unresolved"
|
|
2282
|
+
"line_type_unresolved",
|
|
2283
|
+
"sale_source_sends_own_messages"
|
|
1833
2284
|
]);
|
|
1834
2285
|
var zSaleSmsStatusSchema = z2.object({
|
|
1835
2286
|
can_send_now: z2._default(z2.boolean(), false),
|
|
@@ -1938,6 +2389,8 @@ var zSaleEntitySchema = z2.object({
|
|
|
1938
2389
|
sms_automation: z2.nullable(zSaleSmsStatusSchema),
|
|
1939
2390
|
sms_consent: z2._default(z2.boolean(), false),
|
|
1940
2391
|
sms_consent_granted_at: z2.nullable(z2.iso.datetime({ offset: true })),
|
|
2392
|
+
source: zSaleSource,
|
|
2393
|
+
source_facts: z2.nullable(zAutoLeapSaleFacts),
|
|
1941
2394
|
updated_at: z2.iso.datetime({ offset: true }),
|
|
1942
2395
|
writeback: z2.nullable(zSaleWritebackStatusSchema)
|
|
1943
2396
|
});
|
|
@@ -1947,6 +2400,7 @@ var saleItemSchema = zSaleItemInputSchema;
|
|
|
1947
2400
|
var saleServiceSchema = zSaleServiceInputSchema;
|
|
1948
2401
|
var currencySchema = zSupportedCurrency;
|
|
1949
2402
|
var SUPPORTED_CURRENCIES = currencySchema.options;
|
|
2403
|
+
var SALE_SOURCES = zSaleSource.options;
|
|
1950
2404
|
var centsSchema = z3.int().check(z3.gte(0));
|
|
1951
2405
|
var metadataSchema = z3.record(z3.string(), z3.unknown());
|
|
1952
2406
|
var customerNameSchema = z3.string().check(z3.maxLength(200));
|
|
@@ -2031,10 +2485,12 @@ function phoneField(name, raw, country) {
|
|
|
2031
2485
|
|
|
2032
2486
|
// src/index.ts
|
|
2033
2487
|
var import_client2 = require("@aranova/tracking-react/client");
|
|
2488
|
+
var import_client3 = require("@aranova/tracking-react/client");
|
|
2034
2489
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2035
2490
|
0 && (module.exports = {
|
|
2036
2491
|
AdPlatformTracking,
|
|
2037
2492
|
AranovaApiError,
|
|
2493
|
+
BookingHandoffLink,
|
|
2038
2494
|
ConsentBanner,
|
|
2039
2495
|
DEFAULT_DECLINE_TTL_DAYS,
|
|
2040
2496
|
DEFAULT_PHONE_COUNTRY,
|
|
@@ -2042,9 +2498,12 @@ var import_client2 = require("@aranova/tracking-react/client");
|
|
|
2042
2498
|
GoogleAdsTracking,
|
|
2043
2499
|
NAMED_RANGES,
|
|
2044
2500
|
PhoneField,
|
|
2501
|
+
SALE_SOURCES,
|
|
2045
2502
|
SUPPORTED_CURRENCIES,
|
|
2046
2503
|
TRACKING_PARAM_KEYS,
|
|
2047
2504
|
TRACKING_RANGES,
|
|
2505
|
+
bookableSlots,
|
|
2506
|
+
buildBookingHandoffUrl,
|
|
2048
2507
|
captureTrackingParamsFromLocation,
|
|
2049
2508
|
clearStashedUserData,
|
|
2050
2509
|
createSalesClient,
|
|
@@ -2059,10 +2518,12 @@ var import_client2 = require("@aranova/tracking-react/client");
|
|
|
2059
2518
|
formatPhone,
|
|
2060
2519
|
formatPhoneAsTyped,
|
|
2061
2520
|
fromMinor,
|
|
2521
|
+
getBookingIntegration,
|
|
2062
2522
|
getConsentChoice,
|
|
2063
2523
|
getConsentState,
|
|
2064
2524
|
getEventSemantics,
|
|
2065
2525
|
getTrackingConfigRuntime,
|
|
2526
|
+
newBookingAttemptKey,
|
|
2066
2527
|
onConsentChange,
|
|
2067
2528
|
optIn,
|
|
2068
2529
|
optOut,
|
|
@@ -2077,9 +2538,14 @@ var import_client2 = require("@aranova/tracking-react/client");
|
|
|
2077
2538
|
saleUpdateSchema,
|
|
2078
2539
|
salesRequest,
|
|
2079
2540
|
setConsentState,
|
|
2541
|
+
shopToday,
|
|
2080
2542
|
stashUserData,
|
|
2081
2543
|
toE164,
|
|
2082
2544
|
toMinor,
|
|
2545
|
+
useBookingHandoffUrl,
|
|
2546
|
+
useBookingIntegration,
|
|
2547
|
+
useBookingSlots,
|
|
2548
|
+
useBookingSubmit,
|
|
2083
2549
|
useConsent,
|
|
2084
2550
|
useConsentState,
|
|
2085
2551
|
useCookiePreferences,
|