@peektravel/app-utilities 1.0.0 → 1.1.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 +13 -6
- package/dist/index.cjs +52 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -18
- package/dist/index.d.ts +84 -18
- package/dist/index.js +53 -6
- package/dist/index.js.map +1 -1
- package/docs/webhooks.md +28 -15
- package/llms.txt +12 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -163,6 +163,12 @@ Two kinds of failures surface as exceptions:
|
|
|
163
163
|
- `PiiAccessDisabledError` — a payment / booking-modification operation was
|
|
164
164
|
called on an access service created without `fullCustomerAccess` (see [Access options
|
|
165
165
|
/ PII](#access-options--pii)). Carries `.operation` (the blocked method name).
|
|
166
|
+
- `InstallWebhookError` — `parseInstallWebhook` got a validly signed token that
|
|
167
|
+
does not name the delivered install: the token has no `sub`
|
|
168
|
+
(`.reason === "missing_subject"`), or the body `install_id` is not equal to it
|
|
169
|
+
(`.reason === "install_id_mismatch"`). It extends `jsonwebtoken`'s
|
|
170
|
+
`JsonWebTokenError`, so an `instanceof JsonWebTokenError` check also matches
|
|
171
|
+
it. Respond `401`.
|
|
166
172
|
|
|
167
173
|
Once a token's signature verifies, claim *extraction* never throws: the whole
|
|
168
174
|
`user` block may be absent (`claims.user === null`) and every field inside it is
|
|
@@ -321,7 +327,7 @@ app.post("/install-webhook", async (req, res) => {
|
|
|
321
327
|
const token = req.header("x-peek-auth") ?? "";
|
|
322
328
|
event = parseInstallWebhook(token, req.body, process.env.PEEK_INTERNAL_SECRET!);
|
|
323
329
|
} catch {
|
|
324
|
-
return res.sendStatus(401); // bad
|
|
330
|
+
return res.sendStatus(401); // bad token, or the body names another install
|
|
325
331
|
}
|
|
326
332
|
switch (event.status) {
|
|
327
333
|
case "installed": await provision(event); break;
|
|
@@ -350,11 +356,12 @@ expiry, issuer, and `"Joken"` audience (the same checks as `verifyPeekAuthToken`
|
|
|
350
356
|
throwing the underlying `jsonwebtoken` error), then returns one flat, JSON-safe
|
|
351
357
|
`InstallWebhook`: `installId`, `accountId` (**also called the partner ID**),
|
|
352
358
|
`accountName`, `platform`, `isTest`, `timezone`, `apiUrl`, `status`, `rawStatus`,
|
|
353
|
-
`displayVersion`, and a **nullable** `user`.
|
|
354
|
-
|
|
355
|
-
`
|
|
356
|
-
|
|
357
|
-
|
|
359
|
+
`displayVersion`, and a **nullable** `user`. **`installId` is always the token
|
|
360
|
+
`sub`**: a body `install_id` that is present and not equal to it throws
|
|
361
|
+
`InstallWebhookError`, and so does a token with no `sub`. The **JSON body is the
|
|
362
|
+
source of the other event data**; the fields it shares with the token
|
|
363
|
+
(`accountId` / `status` / `displayVersion` / `user`) fall back to the **verified
|
|
364
|
+
token** when the body omits them. This webhook is the only source of
|
|
358
365
|
`accountId`, `timezone`, and `apiUrl` in the package, since no GraphQL read
|
|
359
366
|
returns them — **persist them per install**. `apiUrl` is the install's **base API
|
|
360
367
|
URL**: pass it straight through (don't reconstruct it from `appId`) as the access
|
package/dist/index.cjs
CHANGED
|
@@ -191,6 +191,12 @@ var AVAILABILITY_TIMES_QUERY = `
|
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
status
|
|
194
|
+
minimumTicketsRequired
|
|
195
|
+
price {
|
|
196
|
+
amount
|
|
197
|
+
currency
|
|
198
|
+
formatted
|
|
199
|
+
}
|
|
194
200
|
availability {
|
|
195
201
|
qty
|
|
196
202
|
taken
|
|
@@ -200,6 +206,24 @@ var AVAILABILITY_TIMES_QUERY = `
|
|
|
200
206
|
}
|
|
201
207
|
`;
|
|
202
208
|
|
|
209
|
+
// src/internal/peek/availability/availability-converter.ts
|
|
210
|
+
var DEFAULT_MINIMUM_TICKETS = 1;
|
|
211
|
+
function toAvailabilityTime(node) {
|
|
212
|
+
const rawMinimum = node.minimumTicketsRequired;
|
|
213
|
+
const minimumTicketsRequired = typeof rawMinimum === "number" && rawMinimum > 0 ? rawMinimum : DEFAULT_MINIMUM_TICKETS;
|
|
214
|
+
return {
|
|
215
|
+
id: node.id,
|
|
216
|
+
time: node.time,
|
|
217
|
+
from: node.from,
|
|
218
|
+
end: node.end,
|
|
219
|
+
duration: node.duration,
|
|
220
|
+
status: node.status,
|
|
221
|
+
minimumTicketsRequired,
|
|
222
|
+
price: node.price ?? null,
|
|
223
|
+
availability: node.availability ?? []
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
203
227
|
// src/internal/peek/availability/availability-service.ts
|
|
204
228
|
var AvailabilityService = class {
|
|
205
229
|
constructor(client) {
|
|
@@ -217,7 +241,7 @@ var AvailabilityService = class {
|
|
|
217
241
|
date: query.date
|
|
218
242
|
}
|
|
219
243
|
);
|
|
220
|
-
return body.data?.availabilityTimes ?? [];
|
|
244
|
+
return (body.data?.availabilityTimes ?? []).map(toAvailabilityTime);
|
|
221
245
|
}
|
|
222
246
|
};
|
|
223
247
|
|
|
@@ -225,8 +249,6 @@ var AvailabilityService = class {
|
|
|
225
249
|
function resolveAccessOptions(options) {
|
|
226
250
|
return { fullCustomerAccess: options?.fullCustomerAccess ?? false };
|
|
227
251
|
}
|
|
228
|
-
|
|
229
|
-
// src/errors.ts
|
|
230
252
|
var FORBIDDEN_STATUS = 403;
|
|
231
253
|
var PERMISSION_MESSAGE_PREFIX = "CNG request forbidden: the app is missing the required permission";
|
|
232
254
|
var PERMISSION_MESSAGE_UNKNOWN = "CNG request forbidden: the app is missing a required permission";
|
|
@@ -326,6 +348,21 @@ var AcmeApiError = class extends Error {
|
|
|
326
348
|
this.body = body;
|
|
327
349
|
}
|
|
328
350
|
};
|
|
351
|
+
var INSTALL_WEBHOOK_INSTALL_ID_MISMATCH = "install_id_mismatch";
|
|
352
|
+
var INSTALL_WEBHOOK_MISSING_SUBJECT = "missing_subject";
|
|
353
|
+
var INSTALL_WEBHOOK_MESSAGES = {
|
|
354
|
+
[INSTALL_WEBHOOK_INSTALL_ID_MISMATCH]: "Install webhook body install_id does not match the token subject",
|
|
355
|
+
[INSTALL_WEBHOOK_MISSING_SUBJECT]: "Install webhook token has no subject (install id)"
|
|
356
|
+
};
|
|
357
|
+
var InstallWebhookError = class extends jwt__namespace.default.JsonWebTokenError {
|
|
358
|
+
/** Why the delivery was rejected. */
|
|
359
|
+
reason;
|
|
360
|
+
constructor(reason) {
|
|
361
|
+
super(INSTALL_WEBHOOK_MESSAGES[reason]);
|
|
362
|
+
this.name = "InstallWebhookError";
|
|
363
|
+
this.reason = reason;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
329
366
|
|
|
330
367
|
// src/models/peek/product.ts
|
|
331
368
|
var ACTIVITY_PRODUCT_TYPE = "ACTIVITY";
|
|
@@ -2791,7 +2828,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
|
|
|
2791
2828
|
// src/internal/sdk-headers.ts
|
|
2792
2829
|
var SDK_HEADER_NAME = "x-peek-sdk";
|
|
2793
2830
|
var SDK_PLATFORM = "js";
|
|
2794
|
-
var SDK_VERSION = "1.
|
|
2831
|
+
var SDK_VERSION = "1.1.0";
|
|
2795
2832
|
var SDK_HEADER_VALUE = `${SDK_PLATFORM}-${SDK_VERSION}`;
|
|
2796
2833
|
|
|
2797
2834
|
// src/internal/peek/graphql-client.ts
|
|
@@ -5281,10 +5318,11 @@ function toPeekPlatform(raw) {
|
|
|
5281
5318
|
function parseInstallWebhook(token, body, secret) {
|
|
5282
5319
|
const payload = verifyPeekJwt(token, secret);
|
|
5283
5320
|
const parsed = extractInstallWebhookBody(body);
|
|
5321
|
+
const installId = resolveInstallId(payload.sub, parsed.install_id);
|
|
5284
5322
|
const account = parsed.account ?? {};
|
|
5285
5323
|
const rawStatus = parsed.status || payload.status || "";
|
|
5286
5324
|
return {
|
|
5287
|
-
installId
|
|
5325
|
+
installId,
|
|
5288
5326
|
accountId: account.id || payload.account?.id || "",
|
|
5289
5327
|
accountName: account.name || "",
|
|
5290
5328
|
platform: toPeekPlatform(account.platform || ""),
|
|
@@ -5297,6 +5335,14 @@ function parseInstallWebhook(token, body, secret) {
|
|
|
5297
5335
|
user: resolveInstallUser(parsed.modified_by, payload.user)
|
|
5298
5336
|
};
|
|
5299
5337
|
}
|
|
5338
|
+
function resolveInstallId(rawSubject, bodyInstallId) {
|
|
5339
|
+
const tokenSubject = cleanTokenString(rawSubject);
|
|
5340
|
+
if (tokenSubject === null) throw new InstallWebhookError(INSTALL_WEBHOOK_MISSING_SUBJECT);
|
|
5341
|
+
if (bodyInstallId && bodyInstallId !== tokenSubject) {
|
|
5342
|
+
throw new InstallWebhookError(INSTALL_WEBHOOK_INSTALL_ID_MISMATCH);
|
|
5343
|
+
}
|
|
5344
|
+
return tokenSubject;
|
|
5345
|
+
}
|
|
5300
5346
|
function resolveInstallUser(bodyUser, tokenUser) {
|
|
5301
5347
|
if (cleanTokenString(bodyUser?.id) !== null) return mapPeekTokenUser(bodyUser);
|
|
5302
5348
|
return mapPeekTokenUser(tokenUser);
|
|
@@ -5328,6 +5374,7 @@ exports.CngProductService = CngProductService;
|
|
|
5328
5374
|
exports.CustomQuestionService = CustomQuestionService;
|
|
5329
5375
|
exports.DailyNoteService = DailyNoteService;
|
|
5330
5376
|
exports.INSTALL_STATUSES = INSTALL_STATUSES;
|
|
5377
|
+
exports.InstallWebhookError = InstallWebhookError;
|
|
5331
5378
|
exports.MembershipService = MembershipService;
|
|
5332
5379
|
exports.PeekAccessService = PeekAccessService;
|
|
5333
5380
|
exports.PeekGraphQLError = PeekGraphQLError;
|