@playaos/api-client 0.12.0 → 0.14.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 +19 -0
- package/dist/applications-schema.d.ts +80 -79
- package/dist/applications-schema.js +83 -76
- package/dist/applications-schema.js.map +1 -1
- package/dist/index.d.ts +1091 -629
- package/dist/index.js +514 -510
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,544 +1,548 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/error.ts
|
|
2
2
|
var ApiClientError = class extends Error {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
retryAfter;
|
|
6
|
+
constructor(status, code, message, retryAfter) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.retryAfter = retryAfter;
|
|
11
|
+
this.name = "ApiClientError";
|
|
12
|
+
}
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Parse the `Retry-After` header into integer seconds. Only the delta-seconds
|
|
16
|
+
* form is honored; the HTTP-date form is ignored (returns undefined) since
|
|
17
|
+
* callers expect a seconds hint.
|
|
18
|
+
*/
|
|
14
19
|
function parseRetryAfter(res) {
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
const header = res.headers?.get?.("retry-after")?.trim();
|
|
21
|
+
return header && /^\d+$/.test(header) ? parseInt(header, 10) : void 0;
|
|
17
22
|
}
|
|
18
23
|
async function parseError(res) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const retryAfter = parseRetryAfter(res);
|
|
25
|
+
try {
|
|
26
|
+
const body = await res.json();
|
|
27
|
+
return new ApiClientError(res.status, body.code ?? "UNKNOWN", body.error ?? res.statusText, retryAfter);
|
|
28
|
+
} catch {
|
|
29
|
+
return new ApiClientError(res.status, "UNKNOWN", res.statusText, retryAfter);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/client.ts
|
|
34
|
+
function v1Headers(auth) {
|
|
35
|
+
const headers = { Authorization: `Bearer ${auth.apiKey}` };
|
|
36
|
+
if (auth.memberToken) headers["X-PlayaOS-Member-Token"] = auth.memberToken;
|
|
37
|
+
return headers;
|
|
26
38
|
}
|
|
27
|
-
|
|
28
|
-
// src/client.ts
|
|
29
39
|
function buildUrl(base, path, params) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
return url.toString();
|
|
40
|
+
const url = new URL(`${base}/api/v1${path}`);
|
|
41
|
+
if (params) {
|
|
42
|
+
for (const [k, v] of Object.entries(params)) if (v !== void 0 && v !== false) url.searchParams.set(k, String(v));
|
|
43
|
+
}
|
|
44
|
+
return url.toString();
|
|
37
45
|
}
|
|
38
46
|
function buildEmbedUrl(base, path) {
|
|
39
|
-
|
|
47
|
+
return `${base}/api/embed/v1${path}`;
|
|
40
48
|
}
|
|
41
49
|
function withCampKey(url, campKey) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const embedUrl = new URL(url);
|
|
51
|
+
embedUrl.searchParams.set("camp_key", campKey);
|
|
52
|
+
return embedUrl.toString();
|
|
45
53
|
}
|
|
46
54
|
async function getEmbed(url, campKey, signal) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
const res = await fetch(withCampKey(url, campKey), {
|
|
56
|
+
headers: { "X-Camp-Key": campKey },
|
|
57
|
+
signal
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) throw await parseError(res);
|
|
60
|
+
return res.json();
|
|
53
61
|
}
|
|
54
62
|
async function getEmbedAuthed(url, campKey, opts) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
const headers = { "X-Camp-Key": campKey };
|
|
64
|
+
if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
|
|
65
|
+
const res = await fetch(withCampKey(url, campKey), {
|
|
66
|
+
headers,
|
|
67
|
+
signal: opts?.signal
|
|
68
|
+
});
|
|
69
|
+
if (!res.ok) throw await parseError(res);
|
|
70
|
+
return res.json();
|
|
60
71
|
}
|
|
61
|
-
async function request(url,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
async function request(url, auth, signal) {
|
|
73
|
+
const res = await fetch(url, {
|
|
74
|
+
headers: v1Headers(auth),
|
|
75
|
+
signal
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) throw await parseError(res);
|
|
78
|
+
return res.json();
|
|
68
79
|
}
|
|
69
|
-
async function post(url,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
async function post(url, auth, body, signal) {
|
|
81
|
+
const res = await fetch(url, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
...v1Headers(auth),
|
|
85
|
+
"Content-Type": "application/json"
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
signal
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok) throw await parseError(res);
|
|
91
|
+
return res.json();
|
|
92
|
+
}
|
|
93
|
+
/** v1 write helpers. `request`/`post` predate these; same auth, other verbs. */
|
|
94
|
+
async function sendV1(method, url, auth, body, signal) {
|
|
95
|
+
const headers = v1Headers(auth);
|
|
96
|
+
const init = {
|
|
97
|
+
method,
|
|
98
|
+
headers,
|
|
99
|
+
signal
|
|
100
|
+
};
|
|
101
|
+
if (body !== void 0) {
|
|
102
|
+
headers["Content-Type"] = "application/json";
|
|
103
|
+
init.body = JSON.stringify(body);
|
|
104
|
+
}
|
|
105
|
+
const res = await fetch(url, init);
|
|
106
|
+
if (!res.ok) throw await parseError(res);
|
|
107
|
+
return res.json();
|
|
78
108
|
}
|
|
79
109
|
async function postEmbed(url, campKey, body, opts) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
110
|
+
const headers = {
|
|
111
|
+
"X-Camp-Key": campKey,
|
|
112
|
+
"Content-Type": "application/json"
|
|
113
|
+
};
|
|
114
|
+
if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
|
|
115
|
+
const res = await fetch(withCampKey(url, campKey), {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers,
|
|
118
|
+
body: JSON.stringify(body),
|
|
119
|
+
signal: opts?.signal
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) throw await parseError(res);
|
|
122
|
+
return res.json();
|
|
93
123
|
}
|
|
94
124
|
async function putEmbed(url, campKey, body, opts) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
const headers = {
|
|
126
|
+
"X-Camp-Key": campKey,
|
|
127
|
+
"Content-Type": "application/json"
|
|
128
|
+
};
|
|
129
|
+
if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
|
|
130
|
+
const res = await fetch(withCampKey(url, campKey), {
|
|
131
|
+
method: "PUT",
|
|
132
|
+
headers,
|
|
133
|
+
body: JSON.stringify(body),
|
|
134
|
+
signal: opts?.signal
|
|
135
|
+
});
|
|
136
|
+
if (!res.ok) throw await parseError(res);
|
|
137
|
+
return res.json();
|
|
108
138
|
}
|
|
109
139
|
async function deleteEmbed(url, campKey, opts) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
140
|
+
const headers = { "X-Camp-Key": campKey };
|
|
141
|
+
if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
|
|
142
|
+
const res = await fetch(withCampKey(url, campKey), {
|
|
143
|
+
method: "DELETE",
|
|
144
|
+
headers,
|
|
145
|
+
signal: opts?.signal
|
|
146
|
+
});
|
|
147
|
+
if (!res.ok) throw await parseError(res);
|
|
148
|
+
return res.json();
|
|
115
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Bearer-only helper for /api/embed/v1/admin/* (PLA-489). The admin token is
|
|
152
|
+
* the sole credential (no X-Camp-Key — orgId comes from the JWT claims
|
|
153
|
+
* server-side). `method` can be GET, POST, PATCH, or PUT; `body` is optional
|
|
154
|
+
* (omitted for GET reads like getAnnotations).
|
|
155
|
+
*/
|
|
116
156
|
async function adminEmbed(method, url, accessToken, body, signal) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
157
|
+
const headers = { Authorization: `Bearer ${accessToken}` };
|
|
158
|
+
const init = {
|
|
159
|
+
method,
|
|
160
|
+
headers,
|
|
161
|
+
signal
|
|
162
|
+
};
|
|
163
|
+
if (body !== void 0) {
|
|
164
|
+
headers["Content-Type"] = "application/json";
|
|
165
|
+
init.body = JSON.stringify(body);
|
|
166
|
+
}
|
|
167
|
+
const res = await fetch(url, init);
|
|
168
|
+
if (!res.ok) throw await parseError(res);
|
|
169
|
+
return res.json();
|
|
126
170
|
}
|
|
127
|
-
var PlayaOSClient = class {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
/** Book an interview slot (PLA-714). Public. */
|
|
417
|
-
book: (token, payload, opts) => {
|
|
418
|
-
const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/book`;
|
|
419
|
-
return fetch(url, {
|
|
420
|
-
method: "POST",
|
|
421
|
-
headers: { "Content-Type": "application/json" },
|
|
422
|
-
body: JSON.stringify(payload),
|
|
423
|
-
signal: opts?.signal
|
|
424
|
-
}).then(async (res) => {
|
|
425
|
-
if (!res.ok) throw await parseError(res);
|
|
426
|
-
return res.json();
|
|
427
|
-
});
|
|
428
|
-
},
|
|
429
|
-
/** Cancel an interview booking (PLA-714). Public. */
|
|
430
|
-
cancel: (token, opts) => {
|
|
431
|
-
const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/cancel`;
|
|
432
|
-
return fetch(url, { method: "POST", signal: opts?.signal }).then(async (res) => {
|
|
433
|
-
if (!res.ok) throw await parseError(res);
|
|
434
|
-
return res.json();
|
|
435
|
-
});
|
|
436
|
-
},
|
|
437
|
-
/** Reschedule an interview booking (PLA-714). Public. */
|
|
438
|
-
reschedule: (token, payload, opts) => {
|
|
439
|
-
const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/reschedule`;
|
|
440
|
-
return fetch(url, {
|
|
441
|
-
method: "POST",
|
|
442
|
-
headers: { "Content-Type": "application/json" },
|
|
443
|
-
body: JSON.stringify(payload),
|
|
444
|
-
signal: opts?.signal
|
|
445
|
-
}).then(async (res) => {
|
|
446
|
-
if (!res.ok) throw await parseError(res);
|
|
447
|
-
return res.json();
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
};
|
|
171
|
+
var PlayaOSClient = class PlayaOSClient {
|
|
172
|
+
baseUrl;
|
|
173
|
+
embedBaseUrl;
|
|
174
|
+
apiKey;
|
|
175
|
+
memberToken;
|
|
176
|
+
constructor(opts) {
|
|
177
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
178
|
+
this.embedBaseUrl = (opts.embedBaseUrl ?? opts.baseUrl).replace(/\/+$/, "");
|
|
179
|
+
this.apiKey = opts.apiKey;
|
|
180
|
+
this.memberToken = opts.memberToken;
|
|
181
|
+
}
|
|
182
|
+
/** Credentials for /api/v1 calls; the embed surface uses `apiKey` alone as the camp key. */
|
|
183
|
+
get auth() {
|
|
184
|
+
return {
|
|
185
|
+
apiKey: this.apiKey,
|
|
186
|
+
memberToken: this.memberToken
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Derive a client that acts as one member (PLA-1195): same host and org key,
|
|
191
|
+
* plus the member's Supabase session JWT on every /api/v1 call. Use this in a
|
|
192
|
+
* server that holds one org key and serves many signed-in members.
|
|
193
|
+
*/
|
|
194
|
+
withMember(memberToken) {
|
|
195
|
+
return new PlayaOSClient({
|
|
196
|
+
baseUrl: this.baseUrl,
|
|
197
|
+
embedBaseUrl: this.embedBaseUrl,
|
|
198
|
+
apiKey: this.apiKey,
|
|
199
|
+
memberToken
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
members = {
|
|
203
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/members", params), this.auth, opts?.signal),
|
|
204
|
+
/**
|
|
205
|
+
* Full profile (phone, emergency contact). Requires a member token; pass
|
|
206
|
+
* `"me"` for the acting member — non-admins may fetch only themselves.
|
|
207
|
+
*/
|
|
208
|
+
get: (id, opts) => request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.auth, opts?.signal),
|
|
209
|
+
/** Member-self profile update (PLA-700). Requires camp key + member Bearer token. */
|
|
210
|
+
selfUpdate: (payload, opts) => putEmbed(buildEmbedUrl(this.embedBaseUrl, "/members/self"), this.apiKey, payload, opts),
|
|
211
|
+
/** Member-self shift signup + cancel (PLA-705). Requires camp key + member Bearer. */
|
|
212
|
+
shifts: {
|
|
213
|
+
signup: (payload, opts) => postEmbed(buildEmbedUrl(this.embedBaseUrl, "/shifts/signup"), this.apiKey, payload, opts),
|
|
214
|
+
cancel: (signupId, opts) => deleteEmbed(buildEmbedUrl(this.embedBaseUrl, `/shifts/signups/${encodeURIComponent(signupId)}`), this.apiKey, opts)
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
applications = {
|
|
218
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/applications", params), this.auth, opts?.signal),
|
|
219
|
+
create: (payload, opts) => postEmbed(buildEmbedUrl(this.embedBaseUrl, "/applications"), this.apiKey, payload, opts)
|
|
220
|
+
};
|
|
221
|
+
waivers = {
|
|
222
|
+
/** Get the active waiver for an org for the current year. */
|
|
223
|
+
active: (orgId, opts) => getEmbed(buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/active`), this.apiKey, opts?.signal),
|
|
224
|
+
/** Sign a waiver. Requires X-Camp-Key; optional accessToken for Bearer path. */
|
|
225
|
+
sign: (payload, opts) => postEmbed(buildEmbedUrl(this.embedBaseUrl, "/waivers/sign"), this.apiKey, payload, opts),
|
|
226
|
+
/** Has this applicant/member signed the active waiver? Anchor: applicationId query or Bearer accessToken. */
|
|
227
|
+
status: (orgId, opts) => {
|
|
228
|
+
const query = opts?.applicationId ? `?applicationId=${encodeURIComponent(opts.applicationId)}` : "";
|
|
229
|
+
return getEmbedAuthed(buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/status${query}`), this.apiKey, opts);
|
|
230
|
+
},
|
|
231
|
+
/** Get a short-lived download URL for a completed signature's PDF. Anchor: applicationId query or Bearer. */
|
|
232
|
+
signedPdf: (orgId, signatureId, opts) => {
|
|
233
|
+
const query = opts?.applicationId ? `?applicationId=${encodeURIComponent(opts.applicationId)}` : "";
|
|
234
|
+
return getEmbedAuthed(buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/signatures/${encodeURIComponent(signatureId)}/pdf${query}`), this.apiKey, opts);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
dues = { list: (params, opts) => request(buildUrl(this.baseUrl, "/dues", params), this.auth, opts?.signal) };
|
|
238
|
+
shifts = { list: (params, opts) => request(buildUrl(this.baseUrl, "/shifts", params), this.auth, opts?.signal) };
|
|
239
|
+
org = { get: (opts) => request(buildUrl(this.baseUrl, "/org"), this.auth, opts?.signal) };
|
|
240
|
+
commitments = { list: (opts) => getEmbed(buildEmbedUrl(this.embedBaseUrl, "/commitments"), this.apiKey, opts?.signal) };
|
|
241
|
+
payments = {
|
|
242
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/payments", params), this.auth, opts?.signal),
|
|
243
|
+
/** Requires a member token. `memberId` may be omitted by a non-admin (defaults to themselves); admins must pass it. */
|
|
244
|
+
page: (memberId, duesCollectionId, opts) => post(buildUrl(this.baseUrl, "/payments/page"), this.auth, {
|
|
245
|
+
memberId,
|
|
246
|
+
duesCollectionId
|
|
247
|
+
}, opts?.signal)
|
|
248
|
+
};
|
|
249
|
+
accounts = { list: (params, opts) => request(buildUrl(this.baseUrl, "/accounts", params), this.auth, opts?.signal) };
|
|
250
|
+
contacts = { list: (params, opts) => request(buildUrl(this.baseUrl, "/contacts", params), this.auth, opts?.signal) };
|
|
251
|
+
notifications = { list: (params, opts) => request(buildUrl(this.baseUrl, "/notifications", params), this.auth, opts?.signal) };
|
|
252
|
+
documents = {
|
|
253
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/documents", params), this.auth, opts?.signal),
|
|
254
|
+
get: (id, opts) => request(buildUrl(this.baseUrl, `/documents/${encodeURIComponent(id)}`), this.auth, opts?.signal)
|
|
255
|
+
};
|
|
256
|
+
shelters = {
|
|
257
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/shelters", params), this.auth, opts?.signal),
|
|
258
|
+
get: (id, opts) => request(buildUrl(this.baseUrl, `/shelters/${encodeURIComponent(id)}`), this.auth, opts?.signal)
|
|
259
|
+
};
|
|
260
|
+
bikes = {
|
|
261
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/bikes", params), this.auth, opts?.signal),
|
|
262
|
+
get: (id, opts) => request(buildUrl(this.baseUrl, `/bikes/${encodeURIComponent(id)}`), this.auth, opts?.signal)
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* DustJunkie bike rentals (PLA-1180), passed through to the vendor. A camp
|
|
266
|
+
* without the integration gets 403; a DustJunkie failure is a 502.
|
|
267
|
+
*/
|
|
268
|
+
bikeRentals = {
|
|
269
|
+
/** Bookable options with estimated totals for `delivery` (server default `camp_delivery`). Org key only. */
|
|
270
|
+
catalog: (params, opts) => request(buildUrl(this.baseUrl, "/bike-rentals/catalog", params), this.auth, opts?.signal),
|
|
271
|
+
/** The rental agreement the member must accept before `requestReservation`. Org key only. */
|
|
272
|
+
waiver: (opts) => request(buildUrl(this.baseUrl, "/bike-rentals/waiver"), this.auth, opts?.signal),
|
|
273
|
+
/**
|
|
274
|
+
* Requires a member token. Admins get the whole camp when `memberId` is
|
|
275
|
+
* omitted; any other role gets their own reservations and 403 naming someone else.
|
|
276
|
+
*/
|
|
277
|
+
reservations: (params, opts) => request(buildUrl(this.baseUrl, "/bike-rentals/reservations", params), this.auth, opts?.signal),
|
|
278
|
+
/**
|
|
279
|
+
* Requires a member token; always requests for the acting member. 409 when
|
|
280
|
+
* the model is sold out, 400 when it is unknown or no longer bookable.
|
|
281
|
+
*/
|
|
282
|
+
requestReservation: (payload, opts) => post(buildUrl(this.baseUrl, "/bike-rentals/requests"), this.auth, payload, opts?.signal),
|
|
283
|
+
/** Members who said they want a bike but have no reservation, for the active camp year. Org key only. */
|
|
284
|
+
interestedNotReserved: (opts) => request(buildUrl(this.baseUrl, "/bike-rentals/interested-not-reserved"), this.auth, opts?.signal)
|
|
285
|
+
};
|
|
286
|
+
tags = { list: (opts) => request(buildUrl(this.baseUrl, "/tags"), this.auth, opts?.signal) };
|
|
287
|
+
groups = {
|
|
288
|
+
list: (opts) => request(buildUrl(this.baseUrl, "/groups"), this.auth, opts?.signal),
|
|
289
|
+
get: (id, opts) => request(buildUrl(this.baseUrl, `/groups/${encodeURIComponent(id)}`), this.auth, opts?.signal),
|
|
290
|
+
create: (payload, opts) => post(buildUrl(this.baseUrl, "/groups"), this.auth, payload, opts?.signal)
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* Camp-scoped packing (PLA-1197). Every method is the acting member's own
|
|
294
|
+
* list — packing has no `memberId` to widen, so these require `memberToken`
|
|
295
|
+
* and 401 on an org key alone.
|
|
296
|
+
*/
|
|
297
|
+
packing = {
|
|
298
|
+
/** The resolved list: curated base + camp layer + personal layer (ADR-071). */
|
|
299
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/packing", params), this.auth, opts?.signal),
|
|
300
|
+
/**
|
|
301
|
+
* Set the member's status for one item. The slug goes in the body, not the
|
|
302
|
+
* path: slugs are `${category}/${name}` and contain a `/`.
|
|
303
|
+
*
|
|
304
|
+
* Pass the same `shelter` the list was read under — validation filters by
|
|
305
|
+
* shelter, so an override omitted here rejects shelter-specific items.
|
|
306
|
+
*/
|
|
307
|
+
setStatus: (payload, opts) => sendV1("PUT", buildUrl(this.baseUrl, "/packing/items/status"), this.auth, payload, opts?.signal),
|
|
308
|
+
personalItems: {
|
|
309
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/packing/personal-items", params), this.auth, opts?.signal),
|
|
310
|
+
create: (payload, opts) => post(buildUrl(this.baseUrl, "/packing/personal-items"), this.auth, payload, opts?.signal),
|
|
311
|
+
/**
|
|
312
|
+
* Flip the item between `need` and `have`. It does **not** set `packed` —
|
|
313
|
+
* use `setStatus` for the full vocabulary. 404 if the item is not the
|
|
314
|
+
* acting member's.
|
|
315
|
+
*/
|
|
316
|
+
toggle: (id, opts) => sendV1("PATCH", buildUrl(this.baseUrl, `/packing/personal-items/${encodeURIComponent(id)}`), this.auth, void 0, opts?.signal),
|
|
317
|
+
/** 404 if the item is not the acting member's. */
|
|
318
|
+
remove: (id, opts) => sendV1("DELETE", buildUrl(this.baseUrl, `/packing/personal-items/${encodeURIComponent(id)}`), this.auth, void 0, opts?.signal)
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
admin = {
|
|
322
|
+
applications: {
|
|
323
|
+
/**
|
|
324
|
+
* Get application audit timeline (PLA-712). Requires admin Bearer token.
|
|
325
|
+
*
|
|
326
|
+
* @deprecated The embed handler for this method never existed — calls
|
|
327
|
+
* always 404'd (see PLA-1199). Retained as a compile-time compatibility
|
|
328
|
+
* surface for published-consumer code; delegates to the canonical
|
|
329
|
+
* `GET /api/v1/audit/application-logs?applicationId=` (ADR-054), which
|
|
330
|
+
* reads the same `application_audit_logs` table. The `accessToken`
|
|
331
|
+
* argument is accepted but unused — the v1 surface authenticates with
|
|
332
|
+
* the org API key (`audit:read` scope) instead of an admin JWT. Remove
|
|
333
|
+
* in the next major version.
|
|
334
|
+
*/
|
|
335
|
+
history: async (id, opts) => {
|
|
336
|
+
const rows = [];
|
|
337
|
+
let cursor = null;
|
|
338
|
+
do {
|
|
339
|
+
const page = await request(buildUrl(this.baseUrl, "/audit/application-logs", {
|
|
340
|
+
applicationId: id,
|
|
341
|
+
limit: 100,
|
|
342
|
+
...cursor ? { cursor } : {}
|
|
343
|
+
}), this.auth, opts?.signal);
|
|
344
|
+
rows.push(...page.data);
|
|
345
|
+
cursor = page.nextCursor;
|
|
346
|
+
} while (cursor);
|
|
347
|
+
return { events: rows.map((row) => ({
|
|
348
|
+
id: row.id,
|
|
349
|
+
applicationId: row.applicationId,
|
|
350
|
+
actorId: row.actorUserId,
|
|
351
|
+
actorName: null,
|
|
352
|
+
type: row.action === "status_changed" ? "status_change" : row.action,
|
|
353
|
+
timestamp: row.createdAt ?? "",
|
|
354
|
+
metadata: row.details ?? void 0
|
|
355
|
+
})) };
|
|
356
|
+
},
|
|
357
|
+
transition: (id, payload, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}`), opts.accessToken, payload, opts.signal),
|
|
358
|
+
getAnnotations: (id, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/annotations`), opts.accessToken, void 0, opts.signal),
|
|
359
|
+
addNote: (id, payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/notes`), opts.accessToken, payload, opts.signal),
|
|
360
|
+
setTags: (id, payload, opts) => adminEmbed("PUT", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/tags`), opts.accessToken, payload, opts.signal),
|
|
361
|
+
setScore: (id, payload, opts) => adminEmbed("PUT", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/scores`), opts.accessToken, payload, opts.signal)
|
|
362
|
+
},
|
|
363
|
+
givebutter: { campaigns: {
|
|
364
|
+
list: (opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, "/admin/givebutter/campaigns"), opts.accessToken, void 0, opts.signal),
|
|
365
|
+
discountCodes: {
|
|
366
|
+
list: (campaignId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/discount-codes`), opts.accessToken, void 0, opts.signal),
|
|
367
|
+
create: (campaignId, payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/discount-codes`), opts.accessToken, payload, opts.signal)
|
|
368
|
+
},
|
|
369
|
+
tickets: { create: (campaignId, payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/tickets`), opts.accessToken, payload, opts.signal) }
|
|
370
|
+
} },
|
|
371
|
+
members: {
|
|
372
|
+
create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/members"), opts.accessToken, payload, opts.signal),
|
|
373
|
+
update: (id, patch, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, `/admin/members/${encodeURIComponent(id)}`), opts.accessToken, patch, opts.signal),
|
|
374
|
+
deactivate: (id, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, `/admin/members/${encodeURIComponent(id)}`), opts.accessToken, { status: "inactive" }, opts.signal)
|
|
375
|
+
},
|
|
376
|
+
announcements: {
|
|
377
|
+
/** Send an email blast to camp members (PLA-706). Requires admin Bearer token. */
|
|
378
|
+
send: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/announcements/send"), opts.accessToken, payload, opts.signal) },
|
|
379
|
+
dues: {
|
|
380
|
+
/** Create a manual dues/payment record (PLA-708). Requires admin Bearer token. */
|
|
381
|
+
create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/dues"), opts.accessToken, payload, opts.signal),
|
|
382
|
+
/** Update a dues/payment record (PLA-708). Requires admin Bearer token. */
|
|
383
|
+
update: (id, payload, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, `/admin/dues/${encodeURIComponent(id)}`), opts.accessToken, payload, opts.signal)
|
|
384
|
+
},
|
|
385
|
+
org: {
|
|
386
|
+
/** Update camp configuration settings (PLA-709). Requires admin Bearer token. */
|
|
387
|
+
update: (payload, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, "/admin/org"), opts.accessToken, payload, opts.signal) },
|
|
388
|
+
scheduling: {
|
|
389
|
+
availability: {
|
|
390
|
+
/** Get interview availability for an interviewer (PLA-713). Requires admin Bearer token. */
|
|
391
|
+
get: (interviewerId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/availability/${encodeURIComponent(interviewerId)}`), opts.accessToken, void 0, opts.signal),
|
|
392
|
+
/** Set interview availability for an interviewer (PLA-713). Requires admin Bearer token. */
|
|
393
|
+
set: (interviewerId, payload, opts) => adminEmbed("PUT", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/availability/${encodeURIComponent(interviewerId)}`), opts.accessToken, payload, opts.signal)
|
|
394
|
+
},
|
|
395
|
+
calendar: {
|
|
396
|
+
/** Get Google Calendar connection status for an interviewer (PLA-726). Requires admin Bearer. */
|
|
397
|
+
status: (interviewerId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/status`), opts.accessToken, void 0, opts.signal),
|
|
398
|
+
/** List available Google Calendars (PLA-726). Requires admin Bearer. */
|
|
399
|
+
listCalendars: (interviewerId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/calendars`), opts.accessToken, void 0, opts.signal),
|
|
400
|
+
/** Select which Google Calendar to use (PLA-726). Requires admin Bearer. */
|
|
401
|
+
select: (interviewerId, payload, opts) => adminEmbed("PUT", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/select`), opts.accessToken, payload, opts.signal),
|
|
402
|
+
/** Disconnect Google Calendar from interviewer (PLA-726). Requires admin Bearer. */
|
|
403
|
+
disconnect: (interviewerId, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/disconnect`), opts.accessToken, void 0, opts.signal)
|
|
404
|
+
},
|
|
405
|
+
bookingTokens: {
|
|
406
|
+
/** Create a booking token for an application's interview link (PLA-714). Requires admin Bearer token. */
|
|
407
|
+
create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/booking-tokens"), opts.accessToken, payload, opts.signal),
|
|
408
|
+
/** Get an existing booking token for an application (PLA-714). Requires admin Bearer token. */
|
|
409
|
+
get: (applicationId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/booking-tokens/${encodeURIComponent(applicationId)}`), opts.accessToken, void 0, opts.signal)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
schedule = {
|
|
414
|
+
/** List available interview slots for a booking token (PLA-714). Public. */
|
|
415
|
+
getSlots: (token, opts) => {
|
|
416
|
+
const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}`;
|
|
417
|
+
return fetch(url, { signal: opts?.signal }).then(async (res) => {
|
|
418
|
+
if (!res.ok) throw await parseError(res);
|
|
419
|
+
return res.json();
|
|
420
|
+
});
|
|
421
|
+
},
|
|
422
|
+
/** Book an interview slot (PLA-714). Public. */
|
|
423
|
+
book: (token, payload, opts) => {
|
|
424
|
+
const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/book`;
|
|
425
|
+
return fetch(url, {
|
|
426
|
+
method: "POST",
|
|
427
|
+
headers: { "Content-Type": "application/json" },
|
|
428
|
+
body: JSON.stringify(payload),
|
|
429
|
+
signal: opts?.signal
|
|
430
|
+
}).then(async (res) => {
|
|
431
|
+
if (!res.ok) throw await parseError(res);
|
|
432
|
+
return res.json();
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
/** Cancel an interview booking (PLA-714). Public. */
|
|
436
|
+
cancel: (token, opts) => {
|
|
437
|
+
const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/cancel`;
|
|
438
|
+
return fetch(url, {
|
|
439
|
+
method: "POST",
|
|
440
|
+
signal: opts?.signal
|
|
441
|
+
}).then(async (res) => {
|
|
442
|
+
if (!res.ok) throw await parseError(res);
|
|
443
|
+
return res.json();
|
|
444
|
+
});
|
|
445
|
+
},
|
|
446
|
+
/** Reschedule an interview booking (PLA-714). Public. */
|
|
447
|
+
reschedule: (token, payload, opts) => {
|
|
448
|
+
const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/reschedule`;
|
|
449
|
+
return fetch(url, {
|
|
450
|
+
method: "POST",
|
|
451
|
+
headers: { "Content-Type": "application/json" },
|
|
452
|
+
body: JSON.stringify(payload),
|
|
453
|
+
signal: opts?.signal
|
|
454
|
+
}).then(async (res) => {
|
|
455
|
+
if (!res.ok) throw await parseError(res);
|
|
456
|
+
return res.json();
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
};
|
|
451
460
|
};
|
|
461
|
+
/** Convenience factory — equivalent to `new PlayaOSClient(opts)`. */
|
|
452
462
|
function createClient(opts) {
|
|
453
|
-
|
|
463
|
+
return new PlayaOSClient(opts);
|
|
454
464
|
}
|
|
455
|
-
|
|
456
|
-
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/exchange.ts
|
|
457
467
|
async function exchangeCode(params) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
idToken: body.idToken,
|
|
490
|
-
expiresAt: body.expiresAt,
|
|
491
|
-
...refreshToken !== void 0 ? { refreshToken } : {}
|
|
492
|
-
};
|
|
468
|
+
const base = params.authBaseUrl ?? "https://auth.playaos.app";
|
|
469
|
+
const isConfidential = params.clientSecret !== void 0;
|
|
470
|
+
const res = await fetch(`${base}/api/auth/v1/exchange`, {
|
|
471
|
+
method: "POST",
|
|
472
|
+
headers: { "content-type": "application/json" },
|
|
473
|
+
body: JSON.stringify({
|
|
474
|
+
code: params.code,
|
|
475
|
+
code_verifier: params.codeVerifier,
|
|
476
|
+
client_id: params.clientId,
|
|
477
|
+
...isConfidential ? { client_secret: params.clientSecret } : {},
|
|
478
|
+
redirect_uri: params.redirectUri
|
|
479
|
+
})
|
|
480
|
+
});
|
|
481
|
+
let body;
|
|
482
|
+
try {
|
|
483
|
+
body = await res.json();
|
|
484
|
+
} catch {
|
|
485
|
+
throw new Error(`Exchange failed: non-JSON response (${res.status})`);
|
|
486
|
+
}
|
|
487
|
+
if (!res.ok) {
|
|
488
|
+
const errorReason = typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" ? body.error : String(res.status);
|
|
489
|
+
throw new Error(`Exchange failed: ${errorReason}`);
|
|
490
|
+
}
|
|
491
|
+
if (typeof body !== "object" || body === null || !("idToken" in body) || typeof body.idToken !== "string" || !("expiresAt" in body) || typeof body.expiresAt !== "string") throw new Error("Exchange failed: malformed success response");
|
|
492
|
+
const refreshToken = "refreshToken" in body && typeof body.refreshToken === "string" ? body.refreshToken : void 0;
|
|
493
|
+
if (isConfidential && refreshToken === void 0) throw new Error("Exchange failed: confidential client response missing refreshToken");
|
|
494
|
+
return {
|
|
495
|
+
idToken: body.idToken,
|
|
496
|
+
expiresAt: body.expiresAt,
|
|
497
|
+
...refreshToken !== void 0 ? { refreshToken } : {}
|
|
498
|
+
};
|
|
493
499
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
500
|
+
//#endregion
|
|
501
|
+
//#region src/public-marketplace.ts
|
|
502
|
+
/** A listing holds at most this many images (matches the DB's position 1–6 check). */
|
|
503
|
+
const MAX_IMAGE_POSITION = 6;
|
|
497
504
|
function createPublicMarketplaceClient(opts) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
505
|
+
const base = opts.baseUrl.replace(/\/+$/, "");
|
|
506
|
+
if (!base) throw new Error("createPublicMarketplaceClient: baseUrl is required");
|
|
507
|
+
async function authedPost(path, body, signal) {
|
|
508
|
+
const token = await opts.getToken();
|
|
509
|
+
if (!token) throw new Error("Not signed in");
|
|
510
|
+
const res = await fetch(`${base}/api/public/marketplace${path}`, {
|
|
511
|
+
method: "POST",
|
|
512
|
+
headers: {
|
|
513
|
+
Authorization: `Bearer ${token}`,
|
|
514
|
+
"Content-Type": "application/json"
|
|
515
|
+
},
|
|
516
|
+
body: JSON.stringify(body),
|
|
517
|
+
signal
|
|
518
|
+
});
|
|
519
|
+
if (!res.ok) throw await parseError(res);
|
|
520
|
+
return res.json();
|
|
521
|
+
}
|
|
522
|
+
async function publicGet(path, signal) {
|
|
523
|
+
const init = {
|
|
524
|
+
signal,
|
|
525
|
+
cache: "no-store"
|
|
526
|
+
};
|
|
527
|
+
const res = await fetch(`${base}/api/public/marketplace${path}`, init);
|
|
528
|
+
if (!res.ok) throw await parseError(res);
|
|
529
|
+
return res.json();
|
|
530
|
+
}
|
|
531
|
+
const seg = (listingId) => `/listings/${encodeURIComponent(listingId)}`;
|
|
532
|
+
return {
|
|
533
|
+
createListing: (input, signal) => authedPost("/listings", input, signal),
|
|
534
|
+
getImageUploadUrl: (listingId, signal) => authedPost(`${seg(listingId)}/image-upload-url`, {}, signal),
|
|
535
|
+
attachImage: (listingId, input, signal) => {
|
|
536
|
+
if (!Number.isInteger(input.position) || input.position < 1 || input.position > 6) return Promise.reject(/* @__PURE__ */ new Error(`attachImage: position must be 1–6`));
|
|
537
|
+
return authedPost(`${seg(listingId)}/images`, input, signal);
|
|
538
|
+
},
|
|
539
|
+
listListingImages: (listingId, signal) => publicGet(`${seg(listingId)}/images`, signal),
|
|
540
|
+
listThumbnails: (listingIds, signal) => publicGet(`/thumbnails?ids=${encodeURIComponent(listingIds.join(","))}`, signal),
|
|
541
|
+
flagListing: (listingId, reason, signal) => authedPost(`${seg(listingId)}/flag`, { reason }, signal),
|
|
542
|
+
replyToListing: (listingId, message, signal) => authedPost(`${seg(listingId)}/reply`, { message }, signal)
|
|
543
|
+
};
|
|
535
544
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
PlayaOSClient,
|
|
540
|
-
createClient,
|
|
541
|
-
createPublicMarketplaceClient,
|
|
542
|
-
exchangeCode
|
|
543
|
-
};
|
|
545
|
+
//#endregion
|
|
546
|
+
export { ApiClientError, MAX_IMAGE_POSITION, PlayaOSClient, createClient, createPublicMarketplaceClient, exchangeCode };
|
|
547
|
+
|
|
544
548
|
//# sourceMappingURL=index.js.map
|