@peektravel/app-utilities 0.7.1 → 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 CHANGED
@@ -309,11 +309,20 @@ expiry, issuer, and `"Joken"` audience (the same checks as `verifyPeekAuthToken`
309
309
  throwing the underlying `jsonwebtoken` error — or `InvalidPeekTokenError` when a
310
310
  present `user` block has no `id`), then returns one flat, JSON-safe
311
311
  `InstallWebhook`: `installId`, `accountId` (**also called the partner ID**),
312
- `accountName`, `platform`, `isTest`, `status`, `rawStatus`, `displayVersion`, and
313
- a **nullable** `user`. The **verified token** is authoritative for `installId` /
314
- `accountId` / `status` / `displayVersion` / `user`; the unsigned body supplies
315
- only `accountName` / `platform` / `isTest`. This webhook is the only source of
316
- `accountId` in the package, since no GraphQL read returns it. `status` and
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
317
326
  `platform` are **nullable** — a value this version doesn't recognise stays `null`
318
327
  (wire value on `rawStatus`) instead of being coerced, so a newer registry can't be
319
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
  }
@@ -3561,25 +3562,27 @@ var PeekAccessService = class {
3561
3562
  bookingService;
3562
3563
  reviewService;
3563
3564
  constructor(config) {
3565
+ const hasApiUrl = !!config.apiUrl;
3564
3566
  const isV2 = config.mode === "v2";
3565
3567
  requireNonEmpty(config.installId, "installId", "PeekAccessService");
3566
3568
  requireNonEmpty(config.jwtSecret, "jwtSecret", "PeekAccessService");
3567
3569
  requireNonEmpty(config.issuer, "issuer", "PeekAccessService");
3568
- requireNonEmpty(config.appId, "appId", "PeekAccessService");
3569
- if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
3570
+ if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "PeekAccessService");
3571
+ if (!hasApiUrl && !isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
3570
3572
  this.jwtSecret = config.jwtSecret;
3571
3573
  const logger = config.logger ?? noopLogger;
3572
3574
  const tokens = createTokenManager(config);
3573
3575
  const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
3574
3576
  this.client = new GraphQLClient({
3575
- baseUrl: config.baseUrl ?? defaultBaseUrl,
3576
- appId: config.appId,
3577
+ apiUrl: config.apiUrl,
3578
+ baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? defaultBaseUrl,
3579
+ appId: hasApiUrl ? void 0 : config.appId,
3577
3580
  gatewayKey: config.gatewayKey,
3578
3581
  getToken: () => tokens.getToken(),
3579
3582
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
3580
3583
  logger,
3581
3584
  fetchFn: config.fetch ?? globalThis.fetch,
3582
- endpointPathPrefix: isV2 ? V2_EXTENDABLE_SLUG : void 0
3585
+ endpointPathPrefix: !hasApiUrl && isV2 ? V2_EXTENDABLE_SLUG : void 0
3583
3586
  });
3584
3587
  this.productServiceOptions = {
3585
3588
  itemOptionsPageSize: config.itemOptionsPageSize
@@ -4007,7 +4010,8 @@ var RestClient = class {
4007
4010
  );
4008
4011
  }
4009
4012
  endpoint(path) {
4010
- const { baseUrl, appId, extendableSlug } = this.options;
4013
+ const { apiUrl, baseUrl, appId, extendableSlug } = this.options;
4014
+ if (apiUrl) return `${apiUrl}/${path}`;
4011
4015
  return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
4012
4016
  }
4013
4017
  buildHeaders() {
@@ -4024,15 +4028,17 @@ var CngAccessService = class {
4024
4028
  client;
4025
4029
  productService;
4026
4030
  constructor(config) {
4031
+ const hasApiUrl = !!config.apiUrl;
4027
4032
  requireNonEmpty(config.installId, "installId", "CngAccessService");
4028
4033
  requireNonEmpty(config.jwtSecret, "jwtSecret", "CngAccessService");
4029
4034
  requireNonEmpty(config.issuer, "issuer", "CngAccessService");
4030
- requireNonEmpty(config.appId, "appId", "CngAccessService");
4035
+ if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "CngAccessService");
4031
4036
  const logger = config.logger ?? noopLogger;
4032
4037
  const tokens = createTokenManager(config);
4033
4038
  this.client = new RestClient({
4034
- baseUrl: config.baseUrl ?? DEFAULT_BASE_URL2,
4035
- appId: config.appId,
4039
+ apiUrl: config.apiUrl,
4040
+ baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? DEFAULT_BASE_URL2,
4041
+ appId: hasApiUrl ? void 0 : config.appId,
4036
4042
  extendableSlug: CNG_EXTENDABLE_SLUG,
4037
4043
  getToken: () => tokens.getToken(),
4038
4044
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
@@ -4138,7 +4144,8 @@ var RestClient2 = class {
4138
4144
  );
4139
4145
  }
4140
4146
  endpoint(path) {
4141
- const { baseUrl, appId, extendableSlug } = this.options;
4147
+ const { apiUrl, baseUrl, appId, extendableSlug } = this.options;
4148
+ if (apiUrl) return `${apiUrl}/${path}`;
4142
4149
  return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
4143
4150
  }
4144
4151
  buildHeaders() {
@@ -4155,15 +4162,17 @@ var AcmeAccessService = class {
4155
4162
  client;
4156
4163
  productService;
4157
4164
  constructor(config) {
4165
+ const hasApiUrl = !!config.apiUrl;
4158
4166
  requireNonEmpty(config.installId, "installId", "AcmeAccessService");
4159
4167
  requireNonEmpty(config.jwtSecret, "jwtSecret", "AcmeAccessService");
4160
4168
  requireNonEmpty(config.issuer, "issuer", "AcmeAccessService");
4161
- requireNonEmpty(config.appId, "appId", "AcmeAccessService");
4169
+ if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "AcmeAccessService");
4162
4170
  const logger = config.logger ?? noopLogger;
4163
4171
  const tokens = createTokenManager(config);
4164
4172
  this.client = new RestClient2({
4165
- baseUrl: config.baseUrl ?? DEFAULT_BASE_URL3,
4166
- appId: config.appId,
4173
+ apiUrl: config.apiUrl,
4174
+ baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? DEFAULT_BASE_URL3,
4175
+ appId: hasApiUrl ? void 0 : config.appId,
4167
4176
  extendableSlug: ACME_EXTENDABLE_SLUG,
4168
4177
  getToken: () => tokens.getToken(),
4169
4178
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
@@ -4187,6 +4196,23 @@ var AcmeAccessService = class {
4187
4196
  }
4188
4197
  };
4189
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
+
4190
4216
  // src/internal/peek/bookings/booking-webhook.ts
4191
4217
  var PAYLOAD_BOOKING_KEY = "booking";
4192
4218
  var VALUE_OPEN_TOKEN = "value {";
@@ -4310,20 +4336,27 @@ function toPeekPlatform(raw) {
4310
4336
  // src/internal/peek/installs/install-webhook.ts
4311
4337
  function parseInstallWebhook(token, body, secret) {
4312
4338
  const payload = verifyPeekJwt(token, secret);
4313
- const account = extractInstallWebhookBody(body).account ?? {};
4314
- const rawStatus = payload.status ?? "";
4339
+ const parsed = extractInstallWebhookBody(body);
4340
+ const account = parsed.account ?? {};
4341
+ const rawStatus = parsed.status || payload.status || "";
4315
4342
  return {
4316
- installId: payload.sub ?? "",
4317
- accountId: payload.account?.id ?? "",
4343
+ installId: parsed.install_id || payload.sub || "",
4344
+ accountId: account.id || payload.account?.id || "",
4318
4345
  accountName: account.name || "",
4319
4346
  platform: toPeekPlatform(account.platform || ""),
4320
4347
  isTest: Boolean(account.is_test),
4348
+ timezone: account.timezone || "",
4349
+ apiUrl: parsed.api?.url || "",
4321
4350
  status: toInstallStatus(rawStatus),
4322
4351
  rawStatus,
4323
- displayVersion: payload.display_version ?? "",
4324
- user: payload.user ? mapPeekTokenUser(payload.user) : null
4352
+ displayVersion: parsed.display_version || payload.display_version || "",
4353
+ user: resolveInstallUser(parsed.modified_by, payload.user)
4325
4354
  };
4326
4355
  }
4356
+ function resolveInstallUser(bodyUser, tokenUser) {
4357
+ if (bodyUser?.id) return mapPeekTokenUser(bodyUser);
4358
+ return tokenUser ? mapPeekTokenUser(tokenUser) : null;
4359
+ }
4327
4360
  function verifyInstallWebhook(token, secret) {
4328
4361
  const payload = verifyPeekJwt(token, secret);
4329
4362
  return {
@@ -4364,6 +4397,7 @@ exports.ResellerService = ResellerService;
4364
4397
  exports.ResourcePoolService = ResourcePoolService;
4365
4398
  exports.ReviewService = ReviewService;
4366
4399
  exports.TimeslotService = TimeslotService;
4400
+ exports.createAccessServiceForInstall = createAccessServiceForInstall;
4367
4401
  exports.noopLogger = noopLogger;
4368
4402
  exports.parseBookingWebhook = parseBookingWebhook;
4369
4403
  exports.parseInstallWebhook = parseInstallWebhook;