@playaos/api-client 0.12.0 → 0.13.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/dist/index.js CHANGED
@@ -1,544 +1,449 @@
1
- // src/error.ts
1
+ //#region src/error.ts
2
2
  var ApiClientError = class extends Error {
3
- constructor(status, code, message, retryAfter) {
4
- super(message);
5
- this.status = status;
6
- this.code = code;
7
- this.retryAfter = retryAfter;
8
- this.name = "ApiClientError";
9
- }
10
- status;
11
- code;
12
- retryAfter;
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
- const header = res.headers?.get?.("retry-after")?.trim();
16
- return header && /^\d+$/.test(header) ? parseInt(header, 10) : void 0;
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
- const retryAfter = parseRetryAfter(res);
20
- try {
21
- const body = await res.json();
22
- return new ApiClientError(res.status, body.code ?? "UNKNOWN", body.error ?? res.statusText, retryAfter);
23
- } catch {
24
- return new ApiClientError(res.status, "UNKNOWN", res.statusText, retryAfter);
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
- const url = new URL(`${base}/api/v1${path}`);
31
- if (params) {
32
- for (const [k, v] of Object.entries(params)) {
33
- if (v !== void 0 && v !== false) url.searchParams.set(k, String(v));
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
- return `${base}/api/embed/v1${path}`;
47
+ return `${base}/api/embed/v1${path}`;
40
48
  }
41
49
  function withCampKey(url, campKey) {
42
- const embedUrl = new URL(url);
43
- embedUrl.searchParams.set("camp_key", campKey);
44
- return embedUrl.toString();
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
- const res = await fetch(withCampKey(url, campKey), {
48
- headers: { "X-Camp-Key": campKey },
49
- signal
50
- });
51
- if (!res.ok) throw await parseError(res);
52
- return res.json();
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
- const headers = { "X-Camp-Key": campKey };
56
- if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
57
- const res = await fetch(withCampKey(url, campKey), { headers, signal: opts?.signal });
58
- if (!res.ok) throw await parseError(res);
59
- return res.json();
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, apiKey, signal) {
62
- const res = await fetch(url, {
63
- headers: { Authorization: `Bearer ${apiKey}` },
64
- signal
65
- });
66
- if (!res.ok) throw await parseError(res);
67
- return res.json();
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, apiKey, body, signal) {
70
- const res = await fetch(url, {
71
- method: "POST",
72
- headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
73
- body: JSON.stringify(body),
74
- signal
75
- });
76
- if (!res.ok) throw await parseError(res);
77
- return res.json();
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();
78
92
  }
79
93
  async function postEmbed(url, campKey, body, opts) {
80
- const headers = {
81
- "X-Camp-Key": campKey,
82
- "Content-Type": "application/json"
83
- };
84
- if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
85
- const res = await fetch(withCampKey(url, campKey), {
86
- method: "POST",
87
- headers,
88
- body: JSON.stringify(body),
89
- signal: opts?.signal
90
- });
91
- if (!res.ok) throw await parseError(res);
92
- return res.json();
94
+ const headers = {
95
+ "X-Camp-Key": campKey,
96
+ "Content-Type": "application/json"
97
+ };
98
+ if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
99
+ const res = await fetch(withCampKey(url, campKey), {
100
+ method: "POST",
101
+ headers,
102
+ body: JSON.stringify(body),
103
+ signal: opts?.signal
104
+ });
105
+ if (!res.ok) throw await parseError(res);
106
+ return res.json();
93
107
  }
94
108
  async function putEmbed(url, campKey, body, opts) {
95
- const headers = {
96
- "X-Camp-Key": campKey,
97
- "Content-Type": "application/json"
98
- };
99
- if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
100
- const res = await fetch(withCampKey(url, campKey), {
101
- method: "PUT",
102
- headers,
103
- body: JSON.stringify(body),
104
- signal: opts?.signal
105
- });
106
- if (!res.ok) throw await parseError(res);
107
- return res.json();
109
+ const headers = {
110
+ "X-Camp-Key": campKey,
111
+ "Content-Type": "application/json"
112
+ };
113
+ if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
114
+ const res = await fetch(withCampKey(url, campKey), {
115
+ method: "PUT",
116
+ headers,
117
+ body: JSON.stringify(body),
118
+ signal: opts?.signal
119
+ });
120
+ if (!res.ok) throw await parseError(res);
121
+ return res.json();
108
122
  }
109
123
  async function deleteEmbed(url, campKey, opts) {
110
- const headers = { "X-Camp-Key": campKey };
111
- if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
112
- const res = await fetch(withCampKey(url, campKey), { method: "DELETE", headers, signal: opts?.signal });
113
- if (!res.ok) throw await parseError(res);
114
- return res.json();
124
+ const headers = { "X-Camp-Key": campKey };
125
+ if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
126
+ const res = await fetch(withCampKey(url, campKey), {
127
+ method: "DELETE",
128
+ headers,
129
+ signal: opts?.signal
130
+ });
131
+ if (!res.ok) throw await parseError(res);
132
+ return res.json();
115
133
  }
134
+ /**
135
+ * Bearer-only helper for /api/embed/v1/admin/* (PLA-489). The admin token is
136
+ * the sole credential (no X-Camp-Key — orgId comes from the JWT claims
137
+ * server-side). `method` can be GET, POST, PATCH, or PUT; `body` is optional
138
+ * (omitted for GET reads like getAnnotations).
139
+ */
116
140
  async function adminEmbed(method, url, accessToken, body, signal) {
117
- const headers = { Authorization: `Bearer ${accessToken}` };
118
- const init = { method, headers, signal };
119
- if (body !== void 0) {
120
- headers["Content-Type"] = "application/json";
121
- init.body = JSON.stringify(body);
122
- }
123
- const res = await fetch(url, init);
124
- if (!res.ok) throw await parseError(res);
125
- return res.json();
141
+ const headers = { Authorization: `Bearer ${accessToken}` };
142
+ const init = {
143
+ method,
144
+ headers,
145
+ signal
146
+ };
147
+ if (body !== void 0) {
148
+ headers["Content-Type"] = "application/json";
149
+ init.body = JSON.stringify(body);
150
+ }
151
+ const res = await fetch(url, init);
152
+ if (!res.ok) throw await parseError(res);
153
+ return res.json();
126
154
  }
127
- var PlayaOSClient = class {
128
- baseUrl;
129
- embedBaseUrl;
130
- apiKey;
131
- constructor(opts) {
132
- this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
133
- this.embedBaseUrl = (opts.embedBaseUrl ?? opts.baseUrl).replace(/\/+$/, "");
134
- this.apiKey = opts.apiKey;
135
- }
136
- members = {
137
- list: (params, opts) => request(buildUrl(this.baseUrl, "/members", params), this.apiKey, opts?.signal),
138
- get: (id, opts) => request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.apiKey, opts?.signal),
139
- /** Member-self profile update (PLA-700). Requires camp key + member Bearer token. */
140
- selfUpdate: (payload, opts) => putEmbed(buildEmbedUrl(this.embedBaseUrl, "/members/self"), this.apiKey, payload, opts),
141
- /** Member-self shift signup + cancel (PLA-705). Requires camp key + member Bearer. */
142
- shifts: {
143
- signup: (payload, opts) => putEmbed(buildEmbedUrl(this.embedBaseUrl, "/shifts/signup"), this.apiKey, payload, opts),
144
- cancel: (signupId, opts) => deleteEmbed(
145
- buildEmbedUrl(this.embedBaseUrl, `/shifts/signups/${encodeURIComponent(signupId)}`),
146
- this.apiKey,
147
- opts
148
- )
149
- }
150
- };
151
- applications = {
152
- list: (params, opts) => request(buildUrl(this.baseUrl, "/applications", params), this.apiKey, opts?.signal),
153
- // Anonymous external-camp submission against POST /api/embed/v1/applications
154
- // (PLA-481). Uses the X-Camp-Key header rather than Bearer auth. Pass
155
- // `accessToken` to additionally send a PlayaOS IdP JWT (PLA-573 path) so
156
- // the server trusts the verified profile claim and skips the email-based
157
- // bootstrap.
158
- create: (payload, opts) => postEmbed(buildEmbedUrl(this.embedBaseUrl, "/applications"), this.apiKey, payload, opts)
159
- };
160
- waivers = {
161
- /** Get the active waiver for an org for the current year. */
162
- active: (orgId, opts) => getEmbed(buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/active`), this.apiKey, opts?.signal),
163
- /** Sign a waiver. Requires X-Camp-Key; optional accessToken for Bearer path. */
164
- sign: (payload, opts) => postEmbed(buildEmbedUrl(this.embedBaseUrl, "/waivers/sign"), this.apiKey, payload, opts),
165
- /** Has this applicant/member signed the active waiver? Anchor: applicationId query or Bearer accessToken. */
166
- status: (orgId, opts) => {
167
- const query = opts?.applicationId ? `?applicationId=${encodeURIComponent(opts.applicationId)}` : "";
168
- return getEmbedAuthed(buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/status${query}`), this.apiKey, opts);
169
- },
170
- /** Get a short-lived download URL for a completed signature's PDF. Anchor: applicationId query or Bearer. */
171
- signedPdf: (orgId, signatureId, opts) => {
172
- const query = opts?.applicationId ? `?applicationId=${encodeURIComponent(opts.applicationId)}` : "";
173
- return getEmbedAuthed(
174
- buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/signatures/${encodeURIComponent(signatureId)}/pdf${query}`),
175
- this.apiKey,
176
- opts
177
- );
178
- }
179
- };
180
- dues = {
181
- list: (params, opts) => request(buildUrl(this.baseUrl, "/dues", params), this.apiKey, opts?.signal)
182
- };
183
- shifts = {
184
- list: (params, opts) => request(buildUrl(this.baseUrl, "/shifts", params), this.apiKey, opts?.signal)
185
- };
186
- org = {
187
- get: (opts) => request(buildUrl(this.baseUrl, "/org"), this.apiKey, opts?.signal)
188
- };
189
- // Public embed surface: active commitment definitions for the org's current
190
- // year, so the <ApplicationForm> can render + validate them (PLA-682). Uses
191
- // the X-Camp-Key (the apiKey doubles as the camp key), same as applications.create.
192
- commitments = {
193
- list: (opts) => getEmbed(buildEmbedUrl(this.embedBaseUrl, "/commitments"), this.apiKey, opts?.signal)
194
- };
195
- payments = {
196
- page: (memberId, duesCollectionId, opts) => post(buildUrl(this.baseUrl, "/payments/page"), this.apiKey, { memberId, duesCollectionId }, opts?.signal)
197
- };
198
- // Admin write APIs (PLA-489). Each method requires `accessToken` — a Supabase
199
- // JWT for a camp admin/super_admin. The org boundary is enforced server-side
200
- // from the JWT claims; these endpoints do not use the X-Camp-Key.
201
- admin = {
202
- applications: {
203
- /** Get application audit timeline (PLA-712). Requires admin Bearer token. */
204
- history: (id, opts) => adminEmbed(
205
- "GET",
206
- buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/history`),
207
- opts.accessToken,
208
- void 0,
209
- opts.signal
210
- ),
211
- transition: (id, payload, opts) => adminEmbed(
212
- "PATCH",
213
- buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}`),
214
- opts.accessToken,
215
- payload,
216
- opts.signal
217
- ),
218
- getAnnotations: (id, opts) => adminEmbed(
219
- "GET",
220
- buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/annotations`),
221
- opts.accessToken,
222
- void 0,
223
- opts.signal
224
- ),
225
- addNote: (id, payload, opts) => adminEmbed(
226
- "POST",
227
- buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/notes`),
228
- opts.accessToken,
229
- payload,
230
- opts.signal
231
- ),
232
- setTags: (id, payload, opts) => adminEmbed(
233
- "PUT",
234
- buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/tags`),
235
- opts.accessToken,
236
- payload,
237
- opts.signal
238
- ),
239
- setScore: (id, payload, opts) => adminEmbed(
240
- "PUT",
241
- buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/scores`),
242
- opts.accessToken,
243
- payload,
244
- opts.signal
245
- )
246
- },
247
- givebutter: {
248
- campaigns: {
249
- list: (opts) => adminEmbed(
250
- "GET",
251
- buildEmbedUrl(this.embedBaseUrl, "/admin/givebutter/campaigns"),
252
- opts.accessToken,
253
- void 0,
254
- opts.signal
255
- ),
256
- discountCodes: {
257
- list: (campaignId, opts) => adminEmbed(
258
- "GET",
259
- buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/discount-codes`),
260
- opts.accessToken,
261
- void 0,
262
- opts.signal
263
- ),
264
- create: (campaignId, payload, opts) => adminEmbed(
265
- "POST",
266
- buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/discount-codes`),
267
- opts.accessToken,
268
- payload,
269
- opts.signal
270
- )
271
- },
272
- tickets: {
273
- create: (campaignId, payload, opts) => adminEmbed(
274
- "POST",
275
- buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/tickets`),
276
- opts.accessToken,
277
- payload,
278
- opts.signal
279
- )
280
- }
281
- }
282
- },
283
- members: {
284
- create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/members"), opts.accessToken, payload, opts.signal),
285
- update: (id, patch, opts) => adminEmbed(
286
- "PATCH",
287
- buildEmbedUrl(this.embedBaseUrl, `/admin/members/${encodeURIComponent(id)}`),
288
- opts.accessToken,
289
- patch,
290
- opts.signal
291
- ),
292
- deactivate: (id, opts) => adminEmbed(
293
- "PATCH",
294
- buildEmbedUrl(this.embedBaseUrl, `/admin/members/${encodeURIComponent(id)}`),
295
- opts.accessToken,
296
- { status: "inactive" },
297
- opts.signal
298
- )
299
- },
300
- announcements: {
301
- /** Send an email blast to camp members (PLA-706). Requires admin Bearer token. */
302
- send: (payload, opts) => adminEmbed(
303
- "POST",
304
- buildEmbedUrl(this.embedBaseUrl, "/admin/announcements/send"),
305
- opts.accessToken,
306
- payload,
307
- opts.signal
308
- )
309
- },
310
- dues: {
311
- /** Create a manual dues/payment record (PLA-708). Requires admin Bearer token. */
312
- create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/dues"), opts.accessToken, payload, opts.signal),
313
- /** Update a dues/payment record (PLA-708). Requires admin Bearer token. */
314
- update: (id, payload, opts) => adminEmbed(
315
- "PATCH",
316
- buildEmbedUrl(this.embedBaseUrl, `/admin/dues/${encodeURIComponent(id)}`),
317
- opts.accessToken,
318
- payload,
319
- opts.signal
320
- )
321
- },
322
- org: {
323
- /** Update camp configuration settings (PLA-709). Requires admin Bearer token. */
324
- update: (payload, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, "/admin/org"), opts.accessToken, payload, opts.signal)
325
- },
326
- scheduling: {
327
- availability: {
328
- /** Get interview availability for an interviewer (PLA-713). Requires admin Bearer token. */
329
- get: (interviewerId, opts) => adminEmbed(
330
- "GET",
331
- buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/availability/${encodeURIComponent(interviewerId)}`),
332
- opts.accessToken,
333
- void 0,
334
- opts.signal
335
- ),
336
- /** Set interview availability for an interviewer (PLA-713). Requires admin Bearer token. */
337
- set: (interviewerId, payload, opts) => adminEmbed(
338
- "PUT",
339
- buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/availability/${encodeURIComponent(interviewerId)}`),
340
- opts.accessToken,
341
- payload,
342
- opts.signal
343
- )
344
- },
345
- calendar: {
346
- /** Get Google Calendar connection status for an interviewer (PLA-726). Requires admin Bearer. */
347
- status: (interviewerId, opts) => adminEmbed(
348
- "GET",
349
- buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/status`),
350
- opts.accessToken,
351
- void 0,
352
- opts.signal
353
- ),
354
- /** List available Google Calendars (PLA-726). Requires admin Bearer. */
355
- listCalendars: (interviewerId, opts) => adminEmbed(
356
- "GET",
357
- buildEmbedUrl(
358
- this.embedBaseUrl,
359
- `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/calendars`
360
- ),
361
- opts.accessToken,
362
- void 0,
363
- opts.signal
364
- ),
365
- /** Select which Google Calendar to use (PLA-726). Requires admin Bearer. */
366
- select: (interviewerId, payload, opts) => adminEmbed(
367
- "PUT",
368
- buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/select`),
369
- opts.accessToken,
370
- payload,
371
- opts.signal
372
- ),
373
- /** Disconnect Google Calendar from interviewer (PLA-726). Requires admin Bearer. */
374
- disconnect: (interviewerId, opts) => adminEmbed(
375
- "POST",
376
- buildEmbedUrl(
377
- this.embedBaseUrl,
378
- `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/disconnect`
379
- ),
380
- opts.accessToken,
381
- void 0,
382
- opts.signal
383
- )
384
- },
385
- bookingTokens: {
386
- /** Create a booking token for an application's interview link (PLA-714). Requires admin Bearer token. */
387
- create: (payload, opts) => adminEmbed(
388
- "POST",
389
- buildEmbedUrl(this.embedBaseUrl, "/admin/booking-tokens"),
390
- opts.accessToken,
391
- payload,
392
- opts.signal
393
- ),
394
- /** Get an existing booking token for an application (PLA-714). Requires admin Bearer token. */
395
- get: (applicationId, opts) => adminEmbed(
396
- "GET",
397
- buildEmbedUrl(this.embedBaseUrl, `/admin/booking-tokens/${encodeURIComponent(applicationId)}`),
398
- opts.accessToken,
399
- void 0,
400
- opts.signal
401
- )
402
- }
403
- }
404
- };
405
- // Public embed-booking endpoints (PLA-714). No auth — these are the
406
- // applicant-facing schedule surface using the hashed booking token.
407
- schedule = {
408
- /** List available interview slots for a booking token (PLA-714). Public. */
409
- getSlots: (token, opts) => {
410
- const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}`;
411
- return fetch(url, { signal: opts?.signal }).then(async (res) => {
412
- if (!res.ok) throw await parseError(res);
413
- return res.json();
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
- };
155
+ var PlayaOSClient = class PlayaOSClient {
156
+ baseUrl;
157
+ embedBaseUrl;
158
+ apiKey;
159
+ memberToken;
160
+ constructor(opts) {
161
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
162
+ this.embedBaseUrl = (opts.embedBaseUrl ?? opts.baseUrl).replace(/\/+$/, "");
163
+ this.apiKey = opts.apiKey;
164
+ this.memberToken = opts.memberToken;
165
+ }
166
+ /** Credentials for /api/v1 calls; the embed surface uses `apiKey` alone as the camp key. */
167
+ get auth() {
168
+ return {
169
+ apiKey: this.apiKey,
170
+ memberToken: this.memberToken
171
+ };
172
+ }
173
+ /**
174
+ * Derive a client that acts as one member (PLA-1195): same host and org key,
175
+ * plus the member's Supabase session JWT on every /api/v1 call. Use this in a
176
+ * server that holds one org key and serves many signed-in members.
177
+ */
178
+ withMember(memberToken) {
179
+ return new PlayaOSClient({
180
+ baseUrl: this.baseUrl,
181
+ embedBaseUrl: this.embedBaseUrl,
182
+ apiKey: this.apiKey,
183
+ memberToken
184
+ });
185
+ }
186
+ members = {
187
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/members", params), this.auth, opts?.signal),
188
+ /**
189
+ * Full profile (phone, emergency contact). Requires a member token; pass
190
+ * `"me"` for the acting member — non-admins may fetch only themselves.
191
+ */
192
+ get: (id, opts) => request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.auth, opts?.signal),
193
+ /** Member-self profile update (PLA-700). Requires camp key + member Bearer token. */
194
+ selfUpdate: (payload, opts) => putEmbed(buildEmbedUrl(this.embedBaseUrl, "/members/self"), this.apiKey, payload, opts),
195
+ /** Member-self shift signup + cancel (PLA-705). Requires camp key + member Bearer. */
196
+ shifts: {
197
+ signup: (payload, opts) => putEmbed(buildEmbedUrl(this.embedBaseUrl, "/shifts/signup"), this.apiKey, payload, opts),
198
+ cancel: (signupId, opts) => deleteEmbed(buildEmbedUrl(this.embedBaseUrl, `/shifts/signups/${encodeURIComponent(signupId)}`), this.apiKey, opts)
199
+ }
200
+ };
201
+ applications = {
202
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/applications", params), this.auth, opts?.signal),
203
+ create: (payload, opts) => postEmbed(buildEmbedUrl(this.embedBaseUrl, "/applications"), this.apiKey, payload, opts)
204
+ };
205
+ waivers = {
206
+ /** Get the active waiver for an org for the current year. */
207
+ active: (orgId, opts) => getEmbed(buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/active`), this.apiKey, opts?.signal),
208
+ /** Sign a waiver. Requires X-Camp-Key; optional accessToken for Bearer path. */
209
+ sign: (payload, opts) => postEmbed(buildEmbedUrl(this.embedBaseUrl, "/waivers/sign"), this.apiKey, payload, opts),
210
+ /** Has this applicant/member signed the active waiver? Anchor: applicationId query or Bearer accessToken. */
211
+ status: (orgId, opts) => {
212
+ const query = opts?.applicationId ? `?applicationId=${encodeURIComponent(opts.applicationId)}` : "";
213
+ return getEmbedAuthed(buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/status${query}`), this.apiKey, opts);
214
+ },
215
+ /** Get a short-lived download URL for a completed signature's PDF. Anchor: applicationId query or Bearer. */
216
+ signedPdf: (orgId, signatureId, opts) => {
217
+ const query = opts?.applicationId ? `?applicationId=${encodeURIComponent(opts.applicationId)}` : "";
218
+ return getEmbedAuthed(buildEmbedUrl(this.embedBaseUrl, `/waivers/${orgId}/signatures/${encodeURIComponent(signatureId)}/pdf${query}`), this.apiKey, opts);
219
+ }
220
+ };
221
+ dues = { list: (params, opts) => request(buildUrl(this.baseUrl, "/dues", params), this.auth, opts?.signal) };
222
+ shifts = { list: (params, opts) => request(buildUrl(this.baseUrl, "/shifts", params), this.auth, opts?.signal) };
223
+ org = { get: (opts) => request(buildUrl(this.baseUrl, "/org"), this.auth, opts?.signal) };
224
+ commitments = { list: (opts) => getEmbed(buildEmbedUrl(this.embedBaseUrl, "/commitments"), this.apiKey, opts?.signal) };
225
+ payments = {
226
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/payments", params), this.auth, opts?.signal),
227
+ /** Requires a member token. `memberId` may be omitted by a non-admin (defaults to themselves); admins must pass it. */
228
+ page: (memberId, duesCollectionId, opts) => post(buildUrl(this.baseUrl, "/payments/page"), this.auth, {
229
+ memberId,
230
+ duesCollectionId
231
+ }, opts?.signal)
232
+ };
233
+ accounts = { list: (params, opts) => request(buildUrl(this.baseUrl, "/accounts", params), this.auth, opts?.signal) };
234
+ contacts = { list: (params, opts) => request(buildUrl(this.baseUrl, "/contacts", params), this.auth, opts?.signal) };
235
+ notifications = { list: (params, opts) => request(buildUrl(this.baseUrl, "/notifications", params), this.auth, opts?.signal) };
236
+ documents = {
237
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/documents", params), this.auth, opts?.signal),
238
+ get: (id, opts) => request(buildUrl(this.baseUrl, `/documents/${encodeURIComponent(id)}`), this.auth, opts?.signal)
239
+ };
240
+ shelters = {
241
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/shelters", params), this.auth, opts?.signal),
242
+ get: (id, opts) => request(buildUrl(this.baseUrl, `/shelters/${encodeURIComponent(id)}`), this.auth, opts?.signal)
243
+ };
244
+ bikes = {
245
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/bikes", params), this.auth, opts?.signal),
246
+ get: (id, opts) => request(buildUrl(this.baseUrl, `/bikes/${encodeURIComponent(id)}`), this.auth, opts?.signal)
247
+ };
248
+ tags = { list: (opts) => request(buildUrl(this.baseUrl, "/tags"), this.auth, opts?.signal) };
249
+ groups = {
250
+ list: (opts) => request(buildUrl(this.baseUrl, "/groups"), this.auth, opts?.signal),
251
+ get: (id, opts) => request(buildUrl(this.baseUrl, `/groups/${encodeURIComponent(id)}`), this.auth, opts?.signal),
252
+ create: (payload, opts) => post(buildUrl(this.baseUrl, "/groups"), this.auth, payload, opts?.signal)
253
+ };
254
+ admin = {
255
+ applications: {
256
+ /** Get application audit timeline (PLA-712). Requires admin Bearer token. */
257
+ history: (id, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/history`), opts.accessToken, void 0, opts.signal),
258
+ transition: (id, payload, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}`), opts.accessToken, payload, opts.signal),
259
+ getAnnotations: (id, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/annotations`), opts.accessToken, void 0, opts.signal),
260
+ addNote: (id, payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/notes`), opts.accessToken, payload, opts.signal),
261
+ setTags: (id, payload, opts) => adminEmbed("PUT", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/tags`), opts.accessToken, payload, opts.signal),
262
+ setScore: (id, payload, opts) => adminEmbed("PUT", buildEmbedUrl(this.embedBaseUrl, `/admin/applications/${encodeURIComponent(id)}/scores`), opts.accessToken, payload, opts.signal)
263
+ },
264
+ givebutter: { campaigns: {
265
+ list: (opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, "/admin/givebutter/campaigns"), opts.accessToken, void 0, opts.signal),
266
+ discountCodes: {
267
+ list: (campaignId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/discount-codes`), opts.accessToken, void 0, opts.signal),
268
+ create: (campaignId, payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/discount-codes`), opts.accessToken, payload, opts.signal)
269
+ },
270
+ tickets: { create: (campaignId, payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, `/admin/givebutter/campaigns/${campaignId}/tickets`), opts.accessToken, payload, opts.signal) }
271
+ } },
272
+ members: {
273
+ create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/members"), opts.accessToken, payload, opts.signal),
274
+ update: (id, patch, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, `/admin/members/${encodeURIComponent(id)}`), opts.accessToken, patch, opts.signal),
275
+ deactivate: (id, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, `/admin/members/${encodeURIComponent(id)}`), opts.accessToken, { status: "inactive" }, opts.signal)
276
+ },
277
+ announcements: {
278
+ /** Send an email blast to camp members (PLA-706). Requires admin Bearer token. */
279
+ send: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/announcements/send"), opts.accessToken, payload, opts.signal) },
280
+ dues: {
281
+ /** Create a manual dues/payment record (PLA-708). Requires admin Bearer token. */
282
+ create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/dues"), opts.accessToken, payload, opts.signal),
283
+ /** Update a dues/payment record (PLA-708). Requires admin Bearer token. */
284
+ update: (id, payload, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, `/admin/dues/${encodeURIComponent(id)}`), opts.accessToken, payload, opts.signal)
285
+ },
286
+ org: {
287
+ /** Update camp configuration settings (PLA-709). Requires admin Bearer token. */
288
+ update: (payload, opts) => adminEmbed("PATCH", buildEmbedUrl(this.embedBaseUrl, "/admin/org"), opts.accessToken, payload, opts.signal) },
289
+ scheduling: {
290
+ availability: {
291
+ /** Get interview availability for an interviewer (PLA-713). Requires admin Bearer token. */
292
+ get: (interviewerId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/availability/${encodeURIComponent(interviewerId)}`), opts.accessToken, void 0, opts.signal),
293
+ /** Set interview availability for an interviewer (PLA-713). Requires admin Bearer token. */
294
+ set: (interviewerId, payload, opts) => adminEmbed("PUT", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/availability/${encodeURIComponent(interviewerId)}`), opts.accessToken, payload, opts.signal)
295
+ },
296
+ calendar: {
297
+ /** Get Google Calendar connection status for an interviewer (PLA-726). Requires admin Bearer. */
298
+ status: (interviewerId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/status`), opts.accessToken, void 0, opts.signal),
299
+ /** List available Google Calendars (PLA-726). Requires admin Bearer. */
300
+ listCalendars: (interviewerId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/calendars`), opts.accessToken, void 0, opts.signal),
301
+ /** Select which Google Calendar to use (PLA-726). Requires admin Bearer. */
302
+ select: (interviewerId, payload, opts) => adminEmbed("PUT", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/select`), opts.accessToken, payload, opts.signal),
303
+ /** Disconnect Google Calendar from interviewer (PLA-726). Requires admin Bearer. */
304
+ disconnect: (interviewerId, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, `/admin/scheduling/calendar/${encodeURIComponent(interviewerId)}/disconnect`), opts.accessToken, void 0, opts.signal)
305
+ },
306
+ bookingTokens: {
307
+ /** Create a booking token for an application's interview link (PLA-714). Requires admin Bearer token. */
308
+ create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.embedBaseUrl, "/admin/booking-tokens"), opts.accessToken, payload, opts.signal),
309
+ /** Get an existing booking token for an application (PLA-714). Requires admin Bearer token. */
310
+ get: (applicationId, opts) => adminEmbed("GET", buildEmbedUrl(this.embedBaseUrl, `/admin/booking-tokens/${encodeURIComponent(applicationId)}`), opts.accessToken, void 0, opts.signal)
311
+ }
312
+ }
313
+ };
314
+ schedule = {
315
+ /** List available interview slots for a booking token (PLA-714). Public. */
316
+ getSlots: (token, opts) => {
317
+ const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}`;
318
+ return fetch(url, { signal: opts?.signal }).then(async (res) => {
319
+ if (!res.ok) throw await parseError(res);
320
+ return res.json();
321
+ });
322
+ },
323
+ /** Book an interview slot (PLA-714). Public. */
324
+ book: (token, payload, opts) => {
325
+ const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/book`;
326
+ return fetch(url, {
327
+ method: "POST",
328
+ headers: { "Content-Type": "application/json" },
329
+ body: JSON.stringify(payload),
330
+ signal: opts?.signal
331
+ }).then(async (res) => {
332
+ if (!res.ok) throw await parseError(res);
333
+ return res.json();
334
+ });
335
+ },
336
+ /** Cancel an interview booking (PLA-714). Public. */
337
+ cancel: (token, opts) => {
338
+ const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/cancel`;
339
+ return fetch(url, {
340
+ method: "POST",
341
+ signal: opts?.signal
342
+ }).then(async (res) => {
343
+ if (!res.ok) throw await parseError(res);
344
+ return res.json();
345
+ });
346
+ },
347
+ /** Reschedule an interview booking (PLA-714). Public. */
348
+ reschedule: (token, payload, opts) => {
349
+ const url = `${this.embedBaseUrl}/api/embed/v1/schedule/${encodeURIComponent(token)}/reschedule`;
350
+ return fetch(url, {
351
+ method: "POST",
352
+ headers: { "Content-Type": "application/json" },
353
+ body: JSON.stringify(payload),
354
+ signal: opts?.signal
355
+ }).then(async (res) => {
356
+ if (!res.ok) throw await parseError(res);
357
+ return res.json();
358
+ });
359
+ }
360
+ };
451
361
  };
