@peektravel/app-utilities 0.2.9 → 0.3.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/dist/index.cjs CHANGED
@@ -25,11 +25,11 @@ var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
25
25
 
26
26
  // src/peek-access-service.ts
27
27
 
28
- // src/internal/gateway-endpoints.ts
28
+ // src/internal/peek/gateway-endpoints.ts
29
29
  var SALES_ENDPOINT = "sales";
30
30
  var V2_EXTENDABLE_SLUG = "peek_backoffice_api-v1";
31
31
 
32
- // src/internal/account-users/account-user-converter.ts
32
+ // src/internal/peek/account-users/account-user-converter.ts
33
33
  var ACTIVE_STATUS = "ACTIVE";
34
34
  function fromAccountUserNode(node) {
35
35
  if (!node || node.status !== ACTIVE_STATUS) {
@@ -57,7 +57,7 @@ function fromAccountUserNodes(nodes) {
57
57
  return users;
58
58
  }
59
59
 
60
- // src/internal/account-users/account-user-queries.ts
60
+ // src/internal/peek/account-users/account-user-queries.ts
61
61
  var USER_QUERY = `
62
62
  query Sales($first: Int, $after: String) {
63
63
  accountUsers(first: $first, after: $after) {
@@ -103,7 +103,7 @@ var USER_BY_FILTER_QUERY = `
103
103
  }
104
104
  `;
105
105
 
106
- // src/internal/account-users/account-user-service.ts
106
+ // src/internal/peek/account-users/account-user-service.ts
107
107
  var DEFAULT_PAGE_SIZE = 50;
108
108
  var AccountUserService = class {
109
109
  constructor(client, options = {}) {
@@ -157,7 +157,7 @@ var AccountUserService = class {
157
157
  }
158
158
  };
159
159
 
160
- // src/internal/availability/availability-queries.ts
160
+ // src/internal/peek/availability/availability-queries.ts
161
161
  var AVAILABILITY_TIMES_QUERY = `
162
162
  query Sales($activityId: ID!, $resourceOptionQuantities: [ResourceOptionQuantityData!]!, $date: Date!) {
163
163
  availabilityTimes(activityId: $activityId, resourceOptionQuantities: $resourceOptionQuantities, date: $date) {
@@ -182,7 +182,7 @@ var AVAILABILITY_TIMES_QUERY = `
182
182
  }
183
183
  `;
184
184
 
185
- // src/internal/availability/availability-service.ts
185
+ // src/internal/peek/availability/availability-service.ts
186
186
  var AvailabilityService = class {
187
187
  constructor(client) {
188
188
  this.client = client;
@@ -203,13 +203,375 @@ var AvailabilityService = class {
203
203
  }
204
204
  };
205
205
 
206
- // src/models/product.ts
206
+ // src/models/peek/product.ts
207
207
  var ACTIVITY_PRODUCT_TYPE = "ACTIVITY";
208
208
  var RENTAL_PRODUCT_TYPE = "RENTAL";
209
209
  var ADD_ON_PRODUCT_TYPE = "ADD-ON";
210
210
 
211
- // src/internal/bookings/booking-converter.ts
211
+ // src/internal/peek/bookings/booking-queries.ts
212
+ var SEARCH_BY_PURCHASE_DATE = "purchaseDate";
213
+ var SEARCH_BY_ACTIVITY_DATE = "activityDate";
214
+ function normalizeBookingId(bookingId) {
215
+ return bookingId.toLowerCase().replace(/-/g, "_");
216
+ }
217
+ var guestFields = `
218
+ id
219
+ name
220
+ country
221
+ dateOfBirth
222
+ email
223
+ isGdpr
224
+ isParticipant
225
+ optinSms
226
+ optinMarketing
227
+ phone
228
+ postalCode
229
+ fieldResponses {
230
+ id
231
+ text
232
+ fieldLocation {
233
+ field {
234
+ name
235
+ }
236
+ }
237
+ }
238
+ `;
239
+ var bookingGuestsFields = `
240
+ bookingGuests {
241
+ ${guestFields}
242
+ }
243
+ primaryGuest {
244
+ ${guestFields}
245
+ }
246
+ `;
247
+ var bookingQueryFields = `
248
+ displayId
249
+ id
250
+ primaryGuest {
251
+ name
252
+ email
253
+ phone
254
+ optinMarketing
255
+ optinSms
256
+ isGdpr
257
+ postalCode
258
+ }
259
+ activitySnapshot {
260
+ type
261
+ name
262
+ id
263
+ }
264
+ ticketQuantities {
265
+ quantity
266
+ resourceOptionSnapshot {
267
+ name
268
+ id
269
+ }
270
+ }
271
+ reservationStatus
272
+ checkinStatus
273
+ returnStatus
274
+ fulfillmentStatusOverride {
275
+ status
276
+ }
277
+ timeSnapshot {
278
+ id
279
+ legacyId
280
+ }
281
+ purchasedAt
282
+ purchasedAtUtc
283
+ startsAt
284
+ startsAtUtc
285
+ endsAt
286
+ endsAtUtc
287
+ availabilityTimeId
288
+ bookingPortalUrl
289
+ operatorNotes
290
+ value {
291
+ total {
292
+ formatted
293
+ amount
294
+ }
295
+ }
296
+ balance {
297
+ total {
298
+ amount
299
+ formatted
300
+ }
301
+ }
302
+ tips {
303
+ price {
304
+ amount
305
+ formatted
306
+ }
307
+ }
308
+ order {
309
+ displayId
310
+ id
311
+ promoCodes {
312
+ code
313
+ }
314
+ channelSnapshot {
315
+ id
316
+ name
317
+ agent {
318
+ name
319
+ }
320
+ }
321
+ initialQuote {
322
+ source {
323
+ actor {
324
+ app
325
+ name
326
+ }
327
+ }
328
+ }
329
+ }
330
+ questionAnswers {
331
+ answer
332
+ questionText
333
+ questionLocationSnapshot {
334
+ latitude
335
+ longitude
336
+ }
337
+ }
338
+ tickets {
339
+ questionAnswers {
340
+ answer
341
+ questionText
342
+ }
343
+ }
344
+ resourcePoolAssignments {
345
+ quantity
346
+ resourcePool {
347
+ name
348
+ shortName
349
+ resources {
350
+ name
351
+ }
352
+ }
353
+ resourceAssignments {
354
+ resource {
355
+ id
356
+ name
357
+ }
358
+ }
359
+ }
360
+ `;
361
+ var PRICE_BREAKDOWN_FIELDS = `
362
+ convenienceFee { amount formatted }
363
+ deposit { amount formatted }
364
+ discount { amount formatted }
365
+ discountedPrice { amount formatted }
366
+ fees { amount formatted }
367
+ flatPartnerFee { amount formatted }
368
+ price { amount formatted }
369
+ retailPrice { amount formatted }
370
+ taxes { amount formatted }
371
+ tips { amount formatted }
372
+ `;
373
+ var TICKET_VALUE_FIELDS = `
374
+ value {
375
+ price { amount formatted }
376
+ total { amount formatted }
377
+ }
378
+ `;
379
+ function buildBookingsListingQuery(includeGuests, includePriceBreakdown) {
380
+ const guestsSection = includeGuests ? bookingGuestsFields : "";
381
+ const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`).replace("ticketQuantities {", `ticketQuantities { ${TICKET_VALUE_FIELDS}`) : bookingQueryFields;
382
+ return `
383
+ query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
384
+ sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
385
+ pageInfo { endCursor hasNextPage }
386
+ edges {
387
+ node {
388
+ ... on Booking {
389
+ ${fields}
390
+ ${guestsSection}
391
+ }
392
+ }
393
+ }
394
+ }
395
+ }
396
+ `;
397
+ }
398
+ var BOOKING_GUESTS_QUERY = `
399
+ query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
400
+ sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
401
+ pageInfo { endCursor hasNextPage }
402
+ edges {
403
+ node {
404
+ ... on Booking {
405
+ displayId
406
+ id
407
+ ${bookingGuestsFields}
408
+ }
409
+ }
410
+ }
411
+ }
412
+ }
413
+ `;
414
+ var BOOKING_PAYMENTS_ON_FILE_QUERY = `
415
+ query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
416
+ sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
417
+ pageInfo { endCursor hasNextPage }
418
+ edges {
419
+ node {
420
+ order {
421
+ payments {
422
+ id
423
+ paymentSource { id }
424
+ appliedAt
425
+ currentAmount { amount currency }
426
+ refundableAmount { amount currency }
427
+ }
428
+ id
429
+ displayId
430
+ paymentSources { description type id }
431
+ }
432
+ ... on Booking {
433
+ displayId
434
+ id
435
+ }
436
+ }
437
+ }
438
+ }
439
+ }
440
+ `;
441
+ var UPDATE_OPERATOR_NOTES_MUTATION = `
442
+ mutation Account($input: UpdateOperatorNotesForBookingInput!) {
443
+ updateOperatorNotesForBooking(input: $input) {
444
+ booking { operatorNotes }
445
+ }
446
+ }
447
+ `;
448
+ var UPDATE_BOOKING_CHECKIN_MUTATION = `
449
+ mutation Account($input: UpdateBookingCheckInInput!) {
450
+ updateBookingCheckIn(input: $input) {
451
+ booking { checkinStatus }
452
+ }
453
+ }
454
+ `;
455
+ var CANCEL_BOOKING_MUTATION = `
456
+ mutation Account($input: CancelBookingInput!) {
457
+ cancelBooking(input: $input) {
458
+ booking { id displayId reservationStatus }
459
+ }
460
+ }
461
+ `;
462
+ var APPLY_PAYMENT_TO_ORDER_MUTATION = `
463
+ mutation ApplyPaymentToOrder($input: ApplyPaymentToOrderInput!) {
464
+ applyPaymentToOrder(input: $input) {
465
+ transactionId
466
+ errors { code detail value }
467
+ }
468
+ }
469
+ `;
470
+ var APPLY_REFUND_TO_ORDER_MUTATION = `
471
+ mutation ApplyRefundToOrder($input: ApplyRefundToOrderInput!) {
472
+ applyRefundToOrder(input: $input) {
473
+ transactionId
474
+ errors { code detail value }
475
+ }
476
+ }
477
+ `;
478
+ var CREATE_INVOICE_LINK_MUTATION = `
479
+ mutation CreateInvoiceLink($input: CreateInvoiceLinkInput!) {
480
+ createInvoiceLink(input: $input) {
481
+ invoiceLink { status url }
482
+ errors { code detail value __typename }
483
+ }
484
+ }
485
+ `;
486
+ var CREATE_QUOTE_FROM_ORDER_MUTATION = `
487
+ mutation CreateQuoteFromOrder($input: CreateQuoteFromOrderInput!) {
488
+ createQuoteFromOrder(input: $input) {
489
+ errors { detail value code }
490
+ quote {
491
+ id
492
+ saleQuotes { refid reservationStatus }
493
+ }
494
+ }
495
+ }
496
+ `;
497
+ var UPDATE_QUOTE_V2_MUTATION = `
498
+ mutation UpdateQuoteV2($input: UpdateQuoteV2Input!) {
499
+ updateQuoteV2(input: $input) {
500
+ errors { detail value code }
501
+ quote { id }
502
+ }
503
+ }
504
+ `;
505
+ var AMEND_ORDER_MUTATION = `
506
+ mutation AmendOrder($input: AmendOrderInput!) {
507
+ amendOrder(input: $input) {
508
+ errors { code detail value }
509
+ order { id }
510
+ }
511
+ }
512
+ `;
513
+ var CREATE_QUOTE_V2_MUTATION = `
514
+ mutation CreateQuoteV2($input: CreateQuoteV2Input!) {
515
+ createQuoteV2(input: $input) {
516
+ errors { detail value code }
517
+ quote { id }
518
+ }
519
+ }
520
+ `;
521
+ var CREATE_ORDER_FROM_QUOTE_MUTATION = `
522
+ mutation CreateOrderFromQuote($input: CreateOrderFromQuoteInput!) {
523
+ createOrderFromQuote(input: $input) {
524
+ errors { code detail value }
525
+ order {
526
+ id
527
+ sales {
528
+ id
529
+ displayId
530
+ ... on Booking {
531
+ balance { total { amount currency formatted } }
532
+ }
533
+ }
534
+ }
535
+ }
536
+ }
537
+ `;
538
+ function buildBookingsVariables(params) {
539
+ const filter = {};
540
+ const bookingFilter = {};
541
+ if (params.bookingId) {
542
+ bookingFilter.ids = [normalizeBookingId(params.bookingId)];
543
+ } else if (params.timeslotId) {
544
+ bookingFilter.timeslotRefid = params.timeslotId;
545
+ } else {
546
+ if (params.searchBy === SEARCH_BY_ACTIVITY_DATE) {
547
+ bookingFilter.overlapsRange = `[${params.startDateTime},${params.endDateTime}]`;
548
+ } else if (params.searchBy === SEARCH_BY_PURCHASE_DATE) {
549
+ filter.purchasedAtRangeUtc = `[${params.startDateTime},${params.endDateTime}]`;
550
+ }
551
+ if (params.productId) {
552
+ bookingFilter.activityIds = [params.productId];
553
+ }
554
+ }
555
+ if (params.email) {
556
+ filter.primaryGuestEmail = params.email;
557
+ }
558
+ if (params.searchString && params.searchString.length > 0) {
559
+ filter.searchString = params.searchString;
560
+ }
561
+ if (Object.keys(bookingFilter).length > 0) {
562
+ filter.bookingFilter = bookingFilter;
563
+ }
564
+ return {
565
+ first: params.pageSize,
566
+ after: params.after,
567
+ orderBy: { direction: "ASC", field: "STARTS_AT" },
568
+ filter
569
+ };
570
+ }
571
+
572
+ // src/internal/peek/bookings/booking-converter.ts
212
573
  var UNKNOWN = "unknown";
574
+ var PEEK_PRO_DEEP_LINK_BASE = "http://pro-app.peek.com/-/order";
213
575
  var SOURCE_SOURCE_MAP = {
214
576
  APP_REGISTRY: "app",
215
577
  BOOKING_IMPORTER_FUTURE: "importer",
@@ -325,6 +687,7 @@ function fromBookingNode(node, includeGuests = false, includePriceBreakdown = fa
325
687
  resellerId: data.order?.channelSnapshot?.id || null,
326
688
  resellerName: resellerNameFromChannelSnapshot(data.order?.channelSnapshot),
327
689
  orderId: data.order?.id || "",
690
+ peekProBookingDeepLink: buildPeekProBookingDeepLink(data.order?.id, data.id),
328
691
  convenienceFee: includePriceBreakdown ? mapPrice(data.value?.convenienceFee) : void 0,
329
692
  deposit: includePriceBreakdown ? mapPrice(data.value?.deposit) : void 0,
330
693
  discount: includePriceBreakdown ? mapPrice(data.value?.discount) : void 0,
@@ -352,618 +715,261 @@ function convertGuests(data) {
352
715
  }
353
716
  return guests;
354
717
  }
355
- function mapGuestNode(guestNode, isPrimary) {
356
- const fieldResponses = Array.isArray(guestNode.fieldResponses) ? guestNode.fieldResponses : [];
357
- const metadata = fieldResponses.map((response) => ({
358
- id: response.id,
359
- name: response.fieldLocation?.field?.name ?? "",
360
- value: response.text ?? ""
361
- }));
362
- return {
363
- id: guestNode.id,
364
- name: guestNode.name ?? null,
365
- country: guestNode.country ?? null,
366
- dateOfBirth: guestNode.dateOfBirth ? new Date(guestNode.dateOfBirth) : null,
367
- phone: guestNode.phone ?? null,
368
- email: guestNode.email ?? null,
369
- isGdpr: Boolean(guestNode.isGdpr),
370
- isParticipant: Boolean(guestNode.isParticipant),
371
- isPrimary,
372
- optinSms: Boolean(guestNode.optinSms),
373
- optinMarketing: Boolean(guestNode.optinMarketing),
374
- postalCode: guestNode.postalCode ?? null,
375
- metadata
376
- };
377
- }
378
- function sourceFromApp(app) {
379
- if (!app) return UNKNOWN;
380
- return SOURCE_SOURCE_MAP[app] ?? UNKNOWN;
381
- }
382
- function sourceDescriptionFromApp(app) {
383
- if (!app) return UNKNOWN;
384
- return SOURCE_DESC_MAP[app] ?? UNKNOWN;
385
- }
386
- function resellerNameFromChannelSnapshot(channelSnapshot) {
387
- if (!channelSnapshot) return null;
388
- let out = channelSnapshot.name ?? "";
389
- if (channelSnapshot.agent?.name) {
390
- out += " - " + channelSnapshot.agent.name;
391
- }
392
- return out;
393
- }
394
- function ticketsToTicketArray(ticketQuantities, includePriceBreakdown) {
395
- if (!ticketQuantities || ticketQuantities.length === 0) return [];
396
- return ticketQuantities.map((ticket) => ({
397
- name: ticket.resourceOptionSnapshot?.name || "Unknown",
398
- quantity: ticket.quantity || 0,
399
- ticketId: ticket.resourceOptionSnapshot?.id || "unknown",
400
- listPrice: includePriceBreakdown ? mapPrice(ticket.value?.price) : void 0,
401
- totalValue: includePriceBreakdown ? mapPrice(ticket.value?.total) : void 0
402
- }));
403
- }
404
- function durationInMin(startsAt, endsAt) {
405
- if (!startsAt || !endsAt) return 0;
406
- const duration = new Date(endsAt).getTime() - new Date(startsAt).getTime();
407
- return Math.floor(duration / 1e3 / 60);
408
- }
409
- function ticketQuantity(ticketQuantities) {
410
- if (!ticketQuantities || ticketQuantities.length === 0) return 0;
411
- return ticketQuantities.reduce((acc, ticket) => acc + (ticket.quantity || 0), 0);
412
- }
413
- function formatTickets(ticketQuantities) {
414
- if (!ticketQuantities || ticketQuantities.length === 0) return "";
415
- return ticketQuantities.map((ticket) => `${ticket.quantity || 0}x ${ticket.resourceOptionSnapshot?.name || "Unknown"}`).join(", ");
416
- }
417
- function mapResourcePoolAssignments(poolAssignments) {
418
- if (!poolAssignments || poolAssignments.length === 0) return [];
419
- return poolAssignments.flatMap(
420
- (pool) => (pool.resourceAssignments ?? []).map((assignment) => ({
421
- id: assignment.resource?.id || "",
422
- name: assignment.resource?.name || ""
423
- }))
424
- );
425
- }
426
- function mapPrice(priceData) {
427
- if (!priceData || !priceData.amount && !priceData.formatted) {
428
- return void 0;
429
- }
430
- return {
431
- amount: priceData.amount || "0",
432
- display: priceData.formatted || ""
433
- };
434
- }
435
-
436
- // src/internal/bookings/booking-guest-converter.ts
437
- function fromBookingGuestsResponse(response) {
438
- const firstEdge = (response?.sales?.edges ?? [])[0];
439
- if (!firstEdge) {
440
- return [];
441
- }
442
- const bookingNode = firstEdge.node;
443
- const primaryGuestNode = bookingNode.primaryGuest;
444
- const bookingGuestsNodes = Array.isArray(bookingNode.bookingGuests) ? bookingNode.bookingGuests : [];
445
- const primaryId = primaryGuestNode?.id;
446
- const guests = [];
447
- for (const guestNode of bookingGuestsNodes) {
448
- guests.push(mapGuestNode(guestNode, primaryId ? guestNode.id === primaryId : false));
449
- }
450
- const hasPrimaryInGuests = primaryId ? bookingGuestsNodes.some((guest) => guest.id === primaryId) : false;
451
- if (!hasPrimaryInGuests && primaryGuestNode) {
452
- guests.push(mapGuestNode(primaryGuestNode, true));
453
- }
454
- return guests;
455
- }
456
-
457
- // src/internal/bookings/payments-on-file-converter.ts
458
- function fromPaymentsOnFileResponse(response, bookingId) {
459
- const firstEdge = (response?.sales?.edges ?? [])[0];
460
- if (!firstEdge) {
461
- return null;
462
- }
463
- const order = firstEdge.node.order;
464
- const orderId = order?.id ?? "";
465
- const rawPaymentSources = order?.paymentSources ?? [];
466
- const rawPayments = order?.payments ?? [];
467
- const paymentsBySourceId = /* @__PURE__ */ new Map();
468
- for (const payment of rawPayments) {
469
- const sourceId = payment.paymentSource?.id;
470
- if (!sourceId) continue;
471
- const mapped = {
472
- id: payment.id,
473
- paidAt: dateOnly(payment.appliedAt),
474
- currentAmount: payment.currentAmount,
475
- refundableAmount: payment.refundableAmount
476
- };
477
- const existing = paymentsBySourceId.get(sourceId);
478
- if (existing) {
479
- existing.push(mapped);
480
- } else {
481
- paymentsBySourceId.set(sourceId, [mapped]);
482
- }
483
- }
484
- const paymentsOnFile = rawPaymentSources.map((source) => {
485
- const payments = paymentsBySourceId.get(source.id);
486
- return {
487
- description: source.description,
488
- id: source.id,
489
- type: source.type,
490
- ...payments ? { payments } : {}
491
- };
492
- });
493
- return { bookingId, orderId, paymentsOnFile };
494
- }
495
- function dateOnly(iso) {
496
- return iso.split("T")[0] ?? iso;
497
- }
498
-
499
- // src/internal/bookings/addon-queries.ts
500
- var RESERVATION_STATUS_CONFIRMED = "CONFIRMED";
501
- var ADDON_OPTION_STATUS_CANCELED = "CANCELED";
502
- var SALES_ADDONS_PAGE_SIZE = 100;
503
- var SALES_ADDONS_QUERY = `
504
- query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
505
- sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
506
- pageInfo { endCursor hasNextPage }
507
- edges {
508
- node {
509
- order { id displayId }
510
- ... on Booking {
511
- displayId
512
- id
513
- refid
514
- reservationStatus
515
- items {
516
- id
517
- refid
518
- value { total { amount currency formatted } }
519
- reservationStatus
520
- options {
521
- refid
522
- reservationStatus
523
- price { amount currency formatted }
524
- itemOptionSnapshot { id name }
525
- itemSnapshot { id name }
526
- }
527
- }
528
- }
529
- }
530
- }
531
- }
532
- }
533
- `;
534
- function buildSalesAddonsVariables(searchString) {
718
+ function mapGuestNode(guestNode, isPrimary) {
719
+ const fieldResponses = Array.isArray(guestNode.fieldResponses) ? guestNode.fieldResponses : [];
720
+ const metadata = fieldResponses.map((response) => ({
721
+ id: response.id,
722
+ name: response.fieldLocation?.field?.name ?? "",
723
+ value: response.text ?? ""
724
+ }));
535
725
  return {
536
- first: SALES_ADDONS_PAGE_SIZE,
537
- after: null,
538
- orderBy: { direction: "DESC", field: "STARTS_AT" },
539
- filter: { searchString }
726
+ id: guestNode.id,
727
+ name: guestNode.name ?? null,
728
+ country: guestNode.country ?? null,
729
+ dateOfBirth: guestNode.dateOfBirth ? new Date(guestNode.dateOfBirth) : null,
730
+ phone: guestNode.phone ?? null,
731
+ email: guestNode.email ?? null,
732
+ isGdpr: Boolean(guestNode.isGdpr),
733
+ isParticipant: Boolean(guestNode.isParticipant),
734
+ isPrimary,
735
+ optinSms: Boolean(guestNode.optinSms),
736
+ optinMarketing: Boolean(guestNode.optinMarketing),
737
+ postalCode: guestNode.postalCode ?? null,
738
+ metadata
540
739
  };
541
740
  }
542
-
543
- // src/internal/bookings/addon-converter.ts
544
- var ERROR_INCONSISTENT_ADDON_ITEM_ID = "Add-on group contains options with mismatched item IDs";
545
- function parseSaleNode(node) {
546
- const bookingId = node.id || "";
547
- const displayId = node.displayId || "";
548
- const orderId = node.order?.id || "";
549
- const bookingQuoteRefid = node.refid || "";
550
- const bookingQuoteReservationStatus = node.reservationStatus || "";
551
- const items = Array.isArray(node.items) ? node.items : [];
552
- return items.map((item) => {
553
- const options = Array.isArray(item.options) ? item.options : [];
554
- const addonItemOptions = options.map((opt) => ({
555
- itemId: opt.itemSnapshot?.id || "",
556
- optionId: opt.itemOptionSnapshot?.id || "",
557
- itemName: opt.itemSnapshot?.name || "",
558
- optionName: opt.itemOptionSnapshot?.name || "",
559
- optionRefid: opt.refid || "",
560
- optionReservationStatus: opt.reservationStatus || "",
561
- itemReservationStatus: item.reservationStatus || "",
562
- itemRefid: item.refid || ""
563
- }));
564
- return {
565
- bookingId,
566
- displayId,
567
- orderId,
568
- total: item.value?.total ?? null,
569
- bookingQuoteRefid,
570
- bookingQuoteReservationStatus,
571
- addonItemOptions
572
- };
573
- });
741
+ function buildPeekProBookingDeepLink(orderId, bookingId) {
742
+ if (!orderId || !bookingId) return "";
743
+ return `${PEEK_PRO_DEEP_LINK_BASE}%2F${normalizeBookingId(orderId)}%3FsaleId=${normalizeBookingId(bookingId)}`;
574
744
  }
575
- function toBookingAddon(item) {
576
- const options = item.addonItemOptions || [];
577
- if (options.length === 0) {
578
- return null;
579
- }
580
- const addonId = options[0].itemId;
581
- const addonName = options[0].itemName;
582
- if (options.some((opt) => opt.itemId !== addonId)) {
583
- throw new Error(ERROR_INCONSISTENT_ADDON_ITEM_ID);
584
- }
585
- const grouped = /* @__PURE__ */ new Map();
586
- options.filter((opt) => opt.optionReservationStatus !== ADDON_OPTION_STATUS_CANCELED).forEach((opt) => {
587
- const existing = grouped.get(opt.optionId);
588
- if (existing) {
589
- existing.quantity += 1;
590
- } else {
591
- grouped.set(opt.optionId, {
592
- addonOptionId: opt.optionId,
593
- addonOptionName: opt.optionName,
594
- quantity: 1
595
- });
596
- }
597
- });
598
- const addonOptions = Array.from(grouped.values());
599
- if (addonOptions.length === 0) {
600
- return null;
601
- }
602
- return { addonId, addonName, total: item.total, addonOptions };
745
+ function sourceFromApp(app) {
746
+ if (!app) return UNKNOWN;
747
+ return SOURCE_SOURCE_MAP[app] ?? UNKNOWN;
603
748
  }
604
-
605
- // src/internal/bookings/booking-queries.ts
606
- var SEARCH_BY_PURCHASE_DATE = "purchaseDate";
607
- var SEARCH_BY_ACTIVITY_DATE = "activityDate";
608
- function normalizeBookingId(bookingId) {
609
- return bookingId.toLowerCase().replace(/-/g, "_");
749
+ function sourceDescriptionFromApp(app) {
750
+ if (!app) return UNKNOWN;
751
+ return SOURCE_DESC_MAP[app] ?? UNKNOWN;
610
752
  }
611
- var guestFields = `
612
- id
613
- name
614
- country
615
- dateOfBirth
616
- email
617
- isGdpr
618
- isParticipant
619
- optinSms
620
- optinMarketing
621
- phone
622
- postalCode
623
- fieldResponses {
624
- id
625
- text
626
- fieldLocation {
627
- field {
628
- name
629
- }
630
- }
631
- }
632
- `;
633
- var bookingGuestsFields = `
634
- bookingGuests {
635
- ${guestFields}
636
- }
637
- primaryGuest {
638
- ${guestFields}
639
- }
640
- `;
641
- var bookingQueryFields = `
642
- displayId
643
- id
644
- primaryGuest {
645
- name
646
- email
647
- phone
648
- optinMarketing
649
- optinSms
650
- isGdpr
651
- postalCode
652
- }
653
- activitySnapshot {
654
- type
655
- name
656
- id
657
- }
658
- ticketQuantities {
659
- quantity
660
- resourceOptionSnapshot {
661
- name
662
- id
663
- }
664
- }
665
- reservationStatus
666
- checkinStatus
667
- returnStatus
668
- fulfillmentStatusOverride {
669
- status
670
- }
671
- timeSnapshot {
672
- id
673
- legacyId
674
- }
675
- purchasedAt
676
- purchasedAtUtc
677
- startsAt
678
- startsAtUtc
679
- endsAt
680
- endsAtUtc
681
- availabilityTimeId
682
- bookingPortalUrl
683
- operatorNotes
684
- value {
685
- total {
686
- formatted
687
- amount
688
- }
689
- }
690
- balance {
691
- total {
692
- amount
693
- formatted
694
- }
695
- }
696
- tips {
697
- price {
698
- amount
699
- formatted
700
- }
753
+ function resellerNameFromChannelSnapshot(channelSnapshot) {
754
+ if (!channelSnapshot) return null;
755
+ let out = channelSnapshot.name ?? "";
756
+ if (channelSnapshot.agent?.name) {
757
+ out += " - " + channelSnapshot.agent.name;
701
758
  }
702
- order {
703
- displayId
704
- id
705
- promoCodes {
706
- code
707
- }
708
- channelSnapshot {
709
- id
710
- name
711
- agent {
712
- name
713
- }
714
- }
715
- initialQuote {
716
- source {
717
- actor {
718
- app
719
- name
720
- }
721
- }
722
- }
759
+ return out;
760
+ }
761
+ function ticketsToTicketArray(ticketQuantities, includePriceBreakdown) {
762
+ if (!ticketQuantities || ticketQuantities.length === 0) return [];
763
+ return ticketQuantities.map((ticket) => ({
764
+ name: ticket.resourceOptionSnapshot?.name || "Unknown",
765
+ quantity: ticket.quantity || 0,
766
+ ticketId: ticket.resourceOptionSnapshot?.id || "unknown",
767
+ listPrice: includePriceBreakdown ? mapPrice(ticket.value?.price) : void 0,
768
+ totalValue: includePriceBreakdown ? mapPrice(ticket.value?.total) : void 0
769
+ }));
770
+ }
771
+ function durationInMin(startsAt, endsAt) {
772
+ if (!startsAt || !endsAt) return 0;
773
+ const duration = new Date(endsAt).getTime() - new Date(startsAt).getTime();
774
+ return Math.floor(duration / 1e3 / 60);
775
+ }
776
+ function ticketQuantity(ticketQuantities) {
777
+ if (!ticketQuantities || ticketQuantities.length === 0) return 0;
778
+ return ticketQuantities.reduce((acc, ticket) => acc + (ticket.quantity || 0), 0);
779
+ }
780
+ function formatTickets(ticketQuantities) {
781
+ if (!ticketQuantities || ticketQuantities.length === 0) return "";
782
+ return ticketQuantities.map((ticket) => `${ticket.quantity || 0}x ${ticket.resourceOptionSnapshot?.name || "Unknown"}`).join(", ");
783
+ }
784
+ function mapResourcePoolAssignments(poolAssignments) {
785
+ if (!poolAssignments || poolAssignments.length === 0) return [];
786
+ return poolAssignments.flatMap(
787
+ (pool) => (pool.resourceAssignments ?? []).map((assignment) => ({
788
+ id: assignment.resource?.id || "",
789
+ name: assignment.resource?.name || ""
790
+ }))
791
+ );
792
+ }
793
+ function mapPrice(priceData) {
794
+ if (!priceData || !priceData.amount && !priceData.formatted) {
795
+ return void 0;
723
796
  }
724
- questionAnswers {
725
- answer
726
- questionText
727
- questionLocationSnapshot {
728
- latitude
729
- longitude
730
- }
797
+ return {
798
+ amount: priceData.amount || "0",
799
+ display: priceData.formatted || ""
800
+ };
801
+ }
802
+
803
+ // src/internal/peek/bookings/booking-guest-converter.ts
804
+ function fromBookingGuestsResponse(response) {
805
+ const firstEdge = (response?.sales?.edges ?? [])[0];
806
+ if (!firstEdge) {
807
+ return [];
731
808
  }
732
- tickets {
733
- questionAnswers {
734
- answer
735
- questionText
736
- }
809
+ const bookingNode = firstEdge.node;
810
+ const primaryGuestNode = bookingNode.primaryGuest;
811
+ const bookingGuestsNodes = Array.isArray(bookingNode.bookingGuests) ? bookingNode.bookingGuests : [];
812
+ const primaryId = primaryGuestNode?.id;
813
+ const guests = [];
814
+ for (const guestNode of bookingGuestsNodes) {
815
+ guests.push(mapGuestNode(guestNode, primaryId ? guestNode.id === primaryId : false));
737
816
  }
738
- resourcePoolAssignments {
739
- quantity
740
- resourcePool {
741
- name
742
- shortName
743
- resources {
744
- name
745
- }
746
- }
747
- resourceAssignments {
748
- resource {
749
- id
750
- name
751
- }
752
- }
817
+ const hasPrimaryInGuests = primaryId ? bookingGuestsNodes.some((guest) => guest.id === primaryId) : false;
818
+ if (!hasPrimaryInGuests && primaryGuestNode) {
819
+ guests.push(mapGuestNode(primaryGuestNode, true));
753
820
  }
754
- `;
755
- var PRICE_BREAKDOWN_FIELDS = `
756
- convenienceFee { amount formatted }
757
- deposit { amount formatted }
758
- discount { amount formatted }
759
- discountedPrice { amount formatted }
760
- fees { amount formatted }
761
- flatPartnerFee { amount formatted }
762
- price { amount formatted }
763
- retailPrice { amount formatted }
764
- taxes { amount formatted }
765
- tips { amount formatted }
766
- `;
767
- var TICKET_VALUE_FIELDS = `
768
- value {
769
- price { amount formatted }
770
- total { amount formatted }
821
+ return guests;
822
+ }
823
+
824
+ // src/internal/peek/bookings/payments-on-file-converter.ts
825
+ function fromPaymentsOnFileResponse(response, bookingId) {
826
+ const firstEdge = (response?.sales?.edges ?? [])[0];
827
+ if (!firstEdge) {
828
+ return null;
771
829
  }
772
- `;
773
- function buildBookingsListingQuery(includeGuests, includePriceBreakdown) {
774
- const guestsSection = includeGuests ? bookingGuestsFields : "";
775
- const fields = includePriceBreakdown ? bookingQueryFields.replace("value {", `value { ${PRICE_BREAKDOWN_FIELDS}`).replace("ticketQuantities {", `ticketQuantities { ${TICKET_VALUE_FIELDS}`) : bookingQueryFields;
776
- return `
777
- query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
778
- sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
779
- pageInfo { endCursor hasNextPage }
780
- edges {
781
- node {
782
- ... on Booking {
783
- ${fields}
784
- ${guestsSection}
785
- }
786
- }
787
- }
788
- }
830
+ const order = firstEdge.node.order;
831
+ const orderId = order?.id ?? "";
832
+ const rawPaymentSources = order?.paymentSources ?? [];
833
+ const rawPayments = order?.payments ?? [];
834
+ const paymentsBySourceId = /* @__PURE__ */ new Map();
835
+ for (const payment of rawPayments) {
836
+ const sourceId = payment.paymentSource?.id;
837
+ if (!sourceId) continue;
838
+ const mapped = {
839
+ id: payment.id,
840
+ paidAt: dateOnly(payment.appliedAt),
841
+ currentAmount: payment.currentAmount,
842
+ refundableAmount: payment.refundableAmount
843
+ };
844
+ const existing = paymentsBySourceId.get(sourceId);
845
+ if (existing) {
846
+ existing.push(mapped);
847
+ } else {
848
+ paymentsBySourceId.set(sourceId, [mapped]);
789
849
  }
790
- `;
850
+ }
851
+ const paymentsOnFile = rawPaymentSources.map((source) => {
852
+ const payments = paymentsBySourceId.get(source.id);
853
+ return {
854
+ description: source.description,
855
+ id: source.id,
856
+ type: source.type,
857
+ ...payments ? { payments } : {}
858
+ };
859
+ });
860
+ return { bookingId, orderId, paymentsOnFile };
791
861
  }
792
- var BOOKING_GUESTS_QUERY = `
862
+ function dateOnly(iso) {
863
+ return iso.split("T")[0] ?? iso;
864
+ }
865
+
866
+ // src/internal/peek/bookings/addon-queries.ts
867
+ var RESERVATION_STATUS_CONFIRMED = "CONFIRMED";
868
+ var ADDON_OPTION_STATUS_CANCELED = "CANCELED";
869
+ var SALES_ADDONS_PAGE_SIZE = 100;
870
+ var SALES_ADDONS_QUERY = `
793
871
  query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
794
872
  sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
795
873
  pageInfo { endCursor hasNextPage }
796
874
  edges {
797
875
  node {
876
+ order { id displayId }
798
877
  ... on Booking {
799
878
  displayId
800
879
  id
801
- ${bookingGuestsFields}
802
- }
803
- }
804
- }
805
- }
806
- }
807
- `;
808
- var BOOKING_PAYMENTS_ON_FILE_QUERY = `
809
- query Sales($after: String, $first: Int, $filter: SalesFilter!, $orderBy: SalesOrdering) {
810
- sales(after: $after, first: $first, filter: $filter, orderBy: $orderBy) {
811
- pageInfo { endCursor hasNextPage }
812
- edges {
813
- node {
814
- order {
815
- payments {
880
+ refid
881
+ reservationStatus
882
+ items {
816
883
  id
817
- paymentSource { id }
818
- appliedAt
819
- currentAmount { amount currency }
820
- refundableAmount { amount currency }
821
- }
822
- id
823
- displayId
824
- paymentSources { description type id }
825
- }
826
- ... on Booking {
827
- displayId
828
- id
829
- }
830
- }
831
- }
832
- }
833
- }
834
- `;
835
- var UPDATE_OPERATOR_NOTES_MUTATION = `
836
- mutation Account($input: UpdateOperatorNotesForBookingInput!) {
837
- updateOperatorNotesForBooking(input: $input) {
838
- booking { operatorNotes }
839
- }
840
- }
841
- `;
842
- var UPDATE_BOOKING_CHECKIN_MUTATION = `
843
- mutation Account($input: UpdateBookingCheckInInput!) {
844
- updateBookingCheckIn(input: $input) {
845
- booking { checkinStatus }
846
- }
847
- }
848
- `;
849
- var CANCEL_BOOKING_MUTATION = `
850
- mutation Account($input: CancelBookingInput!) {
851
- cancelBooking(input: $input) {
852
- booking { id displayId reservationStatus }
853
- }
854
- }
855
- `;
856
- var APPLY_PAYMENT_TO_ORDER_MUTATION = `
857
- mutation ApplyPaymentToOrder($input: ApplyPaymentToOrderInput!) {
858
- applyPaymentToOrder(input: $input) {
859
- transactionId
860
- errors { code detail value }
861
- }
862
- }
863
- `;
864
- var APPLY_REFUND_TO_ORDER_MUTATION = `
865
- mutation ApplyRefundToOrder($input: ApplyRefundToOrderInput!) {
866
- applyRefundToOrder(input: $input) {
867
- transactionId
868
- errors { code detail value }
869
- }
870
- }
871
- `;
872
- var CREATE_INVOICE_LINK_MUTATION = `
873
- mutation CreateInvoiceLink($input: CreateInvoiceLinkInput!) {
874
- createInvoiceLink(input: $input) {
875
- invoiceLink { status url }
876
- errors { code detail value __typename }
877
- }
878
- }
879
- `;
880
- var CREATE_QUOTE_FROM_ORDER_MUTATION = `
881
- mutation CreateQuoteFromOrder($input: CreateQuoteFromOrderInput!) {
882
- createQuoteFromOrder(input: $input) {
883
- errors { detail value code }
884
- quote {
885
- id
886
- saleQuotes { refid reservationStatus }
887
- }
888
- }
889
- }
890
- `;
891
- var UPDATE_QUOTE_V2_MUTATION = `
892
- mutation UpdateQuoteV2($input: UpdateQuoteV2Input!) {
893
- updateQuoteV2(input: $input) {
894
- errors { detail value code }
895
- quote { id }
896
- }
897
- }
898
- `;
899
- var AMEND_ORDER_MUTATION = `
900
- mutation AmendOrder($input: AmendOrderInput!) {
901
- amendOrder(input: $input) {
902
- errors { code detail value }
903
- order { id }
904
- }
905
- }
906
- `;
907
- var CREATE_QUOTE_V2_MUTATION = `
908
- mutation CreateQuoteV2($input: CreateQuoteV2Input!) {
909
- createQuoteV2(input: $input) {
910
- errors { detail value code }
911
- quote { id }
912
- }
913
- }
914
- `;
915
- var CREATE_ORDER_FROM_QUOTE_MUTATION = `
916
- mutation CreateOrderFromQuote($input: CreateOrderFromQuoteInput!) {
917
- createOrderFromQuote(input: $input) {
918
- errors { code detail value }
919
- order {
920
- id
921
- sales {
922
- id
923
- displayId
924
- ... on Booking {
925
- balance { total { amount currency formatted } }
884
+ refid
885
+ value { total { amount currency formatted } }
886
+ reservationStatus
887
+ options {
888
+ refid
889
+ reservationStatus
890
+ price { amount currency formatted }
891
+ itemOptionSnapshot { id name }
892
+ itemSnapshot { id name }
893
+ }
894
+ }
926
895
  }
927
896
  }
928
897
  }
929
898
  }
930
899
  }
931
900
  `;
932
- function buildBookingsVariables(params) {
933
- const filter = {};
934
- const bookingFilter = {};
935
- if (params.bookingId) {
936
- bookingFilter.ids = [normalizeBookingId(params.bookingId)];
937
- } else if (params.timeslotId) {
938
- bookingFilter.timeslotRefid = params.timeslotId;
939
- } else {
940
- if (params.searchBy === SEARCH_BY_ACTIVITY_DATE) {
941
- bookingFilter.overlapsRange = `[${params.startDateTime},${params.endDateTime}]`;
942
- } else if (params.searchBy === SEARCH_BY_PURCHASE_DATE) {
943
- filter.purchasedAtRangeUtc = `[${params.startDateTime},${params.endDateTime}]`;
944
- }
945
- if (params.productId) {
946
- bookingFilter.activityIds = [params.productId];
947
- }
948
- }
949
- if (params.email) {
950
- filter.primaryGuestEmail = params.email;
901
+ function buildSalesAddonsVariables(searchString) {
902
+ return {
903
+ first: SALES_ADDONS_PAGE_SIZE,
904
+ after: null,
905
+ orderBy: { direction: "DESC", field: "STARTS_AT" },
906
+ filter: { searchString }
907
+ };
908
+ }
909
+
910
+ // src/internal/peek/bookings/addon-converter.ts
911
+ var ERROR_INCONSISTENT_ADDON_ITEM_ID = "Add-on group contains options with mismatched item IDs";
912
+ function parseSaleNode(node) {
913
+ const bookingId = node.id || "";
914
+ const displayId = node.displayId || "";
915
+ const orderId = node.order?.id || "";
916
+ const bookingQuoteRefid = node.refid || "";
917
+ const bookingQuoteReservationStatus = node.reservationStatus || "";
918
+ const items = Array.isArray(node.items) ? node.items : [];
919
+ return items.map((item) => {
920
+ const options = Array.isArray(item.options) ? item.options : [];
921
+ const addonItemOptions = options.map((opt) => ({
922
+ itemId: opt.itemSnapshot?.id || "",
923
+ optionId: opt.itemOptionSnapshot?.id || "",
924
+ itemName: opt.itemSnapshot?.name || "",
925
+ optionName: opt.itemOptionSnapshot?.name || "",
926
+ optionRefid: opt.refid || "",
927
+ optionReservationStatus: opt.reservationStatus || "",
928
+ itemReservationStatus: item.reservationStatus || "",
929
+ itemRefid: item.refid || ""
930
+ }));
931
+ return {
932
+ bookingId,
933
+ displayId,
934
+ orderId,
935
+ total: item.value?.total ?? null,
936
+ bookingQuoteRefid,
937
+ bookingQuoteReservationStatus,
938
+ addonItemOptions
939
+ };
940
+ });
941
+ }
942
+ function toBookingAddon(item) {
943
+ const options = item.addonItemOptions || [];
944
+ if (options.length === 0) {
945
+ return null;
951
946
  }
952
- if (params.searchString && params.searchString.length > 0) {
953
- filter.searchString = params.searchString;
947
+ const addonId = options[0].itemId;
948
+ const addonName = options[0].itemName;
949
+ if (options.some((opt) => opt.itemId !== addonId)) {
950
+ throw new Error(ERROR_INCONSISTENT_ADDON_ITEM_ID);
954
951
  }
955
- if (Object.keys(bookingFilter).length > 0) {
956
- filter.bookingFilter = bookingFilter;
952
+ const grouped = /* @__PURE__ */ new Map();
953
+ options.filter((opt) => opt.optionReservationStatus !== ADDON_OPTION_STATUS_CANCELED).forEach((opt) => {
954
+ const existing = grouped.get(opt.optionId);
955
+ if (existing) {
956
+ existing.quantity += 1;
957
+ } else {
958
+ grouped.set(opt.optionId, {
959
+ addonOptionId: opt.optionId,
960
+ addonOptionName: opt.optionName,
961
+ quantity: 1
962
+ });
963
+ }
964
+ });
965
+ const addonOptions = Array.from(grouped.values());
966
+ if (addonOptions.length === 0) {
967
+ return null;
957
968
  }
958
- return {
959
- first: params.pageSize,
960
- after: params.after,
961
- orderBy: { direction: "ASC", field: "STARTS_AT" },
962
- filter
963
- };
969
+ return { addonId, addonName, total: item.total, addonOptions };
964
970
  }
965
971
 
966
- // src/internal/bookings/booking-service.ts
972
+ // src/internal/peek/bookings/booking-service.ts
967
973
  var DEFAULT_PAGE_SIZE2 = 50;
968
974
  var DEFAULT_CANCEL_NOTE = "Canceled";
969
975
  var DEFAULT_CUSTOMER_MESSAGE = "Charge initiated via API";
@@ -1713,7 +1719,7 @@ function buildCancellation(items, addonOptionId, quantity) {
1713
1719
  return { bookingQuotes, canceledCount };
1714
1720
  }
1715
1721
 
1716
- // src/internal/daily-notes/daily-note-converter.ts
1722
+ // src/internal/peek/daily-notes/daily-note-converter.ts
1717
1723
  function toDailyNote(response) {
1718
1724
  const union = response?.dailyNote;
1719
1725
  if (!union) {
@@ -1734,7 +1740,7 @@ function cleanNote(note) {
1734
1740
  return { note: note.note };
1735
1741
  }
1736
1742
 
1737
- // src/internal/daily-notes/daily-note-queries.ts
1743
+ // src/internal/peek/daily-notes/daily-note-queries.ts
1738
1744
  var GLOBAL_NOTE_TYPE = "DASHBOARD";
1739
1745
  var DAILY_NOTE_TODAY_QUERY = `
1740
1746
  query dailyNote($type: GlobalNoteType!) {
@@ -1765,7 +1771,7 @@ function buildUpsertDailyNoteVariables(note) {
1765
1771
  return { input: { type: GLOBAL_NOTE_TYPE, note } };
1766
1772
  }
1767
1773
 
1768
- // src/internal/daily-notes/daily-note-service.ts
1774
+ // src/internal/peek/daily-notes/daily-note-service.ts
1769
1775
  var DailyNoteService = class {
1770
1776
  constructor(client) {
1771
1777
  this.client = client;
@@ -1788,6 +1794,53 @@ var DailyNoteService = class {
1788
1794
  return fromUpsertResponse(body.data);
1789
1795
  }
1790
1796
  };
1797
+ var TokenManager = class {
1798
+ constructor(options) {
1799
+ this.options = options;
1800
+ }
1801
+ options;
1802
+ cached;
1803
+ /**
1804
+ * Returns a valid bearer token, reusing the cached one until it is within
1805
+ * `leewaySeconds` of expiring, at which point a fresh token is minted.
1806
+ */
1807
+ getToken() {
1808
+ const now = Date.now();
1809
+ if (this.cached && now < this.cached.expiresAtMs) {
1810
+ return this.cached.token;
1811
+ }
1812
+ const { secret, issuer, installId, ttlSeconds, leewaySeconds } = this.options;
1813
+ const token = jwt__namespace.sign({}, secret, {
1814
+ expiresIn: ttlSeconds,
1815
+ issuer,
1816
+ subject: installId
1817
+ });
1818
+ this.cached = {
1819
+ token,
1820
+ expiresAtMs: now + (ttlSeconds - leewaySeconds) * 1e3
1821
+ };
1822
+ return token;
1823
+ }
1824
+ };
1825
+
1826
+ // src/access-service-config.ts
1827
+ var DEFAULT_TOKEN_TTL_SECONDS = 3600;
1828
+ var DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS = 60;
1829
+ var DEFAULT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
1830
+ function requireNonEmpty(value, name, serviceName) {
1831
+ if (!value) {
1832
+ throw new Error(`${serviceName}: "${name}" is required`);
1833
+ }
1834
+ }
1835
+ function createTokenManager(config) {
1836
+ return new TokenManager({
1837
+ secret: config.jwtSecret,
1838
+ issuer: config.issuer,
1839
+ installId: config.installId,
1840
+ ttlSeconds: config.tokenTtlSeconds ?? DEFAULT_TOKEN_TTL_SECONDS,
1841
+ leewaySeconds: config.tokenRefreshLeewaySeconds ?? DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS
1842
+ });
1843
+ }
1791
1844
 
1792
1845
  // src/errors.ts
1793
1846
  var AdminAccountRequiredError = class extends Error {
@@ -1815,11 +1868,49 @@ var PeekGraphQLError = class extends Error {
1815
1868
  this.graphqlErrors = graphqlErrors;
1816
1869
  }
1817
1870
  };
1871
+ var CngApiError = class extends Error {
1872
+ /** The HTTP status that triggered this error. */
1873
+ statusCode;
1874
+ /** The raw response body (parsed JSON when possible, otherwise text). */
1875
+ body;
1876
+ constructor(statusCode, body, message) {
1877
+ super(message ?? `CNG request failed with HTTP ${statusCode}`);
1878
+ this.name = "CngApiError";
1879
+ this.statusCode = statusCode;
1880
+ this.body = body;
1881
+ }
1882
+ };
1818
1883
 
1819
- // src/internal/graphql-client.ts
1884
+ // src/internal/http-transport.ts
1820
1885
  var RATE_LIMIT_STATUS = 429;
1821
1886
  var ADMIN_ACCOUNT_REQUIRED_STATUS = 418;
1822
1887
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1888
+ async function requestWithRetry(http, url, init, label, onResponse) {
1889
+ const { retryDelaysMs, logger, fetchFn } = http;
1890
+ for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
1891
+ const response = await fetchFn(url, init);
1892
+ if (response.status === ADMIN_ACCOUNT_REQUIRED_STATUS) {
1893
+ logger.warn(`Admin account required for ${label} (HTTP 418)`, { url });
1894
+ throw new AdminAccountRequiredError();
1895
+ }
1896
+ if (response.status === RATE_LIMIT_STATUS) {
1897
+ const delay = retryDelaysMs[attempt];
1898
+ if (delay !== void 0) {
1899
+ logger.warn(
1900
+ `Rate limited on ${label}, retrying in ${delay}ms (attempt ${attempt + 1}/${retryDelaysMs.length})`
1901
+ );
1902
+ await sleep(delay);
1903
+ continue;
1904
+ }
1905
+ logger.error(`Rate limit exceeded for ${label}`, { url });
1906
+ throw new RateLimitError();
1907
+ }
1908
+ return onResponse(response);
1909
+ }
1910
+ throw new RateLimitError();
1911
+ }
1912
+
1913
+ // src/internal/peek/graphql-client.ts
1823
1914
  var GraphQLClient = class {
1824
1915
  constructor(options) {
1825
1916
  this.options = options;
@@ -1827,50 +1918,39 @@ var GraphQLClient = class {
1827
1918
  options;
1828
1919
  /**
1829
1920
  * Executes a GraphQL query against the named endpoint and returns the raw
1830
- * response body. Retries on HTTP 429 per the configured backoff.
1921
+ * response body. Retries on HTTP 429 per the configured backoff (via the
1922
+ * shared {@link requestWithRetry} loop).
1831
1923
  */
1832
1924
  async request(endpointName, query, variables) {
1833
- const { retryDelaysMs, logger, fetchFn } = this.options;
1925
+ const { logger } = this.options;
1834
1926
  const url = this.endpoint(endpointName);
1835
1927
  const collapsedQuery = query.replace(/\s+/g, " ").trim();
1836
1928
  logger.info("Making GraphQL request", { url, endpointName });
1837
- for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
1838
- const response = await fetchFn(url, {
1929
+ return requestWithRetry(
1930
+ this.options,
1931
+ url,
1932
+ {
1839
1933
  method: "POST",
1840
1934
  headers: this.buildHeaders(),
1841
1935
  body: JSON.stringify({ query: collapsedQuery, variables })
1842
- });
1843
- if (response.status === ADMIN_ACCOUNT_REQUIRED_STATUS) {
1844
- logger.warn(`Admin account required for ${endpointName} (HTTP 418)`, { url });
1845
- throw new AdminAccountRequiredError();
1846
- }
1847
- if (response.status === RATE_LIMIT_STATUS) {
1848
- const delay = retryDelaysMs[attempt];
1849
- if (delay !== void 0) {
1850
- logger.warn(
1851
- `Rate limited on ${endpointName}, retrying in ${delay}ms (attempt ${attempt + 1}/${retryDelaysMs.length})`
1852
- );
1853
- await sleep(delay);
1854
- continue;
1936
+ },
1937
+ endpointName,
1938
+ async (response) => {
1939
+ const body = await response.json();
1940
+ if (body.errors) {
1941
+ logger.error(`GraphQL errors for ${endpointName}`, {
1942
+ url,
1943
+ graphqlErrors: JSON.stringify(body.errors)
1944
+ });
1945
+ throw new PeekGraphQLError(body.errors);
1855
1946
  }
1856
- logger.error(`Rate limit exceeded for ${endpointName}`, { url });
1857
- throw new RateLimitError();
1858
- }
1859
- const body = await response.json();
1860
- if (body.errors) {
1861
- logger.error(`GraphQL errors for ${endpointName}`, {
1862
- url,
1863
- graphqlErrors: JSON.stringify(body.errors)
1864
- });
1865
- throw new PeekGraphQLError(body.errors);
1866
- }
1867
- if (!response.ok) {
1868
- logger.error(`GraphQL request failed with HTTP ${response.status}`, { url });
1869
- throw new Error(`GraphQL request failed with HTTP ${response.status}`);
1947
+ if (!response.ok) {
1948
+ logger.error(`GraphQL request failed with HTTP ${response.status}`, { url });
1949
+ throw new Error(`GraphQL request failed with HTTP ${response.status}`);
1950
+ }
1951
+ return body;
1870
1952
  }
1871
- return body;
1872
- }
1873
- throw new RateLimitError();
1953
+ );
1874
1954
  }
1875
1955
  endpoint(endpointName) {
1876
1956
  const { baseUrl, appId, endpointPathPrefix } = this.options;
@@ -1889,7 +1969,7 @@ var GraphQLClient = class {
1889
1969
  }
1890
1970
  };
1891
1971
 
1892
- // src/internal/memberships/membership-converter.ts
1972
+ // src/internal/peek/memberships/membership-converter.ts
1893
1973
  function fromMembershipsResponse(response) {
1894
1974
  return (response?.memberships ?? []).flatMap(
1895
1975
  (membership) => (membership.membershipVariants ?? []).map((variant) => ({
@@ -1904,7 +1984,7 @@ function fromMembershipsResponse(response) {
1904
1984
  );
1905
1985
  }
1906
1986
 
1907
- // src/internal/memberships/membership-queries.ts
1987
+ // src/internal/peek/memberships/membership-queries.ts
1908
1988
  var MEMBERSHIPS_QUERY = `
1909
1989
  query Sales {
1910
1990
  memberships {
@@ -1949,7 +2029,7 @@ var CREATE_MEMBERSHIP_ORDER_FROM_QUOTE_MUTATION = `
1949
2029
  }
1950
2030
  `;
1951
2031
 
1952
- // src/internal/memberships/membership-service.ts
2032
+ // src/internal/peek/memberships/membership-service.ts
1953
2033
  var QUOTE_STATUS_CONFIRMED = "CONFIRMED";
1954
2034
  var DEFAULT_BALANCE_AMOUNT = "0.00";
1955
2035
  var DEFAULT_BALANCE_CURRENCY = "USD";
@@ -2055,7 +2135,7 @@ function validatePurchaseInput(input) {
2055
2135
  }
2056
2136
  }
2057
2137
 
2058
- // src/internal/resellers/channel-converter.ts
2138
+ // src/internal/peek/resellers/channel-converter.ts
2059
2139
  function fromChannelsResponse(response) {
2060
2140
  return (response?.channels ?? []).map(fromChannelNode);
2061
2141
  }
@@ -2076,7 +2156,7 @@ function fromChannelNode(node) {
2076
2156
  };
2077
2157
  }
2078
2158
 
2079
- // src/internal/resellers/channel-queries.ts
2159
+ // src/internal/peek/resellers/channel-queries.ts
2080
2160
  var CHANNELS_QUERY = `
2081
2161
  query Sales($first: Int) {
2082
2162
  channels {
@@ -2100,7 +2180,7 @@ var CHANNELS_QUERY = `
2100
2180
  }
2101
2181
  `;
2102
2182
 
2103
- // src/internal/resellers/reseller-service.ts
2183
+ // src/internal/peek/resellers/reseller-service.ts
2104
2184
  var DEFAULT_AGENTS_PER_CHANNEL = 10;
2105
2185
  var ResellerService = class {
2106
2186
  constructor(client) {
@@ -2119,7 +2199,7 @@ var ResellerService = class {
2119
2199
  }
2120
2200
  };
2121
2201
 
2122
- // src/internal/resource-pools/resource-pool-converter.ts
2202
+ // src/internal/peek/resource-pools/resource-pool-converter.ts
2123
2203
  function fromResourcePoolsResponse(response) {
2124
2204
  return (response?.resourcePools ?? []).map(fromResourcePoolNode);
2125
2205
  }
@@ -2147,7 +2227,7 @@ function fromAccountUser(accountUser) {
2147
2227
  };
2148
2228
  }
2149
2229
 
2150
- // src/internal/resource-pools/resource-pool-queries.ts
2230
+ // src/internal/peek/resource-pools/resource-pool-queries.ts
2151
2231
  var RESOURCE_POOLS_QUERY = `
2152
2232
  query Sales($filter: ResourcePoolsFilter) {
2153
2233
  resourcePools(filter: $filter) {
@@ -2168,7 +2248,7 @@ var RESOURCE_POOLS_QUERY = `
2168
2248
  }
2169
2249
  `;
2170
2250
 
2171
- // src/internal/resource-pools/resource-pool-service.ts
2251
+ // src/internal/peek/resource-pools/resource-pool-service.ts
2172
2252
  var DEFAULT_MODE = "ALL";
2173
2253
  var ResourcePoolService = class {
2174
2254
  constructor(client) {
@@ -2188,7 +2268,7 @@ var ResourcePoolService = class {
2188
2268
  }
2189
2269
  };
2190
2270
 
2191
- // src/internal/reviews/review-converter.ts
2271
+ // src/internal/peek/reviews/review-converter.ts
2192
2272
  var ISO_DATE_LENGTH = 10;
2193
2273
  function toDateOnly(isoDateTime) {
2194
2274
  return isoDateTime.slice(0, ISO_DATE_LENGTH);
@@ -2212,13 +2292,13 @@ function fromReviewNode(node) {
2212
2292
  };
2213
2293
  }
2214
2294
 
2215
- // src/internal/reviews/review-cursor.ts
2295
+ // src/internal/peek/reviews/review-cursor.ts
2216
2296
  function encodeCursor(offset, pageSize) {
2217
2297
  const start = Math.max(0, offset - pageSize + 1);
2218
2298
  return Buffer.from(`range:${start}..${offset},${offset}`).toString("base64");
2219
2299
  }
2220
2300
 
2221
- // src/internal/reviews/review-queries.ts
2301
+ // src/internal/peek/reviews/review-queries.ts
2222
2302
  var REVIEWS_QUERY = `
2223
2303
  query Reviews($first: Int, $filter: ReviewFilter, $after: String) {
2224
2304
  reviews(first: $first, filter: $filter, after: $after) {
@@ -2253,7 +2333,7 @@ function buildReviewsVariables(params) {
2253
2333
  };
2254
2334
  }
2255
2335
 
2256
- // src/internal/reviews/review-service.ts
2336
+ // src/internal/peek/reviews/review-service.ts
2257
2337
  var DEFAULT_REVIEW_COUNT = 50;
2258
2338
  var MIN_REVIEW_COUNT = 1;
2259
2339
  var MAX_REVIEW_COUNT = 50;
@@ -2314,7 +2394,7 @@ var ReviewService = class {
2314
2394
  }
2315
2395
  };
2316
2396
 
2317
- // src/internal/timeslots/timeslot-converter.ts
2397
+ // src/internal/peek/timeslots/timeslot-converter.ts
2318
2398
  function fromTimeslotNodes(nodes, productId) {
2319
2399
  if (!Array.isArray(nodes) || nodes.length === 0) {
2320
2400
  return [];
@@ -2372,7 +2452,7 @@ function mapAssignedResources(allocations) {
2372
2452
  });
2373
2453
  }
2374
2454
 
2375
- // src/internal/timeslots/guide-matcher.ts
2455
+ // src/internal/peek/timeslots/guide-matcher.ts
2376
2456
  function matchGuideToResourcePool(guideId, guideResourcePools, accountUsers) {
2377
2457
  const directIdMatch = guideResourcePools.find((pool) => pool.id === guideId);
2378
2458
  if (directIdMatch) {
@@ -2406,7 +2486,7 @@ function matchGuideToResourcePool(guideId, guideResourcePools, accountUsers) {
2406
2486
  return null;
2407
2487
  }
2408
2488
 
2409
- // src/internal/timeslots/resource-allocation-queries.ts
2489
+ // src/internal/peek/timeslots/resource-allocation-queries.ts
2410
2490
  var RESOURCE_ALLOCATION_BULK_REQUEST_MUTATION = `
2411
2491
  mutation ResourceAllocationBulkRequest($input: ResourceAllocationBulkRequestInput!) {
2412
2492
  resourceAllocationBulkRequest(input: $input) {
@@ -2425,7 +2505,7 @@ function buildResourceAllocationVariables(timeslotIds, resourcePoolIds, status)
2425
2505
  return { input: { timeslotIds, resourcePoolIds, status } };
2426
2506
  }
2427
2507
 
2428
- // src/internal/timeslots/timeslot-queries.ts
2508
+ // src/internal/peek/timeslots/timeslot-queries.ts
2429
2509
  var TIMESLOTS_QUERY = `
2430
2510
  query Sales($params: TimeslotsFilter!) {
2431
2511
  timeslots(filter: $params) {
@@ -2528,7 +2608,7 @@ function buildTimeslotVariables(productId, date, filter) {
2528
2608
  return { params };
2529
2609
  }
2530
2610
 
2531
- // src/internal/timeslots/timeslot-service.ts
2611
+ // src/internal/peek/timeslots/timeslot-service.ts
2532
2612
  var GUIDE_CATEGORY = "guide";
2533
2613
  var ERROR_MISSING_TIMESLOTS_OR_GUIDES = "At least one timeslot and one guide are required";
2534
2614
  var ERROR_INVALID_ACTION = "Invalid action. Must be either assign or unassign";
@@ -2645,7 +2725,7 @@ function normalizeDate(date) {
2645
2725
  return date.split("T")[0] ?? date;
2646
2726
  }
2647
2727
 
2648
- // src/internal/products/product-converter.ts
2728
+ // src/internal/peek/products/product-converter.ts
2649
2729
  var ADD_ON_COLOR = "#FFFFFF";
2650
2730
  function fromActivities(activities) {
2651
2731
  return activities.map(fromActivity);
@@ -2684,7 +2764,7 @@ function fromItemOptionNodes(nodes) {
2684
2764
  return Array.from(grouped.values());
2685
2765
  }
2686
2766
 
2687
- // src/internal/products/product-queries.ts
2767
+ // src/internal/peek/products/product-queries.ts
2688
2768
  var PRODUCTS_QUERY = `
2689
2769
  query Sales {
2690
2770
  activities {
@@ -2724,7 +2804,7 @@ var ITEM_OPTIONS_QUERY = `
2724
2804
  }
2725
2805
  `;
2726
2806
 
2727
- // src/internal/products/product-service.ts
2807
+ // src/internal/peek/products/product-service.ts
2728
2808
  var DEFAULT_ITEM_OPTIONS_PAGE_SIZE = 50;
2729
2809
  var ProductService = class {
2730
2810
  constructor(client, options = {}) {
@@ -2801,7 +2881,7 @@ var ProductService = class {
2801
2881
  }
2802
2882
  };
2803
2883
 
2804
- // src/internal/promo-codes/promo-code-queries.ts
2884
+ // src/internal/peek/promo-codes/promo-code-queries.ts
2805
2885
  var PROMO_CODES_QUERY = `
2806
2886
  query Sales($first: Int, $after: String) {
2807
2887
  promoCodes(first: $first, after: $after) {
@@ -2840,7 +2920,7 @@ var CREATE_PROMO_CODE_MUTATION = `
2840
2920
  }
2841
2921
  `;
2842
2922
 
2843
- // src/internal/promo-codes/promo-code-service.ts
2923
+ // src/internal/peek/promo-codes/promo-code-service.ts
2844
2924
  var DEFAULT_PAGE_SIZE3 = 50;
2845
2925
  var DEFAULT_CURRENCY = "USD";
2846
2926
  var CURRENCY_PATTERN = /^[A-Z]{3}$/;
@@ -2919,34 +2999,6 @@ var PromoCodeService = class {
2919
2999
  return { id: result.id, name: result.name };
2920
3000
  }
2921
3001
  };
2922
- var TokenManager = class {
2923
- constructor(options) {
2924
- this.options = options;
2925
- }
2926
- options;
2927
- cached;
2928
- /**
2929
- * Returns a valid bearer token, reusing the cached one until it is within
2930
- * `leewaySeconds` of expiring, at which point a fresh token is minted.
2931
- */
2932
- getToken() {
2933
- const now = Date.now();
2934
- if (this.cached && now < this.cached.expiresAtMs) {
2935
- return this.cached.token;
2936
- }
2937
- const { secret, issuer, installId, ttlSeconds, leewaySeconds } = this.options;
2938
- const token = jwt__namespace.sign({}, secret, {
2939
- expiresIn: ttlSeconds,
2940
- issuer,
2941
- subject: installId
2942
- });
2943
- this.cached = {
2944
- token,
2945
- expiresAtMs: now + (ttlSeconds - leewaySeconds) * 1e3
2946
- };
2947
- return token;
2948
- }
2949
- };
2950
3002
 
2951
3003
  // src/logger.ts
2952
3004
  var noopLogger = {
@@ -2961,9 +3013,6 @@ var noopLogger = {
2961
3013
  // src/peek-access-service.ts
2962
3014
  var DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
2963
3015
  var DEFAULT_V2_BASE_URL = "https://app-registry.peeklabs.com/installations-api";
2964
- var DEFAULT_TOKEN_TTL_SECONDS = 3600;
2965
- var DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS = 60;
2966
- var DEFAULT_RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
2967
3016
  var PEEK_TOKEN_ISSUER = "app_registry_v2";
2968
3017
  var PeekAccessService = class {
2969
3018
  client;
@@ -2982,20 +3031,14 @@ var PeekAccessService = class {
2982
3031
  reviewService;
2983
3032
  constructor(config) {
2984
3033
  const isV2 = config.mode === "v2";
2985
- requireNonEmpty(config.installId, "installId");
2986
- requireNonEmpty(config.jwtSecret, "jwtSecret");
2987
- requireNonEmpty(config.issuer, "issuer");
2988
- requireNonEmpty(config.appId, "appId");
2989
- if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey");
3034
+ requireNonEmpty(config.installId, "installId", "PeekAccessService");
3035
+ requireNonEmpty(config.jwtSecret, "jwtSecret", "PeekAccessService");
3036
+ requireNonEmpty(config.issuer, "issuer", "PeekAccessService");
3037
+ requireNonEmpty(config.appId, "appId", "PeekAccessService");
3038
+ if (!isV2) requireNonEmpty(config.gatewayKey ?? "", "gatewayKey", "PeekAccessService");
2990
3039
  this.jwtSecret = config.jwtSecret;
2991
3040
  const logger = config.logger ?? noopLogger;
2992
- const tokens = new TokenManager({
2993
- secret: config.jwtSecret,
2994
- issuer: config.issuer,
2995
- installId: config.installId,
2996
- ttlSeconds: config.tokenTtlSeconds ?? DEFAULT_TOKEN_TTL_SECONDS,
2997
- leewaySeconds: config.tokenRefreshLeewaySeconds ?? DEFAULT_TOKEN_REFRESH_LEEWAY_SECONDS
2998
- });
3041
+ const tokens = createTokenManager(config);
2999
3042
  const defaultBaseUrl = isV2 ? DEFAULT_V2_BASE_URL : DEFAULT_BASE_URL;
3000
3043
  this.client = new GraphQLClient({
3001
3044
  baseUrl: config.baseUrl ?? defaultBaseUrl,
@@ -3321,13 +3364,148 @@ var PeekAccessService = class {
3321
3364
  return this.getReviewService().getReviews(productId, reviewCount, reviewOffset);
3322
3365
  }
3323
3366
  };
3324
- function requireNonEmpty(value, name) {
3325
- if (!value) {
3326
- throw new Error(`PeekAccessService: "${name}" is required`);
3327
- }
3367
+
3368
+ // src/internal/cng/endpoints.ts
3369
+ var CNG_EXTENDABLE_SLUG = "cng_backoffice_api-v1";
3370
+ var PRODUCTS_PATH = "api/v2/commerce-config/products";
3371
+
3372
+ // src/models/cng/product.ts
3373
+ var ACTIVITY_PRODUCT_TYPE2 = "ACTIVITY";
3374
+
3375
+ // src/internal/cng/products/product-converter.ts
3376
+ function fromProductNodes(nodes) {
3377
+ return nodes.map(fromProductNode);
3378
+ }
3379
+ function fromProductNode(node) {
3380
+ return {
3381
+ productId: node.id || "",
3382
+ name: node.name || "",
3383
+ type: node.product_type || ACTIVITY_PRODUCT_TYPE2,
3384
+ color: node.color_hex || "",
3385
+ tickets: (node.tickets ?? []).map((ticket) => ({
3386
+ id: ticket.id,
3387
+ name: ticket.name
3388
+ }))
3389
+ };
3328
3390
  }
3329
3391
 
3330
- // src/internal/bookings/booking-webhook.ts
3392
+ // src/internal/cng/products/product-service.ts
3393
+ var CngProductService = class {
3394
+ constructor(client) {
3395
+ this.client = client;
3396
+ }
3397
+ client;
3398
+ /**
3399
+ * Returns every activity as a single flat list.
3400
+ *
3401
+ * @example
3402
+ * ```ts
3403
+ * const activities = await cng.getProductService().getAllActivities();
3404
+ * ```
3405
+ */
3406
+ async getAllActivities() {
3407
+ const body = await this.client.get(
3408
+ PRODUCTS_PATH
3409
+ );
3410
+ const nodes = Array.isArray(body) ? body : body?.products ?? [];
3411
+ return fromProductNodes(nodes ?? []);
3412
+ }
3413
+ };
3414
+
3415
+ // src/internal/cng/rest-client.ts
3416
+ var RestClient = class {
3417
+ constructor(options) {
3418
+ this.options = options;
3419
+ }
3420
+ options;
3421
+ /**
3422
+ * Issues a GET against the named REST path and returns the parsed JSON body.
3423
+ * Retries on HTTP 429 per the configured backoff (via the shared
3424
+ * {@link requestWithRetry} loop).
3425
+ *
3426
+ * @throws {AdminAccountRequiredError} on HTTP 418
3427
+ * @throws {RateLimitError} on HTTP 429 after retries are exhausted
3428
+ * @throws {CngApiError} on any other non-2xx response
3429
+ */
3430
+ async get(path) {
3431
+ const { logger } = this.options;
3432
+ const url = this.endpoint(path);
3433
+ logger.info("Making CNG request", { url, path });
3434
+ return requestWithRetry(
3435
+ this.options,
3436
+ url,
3437
+ { method: "GET", headers: this.buildHeaders() },
3438
+ path,
3439
+ async (response) => {
3440
+ const body = await this.parseBody(response);
3441
+ if (!response.ok) {
3442
+ logger.error(`CNG request failed with HTTP ${response.status}`, { url });
3443
+ throw new CngApiError(response.status, body);
3444
+ }
3445
+ return body;
3446
+ }
3447
+ );
3448
+ }
3449
+ /** Reads the response body as JSON, falling back to raw text when unparseable. */
3450
+ async parseBody(response) {
3451
+ const text = await response.text();
3452
+ try {
3453
+ return JSON.parse(text);
3454
+ } catch {
3455
+ return text;
3456
+ }
3457
+ }
3458
+ endpoint(path) {
3459
+ const { baseUrl, appId, extendableSlug } = this.options;
3460
+ return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
3461
+ }
3462
+ buildHeaders() {
3463
+ return {
3464
+ "X-Peek-Auth": `Bearer ${this.options.getToken()}`,
3465
+ "Content-Type": "application/json"
3466
+ };
3467
+ }
3468
+ };
3469
+
3470
+ // src/cng-access-service.ts
3471
+ var DEFAULT_BASE_URL2 = "https://app-registry.peeklabs.com/installations-api";
3472
+ var CngAccessService = class {
3473
+ client;
3474
+ productService;
3475
+ constructor(config) {
3476
+ requireNonEmpty(config.installId, "installId", "CngAccessService");
3477
+ requireNonEmpty(config.jwtSecret, "jwtSecret", "CngAccessService");
3478
+ requireNonEmpty(config.issuer, "issuer", "CngAccessService");
3479
+ requireNonEmpty(config.appId, "appId", "CngAccessService");
3480
+ const logger = config.logger ?? noopLogger;
3481
+ const tokens = createTokenManager(config);
3482
+ this.client = new RestClient({
3483
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL2,
3484
+ appId: config.appId,
3485
+ extendableSlug: CNG_EXTENDABLE_SLUG,
3486
+ getToken: () => tokens.getToken(),
3487
+ retryDelaysMs: config.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS,
3488
+ logger,
3489
+ fetchFn: config.fetch ?? globalThis.fetch
3490
+ });
3491
+ }
3492
+ /**
3493
+ * Returns the {@link CngProductService} for this install, bound to the shared
3494
+ * authenticated transport. The instance is created lazily and reused.
3495
+ */
3496
+ getProductService() {
3497
+ if (!this.productService) {
3498
+ this.productService = new CngProductService(this.client);
3499
+ }
3500
+ return this.productService;
3501
+ }
3502
+ /** All activities. Delegates to {@link CngProductService.getAllActivities}. */
3503
+ getAllActivities() {
3504
+ return this.getProductService().getAllActivities();
3505
+ }
3506
+ };
3507
+
3508
+ // src/internal/peek/bookings/booking-webhook.ts
3331
3509
  var PAYLOAD_BOOKING_KEY = "booking";
3332
3510
  var VALUE_OPEN_TOKEN = "value {";
3333
3511
  var PRICE_BREAKDOWN_KEYS = [
@@ -3377,7 +3555,7 @@ function hasPriceBreakdown(node) {
3377
3555
  return PRICE_BREAKDOWN_KEYS.some((key) => key in value);
3378
3556
  }
3379
3557
 
3380
- // src/internal/waivers/waiver-webhook.ts
3558
+ // src/internal/peek/waivers/waiver-webhook.ts
3381
3559
  var PAYLOAD_WAIVER_KEY = "waiver";
3382
3560
  function parseWaiverWebhook(payload) {
3383
3561
  return fromWaiverNode(extractWaiverNode(payload));
@@ -3418,6 +3596,9 @@ exports.AccountUserService = AccountUserService;
3418
3596
  exports.AdminAccountRequiredError = AdminAccountRequiredError;
3419
3597
  exports.AvailabilityService = AvailabilityService;
3420
3598
  exports.BookingService = BookingService;
3599
+ exports.CngAccessService = CngAccessService;
3600
+ exports.CngApiError = CngApiError;
3601
+ exports.CngProductService = CngProductService;
3421
3602
  exports.DailyNoteService = DailyNoteService;
3422
3603
  exports.MembershipService = MembershipService;
3423
3604
  exports.PeekAccessService = PeekAccessService;