@peektravel/app-utilities 0.5.4 → 0.6.1

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.d.cts CHANGED
@@ -345,6 +345,22 @@ interface Product {
345
345
  * pricing overrides use this to set the currency on fixed-price adjustments.
346
346
  */
347
347
  currency: string;
348
+ /**
349
+ * URL of the product's primary display image, or `null` when Peek reports
350
+ * none. Populated for activities/rentals; always `null` for add-ons.
351
+ */
352
+ imageUrl: string | null;
353
+ /**
354
+ * Long-form product description (may contain HTML), or `null` when Peek
355
+ * reports none. Populated for activities/rentals; always `null` for add-ons.
356
+ */
357
+ description: string | null;
358
+ /**
359
+ * Where the experience meets, or `null` when Peek reports no location detail.
360
+ * Populated for activities/rentals; always `null` for add-ons. Each field
361
+ * inside is independently nullable.
362
+ */
363
+ meetingLocation: ProductMeetingLocation | null;
348
364
  /**
349
365
  * The bookable sub-options of this product.
350
366
  *
@@ -353,6 +369,15 @@ interface Product {
353
369
  */
354
370
  tickets: ProductTicket[];
355
371
  }
372
+ /** The meeting point for a bookable experience. Every field is nullable. */
373
+ interface ProductMeetingLocation {
374
+ /** Free-text summary / instructions for the meeting point. */
375
+ summary: string | null;
376
+ /** Formatted postal address of the meeting point. */
377
+ address: string | null;
378
+ /** Map/link URL for the meeting point. */
379
+ url: string | null;
380
+ }
356
381
  /** A single bookable sub-option (resource option or add-on item option). */
