@peektravel/app-utilities 0.6.0 → 0.7.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/README.md +54 -26
- package/dist/index.cjs +64 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +159 -5
- package/dist/index.d.ts +159 -5
- package/dist/index.js +62 -1
- package/dist/index.js.map +1 -1
- package/docs/webhooks.md +130 -51
- package/llms.txt +29 -13
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1791,13 +1791,18 @@ declare class PricingService {
|
|
|
1791
1791
|
private validateResourceOption;
|
|
1792
1792
|
}
|
|
1793
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"];
|
|
1794
1800
|
/**
|
|
1795
1801
|
* Platform that embedded the app for this session. Verification is
|
|
1796
1802
|
* brand-agnostic — one peek-auth JWT is minted for every platform — so this
|
|
1797
1803
|
* claim is the single thing that identifies which platform a request came from.
|
|
1798
|
-
* New platforms extend this union.
|
|
1799
1804
|
*/
|
|
1800
|
-
type PeekPlatform =
|
|
1805
|
+
type PeekPlatform = (typeof PEEK_PLATFORMS)[number];
|
|
1801
1806
|
/** User context embedded in a Peek auth token. */
|
|
1802
1807
|
interface PeekAuthTokenUser {
|
|
1803
1808
|
/** User's email address. */
|
|
@@ -1929,10 +1934,13 @@ declare class PeekAccessService {
|
|
|
1929
1934
|
* malformed
|
|
1930
1935
|
* - `TokenExpiredError` — past `exp`
|
|
1931
1936
|
* - `NotBeforeError` — before `nbf`
|
|
1937
|
+
* - `InvalidPeekTokenError` — signature valid but the `user` block is missing
|
|
1938
|
+
* its `id`
|
|
1932
1939
|
*
|
|
1933
1940
|
* @throws {JsonWebTokenError} signature invalid or token malformed
|
|
1934
1941
|
* @throws {TokenExpiredError} token has expired
|
|
1935
1942
|
* @throws {NotBeforeError} token not yet valid
|
|
1943
|
+
* @throws {InvalidPeekTokenError} verified token has no `user.id`
|
|
1936
1944
|
*/
|
|
1937
1945
|
verifyPeekAuthToken(token: string): PeekAuthTokenClaims;
|
|
1938
1946
|
/**
|
|
@@ -2417,15 +2425,148 @@ interface Waiver {
|
|
|
2417
2425
|
*/
|
|
2418
2426
|
declare function parseWaiverWebhook(payload: unknown, options?: AccessOptions): Waiver;
|
|
2419
2427
|
|
|
2428
|
+
/**
|
|
2429
|
+
* The clean data model for an app-store **install lifecycle event** — the single
|
|
2430
|
+
* flat object `parseInstallWebhook` returns for the webhook Peek's app registry
|
|
2431
|
+
* POSTs when an app is installed, uninstalled, or updated.
|
|
2432
|
+
*
|
|
2433
|
+
* That delivery carries two things at once: a **signed `app_registry_v2` JWT**
|
|
2434
|
+
* (the security boundary) and a plain **JSON body** (the enrichment channel).
|
|
2435
|
+
* `parseInstallWebhook` verifies the JWT and merges both into this shape, so a
|
|
2436
|
+
* consumer sees one clean, flat, JSON-safe record instead of two differently
|
|
2437
|
+
* shaped payloads. The verified JWT is authoritative for the fields it carries
|
|
2438
|
+
* (`installId`, `accountId`, `status`, `displayVersion`, `user`); the JSON body
|
|
2439
|
+
* is the only source of `accountName`, `platform`, and `isTest`.
|
|
2440
|
+
*
|
|
2441
|
+
* It is also the single origin of the account id in the whole system: no
|
|
2442
|
+
* GraphQL read returns it, so whatever a consumer persists from this event is
|
|
2443
|
+
* all it will ever have.
|
|
2444
|
+
*/
|
|
2445
|
+
|
|
2446
|
+
/**
|
|
2447
|
+
* Every install lifecycle status Peek's app registry sends today. Exported so
|
|
2448
|
+
* consumers can validate or enumerate without restating the vocabulary.
|
|
2449
|
+
*/
|
|
2450
|
+
declare const INSTALL_STATUSES: readonly ["installed", "uninstalled", "update_installed"];
|
|
2451
|
+
/** Install lifecycle status. */
|
|
2452
|
+
type InstallStatus = (typeof INSTALL_STATUSES)[number];
|
|
2453
|
+
/**
|
|
2454
|
+
* A verified install lifecycle event — what happened, to which install, and who
|
|
2455
|
+
* (if anyone) triggered it — as one flat, JSON-safe record.
|
|
2456
|
+
*
|
|
2457
|
+
* This is the shape `parseInstallWebhook` returns. It is flat by design: it
|
|
2458
|
+
* round-trips through a datastore unchanged, so the same shape the parser
|
|
2459
|
+
* returns is the shape a consumer persists and later rehydrates. There is
|
|
2460
|
+
* deliberately no nested identity object and no second representation.
|
|
2461
|
+
*
|
|
2462
|
+
* Fields sourced from the **verified JWT** (`installId`, `accountId`, `status`,
|
|
2463
|
+
* `rawStatus`, `displayVersion`, `user`) are trustworthy; the enrichment fields
|
|
2464
|
+
* (`accountName`, `platform`, `isTest`) come from the unauthenticated JSON body.
|
|
2465
|
+
*/
|
|
2466
|
+
interface InstallWebhook {
|
|
2467
|
+
/** Install ID — Peek-assigned UUID. Also the JWT subject (`sub`). */
|
|
2468
|
+
installId: string;
|
|
2469
|
+
/**
|
|
2470
|
+
* Stable Peek account id — the same value Peek calls the **partner ID**.
|
|
2471
|
+
* This event is its only source; it cannot be re-fetched later.
|
|
2472
|
+
*/
|
|
2473
|
+
accountId: string;
|
|
2474
|
+
/** Partner display name. `""` when the JSON body omits it. */
|
|
2475
|
+
accountName: string;
|
|
2476
|
+
/**
|
|
2477
|
+
* Which Peek platform owns the install, or `null` when the registry reported
|
|
2478
|
+
* a platform this version of the package does not know. `null` means "newer
|
|
2479
|
+
* than this package", **not** "absent" — do not coerce it to a default.
|
|
2480
|
+
*
|
|
2481
|
+
* **Persist and track this per install.** It is not cosmetic: the platform
|
|
2482
|
+
* determines which APIs the install is served by, and therefore which access
|
|
2483
|
+
* service a consumer must construct for it — `PeekAccessService` for `"peek"`,
|
|
2484
|
+
* `CngAccessService` for `"cng"`, `AcmeAccessService` for `"acme"`, each
|
|
2485
|
+
* routed at a different gateway. Two installs of the same app can sit on
|
|
2486
|
+
* different platforms, so this cannot be inferred from app-level config and
|
|
2487
|
+
* has no source other than this event.
|
|
2488
|
+
*/
|
|
2489
|
+
platform: PeekPlatform | null;
|
|
2490
|
+
/** Whether this is a test account. Defaults to `false` when not reported. */
|
|
2491
|
+
isTest: boolean;
|
|
2492
|
+
/**
|
|
2493
|
+
* The lifecycle status, or `null` when the registry sent a status this
|
|
2494
|
+
* version of the package does not know. Handle `null` explicitly — treating
|
|
2495
|
+
* it as a no-op silently drops a lifecycle transition.
|
|
2496
|
+
*/
|
|
2497
|
+
status: InstallStatus | null;
|
|
2498
|
+
/** The status exactly as delivered, for logging and for the `null` case. */
|
|
2499
|
+
rawStatus: string;
|
|
2500
|
+
/** App display version at the time of the event. */
|
|
2501
|
+
displayVersion: string;
|
|
2502
|
+
/**
|
|
2503
|
+
* The user that triggered the event, or `null` for system-initiated events
|
|
2504
|
+
* (install lifecycle changes are often system-initiated). From the JWT.
|
|
2505
|
+
*/
|
|
2506
|
+
user: PeekAuthTokenUser | null;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
/**
|
|
2510
|
+
* Verifies an install-status webhook token and merges it with the delivered
|
|
2511
|
+
* JSON body into one flat {@link InstallWebhook}.
|
|
2512
|
+
*
|
|
2513
|
+
* The **verified token** is the authoritative source of `installId`,
|
|
2514
|
+
* `accountId`, `status`, `displayVersion`, and `user`. The **JSON body** — which
|
|
2515
|
+
* is not signed and so is trusted only for fields the token cannot carry —
|
|
2516
|
+
* supplies `accountName`, `platform`, and `isTest`. A missing or malformed body
|
|
2517
|
+
* degrades those three to `""`/`null`/`false`; it never overrides an
|
|
2518
|
+
* authenticated field and never throws.
|
|
2519
|
+
*
|
|
2520
|
+
* Signature verification is strict: the HMAC signature (with `secret`), the
|
|
2521
|
+
* token expiry, the `"app_registry_v2"` issuer, and the `"Joken"` audience are
|
|
2522
|
+
* all checked — the same checks as {@link PeekAccessService.verifyPeekAuthToken}.
|
|
2523
|
+
* Any failure throws from the `jsonwebtoken` library, so callers can branch on
|
|
2524
|
+
* the error kind:
|
|
2525
|
+
*
|
|
2526
|
+
* - `JsonWebTokenError` — signature invalid, wrong issuer/audience, or malformed
|
|
2527
|
+
* - `TokenExpiredError` — past `exp`
|
|
2528
|
+
* - `NotBeforeError` — before `nbf`
|
|
2529
|
+
*
|
|
2530
|
+
* An unrecognised `status` or `platform` maps to `null` (with the wire status
|
|
2531
|
+
* preserved on {@link InstallWebhook.rawStatus}) rather than being coerced —
|
|
2532
|
+
* Peek treats any 2xx as delivered and does not redeliver, so handle a `null`
|
|
2533
|
+
* `status` explicitly:
|
|
2534
|
+
*
|
|
2535
|
+
* ```ts
|
|
2536
|
+
* const event = parseInstallWebhook(token, req.body, secret);
|
|
2537
|
+
* switch (event.status) {
|
|
2538
|
+
* case "installed": return provision(event);
|
|
2539
|
+
* case "uninstalled": return deprovision(event);
|
|
2540
|
+
* case "update_installed": return recordVersion(event);
|
|
2541
|
+
* default: throw new Error(`unknown install status: ${event.rawStatus}`);
|
|
2542
|
+
* }
|
|
2543
|
+
* ```
|
|
2544
|
+
*
|
|
2545
|
+
* @param token the raw JWT delivered by the install webhook (no `Bearer` prefix)
|
|
2546
|
+
* @param body the delivered JSON body (raw object or a JSON string)
|
|
2547
|
+
* @param secret the app's HMAC signing secret (the `jwtSecret` you construct
|
|
2548
|
+
* `PeekAccessService` with)
|
|
2549
|
+
* @throws {JsonWebTokenError} signature invalid or token malformed
|
|
2550
|
+
* @throws {TokenExpiredError} token has expired
|
|
2551
|
+
* @throws {NotBeforeError} token not yet valid
|
|
2552
|
+
*/
|
|
2553
|
+
declare function parseInstallWebhook(token: string, body: unknown, secret: string): InstallWebhook;
|
|
2420
2554
|
/**
|
|
2421
2555
|
* Verifies an install-status webhook token and returns its clean claims.
|
|
2422
2556
|
*
|
|
2557
|
+
* @deprecated Use {@link parseInstallWebhook} instead. This verifier reads only
|
|
2558
|
+
* the signed token, so it cannot report the account **name**, **platform**, or
|
|
2559
|
+
* **test** flag that the webhook's JSON body also carries — the caller was left
|
|
2560
|
+
* to parse that body separately and stitch the two shapes together.
|
|
2561
|
+
* `parseInstallWebhook(token, body, secret)` verifies the same token and returns
|
|
2562
|
+
* one flat {@link InstallWebhook} with every field. This function remains only
|
|
2563
|
+
* for backwards compatibility.
|
|
2564
|
+
*
|
|
2423
2565
|
* Validates the HMAC signature (with `secret`), the token expiry, the
|
|
2424
2566
|
* `"app_registry_v2"` issuer, and the `"Joken"` audience — the same checks as
|
|
2425
2567
|
* {@link PeekAccessService.verifyPeekAuthToken} — then maps the payload to
|
|
2426
2568
|
* {@link InstallWebhookClaims}, tolerating a `null` `user` (system events).
|
|
2427
|
-
* Throws from the `jsonwebtoken` library on any verification failure
|
|
2428
|
-
* can branch on the error kind:
|
|
2569
|
+
* Throws from the `jsonwebtoken` library on any verification failure:
|
|
2429
2570
|
*
|
|
2430
2571
|
* - `JsonWebTokenError` — signature invalid, wrong issuer/audience, or malformed
|
|
2431
2572
|
* - `TokenExpiredError` — past `exp`
|
|
@@ -2498,6 +2639,19 @@ declare class PeekHttpError extends Error {
|
|
|
2498
2639
|
readonly body: unknown;
|
|
2499
2640
|
constructor(statusCode: number, url: string, body: unknown, message?: string);
|
|
2500
2641
|
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Thrown when an `app_registry_v2` token passes signature/issuer/audience
|
|
2644
|
+
* verification but its `user` block is missing a required `id`. The signature is
|
|
2645
|
+
* authentic, so this signals a structurally malformed token from the app
|
|
2646
|
+
* registry rather than a forged one — distinct from the `jsonwebtoken`
|
|
2647
|
+
* `JsonWebTokenError` family. The offending field is preserved on
|
|
2648
|
+
* {@link InvalidPeekTokenError.field}.
|
|
2649
|
+
*/
|
|
2650
|
+
declare class InvalidPeekTokenError extends Error {
|
|
2651
|
+
/** The token field that was missing or empty (e.g. `"user.id"`). */
|
|
2652
|
+
readonly field: string;
|
|
2653
|
+
constructor(field: string);
|
|
2654
|
+
}
|
|
2501
2655
|
/**
|
|
2502
2656
|
* Thrown when a payment or booking-modification operation is called on an
|
|
2503
2657
|
* access service that was constructed without `fullCustomerAccess` (PII access
|
|
@@ -2538,4 +2692,4 @@ declare class AcmeApiError extends Error {
|
|
|
2538
2692
|
constructor(statusCode: number, body: unknown, message?: string);
|
|
2539
2693
|
}
|
|
2540
2694
|
|
|
2541
|
-
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, 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, parseWaiverWebhook, verifyInstallWebhook };
|
|
2695
|
+
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 InstallStatus, type InstallWebhook, 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, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
|
package/dist/index.d.ts
CHANGED
|
@@ -1791,13 +1791,18 @@ declare class PricingService {
|
|
|
1791
1791
|
private validateResourceOption;
|
|
1792
1792
|
}
|
|
1793
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"];
|
|
1794
1800
|
/**
|
|
1795
1801
|
* Platform that embedded the app for this session. Verification is
|
|
1796
1802
|
* brand-agnostic — one peek-auth JWT is minted for every platform — so this
|
|
1797
1803
|
* claim is the single thing that identifies which platform a request came from.
|
|
1798
|
-
* New platforms extend this union.
|
|
1799
1804
|
*/
|
|
1800
|
-
type PeekPlatform =
|
|
1805
|
+
type PeekPlatform = (typeof PEEK_PLATFORMS)[number];
|
|
1801
1806
|
/** User context embedded in a Peek auth token. */
|
|
1802
1807
|
interface PeekAuthTokenUser {
|
|
1803
1808
|
/** User's email address. */
|
|
@@ -1929,10 +1934,13 @@ declare class PeekAccessService {
|
|
|
1929
1934
|
* malformed
|
|
1930
1935
|
* - `TokenExpiredError` — past `exp`
|
|
1931
1936
|
* - `NotBeforeError` — before `nbf`
|
|
1937
|
+
* - `InvalidPeekTokenError` — signature valid but the `user` block is missing
|
|
1938
|
+
* its `id`
|
|
1932
1939
|
*
|
|
1933
1940
|
* @throws {JsonWebTokenError} signature invalid or token malformed
|
|
1934
1941
|
* @throws {TokenExpiredError} token has expired
|
|
1935
1942
|
* @throws {NotBeforeError} token not yet valid
|
|
1943
|
+
* @throws {InvalidPeekTokenError} verified token has no `user.id`
|
|
1936
1944
|
*/
|
|
1937
1945
|
verifyPeekAuthToken(token: string): PeekAuthTokenClaims;
|
|
1938
1946
|
/**
|
|
@@ -2417,15 +2425,148 @@ interface Waiver {
|
|
|
2417
2425
|
*/
|
|
2418
2426
|
declare function parseWaiverWebhook(payload: unknown, options?: AccessOptions): Waiver;
|
|
2419
2427
|
|
|
2428
|
+
/**
|
|
2429
|
+
* The clean data model for an app-store **install lifecycle event** — the single
|
|
2430
|
+
* flat object `parseInstallWebhook` returns for the webhook Peek's app registry
|
|
2431
|
+
* POSTs when an app is installed, uninstalled, or updated.
|
|
2432
|
+
*
|
|
2433
|
+
* That delivery carries two things at once: a **signed `app_registry_v2` JWT**
|
|
2434
|
+
* (the security boundary) and a plain **JSON body** (the enrichment channel).
|
|
2435
|
+
* `parseInstallWebhook` verifies the JWT and merges both into this shape, so a
|
|
2436
|
+
* consumer sees one clean, flat, JSON-safe record instead of two differently
|
|
2437
|
+
* shaped payloads. The verified JWT is authoritative for the fields it carries
|
|
2438
|
+
* (`installId`, `accountId`, `status`, `displayVersion`, `user`); the JSON body
|
|
2439
|
+
* is the only source of `accountName`, `platform`, and `isTest`.
|
|
2440
|
+
*
|
|
2441
|
+
* It is also the single origin of the account id in the whole system: no
|
|
2442
|
+
* GraphQL read returns it, so whatever a consumer persists from this event is
|
|
2443
|
+
* all it will ever have.
|
|
2444
|
+
*/
|
|
2445
|
+
|
|
2446
|
+
/**
|
|
2447
|
+
* Every install lifecycle status Peek's app registry sends today. Exported so
|
|
2448
|
+
* consumers can validate or enumerate without restating the vocabulary.
|
|
2449
|
+
*/
|
|
2450
|
+
declare const INSTALL_STATUSES: readonly ["installed", "uninstalled", "update_installed"];
|
|
2451
|
+
/** Install lifecycle status. */
|
|
2452
|
+
type InstallStatus = (typeof INSTALL_STATUSES)[number];
|
|
2453
|
+
/**
|
|
2454
|
+
* A verified install lifecycle event — what happened, to which install, and who
|
|
2455
|
+
* (if anyone) triggered it — as one flat, JSON-safe record.
|
|
2456
|
+
*
|
|
2457
|
+
* This is the shape `parseInstallWebhook` returns. It is flat by design: it
|
|
2458
|
+
* round-trips through a datastore unchanged, so the same shape the parser
|
|
2459
|
+
* returns is the shape a consumer persists and later rehydrates. There is
|
|
2460
|
+
* deliberately no nested identity object and no second representation.
|
|
2461
|
+
*
|
|
2462
|
+
* Fields sourced from the **verified JWT** (`installId`, `accountId`, `status`,
|
|
2463
|
+
* `rawStatus`, `displayVersion`, `user`) are trustworthy; the enrichment fields
|
|
2464
|
+
* (`accountName`, `platform`, `isTest`) come from the unauthenticated JSON body.
|
|
2465
|
+
*/
|
|
2466
|
+
interface InstallWebhook {
|
|
2467
|
+
/** Install ID — Peek-assigned UUID. Also the JWT subject (`sub`). */
|
|
2468
|
+
installId: string;
|
|
2469
|
+
/**
|
|
2470
|
+
* Stable Peek account id — the same value Peek calls the **partner ID**.
|
|
2471
|
+
* This event is its only source; it cannot be re-fetched later.
|
|
2472
|
+
*/
|
|
2473
|
+
accountId: string;
|
|
2474
|
+
/** Partner display name. `""` when the JSON body omits it. */
|
|
2475
|
+
accountName: string;
|
|
2476
|
+
/**
|
|
2477
|
+
* Which Peek platform owns the install, or `null` when the registry reported
|
|
2478
|
+
* a platform this version of the package does not know. `null` means "newer
|
|
2479
|
+
* than this package", **not** "absent" — do not coerce it to a default.
|
|
2480
|
+
*
|
|
2481
|
+
* **Persist and track this per install.** It is not cosmetic: the platform
|
|
2482
|
+
* determines which APIs the install is served by, and therefore which access
|
|
2483
|
+
* service a consumer must construct for it — `PeekAccessService` for `"peek"`,
|
|
2484
|
+
* `CngAccessService` for `"cng"`, `AcmeAccessService` for `"acme"`, each
|
|
2485
|
+
* routed at a different gateway. Two installs of the same app can sit on
|
|
2486
|
+
* different platforms, so this cannot be inferred from app-level config and
|
|
2487
|
+
* has no source other than this event.
|
|
2488
|
+
*/
|
|
2489
|
+
platform: PeekPlatform | null;
|
|
2490
|
+
/** Whether this is a test account. Defaults to `false` when not reported. */
|
|
2491
|
+
isTest: boolean;
|
|
2492
|
+
/**
|
|
2493
|
+
* The lifecycle status, or `null` when the registry sent a status this
|
|
2494
|
+
* version of the package does not know. Handle `null` explicitly — treating
|
|
2495
|
+
* it as a no-op silently drops a lifecycle transition.
|
|
2496
|
+
*/
|
|
2497
|
+
status: InstallStatus | null;
|
|
2498
|
+
/** The status exactly as delivered, for logging and for the `null` case. */
|
|
2499
|
+
rawStatus: string;
|
|
2500
|
+
/** App display version at the time of the event. */
|
|
2501
|
+
displayVersion: string;
|
|
2502
|
+
/**
|
|
2503
|
+
* The user that triggered the event, or `null` for system-initiated events
|
|
2504
|
+
* (install lifecycle changes are often system-initiated). From the JWT.
|
|
2505
|
+
*/
|
|
2506
|
+
user: PeekAuthTokenUser | null;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
/**
|
|
2510
|
+
* Verifies an install-status webhook token and merges it with the delivered
|
|
2511
|
+
* JSON body into one flat {@link InstallWebhook}.
|
|
2512
|
+
*
|
|
2513
|
+
* The **verified token** is the authoritative source of `installId`,
|
|
2514
|
+
* `accountId`, `status`, `displayVersion`, and `user`. The **JSON body** — which
|
|
2515
|
+
* is not signed and so is trusted only for fields the token cannot carry —
|
|
2516
|
+
* supplies `accountName`, `platform`, and `isTest`. A missing or malformed body
|
|
2517
|
+
* degrades those three to `""`/`null`/`false`; it never overrides an
|
|
2518
|
+
* authenticated field and never throws.
|
|
2519
|
+
*
|
|
2520
|
+
* Signature verification is strict: the HMAC signature (with `secret`), the
|
|
2521
|
+
* token expiry, the `"app_registry_v2"` issuer, and the `"Joken"` audience are
|
|
2522
|
+
* all checked — the same checks as {@link PeekAccessService.verifyPeekAuthToken}.
|
|
2523
|
+
* Any failure throws from the `jsonwebtoken` library, so callers can branch on
|
|
2524
|
+
* the error kind:
|
|
2525
|
+
*
|
|
2526
|
+
* - `JsonWebTokenError` — signature invalid, wrong issuer/audience, or malformed
|
|
2527
|
+
* - `TokenExpiredError` — past `exp`
|
|
2528
|
+
* - `NotBeforeError` — before `nbf`
|
|
2529
|
+
*
|
|
2530
|
+
* An unrecognised `status` or `platform` maps to `null` (with the wire status
|
|
2531
|
+
* preserved on {@link InstallWebhook.rawStatus}) rather than being coerced —
|
|
2532
|
+
* Peek treats any 2xx as delivered and does not redeliver, so handle a `null`
|
|
2533
|
+
* `status` explicitly:
|
|
2534
|
+
*
|
|
2535
|
+
* ```ts
|
|
2536
|
+
* const event = parseInstallWebhook(token, req.body, secret);
|
|
2537
|
+
* switch (event.status) {
|
|
2538
|
+
* case "installed": return provision(event);
|
|
2539
|
+
* case "uninstalled": return deprovision(event);
|
|
2540
|
+
* case "update_installed": return recordVersion(event);
|
|
2541
|
+
* default: throw new Error(`unknown install status: ${event.rawStatus}`);
|
|
2542
|
+
* }
|
|
2543
|
+
* ```
|
|
2544
|
+
*
|
|
2545
|
+
* @param token the raw JWT delivered by the install webhook (no `Bearer` prefix)
|
|
2546
|
+
* @param body the delivered JSON body (raw object or a JSON string)
|
|
2547
|
+
* @param secret the app's HMAC signing secret (the `jwtSecret` you construct
|
|
2548
|
+
* `PeekAccessService` with)
|
|
2549
|
+
* @throws {JsonWebTokenError} signature invalid or token malformed
|
|
2550
|
+
* @throws {TokenExpiredError} token has expired
|
|
2551
|
+
* @throws {NotBeforeError} token not yet valid
|
|
2552
|
+
*/
|
|
2553
|
+
declare function parseInstallWebhook(token: string, body: unknown, secret: string): InstallWebhook;
|
|
2420
2554
|
/**
|
|
2421
2555
|
* Verifies an install-status webhook token and returns its clean claims.
|
|
2422
2556
|
*
|
|
2557
|
+
* @deprecated Use {@link parseInstallWebhook} instead. This verifier reads only
|
|
2558
|
+
* the signed token, so it cannot report the account **name**, **platform**, or
|
|
2559
|
+
* **test** flag that the webhook's JSON body also carries — the caller was left
|
|
2560
|
+
* to parse that body separately and stitch the two shapes together.
|
|
2561
|
+
* `parseInstallWebhook(token, body, secret)` verifies the same token and returns
|
|
2562
|
+
* one flat {@link InstallWebhook} with every field. This function remains only
|
|
2563
|
+
* for backwards compatibility.
|
|
2564
|
+
*
|
|
2423
2565
|
* Validates the HMAC signature (with `secret`), the token expiry, the
|
|
2424
2566
|
* `"app_registry_v2"` issuer, and the `"Joken"` audience — the same checks as
|
|
2425
2567
|
* {@link PeekAccessService.verifyPeekAuthToken} — then maps the payload to
|
|
2426
2568
|
* {@link InstallWebhookClaims}, tolerating a `null` `user` (system events).
|
|
2427
|
-
* Throws from the `jsonwebtoken` library on any verification failure
|
|
2428
|
-
* can branch on the error kind:
|
|
2569
|
+
* Throws from the `jsonwebtoken` library on any verification failure:
|
|
2429
2570
|
*
|
|
2430
2571
|
* - `JsonWebTokenError` — signature invalid, wrong issuer/audience, or malformed
|
|
2431
2572
|
* - `TokenExpiredError` — past `exp`
|
|
@@ -2498,6 +2639,19 @@ declare class PeekHttpError extends Error {
|
|
|
2498
2639
|
readonly body: unknown;
|
|
2499
2640
|
constructor(statusCode: number, url: string, body: unknown, message?: string);
|
|
2500
2641
|
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Thrown when an `app_registry_v2` token passes signature/issuer/audience
|
|
2644
|
+
* verification but its `user` block is missing a required `id`. The signature is
|
|
2645
|
+
* authentic, so this signals a structurally malformed token from the app
|
|
2646
|
+
* registry rather than a forged one — distinct from the `jsonwebtoken`
|
|
2647
|
+
* `JsonWebTokenError` family. The offending field is preserved on
|
|
2648
|
+
* {@link InvalidPeekTokenError.field}.
|
|
2649
|
+
*/
|
|
2650
|
+
declare class InvalidPeekTokenError extends Error {
|
|
2651
|
+
/** The token field that was missing or empty (e.g. `"user.id"`). */
|
|
2652
|
+
readonly field: string;
|
|
2653
|
+
constructor(field: string);
|
|
2654
|
+
}
|
|
2501
2655
|
/**
|
|
2502
2656
|
* Thrown when a payment or booking-modification operation is called on an
|
|
2503
2657
|
* access service that was constructed without `fullCustomerAccess` (PII access
|
|
@@ -2538,4 +2692,4 @@ declare class AcmeApiError extends Error {
|
|
|
2538
2692
|
constructor(statusCode: number, body: unknown, message?: string);
|
|
2539
2693
|
}
|
|
2540
2694
|
|
|
2541
|
-
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, 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, parseWaiverWebhook, verifyInstallWebhook };
|
|
2695
|
+
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 InstallStatus, type InstallWebhook, 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, parseInstallWebhook, 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,
|
|
@@ -3560,10 +3572,13 @@ var PeekAccessService = class {
|
|
|
3560
3572
|
* malformed
|
|
3561
3573
|
* - `TokenExpiredError` — past `exp`
|
|
3562
3574
|
* - `NotBeforeError` — before `nbf`
|
|
3575
|
+
* - `InvalidPeekTokenError` — signature valid but the `user` block is missing
|
|
3576
|
+
* its `id`
|
|
3563
3577
|
*
|
|
3564
3578
|
* @throws {JsonWebTokenError} signature invalid or token malformed
|
|
3565
3579
|
* @throws {TokenExpiredError} token has expired
|
|
3566
3580
|
* @throws {NotBeforeError} token not yet valid
|
|
3581
|
+
* @throws {InvalidPeekTokenError} verified token has no `user.id`
|
|
3567
3582
|
*/
|
|
3568
3583
|
verifyPeekAuthToken(token) {
|
|
3569
3584
|
const payload = verifyPeekJwt(token, this.jwtSecret);
|
|
@@ -4234,7 +4249,53 @@ function extractWaiverNode(payload) {
|
|
|
4234
4249
|
return record;
|
|
4235
4250
|
}
|
|
4236
4251
|
|
|
4252
|
+
// src/models/peek/auth-token.ts
|
|
4253
|
+
var PEEK_PLATFORMS = ["peek", "cng", "acme"];
|
|
4254
|
+
|
|
4255
|
+
// src/models/peek/install.ts
|
|
4256
|
+
var INSTALL_STATUSES = [
|
|
4257
|
+
"installed",
|
|
4258
|
+
"uninstalled",
|
|
4259
|
+
"update_installed"
|
|
4260
|
+
];
|
|
4261
|
+
|
|
4262
|
+
// src/internal/peek/installs/install-converter.ts
|
|
4263
|
+
function extractInstallWebhookBody(payload) {
|
|
4264
|
+
let body = payload;
|
|
4265
|
+
if (typeof body === "string") {
|
|
4266
|
+
try {
|
|
4267
|
+
body = JSON.parse(body);
|
|
4268
|
+
} catch {
|
|
4269
|
+
return {};
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
if (!body || typeof body !== "object") return {};
|
|
4273
|
+
return body;
|
|
4274
|
+
}
|
|
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
|
+
|
|
4237
4282
|
// src/internal/peek/installs/install-webhook.ts
|
|
4283
|
+
function parseInstallWebhook(token, body, secret) {
|
|
4284
|
+
const payload = verifyPeekJwt(token, secret);
|
|
4285
|
+
const account = extractInstallWebhookBody(body).account ?? {};
|
|
4286
|
+
const rawStatus = payload.status ?? "";
|
|
4287
|
+
return {
|
|
4288
|
+
installId: payload.sub ?? "",
|
|
4289
|
+
accountId: payload.account?.id ?? "",
|
|
4290
|
+
accountName: account.name || "",
|
|
4291
|
+
platform: toPeekPlatform(account.platform || ""),
|
|
4292
|
+
isTest: Boolean(account.is_test),
|
|
4293
|
+
status: toInstallStatus(rawStatus),
|
|
4294
|
+
rawStatus,
|
|
4295
|
+
displayVersion: payload.display_version ?? "",
|
|
4296
|
+
user: payload.user ? mapPeekTokenUser(payload.user) : null
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
4238
4299
|
function verifyInstallWebhook(token, secret) {
|
|
4239
4300
|
const payload = verifyPeekJwt(token, secret);
|
|
4240
4301
|
return {
|
|
@@ -4246,6 +4307,6 @@ function verifyInstallWebhook(token, secret) {
|
|
|
4246
4307
|
};
|
|
4247
4308
|
}
|
|
4248
4309
|
|
|
4249
|
-
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 };
|
|
4310
|
+
export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AcmeAccessService, AcmeApiError, AcmeProductService, AdminAccountRequiredError, AvailabilityService, BookingService, CngAccessService, CngApiError, CngProductService, DailyNoteService, INSTALL_STATUSES, InvalidPeekTokenError, MembershipService, PeekAccessService, PeekGraphQLError, PeekHttpError, PiiAccessDisabledError, PricingService, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, noopLogger, parseBookingWebhook, parseInstallWebhook, parseWaiverWebhook, verifyInstallWebhook };
|
|
4250
4311
|
//# sourceMappingURL=index.js.map
|
|
4251
4312
|
//# sourceMappingURL=index.js.map
|