@peektravel/app-utilities 0.8.1 → 0.8.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/dist/index.cjs CHANGED
@@ -207,6 +207,13 @@ function resolveAccessOptions(options) {
207
207
  }
208
208
 
209
209
  // src/errors.ts
210
+ var FORBIDDEN_STATUS = 403;
211
+ var PERMISSION_MESSAGE_PREFIX = "CNG request forbidden: the app is missing the required permission";
212
+ var PERMISSION_MESSAGE_UNKNOWN = "CNG request forbidden: the app is missing a required permission";
213
+ function buildPermissionMessage(permissions) {
214
+ if (permissions.length === 0) return PERMISSION_MESSAGE_UNKNOWN;
215
+ return `${PERMISSION_MESSAGE_PREFIX}${permissions.length > 1 ? "s" : ""}: ${permissions.join(", ")}`;
216
+ }
210
217
  var AdminAccountRequiredError = class extends Error {
211
218
  /** The HTTP status that triggered this error. */
212
219
  statusCode = 418;
@@ -270,6 +277,23 @@ var CngApiError = class extends Error {
270
277
  this.body = body;
271
278
  }
272
279
  };
280
+ var CngPermissionError = class extends Error {
281
+ /** The HTTP status that triggered this error. */
282
+ statusCode = FORBIDDEN_STATUS;
283
+ /**
284
+ * The permissions the gateway reported as missing, e.g. `["products:read"]`.
285
+ * Empty when the response body did not name any.
286
+ */
287
+ permissions;
288
+ /** The raw response body (parsed JSON when possible, otherwise text). */
289
+ body;
290
+ constructor(permissions, body, message) {
291
+ super(message ?? buildPermissionMessage(permissions));
292
+ this.name = "CngPermissionError";
293
+ this.permissions = permissions;
294
+ this.body = body;
295
+ }
296
+ };
273
297
  var AcmeApiError = class extends Error {
274
298
  /** The HTTP status that triggered this error. */
275
299
  statusCode;
@@ -4156,7 +4180,7 @@ var PeekAccessService = class {
4156
4180
 
4157
4181
  // src/internal/cng/endpoints.ts
4158
4182
  var CNG_EXTENDABLE_SLUG = "cng_backoffice_api-v1";
4159
- var PRODUCTS_PATH = "api/v2/commerce-config/products";
4183
+ var PRODUCTS_PATH = "api/v2/app-registry/products?active=1";
4160
4184
 
4161
4185
  // src/models/cng/product.ts
4162
4186
  var ACTIVITY_PRODUCT_TYPE2 = "ACTIVITY";
@@ -4167,14 +4191,11 @@ function fromProductNodes(nodes) {
4167
4191
  }
4168
4192
  function fromProductNode(node) {
4169
4193
  return {
4170
- productId: node.id || "",
4194
+ productId: node.id == null ? "" : String(node.id),
4171
4195
  name: node.name || "",
4172
- type: node.product_type || ACTIVITY_PRODUCT_TYPE2,
4173
- color: node.color_hex || "",
4174
- tickets: (node.tickets ?? []).map((ticket) => ({
4175
- id: ticket.id,
4176
- name: ticket.name
4177
- }))
4196
+ type: ACTIVITY_PRODUCT_TYPE2,
4197
+ color: node.access_control_color_hex || "",
4198
+ tickets: []
4178
4199
  };
4179
4200
  }
4180
4201
 
@@ -4193,15 +4214,13 @@ var CngProductService = class {
4193
4214
  * ```
4194
4215
  */
4195
4216
  async getAllActivities() {
4196
- const body = await this.client.get(
4197
- PRODUCTS_PATH
4198
- );
4199
- const nodes = Array.isArray(body) ? body : body?.products ?? [];
4200
- return fromProductNodes(nodes ?? []);
4217
+ const body = await this.client.get(PRODUCTS_PATH);
4218
+ return fromProductNodes(body?.data ?? []);
4201
4219
  }
4202
4220
  };
4203
4221
 
4204
4222
  // src/internal/cng/rest-client.ts
4223
+ var PERMISSION_SEPARATOR = ": ";
4205
4224
  var RestClient = class {
4206
4225
  constructor(options) {
4207
4226
  this.options = options;
@@ -4214,6 +4233,7 @@ var RestClient = class {
4214
4233
  *
4215
4234
  * @throws {AdminAccountRequiredError} on HTTP 418
4216
4235
  * @throws {RateLimitError} on HTTP 429 after retries are exhausted
4236
+ * @throws {CngPermissionError} on HTTP 403 (app missing a permission)
4217
4237
  * @throws {CngApiError} on any other non-2xx response
4218
4238
  */
4219
4239
  async get(path) {
@@ -4227,6 +4247,11 @@ var RestClient = class {
4227
4247
  path,
4228
4248
  async (response) => {
4229
4249
  const body = await parseBody(response);
4250
+ if (response.status === FORBIDDEN_STATUS) {
4251
+ const permissions = extractMissingPermissions(body);
4252
+ logger.warn(`Missing permission for ${path} (HTTP 403)`, { url, permissions });
4253
+ throw new CngPermissionError(permissions, body);
4254
+ }
4230
4255
  if (!response.ok) {
4231
4256
  logger.error(`CNG request failed with HTTP ${response.status}`, { url });
4232
4257
  throw new CngApiError(response.status, body);
@@ -4247,6 +4272,12 @@ var RestClient = class {
4247
4272
  };
4248
4273
  }
4249
4274
  };
4275
+ function extractMissingPermissions(body) {
4276
+ if (typeof body !== "object" || body === null) return [];
4277
+ const raw = body.errors?.permission;
4278
+ if (!Array.isArray(raw)) return [];
4279
+ return raw.filter((entry) => typeof entry === "string").map((entry) => entry.split(PERMISSION_SEPARATOR).pop().trim()).filter((permission) => permission.length > 0);
4280
+ }
4250
4281
 
4251
4282
  // src/cng-access-service.ts
4252
4283
  var DEFAULT_BASE_URL2 = "https://app-registry.peeklabs.com/installations-api";
@@ -4602,6 +4633,7 @@ exports.AvailabilityService = AvailabilityService;
4602
4633
  exports.BookingService = BookingService;
4603
4634
  exports.CngAccessService = CngAccessService;
4604
4635
  exports.CngApiError = CngApiError;
4636
+ exports.CngPermissionError = CngPermissionError;
4605
4637
  exports.CngProductService = CngProductService;
4606
4638
  exports.DailyNoteService = DailyNoteService;
4607
4639
  exports.INSTALL_STATUSES = INSTALL_STATUSES;