357
382
  interface ProductTicket {
358
383
  /** Unique identifier of the ticket / option. */
@@ -1766,13 +1791,18 @@ declare class PricingService {
1766
1791
  private validateResourceOption;
1767
1792
  }
1768
1793
 
1794
+ /**
1795
+ * Every platform this package knows about. Exported as a value (not just a
1796
+ * union) so runtime code can recognise a reported platform without restating
1797
+ * the list. New platforms extend this array.
1798
+ */
1799
+ declare const PEEK_PLATFORMS: readonly ["peek", "cng", "acme"];
1769
1800
  /**
1770
1801
  * Platform that embedded the app for this session. Verification is
1771
1802
  * brand-agnostic — one peek-auth JWT is minted for every platform — so this
1772
1803
  * claim is the single thing that identifies which platform a request came from.
1773
- * New platforms extend this union.
1774
1804
  */
1775
- type PeekPlatform = "peek" | "cng" | "acme";
1805
+ type PeekPlatform = (typeof PEEK_PLATFORMS)[number];
1776
1806
  /** User context embedded in a Peek auth token. */
1777
1807
  interface PeekAuthTokenUser {
1778
1808
  /** User's email address. */
@@ -1904,10 +1934,13 @@ declare class PeekAccessService {
1904
1934
  * malformed
1905
1935
  * - `TokenExpiredError` — past `exp`
1906
1936
  * - `NotBeforeError` — before `nbf`
1937
+ * - `InvalidPeekTokenError` — signature valid but the `user` block is missing
1938
+ * its `id`
1907
1939
  *
1908
1940
  * @throws {JsonWebTokenError} signature invalid or token malformed
1909
1941
  * @throws {TokenExpiredError} token has expired
1910
1942
  * @throws {NotBeforeError} token not yet valid
1943
+ * @throws {InvalidPeekTokenError} verified token has no `user.id`
1911
1944
  */
1912
1945
  verifyPeekAuthToken(token: string): PeekAuthTokenClaims;
1913
1946
  /**
@@ -2415,6 +2448,128 @@ declare function parseWaiverWebhook(payload: unknown, options?: AccessOptions):
2415
2448
  */
2416
2449
  declare function verifyInstallWebhook(token: string, secret: string): InstallWebhookClaims;
2417
2450
 
2451
+ /**
2452
+ * The clean data model for an app-store **install lifecycle event** — the JSON
2453
+ * webhook body Peek's app registry POSTs when an app is installed, uninstalled,
2454
+ * or updated.
2455
+ *
2456
+ * This is the sibling of the signed-JWT channel behind `verifyInstallWebhook`
2457
+ * (see `models/peek/auth-token.ts`). The two carry the same event; this one is
2458
+ * the richer of the pair, because only it reports the account **name**,
2459
+ * **platform**, and **test** flag.
2460
+ *
2461
+ * It is also the single origin of the account id in the whole system: no
2462
+ * GraphQL read and no peek-auth token returns it, so whatever a consumer
2463
+ * persists from this event is all it will ever have.
2464
+ */
2465
+
2466
+ /**
2467
+ * Every install lifecycle status Peek's app registry sends today. Exported so
2468
+ * consumers can validate or enumerate without restating the vocabulary.
2469
+ */
2470
+ declare const INSTALL_STATUSES: readonly ["installed", "uninstalled", "update_installed"];
2471
+ /** Install lifecycle status. */
2472
+ type InstallStatus = (typeof INSTALL_STATUSES)[number];
2473
+ /**
2474
+ * Who an install is — the core account data, in one object.
2475
+ *
2476
+ * Flat and JSON-safe by design: it round-trips through a datastore unchanged,
2477
+ * so the same shape the parser returns is the shape a consumer persists and
2478
+ * later rehydrates. There is deliberately no second representation.
2479
+ */
2480
+ interface InstallIdentity {
2481
+ /** Install ID — Peek-assigned UUID. Also the subject of every peek-auth JWT. */
2482
+ installId: string;
2483
+ /**
2484
+ * Stable Peek account id — the same value Peek calls the **partner ID**.
2485
+ * This event is its only source; it cannot be re-fetched later.
2486
+ */
2487
+ accountId: string;
2488
+ /** Partner display name. */
2489
+ accountName: string;
2490
+ /**
2491
+ * Which Peek platform owns the install, or `null` when the registry reported
2492
+ * a platform this version of the package does not know. `null` means "newer
2493
+ * than this package", **not** "absent" — do not coerce it to a default.
2494
+ *
2495
+ * **Persist and track this per install.** It is not cosmetic: the platform
2496
+ * determines which APIs the install is served by, and therefore which access
2497
+ * service a consumer must construct for it — `PeekAccessService` for `"peek"`,
2498
+ * `CngAccessService` for `"cng"`, `AcmeAccessService` for `"acme"`, each
2499
+ * routed at a different gateway. Two installs of the same app can sit on
2500
+ * different platforms, so this cannot be inferred from app-level config and
2501
+ * has no source other than this event.
2502
+ */
2503
+ platform: PeekPlatform | null;
2504
+ /** Whether this is a test account. Defaults to `false` when not reported. */
2505
+ isTest: boolean;
2506
+ }
2507
+ /** A parsed install lifecycle event: what happened, to which install. */
2508
+ interface InstallEvent {
2509
+ /**
2510
+ * The lifecycle status, or `null` when the registry sent a status this
2511
+ * version of the package does not know. Handle `null` explicitly — treating
2512
+ * it as a no-op silently drops a lifecycle transition.
2513
+ */
2514
+ status: InstallStatus | null;
2515
+ /** The status exactly as delivered, for logging and for the `null` case. */
2516
+ rawStatus: string;
2517
+ /** App display version at the time of the event. */
2518
+ displayVersion: string;
2519
+ /** The install's core account data. */
2520
+ identity: InstallIdentity;
2521
+ }
2522
+
2523
+ /**
2524
+ * Public webhook surface for the **install-event** channel — the JSON body
2525
+ * Peek's app registry POSTs on install, uninstall, and update.
2526
+ *
2527
+ * This is the second of the two install channels. Its sibling,
2528
+ * `verifyInstallWebhook`, receives the same lifecycle event as a signed
2529
+ * `app_registry_v2` JWT; that payload authenticates itself but carries only the
2530
+ * account **id**. This one carries the account's name, platform, and test flag
2531
+ * as well, which makes it the authoritative source of {@link InstallIdentity}.
2532
+ *
2533
+ * Like the booking and waiver parsers, this is a pure transform — no auth, no
2534
+ * network, no client — so it is a standalone function rather than a method on
2535
+ * `PeekAccessService`. The receiver holds no gateway credentials for an install
2536
+ * it has not recorded yet, and an uninstall describes one being torn down.
2537
+ *
2538
+ * **It never throws.** The sender is first-party and the payload contract is
2539
+ * Peek's own, so defending against malformed input would be defending against a
2540
+ * case that does not arise. What the parser does guard is the contract
2541
+ * *growing*: an unrecognised `status` or `platform` surfaces as `null` rather
2542
+ * than being coerced into a known value, so a newer registry can never be
2543
+ * silently mistaken for an older one.
2544
+ */
2545
+
2546
+ /**
2547
+ * Parses a delivered install-event webhook body into a clean
2548
+ * {@link InstallEvent}.
2549
+ *
2550
+ * Accepts the raw request body, or a JSON string, which is parsed first.
2551
+ * Missing fields map to `""`/`false`; an unknown `status` or `platform` maps to
2552
+ * `null` with the wire value preserved on {@link InstallEvent.rawStatus}.
2553
+ *
2554
+ * Handle a `null` `status` explicitly. Peek treats any 2xx as delivered and
2555
+ * will not redeliver, so responding successfully to a status the package does
2556
+ * not recognise silently drops a lifecycle transition:
2557
+ *
2558
+ * ```ts
2559
+ * const event = parseInstallEvent(req.body);
2560
+ * switch (event.status) {
2561
+ * case "installed": return provision(event.identity, event.displayVersion);
2562
+ * case "uninstalled": return deprovision(event.identity);
2563
+ * case "update_installed": return recordVersion(event.identity, event.displayVersion);
2564
+ * default: throw new Error(`unknown install status: ${event.rawStatus}`);
2565
+ * }
2566
+ * ```
2567
+ *
2568
+ * Persist `event.identity` as a unit: it is flat and JSON-safe, and its
2569
+ * `accountId` (the partner ID) has no other source in the system.
2570
+ */
2571
+ declare function parseInstallEvent(payload: unknown): InstallEvent;
2572
+
2418
2573
  /**
2419
2574
  * Typed errors thrown by the package. Each mirrors a failure mode of the Peek
2420
2575
  * GraphQL gateway (or the CNG REST gateway) so callers can branch on the error
@@ -2473,6 +2628,19 @@ declare class PeekHttpError extends Error {
2473
2628
  readonly body: unknown;
2474
2629
  constructor(statusCode: number, url: string, body: unknown, message?: string);
2475
2630
  }
2631
+ /**
2632
+ * Thrown when an `app_registry_v2` token passes signature/issuer/audience
2633
+ * verification but its `user` block is missing a required `id`. The signature is
2634
+ * authentic, so this signals a structurally malformed token from the app
2635
+ * registry rather than a forged one — distinct from the `jsonwebtoken`
2636
+ * `JsonWebTokenError` family. The offending field is preserved on
2637
+ * {@link InvalidPeekTokenError.field}.
2638
+ */
2639
+ declare class InvalidPeekTokenError extends Error {
2640
+ /** The token field that was missing or empty (e.g. `"user.id"`). */
2641
+ readonly field: string;
2642
+ constructor(field: string);
2643
+ }
2476
2644
  /**
2477
2645
  * Thrown when a payment or booking-modification operation is called on an
2478
2646
  * access service that was constructed without `fullCustomerAccess` (PII access
@@ -2513,4 +2681,4 @@ declare class AcmeApiError extends Error {
2513
2681
  constructor(statusCode: number, body: unknown, message?: string);
2514
2682
  }
2515
2683
 
2516
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, type AccessOptions, type AccountUser, AccountUserService, type AccountUserServiceOptions, AcmeAccessService, type AcmeAccessServiceConfig, type AcmeActivity, type AcmeActivityTicket, AcmeApiError, AcmeProductService, type Activity, type ActivityOverrides, type ActivityTicket, type AddAddonInput, AdminAccountRequiredError, type Agent, type AssignGuideResult, type AssignedActivity, type AssignedResource, type Availability, AvailabilityService, type AvailabilityTime, type AvailabilityTimesQuery, type Booking, type BookingAddon, type BookingAddonMoney, type BookingAddonOption, type BookingAddons, type BookingAddonsMutationResult, type BookingPaymentsOnFile, type BookingReadOptions, type BookingSearchBy, BookingService, type BookingServiceOptions, type BookingTimeRangeSearch, type CancelBookingResult, type Channel, type ClearOverridesInput, CngAccessService, type CngAccessServiceConfig, CngApiError, CngProductService, type CreateBookingGuest, type CreateBookingInput, type CreateBookingTicket, type CreateEngineInput, type CreatePromoCodeInput, type CreatedBooking, type CreatedPricingEngine, type CreatedPromoCode, type CustomQuestionAnswer, type DailyNote, DailyNoteService, type Duration, type Guest, type GuestMetadata, type Guide, type GuideAssignment, type InstallWebhookAccount, type InstallWebhookClaims, type InvoiceLinkResult, type Logger, type MakePaymentInput, type MakePaymentResult, type Membership, type MembershipPurchaseInput, MembershipService, type NoteMode, type Payment, type PaymentSource, PeekAccessService, type PeekAccessServiceConfig, type PeekAuthTokenClaims, type PeekAuthTokenUser, PeekGraphQLError, PeekHttpError, type PeekPlatform, PiiAccessDisabledError, type Price, type PricingActivityContext, type PricingEngine, type PricingMoney, type PricingNamedRef, type PricingOverride, type PricingOverrideFilter, PricingService, type Product, ProductService, type ProductServiceOptions, type ProductTicket, type PromoCode, type PromoCodeFixedAmount, PromoCodeService, type PromoCodeServiceOptions, type PurchasedMembership, RENTAL_PRODUCT_TYPE, RateLimitError, type RefundInput, type RefundResult, ResellerService, type ResolvedOverride, type ResolvedPricingOverride, type ResolvedResourceOption, type Resource, type ResourceOptionOverride, type ResourceOptionQuantity, type ResourcePool, type ResourcePoolAccountUser, type ResourcePoolAssignment, type ResourcePoolMode, ResourcePoolService, type Review, ReviewService, type Ticket, type Timeslot, type TimeslotFilter, TimeslotService, type UpdateEngineInput, type UpdateTimeslotResult, type UpsertOverridesInput, type UpsertOverridesResult, type Waiver, noopLogger, parseBookingWebhook, parseWaiverWebhook, verifyInstallWebhook };
2684
+ export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, type AccessOptions, type AccountUser, AccountUserService, type AccountUserServiceOptions, AcmeAccessService, type AcmeAccessServiceConfig, type AcmeActivity, type AcmeActivityTicket, AcmeApiError, AcmeProductService, type Activity, type ActivityOverrides, type ActivityTicket, type AddAddonInput, AdminAccountRequiredError, type Agent, type AssignGuideResult, type AssignedActivity, type AssignedResource, type Availability, AvailabilityService, type AvailabilityTime, type AvailabilityTimesQuery, type Booking, type BookingAddon, type BookingAddonMoney, type BookingAddonOption, type BookingAddons, type BookingAddonsMutationResult, type BookingPaymentsOnFile, type BookingReadOptions, type BookingSearchBy, BookingService, type BookingServiceOptions, type BookingTimeRangeSearch, type CancelBookingResult, type Channel, type ClearOverridesInput, CngAccessService, type CngAccessServiceConfig, CngApiError, CngProductService, type CreateBookingGuest, type CreateBookingInput, type CreateBookingTicket, type CreateEngineInput, type CreatePromoCodeInput, type CreatedBooking, type CreatedPricingEngine, type CreatedPromoCode, type CustomQuestionAnswer, type DailyNote, DailyNoteService, type Duration, type Guest, type GuestMetadata, type Guide, type GuideAssignment, INSTALL_STATUSES, type InstallEvent, type InstallIdentity, type InstallStatus, type InstallWebhookAccount, type InstallWebhookClaims, InvalidPeekTokenError, type InvoiceLinkResult, type Logger, type MakePaymentInput, type MakePaymentResult, type Membership, type MembershipPurchaseInput, MembershipService, type NoteMode, type Payment, type PaymentSource, PeekAccessService, type PeekAccessServiceConfig, type PeekAuthTokenClaims, type PeekAuthTokenUser, PeekGraphQLError, PeekHttpError, type PeekPlatform, PiiAccessDisabledError, type Price, type PricingActivityContext, type PricingEngine, type PricingMoney, type PricingNamedRef, type PricingOverride, type PricingOverrideFilter, PricingService, type Product, type ProductMeetingLocation, ProductService, type ProductServiceOptions, type ProductTicket, type PromoCode, type PromoCodeFixedAmount, PromoCodeService, type PromoCodeServiceOptions, type PurchasedMembership, RENTAL_PRODUCT_TYPE, RateLimitError, type RefundInput, type RefundResult, ResellerService, type ResolvedOverride, type ResolvedPricingOverride, type ResolvedResourceOption, type Resource, type ResourceOptionOverride, type ResourceOptionQuantity, type ResourcePool, type ResourcePoolAccountUser, type ResourcePoolAssignment, type ResourcePoolMode, ResourcePoolService, type Review, ReviewService, type Ticket, type Timeslot, type TimeslotFilter, TimeslotService, type UpdateEngineInput, type UpdateTimeslotResult, type UpsertOverridesInput, type UpsertOverridesResult, type Waiver, noopLogger, parseBookingWebhook, parseInstallEvent, parseWaiverWebhook, verifyInstallWebhook };
package/dist/index.d.ts CHANGED
@@ -345,6 +345,22 @@ interface Product {
345
345
  * pricing overrides use this to set the currency on fixed-price adjustments.
346
346
  */
347
347
  currency: string;
348
+ /**
349
+ * URL of the product's primary display image, or `null` when Peek reports
350
+ * none. Populated for activities/rentals; always `null` for add-ons.
351
+ */
352
+ imageUrl: string | null;
353
+ /**
354
+ * Long-form product description (may contain HTML), or `null` when Peek
355
+ * reports none. Populated for activities/rentals; always `null` for add-ons.
356
+ */
357
+ description: string | null;
358
+ /**
359
+ * Where the experience meets, or `null` when Peek reports no location detail.
360
+ * Populated for activities/rentals; always `null` for add-ons. Each field
361
+ * inside is independently nullable.
362
+ */
363
+ meetingLocation: ProductMeetingLocation | null;
348
364
  /**
349
365
  * The bookable sub-options of this product.
350
366
  *
@@ -353,6 +369,15 @@ interface Product {
353
369
  */
354
370
  tickets: ProductTicket[];
355
371
  }
372
+ /** The meeting point for a bookable experience. Every field is nullable. */
373
+ interface ProductMeetingLocation {
374
+ /** Free-text summary / instructions for the meeting point. */
375
+ summary: string | null;
376
+ /** Formatted postal address of the meeting point. */
377
+ address: string | null;
378
+ /** Map/link URL for the meeting point. */
379
+ url: string | null;
380
+ }
356
381
  /** A single bookable sub-option (resource option or add-on item option). */
357
382
  interface ProductTicket {
358
383
  /** Unique identifier of the ticket / option. */
@@ -1766,13 +1791,18 @@ declare class PricingService {
1766
1791
  private validateResourceOption;
1767
1792
  }
1768
1793
 
1794
+ /**
1795
+ * Every platform this package knows about. Exported as a value (not just a
1796
+ * union) so runtime code can recognise a reported platform without restating
1797
+ * the list. New platforms extend this array.
1798
+ */
1799
+ declare const PEEK_PLATFORMS: readonly ["peek", "cng", "acme"];
1769
1800
  /**
1770
1801
  * Platform that embedded the app for this session. Verification is
1771
1802
  * brand-agnostic — one peek-auth JWT is minted for every platform — so this
1772
1803
  * claim is the single thing that identifies which platform a request came from.
1773
- * New platforms extend this union.
1774
1804
  */
1775
- type PeekPlatform = "peek" | "cng" | "acme";
1805
+ type PeekPlatform = (typeof PEEK_PLATFORMS)[number];
1776
1806
  /** User context embedded in a Peek auth token. */
1777
1807
  interface PeekAuthTokenUser {
1778
1808
  /** User's email address. */
@@ -1904,10 +1934,13 @@ declare class PeekAccessService {
1904
1934
  * malformed
1905
1935
  * - `TokenExpiredError` — past `exp`
1906
1936
  * - `NotBeforeError` — before `nbf`
1937
+ * - `InvalidPeekTokenError` — signature valid but the `user` block is missing
1938
+ * its `id`
1907
1939
  *
1908
1940
  * @throws {JsonWebTokenError} signature invalid or token malformed
1909
1941
  * @throws {TokenExpiredError} token has expired
1910
1942
  * @throws {NotBeforeError} token not yet valid
1943
+ * @throws {InvalidPeekTokenError} verified token has no `user.id`
1911
1944
  */
1912
1945
  verifyPeekAuthToken(token: string): PeekAuthTokenClaims;
1913
1946
  /**
@@ -2415,6 +2448,128 @@ declare function parseWaiverWebhook(payload: unknown, options?: AccessOptions):
2415
2448
  */
2416
2449
  declare function verifyInstallWebhook(token: string, secret: string): InstallWebhookClaims;
2417
2450
 
2451
+ /**
2452
+ * The clean data model for an app-store **install lifecycle event** — the JSON
2453
+ * webhook body Peek's app registry POSTs when an app is installed, uninstalled,
2454
+ * or updated.
2455
+ *
2456
+ * This is the sibling of the signed-JWT channel behind `verifyInstallWebhook`
2457
+ * (see `models/peek/auth-token.ts`). The two carry the same event; this one is
2458
+ * the richer of the pair, because only it reports the account **name**,
2459
+ * **platform**, and **test** flag.
2460
+ *
2461
+ * It is also the single origin of the account id in the whole system: no
2462
+ * GraphQL read and no peek-auth token returns it, so whatever a consumer
2463
+ * persists from this event is all it will ever have.
2464
+ */
2465
+
2466
+ /**
2467
+ * Every install lifecycle status Peek's app registry sends today. Exported so
2468
+ * consumers can validate or enumerate without restating the vocabulary.
2469
+ */
2470
+ declare const INSTALL_STATUSES: readonly ["installed", "uninstalled", "update_installed"];
2471
+ /** Install lifecycle status. */
2472
+ type InstallStatus = (typeof INSTALL_STATUSES)[number];
2473
+ /**
2474
+ * Who an install is — the core account data, in one object.
2475
+ *
2476
+ * Flat and JSON-safe by design: it round-trips through a datastore unchanged,
2477
+ * so the same shape the parser returns is the shape a consumer persists and
2478
+ * later rehydrates. There is deliberately no second representation.
2479
+ */
2480
+ interface InstallIdentity {
2481
+ /** Install ID — Peek-assigned UUID. Also the subject of every peek-auth JWT. */
2482
+ installId: string;
2483
+ /**
2484
+ * Stable Peek account id — the same value Peek calls the **partner ID**.
2485
+ * This event is its only source; it cannot be re-fetched later.
2486
+ */
2487
+ accountId: string;
2488
+ /** Partner display name. */
2489
+ accountName: string;
2490
+ /**
2491
+ * Which Peek platform owns the install, or `null` when the registry reported
2492
+ * a platform this version of the package does not know. `null` means "newer
2493
+ * than this package", **not** "absent" — do not coerce it to a default.
2494
+ *
2495
+ * **Persist and track this per install.** It is not cosmetic: the platform
2496
+ * determines which APIs the install is served by, and therefore which access
2497
+ * service a consumer must construct for it — `PeekAccessService` for `"peek"`,
2498
+ * `CngAccessService` for `"cng"`, `AcmeAccessService` for `"acme"`, each
2499
+ * routed at a different gateway. Two installs of the same app can sit on
2500
+ * different platforms, so this cannot be inferred from app-level config and
2501
+ * has no source other than this event.
2502
+ */
2503
+ platform: PeekPlatform | null;
2504
+ /** Whether this is a test account. Defaults to `false` when not reported. */
2505
+ isTest: boolean;
2506
+ }
2507
+ /** A parsed install lifecycle event: what happened, to which install. */
2508
+ interface InstallEvent {
2509
+ /**
2510
+ * The lifecycle status, or `null` when the registry sent a status this
2511
+ * version of the package does not know. Handle `null` explicitly — treating
2512
+ * it as a no-op silently drops a lifecycle transition.
2513
+ */
2514
+ status: InstallStatus | null;
2515
+ /** The status exactly as delivered, for logging and for the `null` case. */
2516
+ rawStatus: string;
2517
+ /** App display version at the time of the event. */
2518
+ displayVersion: string;
2519
+ /** The install's core account data. */
2520
+ identity: InstallIdentity;
2521
+ }
2522
+
2523
+ /**
2524
+ * Public webhook surface for the **install-event** channel — the JSON body
2525
+ * Peek's app registry POSTs on install, uninstall, and update.
2526
+ *
2527
+ * This is the second of the two install channels. Its sibling,
2528
+ * `verifyInstallWebhook`, receives the same lifecycle event as a signed
2529
+ * `app_registry_v2` JWT; that payload authenticates itself but carries only the
2530
+ * account **id**. This one carries the account's name, platform, and test flag
2531
+ * as well, which makes it the authoritative source of {@link InstallIdentity}.
2532
+ *
2533
+ * Like the booking and waiver parsers, this is a pure transform — no auth, no
2534
+ * network, no client — so it is a standalone function rather than a method on
2535
+ * `PeekAccessService`. The receiver holds no gateway credentials for an install
2536
+ * it has not recorded yet, and an uninstall describes one being torn down.
2537
+ *
2538
+ * **It never throws.** The sender is first-party and the payload contract is
2539
+ * Peek's own, so defending against malformed input would be defending against a
2540
+ * case that does not arise. What the parser does guard is the contract
2541
+ * *growing*: an unrecognised `status` or `platform` surfaces as `null` rather
2542
+ * than being coerced into a known value, so a newer registry can never be
2543
+ * silently mistaken for an older one.
2544
+ */
2545
+
2546
+ /**
2547
+ * Parses a delivered install-event webhook body into a clean
2548
+ * {@link InstallEvent}.
2549
+ *
2550
+ * Accepts the raw request body, or a JSON string, which is parsed first.
2551
+ * Missing fields map to `""`/`false`; an unknown `status` or `platform` maps to
2552
+ * `null` with the wire value preserved on {@link InstallEvent.rawStatus}.
2553
+ *
2554
+ * Handle a `null` `status` explicitly. Peek treats any 2xx as delivered and
2555
+ * will not redeliver, so responding successfully to a status the package does
2556
+ * not recognise silently drops a lifecycle transition:
2557
+ *
2558
+ * ```ts
2559
+ * const event = parseInstallEvent(req.body);
2560
+ * switch (event.status) {
2561
+ * case "installed": return provision(event.identity, event.displayVersion);
2562
+ * case "uninstalled": return deprovision(event.identity);
2563
+ * case "update_installed": return recordVersion(event.identity, event.displayVersion);
2564
+ * default: throw new Error(`unknown install status: ${event.rawStatus}`);
2565
+ * }
2566
+ * ```
2567
+ *
2568
+ * Persist `event.identity` as a unit: it is flat and JSON-safe, and its
2569
+ * `accountId` (the partner ID) has no other source in the system.
2570
+ */
2571
+ declare function parseInstallEvent(payload: unknown): InstallEvent;
2572
+
2418
2573
  /**
2419
2574
  * Typed errors thrown by the package. Each mirrors a failure mode of the Peek
2420
2575
  * GraphQL gateway (or the CNG REST gateway) so callers can branch on the error
@@ -2473,6 +2628,19 @@ declare class PeekHttpError extends Error {
2473
2628
  readonly body: unknown;
2474
2629
  constructor(statusCode: number, url: string, body: unknown, message?: string);
2475
2630
  }
2631
+ /**
2632
+ * Thrown when an `app_registry_v2` token passes signature/issuer/audience
2633
+ * verification but its `user` block is missing a required `id`. The signature is
2634
+ * authentic, so this signals a structurally malformed token from the app
2635
+ * registry rather than a forged one — distinct from the `jsonwebtoken`
2636
+ * `JsonWebTokenError` family. The offending field is preserved on
2637
+ * {@link InvalidPeekTokenError.field}.
2638
+ */
2639
+ declare class InvalidPeekTokenError extends Error {
2640
+ /** The token field that was missing or empty (e.g. `"user.id"`). */
2641
+ readonly field: string;
2642
+ constructor(field: string);
2643
+ }
2476
2644
  /**
2477
2645
  * Thrown when a payment or booking-modification operation is called on an
2478
2646
  * access service that was constructed without `fullCustomerAccess` (PII access
@@ -2513,4 +2681,4 @@ declare class AcmeApiError extends Error {
2513
2681
  constructor(statusCode: number, body: unknown, message?: string);
2514
2682
  }
2515
2683
 
2516
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, type AccessOptions, type AccountUser, AccountUserService, type AccountUserServiceOptions, AcmeAccessService, type AcmeAccessServiceConfig, type AcmeActivity, type AcmeActivityTicket, AcmeApiError, AcmeProductService, type Activity, type ActivityOverrides, type ActivityTicket, type AddAddonInput, AdminAccountRequiredError, type Agent, type AssignGuideResult, type AssignedActivity, type AssignedResource, type Availability, AvailabilityService, type AvailabilityTime, type AvailabilityTimesQuery, type Booking, type BookingAddon, type BookingAddonMoney, type BookingAddonOption, type BookingAddons, type BookingAddonsMutationResult, type BookingPaymentsOnFile, type BookingReadOptions, type BookingSearchBy, BookingService, type BookingServiceOptions, type BookingTimeRangeSearch, type CancelBookingResult, type Channel, type ClearOverridesInput, CngAccessService, type CngAccessServiceConfig, CngApiError, CngProductService, type CreateBookingGuest, type CreateBookingInput, type CreateBookingTicket, type CreateEngineInput, type CreatePromoCodeInput, type CreatedBooking, type CreatedPricingEngine, type CreatedPromoCode, type CustomQuestionAnswer, type DailyNote, DailyNoteService, type Duration, type Guest, type GuestMetadata, type Guide, type GuideAssignment, type InstallWebhookAccount, type InstallWebhookClaims, type InvoiceLinkResult, type Logger, type MakePaymentInput, type MakePaymentResult, type Membership, type MembershipPurchaseInput, MembershipService, type NoteMode, type Payment, type PaymentSource, PeekAccessService, type PeekAccessServiceConfig, type PeekAuthTokenClaims, type PeekAuthTokenUser, PeekGraphQLError, PeekHttpError, type PeekPlatform, PiiAccessDisabledError, type Price, type PricingActivityContext, type PricingEngine, type PricingMoney, type PricingNamedRef, type PricingOverride, type PricingOverrideFilter, PricingService, type Product, ProductService, type ProductServiceOptions, type ProductTicket, type PromoCode, type PromoCodeFixedAmount, PromoCodeService, type PromoCodeServiceOptions, type PurchasedMembership, RENTAL_PRODUCT_TYPE, RateLimitError, type RefundInput, type RefundResult, ResellerService, type ResolvedOverride, type ResolvedPricingOverride, type ResolvedResourceOption, type Resource, type ResourceOptionOverride, type ResourceOptionQuantity, type ResourcePool, type ResourcePoolAccountUser, type ResourcePoolAssignment, type ResourcePoolMode, ResourcePoolService, type Review, ReviewService, type Ticket, type Timeslot, type TimeslotFilter, TimeslotService, type UpdateEngineInput, type UpdateTimeslotResult, type UpsertOverridesInput, type UpsertOverridesResult, type Waiver, noopLogger, parseBookingWebhook, parseWaiverWebhook, verifyInstallWebhook };
2684
+ export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, type AccessOptions, type AccountUser, AccountUserService, type AccountUserServiceOptions, AcmeAccessService, type AcmeAccessServiceConfig, type AcmeActivity, type AcmeActivityTicket, AcmeApiError, AcmeProductService, type Activity, type ActivityOverrides, type ActivityTicket, type AddAddonInput, AdminAccountRequiredError, type Agent, type AssignGuideResult, type AssignedActivity, type AssignedResource, type Availability, AvailabilityService, type AvailabilityTime, type AvailabilityTimesQuery, type Booking, type BookingAddon, type BookingAddonMoney, type BookingAddonOption, type BookingAddons, type BookingAddonsMutationResult, type BookingPaymentsOnFile, type BookingReadOptions, type BookingSearchBy, BookingService, type BookingServiceOptions, type BookingTimeRangeSearch, type CancelBookingResult, type Channel, type ClearOverridesInput, CngAccessService, type CngAccessServiceConfig, CngApiError, CngProductService, type CreateBookingGuest, type CreateBookingInput, type CreateBookingTicket, type CreateEngineInput, type CreatePromoCodeInput, type CreatedBooking, type CreatedPricingEngine, type CreatedPromoCode, type CustomQuestionAnswer, type DailyNote, DailyNoteService, type Duration, type Guest, type GuestMetadata, type Guide, type GuideAssignment, INSTALL_STATUSES, type InstallEvent, type InstallIdentity, type InstallStatus, type InstallWebhookAccount, type InstallWebhookClaims, InvalidPeekTokenError, type InvoiceLinkResult, type Logger, type MakePaymentInput, type MakePaymentResult, type Membership, type MembershipPurchaseInput, MembershipService, type NoteMode, type Payment, type PaymentSource, PeekAccessService, type PeekAccessServiceConfig, type PeekAuthTokenClaims, type PeekAuthTokenUser, PeekGraphQLError, PeekHttpError, type PeekPlatform, PiiAccessDisabledError, type Price, type PricingActivityContext, type PricingEngine, type PricingMoney, type PricingNamedRef, type PricingOverride, type PricingOverrideFilter, PricingService, type Product, type ProductMeetingLocation, ProductService, type ProductServiceOptions, type ProductTicket, type PromoCode, type PromoCodeFixedAmount, PromoCodeService, type PromoCodeServiceOptions, type PurchasedMembership, RENTAL_PRODUCT_TYPE, RateLimitError, type RefundInput, type RefundResult, ResellerService, type ResolvedOverride, type ResolvedPricingOverride, type ResolvedResourceOption, type Resource, type ResourceOptionOverride, type ResourceOptionQuantity, type ResourcePool, type ResourcePoolAccountUser, type ResourcePoolAssignment, type ResourcePoolMode, ResourcePoolService, type Review, ReviewService, type Ticket, type Timeslot, type TimeslotFilter, TimeslotService, type UpdateEngineInput, type UpdateTimeslotResult, type UpsertOverridesInput, type UpsertOverridesResult, type Waiver, noopLogger, parseBookingWebhook, parseInstallEvent, parseWaiverWebhook, verifyInstallWebhook };
package/dist/index.js CHANGED
@@ -225,6 +225,15 @@ 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
+ };
228
237
  var PiiAccessDisabledError = class extends Error {
229
238
  /** The name of the operation that was blocked (e.g. `"makePayment"`). */
230
239
  operation;
@@ -2037,6 +2046,9 @@ function verifyPeekJwt(token, secret) {
2037
2046
  });
2038
2047
  }
2039
2048
  function mapPeekTokenUser(u) {
2049
+ if (!u.id) {
2050
+ throw new InvalidPeekTokenError("user.id");
2051
+ }
2040
2052
  return {
2041
2053
  email: u.email,
2042
2054
  id: u.id,
@@ -2854,6 +2866,13 @@ function toPricingMoney(raw) {
2854
2866
 
2855
2867
  // src/internal/peek/products/product-converter.ts
2856
2868
  var ADD_ON_COLOR = "#FFFFFF";
2869
+ function toMeetingLocation(activity) {
2870
+ const summary = activity.infoMeetingLocation ?? null;
2871
+ const address = activity.meetingLocationFormattedAddress ?? null;
2872
+ const url = activity.meetingLocationUrl ?? null;
2873
+ if (summary === null && address === null && url === null) return null;
2874
+ return { summary, address, url };
2875
+ }
2857
2876
  function fromActivities(activities) {
2858
2877
  return activities.map(fromActivity);
2859
2878
  }
@@ -2865,6 +2884,9 @@ function fromActivity(activity) {
2865
2884
  type: activity.type,
2866
2885
  color: activity.colorHex || "",
2867
2886
  currency: activity.currency || "",
2887
+ imageUrl: activity.imageUrl ?? null,
2888
+ description: activity.description ?? null,
2889
+ meetingLocation: toMeetingLocation(activity),
2868
2890
  tickets: (activity.resourceOptions ?? []).map((option) => ({
2869
2891
  id: option.id,
2870
2892
  name: option.name,
@@ -2886,6 +2908,9 @@ function fromItemOptionNodes(nodes) {
2886
2908
  type: ADD_ON_PRODUCT_TYPE,
2887
2909
  color: ADD_ON_COLOR,
2888
2910
  currency: "",
2911
+ imageUrl: null,
2912
+ description: null,
2913
+ meetingLocation: null,
2889
2914
  tickets: []
2890
2915
  };
2891
2916
  grouped.set(itemId, product);
@@ -2911,6 +2936,11 @@ var PRODUCTS_QUERY = `
2911
2936
  type
2912
2937
  colorHex
2913
2938
  currency
2939
+ imageUrl
2940
+ description
2941
+ meetingLocationFormattedAddress
2942
+ meetingLocationUrl
2943
+ infoMeetingLocation
2914
2944
  resourceOptions {
2915
2945
  id
2916
2946
  name
@@ -3542,10 +3572,13 @@ var PeekAccessService = class {
3542
3572
  * malformed
3543
3573
  * - `TokenExpiredError` — past `exp`
3544
3574
  * - `NotBeforeError` — before `nbf`
3575
+ * - `InvalidPeekTokenError` — signature valid but the `user` block is missing
3576
+ * its `id`
3545
3577
  *
3546
3578
  * @throws {JsonWebTokenError} signature invalid or token malformed
3547
3579
  * @throws {TokenExpiredError} token has expired
3548
3580
  * @throws {NotBeforeError} token not yet valid
3581
+ * @throws {InvalidPeekTokenError} verified token has no `user.id`
3549
3582
  */
3550
3583
  verifyPeekAuthToken(token) {
3551
3584
  const payload = verifyPeekJwt(token, this.jwtSecret);
@@ -4228,6 +4261,61 @@ function verifyInstallWebhook(token, secret) {
4228
4261
  };
4229
4262
  }
4230
4263
 
4231
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AcmeAccessService, AcmeApiError, AcmeProductService, AdminAccountRequiredError, AvailabilityService, BookingService, CngAccessService, CngApiError, CngProductService, DailyNoteService, MembershipService, PeekAccessService, PeekGraphQLError, PeekHttpError, PiiAccessDisabledError, PricingService, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, noopLogger, parseBookingWebhook, parseWaiverWebhook, verifyInstallWebhook };
4264
+ // src/models/peek/auth-token.ts
4265
+ var PEEK_PLATFORMS = ["peek", "cng", "acme"];
4266
+
4267
+ // src/models/peek/install.ts
4268
+ var INSTALL_STATUSES = [
4269
+ "installed",
4270
+ "uninstalled",
4271
+ "update_installed"
4272
+ ];
4273
+
4274
+ // src/internal/peek/installs/install-converter.ts
4275
+ function toInstallStatus(raw) {
4276
+ return INSTALL_STATUSES.includes(raw) ? raw : null;
4277
+ }
4278
+ function toPeekPlatform(raw) {
4279
+ return PEEK_PLATFORMS.includes(raw) ? raw : null;
4280
+ }
4281
+ function fromInstallIdentityNode(node) {
4282
+ const data = node ?? {};
4283
+ const account = data.account ?? {};
4284
+ return {
4285
+ installId: data.install_id || "",
4286
+ accountId: account.id || "",
4287
+ accountName: account.name || "",
4288
+ platform: toPeekPlatform(account.platform || ""),
4289
+ isTest: Boolean(account.is_test)
4290
+ };
4291
+ }
4292
+ function fromInstallEventNode(node) {
4293
+ const rawStatus = node?.status || "";
4294
+ return {
4295
+ status: toInstallStatus(rawStatus),
4296
+ rawStatus,
4297
+ displayVersion: node?.display_version || "",
4298
+ identity: fromInstallIdentityNode(node)
4299
+ };
4300
+ }
4301
+
4302
+ // src/internal/peek/installs/install-event.ts
4303
+ function parseInstallEvent(payload) {
4304
+ return fromInstallEventNode(extractInstallEventNode(payload));
4305
+ }
4306
+ function extractInstallEventNode(payload) {
4307
+ let body = payload;
4308
+ if (typeof body === "string") {
4309
+ try {
4310
+ body = JSON.parse(body);
4311
+ } catch {
4312
+ return {};
4313
+ }
4314
+ }
4315
+ if (!body || typeof body !== "object") return {};
4316
+ return body;
4317
+ }
4318
+
4319
+ 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, parseInstallEvent, parseWaiverWebhook, verifyInstallWebhook };
4232
4320
  //# sourceMappingURL=index.js.map
4233
4321
  //# sourceMappingURL=index.js.map