@peektravel/app-utilities 0.6.1 → 0.7.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 -50
- package/dist/index.cjs +33 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +93 -82
- package/dist/index.d.ts +93 -82
- package/dist/index.js +33 -42
- package/dist/index.js.map +1 -1
- package/docs/webhooks.md +103 -128
- package/llms.txt +25 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,7 +124,8 @@ Two kinds of failures surface as exceptions:
|
|
|
124
124
|
- `PiiAccessDisabledError` — a payment / booking-modification operation was
|
|
125
125
|
called on an access service created without `fullCustomerAccess` (see [Access options
|
|
126
126
|
/ PII](#access-options--pii)). Carries `.operation` (the blocked method name).
|
|
127
|
-
- `InvalidPeekTokenError` — `verifyPeekAuthToken` (or `
|
|
127
|
+
- `InvalidPeekTokenError` — `verifyPeekAuthToken` (or `parseInstallWebhook` /
|
|
128
|
+
`verifyInstallWebhook`)
|
|
128
129
|
received a token whose signature is valid but whose `user` block has no `id`.
|
|
129
130
|
Carries `.field` (`"user.id"`). Thrown alongside the `jsonwebtoken` errors.
|
|
130
131
|
|
|
@@ -245,22 +246,20 @@ Functions runtime) resolve correctly. Its only runtime dependency is
|
|
|
245
246
|
|
|
246
247
|
## Webhooks
|
|
247
248
|
|
|
248
|
-
Receiver apps can consume Peek **booking**, **waiver**, **install
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
Receiver apps can consume Peek **booking**, **waiver**, and **install** webhooks
|
|
250
|
+
without hand-writing the payload handling. The booking and waiver webhooks have a
|
|
251
|
+
pure parser (construct nothing — no auth/network) that returns a clean model; the
|
|
252
|
+
install webhook delivers a signed token alongside a JSON body, so its helper
|
|
253
|
+
**verifies** the token before merging the two:
|
|
253
254
|
|
|
254
255
|
```ts
|
|
255
256
|
import {
|
|
256
257
|
parseBookingWebhook,
|
|
257
|
-
|
|
258
|
+
parseInstallWebhook,
|
|
258
259
|
parseWaiverWebhook,
|
|
259
|
-
verifyInstallWebhook,
|
|
260
260
|
type Booking,
|
|
261
|
-
type
|
|
261
|
+
type InstallWebhook,
|
|
262
262
|
type Waiver,
|
|
263
|
-
type InstallWebhookClaims,
|
|
264
263
|
} from "@peektravel/app-utilities";
|
|
265
264
|
|
|
266
265
|
app.post("/booking-webhook", (req, res) => {
|
|
@@ -274,57 +273,56 @@ app.post("/waiver-webhook", (req, res) => {
|
|
|
274
273
|
res.sendStatus(200);
|
|
275
274
|
});
|
|
276
275
|
|
|
277
|
-
app.post("/install-
|
|
278
|
-
|
|
276
|
+
app.post("/install-webhook", async (req, res) => {
|
|
277
|
+
let event: InstallWebhook;
|
|
278
|
+
try {
|
|
279
|
+
// Verifies the signed token, then merges in the JSON body:
|
|
280
|
+
event = parseInstallWebhook(req.token, req.body, process.env.PEEK_INTERNAL_SECRET!);
|
|
281
|
+
} catch {
|
|
282
|
+
return res.sendStatus(401); // bad signature / issuer / audience / expired
|
|
283
|
+
}
|
|
279
284
|
switch (event.status) {
|
|
280
|
-
case "installed": await provision(event
|
|
281
|
-
case "uninstalled": await deprovision(event
|
|
282
|
-
case "update_installed": await recordVersion(event
|
|
285
|
+
case "installed": await provision(event); break;
|
|
286
|
+
case "uninstalled": await deprovision(event); break;
|
|
287
|
+
case "update_installed": await recordVersion(event); break;
|
|
283
288
|
// `status` is null when Peek sends one this version doesn't know. Peek
|
|
284
289
|
// doesn't redeliver a 2xx, so fail loudly rather than silently no-op.
|
|
285
290
|
default: return res.status(500).json({ error: `unknown status: ${event.rawStatus}` });
|
|
286
291
|
}
|
|
287
292
|
res.sendStatus(200);
|
|
288
293
|
});
|
|
289
|
-
|
|
290
|
-
app.post("/install-webhook", (req, res) => {
|
|
291
|
-
try {
|
|
292
|
-
// The payload IS a signed JWT — verify signature/issuer/audience/expiry:
|
|
293
|
-
const claims: InstallWebhookClaims = verifyInstallWebhook(req.body, process.env.PEEK_INTERNAL_SECRET!);
|
|
294
|
-
if (claims.status === "uninstalled") {
|
|
295
|
-
/* tear down this install */
|
|
296
|
-
}
|
|
297
|
-
res.sendStatus(200);
|
|
298
|
-
} catch {
|
|
299
|
-
res.sendStatus(401); // bad signature / issuer / audience / expired
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
294
|
```
|
|
303
295
|
|
|
304
|
-
The booking
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
`InstallIdentity` (`installId`, `accountId` — **also called the partner ID** —
|
|
308
|
-
`accountName`, `platform`, `isTest`) that is flat and JSON-safe, so it persists
|
|
309
|
-
and rehydrates as a unit; that body is the only source of `accountId` in the
|
|
310
|
-
package, since no GraphQL read or token returns it. Its `status` and `platform`
|
|
311
|
-
are **nullable** — a value this version doesn't recognise stays `null` instead of
|
|
312
|
-
being coerced, so a newer registry can't be mistaken for an older one.
|
|
313
|
-
`parseWaiverWebhook` also takes an optional
|
|
296
|
+
The booking and waiver parsers tolerate the delivery envelope / a bare node / a
|
|
297
|
+
JSON string and never throw on malformed input; **authenticating those deliveries
|
|
298
|
+
is the receiver's job**. `parseWaiverWebhook` also takes an optional
|
|
314
299
|
`AccessOptions` (`{ fullCustomerAccess }`) and redacts the participant `guestName`
|
|
315
300
|
+ document `fileUrl` by default — see [Access options / PII](#access-options--pii)
|
|
316
|
-
below.
|
|
317
|
-
|
|
318
|
-
`
|
|
319
|
-
|
|
320
|
-
`
|
|
321
|
-
|
|
322
|
-
`
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
301
|
+
below.
|
|
302
|
+
|
|
303
|
+
`parseInstallWebhook(token, body, secret)` is different: the delivery carries a
|
|
304
|
+
signed `app_registry_v2` JWT plus a JSON body. It validates the HMAC signature,
|
|
305
|
+
expiry, issuer, and `"Joken"` audience (the same checks as `verifyPeekAuthToken`,
|
|
306
|
+
throwing the underlying `jsonwebtoken` error — or `InvalidPeekTokenError` when a
|
|
307
|
+
present `user` block has no `id`), then returns one flat, JSON-safe
|
|
308
|
+
`InstallWebhook`: `installId`, `accountId` (**also called the partner ID**),
|
|
309
|
+
`accountName`, `platform`, `isTest`, `status`, `rawStatus`, `displayVersion`, and
|
|
310
|
+
a **nullable** `user`. The **verified token** is authoritative for `installId` /
|
|
311
|
+
`accountId` / `status` / `displayVersion` / `user`; the unsigned body supplies
|
|
312
|
+
only `accountName` / `platform` / `isTest`. This webhook is the only source of
|
|
313
|
+
`accountId` in the package, since no GraphQL read returns it. `status` and
|
|
314
|
+
`platform` are **nullable** — a value this version doesn't recognise stays `null`
|
|
315
|
+
(wire value on `rawStatus`) instead of being coerced, so a newer registry can't be
|
|
316
|
+
mistaken for an older one. (The older `verifyInstallWebhook(token, secret)` is
|
|
317
|
+
still exported but **`@deprecated`** — it reads only the token, missing the
|
|
318
|
+
account name/platform/test flag; `parseInstallEvent` has been **removed**.)
|
|
319
|
+
|
|
320
|
+
The booking and waiver webhooks differ on registration: a **booking** webhook's
|
|
321
|
+
payload shape is set by a GraphQL query configured **once in an external system**
|
|
322
|
+
(the App Store `broadcast_to_url` config) — this package documents and
|
|
323
|
+
drift-guards the exact query to paste there — whereas the **waiver** and
|
|
324
|
+
**install** webhooks have fixed payloads, so you just subscribe to their event
|
|
325
|
+
with no query. **The query to register and the full guide:
|
|
328
326
|
[`docs/webhooks.md`](docs/webhooks.md) (shipped).**
|
|
329
327
|
|
|
330
328
|
## UI components (`/ui`)
|
package/dist/index.cjs
CHANGED
|
@@ -4271,18 +4271,6 @@ function extractWaiverNode(payload) {
|
|
|
4271
4271
|
return record;
|
|
4272
4272
|
}
|
|
4273
4273
|
|
|
4274
|
-
// src/internal/peek/installs/install-webhook.ts
|
|
4275
|
-
function verifyInstallWebhook(token, secret) {
|
|
4276
|
-
const payload = verifyPeekJwt(token, secret);
|
|
4277
|
-
return {
|
|
4278
|
-
installId: payload.sub ?? "",
|
|
4279
|
-
account: { id: payload.account?.id ?? "" },
|
|
4280
|
-
status: payload.status ?? "",
|
|
4281
|
-
displayVersion: payload.display_version ?? "",
|
|
4282
|
-
user: payload.user ? mapPeekTokenUser(payload.user) : null
|
|
4283
|
-
};
|
|
4284
|
-
}
|
|
4285
|
-
|
|
4286
4274
|
// src/models/peek/auth-token.ts
|
|
4287
4275
|
var PEEK_PLATFORMS = ["peek", "cng", "acme"];
|
|
4288
4276
|
|
|
@@ -4294,48 +4282,51 @@ var INSTALL_STATUSES = [
|
|
|
4294
4282
|
];
|
|
4295
4283
|
|
|
4296
4284
|
// src/internal/peek/installs/install-converter.ts
|
|
4285
|
+
function extractInstallWebhookBody(payload) {
|
|
4286
|
+
let body = payload;
|
|
4287
|
+
if (typeof body === "string") {
|
|
4288
|
+
try {
|
|
4289
|
+
body = JSON.parse(body);
|
|
4290
|
+
} catch {
|
|
4291
|
+
return {};
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
if (!body || typeof body !== "object") return {};
|
|
4295
|
+
return body;
|
|
4296
|
+
}
|
|
4297
4297
|
function toInstallStatus(raw) {
|
|
4298
4298
|
return INSTALL_STATUSES.includes(raw) ? raw : null;
|
|
4299
4299
|
}
|
|
4300
4300
|
function toPeekPlatform(raw) {
|
|
4301
4301
|
return PEEK_PLATFORMS.includes(raw) ? raw : null;
|
|
4302
4302
|
}
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4303
|
+
|
|
4304
|
+
// src/internal/peek/installs/install-webhook.ts
|
|
4305
|
+
function parseInstallWebhook(token, body, secret) {
|
|
4306
|
+
const payload = verifyPeekJwt(token, secret);
|
|
4307
|
+
const account = extractInstallWebhookBody(body).account ?? {};
|
|
4308
|
+
const rawStatus = payload.status ?? "";
|
|
4306
4309
|
return {
|
|
4307
|
-
installId:
|
|
4308
|
-
accountId: account
|
|
4310
|
+
installId: payload.sub ?? "",
|
|
4311
|
+
accountId: payload.account?.id ?? "",
|
|
4309
4312
|
accountName: account.name || "",
|
|
4310
4313
|
platform: toPeekPlatform(account.platform || ""),
|
|
4311
|
-
isTest: Boolean(account.is_test)
|
|
4312
|
-
};
|
|
4313
|
-
}
|
|
4314
|
-
function fromInstallEventNode(node) {
|
|
4315
|
-
const rawStatus = node?.status || "";
|
|
4316
|
-
return {
|
|
4314
|
+
isTest: Boolean(account.is_test),
|
|
4317
4315
|
status: toInstallStatus(rawStatus),
|
|
4318
4316
|
rawStatus,
|
|
4319
|
-
displayVersion:
|
|
4320
|
-
|
|
4317
|
+
displayVersion: payload.display_version ?? "",
|
|
4318
|
+
user: payload.user ? mapPeekTokenUser(payload.user) : null
|
|
4321
4319
|
};
|
|
4322
4320
|
}
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
}
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
body = JSON.parse(body);
|
|
4333
|
-
} catch {
|
|
4334
|
-
return {};
|
|
4335
|
-
}
|
|
4336
|
-
}
|
|
4337
|
-
if (!body || typeof body !== "object") return {};
|
|
4338
|
-
return body;
|
|
4321
|
+
function verifyInstallWebhook(token, secret) {
|
|
4322
|
+
const payload = verifyPeekJwt(token, secret);
|
|
4323
|
+
return {
|
|
4324
|
+
installId: payload.sub ?? "",
|
|
4325
|
+
account: { id: payload.account?.id ?? "" },
|
|
4326
|
+
status: payload.status ?? "",
|
|
4327
|
+
displayVersion: payload.display_version ?? "",
|
|
4328
|
+
user: payload.user ? mapPeekTokenUser(payload.user) : null
|
|
4329
|
+
};
|
|
4339
4330
|
}
|
|
4340
4331
|
|
|
4341
4332
|
exports.ACTIVITY_PRODUCT_TYPE = ACTIVITY_PRODUCT_TYPE;
|
|
@@ -4369,7 +4360,7 @@ exports.ReviewService = ReviewService;
|
|
|
4369
4360
|
exports.TimeslotService = TimeslotService;
|
|
4370
4361
|
exports.noopLogger = noopLogger;
|
|
4371
4362
|
exports.parseBookingWebhook = parseBookingWebhook;
|
|
4372
|
-
exports.
|
|
4363
|
+
exports.parseInstallWebhook = parseInstallWebhook;
|
|
4373
4364
|
exports.parseWaiverWebhook = parseWaiverWebhook;
|
|
4374
4365
|
exports.verifyInstallWebhook = verifyInstallWebhook;
|
|
4375
4366
|
//# sourceMappingURL=index.cjs.map
|