@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.mjs
CHANGED
|
@@ -221,6 +221,8 @@ var TRACKING_CAPABILITIES = [
|
|
|
221
221
|
"ad_tags_meta",
|
|
222
222
|
"base_tracking",
|
|
223
223
|
"blog_rendering",
|
|
224
|
+
"booking_handoff",
|
|
225
|
+
"booking_integration",
|
|
224
226
|
"calendar_read",
|
|
225
227
|
"calendar_write",
|
|
226
228
|
"consent_controls",
|
|
@@ -500,6 +502,42 @@ function buildLandingPayloadFields(sessionId, override) {
|
|
|
500
502
|
};
|
|
501
503
|
}
|
|
502
504
|
|
|
505
|
+
// ../tracking-core/src/uuid.ts
|
|
506
|
+
function uuidV4() {
|
|
507
|
+
const cryptoApi = globalThis.crypto;
|
|
508
|
+
if (typeof cryptoApi?.randomUUID === "function") return cryptoApi.randomUUID();
|
|
509
|
+
const bytes = new Uint8Array(16);
|
|
510
|
+
if (typeof cryptoApi?.getRandomValues === "function") cryptoApi.getRandomValues(bytes);
|
|
511
|
+
else for (let i = 0; i < 16; i += 1) bytes[i] = Math.floor(Math.random() * 256);
|
|
512
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
513
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
514
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
515
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// ../tracking-core/src/session.ts
|
|
519
|
+
var SESSION_STORAGE_KEY = "aranova_tracking_session";
|
|
520
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
521
|
+
function readLocalStorage(key) {
|
|
522
|
+
try {
|
|
523
|
+
return window.localStorage.getItem(key);
|
|
524
|
+
} catch {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function peekSessionId(now = Date.now()) {
|
|
529
|
+
if (typeof window === "undefined") return null;
|
|
530
|
+
const raw = readLocalStorage(SESSION_STORAGE_KEY);
|
|
531
|
+
if (!raw) return null;
|
|
532
|
+
try {
|
|
533
|
+
const parsed = JSON.parse(raw);
|
|
534
|
+
if (typeof parsed.id !== "string" || typeof parsed.last_event_at !== "number") return null;
|
|
535
|
+
return now - parsed.last_event_at <= SESSION_IDLE_MS ? parsed.id : null;
|
|
536
|
+
} catch {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
503
541
|
// ../tracking-core/src/cross-site-link.ts
|
|
504
542
|
var LINK_PARAM = "_arv";
|
|
505
543
|
var LINK_TOKEN_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -641,6 +679,69 @@ function parseFiring(value) {
|
|
|
641
679
|
currency: typeof f.currency === "string" ? f.currency : null
|
|
642
680
|
};
|
|
643
681
|
}
|
|
682
|
+
function isNumberList(value) {
|
|
683
|
+
return Array.isArray(value) && value.every((v) => typeof v === "number");
|
|
684
|
+
}
|
|
685
|
+
function isStringList(value) {
|
|
686
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
687
|
+
}
|
|
688
|
+
function parseWindowsMap(value) {
|
|
689
|
+
const windowsByKey = {};
|
|
690
|
+
if (!value || typeof value !== "object") return windowsByKey;
|
|
691
|
+
for (const [key, windows] of Object.entries(value)) {
|
|
692
|
+
if (!windows || typeof windows !== "object") continue;
|
|
693
|
+
const w = windows;
|
|
694
|
+
windowsByKey[key] = {
|
|
695
|
+
morning: isNumberList(w.morning) ? w.morning : [],
|
|
696
|
+
afternoon: isNumberList(w.afternoon) ? w.afternoon : []
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
return windowsByKey;
|
|
700
|
+
}
|
|
701
|
+
function parseBookingRules(value) {
|
|
702
|
+
if (!value || typeof value !== "object") return null;
|
|
703
|
+
const r = value;
|
|
704
|
+
if (typeof r.min_days_ahead !== "number" || typeof r.max_days_ahead !== "number") return null;
|
|
705
|
+
if (!r.hours || typeof r.hours !== "object") return null;
|
|
706
|
+
const hours = parseWindowsMap(r.hours);
|
|
707
|
+
return {
|
|
708
|
+
min_days_ahead: r.min_days_ahead,
|
|
709
|
+
max_days_ahead: r.max_days_ahead,
|
|
710
|
+
hours,
|
|
711
|
+
closed_dates: isStringList(r.closed_dates) ? r.closed_dates : [],
|
|
712
|
+
special_hours: parseWindowsMap(r.special_hours),
|
|
713
|
+
vehicle_required: r.vehicle_required === true,
|
|
714
|
+
drop_off_options: isStringList(r.drop_off_options) ? r.drop_off_options : []
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function parseBookingOption(value) {
|
|
718
|
+
if (!value || typeof value !== "object") return [];
|
|
719
|
+
const o = value;
|
|
720
|
+
if (typeof o.key !== "string" || !o.key || typeof o.label !== "string") return [];
|
|
721
|
+
return [{ key: o.key, label: o.label }];
|
|
722
|
+
}
|
|
723
|
+
function parseIntegrations(value) {
|
|
724
|
+
if (!value || typeof value !== "object") return {};
|
|
725
|
+
const autoleap = value.autoleap;
|
|
726
|
+
if (!autoleap || typeof autoleap !== "object") return {};
|
|
727
|
+
const booking = autoleap.booking;
|
|
728
|
+
if (!booking || typeof booking !== "object") return {};
|
|
729
|
+
const b = booking;
|
|
730
|
+
if (typeof b.fallback_url !== "string" || !b.fallback_url) return {};
|
|
731
|
+
return {
|
|
732
|
+
autoleap: {
|
|
733
|
+
booking: {
|
|
734
|
+
enabled: b.enabled === true,
|
|
735
|
+
fallback_url: b.fallback_url,
|
|
736
|
+
timezone: typeof b.timezone === "string" ? b.timezone : "UTC",
|
|
737
|
+
config_version: typeof b.config_version === "number" ? b.config_version : null,
|
|
738
|
+
accepts_bookings: b.accepts_bookings === true,
|
|
739
|
+
rules: parseBookingRules(b.rules),
|
|
740
|
+
options: Array.isArray(b.options) ? b.options.flatMap(parseBookingOption) : []
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
644
745
|
function parseTrigger(value) {
|
|
645
746
|
if (!value || typeof value !== "object") return null;
|
|
646
747
|
const t = value;
|
|
@@ -700,7 +801,8 @@ function parseConversionConfig(raw) {
|
|
|
700
801
|
meta_pixel_ids: isStringMap(obj.meta_pixel_ids) ? obj.meta_pixel_ids : {},
|
|
701
802
|
services,
|
|
702
803
|
goals,
|
|
703
|
-
linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : []
|
|
804
|
+
linked_hostnames: Array.isArray(obj.linked_hostnames) ? obj.linked_hostnames.filter((h) => typeof h === "string" && h.length > 0) : [],
|
|
805
|
+
integrations: parseIntegrations(obj.integrations)
|
|
704
806
|
};
|
|
705
807
|
}
|
|
706
808
|
var CACHE_PREFIX = "_aranova_cfg_";
|
|
@@ -1147,6 +1249,13 @@ var TrackingConfigRuntime = class {
|
|
|
1147
1249
|
linkedHostnames() {
|
|
1148
1250
|
return this.current?.linked_hostnames ?? [];
|
|
1149
1251
|
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Integration blocks, from the cached object even while unconfirmed: the booking fallback
|
|
1254
|
+
* link is safe to show stale. Same reference until the next adopt, for `useSyncExternalStore`.
|
|
1255
|
+
*/
|
|
1256
|
+
integrations() {
|
|
1257
|
+
return this.current?.integrations ?? null;
|
|
1258
|
+
}
|
|
1150
1259
|
async revalidateNow() {
|
|
1151
1260
|
try {
|
|
1152
1261
|
const headers = {};
|
|
@@ -1316,6 +1425,105 @@ function getTrackingConfigRuntime(ref, fetchImpl) {
|
|
|
1316
1425
|
return runtime;
|
|
1317
1426
|
}
|
|
1318
1427
|
|
|
1428
|
+
// ../../contracts/autoleap-booking-links.json
|
|
1429
|
+
var autoleap_booking_links_default = {
|
|
1430
|
+
hosts: ["myalp.io"]
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
// ../tracking-core/src/integrations-public.ts
|
|
1434
|
+
var HANDOFF_HOSTS = new Set(autoleap_booking_links_default.hosts);
|
|
1435
|
+
function getBookingIntegration(integrations) {
|
|
1436
|
+
const booking = integrations?.autoleap?.booking;
|
|
1437
|
+
if (!booking) return null;
|
|
1438
|
+
const rules = booking.rules ? camelRules(booking.rules) : null;
|
|
1439
|
+
return {
|
|
1440
|
+
provider: "autoleap",
|
|
1441
|
+
enabled: booking.enabled,
|
|
1442
|
+
fallbackUrl: booking.fallback_url,
|
|
1443
|
+
acceptsBookings: booking.accepts_bookings && rules !== null && booking.config_version !== null,
|
|
1444
|
+
timezone: booking.timezone,
|
|
1445
|
+
configVersion: booking.config_version,
|
|
1446
|
+
rules,
|
|
1447
|
+
options: booking.options.map((option) => ({ key: option.key, label: option.label }))
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
function camelRules(rules) {
|
|
1451
|
+
return {
|
|
1452
|
+
minDaysAhead: rules.min_days_ahead,
|
|
1453
|
+
maxDaysAhead: rules.max_days_ahead,
|
|
1454
|
+
hours: rules.hours,
|
|
1455
|
+
closedDates: rules.closed_dates,
|
|
1456
|
+
specialHours: rules.special_hours,
|
|
1457
|
+
vehicleRequired: rules.vehicle_required,
|
|
1458
|
+
dropOffOptions: rules.drop_off_options
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
var WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
1462
|
+
function shopToday(timeZone, now = /* @__PURE__ */ new Date()) {
|
|
1463
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
1464
|
+
timeZone,
|
|
1465
|
+
year: "numeric",
|
|
1466
|
+
month: "2-digit",
|
|
1467
|
+
day: "2-digit"
|
|
1468
|
+
}).formatToParts(now);
|
|
1469
|
+
const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
1470
|
+
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
1471
|
+
}
|
|
1472
|
+
function addDays(isoDate, days) {
|
|
1473
|
+
const [year, month, day] = isoDate.split("-").map(Number);
|
|
1474
|
+
const date = new Date(Date.UTC(year, month - 1, day + days));
|
|
1475
|
+
return date.toISOString().slice(0, 10);
|
|
1476
|
+
}
|
|
1477
|
+
function bookableSlots(rules, { timeZone, now = /* @__PURE__ */ new Date() }) {
|
|
1478
|
+
const today = shopToday(timeZone, now);
|
|
1479
|
+
const days = [];
|
|
1480
|
+
const closed = new Set(rules.closedDates);
|
|
1481
|
+
for (let offset = rules.minDaysAhead; offset <= rules.maxDaysAhead; offset += 1) {
|
|
1482
|
+
const date = addDays(today, offset);
|
|
1483
|
+
if (closed.has(date)) continue;
|
|
1484
|
+
const weekday = WEEKDAYS[(/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay()];
|
|
1485
|
+
const windows = rules.specialHours[date] ?? rules.hours[weekday];
|
|
1486
|
+
if (!windows || windows.morning.length === 0 && windows.afternoon.length === 0) continue;
|
|
1487
|
+
days.push({
|
|
1488
|
+
date,
|
|
1489
|
+
weekday,
|
|
1490
|
+
morning: [...windows.morning],
|
|
1491
|
+
afternoon: [...windows.afternoon]
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
return days;
|
|
1495
|
+
}
|
|
1496
|
+
function buildBookingHandoffUrl(fallbackUrl, prefill = {}) {
|
|
1497
|
+
let url;
|
|
1498
|
+
try {
|
|
1499
|
+
url = new URL(fallbackUrl);
|
|
1500
|
+
} catch {
|
|
1501
|
+
return null;
|
|
1502
|
+
}
|
|
1503
|
+
if (url.protocol !== "https:" || !HANDOFF_HOSTS.has(url.hostname) || url.username || url.password || url.port) {
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
const [firstName, lastName] = prefill.firstName?.trim() ? [prefill.firstName, prefill.lastName] : splitFullName(prefill.name);
|
|
1507
|
+
const values = [
|
|
1508
|
+
["firstName", firstName?.trim()],
|
|
1509
|
+
["lastName", lastName?.trim()],
|
|
1510
|
+
["phone", prefill.phone ? nationalDigits(prefill.phone) : ""],
|
|
1511
|
+
["email", prefill.email?.trim()]
|
|
1512
|
+
];
|
|
1513
|
+
for (const [key, value] of values) {
|
|
1514
|
+
if (value) url.searchParams.set(key, value);
|
|
1515
|
+
}
|
|
1516
|
+
return url.toString();
|
|
1517
|
+
}
|
|
1518
|
+
function splitFullName(name) {
|
|
1519
|
+
const [first = "", ...rest] = (name ?? "").trim().split(/\s+/);
|
|
1520
|
+
return [first, rest.join(" ")];
|
|
1521
|
+
}
|
|
1522
|
+
function nationalDigits(phone) {
|
|
1523
|
+
const parsed = parsePhone(phone);
|
|
1524
|
+
return (parsed.isValid ? parsed.national : phone).replace(/\D/g, "");
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1319
1527
|
// ../tracking-core/src/events/semantics.ts
|
|
1320
1528
|
var EVENT_SEMANTICS = {
|
|
1321
1529
|
page_view: {
|
|
@@ -1422,6 +1630,7 @@ var AranovaApiError = class extends Error {
|
|
|
1422
1630
|
this.status = options.status;
|
|
1423
1631
|
this.code = options.code;
|
|
1424
1632
|
this.requestId = options.requestId;
|
|
1633
|
+
this.context = options.context;
|
|
1425
1634
|
}
|
|
1426
1635
|
};
|
|
1427
1636
|
|
|
@@ -1448,19 +1657,24 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
1448
1657
|
if (!response.ok) {
|
|
1449
1658
|
let detail;
|
|
1450
1659
|
let code;
|
|
1660
|
+
let context;
|
|
1451
1661
|
try {
|
|
1452
1662
|
const parsed = await response.json();
|
|
1453
1663
|
if (parsed && typeof parsed === "object") {
|
|
1454
1664
|
const record3 = parsed;
|
|
1455
1665
|
if (typeof record3.detail === "string") detail = record3.detail;
|
|
1456
1666
|
if (typeof record3.code === "string") code = record3.code;
|
|
1667
|
+
if (record3.context && typeof record3.context === "object") {
|
|
1668
|
+
context = record3.context;
|
|
1669
|
+
}
|
|
1457
1670
|
}
|
|
1458
1671
|
} catch {
|
|
1459
1672
|
}
|
|
1460
1673
|
throw new AranovaApiError(detail ?? response.statusText ?? "Request failed", {
|
|
1461
1674
|
status: response.status,
|
|
1462
1675
|
code,
|
|
1463
|
-
requestId: response.headers.get("x-request-id") ?? void 0
|
|
1676
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
1677
|
+
context
|
|
1464
1678
|
});
|
|
1465
1679
|
}
|
|
1466
1680
|
if (response.status === 204) return void 0;
|
|
@@ -1470,6 +1684,179 @@ async function apiRequest(config, method, path, body, extraHeaders) {
|
|
|
1470
1684
|
// ../tracking-core/src/resources/sales/transport.ts
|
|
1471
1685
|
var salesRequest = apiRequest;
|
|
1472
1686
|
|
|
1687
|
+
// ../tracking-core/src/resources/sales/bookings.ts
|
|
1688
|
+
var BOOKING_ERROR_CODES = {
|
|
1689
|
+
unavailable: "booking_unavailable",
|
|
1690
|
+
stale: "booking_config_stale",
|
|
1691
|
+
rejected: "booking_rejected"
|
|
1692
|
+
};
|
|
1693
|
+
var POLL_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 15e3];
|
|
1694
|
+
var INVALID_DETAILS = "Please check your details and try again.";
|
|
1695
|
+
var newBookingAttemptKey = uuidV4;
|
|
1696
|
+
function sleep(ms) {
|
|
1697
|
+
return new Promise((resolve) => {
|
|
1698
|
+
setTimeout(resolve, ms);
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
function outcomeOf(body) {
|
|
1702
|
+
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 };
|
|
1706
|
+
}
|
|
1707
|
+
function formSubmitPayload(input, formId, labels) {
|
|
1708
|
+
const vehicle = input.vehicle ? `${input.vehicle.year} ${input.vehicle.make} ${input.vehicle.model}` : "";
|
|
1709
|
+
const field = (name, type, label, value) => ({
|
|
1710
|
+
name,
|
|
1711
|
+
type,
|
|
1712
|
+
label,
|
|
1713
|
+
value
|
|
1714
|
+
});
|
|
1715
|
+
return {
|
|
1716
|
+
form: {
|
|
1717
|
+
id: formId,
|
|
1718
|
+
action: "/tracking/bookings",
|
|
1719
|
+
fields: [
|
|
1720
|
+
field("first_name", "text", "First name", input.firstName),
|
|
1721
|
+
field("last_name", "text", "Last name", input.lastName),
|
|
1722
|
+
// E.164 on the wire: it keys the customer and enhanced-conversion matching.
|
|
1723
|
+
field("phone", "tel", "Phone", toE164(input.phone) ?? input.phone),
|
|
1724
|
+
field("email", "email", "Email", input.email ?? ""),
|
|
1725
|
+
field(
|
|
1726
|
+
"service",
|
|
1727
|
+
"select",
|
|
1728
|
+
"Service",
|
|
1729
|
+
input.services.map((key) => labels.get(key) ?? key).join(", ")
|
|
1730
|
+
),
|
|
1731
|
+
field("vehicle", "text", "Vehicle", vehicle),
|
|
1732
|
+
field(
|
|
1733
|
+
"start",
|
|
1734
|
+
"text",
|
|
1735
|
+
"Requested time",
|
|
1736
|
+
`${input.startDate} ${String(input.startHour).padStart(2, "0")}:00`
|
|
1737
|
+
),
|
|
1738
|
+
field("note", "textarea", "Notes", input.notes ?? "")
|
|
1739
|
+
]
|
|
1740
|
+
},
|
|
1741
|
+
page: { path: typeof window === "undefined" ? "/" : window.location.pathname }
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function runtimeOf(firing) {
|
|
1745
|
+
return firing && "integrations" in firing ? firing : null;
|
|
1746
|
+
}
|
|
1747
|
+
function createSalesBookingsClient(config, firing) {
|
|
1748
|
+
const submittedForms = /* @__PURE__ */ new Set();
|
|
1749
|
+
const staleAttempts = /* @__PURE__ */ new Set();
|
|
1750
|
+
function handoff(input) {
|
|
1751
|
+
const booking = getBookingIntegration(runtimeOf(firing)?.integrations());
|
|
1752
|
+
if (!booking) return null;
|
|
1753
|
+
return buildBookingHandoffUrl(booking.fallbackUrl, {
|
|
1754
|
+
firstName: input.firstName,
|
|
1755
|
+
lastName: input.lastName,
|
|
1756
|
+
phone: input.phone,
|
|
1757
|
+
email: input.email
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
function withHandoff(outcome, input) {
|
|
1761
|
+
if (outcome.status === "failed" && !outcome.fallbackUrl) {
|
|
1762
|
+
return { ...outcome, fallbackUrl: handoff(input) };
|
|
1763
|
+
}
|
|
1764
|
+
return outcome;
|
|
1765
|
+
}
|
|
1766
|
+
async function poll(id) {
|
|
1767
|
+
let outcome = { status: "pending", id };
|
|
1768
|
+
for (const delay of POLL_DELAYS_MS) {
|
|
1769
|
+
await sleep(delay);
|
|
1770
|
+
try {
|
|
1771
|
+
const path = `/bookings/${encodeURIComponent(id)}`;
|
|
1772
|
+
outcome = outcomeOf(await salesRequest(config, "GET", path));
|
|
1773
|
+
} catch {
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
if (outcome.status !== "pending") return outcome;
|
|
1777
|
+
}
|
|
1778
|
+
return outcome;
|
|
1779
|
+
}
|
|
1780
|
+
async function rejected(error, input, attemptKey, runtime) {
|
|
1781
|
+
if (!(error instanceof AranovaApiError)) {
|
|
1782
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input) };
|
|
1783
|
+
}
|
|
1784
|
+
if (error.code === BOOKING_ERROR_CODES.stale) {
|
|
1785
|
+
if (staleAttempts.has(attemptKey)) {
|
|
1786
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input) };
|
|
1787
|
+
}
|
|
1788
|
+
staleAttempts.add(attemptKey);
|
|
1789
|
+
await runtime?.revalidate();
|
|
1790
|
+
return { status: "stale", message: error.message };
|
|
1791
|
+
}
|
|
1792
|
+
if (error.code === BOOKING_ERROR_CODES.rejected) {
|
|
1793
|
+
const reasons = error.context?.reasons;
|
|
1794
|
+
return {
|
|
1795
|
+
status: "invalid",
|
|
1796
|
+
message: error.message,
|
|
1797
|
+
reasons: Array.isArray(reasons) ? reasons.map(String) : []
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
if (error.code === "validation_error") {
|
|
1801
|
+
return { status: "invalid", message: INVALID_DETAILS, reasons: [] };
|
|
1802
|
+
}
|
|
1803
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input) };
|
|
1804
|
+
}
|
|
1805
|
+
return {
|
|
1806
|
+
async submit(input, { attemptKey, formSubmit }) {
|
|
1807
|
+
registerCapability("booking_integration");
|
|
1808
|
+
const runtime = runtimeOf(firing);
|
|
1809
|
+
const booking = getBookingIntegration(runtime?.integrations());
|
|
1810
|
+
if (!booking?.acceptsBookings || booking.configVersion === null) {
|
|
1811
|
+
return { status: "failed", id: null, fallbackUrl: handoff(input) };
|
|
1812
|
+
}
|
|
1813
|
+
if (formSubmit && !submittedForms.has(attemptKey)) {
|
|
1814
|
+
submittedForms.add(attemptKey);
|
|
1815
|
+
const labels = new Map(booking.options.map((option) => [option.key, option.label]));
|
|
1816
|
+
formSubmit.tracking.trackEvent(
|
|
1817
|
+
"form_submit",
|
|
1818
|
+
formSubmitPayload(input, formSubmit.formId, labels)
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
const body = {
|
|
1822
|
+
idempotency_key: attemptKey,
|
|
1823
|
+
// The tracking client's own visit, so the booking and its form_submit share one session.
|
|
1824
|
+
session_id: formSubmit?.tracking.getSessionId() ?? peekSessionId() ?? `no-session-${attemptKey}`,
|
|
1825
|
+
config_version: booking.configVersion,
|
|
1826
|
+
start_date: input.startDate,
|
|
1827
|
+
start_hour: input.startHour,
|
|
1828
|
+
first_name: input.firstName.trim(),
|
|
1829
|
+
last_name: input.lastName.trim(),
|
|
1830
|
+
phone: input.phone,
|
|
1831
|
+
email: input.email?.trim() || null,
|
|
1832
|
+
vehicle: input.vehicle ?? null,
|
|
1833
|
+
drop_off: input.dropOff ?? null,
|
|
1834
|
+
services: input.services,
|
|
1835
|
+
notes: input.notes?.trim() || null,
|
|
1836
|
+
marketing_sms_consent: input.marketingSmsConsent === true,
|
|
1837
|
+
page_path: typeof window === "undefined" ? null : window.location.pathname
|
|
1838
|
+
};
|
|
1839
|
+
let response;
|
|
1840
|
+
try {
|
|
1841
|
+
response = await salesRequest(config, "POST", "/bookings", body);
|
|
1842
|
+
} catch (error) {
|
|
1843
|
+
return rejected(error, input, attemptKey, runtime);
|
|
1844
|
+
}
|
|
1845
|
+
const outcome = outcomeOf(response);
|
|
1846
|
+
return withHandoff(outcome.status === "pending" ? await poll(outcome.id) : outcome, input);
|
|
1847
|
+
},
|
|
1848
|
+
async status(id) {
|
|
1849
|
+
return outcomeOf(
|
|
1850
|
+
await salesRequest(
|
|
1851
|
+
config,
|
|
1852
|
+
"GET",
|
|
1853
|
+
`/bookings/${encodeURIComponent(id)}`
|
|
1854
|
+
)
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1473
1860
|
// ../tracking-core/src/resources/sales/client.ts
|
|
1474
1861
|
function fireRecordedConversions(firing, input, recorded, sale, currency) {
|
|
1475
1862
|
if (!firing) return;
|
|
@@ -1644,7 +2031,8 @@ function createSalesClient(config) {
|
|
|
1644
2031
|
async config() {
|
|
1645
2032
|
return salesRequest(config, "GET", "/business/config");
|
|
1646
2033
|
}
|
|
1647
|
-
}
|
|
2034
|
+
},
|
|
2035
|
+
bookings: createSalesBookingsClient(config, config.firing)
|
|
1648
2036
|
};
|
|
1649
2037
|
}
|
|
1650
2038
|
|
|
@@ -1653,6 +2041,12 @@ import * as z3 from "zod/mini";
|
|
|
1653
2041
|
|
|
1654
2042
|
// ../tracking-core/src/resources/sales/generated/zod.gen.ts
|
|
1655
2043
|
import * as z2 from "zod/mini";
|
|
2044
|
+
var zAutoLeapSaleFacts = z2.object({
|
|
2045
|
+
first_order: z2.boolean(),
|
|
2046
|
+
fleet_customer: z2.boolean(),
|
|
2047
|
+
invoice_code: z2.string(),
|
|
2048
|
+
invoice_id: z2.string()
|
|
2049
|
+
});
|
|
1656
2050
|
var zErrorCode = z2.enum([
|
|
1657
2051
|
"missing_credentials",
|
|
1658
2052
|
"invalid_api_key",
|
|
@@ -1680,6 +2074,9 @@ var zErrorCode = z2.enum([
|
|
|
1680
2074
|
"booking_not_reschedulable",
|
|
1681
2075
|
"manage_token_invalid",
|
|
1682
2076
|
"google_error",
|
|
2077
|
+
"booking_unavailable",
|
|
2078
|
+
"booking_config_stale",
|
|
2079
|
+
"booking_rejected",
|
|
1683
2080
|
"calendar_not_owned",
|
|
1684
2081
|
"calendar_is_sandbox",
|
|
1685
2082
|
"sandbox_exists",
|
|
@@ -1721,6 +2118,7 @@ var zSaleServiceInputSchema = z2.strictObject({
|
|
|
1721
2118
|
amount_cents: z2.int().check(z2.gte(0)),
|
|
1722
2119
|
service: z2.string()
|
|
1723
2120
|
});
|
|
2121
|
+
var zSaleSource = z2.enum(["api", "admin", "autoleap"]);
|
|
1724
2122
|
var zSmsAutomationState = z2.enum([
|
|
1725
2123
|
"needs_check",
|
|
1726
2124
|
"failed",
|
|
@@ -1752,7 +2150,8 @@ var zSmsWithheldReason = z2.enum([
|
|
|
1752
2150
|
"number_not_active",
|
|
1753
2151
|
"a2p_registration_required",
|
|
1754
2152
|
"alert_recipient_cannot_receive_sms",
|
|
1755
|
-
"line_type_unresolved"
|
|
2153
|
+
"line_type_unresolved",
|
|
2154
|
+
"sale_source_sends_own_messages"
|
|
1756
2155
|
]);
|
|
1757
2156
|
var zSaleSmsStatusSchema = z2.object({
|
|
1758
2157
|
can_send_now: z2._default(z2.boolean(), false),
|
|
@@ -1861,6 +2260,8 @@ var zSaleEntitySchema = z2.object({
|
|
|
1861
2260
|
sms_automation: z2.nullable(zSaleSmsStatusSchema),
|
|
1862
2261
|
sms_consent: z2._default(z2.boolean(), false),
|
|
1863
2262
|
sms_consent_granted_at: z2.nullable(z2.iso.datetime({ offset: true })),
|
|
2263
|
+
source: zSaleSource,
|
|
2264
|
+
source_facts: z2.nullable(zAutoLeapSaleFacts),
|
|
1864
2265
|
updated_at: z2.iso.datetime({ offset: true }),
|
|
1865
2266
|
writeback: z2.nullable(zSaleWritebackStatusSchema)
|
|
1866
2267
|
});
|
|
@@ -1870,6 +2271,7 @@ var saleItemSchema = zSaleItemInputSchema;
|
|
|
1870
2271
|
var saleServiceSchema = zSaleServiceInputSchema;
|
|
1871
2272
|
var currencySchema = zSupportedCurrency;
|
|
1872
2273
|
var SUPPORTED_CURRENCIES = currencySchema.options;
|
|
2274
|
+
var SALE_SOURCES = zSaleSource.options;
|
|
1873
2275
|
var centsSchema = z3.int().check(z3.gte(0));
|
|
1874
2276
|
var metadataSchema = z3.record(z3.string(), z3.unknown());
|
|
1875
2277
|
var customerNameSchema = z3.string().check(z3.maxLength(200));
|
|
@@ -1958,9 +2360,17 @@ import {
|
|
|
1958
2360
|
usePhoneConfig,
|
|
1959
2361
|
usePhoneField
|
|
1960
2362
|
} from "@aranova/tracking-react/client";
|
|
2363
|
+
import {
|
|
2364
|
+
BookingHandoffLink,
|
|
2365
|
+
useBookingHandoffUrl,
|
|
2366
|
+
useBookingIntegration,
|
|
2367
|
+
useBookingSlots,
|
|
2368
|
+
useBookingSubmit
|
|
2369
|
+
} from "@aranova/tracking-react/client";
|
|
1961
2370
|
export {
|
|
1962
2371
|
AdPlatformTracking,
|
|
1963
2372
|
AranovaApiError,
|
|
2373
|
+
BookingHandoffLink,
|
|
1964
2374
|
ConsentBanner,
|
|
1965
2375
|
DEFAULT_DECLINE_TTL_DAYS,
|
|
1966
2376
|
DEFAULT_PHONE_COUNTRY,
|
|
@@ -1968,9 +2378,12 @@ export {
|
|
|
1968
2378
|
GoogleAdsTracking,
|
|
1969
2379
|
NAMED_RANGES,
|
|
1970
2380
|
PhoneField,
|
|
2381
|
+
SALE_SOURCES,
|
|
1971
2382
|
SUPPORTED_CURRENCIES,
|
|
1972
2383
|
TRACKING_PARAM_KEYS,
|
|
1973
2384
|
TRACKING_RANGES,
|
|
2385
|
+
bookableSlots,
|
|
2386
|
+
buildBookingHandoffUrl,
|
|
1974
2387
|
captureTrackingParamsFromLocation,
|
|
1975
2388
|
clearStashedUserData,
|
|
1976
2389
|
createSalesClient,
|
|
@@ -1985,10 +2398,12 @@ export {
|
|
|
1985
2398
|
formatPhone,
|
|
1986
2399
|
formatPhoneAsTyped,
|
|
1987
2400
|
fromMinor,
|
|
2401
|
+
getBookingIntegration,
|
|
1988
2402
|
getConsentChoice,
|
|
1989
2403
|
getConsentState,
|
|
1990
2404
|
getEventSemantics,
|
|
1991
2405
|
getTrackingConfigRuntime,
|
|
2406
|
+
newBookingAttemptKey,
|
|
1992
2407
|
onConsentChange,
|
|
1993
2408
|
optIn,
|
|
1994
2409
|
optOut,
|
|
@@ -2003,9 +2418,14 @@ export {
|
|
|
2003
2418
|
saleUpdateSchema,
|
|
2004
2419
|
salesRequest,
|
|
2005
2420
|
setConsentState,
|
|
2421
|
+
shopToday,
|
|
2006
2422
|
stashUserData,
|
|
2007
2423
|
toE164,
|
|
2008
2424
|
toMinor,
|
|
2425
|
+
useBookingHandoffUrl,
|
|
2426
|
+
useBookingIntegration,
|
|
2427
|
+
useBookingSlots,
|
|
2428
|
+
useBookingSubmit,
|
|
2009
2429
|
useConsent,
|
|
2010
2430
|
useConsentState,
|
|
2011
2431
|
useCookiePreferences,
|