@peektravel/app-utilities 0.7.1 → 0.7.3

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/dist/index.js CHANGED
@@ -225,15 +225,6 @@ var PeekHttpError = class extends Error {
225
225
  this.body = body;
226
226
  }
227
227
  };
228
- var InvalidPeekTokenError = class extends Error {
229
- /** The token field that was missing or empty (e.g. `"user.id"`). */
230
- field;
231
- constructor(field) {
232
- super(`Peek token is missing required field "${field}"`);
233
- this.name = "InvalidPeekTokenError";
234
- this.field = field;
235
- }
236
- };
237
228
  var PiiAccessDisabledError = class extends Error {
238
229
  /** The name of the operation that was blocked (e.g. `"makePayment"`). */
239
230
  operation;
@@ -2022,7 +2013,8 @@ var GraphQLClient = class {
2022
2013
  );
2023
2014
  }
2024
2015
  endpoint(endpointName) {
2025
- const { baseUrl, appId, endpointPathPrefix } = this.options;
2016
+ const { apiUrl, baseUrl, appId, endpointPathPrefix } = this.options;
2017
+ if (apiUrl) return apiUrl;
2026
2018
  const prefix = endpointPathPrefix ? `${endpointPathPrefix}/` : "";
2027
2019
  return `${baseUrl}/${appId}/${prefix}${endpointName}`;
2028
2020
  }
@@ -2037,6 +2029,11 @@ var GraphQLClient = class {
2037
2029
  return headers;
2038
2030
  }
2039
2031
  };
2032
+
2033
+ // src/models/peek/auth-token.ts
2034
+ var PEEK_PLATFORMS = ["peek", "cng", "acme"];
2035
+
2036
+ // src/internal/peek/peek-auth-token.ts
2040
2037
  var PEEK_TOKEN_ISSUER = "app_registry_v2";
2041
2038
  var PEEK_TOKEN_AUDIENCE = "Joken";
2042
2039
  var BEARER_PREFIX = "bearer ";
@@ -2044,6 +2041,16 @@ function stripBearer(token) {
2044
2041
  const trimmed = token.trim();
2045
2042
  return trimmed.toLowerCase().startsWith(BEARER_PREFIX) ? trimmed.slice(BEARER_PREFIX.length).trim() : trimmed;
2046
2043
  }
