@playaos/api-client 0.1.0 → 0.1.1
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.d.ts +11 -8
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -220,26 +220,29 @@ declare class PlayaOSClient {
|
|
|
220
220
|
declare function createClient(opts: ClientOptions): PlayaOSClient;
|
|
221
221
|
|
|
222
222
|
/**
|
|
223
|
-
* Server-side OAuth-code → ID-token exchange
|
|
223
|
+
* Server-side OAuth-code → ID-token exchange.
|
|
224
224
|
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* Public/PKCE-only clients
|
|
228
|
-
*
|
|
229
|
-
* clients
|
|
225
|
+
* Swaps a one-time authorization code from the PlayaOS IdP for a short-lived
|
|
226
|
+
* ID token. Confidential clients pass `clientSecret`, which is sent to the
|
|
227
|
+
* token endpoint server-side. Public/PKCE-only clients omit it — the
|
|
228
|
+
* authorization-code + code_verifier pair is the sole credential. Public
|
|
229
|
+
* clients can also exchange directly from the browser via `usePlayaOSAuth`
|
|
230
|
+
* in `@playaos/react`.
|
|
230
231
|
*/
|
|
231
232
|
interface ExchangeParams {
|
|
232
233
|
authBaseUrl?: string;
|
|
233
234
|
code: string;
|
|
234
235
|
codeVerifier: string;
|
|
235
236
|
clientId: string;
|
|
236
|
-
|
|
237
|
+
/** Confidential clients only. Omit for public/PKCE-only clients. */
|
|
238
|
+
clientSecret?: string;
|
|
237
239
|
redirectUri: string;
|
|
238
240
|
}
|
|
239
241
|
interface ExchangeResult {
|
|
240
242
|
idToken: string;
|
|
241
243
|
expiresAt: string;
|
|
242
|
-
|
|
244
|
+
/** Confidential clients only. Undefined for public clients. */
|
|
245
|
+
refreshToken?: string;
|
|
243
246
|
}
|
|
244
247
|
declare function exchangeCode(params: ExchangeParams): Promise<ExchangeResult>;
|
|
245
248
|
|
package/dist/index.js
CHANGED
|
@@ -145,6 +145,7 @@ function createClient(opts) {
|
|
|
145
145
|
// src/exchange.ts
|
|
146
146
|
async function exchangeCode(params) {
|
|
147
147
|
const base = params.authBaseUrl ?? "https://auth.playaos.app";
|
|
148
|
+
const isConfidential = params.clientSecret !== void 0;
|
|
148
149
|
const res = await fetch(`${base}/api/auth/v1/exchange`, {
|
|
149
150
|
method: "POST",
|
|
150
151
|
headers: { "content-type": "application/json" },
|
|
@@ -152,7 +153,7 @@ async function exchangeCode(params) {
|
|
|
152
153
|
code: params.code,
|
|
153
154
|
code_verifier: params.codeVerifier,
|
|
154
155
|
client_id: params.clientId,
|
|
155
|
-
client_secret: params.clientSecret,
|
|
156
|
+
...isConfidential ? { client_secret: params.clientSecret } : {},
|
|
156
157
|
redirect_uri: params.redirectUri
|
|
157
158
|
})
|
|
158
159
|
});
|
|
@@ -170,13 +171,13 @@ async function exchangeCode(params) {
|
|
|
170
171
|
throw new Error("Exchange failed: malformed success response");
|
|
171
172
|
}
|
|
172
173
|
const refreshToken = "refreshToken" in body && typeof body.refreshToken === "string" ? body.refreshToken : void 0;
|
|
173
|
-
if (refreshToken === void 0) {
|
|
174
|
-
throw new Error("Exchange failed:
|
|
174
|
+
if (isConfidential && refreshToken === void 0) {
|
|
175
|
+
throw new Error("Exchange failed: confidential client response missing refreshToken");
|
|
175
176
|
}
|
|
176
177
|
return {
|
|
177
178
|
idToken: body.idToken,
|
|
178
179
|
expiresAt: body.expiresAt,
|
|
179
|
-
refreshToken
|
|
180
|
+
...refreshToken !== void 0 ? { refreshToken } : {}
|
|
180
181
|
};
|
|
181
182
|
}
|
|
182
183
|
export {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/error.ts","../src/client.ts","../src/exchange.ts"],"sourcesContent":["export class ApiClientError extends Error {\n constructor(\n public readonly status: number,\n public readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = \"ApiClientError\";\n }\n}\n\nexport async function parseError(res: Response): Promise<ApiClientError> {\n try {\n const body = (await res.json()) as { error?: string; code?: string };\n return new ApiClientError(res.status, body.code ?? \"UNKNOWN\", body.error ?? res.statusText);\n } catch {\n return new ApiClientError(res.status, \"UNKNOWN\", res.statusText);\n }\n}\n","import { type ApiClientError, parseError } from \"./error.js\";\nimport type {\n Application,\n ApplicationCreatePayload,\n ApplicationCreateResponse,\n ApplicationStatus,\n ApplicationTransitionPayload,\n ApplicationTransitionResponse,\n DuesStatus,\n Member,\n MemberCreatePayload,\n MemberCreateResponse,\n MemberDeactivateResponse,\n MemberRole,\n MemberUpdatePayload,\n MemberUpdateResponse,\n OrgConfig,\n PaymentPageResponse,\n Shift,\n} from \"./types.js\";\n\nexport type { ApiClientError };\n\ninterface ClientOptions {\n /** Base URL of the PlayaOS portal, e.g. https://your-camp.playaos.app */\n baseUrl: string;\n /** API key in the format pk_live_* */\n apiKey: string;\n}\n\nfunction buildUrl(base: string, path: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(`${base}/api/v1${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n // Skip undefined and false booleans — z.coerce.boolean() on the server uses Boolean(),\n // which treats any non-empty string (including \"false\") as true. Omitting a false boolean\n // param has the same effect as the server default (unfiltered).\n if (v !== undefined && v !== false) url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\nfunction buildEmbedUrl(base: string, path: string): string {\n return `${base}/api/embed/v1${path}`;\n}\n\nasync function request<T>(url: string, apiKey: string, signal?: AbortSignal): Promise<T> {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${apiKey}` },\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nasync function post<T>(url: string, apiKey: string, body: unknown, signal?: AbortSignal): Promise<T> {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nasync function postEmbed<T>(\n url: string,\n campKey: string,\n body: unknown,\n opts?: { accessToken?: string; signal?: AbortSignal },\n): Promise<T> {\n const headers: Record<string, string> = {\n \"X-Camp-Key\": campKey,\n \"Content-Type\": \"application/json\",\n };\n if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: opts?.signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\n/**\n * Admin write helper for /api/embed/v1/admin/* (PLA-489). Bearer-only: the\n * admin token is the sole credential (no X-Camp-Key — orgId comes from the JWT\n * claims server-side). `method` is POST for create, PATCH for transition/update/\n * deactivate.\n */\nasync function adminEmbed<T>(\n method: \"POST\" | \"PATCH\",\n url: string,\n accessToken: string,\n body: unknown,\n signal?: AbortSignal,\n): Promise<T> {\n const res = await fetch(url, {\n method,\n headers: { Authorization: `Bearer ${accessToken}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nexport class PlayaOSClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\");\n this.apiKey = opts.apiKey;\n }\n\n readonly members = {\n list: (params?: { role?: MemberRole; status?: string }, opts?: { signal?: AbortSignal }): Promise<Member[]> =>\n request(buildUrl(this.baseUrl, \"/members\", params), this.apiKey, opts?.signal),\n\n get: (id: string, opts?: { signal?: AbortSignal }): Promise<Member> =>\n request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.apiKey, opts?.signal),\n };\n\n readonly applications = {\n list: (\n params?: { status?: ApplicationStatus; year?: number },\n opts?: { signal?: AbortSignal },\n ): Promise<Application[]> => request(buildUrl(this.baseUrl, \"/applications\", params), this.apiKey, opts?.signal),\n\n // Anonymous external-camp submission against POST /api/embed/v1/applications\n // (PLA-481). Uses the X-Camp-Key header rather than Bearer auth. Pass\n // `accessToken` to additionally send a PlayaOS IdP JWT (PLA-573 path) so\n // the server trusts the verified profile claim and skips the email-based\n // bootstrap.\n create: (\n payload: ApplicationCreatePayload,\n opts?: { accessToken?: string; signal?: AbortSignal },\n ): Promise<ApplicationCreateResponse> =>\n postEmbed(buildEmbedUrl(this.baseUrl, \"/applications\"), this.apiKey, payload, opts),\n };\n\n readonly dues = {\n list: (params?: { userId?: string; year?: number }, opts?: { signal?: AbortSignal }): Promise<DuesStatus[]> =>\n request(buildUrl(this.baseUrl, \"/dues\", params), this.apiKey, opts?.signal),\n };\n\n readonly shifts = {\n list: (\n params?: { year?: number; fromDate?: string; toDate?: string; publishedOnly?: boolean },\n opts?: { signal?: AbortSignal },\n ): Promise<Shift[]> => request(buildUrl(this.baseUrl, \"/shifts\", params), this.apiKey, opts?.signal),\n };\n\n readonly org = {\n get: (opts?: { signal?: AbortSignal }): Promise<OrgConfig> =>\n request(buildUrl(this.baseUrl, \"/org\"), this.apiKey, opts?.signal),\n };\n\n readonly payments = {\n page: (\n memberId: string,\n duesCollectionId?: string,\n opts?: { signal?: AbortSignal },\n ): Promise<PaymentPageResponse> =>\n post(buildUrl(this.baseUrl, \"/payments/page\"), this.apiKey, { memberId, duesCollectionId }, opts?.signal),\n };\n\n // Admin write APIs (PLA-489). Each method requires `accessToken` — a Supabase\n // JWT for a camp admin/super_admin. The org boundary is enforced server-side\n // from the JWT claims; these endpoints do not use the X-Camp-Key.\n readonly admin = {\n applications: {\n transition: (\n id: string,\n payload: ApplicationTransitionPayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<ApplicationTransitionResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/applications/${encodeURIComponent(id)}`),\n opts.accessToken,\n payload,\n opts.signal,\n ),\n },\n members: {\n create: (\n payload: MemberCreatePayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberCreateResponse> =>\n adminEmbed(\"POST\", buildEmbedUrl(this.baseUrl, \"/admin/members\"), opts.accessToken, payload, opts.signal),\n\n update: (\n id: string,\n patch: MemberUpdatePayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberUpdateResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),\n opts.accessToken,\n patch,\n opts.signal,\n ),\n\n deactivate: (\n id: string,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberDeactivateResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),\n opts.accessToken,\n { status: \"inactive\" },\n opts.signal,\n ),\n },\n };\n}\n\n/** Convenience factory — equivalent to `new PlayaOSClient(opts)`. */\nexport function createClient(opts: ClientOptions): PlayaOSClient {\n return new PlayaOSClient(opts);\n}\n","/**\n * Server-side OAuth-code → ID-token exchange (PLA-573 T24).\n *\n * Used by camp sites' confidential-client backends to swap a one-time\n * authorization code from the PlayaOS IdP for a short-lived ID token.\n * Public/PKCE-only clients exchange directly from the browser via\n * `usePlayaOSAuth` in `@playaos/react`; this helper exists for confidential\n * clients that need to send the client_secret server-side.\n */\n\nexport interface ExchangeParams {\n authBaseUrl?: string;\n code: string;\n codeVerifier: string;\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n}\n\nexport interface ExchangeResult {\n idToken: string;\n expiresAt: string;\n refreshToken: string;\n}\n\nexport async function exchangeCode(params: ExchangeParams): Promise<ExchangeResult> {\n const base = params.authBaseUrl ?? \"https://auth.playaos.app\";\n const res = await fetch(`${base}/api/auth/v1/exchange`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n code: params.code,\n code_verifier: params.codeVerifier,\n client_id: params.clientId,\n client_secret: params.clientSecret,\n redirect_uri: params.redirectUri,\n }),\n });\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n throw new Error(`Exchange failed: non-JSON response (${res.status})`);\n }\n\n if (!res.ok) {\n const errorReason =\n typeof body === \"object\" && body !== null && \"error\" in body && typeof body.error === \"string\"\n ? body.error\n : String(res.status);\n throw new Error(`Exchange failed: ${errorReason}`);\n }\n\n if (\n typeof body !== \"object\" ||\n body === null ||\n !(\"idToken\" in body) ||\n typeof body.idToken !== \"string\" ||\n !(\"expiresAt\" in body) ||\n typeof body.expiresAt !== \"string\"\n ) {\n throw new Error(\"Exchange failed: malformed success response\");\n }\n\n // refreshToken is optional in the response (public clients don't get one);\n // surface as undefined when absent. Result type's refreshToken is required\n // because exchangeCode is the confidential-client helper — keep it required\n // in the typed return but pull it defensively in case the server omits it.\n const refreshToken = \"refreshToken\" in body && typeof body.refreshToken === \"string\" ? body.refreshToken : undefined;\n\n if (refreshToken === undefined) {\n throw new Error(\"Exchange failed: missing refreshToken in confidential response\");\n }\n\n return {\n idToken: body.idToken,\n expiresAt: body.expiresAt,\n refreshToken,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/client.ts","../src/exchange.ts"],"sourcesContent":["export class ApiClientError extends Error {\n constructor(\n public readonly status: number,\n public readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = \"ApiClientError\";\n }\n}\n\nexport async function parseError(res: Response): Promise<ApiClientError> {\n try {\n const body = (await res.json()) as { error?: string; code?: string };\n return new ApiClientError(res.status, body.code ?? \"UNKNOWN\", body.error ?? res.statusText);\n } catch {\n return new ApiClientError(res.status, \"UNKNOWN\", res.statusText);\n }\n}\n","import { type ApiClientError, parseError } from \"./error.js\";\nimport type {\n Application,\n ApplicationCreatePayload,\n ApplicationCreateResponse,\n ApplicationStatus,\n ApplicationTransitionPayload,\n ApplicationTransitionResponse,\n DuesStatus,\n Member,\n MemberCreatePayload,\n MemberCreateResponse,\n MemberDeactivateResponse,\n MemberRole,\n MemberUpdatePayload,\n MemberUpdateResponse,\n OrgConfig,\n PaymentPageResponse,\n Shift,\n} from \"./types.js\";\n\nexport type { ApiClientError };\n\ninterface ClientOptions {\n /** Base URL of the PlayaOS portal, e.g. https://your-camp.playaos.app */\n baseUrl: string;\n /** API key in the format pk_live_* */\n apiKey: string;\n}\n\nfunction buildUrl(base: string, path: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(`${base}/api/v1${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n // Skip undefined and false booleans — z.coerce.boolean() on the server uses Boolean(),\n // which treats any non-empty string (including \"false\") as true. Omitting a false boolean\n // param has the same effect as the server default (unfiltered).\n if (v !== undefined && v !== false) url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\nfunction buildEmbedUrl(base: string, path: string): string {\n return `${base}/api/embed/v1${path}`;\n}\n\nasync function request<T>(url: string, apiKey: string, signal?: AbortSignal): Promise<T> {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${apiKey}` },\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nasync function post<T>(url: string, apiKey: string, body: unknown, signal?: AbortSignal): Promise<T> {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nasync function postEmbed<T>(\n url: string,\n campKey: string,\n body: unknown,\n opts?: { accessToken?: string; signal?: AbortSignal },\n): Promise<T> {\n const headers: Record<string, string> = {\n \"X-Camp-Key\": campKey,\n \"Content-Type\": \"application/json\",\n };\n if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: opts?.signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\n/**\n * Admin write helper for /api/embed/v1/admin/* (PLA-489). Bearer-only: the\n * admin token is the sole credential (no X-Camp-Key — orgId comes from the JWT\n * claims server-side). `method` is POST for create, PATCH for transition/update/\n * deactivate.\n */\nasync function adminEmbed<T>(\n method: \"POST\" | \"PATCH\",\n url: string,\n accessToken: string,\n body: unknown,\n signal?: AbortSignal,\n): Promise<T> {\n const res = await fetch(url, {\n method,\n headers: { Authorization: `Bearer ${accessToken}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nexport class PlayaOSClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\");\n this.apiKey = opts.apiKey;\n }\n\n readonly members = {\n list: (params?: { role?: MemberRole; status?: string }, opts?: { signal?: AbortSignal }): Promise<Member[]> =>\n request(buildUrl(this.baseUrl, \"/members\", params), this.apiKey, opts?.signal),\n\n get: (id: string, opts?: { signal?: AbortSignal }): Promise<Member> =>\n request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.apiKey, opts?.signal),\n };\n\n readonly applications = {\n list: (\n params?: { status?: ApplicationStatus; year?: number },\n opts?: { signal?: AbortSignal },\n ): Promise<Application[]> => request(buildUrl(this.baseUrl, \"/applications\", params), this.apiKey, opts?.signal),\n\n // Anonymous external-camp submission against POST /api/embed/v1/applications\n // (PLA-481). Uses the X-Camp-Key header rather than Bearer auth. Pass\n // `accessToken` to additionally send a PlayaOS IdP JWT (PLA-573 path) so\n // the server trusts the verified profile claim and skips the email-based\n // bootstrap.\n create: (\n payload: ApplicationCreatePayload,\n opts?: { accessToken?: string; signal?: AbortSignal },\n ): Promise<ApplicationCreateResponse> =>\n postEmbed(buildEmbedUrl(this.baseUrl, \"/applications\"), this.apiKey, payload, opts),\n };\n\n readonly dues = {\n list: (params?: { userId?: string; year?: number }, opts?: { signal?: AbortSignal }): Promise<DuesStatus[]> =>\n request(buildUrl(this.baseUrl, \"/dues\", params), this.apiKey, opts?.signal),\n };\n\n readonly shifts = {\n list: (\n params?: { year?: number; fromDate?: string; toDate?: string; publishedOnly?: boolean },\n opts?: { signal?: AbortSignal },\n ): Promise<Shift[]> => request(buildUrl(this.baseUrl, \"/shifts\", params), this.apiKey, opts?.signal),\n };\n\n readonly org = {\n get: (opts?: { signal?: AbortSignal }): Promise<OrgConfig> =>\n request(buildUrl(this.baseUrl, \"/org\"), this.apiKey, opts?.signal),\n };\n\n readonly payments = {\n page: (\n memberId: string,\n duesCollectionId?: string,\n opts?: { signal?: AbortSignal },\n ): Promise<PaymentPageResponse> =>\n post(buildUrl(this.baseUrl, \"/payments/page\"), this.apiKey, { memberId, duesCollectionId }, opts?.signal),\n };\n\n // Admin write APIs (PLA-489). Each method requires `accessToken` — a Supabase\n // JWT for a camp admin/super_admin. The org boundary is enforced server-side\n // from the JWT claims; these endpoints do not use the X-Camp-Key.\n readonly admin = {\n applications: {\n transition: (\n id: string,\n payload: ApplicationTransitionPayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<ApplicationTransitionResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/applications/${encodeURIComponent(id)}`),\n opts.accessToken,\n payload,\n opts.signal,\n ),\n },\n members: {\n create: (\n payload: MemberCreatePayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberCreateResponse> =>\n adminEmbed(\"POST\", buildEmbedUrl(this.baseUrl, \"/admin/members\"), opts.accessToken, payload, opts.signal),\n\n update: (\n id: string,\n patch: MemberUpdatePayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberUpdateResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),\n opts.accessToken,\n patch,\n opts.signal,\n ),\n\n deactivate: (\n id: string,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberDeactivateResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),\n opts.accessToken,\n { status: \"inactive\" },\n opts.signal,\n ),\n },\n };\n}\n\n/** Convenience factory — equivalent to `new PlayaOSClient(opts)`. */\nexport function createClient(opts: ClientOptions): PlayaOSClient {\n return new PlayaOSClient(opts);\n}\n","/**\n * Server-side OAuth-code → ID-token exchange.\n *\n * Swaps a one-time authorization code from the PlayaOS IdP for a short-lived\n * ID token. Confidential clients pass `clientSecret`, which is sent to the\n * token endpoint server-side. Public/PKCE-only clients omit it — the\n * authorization-code + code_verifier pair is the sole credential. Public\n * clients can also exchange directly from the browser via `usePlayaOSAuth`\n * in `@playaos/react`.\n */\n\nexport interface ExchangeParams {\n authBaseUrl?: string;\n code: string;\n codeVerifier: string;\n clientId: string;\n /** Confidential clients only. Omit for public/PKCE-only clients. */\n clientSecret?: string;\n redirectUri: string;\n}\n\nexport interface ExchangeResult {\n idToken: string;\n expiresAt: string;\n /** Confidential clients only. Undefined for public clients. */\n refreshToken?: string;\n}\n\nexport async function exchangeCode(params: ExchangeParams): Promise<ExchangeResult> {\n const base = params.authBaseUrl ?? \"https://auth.playaos.app\";\n // A confidential client is identified by passing clientSecret at all — even an\n // empty string. We forward it as-is so the server validates it (an empty or\n // wrong secret is rejected there); public clients omit the field entirely.\n const isConfidential = params.clientSecret !== undefined;\n const res = await fetch(`${base}/api/auth/v1/exchange`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n code: params.code,\n code_verifier: params.codeVerifier,\n client_id: params.clientId,\n ...(isConfidential ? { client_secret: params.clientSecret } : {}),\n redirect_uri: params.redirectUri,\n }),\n });\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n throw new Error(`Exchange failed: non-JSON response (${res.status})`);\n }\n\n if (!res.ok) {\n const errorReason =\n typeof body === \"object\" && body !== null && \"error\" in body && typeof body.error === \"string\"\n ? body.error\n : String(res.status);\n throw new Error(`Exchange failed: ${errorReason}`);\n }\n\n if (\n typeof body !== \"object\" ||\n body === null ||\n !(\"idToken\" in body) ||\n typeof body.idToken !== \"string\" ||\n !(\"expiresAt\" in body) ||\n typeof body.expiresAt !== \"string\"\n ) {\n throw new Error(\"Exchange failed: malformed success response\");\n }\n\n // The server mints a refresh token only for confidential clients (it sets\n // `withRefreshToken: client.type === \"confidential\"`); public clients get\n // none. Surface it when present, otherwise leave it undefined.\n const refreshToken = \"refreshToken\" in body && typeof body.refreshToken === \"string\" ? body.refreshToken : undefined;\n\n // A confidential client that gets no refresh token means the server response\n // is malformed or the rotation flow is broken — surface it rather than\n // silently returning a result the caller can't refresh.\n if (isConfidential && refreshToken === undefined) {\n throw new Error(\"Exchange failed: confidential client response missing refreshToken\");\n }\n\n return {\n idToken: body.idToken,\n expiresAt: body.expiresAt,\n ...(refreshToken !== undefined ? { refreshToken } : {}),\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playaos/api-client",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Typed API client for PlayaOS
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Typed API client for PlayaOS \u2014 manage camp members, dues, shifts, and applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"tsup": "^8.5.1",
|
|
37
|
-
"typescript": "^
|
|
38
|
-
"vitest": "^4.1.
|
|
37
|
+
"typescript": "^6.0.3",
|
|
38
|
+
"vitest": "^4.1.7",
|
|
39
39
|
"zod": "^4.4.3"
|
|
40
40
|
}
|
|
41
41
|
}
|