@aranova/tracking-react 1.2.5 → 1.3.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 +70 -0
- package/dist/{tracking-config-runtime-C6H7ofmo.d.mts → bookings-CqdjLMkv.d.ts} +144 -1
- package/dist/{tracking-config-runtime-C6H7ofmo.d.ts → bookings-VsQdopTt.d.mts} +144 -1
- package/dist/client.d.mts +56 -5
- package/dist/client.d.ts +56 -5
- package/dist/client.js +281 -11
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +276 -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 +429 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +424 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{ingest-typed-oAwnKqH2.d.mts → ingest-typed-BCUv93ax.d.mts} +78 -2
- package/dist/{ingest-typed-CUpdCtmM.d.ts → ingest-typed-DGqzbteM.d.ts} +78 -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-DSL6vMuY.d.mts} +23 -2
- package/dist/{sales-BuKD_bdo.d.mts → sales-_F3nnI2r.d.ts} +23 -2
- package/dist/sales.d.mts +3 -2
- package/dist/sales.d.ts +3 -2
- package/dist/sales.js +307 -9
- package/dist/sales.js.map +1 -1
- package/dist/sales.mjs +305 -9
- 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;
|
|
@@ -718,6 +767,69 @@ function parseFiring(value) {
|
|
|
718
767
|
currency: typeof f.currency === "string" ? f.currency : null
|
|
719
768
|
};
|
|
720
769
|
}
|
|
770
|
+
function isNumberList(value) {
|
|
771
|
+
return Array.isArray(value) && value.every((v) => typeof v === "number");
|
|
772
|
+
}
|
|
773
|
+
function isStringList(value) {
|
|
774
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
775
|
+
}
|
|
776
|
+
function parseWindowsMap(value) {
|
|
777
|
+
const windowsByKey = {};
|
|
778
|
+
if (!value || typeof value !== "object") return windowsByKey;
|
|
779
|
+
for (const [key, windows] of Object.entries(value)) {
|
|
780
|
+
if (!windows || typeof windows !== "object") continue;
|
|
781
|
+
const w = windows;
|
|
782
|
+
windowsByKey[key] = {
|
|
783
|
+
morning: isNumberList(w.morning) ? w.morning : [],
|
|
784
|
+
afternoon: isNumberList(w.afternoon) ? w.afternoon : []
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
return windowsByKey;
|
|
788
|
+
}
|
|
789
|
+
function parseBookingRules(value) {
|
|
790
|
+
if (!value || typeof value !== "object") return null;
|
|
791
|
+
const r = value;
|
|
792
|
+
if (typeof r.min_days_ahead !== "number" || typeof r.max_days_ahead !== "number") return null;
|
|
793
|
+
if (!r.hours || typeof r.hours !== "object") return null;
|
|
794
|
+
const hours = parseWindowsMap(r.hours);
|
|
795
|
+
return {
|
|
796
|
+
min_days_ahead: r.min_days_ahead,
|
|
797
|
+
max_days_ahead: r.max_days_ahead,
|
|
798
|
+
hours,
|
|
799
|
+
closed_dates: isStringList(r.closed_dates) ? r.closed_dates : [],
|
|
800
|
+
special_hours: parseWindowsMap(r.special_hours),
|
|
801
|
+
vehicle_required: r.vehicle_required === true,
|
|
802
|
+
drop_off_options: isStringList(r.drop_off_options) ? r.drop_off_options : []
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
function parseBookingOption(value) {
|
|
806
|
+
if (!value || typeof value !== "object") return [];
|
|
807
|
+
const o = value;
|
|
808
|
+
if (typeof o.key !== "string" || !o.key || typeof o.label !== "string") return [];
|
|
809
|
+
return [{ key: o.key, label: o.label }];
|
|
810
|
+
}
|
|
811
|
+
function parseIntegrations(value) {
|
|
812
|
+
if (!value || typeof value !== "object") return {};
|
|
813
|
+
const autoleap = value.autoleap;
|
|
814
|
+
if (!autoleap || typeof autoleap !== "object") return {};
|
|
815
|
+
const booking = autoleap.booking;
|
|
816
|
+
if (!booking || typeof booking !== "object") return {};
|
|
817
|
+
const b = booking;
|
|
818
|
+
if (typeof b.fallback_url !== "string" || !b.fallback_url) return {};
|
|
819
|
+
return {
|
|
820
|
+
autoleap: {
|
|
821
|
+
booking: {
|
|
822
|
+
enabled: b.enabled === true,
|
|
823
|
+
fallback_url: b.fallback_url,
|
|
824
|
+
timezone: typeof b.timezone === "string" ? b.timezone : "UTC",
|
|
825
|
+
config_version: typeof b.config_version === "number" ? b.config_version : null,
|
|
826
|
+
accepts_bookings: b.accepts_bookings === true,
|
|
827
|
+
rules: parseBookingRules(b.rules),
|
|
828
|
+
options: Array.isArray(b.options) ? b.options.flatMap(parseBookingOption) : []
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
}
|
|
721
833
|
function parseTrigger(value) {
|
|
722
834
|
if (!value || typeof value !== "object") return null;
|
|
723
835
|
const t = value;
|
|
@@ -777,7 +889,8 @@ function parseConversionConfig(raw) {
|
|
|
777
889
|
meta_pixel_ids: isStringMap(obj.meta_pixel_ids) ? obj.meta_pixel_ids : {},
|
|
778
890
|
services,
|
|
779
891
|
goals,
|
|
780
|
-
linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : []
|
|
892
|
+
linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : [],
|
|
893
|
+
integrations: parseIntegrations(obj.integrations)
|
|
781
894
|
};
|
|
782
895
|
}
|
|
783
896
|
var CACHE_PREFIX = "_aranova_cfg_";
|
|
@@ -1224,6 +1337,13 @@ var TrackingConfigRuntime = class {
|
|
|
1224
1337
|
linkedHostnames() {
|
|
1225
1338
|
return this.current?.linked_hostnames ?? [];
|
|
1226
1339
|
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Integration blocks, from the cached object even while unconfirmed: the booking fallback
|
|
1342
|
+
* link is safe to show stale. Same reference until the next adopt, for `useSyncExternalStore`.
|
|
1343
|
+
*/
|
|
1344
|
+
integrations() {
|
|
1345
|
+
return this.current?.integrations ?? null;
|
|
1346
|
+
}
|
|
1227
1347
|
async revalidateNow() {
|
|
1228
1348
|
try {
|
|
1229
1349
|
const headers = {};
|
|
@@ -1393,6 +1513,105 @@ function getTrackingConfigRuntime(ref, fetchImpl) {
|
|
|
1393
1513
|
return runtime;
|
|
1394
1514
|
}
|
|
1395
1515
|
|
|
1516
|
+
// ../../contracts/autoleap-booking-links.json
|
|
1517
|
+
var autoleap_booking_links_default = {
|
|
1518
|
+
hosts: ["myalp.io"]
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
// ../tracking-core/src/integrations-public.ts
|
|
1522
|
+
var HANDOFF_HOSTS = new Set(autoleap_booking_links_default.hosts);
|
|
1523
|
+
function getBookingIntegration(integrations) {
|
|
1524
|
+
const booking = integrations?.autoleap?.booking;
|
|
1525
|
+
if (!booking) return null;
|
|
1526
|
+
const rules = booking.rules ? camelRules(booking.rules) : null;
|
|
1527
|
+
return {
|
|
1528
|
+
provider: "autoleap",
|
|
1529
|
+
enabled: booking.enabled,
|
|
1530
|
+
fallbackUrl: booking.fallback_url,
|
|
1531
|
+
acceptsBookings: booking.accepts_bookings && rules !== null && booking.config_version !== null,
|
|
1532
|
+
timezone: booking.timezone,
|
|
1533
|
+
configVersion: booking.config_version,
|
|
1534
|
+
rules,
|
|
1535
|
+
options: booking.options.map((option) => ({ key: option.key, label: option.label }))
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
function camelRules(rules) {
|
|
1539
|
+
return {
|
|
1540
|
+
minDaysAhead: rules.min_days_ahead,
|
|
1541
|
+
maxDaysAhead: rules.max_days_ahead,
|
|
1542
|
+
hours: rules.hours,
|
|
1543
|
+
closedDates: rules.closed_dates,
|
|
1544
|
+
specialHours: rules.special_hours,
|
|
1545
|
+
vehicleRequired: rules.vehicle_required,
|
|
1546
|
+
dropOffOptions: rules.drop_off_options
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
var WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
1550
|
+
function shopToday(timeZone, now = /* @__PURE__ */ new Date()) {
|
|
1551
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
1552
|
+
timeZone,
|
|
1553
|
+
year: "numeric",
|
|
1554
|
+
month: "2-digit",
|
|
1555
|
+
day: "2-digit"
|
|
1556
|
+
}).formatToParts(now);
|
|
1557
|
+
const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
1558
|
+
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
1559
|
+
}
|
|
1560
|
+
function addDays(isoDate, days) {
|
|
1561
|
+
const [year, month, day] = isoDate.split("-").map(Number);
|
|
1562
|
+
const date = new Date(Date.UTC(year, month - 1, day + days));
|
|
1563
|
+
return date.toISOString().slice(0, 10);
|
|
1564
|
+
}
|
|
1565
|
+
function bookableSlots(rules, { timeZone, now = /* @__PURE__ */ new Date() }) {
|
|
1566
|
+
const today = shopToday(timeZone, now);
|
|
1567
|
+
const days = [];
|
|
1568
|
+
const closed = new Set(rules.closedDates);
|
|
1569
|
+
for (let offset = rules.minDaysAhead; offset <= rules.maxDaysAhead; offset += 1) {
|
|
1570
|
+
const date = addDays(today, offset);
|
|
1571
|
+
if (closed.has(date)) continue;
|
|
1572
|
+
const weekday = WEEKDAYS[(/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay()];
|
|
1573
|
+
const windows = rules.specialHours[date] ?? rules.hours[weekday];
|
|
1574
|
+
if (!windows || windows.morning.length === 0 && windows.afternoon.length === 0) continue;
|
|
1575
|
+
days.push({
|
|
1576
|
+
date,
|
|
1577
|
+
weekday,
|
|
1578
|
+
morning: [...windows.morning],
|
|
1579
|
+
afternoon: [...windows.afternoon]
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
return days;
|
|
1583
|
+
}
|
|
1584
|
+
function buildBookingHandoffUrl(fallbackUrl, prefill = {}) {
|
|
1585
|
+
let url;
|
|
1586
|
+
try {
|
|
1587
|
+
url = new URL(fallbackUrl);
|
|
1588
|
+
} catch {
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
if (url.protocol !== "https:" || !HANDOFF_HOSTS.has(url.hostname) || url.username || url.password || url.port) {
|
|
1592
|
+
return null;
|
|
1593
|
+
}
|
|
1594
|
+
const [firstName, lastName] = prefill.firstName?.trim() ? [prefill.firstName, prefill.lastName] : splitFullName(prefill.name);
|
|
1595
|
+
const values = [
|
|
1596
|
+
["firstName", firstName?.trim()],
|
|
1597
|
+
["lastName", lastName?.trim()],
|
|
1598
|
+
["phone", prefill.phone ? nationalDigits(prefill.phone) : ""],
|
|
1599
|
+
["email", prefill.email?.trim()]
|
|
1600
|
+
];
|
|
1601
|
+
for (const [key, value] of values) {
|
|
1602
|
+
if (value) url.searchParams.set(key, value);
|
|
1603
|
+
}
|
|
1604
|
+
return url.toString();
|
|
1605
|
+
}
|
|
1606
|
+
function splitFullName(name) {
|
|
1607
|
+
const [first = "", ...rest] = (name ?? "").trim().split(/\s+/);
|
|
1608
|
+
return [first, rest.join(" ")];
|
|
1609
|
+
}
|
|
1610
|
+
function nationalDigits(phone) {
|
|
1611
|
+
const parsed = parsePhone(phone);
|
|
1612
|
+
return (parsed.isValid ? parsed.national : phone).replace(/\D/g, "");
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1396
1615
|
// ../tracking-core/src/events/semantics.ts
|
|
1397
1616
|
var EVENT_SEMANTICS = {
|
|
1398
1617
|
page_view: {
|
|
@@ -1499,6 +1718,7 @@ var AranovaApiError = class extends Error {
|
|
|
1499
1718
|
this.status = options.status;
|
|
1500
1719
|
this.code = options.code;
|
|
1501
1720
|
this.requestId = options.requestId;
|
|
1721
|
+
this.context = options.context;
|
|
1502
1722
|
}
|
|
1503
1723
|
};
|
|
1504
1724
|
|
|
@@ -1525,19 +1745,24 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
1525
1745
|
if (!response.ok) {
|
|
1526
1746
|
let detail;
|
|
1527
1747
|
let code;
|
|
1748
|
+
let context;
|
|
1528
1749
|
try {
|
|
1529
1750
|
const parsed = await response.json();
|
|
1530
1751
|
if (parsed && typeof parsed === "object") {
|
|
1531
1752
|
const record3 = parsed;
|
|
1532
1753
|
if (typeof record3.detail === "string") detail = record3.detail;
|
|
1533
1754
|
if (typeof record3.code === "string") code = record3.code;
|
|
1755
|
+
if (record3.context && typeof record3.context === "object") {
|
|
1756
|
+
context = record3.context;
|
|
1757
|
+
}
|
|
1534
1758
|
}
|
|
1535
1759
|
} catch {
|
|
1536
1760
|
}
|
|
1537
1761
|
throw new AranovaApiError(detail ?? response.statusText ?? "Request failed", {
|
|
1538
1762
|
status: response.status,
|
|
1539
1763
|
code,
|
|
1540
|
-
requestId: response.headers.get("x-request-id") ?? void 0
|
|
1764
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
1765
|
+
context
|
|
1541
1766
|
});
|
|
1542
1767
|
}
|
|
1543
1768
|
if (response.status === 204) return void 0;
|
|
@@ -1547,6 +1772,179 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
1547
1772
|
// ../tracking-core/src/resources/sales/transport.ts
|
|
1548
1773
|
var salesRequest = apiRequest;
|
|
1549
1774
|
|
|
1775
|
+
// ../tracking-core/src/resources/sales/bookings.ts
|
|
1776
|
+
var BOOKING_ERROR_CODES = {
|
|
1777
|
+
unavailable: "booking_unavailable",
|
|
1778
|
+
stale: "booking_config_stale",
|
|
1779
|
+
rejected: "booking_rejected"
|
|
1780
|
+
};
|
|
1781
|
+
var POLL_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
1782
|
+
var INVALID_DETAILS = "Please check your details and try again.";
|
|
1783
|
+
var newBookingAttemptKey = uuidV4;
|
|
1784
|
+
function sleep(ms) {
|
|
1785
|
+
return new Promise((resolve) => {
|
|
1786
|
+
setTimeout(resolve, ms);
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
function outcomeOf(body) {
|
|
1790
|
+
if (body.status === "created")
|
|
1791
|
+
return { status: "created", id: body.id, requestCode: body.request_code };
|
|
1792
|
+
if (body.status === "pending") return { status: "pending", id: body.id };
|
|
1793
|
+
return { status: "failed", id: body.id, fallbackUrl: body.fallback_url };
|
|
1794
|
+
}
|
|
1795
|
+
function formSubmitPayload(input, formId, labels) {
|
|
1796
|
+
const vehicle = input.vehicle ? `${input.vehicle.year} ${input.vehicle.make} ${input.vehicle.model}` : "";
|
|
1797
|
+
const field = (name, type, label, value) => ({
|
|
1798
|
+
name,
|
|
1799
|
+
type,
|
|
1800
|
+
label,
|
|
1801
|
+
value
|
|
1802
|
+
});
|
|
1803
|
+
return {
|
|
1804
|
+
form: {
|
|
1805
|
+
id: formId,
|
|
1806
|
+
action: "/tracking/bookings",
|
|
1807
|
+
fields: [
|
|
1808
|
+
field("first_name", "text", "First name", input.firstName),
|
|
1809
|
+
field("last_name", "text", "Last name", input.lastName),
|
|
1810
|
+
// E.164 on the wire: it keys the customer and enhanced-conversion matching.
|
|
1811
|
+
field("phone", "tel", "Phone", toE164(input.phone) ?? input.phone),
|
|
1812
|
+
field("email", "email", "Email", input.email ?? ""),
|
|
1813
|
+
field(
|
|
1814
|
+
"service",
|
|
1815
|
+
"select",
|
|
1816
|
+
"Service",
|
|
1817
|
+
input.services.map((key) => labels.get(key) ?? key).join(", ")
|
|
1818
|
+
),
|
|
1819
|
+
field("vehicle", "text", "Vehicle", vehicle),
|
|
1820
|
+
field(
|
|
1821
|
+
"start",
|
|
1822
|
+
"text",
|
|
1823
|
+
"Requested time",
|
|
1824
|
+
`${input.startDate} ${String(input.startHour).padStart(2, "0")}:00`
|
|
1825
|
+
),
|
|
1826
|
+
field("note", "textarea", "Notes", input.notes ?? "")
|
|
1827
|
+
]
|
|
1828
|
+
},
|
|
1829
|
+
page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
function runtimeOf(firing) {
|
|
1833
|
+
return firing && "integrations" in firing ? firing : null;
|
|
1834
|
+
}
|
|
1835
|
+
function createSalesBookingsClient(config, firing) {
|
|
1836
|
+
const submittedForms = /* @__PURE__ */ new Set();
|
|
1837
|
+
const staleAttempts = /* @__PURE__ */ new Set();
|
|
1838
|
+
function handoff(input) {
|
|
1839
|
+
const booking = getBookingIntegration(runtimeOf(firing)?.integrations());
|
|
1840
|
+
if (!booking) return null;
|
|
1841
|
+
return buildBookingHandoffUrl(booking.fallbackUrl, {
|
|
1842
|
+
firstName: input.firstName,
|
|
1843
|
+
lastName: input.lastName,
|
|
1844
|
+
phone: input.phone,
|
|
1845
|
+
email: input.email
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
function withHandoff(outcome, input) {
|
|
1849
|
+
if (outcome.status === "failed" && !outcome.fallbackUrl) {
|
|
1850
|
+
return { ...outcome, fallbackUrl: handoff(input) };
|
|
1851
|
+
}
|
|
1852
|
+
return outcome;
|
|
1853
|
+
}
|
|
1854
|
+
async function poll(id) {
|
|
1855
|
+
let outcome = { status: "pending", id };
|
|
1856
|
+
for (const delay of POLL_DELAYS_MS) {
|
|
1857
|
+
await sleep(delay);
|
|
1858
|
+
try {
|
|
1859
|
+
const path = `/bookings/${encodeURIComponent(id)}`;
|
|
1860
|
+
outcome = outcomeOf(await salesRequest(config, "GET", path));
|
|
1861
|
+
} catch {
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
if (outcome.status !== "pending") return outcome;
|
|
1865
|
+
}
|
|
1866
|
+
return outcome;
|
|
1867
|
+
}
|
|
1868
|
+
async function rejected(error, input, attemptKey, runtime) {
|
|
1869
|
+
if (!(error instanceof AranovaApiError)) {
|
|
1870
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input) };
|
|
1871
|
+
}
|
|
1872
|
+
if (error.code === BOOKING_ERROR_CODES.stale) {
|
|
1873
|
+
if (staleAttempts.has(attemptKey)) {
|
|
1874
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input) };
|
|
1875
|
+
}
|
|
1876
|
+
staleAttempts.add(attemptKey);
|
|
1877
|
+
await runtime?.revalidate();
|
|
1878
|
+
return { status: "stale", message: error.message };
|
|
1879
|
+
}
|
|
1880
|
+
if (error.code === BOOKING_ERROR_CODES.rejected) {
|
|
1881
|
+
const reasons = error.context?.reasons;
|
|
1882
|
+
return {
|
|
1883
|
+
status: "invalid",
|
|
1884
|
+
message: error.message,
|
|
1885
|
+
reasons: Array.isArray(reasons) ? reasons.map(String) : []
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
if (error.code === "validation_error") {
|
|
1889
|
+
return { status: "invalid", message: INVALID_DETAILS, reasons: [] };
|
|
1890
|
+
}
|
|
1891
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input) };
|
|
1892
|
+
}
|
|
1893
|
+
return {
|
|
1894
|
+
async submit(input, { attemptKey, formSubmit }) {
|
|
1895
|
+
registerCapability("booking_integration");
|
|
1896
|
+
const runtime = runtimeOf(firing);
|
|
1897
|
+
const booking = getBookingIntegration(runtime?.integrations());
|
|
1898
|
+
if (!booking?.acceptsBookings || booking.configVersion === null) {
|
|
1899
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input) };
|
|
1900
|
+
}
|
|
1901
|
+
if (formSubmit && !submittedForms.has(attemptKey)) {
|
|
1902
|
+
submittedForms.add(attemptKey);
|
|
1903
|
+
const labels = new Map(booking.options.map((option) => [option.key, option.label]));
|
|
1904
|
+
formSubmit.tracking.trackEvent(
|
|
1905
|
+
"form_submit",
|
|
1906
|
+
formSubmitPayload(input, formSubmit.formId, labels)
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
const body = {
|
|
1910
|
+
idempotency_key: attemptKey,
|
|
1911
|
+
// The tracking client's own visit, so the booking and its form_submit share one session.
|
|
1912
|
+
session_id: formSubmit?.tracking.getSessionId() ?? peekSessionId() ?? `no-session-${attemptKey}`,
|
|
1913
|
+
config_version: booking.configVersion,
|
|
1914
|
+
start_date: input.startDate,
|
|
1915
|
+
start_hour: input.startHour,
|
|
1916
|
+
first_name: input.firstName.trim(),
|
|
1917
|
+
last_name: input.lastName.trim(),
|
|
1918
|
+
phone: input.phone,
|
|
1919
|
+
email: input.email?.trim() || null,
|
|
1920
|
+
vehicle: input.vehicle ?? null,
|
|
1921
|
+
drop_off: input.dropOff ?? null,
|
|
1922
|
+
services: input.services,
|
|
1923
|
+
notes: input.notes?.trim() || null,
|
|
1924
|
+
marketing_sms_consent: input.marketingSmsConsent === true,
|
|
1925
|
+
page_path: typeof window === "undefined" ? null : window.location.pathname
|
|
1926
|
+
};
|
|
1927
|
+
let response;
|
|
1928
|
+
try {
|
|
1929
|
+
response = await salesRequest(config, "POST", "/bookings", body);
|
|
1930
|
+
} catch (error) {
|
|
1931
|
+
return rejected(error, input, attemptKey, runtime);
|
|
1932
|
+
}
|
|
1933
|
+
const outcome = outcomeOf(response);
|
|
1934
|
+
return withHandoff(outcome.status === "pending" ? await poll(outcome.id) : outcome, input);
|
|
1935
|
+
},
|
|
1936
|
+
async status(id) {
|
|
1937
|
+
return outcomeOf(
|
|
1938
|
+
await salesRequest(
|
|
1939
|
+
config,
|
|
1940
|
+
"GET",
|
|
1941
|
+
`/bookings/${encodeURIComponent(id)}`
|
|
1942
|
+
)
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1550
1948
|
// ../tracking-core/src/resources/sales/client.ts
|
|
1551
1949
|
function fireRecordedConversions(firing, input, recorded, sale, currency) {
|
|
1552
1950
|
if (!firing) return;
|
|
@@ -1721,7 +2119,8 @@ function createSalesClient(config) {
|
|
|
1721
2119
|
async config() {
|
|
1722
2120
|
return salesRequest(config, "GET", "/business/config");
|
|
1723
2121
|
}
|
|
1724
|
-
}
|
|
2122
|
+
},
|
|
2123
|
+
bookings: createSalesBookingsClient(config, config.firing)
|
|
1725
2124
|
};
|
|
1726
2125
|
}
|
|
1727
2126
|
|
|
@@ -1730,6 +2129,12 @@ var z3 = __toESM(require("zod/mini"));
|
|
|
1730
2129
|
|
|
1731
2130
|
// ../tracking-core/src/resources/sales/generated/zod.gen.ts
|
|
1732
2131
|
var z2 = __toESM(require("zod/mini"));
|
|
2132
|
+
var zAutoLeapSaleFacts = z2.object({
|
|
2133
|
+
first_order: z2.boolean(),
|
|
2134
|
+
fleet_customer: z2.boolean(),
|
|
2135
|
+
invoice_code: z2.string(),
|
|
2136
|
+
invoice_id: z2.string()
|
|
2137
|
+
});
|
|
1733
2138
|
var zErrorCode = z2.enum([
|
|
1734
2139
|
"missing_credentials",
|
|
1735
2140
|
"invalid_api_key",
|
|
@@ -1757,6 +2162,9 @@ var zErrorCode = z2.enum([
|
|
|
1757
2162
|
"booking_not_reschedulable",
|
|
1758
2163
|
"manage_token_invalid",
|
|
1759
2164
|
"google_error",
|
|
2165
|
+
"booking_unavailable",
|
|
2166
|
+
"booking_config_stale",
|
|
2167
|
+
"booking_rejected",
|
|
1760
2168
|
"calendar_not_owned",
|
|
1761
2169
|
"calendar_is_sandbox",
|
|
1762
2170
|
"sandbox_exists",
|
|
@@ -1798,6 +2206,7 @@ var zSaleServiceInputSchema = z2.strictObject({
|
|
|
1798
2206
|
amount_cents: z2.int().check(z2.gte(0)),
|
|
1799
2207
|
service: z2.string()
|
|
1800
2208
|
});
|
|
2209
|
+
var zSaleSource = z2.enum(["api", "admin", "autoleap"]);
|
|
1801
2210
|
var zSmsAutomationState = z2.enum([
|
|
1802
2211
|
"needs_check",
|
|
1803
2212
|
"failed",
|
|
@@ -1829,7 +2238,8 @@ var zSmsWithheldReason = z2.enum([
|
|
|
1829
2238
|
"number_not_active",
|
|
1830
2239
|
"a2p_registration_required",
|
|
1831
2240
|
"alert_recipient_cannot_receive_sms",
|
|
1832
|
-
"line_type_unresolved"
|
|
2241
|
+
"line_type_unresolved",
|
|
2242
|
+
"sale_source_sends_own_messages"
|
|
1833
2243
|
]);
|
|
1834
2244
|
var zSaleSmsStatusSchema = z2.object({
|
|
1835
2245
|
can_send_now: z2._default(z2.boolean(), false),
|
|
@@ -1938,6 +2348,8 @@ var zSaleEntitySchema = z2.object({
|
|
|
1938
2348
|
sms_automation: z2.nullable(zSaleSmsStatusSchema),
|
|
1939
2349
|
sms_consent: z2._default(z2.boolean(), false),
|
|
1940
2350
|
sms_consent_granted_at: z2.nullable(z2.iso.datetime({ offset: true })),
|
|
2351
|
+
source: zSaleSource,
|
|
2352
|
+
source_facts: z2.nullable(zAutoLeapSaleFacts),
|
|
1941
2353
|
updated_at: z2.iso.datetime({ offset: true }),
|
|
1942
2354
|
writeback: z2.nullable(zSaleWritebackStatusSchema)
|
|
1943
2355
|
});
|
|
@@ -1947,6 +2359,7 @@ var saleItemSchema = zSaleItemInputSchema;
|
|
|
1947
2359
|
var saleServiceSchema = zSaleServiceInputSchema;
|
|
1948
2360
|
var currencySchema = zSupportedCurrency;
|
|
1949
2361
|
var SUPPORTED_CURRENCIES = currencySchema.options;
|
|
2362
|
+
var SALE_SOURCES = zSaleSource.options;
|
|
1950
2363
|
var centsSchema = z3.int().check(z3.gte(0));
|
|
1951
2364
|
var metadataSchema = z3.record(z3.string(), z3.unknown());
|
|
1952
2365
|
var customerNameSchema = z3.string().check(z3.maxLength(200));
|
|
@@ -2031,10 +2444,12 @@ function phoneField(name, raw, country) {
|
|
|
2031
2444
|
|
|
2032
2445
|
// src/index.ts
|
|
2033
2446
|
var import_client2 = require("@aranova/tracking-react/client");
|
|
2447
|
+
var import_client3 = require("@aranova/tracking-react/client");
|
|
2034
2448
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2035
2449
|
0 && (module.exports = {
|
|
2036
2450
|
AdPlatformTracking,
|
|
2037
2451
|
AranovaApiError,
|
|
2452
|
+
BookingHandoffLink,
|
|
2038
2453
|
ConsentBanner,
|
|
2039
2454
|
DEFAULT_DECLINE_TTL_DAYS,
|
|
2040
2455
|
DEFAULT_PHONE_COUNTRY,
|
|
@@ -2042,9 +2457,12 @@ var import_client2 = require("@aranova/tracking-react/client");
|
|
|
2042
2457
|
GoogleAdsTracking,
|
|
2043
2458
|
NAMED_RANGES,
|
|
2044
2459
|
PhoneField,
|
|
2460
|
+
SALE_SOURCES,
|
|
2045
2461
|
SUPPORTED_CURRENCIES,
|
|
2046
2462
|
TRACKING_PARAM_KEYS,
|
|
2047
2463
|
TRACKING_RANGES,
|
|
2464
|
+
bookableSlots,
|
|
2465
|
+
buildBookingHandoffUrl,
|
|
2048
2466
|
captureTrackingParamsFromLocation,
|
|
2049
2467
|
clearStashedUserData,
|
|
2050
2468
|
createSalesClient,
|
|
@@ -2059,10 +2477,12 @@ var import_client2 = require("@aranova/tracking-react/client");
|
|
|
2059
2477
|
formatPhone,
|
|
2060
2478
|
formatPhoneAsTyped,
|
|
2061
2479
|
fromMinor,
|
|
2480
|
+
getBookingIntegration,
|
|
2062
2481
|
getConsentChoice,
|
|
2063
2482
|
getConsentState,
|
|
2064
2483
|
getEventSemantics,
|
|
2065
2484
|
getTrackingConfigRuntime,
|
|
2485
|
+
newBookingAttemptKey,
|
|
2066
2486
|
onConsentChange,
|
|
2067
2487
|
optIn,
|
|
2068
2488
|
optOut,
|
|
@@ -2077,9 +2497,14 @@ var import_client2 = require("@aranova/tracking-react/client");
|
|
|
2077
2497
|
saleUpdateSchema,
|
|
2078
2498
|
salesRequest,
|
|
2079
2499
|
setConsentState,
|
|
2500
|
+
shopToday,
|
|
2080
2501
|
stashUserData,
|
|
2081
2502
|
toE164,
|
|
2082
2503
|
toMinor,
|
|
2504
|
+
useBookingHandoffUrl,
|
|
2505
|
+
useBookingIntegration,
|
|
2506
|
+
useBookingSlots,
|
|
2507
|
+
useBookingSubmit,
|
|
2083
2508
|
useConsent,
|
|
2084
2509
|
useConsentState,
|
|
2085
2510
|
useCookiePreferences,
|