@peektravel/app-utilities 1.0.1 → 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 +27 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -8
- package/dist/index.d.ts +49 -8
- package/dist/index.js +28 -5
- 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
|
@@ -249,8 +249,6 @@ var AvailabilityService = class {
|
|
|
249
249
|
function resolveAccessOptions(options) {
|
|
250
250
|
return { fullCustomerAccess: options?.fullCustomerAccess ?? false };
|
|
251
251
|
}
|
|
252
|
-
|
|
253
|
-
// src/errors.ts
|
|
254
252
|
var FORBIDDEN_STATUS = 403;
|
|
255
253
|
var PERMISSION_MESSAGE_PREFIX = "CNG request forbidden: the app is missing the required permission";
|
|
256
254
|
var PERMISSION_MESSAGE_UNKNOWN = "CNG request forbidden: the app is missing a required permission";
|
|
@@ -350,6 +348,21 @@ var AcmeApiError = class extends Error {
|
|
|
350
348
|
this.body = body;
|
|
351
349
|
}
|
|
352
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
|
+
};
|
|
353
366
|
|
|
354
367
|
// src/models/peek/product.ts
|
|
355
368
|
var ACTIVITY_PRODUCT_TYPE = "ACTIVITY";
|
|
@@ -2815,7 +2828,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
|
|
|
2815
2828
|
// src/internal/sdk-headers.ts
|
|
2816
2829
|
var SDK_HEADER_NAME = "x-peek-sdk";
|
|
2817
2830
|
var SDK_PLATFORM = "js";
|
|
2818
|
-
var SDK_VERSION = "1.0
|
|
2831
|
+
var SDK_VERSION = "1.1.0";
|
|
2819
2832
|
var SDK_HEADER_VALUE = `${SDK_PLATFORM}-${SDK_VERSION}`;
|
|
2820
2833
|
|
|
2821
2834
|
// src/internal/peek/graphql-client.ts
|
|
@@ -5305,10 +5318,11 @@ function toPeekPlatform(raw) {
|
|
|
5305
5318
|
function parseInstallWebhook(token, body, secret) {
|
|
5306
5319
|
const payload = verifyPeekJwt(token, secret);
|
|
5307
5320
|
const parsed = extractInstallWebhookBody(body);
|
|
5321
|
+
const installId = resolveInstallId(payload.sub, parsed.install_id);
|
|
5308
5322
|
const account = parsed.account ?? {};
|
|
5309
5323
|
const rawStatus = parsed.status || payload.status || "";
|
|
5310
5324
|
return {
|
|
5311
|
-
installId
|
|
5325
|
+
installId,
|
|
5312
5326
|
accountId: account.id || payload.account?.id || "",
|
|
5313
5327
|
accountName: account.name || "",
|
|
5314
5328
|
platform: toPeekPlatform(account.platform || ""),
|
|
@@ -5321,6 +5335,14 @@ function parseInstallWebhook(token, body, secret) {
|
|
|
5321
5335
|
user: resolveInstallUser(parsed.modified_by, payload.user)
|
|
5322
5336
|
};
|
|
5323
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
|
+
}
|
|
5324
5346
|
function resolveInstallUser(bodyUser, tokenUser) {
|
|
5325
5347
|
if (cleanTokenString(bodyUser?.id) !== null) return mapPeekTokenUser(bodyUser);
|
|
5326
5348
|
return mapPeekTokenUser(tokenUser);
|
|
@@ -5352,6 +5374,7 @@ exports.CngProductService = CngProductService;
|
|
|
5352
5374
|
exports.CustomQuestionService = CustomQuestionService;
|
|
5353
5375
|
exports.DailyNoteService = DailyNoteService;
|
|
5354
5376
|
exports.INSTALL_STATUSES = INSTALL_STATUSES;
|
|
5377
|
+
exports.InstallWebhookError = InstallWebhookError;
|
|
5355
5378
|
exports.MembershipService = MembershipService;
|
|
5356
5379
|
exports.PeekAccessService = PeekAccessService;
|
|
5357
5380
|
exports.PeekGraphQLError = PeekGraphQLError;
|