362
+ /** Convenience factory — equivalent to `new PlayaOSClient(opts)`. */
452
363
  function createClient(opts) {
453
- return new PlayaOSClient(opts);
364
+ return new PlayaOSClient(opts);
454
365
  }
455
-
456
- // src/exchange.ts
366
+ //#endregion
367
+ //#region src/exchange.ts
457
368
  async function exchangeCode(params) {
458
- const base = params.authBaseUrl ?? "https://auth.playaos.app";
459
- const isConfidential = params.clientSecret !== void 0;
460
- const res = await fetch(`${base}/api/auth/v1/exchange`, {
461
- method: "POST",
462
- headers: { "content-type": "application/json" },
463
- body: JSON.stringify({
464
- code: params.code,
465
- code_verifier: params.codeVerifier,
466
- client_id: params.clientId,
467
- ...isConfidential ? { client_secret: params.clientSecret } : {},
468
- redirect_uri: params.redirectUri
469
- })
470
- });
471
- let body;
472
- try {
473
- body = await res.json();
474
- } catch {
475
- throw new Error(`Exchange failed: non-JSON response (${res.status})`);
476
- }
477
- if (!res.ok) {
478
- const errorReason = typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" ? body.error : String(res.status);
479
- throw new Error(`Exchange failed: ${errorReason}`);
480
- }
481
- if (typeof body !== "object" || body === null || !("idToken" in body) || typeof body.idToken !== "string" || !("expiresAt" in body) || typeof body.expiresAt !== "string") {
482
- throw new Error("Exchange failed: malformed success response");
483
- }
484
- const refreshToken = "refreshToken" in body && typeof body.refreshToken === "string" ? body.refreshToken : void 0;
485
- if (isConfidential && refreshToken === void 0) {
486
- throw new Error("Exchange failed: confidential client response missing refreshToken");
487
- }
488
- return {
489
- idToken: body.idToken,
490
- expiresAt: body.expiresAt,
491
- ...refreshToken !== void 0 ? { refreshToken } : {}
492
- };
369
+ const base = params.authBaseUrl ?? "https://auth.playaos.app";
370
+ const isConfidential = params.clientSecret !== void 0;
371
+ const res = await fetch(`${base}/api/auth/v1/exchange`, {
372
+ method: "POST",
373
+ headers: { "content-type": "application/json" },
374
+ body: JSON.stringify({
375
+ code: params.code,
376
+ code_verifier: params.codeVerifier,
377
+ client_id: params.clientId,
378
+ ...isConfidential ? { client_secret: params.clientSecret } : {},
379
+ redirect_uri: params.redirectUri
380
+ })
381
+ });
382
+ let body;
383
+ try {
384
+ body = await res.json();
385
+ } catch {
386
+ throw new Error(`Exchange failed: non-JSON response (${res.status})`);
387
+ }
388
+ if (!res.ok) {
389
+ const errorReason = typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" ? body.error : String(res.status);
390
+ throw new Error(`Exchange failed: ${errorReason}`);
391
+ }
392
+ 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");
393
+ const refreshToken = "refreshToken" in body && typeof body.refreshToken === "string" ? body.refreshToken : void 0;
394
+ if (isConfidential && refreshToken === void 0) throw new Error("Exchange failed: confidential client response missing refreshToken");
395
+ return {
396
+ idToken: body.idToken,
397
+ expiresAt: body.expiresAt,
398
+ ...refreshToken !== void 0 ? { refreshToken } : {}
399
+ };
493
400
  }
