@peektravel/app-utilities 0.7.0 → 0.7.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 +20 -8
- package/dist/index.cjs +64 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +181 -43
- package/dist/index.d.ts +181 -43
- package/dist/index.js +64 -25
- package/dist/index.js.map +1 -1
- package/dist/ui/index.cjs +26 -0
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +77 -3
- package/dist/ui/index.d.ts +77 -3
- package/dist/ui/index.js +25 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/odyssey.css +24 -2
- package/dist/ui/tokens.css +10 -1
- package/docs/ui.md +63 -14
- package/docs/webhooks.md +118 -31
- package/llms.txt +38 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -276,8 +276,9 @@ app.post("/waiver-webhook", (req, res) => {
|
|
|
276
276
|
app.post("/install-webhook", async (req, res) => {
|
|
277
277
|
let event: InstallWebhook;
|
|
278
278
|
try {
|
|
279
|
-
//
|
|
280
|
-
|
|
279
|
+
// Token rides in the `x-peek-auth` header (a `Bearer ` prefix is stripped for you):
|
|
280
|
+
const token = req.header("x-peek-auth") ?? "";
|
|
281
|
+
event = parseInstallWebhook(token, req.body, process.env.PEEK_INTERNAL_SECRET!);
|
|
281
282
|
} catch {
|
|
282
283
|
return res.sendStatus(401); // bad signature / issuer / audience / expired
|
|
283
284
|
}
|
|
@@ -301,16 +302,27 @@ is the receiver's job**. `parseWaiverWebhook` also takes an optional
|
|
|
301
302
|
below.
|
|
302
303
|
|
|
303
304
|
`parseInstallWebhook(token, body, secret)` is different: the delivery carries a
|
|
304
|
-
signed `app_registry_v2` JWT plus a JSON body.
|
|
305
|
+
signed `app_registry_v2` JWT plus a JSON body. Peek sends the token in the
|
|
306
|
+
`x-peek-auth` header — pass that value straight in (`token` accepts it with or
|
|
307
|
+
without a `Bearer ` prefix). It validates the HMAC signature,
|
|
305
308
|
expiry, issuer, and `"Joken"` audience (the same checks as `verifyPeekAuthToken`,
|
|
306
309
|
throwing the underlying `jsonwebtoken` error — or `InvalidPeekTokenError` when a
|
|
307
310
|
present `user` block has no `id`), then returns one flat, JSON-safe
|
|
308
311
|
`InstallWebhook`: `installId`, `accountId` (**also called the partner ID**),
|
|
309
|
-
`accountName`, `platform`, `isTest`, `
|
|
310
|
-
a **nullable** `user`. The **
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
312
|
+
`accountName`, `platform`, `isTest`, `timezone`, `apiUrl`, `status`, `rawStatus`,
|
|
313
|
+
`displayVersion`, and a **nullable** `user`. The **JSON body is the source of the
|
|
314
|
+
event data**; the fields it shares with the token (`installId` / `accountId` /
|
|
315
|
+
`status` / `displayVersion` / `user`) fall back to the **verified token** when
|
|
316
|
+
the body omits them. The token authenticates the whole delivery, so the body is
|
|
317
|
+
trusted within a verified request. This webhook is the only source of
|
|
318
|
+
`accountId`, `timezone`, and `apiUrl` in the package, since no GraphQL read
|
|
319
|
+
returns them — **persist them per install**. `apiUrl` is the install's app
|
|
320
|
+
endpoint: use it **as given** (don't reconstruct it from `appId`) — pass it as the
|
|
321
|
+
access service's `apiUrl`, or hand the whole install to
|
|
322
|
+
`createAccessServiceForInstall(install, { jwtSecret, issuer })`, which picks the
|
|
323
|
+
service by `platform` and wires the endpoint for you. Every event is a **full
|
|
324
|
+
snapshot** — an `update_installed` carries the same fields as the original
|
|
325
|
+
`installed`, so upsert by `installId` and take the latest `apiUrl`. `status` and
|
|
314
326
|
`platform` are **nullable** — a value this version doesn't recognise stays `null`
|
|
315
327
|
(wire value on `rawStatus`) instead of being coerced, so a newer registry can't be
|
|
316
328
|
mistaken for an older one. (The older `verifyInstallWebhook(token, secret)` is
|
package/dist/index.cjs
CHANGED
|
@@ -2044,7 +2044,8 @@ var GraphQLClient = class {
|
|
|
2044
2044
|
);
|
|
2045
2045
|
}
|
|
2046
2046
|
endpoint(endpointName) {
|
|
2047
|
-
const { baseUrl, appId, endpointPathPrefix } = this.options;
|
|
2047
|
+
const { apiUrl, baseUrl, appId, endpointPathPrefix } = this.options;
|
|
2048
|
+
if (apiUrl) return apiUrl;
|
|
2048
2049
|
const prefix = endpointPathPrefix ? `${endpointPathPrefix}/` : "";
|
|
2049
2050
|
return `${baseUrl}/${appId}/${prefix}${endpointName}`;
|
|
2050
2051
|
}
|
|
@@ -2061,8 +2062,13 @@ var GraphQLClient = class {
|
|
|
2061
2062
|
};
|
|
2062
2063
|
var PEEK_TOKEN_ISSUER = "app_registry_v2";
|
|
2063
2064
|
var PEEK_TOKEN_AUDIENCE = "Joken";
|
|
2065
|
+
var BEARER_PREFIX = "bearer ";
|
|
2066
|
+
function stripBearer(token) {
|
|
2067
|
+
const trimmed = token.trim();
|
|
2068
|
+
return trimmed.toLowerCase().startsWith(BEARER_PREFIX) ? trimmed.slice(BEARER_PREFIX.length).trim() : trimmed;
|
|
2069
|
+
}
|
|
2064
2070
|
function verifyPeekJwt(token, secret) {
|
|
2065
|
-
return jwt__namespace.verify(token, secret, {
|
|
2071
|
+
return jwt__namespace.verify(stripBearer(token), secret, {
|
|
2066
2072
|
issuer: PEEK_TOKEN_ISSUER,
|
|
2067
2073
|
audience: PEEK_TOKEN_AUDIENCE
|
|
2068
2074
|
});
|
|
@@ -3556,25 +3562,27 @@ var PeekAccessService = class {
|
|
|
3556
3562
|
bookingService;
|
|
3557
3563
|
reviewService;
|
|
3558
3564
|
constructor(config) {
|
|
3565
|
+
const hasApiUrl = !!config.apiUrl;
|
|
3559
3566
|
const isV2 = config.mode === "v2";
|
|
3560
3567
|
requireNonEmpty(config.installId, "installId", "PeekAccessService");
|
|
3561
3568
|
requireNonEmpty(config.jwtSecret, "jwtSecret", "PeekAccessService");
|
|
3562
3569
|
requireNonEmpty(config.issuer, "issuer", "PeekAccessService");
|
|
3563
|
-
requireNonEmpty(config.appId, "appId", "PeekAccessService");
|
|
3564
|
-
if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
|
|
3570
|
+
if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "PeekAccessService");
|
|
3571
|
+
if (!hasApiUrl && !isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
|
|
3565
3572
|
this.jwtSecret = config.jwtSecret;
|
|
3566
3573
|
const logger = config.logger ?? noopLogger;
|
|
3567
3574
|
const tokens = createTokenManager(config);
|
|
3568
3575
|
const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
|
|
3569
3576
|
this.client = new GraphQLClient({
|
|
3570
|
-
|
|
3571
|
-
|
|
3577
|
+
apiUrl: config.apiUrl,
|
|
3578
|
+
baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? defaultBaseUrl,
|
|
3579
|
+
appId: hasApiUrl ? void 0 : config.appId,
|
|
3572
3580
|
gatewayKey: config.gatewayKey,
|
|
3573
3581
|
getToken: () => tokens.getToken(),
|
|
3574
3582
|
retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
|
|
3575
3583
|
logger,
|
|
3576
3584
|
fetchFn: config.fetch ?? globalThis.fetch,
|
|
3577
|
-
endpointPathPrefix: isV2 ? V2_EXTENDABLE_SLUG : void 0
|
|
3585
|
+
endpointPathPrefix: !hasApiUrl && isV2 ? V2_EXTENDABLE_SLUG : void 0
|
|
3578
3586
|
});
|
|
3579
3587
|
this.productServiceOptions = {
|
|
3580
3588
|
itemOptionsPageSize: config.itemOptionsPageSize
|
|
@@ -3586,9 +3594,10 @@ var PeekAccessService = class {
|
|
|
3586
3594
|
* decoded claims.
|
|
3587
3595
|
*
|
|
3588
3596
|
* Validates the HMAC signature (using this service's `jwtSecret`), the token
|
|
3589
|
-
* expiry, the `"app_registry_v2"` issuer, and the `"Joken"` audience.
|
|
3590
|
-
*
|
|
3591
|
-
*
|
|
3597
|
+
* expiry, the `"app_registry_v2"` issuer, and the `"Joken"` audience. Pass the
|
|
3598
|
+
* raw `x-peek-auth` request header value — a leading `Bearer ` prefix, if
|
|
3599
|
+
* present, is stripped for you. Throws from the `jsonwebtoken` library on any
|
|
3600
|
+
* failure — callers should catch to distinguish error kinds:
|
|
3592
3601
|
*
|
|
3593
3602
|
* - `JsonWebTokenError` — signature invalid, wrong issuer/audience, or token
|
|
3594
3603
|
* malformed
|
|
@@ -4001,7 +4010,8 @@ var RestClient = class {
|
|
|
4001
4010
|
);
|
|
4002
4011
|
}
|
|
4003
4012
|
endpoint(path) {
|
|
4004
|
-
const { baseUrl, appId, extendableSlug } = this.options;
|
|
4013
|
+
const { apiUrl, baseUrl, appId, extendableSlug } = this.options;
|
|
4014
|
+
if (apiUrl) return `${apiUrl}/${path}`;
|
|
4005
4015
|
return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
|
|
4006
4016
|
}
|
|
4007
4017
|
buildHeaders() {
|
|
@@ -4018,15 +4028,17 @@ var CngAccessService = class {
|
|
|
4018
4028
|
client;
|
|
4019
4029
|
productService;
|
|
4020
4030
|
constructor(config) {
|
|
4031
|
+
const hasApiUrl = !!config.apiUrl;
|
|
4021
4032
|
requireNonEmpty(config.installId, "installId", "CngAccessService");
|
|
4022
4033
|
requireNonEmpty(config.jwtSecret, "jwtSecret", "CngAccessService");
|
|
4023
4034
|
requireNonEmpty(config.issuer, "issuer", "CngAccessService");
|
|
4024
|
-
requireNonEmpty(config.appId, "appId", "CngAccessService");
|
|
4035
|
+
if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "CngAccessService");
|
|
4025
4036
|
const logger = config.logger ?? noopLogger;
|
|
4026
4037
|
const tokens = createTokenManager(config);
|
|
4027
4038
|
this.client = new RestClient({
|
|
4028
|
-
|
|
4029
|
-
|
|
4039
|
+
apiUrl: config.apiUrl,
|
|
4040
|
+
baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? DEFAULT_BASE_URL2,
|
|
4041
|
+
appId: hasApiUrl ? void 0 : config.appId,
|
|
4030
4042
|
extendableSlug: CNG_EXTENDABLE_SLUG,
|
|
4031
4043
|
getToken: () => tokens.getToken(),
|
|
4032
4044
|
retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
|
|
@@ -4132,7 +4144,8 @@ var RestClient2 = class {
|
|
|
4132
4144
|
);
|
|
4133
4145
|
}
|
|
4134
4146
|
endpoint(path) {
|
|
4135
|
-
const { baseUrl, appId, extendableSlug } = this.options;
|
|
4147
|
+
const { apiUrl, baseUrl, appId, extendableSlug } = this.options;
|
|
4148
|
+
if (apiUrl) return `${apiUrl}/${path}`;
|
|
4136
4149
|
return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
|
|
4137
4150
|
}
|
|
4138
4151
|
buildHeaders() {
|
|
@@ -4149,15 +4162,17 @@ var AcmeAccessService = class {
|
|
|
4149
4162
|
client;
|
|
4150
4163
|
productService;
|
|
4151
4164
|
constructor(config) {
|
|
4165
|
+
const hasApiUrl = !!config.apiUrl;
|
|
4152
4166
|
requireNonEmpty(config.installId, "installId", "AcmeAccessService");
|
|
4153
4167
|
requireNonEmpty(config.jwtSecret, "jwtSecret", "AcmeAccessService");
|
|
4154
4168
|
requireNonEmpty(config.issuer, "issuer", "AcmeAccessService");
|
|
4155
|
-
requireNonEmpty(config.appId, "appId", "AcmeAccessService");
|
|
4169
|
+
if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "AcmeAccessService");
|
|
4156
4170
|
const logger = config.logger ?? noopLogger;
|
|
4157
4171
|
const tokens = createTokenManager(config);
|
|
4158
4172
|
this.client = new RestClient2({
|
|
4159
|
-
|
|
4160
|
-
|
|
4173
|
+
apiUrl: config.apiUrl,
|
|
4174
|
+
baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? DEFAULT_BASE_URL3,
|
|
4175
|
+
appId: hasApiUrl ? void 0 : config.appId,
|
|
4161
4176
|
extendableSlug: ACME_EXTENDABLE_SLUG,
|
|
4162
4177
|
getToken: () => tokens.getToken(),
|
|
4163
4178
|
retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
|
|
@@ -4181,6 +4196,23 @@ var AcmeAccessService = class {
|
|
|
4181
4196
|
}
|
|
4182
4197
|
};
|
|
4183
4198
|
|
|
4199
|
+
// src/access-service-factory.ts
|
|
4200
|
+
function createAccessServiceForInstall(install, config) {
|
|
4201
|
+
const shared = { ...config, installId: install.installId, apiUrl: install.apiUrl };
|
|
4202
|
+
switch (install.platform) {
|
|
4203
|
+
case "peek":
|
|
4204
|
+
return new PeekAccessService(shared);
|
|
4205
|
+
case "cng":
|
|
4206
|
+
return new CngAccessService(shared);
|
|
4207
|
+
case "acme":
|
|
4208
|
+
return new AcmeAccessService(shared);
|
|
4209
|
+
default:
|
|
4210
|
+
throw new Error(
|
|
4211
|
+
`createAccessServiceForInstall: unknown or missing platform "${install.platform}" for install "${install.installId}"`
|
|
4212
|
+
);
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4184
4216
|
// src/internal/peek/bookings/booking-webhook.ts
|
|
4185
4217
|
var PAYLOAD_BOOKING_KEY = "booking";
|
|
4186
4218
|
var VALUE_OPEN_TOKEN = "value {";
|
|
@@ -4304,20 +4336,27 @@ function toPeekPlatform(raw) {
|
|
|
4304
4336
|
// src/internal/peek/installs/install-webhook.ts
|
|
4305
4337
|
function parseInstallWebhook(token, body, secret) {
|
|
4306
4338
|
const payload = verifyPeekJwt(token, secret);
|
|
4307
|
-
const
|
|
4308
|
-
const
|
|
4339
|
+
const parsed = extractInstallWebhookBody(body);
|
|
4340
|
+
const account = parsed.account ?? {};
|
|
4341
|
+
const rawStatus = parsed.status || payload.status || "";
|
|
4309
4342
|
return {
|
|
4310
|
-
installId: payload.sub
|
|
4311
|
-
accountId: payload.account?.id
|
|
4343
|
+
installId: parsed.install_id || payload.sub || "",
|
|
4344
|
+
accountId: account.id || payload.account?.id || "",
|
|
4312
4345
|
accountName: account.name || "",
|
|
4313
4346
|
platform: toPeekPlatform(account.platform || ""),
|
|
4314
4347
|
isTest: Boolean(account.is_test),
|
|
4348
|
+
timezone: account.timezone || "",
|
|
4349
|
+
apiUrl: parsed.api?.url || "",
|
|
4315
4350
|
status: toInstallStatus(rawStatus),
|
|
4316
4351
|
rawStatus,
|
|
4317
|
-
displayVersion: payload.display_version
|
|
4318
|
-
user:
|
|
4352
|
+
displayVersion: parsed.display_version || payload.display_version || "",
|
|
4353
|
+
user: resolveInstallUser(parsed.modified_by, payload.user)
|
|
4319
4354
|
};
|
|
4320
4355
|
}
|
|
4356
|
+
function resolveInstallUser(bodyUser, tokenUser) {
|
|
4357
|
+
if (bodyUser?.id) return mapPeekTokenUser(bodyUser);
|
|
4358
|
+
return tokenUser ? mapPeekTokenUser(tokenUser) : null;
|
|
4359
|
+
}
|
|
4321
4360
|
function verifyInstallWebhook(token, secret) {
|
|
4322
4361
|
const payload = verifyPeekJwt(token, secret);
|
|
4323
4362
|
return {
|
|
@@ -4358,6 +4397,7 @@ exports.ResellerService = ResellerService;
|
|
|
4358
4397
|
exports.ResourcePoolService = ResourcePoolService;
|
|
4359
4398
|
exports.ReviewService = ReviewService;
|
|
4360
4399
|
exports.TimeslotService = TimeslotService;
|
|
4400
|
+
exports.createAccessServiceForInstall = createAccessServiceForInstall;
|
|
4361
4401
|
exports.noopLogger = noopLogger;
|
|
4362
4402
|
exports.parseBookingWebhook = parseBookingWebhook;
|
|
4363
4403
|
exports.parseInstallWebhook = parseInstallWebhook;
|