2044
+ var NULLISH_STRINGS = /* @__PURE__ */ new Set(["", "null"]);
2045
+ function cleanTokenString(value) {
2046
+ if (typeof value !== "string") return null;
2047
+ const trimmed = value.trim();
2048
+ return NULLISH_STRINGS.has(trimmed.toLowerCase()) ? null : trimmed;
2049
+ }
2050
+ function cleanTokenPlatform(value) {
2051
+ const cleaned = cleanTokenString(value);
2052
+ return cleaned !== null && PEEK_PLATFORMS.includes(cleaned) ? cleaned : null;
2053
+ }
2047
2054
  function verifyPeekJwt(token, secret) {
2048
2055
  return jwt.verify(stripBearer(token), secret, {
2049
2056
  issuer: PEEK_TOKEN_ISSUER,
@@ -2051,16 +2058,14 @@ function verifyPeekJwt(token, secret) {
2051
2058
  });
2052
2059
  }
2053
2060
  function mapPeekTokenUser(u) {
2054
- if (!u.id) {
2055
- throw new InvalidPeekTokenError("user.id");
2056
- }
2061
+ if (!u) return null;
2057
2062
  return {
2058
- email: u.email,
2059
- id: u.id,
2060
- isAdmin: u.is_admin,
2061
- locale: u.locale,
2062
- name: u.name,
2063
- platform: u.platform
2063
+ email: cleanTokenString(u.email),
2064
+ id: cleanTokenString(u.id),
2065
+ isAdmin: typeof u.is_admin === "boolean" ? u.is_admin : null,
2066
+ locale: cleanTokenString(u.locale),
2067
+ name: cleanTokenString(u.name),
2068
+ platform: cleanTokenPlatform(u.platform)
2064
2069
  };
2065
2070
  }
2066
2071
 
@@ -3539,25 +3544,27 @@ var PeekAccessService = class {
3539
3544
  bookingService;
3540
3545
  reviewService;
3541
3546
  constructor(config) {
3547
+ const hasApiUrl = !!config.apiUrl;
3542
3548
  const isV2 = config.mode === "v2";
3543
3549
  requireNonEmpty(config.installId, "installId", "PeekAccessService");
3544
3550
  requireNonEmpty(config.jwtSecret, "jwtSecret", "PeekAccessService");
3545
3551
  requireNonEmpty(config.issuer, "issuer", "PeekAccessService");
3546
- requireNonEmpty(config.appId, "appId", "PeekAccessService");
3547
- if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
3552
+ if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "PeekAccessService");
3553
+ if (!hasApiUrl && !isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
3548
3554
  this.jwtSecret = config.jwtSecret;
3549
3555
  const logger = config.logger ?? noopLogger;
3550
3556
  const tokens = createTokenManager(config);
3551
3557
  const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
3552
3558
  this.client = new GraphQLClient({
3553
- baseUrl: config.baseUrl ?? defaultBaseUrl,
3554
- appId: config.appId,
3559
+ apiUrl: config.apiUrl,
3560
+ baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? defaultBaseUrl,
3561
+ appId: hasApiUrl ? void 0 : config.appId,
3555
3562
  gatewayKey: config.gatewayKey,
3556
3563
  getToken: () => tokens.getToken(),
3557
3564
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
3558
3565
  logger,
3559
3566
  fetchFn: config.fetch ?? globalThis.fetch,
3560
- endpointPathPrefix: isV2 ? V2_EXTENDABLE_SLUG : void 0
3567
+ endpointPathPrefix: !hasApiUrl && isV2 ? V2_EXTENDABLE_SLUG : void 0
3561
3568
  });
3562
3569
  this.productServiceOptions = {
3563
3570
  itemOptionsPageSize: config.itemOptionsPageSize
@@ -3578,13 +3585,15 @@ var PeekAccessService = class {
3578
3585
  * malformed
3579
3586
  * - `TokenExpiredError` — past `exp`
3580
3587
  * - `NotBeforeError` — before `nbf`
3581
- * - `InvalidPeekTokenError` — signature valid but the `user` block is missing
3582
- * its `id`
3588
+ *
3589
+ * Claim *extraction* is defensive: once the signature verifies, a token with
3590
+ * no `user` block yields `claims.user === null`, and any individual user field
3591
+ * that is absent or a sentinel (`""`, `"null"`) is normalized to `null`. This
3592
+ * method never throws on a shape problem — only on a verification failure.
3583
3593
  *
3584
3594
  * @throws {JsonWebTokenError} signature invalid or token malformed
3585
3595
  * @throws {TokenExpiredError} token has expired
3586
3596
  * @throws {NotBeforeError} token not yet valid
3587
- * @throws {InvalidPeekTokenError} verified token has no `user.id`
3588
3597
  */
3589
3598
  verifyPeekAuthToken(token) {
3590
3599
  const payload = verifyPeekJwt(token, this.jwtSecret);
@@ -3985,7 +3994,8 @@ var RestClient = class {
3985
3994
  );
3986
3995
  }
3987
3996
  endpoint(path) {
3988
- const { baseUrl, appId, extendableSlug } = this.options;
3997
+ const { apiUrl, baseUrl, appId, extendableSlug } = this.options;
3998
+ if (apiUrl) return `${apiUrl}/${path}`;
3989
3999
  return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
3990
4000
  }
3991
4001
  buildHeaders() {
@@ -4002,15 +4012,17 @@ var CngAccessService = class {
4002
4012
  client;
4003
4013
  productService;
4004
4014
  constructor(config) {
4015
+ const hasApiUrl = !!config.apiUrl;
4005
4016
  requireNonEmpty(config.installId, "installId", "CngAccessService");
4006
4017
  requireNonEmpty(config.jwtSecret, "jwtSecret", "CngAccessService");
4007
4018
  requireNonEmpty(config.issuer, "issuer", "CngAccessService");
4008
- requireNonEmpty(config.appId, "appId", "CngAccessService");
4019
+ if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "CngAccessService");
4009
4020
  const logger = config.logger ?? noopLogger;
4010
4021
  const tokens = createTokenManager(config);
4011
4022
  this.client = new RestClient({
4012
- baseUrl: config.baseUrl ?? DEFAULT_BASE_URL2,
4013
- appId: config.appId,
4023
+ apiUrl: config.apiUrl,
4024
+ baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? DEFAULT_BASE_URL2,
4025
+ appId: hasApiUrl ? void 0 : config.appId,
4014
4026
  extendableSlug: CNG_EXTENDABLE_SLUG,
4015
4027
  getToken: () => tokens.getToken(),
4016
4028
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
@@ -4116,7 +4128,8 @@ var RestClient2 = class {
4116
4128
  );
4117
4129
  }
4118
4130
  endpoint(path) {
4119
- const { baseUrl, appId, extendableSlug } = this.options;
4131
+ const { apiUrl, baseUrl, appId, extendableSlug } = this.options;
4132
+ if (apiUrl) return `${apiUrl}/${path}`;
4120
4133
  return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
4121
4134
  }
4122
4135
  buildHeaders() {
@@ -4133,15 +4146,17 @@ var AcmeAccessService = class {
4133
4146
  client;
4134
4147
  productService;
4135
4148
  constructor(config) {
4149
+ const hasApiUrl = !!config.apiUrl;
4136
4150
  requireNonEmpty(config.installId, "installId", "AcmeAccessService");
4137
4151
  requireNonEmpty(config.jwtSecret, "jwtSecret", "AcmeAccessService");
4138
4152
  requireNonEmpty(config.issuer, "issuer", "AcmeAccessService");
4139
- requireNonEmpty(config.appId, "appId", "AcmeAccessService");
4153
+ if (!hasApiUrl) requireNonEmpty(config.appId ?? "", "appId", "AcmeAccessService");
4140
4154
  const logger = config.logger ?? noopLogger;
4141
4155
  const tokens = createTokenManager(config);
4142
4156
  this.client = new RestClient2({
4143
- baseUrl: config.baseUrl ?? DEFAULT_BASE_URL3,
4144
- appId: config.appId,
4157
+ apiUrl: config.apiUrl,
4158
+ baseUrl: hasApiUrl ? void 0 : config.baseUrl ?? DEFAULT_BASE_URL3,
4159
+ appId: hasApiUrl ? void 0 : config.appId,
4145
4160
  extendableSlug: ACME_EXTENDABLE_SLUG,
4146
4161
  getToken: () => tokens.getToken(),
4147
4162
  retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
@@ -4165,6 +4180,23 @@ var AcmeAccessService = class {
4165
4180
  }
4166
4181
  };
4167
4182
 
4183
+ // src/access-service-factory.ts
4184
+ function createAccessServiceForInstall(install, config) {
4185
+ const shared = { ...config, installId: install.installId, apiUrl: install.apiUrl };
4186
+ switch (install.platform) {
4187
+ case "peek":
4188
+ return new PeekAccessService(shared);
4189
+ case "cng":
4190
+ return new CngAccessService(shared);
4191
+ case "acme":
4192
+ return new AcmeAccessService(shared);
4193
+ default:
4194
+ throw new Error(
4195
+ `createAccessServiceForInstall: unknown or missing platform "${install.platform}" for install "${install.installId}"`
4196
+ );
4197
+ }
4198
+ }
4199
+
4168
4200
  // src/internal/peek/bookings/booking-webhook.ts
4169
4201
  var PAYLOAD_BOOKING_KEY = "booking";
4170
4202
  var VALUE_OPEN_TOKEN = "value {";
@@ -4255,9 +4287,6 @@ function extractWaiverNode(payload) {
4255
4287
  return record;
4256
4288
  }
4257
4289
 
4258
- // src/models/peek/auth-token.ts
4259
- var PEEK_PLATFORMS = ["peek", "cng", "acme"];
4260
-
4261
4290
  // src/models/peek/install.ts
4262
4291
  var INSTALL_STATUSES = [
4263
4292
  "installed",
@@ -4288,20 +4317,27 @@ function toPeekPlatform(raw) {
4288
4317
  // src/internal/peek/installs/install-webhook.ts
4289
4318
  function parseInstallWebhook(token, body, secret) {
4290
4319
  const payload = verifyPeekJwt(token, secret);
4291
- const account = extractInstallWebhookBody(body).account ?? {};
4292
- const rawStatus = payload.status ?? "";
4320
+ const parsed = extractInstallWebhookBody(body);
4321
+ const account = parsed.account ?? {};
4322
+ const rawStatus = parsed.status || payload.status || "";
4293
4323
  return {
4294
- installId: payload.sub ?? "",
4295
- accountId: payload.account?.id ?? "",
4324
+ installId: parsed.install_id || payload.sub || "",
4325
+ accountId: account.id || payload.account?.id || "",
4296
4326
  accountName: account.name || "",
4297
4327
  platform: toPeekPlatform(account.platform || ""),
4298
4328
  isTest: Boolean(account.is_test),
4329
+ timezone: account.timezone || "",
4330
+ apiUrl: parsed.api?.url || "",
4299
4331
  status: toInstallStatus(rawStatus),
4300
4332
  rawStatus,
4301
- displayVersion: payload.display_version ?? "",
4302
- user: payload.user ? mapPeekTokenUser(payload.user) : null
4333
+ displayVersion: parsed.display_version || payload.display_version || "",
4334
+ user: resolveInstallUser(parsed.modified_by, payload.user)
4303
4335
  };
4304
4336
  }
4337
+ function resolveInstallUser(bodyUser, tokenUser) {
4338
+ if (cleanTokenString(bodyUser?.id) !== null) return mapPeekTokenUser(bodyUser);
4339
+ return mapPeekTokenUser(tokenUser);
4340
+ }
4305
4341
  function verifyInstallWebhook(token, secret) {
4306
4342
  const payload = verifyPeekJwt(token, secret);
4307
4343
  return {
@@ -4309,10 +4345,10 @@ function verifyInstallWebhook(token, secret) {
4309
4345
  account: { id: payload.account?.id ?? "" },
4310
4346
  status: payload.status ?? "",
4311
4347
  displayVersion: payload.display_version ?? "",
4312
- user: payload.user ? mapPeekTokenUser(payload.user) : null
4348
+ user: mapPeekTokenUser(payload.user)
4313
4349
  };
4314
4350
  }
4315
4351
 
4316
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AcmeAccessService, AcmeApiError, AcmeProductService, AdminAccountRequiredError, AvailabilityService, BookingService, CngAccessService, CngApiError, CngProductService, DailyNoteService, INSTALL_STATUSES, InvalidPeekTokenError, MembershipService, PeekAccessService, PeekGraphQLError, PeekHttpError, PiiAccessDisabledError, PricingService, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
4352
+ export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AcmeAccessService, AcmeApiError, AcmeProductService, AdminAccountRequiredError, AvailabilityService, BookingService, CngAccessService, CngApiError, CngProductService, DailyNoteService, INSTALL_STATUSES, MembershipService, PeekAccessService, PeekGraphQLError, PeekHttpError, PiiAccessDisabledError, PricingService, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, createAccessServiceForInstall, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
4317
4353
  //# sourceMappingURL=index.js.map
4318
4354
  //# sourceMappingURL=index.js.map