494
-
495
- // src/public-marketplace.ts
496
- var MAX_IMAGE_POSITION = 6;
401
+ //#endregion
402
+ //#region src/public-marketplace.ts
403
+ /** A listing holds at most this many images (matches the DB's position 1–6 check). */
404
+ const MAX_IMAGE_POSITION = 6;
497
405
  function createPublicMarketplaceClient(opts) {
498
- const base = opts.baseUrl.replace(/\/+$/, "");
499
- if (!base) {
500
- throw new Error("createPublicMarketplaceClient: baseUrl is required");
501
- }
502
- async function authedPost(path, body, signal) {
503
- const token = await opts.getToken();
504
- if (!token) throw new Error("Not signed in");
505
- const res = await fetch(`${base}/api/public/marketplace${path}`, {
506
- method: "POST",
507
- headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
508
- body: JSON.stringify(body),
509
- signal
510
- });
511
- if (!res.ok) throw await parseError(res);
512
- return res.json();
513
- }
514
- async function publicGet(path, signal) {
515
- const init = { signal, cache: "no-store" };
516
- const res = await fetch(`${base}/api/public/marketplace${path}`, init);
517
- if (!res.ok) throw await parseError(res);
518
- return res.json();
519
- }
520
- const seg = (listingId) => `/listings/${encodeURIComponent(listingId)}`;
521
- return {
522
- createListing: (input, signal) => authedPost("/listings", input, signal),
523
- getImageUploadUrl: (listingId, signal) => authedPost(`${seg(listingId)}/image-upload-url`, {}, signal),
524
- attachImage: (listingId, input, signal) => {
525
- if (!Number.isInteger(input.position) || input.position < 1 || input.position > MAX_IMAGE_POSITION) {
526
- return Promise.reject(new Error(`attachImage: position must be 1\u2013${MAX_IMAGE_POSITION}`));
527
- }
528
- return authedPost(`${seg(listingId)}/images`, input, signal);
529
- },
530
- listListingImages: (listingId, signal) => publicGet(`${seg(listingId)}/images`, signal),
531
- listThumbnails: (listingIds, signal) => publicGet(`/thumbnails?ids=${encodeURIComponent(listingIds.join(","))}`, signal),
532
- flagListing: (listingId, reason, signal) => authedPost(`${seg(listingId)}/flag`, { reason }, signal),
533
- replyToListing: (listingId, message, signal) => authedPost(`${seg(listingId)}/reply`, { message }, signal)
534
- };
406
+ const base = opts.baseUrl.replace(/\/+$/, "");
407
+ if (!base) throw new Error("createPublicMarketplaceClient: baseUrl is required");
408
+ async function authedPost(path, body, signal) {
409
+ const token = await opts.getToken();
410
+ if (!token) throw new Error("Not signed in");
411
+ const res = await fetch(`${base}/api/public/marketplace${path}`, {
412
+ method: "POST",
413
+ headers: {
414
+ Authorization: `Bearer ${token}`,
415
+ "Content-Type": "application/json"
416
+ },
417
+ body: JSON.stringify(body),
418
+ signal
419
+ });
420
+ if (!res.ok) throw await parseError(res);
421
+ return res.json();
422
+ }
423
+ async function publicGet(path, signal) {
424
+ const init = {
425
+ signal,
426
+ cache: "no-store"
427
+ };
428
+ const res = await fetch(`${base}/api/public/marketplace${path}`, init);
429
+ if (!res.ok) throw await parseError(res);
430
+ return res.json();
431
+ }
432
+ const seg = (listingId) => `/listings/${encodeURIComponent(listingId)}`;
433
+ return {
434
+ createListing: (input, signal) => authedPost("/listings", input, signal),
435
+ getImageUploadUrl: (listingId, signal) => authedPost(`${seg(listingId)}/image-upload-url`, {}, signal),
436
+ attachImage: (listingId, input, signal) => {
437
+ if (!Number.isInteger(input.position) || input.position < 1 || input.position > 6) return Promise.reject(/* @__PURE__ */ new Error(`attachImage: position must be 1–6`));
438
+ return authedPost(`${seg(listingId)}/images`, input, signal);
439
+ },
440
+ listListingImages: (listingId, signal) => publicGet(`${seg(listingId)}/images`, signal),
441
+ listThumbnails: (listingIds, signal) => publicGet(`/thumbnails?ids=${encodeURIComponent(listingIds.join(","))}`, signal),
442
+ flagListing: (listingId, reason, signal) => authedPost(`${seg(listingId)}/flag`, { reason }, signal),
443
+ replyToListing: (listingId, message, signal) => authedPost(`${seg(listingId)}/reply`, { message }, signal)
444
+ };
535
445
  }
536
- export {
537
- ApiClientError,
538
- MAX_IMAGE_POSITION,
539
- PlayaOSClient,
540
- createClient,
541
- createPublicMarketplaceClient,
542
- exchangeCode
543
- };
446
+ //#endregion
447
+ export { ApiClientError, MAX_IMAGE_POSITION, PlayaOSClient, createClient, createPublicMarketplaceClient, exchangeCode };
448
+
544
449
  //# sourceMappingURL=index.js.map