@peektravel/app-utilities 0.1.5 → 0.2.2
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 +34 -0
- package/dist/index.cjs +103 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -6
- package/dist/index.d.ts +98 -6
- package/dist/index.js +102 -7
- package/dist/index.js.map +1 -1
- package/docs/webhooks.md +141 -0
- package/llms.txt +18 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -196,6 +196,39 @@ both `import` and `require` consumers (including the Node 22 / CommonJS Firebase
|
|
|
196
196
|
Functions runtime) resolve correctly. Its only runtime dependency is
|
|
197
197
|
`jsonwebtoken`.
|
|
198
198
|
|
|
199
|
+
## Webhooks
|
|
200
|
+
|
|
201
|
+
Receiver apps can consume Peek **booking** and **waiver** webhooks without
|
|
202
|
+
hand-writing a payload parser. Each has a pure parser (construct nothing — no
|
|
203
|
+
auth/network) that returns a clean model:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import {
|
|
207
|
+
parseBookingWebhook,
|
|
208
|
+
parseWaiverWebhook,
|
|
209
|
+
type Booking,
|
|
210
|
+
type Waiver,
|
|
211
|
+
} from "@peektravel/app-utilities";
|
|
212
|
+
|
|
213
|
+
app.post("/booking-webhook", (req, res) => {
|
|
214
|
+
const booking: Booking = parseBookingWebhook(req.body);
|
|
215
|
+
res.sendStatus(200);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
app.post("/waiver-webhook", (req, res) => {
|
|
219
|
+
const waiver: Waiver = parseWaiverWebhook(req.body);
|
|
220
|
+
res.sendStatus(200);
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Both tolerate the delivery envelope / a bare node / a JSON string and never throw
|
|
225
|
+
on malformed input. They differ on registration: a **booking** webhook's payload
|
|
226
|
+
shape is set by a GraphQL query configured **once in an external system** (the
|
|
227
|
+
App Store `broadcast_to_url` config) — this package documents and drift-guards
|
|
228
|
+
the exact query to paste there — whereas a **waiver** webhook has a fixed payload,
|
|
229
|
+
so you just subscribe to its event with no query. **The query to register and the
|
|
230
|
+
full guide: [`docs/webhooks.md`](docs/webhooks.md) (shipped).**
|
|
231
|
+
|
|
199
232
|
## UI components (`/ui`)
|
|
200
233
|
|
|
201
234
|
The package also ships framework-agnostic **Web Components** ported from the Peek
|
|
@@ -326,6 +359,7 @@ src/ui/ Web Components + Odyssey CSS (barrel: src/ui/index.ts
|
|
|
326
359
|
test/ vitest unit tests (test/ui/* run under happy-dom)
|
|
327
360
|
examples/ui-gallery.html component gallery (npm run sample)
|
|
328
361
|
dist/ build output (generated, git-ignored)
|
|
362
|
+
docs/webhooks.md booking-webhook consumer guide (shipped)
|
|
329
363
|
docs/internal/ maintainer docs (ARCHITECTURE.md — not shipped)
|
|
330
364
|
llms.txt AI-agent quickstart (shipped in the package)
|
|
331
365
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
|
|
|
25
25
|
|
|
26
26
|
// src/internal/gateway-endpoints.ts
|
|
27
27
|
var SALES_ENDPOINT = "sales";
|
|
28
|
+
var V2_EXTENDABLE_SLUG = "peek_backoffice_api-v1";
|
|
28
29
|
|
|
29
30
|
// src/internal/account-users/account-user-converter.ts
|
|
30
31
|
var ACTIVE_STATUS = "ACTIVE";
|
|
@@ -1841,14 +1842,19 @@ var GraphQLClient = class {
|
|
|
1841
1842
|
throw new RateLimitError();
|
|
1842
1843
|
}
|
|
1843
1844
|
endpoint(endpointName) {
|
|
1844
|
-
|
|
1845
|
+
const { baseUrl, appId, endpointPathPrefix } = this.options;
|
|
1846
|
+
const prefix = endpointPathPrefix ? `${endpointPathPrefix}/` : "";
|
|
1847
|
+
return `${baseUrl}/${appId}/${prefix}${endpointName}`;
|
|
1845
1848
|
}
|
|
1846
1849
|
buildHeaders() {
|
|
1847
|
-
|
|
1850
|
+
const headers = {
|
|
1848
1851
|
"X-Peek-Auth": `Bearer ${this.options.getToken()}`,
|
|
1849
|
-
"pk-api-key": this.options.gatewayKey,
|
|
1850
1852
|
"Content-Type": "application/json"
|
|
1851
1853
|
};
|
|
1854
|
+
if (this.options.gatewayKey) {
|
|
1855
|
+
headers["pk-api-key"] = this.options.gatewayKey;
|
|
1856
|
+
}
|
|
1857
|
+
return headers;
|
|
1852
1858
|
}
|
|
1853
1859
|
};
|
|
1854
1860
|
|
|
@@ -2908,6 +2914,7 @@ var noopLogger = {
|
|
|
2908
2914
|
|
|
2909
2915
|
// src/peek-access-service.ts
|
|
2910
2916
|
var DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
|
|
2917
|
+
var DEFAULT_V2_BASE_URL = "https://app-registry.peeklabs.com/installations-api";
|
|
2911
2918
|
var DEFAULT_TOKEN_TTL_SECONDS = 3600;
|
|
2912
2919
|
var DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS = 60;
|
|
2913
2920
|
var DEFAULT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
|
|
@@ -2926,11 +2933,12 @@ var PeekAccessService = class {
|
|
|
2926
2933
|
bookingService;
|
|
2927
2934
|
reviewService;
|
|
2928
2935
|
constructor(config) {
|
|
2936
|
+
const isV2 = config.mode === "v2";
|
|
2929
2937
|
requireNonEmpty(config.installId, "installId");
|
|
2930
2938
|
requireNonEmpty(config.jwtSecret, "jwtSecret");
|
|
2931
2939
|
requireNonEmpty(config.issuer, "issuer");
|
|
2932
2940
|
requireNonEmpty(config.appId, "appId");
|
|
2933
|
-
requireNonEmpty(config.gatewayKey, "gatewayKey");
|
|
2941
|
+
if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey");
|
|
2934
2942
|
const logger = config.logger ?? noopLogger;
|
|
2935
2943
|
const tokens = new TokenManager({
|
|
2936
2944
|
secret: config.jwtSecret,
|
|
@@ -2939,14 +2947,16 @@ var PeekAccessService = class {
|
|
|
2939
2947
|
ttlSeconds: config.tokenTtlSeconds ?? DEFAULT_TOKEN_TTL_SECONDS,
|
|
2940
2948
|
leewaySeconds: config.tokenRefreshLeewaySeconds ?? DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS
|
|
2941
2949
|
});
|
|
2950
|
+
const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
|
|
2942
2951
|
this.client = new GraphQLClient({
|
|
2943
|
-
baseUrl: config.baseUrl ??
|
|
2952
|
+
baseUrl: config.baseUrl ?? defaultBaseUrl,
|
|
2944
2953
|
appId: config.appId,
|
|
2945
2954
|
gatewayKey: config.gatewayKey,
|
|
2946
2955
|
getToken: () => tokens.getToken(),
|
|
2947
2956
|
retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
|
|
2948
2957
|
logger,
|
|
2949
|
-
fetchFn: config.fetch ?? globalThis.fetch
|
|
2958
|
+
fetchFn: config.fetch ?? globalThis.fetch,
|
|
2959
|
+
endpointPathPrefix: isV2 ? V2_EXTENDABLE_SLUG : void 0
|
|
2950
2960
|
});
|
|
2951
2961
|
this.productServiceOptions = {
|
|
2952
2962
|
itemOptionsPageSize: config.itemOptionsPageSize
|
|
@@ -3078,6 +3088,91 @@ function requireNonEmpty(value, name) {
|
|
|
3078
3088
|
}
|
|
3079
3089
|
}
|
|
3080
3090
|
|
|
3091
|
+
// src/internal/bookings/booking-webhook.ts
|
|
3092
|
+
var PAYLOAD_BOOKING_KEY = "booking";
|
|
3093
|
+
var VALUE_OPEN_TOKEN = "value {";
|
|
3094
|
+
var PRICE_BREAKDOWN_KEYS = [
|
|
3095
|
+
"convenienceFee",
|
|
3096
|
+
"deposit",
|
|
3097
|
+
"discount",
|
|
3098
|
+
"discountedPrice",
|
|
3099
|
+
"fees",
|
|
3100
|
+
"flatPartnerFee",
|
|
3101
|
+
"price",
|
|
3102
|
+
"retailPrice",
|
|
3103
|
+
"taxes"
|
|
3104
|
+
];
|
|
3105
|
+
buildMaximalWebhookQuery();
|
|
3106
|
+
function buildMaximalWebhookQuery() {
|
|
3107
|
+
const fields = bookingQueryFields.replace(
|
|
3108
|
+
VALUE_OPEN_TOKEN,
|
|
3109
|
+
`${VALUE_OPEN_TOKEN} ${PRICE_BREAKDOWN_FIELDS}`
|
|
3110
|
+
);
|
|
3111
|
+
return `{ ${fields} ${bookingGuestsFields} }`.replace(/\s+/g, " ").trim();
|
|
3112
|
+
}
|
|
3113
|
+
function parseBookingWebhook(payload) {
|
|
3114
|
+
const node = extractBookingNode(payload);
|
|
3115
|
+
return fromBookingNode(node, hasGuests(node), hasPriceBreakdown(node));
|
|
3116
|
+
}
|
|
3117
|
+
function extractBookingNode(payload) {
|
|
3118
|
+
let body = payload;
|
|
3119
|
+
if (typeof body === "string") {
|
|
3120
|
+
try {
|
|
3121
|
+
body = JSON.parse(body);
|
|
3122
|
+
} catch {
|
|
3123
|
+
return {};
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
if (!body || typeof body !== "object") return {};
|
|
3127
|
+
const record = body;
|
|
3128
|
+
const inner = record[PAYLOAD_BOOKING_KEY];
|
|
3129
|
+
if (inner && typeof inner === "object") return inner;
|
|
3130
|
+
return record;
|
|
3131
|
+
}
|
|
3132
|
+
function hasGuests(node) {
|
|
3133
|
+
return Array.isArray(node.bookingGuests);
|
|
3134
|
+
}
|
|
3135
|
+
function hasPriceBreakdown(node) {
|
|
3136
|
+
const value = node.value;
|
|
3137
|
+
if (!value) return false;
|
|
3138
|
+
return PRICE_BREAKDOWN_KEYS.some((key) => key in value);
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// src/internal/waivers/waiver-webhook.ts
|
|
3142
|
+
var PAYLOAD_WAIVER_KEY = "waiver";
|
|
3143
|
+
function parseWaiverWebhook(payload) {
|
|
3144
|
+
return fromWaiverNode(extractWaiverNode(payload));
|
|
3145
|
+
}
|
|
3146
|
+
function fromWaiverNode(node) {
|
|
3147
|
+
const data = node ?? {};
|
|
3148
|
+
const waiverData = data.waiver_data ?? {};
|
|
3149
|
+
return {
|
|
3150
|
+
templateId: data.agreement_template_id || "",
|
|
3151
|
+
bookingId: data.booking_id || "",
|
|
3152
|
+
fileUrl: data.file_url || "",
|
|
3153
|
+
signedAt: data.signed_at || "",
|
|
3154
|
+
isSignedByGuardian: Boolean(data.signed_by_guardian),
|
|
3155
|
+
guestName: waiverData.participant_name || null,
|
|
3156
|
+
isOptinMarketing: Boolean(waiverData.participant_optin_marketing),
|
|
3157
|
+
isOptinSms: Boolean(waiverData.participant_optin_sms)
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
function extractWaiverNode(payload) {
|
|
3161
|
+
let body = payload;
|
|
3162
|
+
if (typeof body === "string") {
|
|
3163
|
+
try {
|
|
3164
|
+
body = JSON.parse(body);
|
|
3165
|
+
} catch {
|
|
3166
|
+
return {};
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
if (!body || typeof body !== "object") return {};
|
|
3170
|
+
const record = body;
|
|
3171
|
+
const inner = record[PAYLOAD_WAIVER_KEY];
|
|
3172
|
+
if (inner && typeof inner === "object") return inner;
|
|
3173
|
+
return record;
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3081
3176
|
exports.ADD_ON_PRODUCT_TYPE = ADD_ON_PRODUCT_TYPE;
|
|
3082
3177
|
exports.AccountUserService = AccountUserService;
|
|
3083
3178
|
exports.AdminAccountRequiredError = AdminAccountRequiredError;
|
|
@@ -3095,5 +3190,7 @@ exports.ResourcePoolService = ResourcePoolService;
|
|
|
3095
3190
|
exports.ReviewService = ReviewService;
|
|
3096
3191
|
exports.TimeslotService = TimeslotService;
|
|
3097
3192
|
exports.noopLogger = noopLogger;
|
|
3193
|
+
exports.parseBookingWebhook = parseBookingWebhook;
|
|
3194
|
+
exports.parseWaiverWebhook = parseWaiverWebhook;
|
|
3098
3195
|
//# sourceMappingURL=index.cjs.map
|
|
3099
3196
|
//# sourceMappingURL=index.cjs.map
|