@peektravel/app-utilities 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,5 @@
1
- import * as jwt from 'jsonwebtoken';
2
1
  import { randomUUID } from 'crypto';
3
-
4
- // src/peek-access-service.ts
2
+ import * as jwt from 'jsonwebtoken';
5
3
 
6
4
  // src/internal/peek/gateway-endpoints.ts
7
5
  var SALES_ENDPOINT = "sales";
@@ -212,6 +210,21 @@ var PeekGraphQLError = class extends Error {
212
210
  this.graphqlErrors = graphqlErrors;
213
211
  }
214
212
  };
213
+ var PeekHttpError = class extends Error {
214
+ /** The HTTP status that triggered this error. */
215
+ statusCode;
216
+ /** The request URL that produced the failing response. */
217
+ url;
218
+ /** The raw response body (parsed JSON when possible, otherwise text). */
219
+ body;
220
+ constructor(statusCode, url, body, message) {
221
+ super(message ?? `Peek request failed with HTTP ${statusCode}`);
222
+ this.name = "PeekHttpError";
223
+ this.statusCode = statusCode;
224
+ this.url = url;
225
+ this.body = body;
226
+ }
227
+ };
215
228
  var PiiAccessDisabledError = class extends Error {
216
229
  /** The name of the operation that was blocked (e.g. `"makePayment"`). */
217
230
  operation;
@@ -1922,6 +1935,14 @@ function createTokenManager(config) {
1922
1935
  var RATE_LIMIT_STATUS = 429;
1923
1936
  var ADMIN_ACCOUNT_REQUIRED_STATUS = 418;
1924
1937
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1938
+ async function parseBody(response) {
1939
+ const text = await response.text();
1940
+ try {
1941
+ return JSON.parse(text);
1942
+ } catch {
1943
+ return text;
1944
+ }
1945
+ }
1925
1946
  async function requestWithRetry(http, url, init, label, onResponse) {
1926
1947
  const { retryDelaysMs, logger, fetchFn } = http;
1927
1948
  for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
@@ -1948,6 +1969,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
1948
1969
  }
1949
1970
 
1950
1971
  // src/internal/peek/graphql-client.ts
1972
+ var isObject = (value) => typeof value === "object" && value !== null;
1951
1973
  var GraphQLClient = class {
1952
1974
  constructor(options) {
1953
1975
  this.options = options;
@@ -1973,7 +1995,8 @@ var GraphQLClient = class {
1973
1995
  },
1974
1996
  endpointName,
1975
1997
  async (response) => {
1976
- const body = await response.json();
1998
+ const raw = await parseBody(response);
1999
+ const body = isObject(raw) ? raw : {};
1977
2000
  if (body.errors) {
1978
2001
  logger.error(`GraphQL errors for ${endpointName}`, {
1979
2002
  url,
@@ -1983,7 +2006,7 @@ var GraphQLClient = class {
1983
2006
  }
1984
2007
  if (!response.ok) {
1985
2008
  logger.error(`GraphQL request failed with HTTP ${response.status}`, { url });
1986
- throw new Error(`GraphQL request failed with HTTP ${response.status}`);
2009
+ throw new PeekHttpError(response.status, url, raw);
1987
2010
  }
1988
2011
  return body;
1989
2012
  }
@@ -2005,6 +2028,24 @@ var GraphQLClient = class {
2005
2028
  return headers;
2006
2029
  }
2007
2030
  };
2031
+ var PEEK_TOKEN_ISSUER = "app_registry_v2";
2032
+ var PEEK_TOKEN_AUDIENCE = "Joken";
2033
+ function verifyPeekJwt(token, secret) {
2034
+ return jwt.verify(token, secret, {
2035
+ issuer: PEEK_TOKEN_ISSUER,
2036
+ audience: PEEK_TOKEN_AUDIENCE
2037
+ });
2038
+ }
2039
+ function mapPeekTokenUser(u) {
2040
+ return {
2041
+ email: u.email,
2042
+ id: u.id,
2043
+ isAdmin: u.is_admin,
2044
+ locale: u.locale,
2045
+ name: u.name,
2046
+ platform: u.platform
2047
+ };
2048
+ }
2008
2049
 
2009
2050
  // src/internal/peek/memberships/membership-converter.ts
2010
2051
  function fromMembershipsResponse(response) {
@@ -2473,10 +2514,17 @@ function fromTimeslotNode(node, productId) {
2473
2514
  durationMin: node.minuteLength ?? 0,
2474
2515
  date: node.date || "",
2475
2516
  startTime: node.start ?? null,
2476
- assignedResources: mapAssignedResources(node.inheritedResourceAllocations)
2517
+ assignedResources: mapAssignedResources(selectResourceAllocations(node))
2477
2518
  };
2478
2519
  }
2479
2520
  var ACTIVE_ALLOCATION_STATUS = "ACTIVE";
2521
+ function selectResourceAllocations(node) {
2522
+ const inherited = node.inheritedResourceAllocations;
2523
+ if (Array.isArray(inherited) && inherited.length > 0) {
2524
+ return inherited;
2525
+ }
2526
+ return node.resourceAllocations;
2527
+ }
2480
2528
  function mapAssignedResources(allocations) {
2481
2529
  if (!Array.isArray(allocations) || allocations.length === 0) {
2482
2530
  return [];
@@ -2563,9 +2611,24 @@ var TIMESLOTS_QUERY = `
2563
2611
  status
2564
2612
  date
2565
2613
  inheritedResourceAllocations {
2614
+ id
2615
+ status
2616
+ quantity
2617
+ resourcePool {
2618
+ id
2619
+ name
2620
+ category
2621
+ capacity
2622
+ accountUser {
2623
+ id
2624
+ }
2625
+ }
2626
+ }
2627
+ resourceAllocations {
2566
2628
  quantity
2567
2629
  status
2568
2630
  resourcePool {
2631
+ id
2569
2632
  name
2570
2633
  category
2571
2634
  capacity
@@ -2601,9 +2664,24 @@ var TIMESLOT_BY_ID_QUERY = `
2601
2664
  status
2602
2665
  date
2603
2666
  inheritedResourceAllocations {
2667
+ id
2668
+ status
2669
+ quantity
2670
+ resourcePool {
2671
+ id
2672
+ name
2673
+ category
2674
+ capacity
2675
+ accountUser {
2676
+ id
2677
+ }
2678
+ }
2679
+ }
2680
+ resourceAllocations {
2604
2681
  quantity
2605
2682
  status
2606
2683
  resourcePool {
2684
+ id
2607
2685
  name
2608
2686
  category
2609
2687
  capacity
@@ -2766,6 +2844,14 @@ function normalizeDate(date) {
2766
2844
  return date.split("T")[0] ?? date;
2767
2845
  }
2768
2846
 
2847
+ // src/internal/peek/money.ts
2848
+ function toPricingMoney(raw) {
2849
+ if (!raw) return null;
2850
+ const money = { amount: raw.amount, currency: raw.currency };
2851
+ if (raw.formatted != null) money.displayPrice = raw.formatted;
2852
+ return money;
2853
+ }
2854
+
2769
2855
  // src/internal/peek/products/product-converter.ts
2770
2856
  var ADD_ON_COLOR = "#FFFFFF";
2771
2857
  function fromActivities(activities) {
@@ -2781,7 +2867,9 @@ function fromActivity(activity) {
2781
2867
  currency: activity.currency || "",
2782
2868
  tickets: (activity.resourceOptions ?? []).map((option) => ({
2783
2869
  id: option.id,
2784
- name: option.name
2870
+ name: option.name,
2871
+ minPrice: toPricingMoney(option.priceRange?.min),
2872
+ maxPrice: toPricingMoney(option.priceRange?.max)
2785
2873
  }))
2786
2874
  };
2787
2875
  }
@@ -2802,7 +2890,12 @@ function fromItemOptionNodes(nodes) {
2802
2890
  };
2803
2891
  grouped.set(itemId, product);
2804
2892
  }
2805
- product.tickets.push({ id: node.id, name: node.name });
2893
+ product.tickets.push({
2894
+ id: node.id,
2895
+ name: node.name,
2896
+ minPrice: null,
2897
+ maxPrice: null
2898
+ });
2806
2899
  }
2807
2900
  return Array.from(grouped.values());
2808
2901
  }
@@ -2821,6 +2914,18 @@ var PRODUCTS_QUERY = `
2821
2914
  resourceOptions {
2822
2915
  id
2823
2916
  name
2917
+ priceRange {
2918
+ min {
2919
+ currency
2920
+ amount
2921
+ formatted
2922
+ }
2923
+ max {
2924
+ currency
2925
+ amount
2926
+ formatted
2927
+ }
2928
+ }
2824
2929
  }
2825
2930
  }
2826
2931
  }
@@ -3124,6 +3229,7 @@ var UPSERT_PRICING_OVERRIDES_MUTATION = `
3124
3229
  price {
3125
3230
  amount
3126
3231
  currency
3232
+ formatted
3127
3233
  }
3128
3234
  }
3129
3235
  ... on PricingOverridesActivityContextResourceOptionPercentageAdjustmentOverride {
@@ -3172,7 +3278,11 @@ function buildUpsertInput(input) {
3172
3278
  overrides: activity.overrides.map((override) => ({
3173
3279
  order: override.order,
3174
3280
  resourceOptions: override.resourceOptions.map(
3175
- (option) => option.mode === "fixed" ? { id: option.id, price: option.price } : { id: option.id, percentageAdjustment: option.percentageAdjustment }
3281
+ (option) => option.mode === "fixed" ? {
3282
+ id: option.id,
3283
+ // Send only the wire fields — `displayPrice` is read-only.
3284
+ price: { amount: option.price.amount, currency: option.price.currency }
3285
+ } : { id: option.id, percentageAdjustment: option.percentageAdjustment }
3176
3286
  ),
3177
3287
  filters: override.filters
3178
3288
  }))
@@ -3219,7 +3329,7 @@ function fromResourceOption(option) {
3219
3329
  };
3220
3330
  }
3221
3331
  function fromResolvedOverride(override) {
3222
- return "price" in override ? { mode: "fixed", price: override.price } : { mode: "percentage", percentageAdjustment: override.percentageAdjustment };
3332
+ return "price" in override ? { mode: "fixed", price: toPricingMoney(override.price) } : { mode: "percentage", percentageAdjustment: override.percentageAdjustment };
3223
3333
  }
3224
3334
  function fromFilter(filter) {
3225
3335
  if (filter.__typename === SPOTS_TAKEN_FILTER_TYPENAME) {
@@ -3376,7 +3486,6 @@ var noopLogger = {
3376
3486
  // src/peek-access-service.ts
3377
3487
  var DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
3378
3488
  var DEFAULT_V2_BASE_URL = "https://app-registry.peeklabs.com/installations-api";
3379
- var PEEK_TOKEN_ISSUER = "app_registry_v2";
3380
3489
  var PeekAccessService = class {
3381
3490
  client;
3382
3491
  productServiceOptions;
@@ -3439,21 +3548,11 @@ var PeekAccessService = class {
3439
3548
  * @throws {NotBeforeError} token not yet valid
3440
3549
  */
3441
3550
  verifyPeekAuthToken(token) {
3442
- const payload = jwt.verify(token, this.jwtSecret, {
3443
- issuer: PEEK_TOKEN_ISSUER
3444
- });
3445
- const { user: u } = payload;
3551
+ const payload = verifyPeekJwt(token, this.jwtSecret);
3446
3552
  return {
3447
3553
  installId: payload.sub,
3448
3554
  displayVersion: payload.display_version,
3449
- user: {
3450
- email: u.email,
3451
- id: u.id,
3452
- isAdmin: u.is_admin,
3453
- locale: u.locale,
3454
- name: u.name,
3455
- platform: u.platform
3456
- }
3555
+ user: mapPeekTokenUser(payload.user)
3457
3556
  };
3458
3557
  }
3459
3558
  /**
@@ -3837,7 +3936,7 @@ var RestClient = class {
3837
3936
  { method: "GET", headers: this.buildHeaders() },
3838
3937
  path,
3839
3938
  async (response) => {
3840
- const body = await this.parseBody(response);
3939
+ const body = await parseBody(response);
3841
3940
  if (!response.ok) {
3842
3941
  logger.error(`CNG request failed with HTTP ${response.status}`, { url });
3843
3942
  throw new CngApiError(response.status, body);
@@ -3846,15 +3945,6 @@ var RestClient = class {
3846
3945
  }
3847
3946
  );
3848
3947
  }
3849
- /** Reads the response body as JSON, falling back to raw text when unparseable. */
3850
- async parseBody(response) {
3851
- const text = await response.text();
3852
- try {
3853
- return JSON.parse(text);
3854
- } catch {
3855
- return text;
3856
- }
3857
- }
3858
3948
  endpoint(path) {
3859
3949
  const { baseUrl, appId, extendableSlug } = this.options;
3860
3950
  return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
@@ -3977,7 +4067,7 @@ var RestClient2 = class {
3977
4067
  { method: "GET", headers: this.buildHeaders() },
3978
4068
  path,
3979
4069
  async (response) => {
3980
- const body = await this.parseBody(response);
4070
+ const body = await parseBody(response);
3981
4071
  if (!response.ok) {
3982
4072
  logger.error(`ACME request failed with HTTP ${response.status}`, { url });
3983
4073
  throw new AcmeApiError(response.status, body);
@@ -3986,15 +4076,6 @@ var RestClient2 = class {
3986
4076
  }
3987
4077
  );
3988
4078
  }
3989
- /** Reads the response body as JSON, falling back to raw text when unparseable. */
3990
- async parseBody(response) {
3991
- const text = await response.text();
3992
- try {
3993
- return JSON.parse(text);
3994
- } catch {
3995
- return text;
3996
- }
3997
- }
3998
4079
  endpoint(path) {
3999
4080
  const { baseUrl, appId, extendableSlug } = this.options;
4000
4081
  return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
@@ -4135,6 +4216,18 @@ function extractWaiverNode(payload) {
4135
4216
  return record;
4136
4217
  }
4137
4218
 
4138
- export { ACTIVITY_PRODUCT_TYPE, ADD_ON_PRODUCT_TYPE, AccountUserService, AcmeAccessService, AcmeApiError, AcmeProductService, AdminAccountRequiredError, AvailabilityService, BookingService, CngAccessService, CngApiError, CngProductService, DailyNoteService, MembershipService, PeekAccessService, PeekGraphQLError, PiiAccessDisabledError, PricingService, ProductService, PromoCodeService, RENTAL_PRODUCT_TYPE, RateLimitError, ResellerService, ResourcePoolService, ReviewService, TimeslotService, noopLogger, parseBookingWebhook, parseWaiverWebhook };
4219
+ // src/internal/peek/installs/install-webhook.ts
4220
+ function verifyInstallWebhook(token, secret) {
4221
+ const payload = verifyPeekJwt(token, secret);
4222
+ return {
4223
+ installId: payload.sub ?? "",
4224
+ account: { id: payload.account?.id ?? "" },
4225
+ status: payload.status ?? "",
4226
+ displayVersion: payload.display_version ?? "",
4227
+ user: payload.user ? mapPeekTokenUser(payload.user) : null
4228
+ };
4229
+ }
4230
+
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 };
4139
4232
  //# sourceMappingURL=index.js.map
4140
4233
  //# sourceMappingURL=index.js.map