@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/README.md +69 -14
- package/dist/index.cjs +138 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +332 -236
- package/dist/index.d.ts +332 -236
- package/dist/index.js +137 -44
- package/dist/index.js.map +1 -1
- package/dist/ui/index.cjs +35 -1
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +35 -1
- package/dist/ui/index.js.map +1 -1
- package/docs/ui.md +26 -2
- package/docs/webhooks.md +80 -9
- package/llms.txt +86 -18
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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({
|
|
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" ? {
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|