@aranova/tracking-next 1.1.0 → 1.2.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 +48 -1
- package/dist/calendar-server.d.mts +1 -1
- package/dist/calendar-server.d.ts +1 -1
- package/dist/calendar.d.mts +14 -3
- package/dist/calendar.d.ts +14 -3
- package/dist/calendar.js +212 -18
- package/dist/calendar.js.map +1 -1
- package/dist/calendar.mjs +211 -18
- package/dist/calendar.mjs.map +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +1 -1
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +161 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +160 -19
- package/dist/index.mjs.map +1 -1
- package/dist/sales.js +11 -0
- package/dist/sales.js.map +1 -1
- package/dist/sales.mjs +11 -0
- package/dist/sales.mjs.map +1 -1
- package/dist/{transport-I_cMSFQ6.d.mts → transport-BlXQZs9Z.d.mts} +120 -50
- package/dist/{transport-CDmGOiCq.d.ts → transport-CAZYrZbB.d.ts} +120 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -407,7 +407,54 @@ its Origin allowlist is a browser-enforced header a script forges trivially. A j
|
|
|
407
407
|
a database row you can delete; a junk **booking** puts an event on a real person's calendar
|
|
408
408
|
and emails their contacts.
|
|
409
409
|
|
|
410
|
-
|
|
410
|
+
Doing your own work around a booking (validation, emails, a CRM)? Call the write client from
|
|
411
|
+
your own route instead of mounting the handler:
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
// app/api/book/route.ts
|
|
415
|
+
import { createCalendarWriteClient } from "@aranova/tracking-next/calendar-server";
|
|
416
|
+
import { isCalendarError } from "@aranova/tracking-next/calendar";
|
|
417
|
+
|
|
418
|
+
const calendar = createCalendarWriteClient<AranovaCalendarKey>({
|
|
419
|
+
apiKey: process.env.ARANOVA_TRACKING_SECRET_KEY!,
|
|
420
|
+
endpoint: process.env.ARANOVA_TRACKING_ENDPOINT!,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const booking = await calendar.create({
|
|
424
|
+
calendar_key: "bookings",
|
|
425
|
+
start_at: "2026-10-14T13:00:00-04:00", // timed: start_at + duration_minutes (or end_at)
|
|
426
|
+
duration_minutes: 60,
|
|
427
|
+
// date: "2026-10-14", // all-day instead: a local date, no start/end/buffers
|
|
428
|
+
time_zone: "America/Toronto",
|
|
429
|
+
customer: { email, name, phone },
|
|
430
|
+
description: "<b>Arrive 1–2 PM</b>", // reaches Google exactly as sent
|
|
431
|
+
idempotency_key: submissionId,
|
|
432
|
+
metadata: body.attribution, // getBookingAttribution(), sent from the browser
|
|
433
|
+
});
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- **Forward the visit.** `getBookingAttribution()` (from `./calendar`, browser only) returns the
|
|
437
|
+
visit's `session_id`, `visitor_id`, `gclid`/`fbclid` and `utm_*`. It is what ties a booking to the
|
|
438
|
+
visitor's journey and form submission in the dashboard, and what missed-booking detection matches
|
|
439
|
+
on. It reads browser storage, so a custom route can't compute it: the browser sends it with the
|
|
440
|
+
form and the route passes it into `create({ metadata })`. `useBookingForm` merges it in for you
|
|
441
|
+
(your `metadata` keys win).
|
|
442
|
+
|
|
443
|
+
- **Overlaps are per calendar.** `ARANOVA_CALENDARS[].allowOverlaps` (from the CLI) says whether a
|
|
444
|
+
calendar accepts double bookings. When it is `false`, `create()` can throw `slot_unavailable`:
|
|
445
|
+
`isCalendarError(err, "slot_unavailable")`. Other codes: `calendar_not_connected`,
|
|
446
|
+
`calendar_disconnected`, `booking_not_found`, `booking_not_cancellable`,
|
|
447
|
+
`booking_not_reschedulable`, `manage_token_invalid`, `rate_limited`, `google_error`.
|
|
448
|
+
- **No server-side booking window.** Notice and how far ahead customers may book are your policy.
|
|
449
|
+
- **Your email can be the calendar invite.** The customer is always an attendee of the Google
|
|
450
|
+
event. Pass `send_invites: false` so Google sends nothing, then attach an ICS (`METHOD:REQUEST`)
|
|
451
|
+
with `UID` = `booking.ical_uid` and `ORGANIZER` = `booking.organizer_email`, so the customer's
|
|
452
|
+
copy is the same event as the calendar owner's. For `booking.all_day`, use
|
|
453
|
+
`DTSTART;VALUE=DATE`. `ical_uid` is null if Google hasn't confirmed the write; send a plain
|
|
454
|
+
confirmation then.
|
|
455
|
+
- **All-day bookings** are moved in Google Calendar, not through `reschedule()`.
|
|
456
|
+
|
|
457
|
+
`create()` returns a one-time `manage_token`. Put it in your confirmation email and
|
|
411
458
|
send it as `X-Aranova-Manage-Token` to reschedule or cancel — it authorises exactly that one
|
|
412
459
|
booking, which is why a public key alone still manages nothing.
|
|
413
460
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CalendarTransportConfig, B as BookingCreateInput, a as Booking, b as BookingRescheduleInput, c as BookingCancelInput } from './transport-
|
|
1
|
+
import { C as CalendarTransportConfig, B as BookingCreateInput, a as Booking, b as BookingRescheduleInput, c as BookingCancelInput } from './transport-BlXQZs9Z.mjs';
|
|
2
2
|
import 'zod/mini';
|
|
3
3
|
import './request-Doq36Rt0.mjs';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CalendarTransportConfig, B as BookingCreateInput, a as Booking, b as BookingRescheduleInput, c as BookingCancelInput } from './transport-
|
|
1
|
+
import { C as CalendarTransportConfig, B as BookingCreateInput, a as Booking, b as BookingRescheduleInput, c as BookingCancelInput } from './transport-CAZYrZbB.js';
|
|
2
2
|
import 'zod/mini';
|
|
3
3
|
import './request-Doq36Rt0.js';
|
|
4
4
|
|
package/dist/calendar.d.mts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
|
+
import { j as BookingMetadata, r as CalendarSummary, m as BusyResponse, C as CalendarTransportConfig, q as CalendarErrorCode, d as BusyInterval } from './transport-BlXQZs9Z.mjs';
|
|
2
|
+
export { e as BOOKING_SOURCES, f as BOOKING_STATUSES, a as Booking, g as BookingAttendee, c as BookingCancelInput, B as BookingCreateInput, h as BookingCustomer, i as BookingLocation, k as BookingReminders, b as BookingRescheduleInput, l as BookingStatus, n as CALENDAR_ERROR_CODES, o as CALENDAR_METADATA_SCHEMA_VERSION, p as CALENDAR_SYNC_STATUSES, s as CalendarSyncStatus, G as GOOGLE_SYNC_STATES, t as GoogleSyncState, J as JsonScalar, M as MAX_ATTENDEES, u as MAX_BOOKING_MINUTES, v as MAX_REMINDER_MINUTES, w as MAX_REMINDER_OVERRIDES, x as bookingAttendeeSchema, y as bookingCancelSchema, z as bookingCreateSchema, A as bookingCustomerSchema, D as bookingLocationNullableSchema, E as bookingLocationSchema, F as bookingMetadataSchema, H as bookingReminderSchema, I as bookingRemindersSchema, K as bookingRescheduleSchema } from './transport-BlXQZs9Z.mjs';
|
|
1
3
|
import { A as AranovaApiError } from './date-BHetnL7s.mjs';
|
|
2
4
|
export { f as formatDateInTz } from './date-BHetnL7s.mjs';
|
|
3
|
-
import { r as CalendarSummary, m as BusyResponse, C as CalendarTransportConfig, q as CalendarErrorCode, d as BusyInterval } from './transport-I_cMSFQ6.mjs';
|
|
4
|
-
export { e as BOOKING_SOURCES, f as BOOKING_STATUSES, a as Booking, g as BookingAttendee, c as BookingCancelInput, B as BookingCreateInput, h as BookingCustomer, i as BookingLocation, j as BookingMetadata, k as BookingReminders, b as BookingRescheduleInput, l as BookingStatus, n as CALENDAR_ERROR_CODES, o as CALENDAR_METADATA_SCHEMA_VERSION, p as CALENDAR_SYNC_STATUSES, s as CalendarSyncStatus, G as GOOGLE_SYNC_STATES, t as GoogleSyncState, J as JsonScalar, M as MAX_ATTENDEES, u as MAX_BOOKING_MINUTES, v as MAX_REMINDER_MINUTES, w as MAX_REMINDER_OVERRIDES, x as bookingAttendeeSchema, y as bookingCancelSchema, z as bookingCreateSchema, A as bookingCustomerSchema, D as bookingLocationNullableSchema, E as bookingLocationSchema, F as bookingMetadataSchema, H as bookingReminderSchema, I as bookingRemindersSchema, K as bookingRescheduleSchema } from './transport-I_cMSFQ6.mjs';
|
|
5
5
|
import 'zod/mini';
|
|
6
6
|
import './request-Doq36Rt0.mjs';
|
|
7
7
|
|
|
8
|
+
/** The metadata keys a browser can fill: which visit made the booking, and how it arrived. */
|
|
9
|
+
type BookingAttribution = Pick<BookingMetadata, "session_id" | "visitor_id" | "gclid" | "fbclid" | "utm_source" | "utm_medium" | "utm_campaign" | "utm_term" | "utm_content">;
|
|
10
|
+
/**
|
|
11
|
+
* This browser's visit and click attribution, for a booking's `metadata`.
|
|
12
|
+
*
|
|
13
|
+
* `useBookingForm` merges it in for you. A site posting to its own route (e.g. `/api/book`)
|
|
14
|
+
* sends it along from the browser and passes it into `create({ metadata })`: that is what lets
|
|
15
|
+
* the dashboard tie the booking to the visitor's journey. Only keys with a value are present.
|
|
16
|
+
*/
|
|
17
|
+
declare function getBookingAttribution(): BookingAttribution;
|
|
18
|
+
|
|
8
19
|
/**
|
|
9
20
|
* The browser-side calendar client — reads only.
|
|
10
21
|
*
|
|
@@ -109,4 +120,4 @@ declare function groupSlotsByDay(slots: Slot[], timeZone: string): {
|
|
|
109
120
|
slots: Slot[];
|
|
110
121
|
}[];
|
|
111
122
|
|
|
112
|
-
export { AranovaApiError, BusyInterval, type BusyQuery, BusyResponse, CalendarErrorCode, type CalendarReadClient, type CalendarReadClientConfig, CalendarSummary, CalendarTransportConfig, type ComputeSlotsOptions, type ComputeSlotsResult, type OpeningHours, type OpeningInterval, type Slot, computeSlots, createCalendarReadClient, durationMinutes, endOfDayInTz, formatSlotRange, groupSlotsByDay, instantFromWallClock, isCalendarError, localDateKey, offsetMinutesAt, resolveBrowserTimeZone, startOfDayInTz };
|
|
123
|
+
export { AranovaApiError, type BookingAttribution, BookingMetadata, BusyInterval, type BusyQuery, BusyResponse, CalendarErrorCode, type CalendarReadClient, type CalendarReadClientConfig, CalendarSummary, CalendarTransportConfig, type ComputeSlotsOptions, type ComputeSlotsResult, type OpeningHours, type OpeningInterval, type Slot, computeSlots, createCalendarReadClient, durationMinutes, endOfDayInTz, formatSlotRange, getBookingAttribution, groupSlotsByDay, instantFromWallClock, isCalendarError, localDateKey, offsetMinutesAt, resolveBrowserTimeZone, startOfDayInTz };
|
package/dist/calendar.d.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
|
+
import { j as BookingMetadata, r as CalendarSummary, m as BusyResponse, C as CalendarTransportConfig, q as CalendarErrorCode, d as BusyInterval } from './transport-CAZYrZbB.js';
|
|
2
|
+
export { e as BOOKING_SOURCES, f as BOOKING_STATUSES, a as Booking, g as BookingAttendee, c as BookingCancelInput, B as BookingCreateInput, h as BookingCustomer, i as BookingLocation, k as BookingReminders, b as BookingRescheduleInput, l as BookingStatus, n as CALENDAR_ERROR_CODES, o as CALENDAR_METADATA_SCHEMA_VERSION, p as CALENDAR_SYNC_STATUSES, s as CalendarSyncStatus, G as GOOGLE_SYNC_STATES, t as GoogleSyncState, J as JsonScalar, M as MAX_ATTENDEES, u as MAX_BOOKING_MINUTES, v as MAX_REMINDER_MINUTES, w as MAX_REMINDER_OVERRIDES, x as bookingAttendeeSchema, y as bookingCancelSchema, z as bookingCreateSchema, A as bookingCustomerSchema, D as bookingLocationNullableSchema, E as bookingLocationSchema, F as bookingMetadataSchema, H as bookingReminderSchema, I as bookingRemindersSchema, K as bookingRescheduleSchema } from './transport-CAZYrZbB.js';
|
|
1
3
|
import { A as AranovaApiError } from './date-BHetnL7s.js';
|
|
2
4
|
export { f as formatDateInTz } from './date-BHetnL7s.js';
|
|
3
|
-
import { r as CalendarSummary, m as BusyResponse, C as CalendarTransportConfig, q as CalendarErrorCode, d as BusyInterval } from './transport-CDmGOiCq.js';
|
|
4
|
-
export { e as BOOKING_SOURCES, f as BOOKING_STATUSES, a as Booking, g as BookingAttendee, c as BookingCancelInput, B as BookingCreateInput, h as BookingCustomer, i as BookingLocation, j as BookingMetadata, k as BookingReminders, b as BookingRescheduleInput, l as BookingStatus, n as CALENDAR_ERROR_CODES, o as CALENDAR_METADATA_SCHEMA_VERSION, p as CALENDAR_SYNC_STATUSES, s as CalendarSyncStatus, G as GOOGLE_SYNC_STATES, t as GoogleSyncState, J as JsonScalar, M as MAX_ATTENDEES, u as MAX_BOOKING_MINUTES, v as MAX_REMINDER_MINUTES, w as MAX_REMINDER_OVERRIDES, x as bookingAttendeeSchema, y as bookingCancelSchema, z as bookingCreateSchema, A as bookingCustomerSchema, D as bookingLocationNullableSchema, E as bookingLocationSchema, F as bookingMetadataSchema, H as bookingReminderSchema, I as bookingRemindersSchema, K as bookingRescheduleSchema } from './transport-CDmGOiCq.js';
|
|
5
5
|
import 'zod/mini';
|
|
6
6
|
import './request-Doq36Rt0.js';
|
|
7
7
|
|
|
8
|
+
/** The metadata keys a browser can fill: which visit made the booking, and how it arrived. */
|
|
9
|
+
type BookingAttribution = Pick<BookingMetadata, "session_id" | "visitor_id" | "gclid" | "fbclid" | "utm_source" | "utm_medium" | "utm_campaign" | "utm_term" | "utm_content">;
|
|
10
|
+
/**
|
|
11
|
+
* This browser's visit and click attribution, for a booking's `metadata`.
|
|
12
|
+
*
|
|
13
|
+
* `useBookingForm` merges it in for you. A site posting to its own route (e.g. `/api/book`)
|
|
14
|
+
* sends it along from the browser and passes it into `create({ metadata })`: that is what lets
|
|
15
|
+
* the dashboard tie the booking to the visitor's journey. Only keys with a value are present.
|
|
16
|
+
*/
|
|
17
|
+
declare function getBookingAttribution(): BookingAttribution;
|
|
18
|
+
|
|
8
19
|
/**
|
|
9
20
|
* The browser-side calendar client — reads only.
|
|
10
21
|
*
|
|
@@ -109,4 +120,4 @@ declare function groupSlotsByDay(slots: Slot[], timeZone: string): {
|
|
|
109
120
|
slots: Slot[];
|
|
110
121
|
}[];
|
|
111
122
|
|
|
112
|
-
export { AranovaApiError, BusyInterval, type BusyQuery, BusyResponse, CalendarErrorCode, type CalendarReadClient, type CalendarReadClientConfig, CalendarSummary, CalendarTransportConfig, type ComputeSlotsOptions, type ComputeSlotsResult, type OpeningHours, type OpeningInterval, type Slot, computeSlots, createCalendarReadClient, durationMinutes, endOfDayInTz, formatSlotRange, groupSlotsByDay, instantFromWallClock, isCalendarError, localDateKey, offsetMinutesAt, resolveBrowserTimeZone, startOfDayInTz };
|
|
123
|
+
export { AranovaApiError, type BookingAttribution, BookingMetadata, BusyInterval, type BusyQuery, BusyResponse, CalendarErrorCode, type CalendarReadClient, type CalendarReadClientConfig, CalendarSummary, CalendarTransportConfig, type ComputeSlotsOptions, type ComputeSlotsResult, type OpeningHours, type OpeningInterval, type Slot, computeSlots, createCalendarReadClient, durationMinutes, endOfDayInTz, formatSlotRange, getBookingAttribution, groupSlotsByDay, instantFromWallClock, isCalendarError, localDateKey, offsetMinutesAt, resolveBrowserTimeZone, startOfDayInTz };
|
package/dist/calendar.js
CHANGED
|
@@ -57,6 +57,7 @@ __export(calendar_exports, {
|
|
|
57
57
|
endOfDayInTz: () => endOfDayInTz,
|
|
58
58
|
formatDateInTz: () => formatDateInTz,
|
|
59
59
|
formatSlotRange: () => formatSlotRange,
|
|
60
|
+
getBookingAttribution: () => getBookingAttribution,
|
|
60
61
|
groupSlotsByDay: () => groupSlotsByDay,
|
|
61
62
|
instantFromWallClock: () => instantFromWallClock,
|
|
62
63
|
isCalendarError: () => isCalendarError,
|
|
@@ -67,6 +68,135 @@ __export(calendar_exports, {
|
|
|
67
68
|
});
|
|
68
69
|
module.exports = __toCommonJS(calendar_exports);
|
|
69
70
|
|
|
71
|
+
// ../tracking-core/src/tracking.ts
|
|
72
|
+
var TRACKING_PARAM_KEYS = [
|
|
73
|
+
"gclid",
|
|
74
|
+
// Google's iOS/Safari replacement click IDs — issued when privacy features
|
|
75
|
+
// withhold gclid (wbraid: web-to-web, gbraid: app-to-web). First-class
|
|
76
|
+
// citizens: captured, persisted, and attributed exactly like gclid.
|
|
77
|
+
"wbraid",
|
|
78
|
+
"gbraid",
|
|
79
|
+
// Yelp Ads click id (40 hex chars), appended to ad-click URLs.
|
|
80
|
+
"ylpcid",
|
|
81
|
+
"fbclid",
|
|
82
|
+
"utm_source",
|
|
83
|
+
"utm_medium",
|
|
84
|
+
"utm_campaign",
|
|
85
|
+
"utm_term",
|
|
86
|
+
"utm_content"
|
|
87
|
+
];
|
|
88
|
+
function createEmptyTrackingParams() {
|
|
89
|
+
return {
|
|
90
|
+
gclid: null,
|
|
91
|
+
wbraid: null,
|
|
92
|
+
gbraid: null,
|
|
93
|
+
ylpcid: null,
|
|
94
|
+
fbclid: null,
|
|
95
|
+
utm_source: null,
|
|
96
|
+
utm_medium: null,
|
|
97
|
+
utm_campaign: null,
|
|
98
|
+
utm_term: null,
|
|
99
|
+
utm_content: null
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function normalizeTrackingCookieValue(value) {
|
|
103
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
104
|
+
}
|
|
105
|
+
function getTrackingParamsFromCookieReader(readCookie) {
|
|
106
|
+
return TRACKING_PARAM_KEYS.reduce((params, key) => {
|
|
107
|
+
params[key] = normalizeTrackingCookieValue(readCookie(key));
|
|
108
|
+
return params;
|
|
109
|
+
}, createEmptyTrackingParams());
|
|
110
|
+
}
|
|
111
|
+
var FALLBACK_STORAGE_PREFIX = "_aranova_track_";
|
|
112
|
+
function fallbackKey(name) {
|
|
113
|
+
return `${FALLBACK_STORAGE_PREFIX}${name}`;
|
|
114
|
+
}
|
|
115
|
+
function readCookieValue(name) {
|
|
116
|
+
if (typeof document !== "undefined") {
|
|
117
|
+
const cookies = document.cookie ? document.cookie.split("; ") : [];
|
|
118
|
+
const match = cookies.find((cookie) => cookie.startsWith(`${name}=`));
|
|
119
|
+
if (match) {
|
|
120
|
+
const [, rawValue = ""] = match.split("=");
|
|
121
|
+
return normalizeTrackingCookieValue(decodeURIComponent(rawValue));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (typeof window !== "undefined") {
|
|
125
|
+
try {
|
|
126
|
+
const stored = window.localStorage.getItem(fallbackKey(name));
|
|
127
|
+
if (stored) return normalizeTrackingCookieValue(decodeURIComponent(stored));
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function getCookieValueFromDocument(key) {
|
|
134
|
+
return readCookieValue(key);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ../tracking-core/src/session.ts
|
|
138
|
+
var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
|
|
139
|
+
var SESSION_STORAGE_KEY = "aranova_tracking_session";
|
|
140
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
141
|
+
function readLocalStorage(key) {
|
|
142
|
+
try {
|
|
143
|
+
return window.localStorage.getItem(key);
|
|
144
|
+
} catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function peekVisitorId() {
|
|
149
|
+
if (typeof window === "undefined") return null;
|
|
150
|
+
const existing = readLocalStorage(VISITOR_STORAGE_KEY);
|
|
151
|
+
return existing && existing.length > 0 ? existing : null;
|
|
152
|
+
}
|
|
153
|
+
function peekSessionId(now = Date.now()) {
|
|
154
|
+
if (typeof window === "undefined") return null;
|
|
155
|
+
const raw = readLocalStorage(SESSION_STORAGE_KEY);
|
|
156
|
+
if (!raw) return null;
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(raw);
|
|
159
|
+
if (typeof parsed.id !== "string" || typeof parsed.last_event_at !== "number") return null;
|
|
160
|
+
return now - parsed.last_event_at <= SESSION_IDLE_MS ? parsed.id : null;
|
|
161
|
+
} catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ../tracking-core/src/resources/calendar/attribution.ts
|
|
167
|
+
var LIMITS = {
|
|
168
|
+
session_id: 64,
|
|
169
|
+
visitor_id: 64,
|
|
170
|
+
gclid: 256,
|
|
171
|
+
fbclid: 256,
|
|
172
|
+
utm_source: 128,
|
|
173
|
+
utm_medium: 128,
|
|
174
|
+
utm_campaign: 128,
|
|
175
|
+
utm_term: 128,
|
|
176
|
+
utm_content: 128
|
|
177
|
+
};
|
|
178
|
+
function getBookingAttribution() {
|
|
179
|
+
if (typeof window === "undefined") return {};
|
|
180
|
+
const params = getTrackingParamsFromCookieReader(getCookieValueFromDocument);
|
|
181
|
+
const values = {
|
|
182
|
+
session_id: peekSessionId(),
|
|
183
|
+
visitor_id: peekVisitorId(),
|
|
184
|
+
gclid: params.gclid,
|
|
185
|
+
fbclid: params.fbclid,
|
|
186
|
+
utm_source: params.utm_source,
|
|
187
|
+
utm_medium: params.utm_medium,
|
|
188
|
+
utm_campaign: params.utm_campaign,
|
|
189
|
+
utm_term: params.utm_term,
|
|
190
|
+
utm_content: params.utm_content
|
|
191
|
+
};
|
|
192
|
+
const attribution = {};
|
|
193
|
+
for (const key of Object.keys(LIMITS)) {
|
|
194
|
+
const value = values[key];
|
|
195
|
+
if (value) attribution[key] = value.slice(0, LIMITS[key]);
|
|
196
|
+
}
|
|
197
|
+
return attribution;
|
|
198
|
+
}
|
|
199
|
+
|
|
70
200
|
// ../tracking-core/src/capabilities.ts
|
|
71
201
|
var registered = /* @__PURE__ */ new Set();
|
|
72
202
|
function registerCapability(capability) {
|
|
@@ -280,22 +410,27 @@ var zBookingRescheduleSchema = z.strictObject({
|
|
|
280
410
|
time_zone: z.string().check(z.minLength(1), z.maxLength(64), z.regex(/^[A-Za-z0-9_+\-\/]+$/))
|
|
281
411
|
});
|
|
282
412
|
var zBookingResponse = z.strictObject({
|
|
413
|
+
all_day: z.boolean(),
|
|
283
414
|
appointment_type: z.nullable(z.string()),
|
|
284
415
|
calendar_key: z.string(),
|
|
416
|
+
created_at: z.iso.datetime({ offset: true }),
|
|
285
417
|
customer: zBookingCustomerOutput,
|
|
286
418
|
end_at: z.iso.datetime({ offset: true }),
|
|
419
|
+
google_event_id: z.nullable(z.string()),
|
|
287
420
|
google_html_link: z.nullable(z.string()),
|
|
288
|
-
google_sync_state: z.
|
|
421
|
+
google_sync_state: z.enum(["queued", "synced", "uncertain", "failed", "drifted"]),
|
|
422
|
+
ical_uid: z.nullable(z.string()),
|
|
289
423
|
id: z.string(),
|
|
290
424
|
manage_token: z.nullable(z.string()),
|
|
291
425
|
meet_url: z.nullable(z.string()),
|
|
426
|
+
organizer_email: z.nullable(z.string()),
|
|
292
427
|
start_at: z.iso.datetime({ offset: true }),
|
|
293
|
-
status: z.
|
|
428
|
+
status: z.enum(["pending", "confirmed", "cancelled", "no_show"]),
|
|
294
429
|
time_zone: z.string(),
|
|
295
430
|
title: z.string()
|
|
296
431
|
});
|
|
297
432
|
var zBookingSource = z.enum(["website", "phone", "walk_in", "import", "operator"]);
|
|
298
|
-
var
|
|
433
|
+
var zBookingMetadataInput = z.strictObject({
|
|
299
434
|
custom: z.nullish(
|
|
300
435
|
z.record(z.string(), z.nullable(z.union([z.string(), z.int(), z.number(), z.boolean()])))
|
|
301
436
|
),
|
|
@@ -315,6 +450,40 @@ var zBookingMetadata = z.strictObject({
|
|
|
315
450
|
utm_term: z.nullish(z.string().check(z.maxLength(128))),
|
|
316
451
|
visitor_id: z.nullish(z.string().check(z.maxLength(64)))
|
|
317
452
|
});
|
|
453
|
+
var zBusySource = z.enum(["google", "booking"]);
|
|
454
|
+
var zBusyInterval = z.strictObject({
|
|
455
|
+
end_at: z.iso.datetime({ offset: true }),
|
|
456
|
+
source: zBusySource,
|
|
457
|
+
start_at: z.iso.datetime({ offset: true })
|
|
458
|
+
});
|
|
459
|
+
var zBusyWindow = z.strictObject({
|
|
460
|
+
since: z.iso.datetime({ offset: true }),
|
|
461
|
+
until: z.iso.datetime({ offset: true })
|
|
462
|
+
});
|
|
463
|
+
var zBusyResponse = z.strictObject({
|
|
464
|
+
busy: z.array(zBusyInterval),
|
|
465
|
+
calendar_key: z.string(),
|
|
466
|
+
generated_at: z.iso.datetime({ offset: true }),
|
|
467
|
+
time_zone: z.string(),
|
|
468
|
+
window: zBusyWindow
|
|
469
|
+
});
|
|
470
|
+
var zCalendarSummary = z.strictObject({
|
|
471
|
+
allow_overlaps: z.boolean(),
|
|
472
|
+
calendar_key: z.string(),
|
|
473
|
+
connected_at: z.nullable(z.iso.datetime({ offset: true })),
|
|
474
|
+
google_account_email: z.nullable(z.string()),
|
|
475
|
+
granted_scopes: z.nullable(z.array(z.string())),
|
|
476
|
+
label: z.string(),
|
|
477
|
+
status: z.enum([
|
|
478
|
+
"pending_invite",
|
|
479
|
+
"awaiting_calendar_selection",
|
|
480
|
+
"connected",
|
|
481
|
+
"needs_reconnect",
|
|
482
|
+
"revoked",
|
|
483
|
+
"disabled"
|
|
484
|
+
]),
|
|
485
|
+
time_zone: z.string()
|
|
486
|
+
});
|
|
318
487
|
var zCustomLocation = z.strictObject({
|
|
319
488
|
kind: z.literal("custom"),
|
|
320
489
|
text: z.string().check(z.maxLength(1024))
|
|
@@ -334,9 +503,19 @@ var zErrorCode = z.enum([
|
|
|
334
503
|
"bad_request",
|
|
335
504
|
"not_found",
|
|
336
505
|
"conflict",
|
|
506
|
+
"gone",
|
|
337
507
|
"incompatible_payload",
|
|
338
508
|
"repeat_confirmation_required",
|
|
339
509
|
"idempotency_conflict",
|
|
510
|
+
"slot_unavailable",
|
|
511
|
+
"calendar_not_connected",
|
|
512
|
+
"calendar_disconnected",
|
|
513
|
+
"booking_not_found",
|
|
514
|
+
"booking_not_cancellable",
|
|
515
|
+
"booking_not_reschedulable",
|
|
516
|
+
"manage_token_invalid",
|
|
517
|
+
"google_error",
|
|
518
|
+
"calendar_not_owned",
|
|
340
519
|
"validation_error",
|
|
341
520
|
"unknown_service",
|
|
342
521
|
"invalid_sale_properties",
|
|
@@ -371,15 +550,16 @@ var zBookingCreateSchema = z.strictObject({
|
|
|
371
550
|
buffer_before_minutes: z._default(z.optional(z.int().check(z.gte(0), z.lte(1440))), 0),
|
|
372
551
|
calendar_key: z.string().check(z.minLength(1), z.maxLength(64)),
|
|
373
552
|
customer: zBookingCustomerInput,
|
|
553
|
+
date: z.nullish(z.iso.date()),
|
|
374
554
|
description: z.nullish(z.string().check(z.maxLength(8e3))),
|
|
375
555
|
duration_minutes: z.nullish(z.int().check(z.gt(0), z.lte(1440))),
|
|
376
556
|
end_at: z.nullish(z.iso.datetime({ offset: true })),
|
|
377
557
|
idempotency_key: z.nullish(z.string().check(z.maxLength(128))),
|
|
378
558
|
location: z.nullish(z.union([zMeetLocation, zPhysicalLocation, zPhoneLocation, zCustomLocation])),
|
|
379
|
-
metadata: z.nullish(
|
|
559
|
+
metadata: z.nullish(zBookingMetadataInput),
|
|
380
560
|
reminders: z.nullish(zBookingReminders),
|
|
381
561
|
send_invites: z._default(z.optional(z.boolean()), true),
|
|
382
|
-
start_at: z.iso.datetime({ offset: true }),
|
|
562
|
+
start_at: z.nullish(z.iso.datetime({ offset: true })),
|
|
383
563
|
time_zone: z.string().check(z.minLength(1), z.maxLength(64), z.regex(/^[A-Za-z0-9_+\-\/]+$/)),
|
|
384
564
|
title: z.nullish(z.string().check(z.maxLength(300)))
|
|
385
565
|
});
|
|
@@ -413,22 +593,17 @@ var MAX_REMINDER_OVERRIDES = 5;
|
|
|
413
593
|
var MAX_REMINDER_MINUTES = 40320;
|
|
414
594
|
var MAX_BOOKING_MINUTES = 1440;
|
|
415
595
|
var BOOKING_SOURCES = ["website", "phone", "walk_in", "import", "operator"];
|
|
416
|
-
var BOOKING_STATUSES =
|
|
417
|
-
var GOOGLE_SYNC_STATES =
|
|
418
|
-
var CALENDAR_SYNC_STATUSES =
|
|
419
|
-
"pending_invite",
|
|
420
|
-
"awaiting_calendar_selection",
|
|
421
|
-
"connected",
|
|
422
|
-
"needs_reconnect",
|
|
423
|
-
"revoked",
|
|
424
|
-
"disabled"
|
|
425
|
-
];
|
|
596
|
+
var BOOKING_STATUSES = zBookingResponse.shape.status.options;
|
|
597
|
+
var GOOGLE_SYNC_STATES = zBookingResponse.shape.google_sync_state.options;
|
|
598
|
+
var CALENDAR_SYNC_STATUSES = zCalendarSummary.shape.status.options;
|
|
426
599
|
var CALENDAR_ERROR_CODES = [
|
|
427
600
|
"slot_unavailable",
|
|
428
601
|
"calendar_not_connected",
|
|
429
602
|
"calendar_disconnected",
|
|
430
603
|
"booking_not_found",
|
|
431
|
-
"
|
|
604
|
+
"booking_not_cancellable",
|
|
605
|
+
"booking_not_reschedulable",
|
|
606
|
+
"manage_token_invalid",
|
|
432
607
|
"rate_limited",
|
|
433
608
|
"google_error"
|
|
434
609
|
];
|
|
@@ -449,7 +624,7 @@ var ianaTz = z2.string().check(
|
|
|
449
624
|
var bookingAttendeeSchema = zBookingAttendee;
|
|
450
625
|
var bookingReminderSchema = zBookingReminder;
|
|
451
626
|
var bookingCustomerSchema = zBookingCustomerInput;
|
|
452
|
-
var bookingMetadataSchema = z2.extend(
|
|
627
|
+
var bookingMetadataSchema = z2.extend(zBookingMetadataInput, {
|
|
453
628
|
custom: z2.nullish(
|
|
454
629
|
z2.record(z2.string(), z2.union([z2.string(), z2.int(), z2.number(), z2.boolean(), z2.null()]))
|
|
455
630
|
)
|
|
@@ -478,6 +653,24 @@ function refineEndXor(value, ctx) {
|
|
|
478
653
|
addIssue(ctx, ["end_at"], "end_at must be after start_at");
|
|
479
654
|
}
|
|
480
655
|
}
|
|
656
|
+
function refineStartXor(value, ctx) {
|
|
657
|
+
const hasStart = value.start_at !== void 0 && value.start_at !== null;
|
|
658
|
+
const hasDate = value.date !== void 0 && value.date !== null;
|
|
659
|
+
if (hasStart === hasDate) {
|
|
660
|
+
addIssue(ctx, ["start_at"], "provide exactly one of start_at or date");
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (hasStart) {
|
|
664
|
+
refineEndXor(value, ctx);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (value.end_at != null || value.duration_minutes != null) {
|
|
668
|
+
addIssue(ctx, ["date"], "an all-day booking (date) takes no end_at or duration_minutes");
|
|
669
|
+
}
|
|
670
|
+
if (value.buffer_before_minutes || value.buffer_after_minutes) {
|
|
671
|
+
addIssue(ctx, ["date"], "an all-day booking (date) takes no buffers");
|
|
672
|
+
}
|
|
673
|
+
}
|
|
481
674
|
var bookingRemindersSchema = z2.extend(zBookingReminders, {
|
|
482
675
|
overrides: z2._default(
|
|
483
676
|
z2.array(bookingReminderSchema).check(z2.maxLength(MAX_REMINDER_OVERRIDES)),
|
|
@@ -496,7 +689,7 @@ var bookingCreateSchema = z2.extend(zBookingCreateSchema, {
|
|
|
496
689
|
// our compositions into its own output.
|
|
497
690
|
reminders: z2.nullish(bookingRemindersSchema),
|
|
498
691
|
metadata: z2.nullish(bookingMetadataSchema)
|
|
499
|
-
}).check((ctx) =>
|
|
692
|
+
}).check((ctx) => refineStartXor(ctx.value, ctx));
|
|
500
693
|
var bookingRescheduleSchema = z2.extend(zBookingRescheduleSchema, { time_zone: ianaTz }).check((ctx) => refineEndXor(ctx.value, ctx));
|
|
501
694
|
var bookingCancelSchema = zBookingCancelSchema;
|
|
502
695
|
|
|
@@ -614,6 +807,7 @@ function groupSlotsByDay(slots, timeZone) {
|
|
|
614
807
|
endOfDayInTz,
|
|
615
808
|
formatDateInTz,
|
|
616
809
|
formatSlotRange,
|
|
810
|
+
getBookingAttribution,
|
|
617
811
|
groupSlotsByDay,
|
|
618
812
|
instantFromWallClock,
|
|
619
813
|
isCalendarError,
|