@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/README.md
CHANGED
|
@@ -109,8 +109,14 @@ Two kinds of failures surface as exceptions:
|
|
|
109
109
|
rights). Carries `.statusCode === 418`.
|
|
110
110
|
- `RateLimitError` — HTTP 429 after the configured `retryDelaysMs` backoff was
|
|
111
111
|
exhausted. Carries `.statusCode === 429`.
|
|
112
|
-
- `PeekGraphQLError` — the response contained a GraphQL `errors` array
|
|
113
|
-
on `.graphqlErrors`.
|
|
112
|
+
- `PeekGraphQLError` — the response contained a GraphQL `errors` array (a
|
|
113
|
+
resolver-level failure), preserved on `.graphqlErrors`.
|
|
114
|
+
- `PeekHttpError` — the gateway returned a non-2xx HTTP status (other than
|
|
115
|
+
418/429) with no GraphQL `errors` array — a transport-level failure such as
|
|
116
|
+
`401` (auth/secret wrong), `404` (wrong app id / not provisioned), or `5xx`.
|
|
117
|
+
Carries `.statusCode`, `.url`, and the raw `.body` (parsed JSON when possible,
|
|
118
|
+
otherwise the response text). Surfaced *before* the body is parsed as JSON, so
|
|
119
|
+
a non-JSON error page reports its real status instead of a JSON parse error.
|
|
114
120
|
- `PiiAccessDisabledError` — a payment / booking-modification operation was
|
|
115
121
|
called on an access service created without `fullCustomerAccess` (see [Access options
|
|
116
122
|
/ PII](#access-options--pii)). Carries `.operation` (the blocked method name).
|
|
@@ -127,6 +133,7 @@ import {
|
|
|
127
133
|
RateLimitError,
|
|
128
134
|
AdminAccountRequiredError,
|
|
129
135
|
PeekGraphQLError,
|
|
136
|
+
PeekHttpError,
|
|
130
137
|
} from '@peektravel/app-utilities';
|
|
131
138
|
|
|
132
139
|
try {
|
|
@@ -136,6 +143,8 @@ try {
|
|
|
136
143
|
// back off and retry later
|
|
137
144
|
} else if (err instanceof AdminAccountRequiredError) {
|
|
138
145
|
// this install can't perform admin-only operations
|
|
146
|
+
} else if (err instanceof PeekHttpError) {
|
|
147
|
+
console.error(err.statusCode, err.url, err.body); // which config is wrong
|
|
139
148
|
} else if (err instanceof PeekGraphQLError) {
|
|
140
149
|
console.error(err.graphqlErrors); // raw gateway errors
|
|
141
150
|
} else {
|
|
@@ -229,16 +238,20 @@ Functions runtime) resolve correctly. Its only runtime dependency is
|
|
|
229
238
|
|
|
230
239
|
## Webhooks
|
|
231
240
|
|
|
232
|
-
Receiver apps can consume Peek **booking** and **
|
|
233
|
-
hand-writing
|
|
234
|
-
auth/network) that returns a
|
|
241
|
+
Receiver apps can consume Peek **booking**, **waiver**, and **install-status**
|
|
242
|
+
webhooks without hand-writing the payload handling. The booking and waiver
|
|
243
|
+
webhooks have a pure parser (construct nothing — no auth/network) that returns a
|
|
244
|
+
clean model; the install-status webhook delivers a signed token, so its helper
|
|
245
|
+
**verifies** it:
|
|
235
246
|
|
|
236
247
|
```ts
|
|
237
248
|
import {
|
|
238
249
|
parseBookingWebhook,
|
|
239
250
|
parseWaiverWebhook,
|
|
251
|
+
verifyInstallWebhook,
|
|
240
252
|
type Booking,
|
|
241
253
|
type Waiver,
|
|
254
|
+
type InstallWebhookClaims,
|
|
242
255
|
} from "@peektravel/app-utilities";
|
|
243
256
|
|
|
244
257
|
app.post("/booking-webhook", (req, res) => {
|
|
@@ -251,17 +264,38 @@ app.post("/waiver-webhook", (req, res) => {
|
|
|
251
264
|
const waiver: Waiver = parseWaiverWebhook(req.body, { fullCustomerAccess: true });
|
|
252
265
|
res.sendStatus(200);
|
|
253
266
|
});
|
|
267
|
+
|
|
268
|
+
app.post("/install-webhook", (req, res) => {
|
|
269
|
+
try {
|
|
270
|
+
// The payload IS a signed JWT — verify signature/issuer/audience/expiry:
|
|
271
|
+
const claims: InstallWebhookClaims = verifyInstallWebhook(req.body, process.env.PEEK_INTERNAL_SECRET!);
|
|
272
|
+
if (claims.status === "uninstalled") {
|
|
273
|
+
/* tear down this install */
|
|
274
|
+
}
|
|
275
|
+
res.sendStatus(200);
|
|
276
|
+
} catch {
|
|
277
|
+
res.sendStatus(401); // bad signature / issuer / audience / expired
|
|
278
|
+
}
|
|
279
|
+
});
|
|
254
280
|
```
|
|
255
281
|
|
|
256
|
-
|
|
257
|
-
on malformed input
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
282
|
+
The booking and waiver parsers tolerate the delivery envelope / a bare node / a
|
|
283
|
+
JSON string and never throw on malformed input; **authenticating those deliveries
|
|
284
|
+
is the receiver's job**. `parseWaiverWebhook` also takes an optional
|
|
285
|
+
`AccessOptions` (`{ fullCustomerAccess }`) and redacts the participant `guestName`
|
|
286
|
+
+ document `fileUrl` by default — see [Access options / PII](#access-options--pii)
|
|
287
|
+
below. `verifyInstallWebhook(token, secret)` is different: the payload is a signed
|
|
288
|
+
`app_registry_v2` JWT, so it validates the HMAC signature, expiry, issuer, and
|
|
289
|
+
`"Joken"` audience (the same checks as `verifyPeekAuthToken`) and returns typed
|
|
290
|
+
claims (`installId`, `account.id`, `status`, `displayVersion`, and a **nullable**
|
|
291
|
+
`user` for system-initiated events), throwing the underlying `jsonwebtoken` error
|
|
292
|
+
on any failure. The booking and waiver webhooks differ on registration: a
|
|
293
|
+
**booking** webhook's payload shape is set by a GraphQL query configured **once in
|
|
294
|
+
an external system** (the App Store `broadcast_to_url` config) — this package
|
|
295
|
+
documents and drift-guards the exact query to paste there — whereas the **waiver**
|
|
296
|
+
and **install-status** webhooks have fixed payloads, so you just subscribe to
|
|
297
|
+
their event with no query. **The query to register and the full guide:
|
|
298
|
+
[`docs/webhooks.md`](docs/webhooks.md) (shipped).**
|
|
265
299
|
|
|
266
300
|
## UI components (`/ui`)
|
|
267
301
|
|
|
@@ -299,6 +333,27 @@ attribute. Exported classes/types and helpers (`iconSvg`, `registerIcon`,
|
|
|
299
333
|
`portal`, `position`, `toast`) are available from `@peektravel/app-utilities/ui`
|
|
300
334
|
for subclassing or typing.
|
|
301
335
|
|
|
336
|
+
### Using the components from React 19
|
|
337
|
+
|
|
338
|
+
React 19 sets JSX props on a custom element as DOM **properties**
|
|
339
|
+
(`el.searchable = true`), not attributes. That's handled for you: every
|
|
340
|
+
reflected/read-only accessor accepts assignment, so `<ody-dropdown-single
|
|
341
|
+
searchable options={items} />` and friends work without the
|
|
342
|
+
`Cannot set property … which has only a getter` crash that a getter-only
|
|
343
|
+
property would otherwise cause. Two things worth knowing:
|
|
344
|
+
|
|
345
|
+
- **Boolean and data props reflect to the attribute**, so they take effect as
|
|
346
|
+
expected. Pass booleans as real booleans (`searchable={true}`) and rich data
|
|
347
|
+
as values (`options={items}`) — the object is reflected as a JSON attribute.
|
|
348
|
+
- **Read-only state accessors are inert to assignment.** Props that mirror live
|
|
349
|
+
internal state (`isOpen`, `isVisible`) ignore writes — drive them through the
|
|
350
|
+
imperative methods on a `ref` (`ref.current.openPopover()`, `.show()`) and read
|
|
351
|
+
them back through the component's `CustomEvent`s, not by setting the prop.
|
|
352
|
+
|
|
353
|
+
TypeScript still types these accessors as read-only, so a direct
|
|
354
|
+
`el.isOpen = true` in TS is flagged — the setters are a runtime safety net for
|
|
355
|
+
framework-driven assignment, not an invitation to write to derived state.
|
|
356
|
+
|
|
302
357
|
**Try the gallery:** `npm run sample` builds the package and serves
|
|
303
358
|
`examples/ui-gallery.html`, which shows every component with its variants.
|
|
304
359
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var jwt = require('jsonwebtoken');
|
|
4
3
|
var crypto = require('crypto');
|
|
4
|
+
var jwt = require('jsonwebtoken');
|
|
5
5
|
|
|
6
6
|
function _interopNamespace(e) {
|
|
7
7
|
if (e && e.__esModule) return e;
|
|
@@ -23,8 +23,6 @@ function _interopNamespace(e) {
|
|
|
23
23
|
|
|
24
24
|
var jwt__namespace = /*#__PURE__*/_interopNamespace(jwt);
|
|
25
25
|
|
|
26
|
-
// src/peek-access-service.ts
|
|
27
|
-
|
|
28
26
|
// src/internal/peek/gateway-endpoints.ts
|
|
29
27
|
var SALES_ENDPOINT = "sales";
|
|
30
28
|
var V2_EXTENDABLE_SLUG = "peek_backoffice_api-v1";
|
|
@@ -234,6 +232,21 @@ var PeekGraphQLError = class extends Error {
|
|
|
234
232
|
this.graphqlErrors = graphqlErrors;
|
|
235
233
|
}
|
|
236
234
|
};
|
|
235
|
+
var PeekHttpError = class extends Error {
|
|
236
|
+
/** The HTTP status that triggered this error. */
|
|
237
|
+
statusCode;
|
|
238
|
+
/** The request URL that produced the failing response. */
|
|
239
|
+
url;
|
|
240
|
+
/** The raw response body (parsed JSON when possible, otherwise text). */
|
|
241
|
+
body;
|
|
242
|
+
constructor(statusCode, url, body, message) {
|
|
243
|
+
super(message ?? `Peek request failed with HTTP ${statusCode}`);
|
|
244
|
+
this.name = "PeekHttpError";
|
|
245
|
+
this.statusCode = statusCode;
|
|
246
|
+
this.url = url;
|
|
247
|
+
this.body = body;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
237
250
|
var PiiAccessDisabledError = class extends Error {
|
|
238
251
|
/** The name of the operation that was blocked (e.g. `"makePayment"`). */
|
|
239
252
|
operation;
|
|
@@ -1944,6 +1957,14 @@ function createTokenManager(config) {
|
|
|
1944
1957
|
var RATE_LIMIT_STATUS = 429;
|
|
1945
1958
|
var ADMIN_ACCOUNT_REQUIRED_STATUS = 418;
|
|
1946
1959
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1960
|
+
async function parseBody(response) {
|
|
1961
|
+
const text = await response.text();
|
|
1962
|
+
try {
|
|
1963
|
+
return JSON.parse(text);
|
|
1964
|
+
} catch {
|
|
1965
|
+
return text;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1947
1968
|
async function requestWithRetry(http, url, init, label, onResponse) {
|
|
1948
1969
|
const { retryDelaysMs, logger, fetchFn } = http;
|
|
1949
1970
|
for (let attempt = 0; attempt <= retryDelaysMs.length; attempt++) {
|
|
@@ -1970,6 +1991,7 @@ async function requestWithRetry(http, url, init, label, onResponse) {
|
|
|
1970
1991
|
}
|
|
1971
1992
|
|
|
1972
1993
|
// src/internal/peek/graphql-client.ts
|
|
1994
|
+
var isObject = (value) => typeof value === "object" && value !== null;
|
|
1973
1995
|
var GraphQLClient = class {
|
|
1974
1996
|
constructor(options) {
|
|
1975
1997
|
this.options = options;
|
|
@@ -1995,7 +2017,8 @@ var GraphQLClient = class {
|
|
|
1995
2017
|
},
|
|
1996
2018
|
endpointName,
|
|
1997
2019
|
async (response) => {
|
|
1998
|
-
const
|
|
2020
|
+
const raw = await parseBody(response);
|
|
2021
|
+
const body = isObject(raw) ? raw : {};
|
|
1999
2022
|
if (body.errors) {
|
|
2000
2023
|
logger.error(`GraphQL errors for ${endpointName}`, {
|
|
2001
2024
|
url,
|
|
@@ -2005,7 +2028,7 @@ var GraphQLClient = class {
|
|
|
2005
2028
|
}
|
|
2006
2029
|
if (!response.ok) {
|
|
2007
2030
|
logger.error(`GraphQL request failed with HTTP ${response.status}`, { url });
|
|
2008
|
-
throw new
|
|
2031
|
+
throw new PeekHttpError(response.status, url, raw);
|
|
2009
2032
|
}
|
|
2010
2033
|
return body;
|
|
2011
2034
|
}
|
|
@@ -2027,6 +2050,24 @@ var GraphQLClient = class {
|
|
|
2027
2050
|
return headers;
|
|
2028
2051
|
}
|
|
2029
2052
|
};
|
|
2053
|
+
var PEEK_TOKEN_ISSUER = "app_registry_v2";
|
|
2054
|
+
var PEEK_TOKEN_AUDIENCE = "Joken";
|
|
2055
|
+
function verifyPeekJwt(token, secret) {
|
|
2056
|
+
return jwt__namespace.verify(token, secret, {
|
|
2057
|
+
issuer: PEEK_TOKEN_ISSUER,
|
|
2058
|
+
audience: PEEK_TOKEN_AUDIENCE
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
function mapPeekTokenUser(u) {
|
|
2062
|
+
return {
|
|
2063
|
+
email: u.email,
|
|
2064
|
+
id: u.id,
|
|
2065
|
+
isAdmin: u.is_admin,
|
|
2066
|
+
locale: u.locale,
|
|
2067
|
+
name: u.name,
|
|
2068
|
+
platform: u.platform
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2030
2071
|
|
|
2031
2072
|
// src/internal/peek/memberships/membership-converter.ts
|
|
2032
2073
|
function fromMembershipsResponse(response) {
|
|
@@ -2495,10 +2536,17 @@ function fromTimeslotNode(node, productId) {
|
|
|
2495
2536
|
durationMin: node.minuteLength ?? 0,
|
|
2496
2537
|
date: node.date || "",
|
|
2497
2538
|
startTime: node.start ?? null,
|
|
2498
|
-
assignedResources: mapAssignedResources(node
|
|
2539
|
+
assignedResources: mapAssignedResources(selectResourceAllocations(node))
|
|
2499
2540
|
};
|
|
2500
2541
|
}
|
|
2501
2542
|
var ACTIVE_ALLOCATION_STATUS = "ACTIVE";
|
|
2543
|
+
function selectResourceAllocations(node) {
|
|
2544
|
+
const inherited = node.inheritedResourceAllocations;
|
|
2545
|
+
if (Array.isArray(inherited) && inherited.length > 0) {
|
|
2546
|
+
return inherited;
|
|
2547
|
+
}
|
|
2548
|
+
return node.resourceAllocations;
|
|
2549
|
+
}
|
|
2502
2550
|
function mapAssignedResources(allocations) {
|
|
2503
2551
|
if (!Array.isArray(allocations) || allocations.length === 0) {
|
|
2504
2552
|
return [];
|
|
@@ -2585,9 +2633,24 @@ var TIMESLOTS_QUERY = `
|
|
|
2585
2633
|
status
|
|
2586
2634
|
date
|
|
2587
2635
|
inheritedResourceAllocations {
|
|
2636
|
+
id
|
|
2637
|
+
status
|
|
2638
|
+
quantity
|
|
2639
|
+
resourcePool {
|
|
2640
|
+
id
|
|
2641
|
+
name
|
|
2642
|
+
category
|
|
2643
|
+
capacity
|
|
2644
|
+
accountUser {
|
|
2645
|
+
id
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
resourceAllocations {
|
|
2588
2650
|
quantity
|
|
2589
2651
|
status
|
|
2590
2652
|
resourcePool {
|
|
2653
|
+
id
|
|
2591
2654
|
name
|
|
2592
2655
|
category
|
|
2593
2656
|
capacity
|
|
@@ -2623,9 +2686,24 @@ var TIMESLOT_BY_ID_QUERY = `
|
|
|
2623
2686
|
status
|
|
2624
2687
|
date
|
|
2625
2688
|
inheritedResourceAllocations {
|
|
2689
|
+
id
|
|
2690
|
+
status
|
|
2691
|
+
quantity
|
|
2692
|
+
resourcePool {
|
|
2693
|
+
id
|
|
2694
|
+
name
|
|
2695
|
+
category
|
|
2696
|
+
capacity
|
|
2697
|
+
accountUser {
|
|
2698
|
+
id
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
resourceAllocations {
|
|
2626
2703
|
quantity
|
|
2627
2704
|
status
|
|
2628
2705
|
resourcePool {
|
|
2706
|
+
id
|
|
2629
2707
|
name
|
|
2630
2708
|
category
|
|
2631
2709
|
capacity
|
|
@@ -2788,6 +2866,14 @@ function normalizeDate(date) {
|
|
|
2788
2866
|
return date.split("T")[0] ?? date;
|
|
2789
2867
|
}
|
|
2790
2868
|
|
|
2869
|
+
// src/internal/peek/money.ts
|
|
2870
|
+
function toPricingMoney(raw) {
|
|
2871
|
+
if (!raw) return null;
|
|
2872
|
+
const money = { amount: raw.amount, currency: raw.currency };
|
|
2873
|
+
if (raw.formatted != null) money.displayPrice = raw.formatted;
|
|
2874
|
+
return money;
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2791
2877
|
// src/internal/peek/products/product-converter.ts
|
|
2792
2878
|
var ADD_ON_COLOR = "#FFFFFF";
|
|
2793
2879
|
function fromActivities(activities) {
|
|
@@ -2803,7 +2889,9 @@ function fromActivity(activity) {
|
|
|
2803
2889
|
currency: activity.currency || "",
|
|
2804
2890
|
tickets: (activity.resourceOptions ?? []).map((option) => ({
|
|
2805
2891
|
id: option.id,
|
|
2806
|
-
name: option.name
|
|
2892
|
+
name: option.name,
|
|
2893
|
+
minPrice: toPricingMoney(option.priceRange?.min),
|
|
2894
|
+
maxPrice: toPricingMoney(option.priceRange?.max)
|
|
2807
2895
|
}))
|
|
2808
2896
|
};
|
|
2809
2897
|
}
|
|
@@ -2824,7 +2912,12 @@ function fromItemOptionNodes(nodes) {
|
|
|
2824
2912
|
};
|
|
2825
2913
|
grouped.set(itemId, product);
|
|
2826
2914
|
}
|
|
2827
|
-
product.tickets.push({
|
|
2915
|
+
product.tickets.push({
|
|
2916
|
+
id: node.id,
|
|
2917
|
+
name: node.name,
|
|
2918
|
+
minPrice: null,
|
|
2919
|
+
maxPrice: null
|
|
2920
|
+
});
|
|
2828
2921
|
}
|
|
2829
2922
|
return Array.from(grouped.values());
|
|
2830
2923
|
}
|
|
@@ -2843,6 +2936,18 @@ var PRODUCTS_QUERY = `
|
|
|
2843
2936
|
resourceOptions {
|
|
2844
2937
|
id
|
|
2845
2938
|
name
|
|
2939
|
+
priceRange {
|
|
2940
|
+
min {
|
|
2941
|
+
currency
|
|
2942
|
+
amount
|
|
2943
|
+
formatted
|
|
2944
|
+
}
|
|
2945
|
+
max {
|
|
2946
|
+
currency
|
|
2947
|
+
amount
|
|
2948
|
+
formatted
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2846
2951
|
}
|
|
2847
2952
|
}
|
|
2848
2953
|
}
|
|
@@ -3146,6 +3251,7 @@ var UPSERT_PRICING_OVERRIDES_MUTATION = `
|
|
|
3146
3251
|
price {
|
|
3147
3252
|
amount
|
|
3148
3253
|
currency
|
|
3254
|
+
formatted
|
|
3149
3255
|
}
|
|
3150
3256
|
}
|
|
3151
3257
|
... on PricingOverridesActivityContextResourceOptionPercentageAdjustmentOverride {
|
|
@@ -3194,7 +3300,11 @@ function buildUpsertInput(input) {
|
|
|
3194
3300
|
overrides: activity.overrides.map((override) => ({
|
|
3195
3301
|
order: override.order,
|
|
3196
3302
|
resourceOptions: override.resourceOptions.map(
|
|
3197
|
-
(option) => option.mode === "fixed" ? {
|
|
3303
|
+
(option) => option.mode === "fixed" ? {
|
|
3304
|
+
id: option.id,
|
|
3305
|
+
// Send only the wire fields — `displayPrice` is read-only.
|
|
3306
|
+
price: { amount: option.price.amount, currency: option.price.currency }
|
|
3307
|
+
} : { id: option.id, percentageAdjustment: option.percentageAdjustment }
|
|
3198
3308
|
),
|
|
3199
3309
|
filters: override.filters
|
|
3200
3310
|
}))
|
|
@@ -3241,7 +3351,7 @@ function fromResourceOption(option) {
|
|
|
3241
3351
|
};
|
|
3242
3352
|
}
|
|
3243
3353
|
function fromResolvedOverride(override) {
|
|
3244
|
-
return "price" in override ? { mode: "fixed", price: override.price } : { mode: "percentage", percentageAdjustment: override.percentageAdjustment };
|
|
3354
|
+
return "price" in override ? { mode: "fixed", price: toPricingMoney(override.price) } : { mode: "percentage", percentageAdjustment: override.percentageAdjustment };
|
|
3245
3355
|
}
|
|
3246
3356
|
function fromFilter(filter) {
|
|
3247
3357
|
if (filter.__typename === SPOTS_TAKEN_FILTER_TYPENAME) {
|
|
@@ -3398,7 +3508,6 @@ var noopLogger = {
|
|
|
3398
3508
|
// src/peek-access-service.ts
|
|
3399
3509
|
var DEFAULT_BASE_URL = "https://apps.peekapis.com/backoffice-gql";
|
|
3400
3510
|
var DEFAULT_V2_BASE_URL = "https://app-registry.peeklabs.com/installations-api";
|
|
3401
|
-
var PEEK_TOKEN_ISSUER = "app_registry_v2";
|
|
3402
3511
|
var PeekAccessService = class {
|
|
3403
3512
|
client;
|
|
3404
3513
|
productServiceOptions;
|
|
@@ -3461,21 +3570,11 @@ var PeekAccessService = class {
|
|
|
3461
3570
|
* @throws {NotBeforeError} token not yet valid
|
|
3462
3571
|
*/
|
|
3463
3572
|
verifyPeekAuthToken(token) {
|
|
3464
|
-
const payload =
|
|
3465
|
-
issuer: PEEK_TOKEN_ISSUER
|
|
3466
|
-
});
|
|
3467
|
-
const { user: u } = payload;
|
|
3573
|
+
const payload = verifyPeekJwt(token, this.jwtSecret);
|
|
3468
3574
|
return {
|
|
3469
3575
|
installId: payload.sub,
|
|
3470
3576
|
displayVersion: payload.display_version,
|
|
3471
|
-
user:
|
|
3472
|
-
email: u.email,
|
|
3473
|
-
id: u.id,
|
|
3474
|
-
isAdmin: u.is_admin,
|
|
3475
|
-
locale: u.locale,
|
|
3476
|
-
name: u.name,
|
|
3477
|
-
platform: u.platform
|
|
3478
|
-
}
|
|
3577
|
+
user: mapPeekTokenUser(payload.user)
|
|
3479
3578
|
};
|
|
3480
3579
|
}
|
|
3481
3580
|
/**
|
|
@@ -3859,7 +3958,7 @@ var RestClient = class {
|
|
|
3859
3958
|
{ method: "GET", headers: this.buildHeaders() },
|
|
3860
3959
|
path,
|
|
3861
3960
|
async (response) => {
|
|
3862
|
-
const body = await
|
|
3961
|
+
const body = await parseBody(response);
|
|
3863
3962
|
if (!response.ok) {
|
|
3864
3963
|
logger.error(`CNG request failed with HTTP ${response.status}`, { url });
|
|
3865
3964
|
throw new CngApiError(response.status, body);
|
|
@@ -3868,15 +3967,6 @@ var RestClient = class {
|
|
|
3868
3967
|
}
|
|
3869
3968
|
);
|
|
3870
3969
|
}
|
|
3871
|
-
/** Reads the response body as JSON, falling back to raw text when unparseable. */
|
|
3872
|
-
async parseBody(response) {
|
|
3873
|
-
const text = await response.text();
|
|
3874
|
-
try {
|
|
3875
|
-
return JSON.parse(text);
|
|
3876
|
-
} catch {
|
|
3877
|
-
return text;
|
|
3878
|
-
}
|
|
3879
|
-
}
|
|
3880
3970
|
endpoint(path) {
|
|
3881
3971
|
const { baseUrl, appId, extendableSlug } = this.options;
|
|
3882
3972
|
return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
|
|
@@ -3999,7 +4089,7 @@ var RestClient2 = class {
|
|
|
3999
4089
|
{ method: "GET", headers: this.buildHeaders() },
|
|
4000
4090
|
path,
|
|
4001
4091
|
async (response) => {
|
|
4002
|
-
const body = await
|
|
4092
|
+
const body = await parseBody(response);
|
|
4003
4093
|
if (!response.ok) {
|
|
4004
4094
|
logger.error(`ACME request failed with HTTP ${response.status}`, { url });
|
|
4005
4095
|
throw new AcmeApiError(response.status, body);
|
|
@@ -4008,15 +4098,6 @@ var RestClient2 = class {
|
|
|
4008
4098
|
}
|
|
4009
4099
|
);
|
|
4010
4100
|
}
|
|
4011
|
-
/** Reads the response body as JSON, falling back to raw text when unparseable. */
|
|
4012
|
-
async parseBody(response) {
|
|
4013
|
-
const text = await response.text();
|
|
4014
|
-
try {
|
|
4015
|
-
return JSON.parse(text);
|
|
4016
|
-
} catch {
|
|
4017
|
-
return text;
|
|
4018
|
-
}
|
|
4019
|
-
}
|
|
4020
4101
|
endpoint(path) {
|
|
4021
4102
|
const { baseUrl, appId, extendableSlug } = this.options;
|
|
4022
4103
|
return `${baseUrl}/${appId}/${extendableSlug}/${path}`;
|
|
@@ -4157,6 +4238,18 @@ function extractWaiverNode(payload) {
|
|
|
4157
4238
|
return record;
|
|
4158
4239
|
}
|
|
4159
4240
|
|
|
4241
|
+
// src/internal/peek/installs/install-webhook.ts
|
|
4242
|
+
function verifyInstallWebhook(token, secret) {
|
|
4243
|
+
const payload = verifyPeekJwt(token, secret);
|
|
4244
|
+
return {
|
|
4245
|
+
installId: payload.sub ?? "",
|
|
4246
|
+
account: { id: payload.account?.id ?? "" },
|
|
4247
|
+
status: payload.status ?? "",
|
|
4248
|
+
displayVersion: payload.display_version ?? "",
|
|
4249
|
+
user: payload.user ? mapPeekTokenUser(payload.user) : null
|
|
4250
|
+
};
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4160
4253
|
exports.ACTIVITY_PRODUCT_TYPE = ACTIVITY_PRODUCT_TYPE;
|
|
4161
4254
|
exports.ADD_ON_PRODUCT_TYPE = ADD_ON_PRODUCT_TYPE;
|
|
4162
4255
|
exports.AccountUserService = AccountUserService;
|
|
@@ -4173,6 +4266,7 @@ exports.DailyNoteService = DailyNoteService;
|
|
|
4173
4266
|
exports.MembershipService = MembershipService;
|
|
4174
4267
|
exports.PeekAccessService = PeekAccessService;
|
|
4175
4268
|
exports.PeekGraphQLError = PeekGraphQLError;
|
|
4269
|
+
exports.PeekHttpError = PeekHttpError;
|
|
4176
4270
|
exports.PiiAccessDisabledError = PiiAccessDisabledError;
|
|
4177
4271
|
exports.PricingService = PricingService;
|
|
4178
4272
|
exports.ProductService = ProductService;
|
|
@@ -4186,5 +4280,6 @@ exports.TimeslotService = TimeslotService;
|
|
|
4186
4280
|
exports.noopLogger = noopLogger;
|
|
4187
4281
|
exports.parseBookingWebhook = parseBookingWebhook;
|
|
4188
4282
|
exports.parseWaiverWebhook = parseWaiverWebhook;
|
|
4283
|
+
exports.verifyInstallWebhook = verifyInstallWebhook;
|
|
4189
4284
|
//# sourceMappingURL=index.cjs.map
|
|
4190
4285
|
//# sourceMappingURL=index.cjs.map
|