@pulseid/client 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BRQ2T53Z.js → chunk-6WAMTQKC.js} +139 -2
- package/dist/chunk-6WAMTQKC.js.map +1 -0
- package/dist/index.d.ts +161 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +73 -3
- package/dist/server.js +106 -2
- package/dist/server.js.map +1 -1
- package/package.json +18 -19
- package/dist/chunk-BRQ2T53Z.js.map +0 -1
|
@@ -308,6 +308,143 @@ var PulseIdClient = class {
|
|
|
308
308
|
});
|
|
309
309
|
}
|
|
310
310
|
// ===========================================================================
|
|
311
|
+
// EMAIL VERIFICATION
|
|
312
|
+
// ===========================================================================
|
|
313
|
+
/**
|
|
314
|
+
* Resend the email verification link.
|
|
315
|
+
*
|
|
316
|
+
* Requires the `email` scope. Will fail if email is already verified.
|
|
317
|
+
*
|
|
318
|
+
* @param accessToken - A valid access token with `email` scope
|
|
319
|
+
* @returns Success confirmation
|
|
320
|
+
* @throws {PulseIdError} If email is already verified or request fails
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```typescript
|
|
324
|
+
* try {
|
|
325
|
+
* await client.resendVerificationEmail(accessToken);
|
|
326
|
+
* console.log('Verification email sent!');
|
|
327
|
+
* } catch (error) {
|
|
328
|
+
* if (error.code === 'invalid_request') {
|
|
329
|
+
* console.log('Email already verified');
|
|
330
|
+
* }
|
|
331
|
+
* }
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
async resendVerificationEmail(accessToken) {
|
|
335
|
+
return this.http.post(
|
|
336
|
+
"/api/v1/me/resend-verification",
|
|
337
|
+
{},
|
|
338
|
+
{
|
|
339
|
+
Authorization: `Bearer ${accessToken}`
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
// ===========================================================================
|
|
344
|
+
// USERS (Consent-gated user data)
|
|
345
|
+
// ===========================================================================
|
|
346
|
+
/**
|
|
347
|
+
* Get a paginated list of users who have consented to this app.
|
|
348
|
+
*
|
|
349
|
+
* Requires the `users:read` scope. Returns only public profile data.
|
|
350
|
+
* If the token has `users:admin` scope, returns extended user data.
|
|
351
|
+
*
|
|
352
|
+
* @param accessToken - A valid access token with `users:read` scope
|
|
353
|
+
* @param options - Pagination and search options
|
|
354
|
+
* @returns Paginated list of user profiles
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```typescript
|
|
358
|
+
* const result = await client.getUsers(accessToken, {
|
|
359
|
+
* limit: 20,
|
|
360
|
+
* search: 'john',
|
|
361
|
+
* });
|
|
362
|
+
* console.log(`Found ${result.pagination.total} users`);
|
|
363
|
+
* result.data.forEach(user => console.log(user.displayName));
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
async getUsers(accessToken, options) {
|
|
367
|
+
const params = new URLSearchParams();
|
|
368
|
+
if (options?.limit !== void 0) {
|
|
369
|
+
params.set("limit", String(options.limit));
|
|
370
|
+
}
|
|
371
|
+
if (options?.offset !== void 0) {
|
|
372
|
+
params.set("offset", String(options.offset));
|
|
373
|
+
}
|
|
374
|
+
if (options?.search) {
|
|
375
|
+
params.set("search", options.search);
|
|
376
|
+
}
|
|
377
|
+
const query = params.toString();
|
|
378
|
+
const path = query ? `/api/v1/users?${query}` : "/api/v1/users";
|
|
379
|
+
return this.http.get(path, {
|
|
380
|
+
Authorization: `Bearer ${accessToken}`
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Get a single user by their ID.
|
|
385
|
+
*
|
|
386
|
+
* Requires the `users:read` scope. Returns 404 if the user hasn't consented
|
|
387
|
+
* to this app or doesn't exist.
|
|
388
|
+
*
|
|
389
|
+
* @param accessToken - A valid access token with `users:read` scope
|
|
390
|
+
* @param userId - The user's UUID
|
|
391
|
+
* @returns The user's profile
|
|
392
|
+
* @throws {NotFoundError} If user not found or hasn't consented to this app
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```typescript
|
|
396
|
+
* try {
|
|
397
|
+
* const user = await client.getUser(accessToken, userId);
|
|
398
|
+
* console.log(`Hello, ${user.displayName}!`);
|
|
399
|
+
* } catch (error) {
|
|
400
|
+
* if (error instanceof NotFoundError) {
|
|
401
|
+
* console.log('User not found');
|
|
402
|
+
* }
|
|
403
|
+
* }
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
async getUser(accessToken, userId) {
|
|
407
|
+
return this.http.get(
|
|
408
|
+
`/api/v1/users/${encodeURIComponent(userId)}`,
|
|
409
|
+
{
|
|
410
|
+
Authorization: `Bearer ${accessToken}`
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get multiple users by their IDs in a single request.
|
|
416
|
+
*
|
|
417
|
+
* Requires the `users:read` scope. Only returns users who have consented
|
|
418
|
+
* to this app - missing IDs are silently omitted from the response.
|
|
419
|
+
*
|
|
420
|
+
* @param accessToken - A valid access token with `users:read` scope
|
|
421
|
+
* @param userIds - Array of user UUIDs (max 100)
|
|
422
|
+
* @returns Paginated list of found user profiles
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```typescript
|
|
426
|
+
* const groupMemberIds = ['uuid-1', 'uuid-2', 'uuid-3'];
|
|
427
|
+
* const result = await client.getUsersByIds(accessToken, groupMemberIds);
|
|
428
|
+
* // Note: result.data.length may be less than groupMemberIds.length
|
|
429
|
+
* // if some users haven't consented to this app
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
async getUsersByIds(accessToken, userIds) {
|
|
433
|
+
if (userIds.length === 0) {
|
|
434
|
+
return {
|
|
435
|
+
data: [],
|
|
436
|
+
pagination: { total: 0, limit: 0, offset: 0, hasMore: false }
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const ids = userIds.slice(0, 100).join(",");
|
|
440
|
+
return this.http.get(
|
|
441
|
+
`/api/v1/users?ids=${encodeURIComponent(ids)}`,
|
|
442
|
+
{
|
|
443
|
+
Authorization: `Bearer ${accessToken}`
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
// ===========================================================================
|
|
311
448
|
// UTILITY METHODS
|
|
312
449
|
// ===========================================================================
|
|
313
450
|
/**
|
|
@@ -402,5 +539,5 @@ var PulseIdClient = class {
|
|
|
402
539
|
};
|
|
403
540
|
|
|
404
541
|
export { InsufficientScopeError, InvalidTokenError, NetworkError, NotFoundError, PulseIdClient, PulseIdError, TimeoutError };
|
|
405
|
-
//# sourceMappingURL=chunk-
|
|
406
|
-
//# sourceMappingURL=chunk-
|
|
542
|
+
//# sourceMappingURL=chunk-6WAMTQKC.js.map
|
|
543
|
+
//# sourceMappingURL=chunk-6WAMTQKC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"names":[],"mappings":"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","file":"chunk-6WAMTQKC.js","sourcesContent":["/**\n * PULSE ID SDK Errors\n *\n * Custom error classes for better error handling.\n */\n\nimport type { ApiErrorResponse, ErrorCode } from './types.js';\n\n/**\n * Base error class for PULSE ID SDK errors.\n */\nexport class PulseIdError extends Error {\n readonly code: ErrorCode;\n readonly statusCode: number;\n\n constructor(code: ErrorCode, message: string, statusCode = 400) {\n super(message);\n this.name = 'PulseIdError';\n this.code = code;\n this.statusCode = statusCode;\n\n // Maintains proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PulseIdError);\n }\n }\n\n /**\n * Create an error from an API response.\n */\n static fromResponse(response: ApiErrorResponse, statusCode: number): PulseIdError {\n const code = mapErrorCode(response.error);\n\n switch (code) {\n case 'invalid_token':\n case 'expired_token':\n return new InvalidTokenError(response.error_description);\n case 'insufficient_scope':\n return new InsufficientScopeError(\n response.error_description,\n response.required_scope\n );\n case 'not_found':\n return new NotFoundError(response.error_description);\n default:\n return new PulseIdError(code, response.error_description, statusCode);\n }\n }\n}\n\n/**\n * Error thrown when the access token is invalid or expired.\n */\nexport class InvalidTokenError extends PulseIdError {\n constructor(message = 'Invalid or expired access token') {\n super('invalid_token', message, 401);\n this.name = 'InvalidTokenError';\n }\n}\n\n/**\n * Error thrown when the access token lacks required scopes.\n */\nexport class InsufficientScopeError extends PulseIdError {\n readonly requiredScope: string | undefined;\n\n constructor(message: string, requiredScope?: string) {\n super('insufficient_scope', message, 403);\n this.name = 'InsufficientScopeError';\n this.requiredScope = requiredScope;\n }\n}\n\n/**\n * Error thrown when a resource is not found.\n */\nexport class NotFoundError extends PulseIdError {\n constructor(message = 'Resource not found') {\n super('not_found', message, 404);\n this.name = 'NotFoundError';\n }\n}\n\n/**\n * Error thrown when a network request fails.\n */\nexport class NetworkError extends PulseIdError {\n readonly cause: Error | undefined;\n\n constructor(message: string, cause?: Error) {\n super('server_error', message, 0);\n this.name = 'NetworkError';\n this.cause = cause;\n }\n}\n\n/**\n * Error thrown when a request times out.\n */\nexport class TimeoutError extends PulseIdError {\n constructor(message = 'Request timed out') {\n super('server_error', message, 0);\n this.name = 'TimeoutError';\n }\n}\n\n/**\n * Map API error codes to SDK error codes.\n */\nfunction mapErrorCode(apiError: string): ErrorCode {\n switch (apiError) {\n case 'invalid_token':\n return 'invalid_token';\n case 'expired_token':\n return 'expired_token';\n case 'insufficient_scope':\n return 'insufficient_scope';\n case 'not_found':\n return 'not_found';\n case 'invalid_request':\n return 'invalid_request';\n default:\n return 'server_error';\n }\n}\n","/**\n * HTTP Client Utilities\n *\n * Internal utilities for making HTTP requests with proper error handling.\n */\n\nimport { NetworkError, PulseIdError, TimeoutError } from './errors.js';\nimport type { ApiErrorResponse, PulseIdConfig } from './types.js';\n\nconst DEFAULT_TIMEOUT = 30_000; // 30 seconds\n\ntype HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\ntype RequestOptions = {\n method: HttpMethod;\n headers?: Record<string, string>;\n body?: unknown;\n timeout?: number;\n};\n\n/**\n * Validate that a URL is a valid HTTPS URL.\n * Prevents SSRF and ensures secure communication.\n */\nfunction validateIssuerUrl(issuer: string): string {\n let url: URL;\n try {\n url = new URL(issuer);\n } catch {\n throw new Error(`Invalid issuer URL: ${issuer}`);\n }\n\n if (url.username || url.password) {\n throw new Error(`Issuer URL must not include credentials: ${issuer}`);\n }\n\n // Only allow HTTPS in production (allow HTTP for localhost in development)\n const isLocalhost =\n url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '::1';\n if (url.protocol !== 'https:' && !isLocalhost) {\n throw new Error(`Issuer URL must use HTTPS: ${issuer}`);\n }\n\n // Remove trailing slash for consistent URL building\n return url.origin + url.pathname.replace(/\\/$/, '');\n}\n\n/**\n * Create a configured HTTP client for the PULSE ID API.\n */\nexport function createHttpClient(config: PulseIdConfig) {\n const baseUrl = validateIssuerUrl(config.issuer);\n const fetchFn = config.fetch ?? globalThis.fetch;\n const timeout = config.timeout ?? DEFAULT_TIMEOUT;\n\n /**\n * Make an HTTP request to the PULSE ID API.\n */\n async function request<T>(path: string, options: RequestOptions): Promise<T> {\n const url = `${baseUrl}${path}`;\n const controller = new AbortController();\n\n // Set up timeout\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, options.timeout ?? timeout);\n\n try {\n const response = await fetchFn(url, {\n method: options.method,\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...options.headers,\n },\n ...(options.body !== undefined && { body: JSON.stringify(options.body) }),\n signal: controller.signal,\n });\n\n // Clear the timeout\n clearTimeout(timeoutId);\n\n // Parse response body\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n if (isJson) {\n const errorBody = (await response.json()) as ApiErrorResponse;\n throw PulseIdError.fromResponse(errorBody, response.status);\n }\n\n const errorText = await response.text();\n throw new PulseIdError(\n 'server_error',\n errorText || `HTTP ${response.status}`,\n response.status\n );\n }\n\n // Return parsed JSON or empty object for 204\n if (response.status === 204 || !isJson) {\n return {} as T;\n }\n\n return (await response.json()) as T;\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Re-throw SDK errors as-is\n if (error instanceof PulseIdError) {\n throw error;\n }\n\n // Handle abort (timeout)\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new TimeoutError(`Request to ${path} timed out after ${timeout}ms`);\n }\n\n // Handle network errors\n if (error instanceof TypeError) {\n throw new NetworkError(`Network request to ${path} failed`, error);\n }\n\n // Unknown error\n throw new NetworkError(\n `Unknown error during request to ${path}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n return {\n /**\n * Make a GET request.\n */\n get<T>(path: string, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'GET', ...(headers && { headers }) });\n },\n\n /**\n * Make a POST request.\n */\n post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'POST', body, ...(headers && { headers }) });\n },\n\n /**\n * Make a PATCH request.\n */\n patch<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'PATCH', body, ...(headers && { headers }) });\n },\n\n /**\n * Make a DELETE request.\n */\n delete<T>(path: string, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'DELETE', ...(headers && { headers }) });\n },\n\n /**\n * Make a form-encoded POST request (for OAuth token endpoints).\n */\n async postForm<T>(\n path: string,\n data: Record<string, string>,\n headers?: Record<string, string>\n ): Promise<T> {\n const url = `${baseUrl}${path}`;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetchFn(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n ...headers,\n },\n body: new URLSearchParams(data).toString(),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n if (isJson) {\n const errorBody = (await response.json()) as ApiErrorResponse;\n throw PulseIdError.fromResponse(errorBody, response.status);\n }\n // Handle non-JSON error responses (e.g., HTML from proxy)\n const errorText = await response.text();\n throw new PulseIdError(\n 'server_error',\n errorText || `HTTP ${response.status}`,\n response.status\n );\n }\n\n return (await response.json()) as T;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof PulseIdError) throw error;\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new TimeoutError();\n }\n throw new NetworkError(\n `Request to ${path} failed`,\n error instanceof Error ? error : undefined\n );\n }\n },\n };\n}\n\nexport type HttpClient = ReturnType<typeof createHttpClient>;\n","/**\n * PULSE ID Client\n *\n * Client-side SDK for interacting with the PULSE ID API.\n * Use this in browser environments where you have an access token.\n *\n * For server-side usage with refresh token management, use `PulseIdServer` from './server'.\n */\n\nimport { createHttpClient, type HttpClient } from './http.js';\nimport type {\n AdminUserProfile,\n Profile,\n ProfileUpdate,\n PublicUserProfile,\n PulseIdConfig,\n UserInfo,\n UsersListResponse,\n UsersQueryOptions,\n} from './types.js';\n\n/**\n * PULSE ID API Client.\n *\n * @example\n * ```typescript\n * const client = new PulseIdClient({\n * issuer: 'https://id.pulserunning.at',\n * clientId: 'your-client-id',\n * });\n *\n * const profile = await client.getProfile(accessToken);\n * console.log(profile.displayName);\n * ```\n */\nexport class PulseIdClient {\n protected readonly http: HttpClient;\n protected readonly config: PulseIdConfig;\n\n constructor(config: PulseIdConfig) {\n this.config = config;\n this.http = createHttpClient(config);\n }\n\n // ===========================================================================\n // PROFILE\n // ===========================================================================\n\n /**\n * Get the authenticated user's profile.\n *\n * The fields returned depend on the scopes granted to the access token:\n * - `profile`: name, avatar, birthday, etc.\n * - `email`: email address and verification status\n * - `address`: address information\n * - `phone`: phone number\n *\n * @param accessToken - A valid access token\n * @returns The user's profile\n *\n * @example\n * ```typescript\n * const profile = await client.getProfile(accessToken);\n * console.log(`Hello, ${profile.displayName}!`);\n * ```\n */\n async getProfile(accessToken: string): Promise<Profile> {\n return this.http.get<Profile>('/api/v1/me', {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n /**\n * Update the authenticated user's profile.\n *\n * Requires the `profile:write` scope.\n *\n * @param accessToken - A valid access token with `profile:write` scope\n * @param data - The profile fields to update\n * @returns The updated profile\n *\n * @example\n * ```typescript\n * const updated = await client.updateProfile(accessToken, {\n * displayName: 'Max Runner',\n * height: 180,\n * });\n * ```\n */\n async updateProfile(accessToken: string, data: ProfileUpdate): Promise<Profile> {\n return this.http.patch<Profile>('/api/v1/me', data, {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n /**\n * Get the authenticated user's OIDC userinfo.\n *\n * Requires the `openid` scope. Additional fields depend on granted scopes.\n *\n * @param accessToken - A valid access token\n * @returns The user's OIDC userinfo\n *\n * @example\n * ```typescript\n * const info = await client.getUserInfo(accessToken);\n * console.log(info.sub);\n * ```\n */\n async getUserInfo(accessToken: string): Promise<UserInfo> {\n return this.http.get<UserInfo>('/userinfo', {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n // ===========================================================================\n // EMAIL VERIFICATION\n // ===========================================================================\n\n /**\n * Resend the email verification link.\n *\n * Requires the `email` scope. Will fail if email is already verified.\n *\n * @param accessToken - A valid access token with `email` scope\n * @returns Success confirmation\n * @throws {PulseIdError} If email is already verified or request fails\n *\n * @example\n * ```typescript\n * try {\n * await client.resendVerificationEmail(accessToken);\n * console.log('Verification email sent!');\n * } catch (error) {\n * if (error.code === 'invalid_request') {\n * console.log('Email already verified');\n * }\n * }\n * ```\n */\n async resendVerificationEmail(accessToken: string): Promise<{ success: true; message: string }> {\n return this.http.post<{ success: true; message: string }>(\n '/api/v1/me/resend-verification',\n {},\n {\n Authorization: `Bearer ${accessToken}`,\n }\n );\n }\n\n // ===========================================================================\n // USERS (Consent-gated user data)\n // ===========================================================================\n\n /**\n * Get a paginated list of users who have consented to this app.\n *\n * Requires the `users:read` scope. Returns only public profile data.\n * If the token has `users:admin` scope, returns extended user data.\n *\n * @param accessToken - A valid access token with `users:read` scope\n * @param options - Pagination and search options\n * @returns Paginated list of user profiles\n *\n * @example\n * ```typescript\n * const result = await client.getUsers(accessToken, {\n * limit: 20,\n * search: 'john',\n * });\n * console.log(`Found ${result.pagination.total} users`);\n * result.data.forEach(user => console.log(user.displayName));\n * ```\n */\n async getUsers(\n accessToken: string,\n options?: UsersQueryOptions\n ): Promise<UsersListResponse<PublicUserProfile | AdminUserProfile>> {\n const params = new URLSearchParams();\n if (options?.limit !== undefined) {\n params.set('limit', String(options.limit));\n }\n if (options?.offset !== undefined) {\n params.set('offset', String(options.offset));\n }\n if (options?.search) {\n params.set('search', options.search);\n }\n\n const query = params.toString();\n const path = query ? `/api/v1/users?${query}` : '/api/v1/users';\n\n return this.http.get<UsersListResponse<PublicUserProfile | AdminUserProfile>>(path, {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n /**\n * Get a single user by their ID.\n *\n * Requires the `users:read` scope. Returns 404 if the user hasn't consented\n * to this app or doesn't exist.\n *\n * @param accessToken - A valid access token with `users:read` scope\n * @param userId - The user's UUID\n * @returns The user's profile\n * @throws {NotFoundError} If user not found or hasn't consented to this app\n *\n * @example\n * ```typescript\n * try {\n * const user = await client.getUser(accessToken, userId);\n * console.log(`Hello, ${user.displayName}!`);\n * } catch (error) {\n * if (error instanceof NotFoundError) {\n * console.log('User not found');\n * }\n * }\n * ```\n */\n async getUser(\n accessToken: string,\n userId: string\n ): Promise<PublicUserProfile | AdminUserProfile> {\n return this.http.get<PublicUserProfile | AdminUserProfile>(\n `/api/v1/users/${encodeURIComponent(userId)}`,\n {\n Authorization: `Bearer ${accessToken}`,\n }\n );\n }\n\n /**\n * Get multiple users by their IDs in a single request.\n *\n * Requires the `users:read` scope. Only returns users who have consented\n * to this app - missing IDs are silently omitted from the response.\n *\n * @param accessToken - A valid access token with `users:read` scope\n * @param userIds - Array of user UUIDs (max 100)\n * @returns Paginated list of found user profiles\n *\n * @example\n * ```typescript\n * const groupMemberIds = ['uuid-1', 'uuid-2', 'uuid-3'];\n * const result = await client.getUsersByIds(accessToken, groupMemberIds);\n * // Note: result.data.length may be less than groupMemberIds.length\n * // if some users haven't consented to this app\n * ```\n */\n async getUsersByIds(\n accessToken: string,\n userIds: string[]\n ): Promise<UsersListResponse<PublicUserProfile | AdminUserProfile>> {\n if (userIds.length === 0) {\n return {\n data: [],\n pagination: { total: 0, limit: 0, offset: 0, hasMore: false },\n };\n }\n\n const ids = userIds.slice(0, 100).join(',');\n return this.http.get<UsersListResponse<PublicUserProfile | AdminUserProfile>>(\n `/api/v1/users?ids=${encodeURIComponent(ids)}`,\n {\n Authorization: `Bearer ${accessToken}`,\n }\n );\n }\n\n // ===========================================================================\n // UTILITY METHODS\n // ===========================================================================\n\n /**\n * Get the configured issuer URL.\n */\n get issuer(): string {\n return this.config.issuer;\n }\n\n /**\n * Get the configured client ID.\n */\n get clientId(): string {\n return this.config.clientId;\n }\n\n /**\n * Build an authorization URL for the OAuth flow.\n *\n * @param options - Authorization options\n * @returns The authorization URL to redirect the user to\n *\n * @example\n * ```typescript\n * const authUrl = client.buildAuthorizationUrl({\n * redirectUri: 'https://myapp.com/callback',\n * scope: 'openid profile email',\n * state: 'random-state-string',\n * });\n * window.location.href = authUrl;\n * ```\n */\n buildAuthorizationUrl(options: {\n redirectUri: string;\n scope: string;\n state: string;\n nonce?: string;\n codeChallenge: string;\n codeChallengeMethod?: 'S256';\n prompt?: 'none' | 'login' | 'consent' | 'create';\n loginHint?: string;\n }): string {\n if (options.codeChallengeMethod && options.codeChallengeMethod !== 'S256') {\n throw new Error('code_challenge_method must be S256');\n }\n\n if (!options.state) {\n throw new Error('state is required for authorization requests');\n }\n\n if (!options.codeChallenge) {\n throw new Error('code_challenge is required for authorization requests');\n }\n\n const scopes = options.scope.split(' ').filter(Boolean);\n if (scopes.includes('openid') && !options.nonce) {\n throw new Error('nonce is required when requesting openid scope');\n }\n\n const url = new URL(`${this.config.issuer}/authorize`);\n\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', this.config.clientId);\n url.searchParams.set('redirect_uri', options.redirectUri);\n url.searchParams.set('scope', options.scope);\n\n url.searchParams.set('state', options.state);\n if (options.nonce) {\n url.searchParams.set('nonce', options.nonce);\n }\n url.searchParams.set('code_challenge', options.codeChallenge);\n url.searchParams.set('code_challenge_method', options.codeChallengeMethod ?? 'S256');\n if (options.prompt) {\n url.searchParams.set('prompt', options.prompt);\n }\n if (options.loginHint) {\n url.searchParams.set('login_hint', options.loginHint);\n }\n\n return url.toString();\n }\n\n /**\n * Build a logout URL for ending the session.\n *\n * @param options - Logout options\n * @returns The logout URL to redirect the user to\n *\n * @example\n * ```typescript\n * const logoutUrl = client.buildLogoutUrl({\n * idTokenHint: idToken,\n * postLogoutRedirectUri: 'https://myapp.com',\n * });\n * window.location.href = logoutUrl;\n * ```\n */\n buildLogoutUrl(options?: {\n idTokenHint?: string;\n postLogoutRedirectUri?: string;\n state?: string;\n }): string {\n const url = new URL(`${this.config.issuer}/logout`);\n\n if (options?.idTokenHint) {\n url.searchParams.set('id_token_hint', options.idTokenHint);\n }\n if (options?.postLogoutRedirectUri) {\n url.searchParams.set('post_logout_redirect_uri', options.postLogoutRedirectUri);\n }\n if (options?.state) {\n url.searchParams.set('state', options.state);\n }\n\n return url.toString();\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -179,8 +179,79 @@ declare const SCOPES: {
|
|
|
179
179
|
readonly ACTIVITIES_WRITE: "activities:write";
|
|
180
180
|
/** Manage external sync connections (future) */
|
|
181
181
|
readonly CONNECTIONS: "connections";
|
|
182
|
+
/** Read public profiles of users who use this app */
|
|
183
|
+
readonly USERS_READ: "users:read";
|
|
184
|
+
/** Read extended user data (email, dates, status) - requires client credentials */
|
|
185
|
+
readonly USERS_ADMIN: "users:admin";
|
|
182
186
|
};
|
|
183
187
|
type Scope = (typeof SCOPES)[keyof typeof SCOPES];
|
|
188
|
+
/**
|
|
189
|
+
* Public user profile - visible to apps with users:read scope.
|
|
190
|
+
* Only includes non-sensitive, public-facing information.
|
|
191
|
+
*/
|
|
192
|
+
type PublicUserProfile = {
|
|
193
|
+
/** Unique user identifier (UUID) */
|
|
194
|
+
id: string;
|
|
195
|
+
/** Username (unique handle) */
|
|
196
|
+
username: string | null;
|
|
197
|
+
/** Display name */
|
|
198
|
+
displayName: string | null;
|
|
199
|
+
/** First name */
|
|
200
|
+
firstName: string | null;
|
|
201
|
+
/** Last name */
|
|
202
|
+
lastName: string | null;
|
|
203
|
+
/** Avatar URL */
|
|
204
|
+
avatar: string | null;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Admin user profile - extended data for apps with users:admin scope.
|
|
208
|
+
* Includes all public fields plus sensitive/admin data.
|
|
209
|
+
*/
|
|
210
|
+
type AdminUserProfile = PublicUserProfile & {
|
|
211
|
+
/** User's email address */
|
|
212
|
+
email: string;
|
|
213
|
+
/** Whether the email has been verified */
|
|
214
|
+
emailVerified: boolean;
|
|
215
|
+
/** When the account was created (ISO 8601) */
|
|
216
|
+
createdAt: string;
|
|
217
|
+
/** When the user last logged in (ISO 8601), or null if never */
|
|
218
|
+
lastLoginAt: string | null;
|
|
219
|
+
/** Whether the account is active */
|
|
220
|
+
isActive: boolean;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Pagination metadata for list responses.
|
|
224
|
+
*/
|
|
225
|
+
type Pagination = {
|
|
226
|
+
/** Total number of matching items */
|
|
227
|
+
total: number;
|
|
228
|
+
/** Number of items per page */
|
|
229
|
+
limit: number;
|
|
230
|
+
/** Current offset */
|
|
231
|
+
offset: number;
|
|
232
|
+
/** Whether there are more items after this page */
|
|
233
|
+
hasMore: boolean;
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* Options for querying users.
|
|
237
|
+
*/
|
|
238
|
+
type UsersQueryOptions = {
|
|
239
|
+
/** Maximum results to return (1-100, default: 20) */
|
|
240
|
+
limit?: number;
|
|
241
|
+
/** Offset for pagination (default: 0) */
|
|
242
|
+
offset?: number;
|
|
243
|
+
/** Search term - matches username, displayName, firstName, lastName */
|
|
244
|
+
search?: string;
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Response from the users list endpoint.
|
|
248
|
+
*/
|
|
249
|
+
type UsersListResponse<T extends PublicUserProfile = PublicUserProfile> = {
|
|
250
|
+
/** Array of user profiles */
|
|
251
|
+
data: T[];
|
|
252
|
+
/** Pagination metadata */
|
|
253
|
+
pagination: Pagination;
|
|
254
|
+
};
|
|
184
255
|
|
|
185
256
|
/**
|
|
186
257
|
* HTTP Client Utilities
|
|
@@ -294,6 +365,95 @@ declare class PulseIdClient {
|
|
|
294
365
|
* ```
|
|
295
366
|
*/
|
|
296
367
|
getUserInfo(accessToken: string): Promise<UserInfo>;
|
|
368
|
+
/**
|
|
369
|
+
* Resend the email verification link.
|
|
370
|
+
*
|
|
371
|
+
* Requires the `email` scope. Will fail if email is already verified.
|
|
372
|
+
*
|
|
373
|
+
* @param accessToken - A valid access token with `email` scope
|
|
374
|
+
* @returns Success confirmation
|
|
375
|
+
* @throws {PulseIdError} If email is already verified or request fails
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```typescript
|
|
379
|
+
* try {
|
|
380
|
+
* await client.resendVerificationEmail(accessToken);
|
|
381
|
+
* console.log('Verification email sent!');
|
|
382
|
+
* } catch (error) {
|
|
383
|
+
* if (error.code === 'invalid_request') {
|
|
384
|
+
* console.log('Email already verified');
|
|
385
|
+
* }
|
|
386
|
+
* }
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
resendVerificationEmail(accessToken: string): Promise<{
|
|
390
|
+
success: true;
|
|
391
|
+
message: string;
|
|
392
|
+
}>;
|
|
393
|
+
/**
|
|
394
|
+
* Get a paginated list of users who have consented to this app.
|
|
395
|
+
*
|
|
396
|
+
* Requires the `users:read` scope. Returns only public profile data.
|
|
397
|
+
* If the token has `users:admin` scope, returns extended user data.
|
|
398
|
+
*
|
|
399
|
+
* @param accessToken - A valid access token with `users:read` scope
|
|
400
|
+
* @param options - Pagination and search options
|
|
401
|
+
* @returns Paginated list of user profiles
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```typescript
|
|
405
|
+
* const result = await client.getUsers(accessToken, {
|
|
406
|
+
* limit: 20,
|
|
407
|
+
* search: 'john',
|
|
408
|
+
* });
|
|
409
|
+
* console.log(`Found ${result.pagination.total} users`);
|
|
410
|
+
* result.data.forEach(user => console.log(user.displayName));
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
getUsers(accessToken: string, options?: UsersQueryOptions): Promise<UsersListResponse<PublicUserProfile | AdminUserProfile>>;
|
|
414
|
+
/**
|
|
415
|
+
* Get a single user by their ID.
|
|
416
|
+
*
|
|
417
|
+
* Requires the `users:read` scope. Returns 404 if the user hasn't consented
|
|
418
|
+
* to this app or doesn't exist.
|
|
419
|
+
*
|
|
420
|
+
* @param accessToken - A valid access token with `users:read` scope
|
|
421
|
+
* @param userId - The user's UUID
|
|
422
|
+
* @returns The user's profile
|
|
423
|
+
* @throws {NotFoundError} If user not found or hasn't consented to this app
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```typescript
|
|
427
|
+
* try {
|
|
428
|
+
* const user = await client.getUser(accessToken, userId);
|
|
429
|
+
* console.log(`Hello, ${user.displayName}!`);
|
|
430
|
+
* } catch (error) {
|
|
431
|
+
* if (error instanceof NotFoundError) {
|
|
432
|
+
* console.log('User not found');
|
|
433
|
+
* }
|
|
434
|
+
* }
|
|
435
|
+
* ```
|
|
436
|
+
*/
|
|
437
|
+
getUser(accessToken: string, userId: string): Promise<PublicUserProfile | AdminUserProfile>;
|
|
438
|
+
/**
|
|
439
|
+
* Get multiple users by their IDs in a single request.
|
|
440
|
+
*
|
|
441
|
+
* Requires the `users:read` scope. Only returns users who have consented
|
|
442
|
+
* to this app - missing IDs are silently omitted from the response.
|
|
443
|
+
*
|
|
444
|
+
* @param accessToken - A valid access token with `users:read` scope
|
|
445
|
+
* @param userIds - Array of user UUIDs (max 100)
|
|
446
|
+
* @returns Paginated list of found user profiles
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```typescript
|
|
450
|
+
* const groupMemberIds = ['uuid-1', 'uuid-2', 'uuid-3'];
|
|
451
|
+
* const result = await client.getUsersByIds(accessToken, groupMemberIds);
|
|
452
|
+
* // Note: result.data.length may be less than groupMemberIds.length
|
|
453
|
+
* // if some users haven't consented to this app
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
getUsersByIds(accessToken: string, userIds: string[]): Promise<UsersListResponse<PublicUserProfile | AdminUserProfile>>;
|
|
297
457
|
/**
|
|
298
458
|
* Get the configured issuer URL.
|
|
299
459
|
*/
|
|
@@ -401,4 +561,4 @@ declare class TimeoutError extends PulseIdError {
|
|
|
401
561
|
constructor(message?: string);
|
|
402
562
|
}
|
|
403
563
|
|
|
404
|
-
export { type ApiErrorResponse, type ErrorCode, InsufficientScopeError, InvalidTokenError, NetworkError, NotFoundError, type Profile, type ProfileUpdate, PulseIdClient, type PulseIdConfig, PulseIdError, type RefreshOptions, SCOPES, type Scope, TimeoutError, type TokenResponse, type UserInfo };
|
|
564
|
+
export { type AdminUserProfile, type ApiErrorResponse, type ErrorCode, InsufficientScopeError, InvalidTokenError, NetworkError, NotFoundError, type Pagination, type Profile, type ProfileUpdate, type PublicUserProfile, PulseIdClient, type PulseIdConfig, PulseIdError, type RefreshOptions, SCOPES, type Scope, TimeoutError, type TokenResponse, type UserInfo, type UsersListResponse, type UsersQueryOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { InsufficientScopeError, InvalidTokenError, NetworkError, NotFoundError, PulseIdClient, PulseIdError, TimeoutError } from './chunk-
|
|
1
|
+
export { InsufficientScopeError, InvalidTokenError, NetworkError, NotFoundError, PulseIdClient, PulseIdError, TimeoutError } from './chunk-6WAMTQKC.js';
|
|
2
2
|
|
|
3
3
|
// src/types.ts
|
|
4
4
|
var SCOPES = {
|
|
@@ -21,7 +21,11 @@ var SCOPES = {
|
|
|
21
21
|
/** Create/update activities (future) */
|
|
22
22
|
ACTIVITIES_WRITE: "activities:write",
|
|
23
23
|
/** Manage external sync connections (future) */
|
|
24
|
-
CONNECTIONS: "connections"
|
|
24
|
+
CONNECTIONS: "connections",
|
|
25
|
+
/** Read public profiles of users who use this app */
|
|
26
|
+
USERS_READ: "users:read",
|
|
27
|
+
/** Read extended user data (email, dates, status) - requires client credentials */
|
|
28
|
+
USERS_ADMIN: "users:admin"
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
export { SCOPES };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";;;AAiOO,IAAM,MAAA,GAAS;AAAA;AAAA,EAEpB,MAAA,EAAQ,QAAA;AAAA;AAAA,EAGR,OAAA,EAAS,SAAA;AAAA;AAAA,EAGT,aAAA,EAAe,eAAA;AAAA;AAAA,EAGf,KAAA,EAAO,OAAA;AAAA;AAAA,EAGP,OAAA,EAAS,SAAA;AAAA;AAAA,EAGT,KAAA,EAAO,OAAA;AAAA;AAAA,EAGP,cAAA,EAAgB,gBAAA;AAAA;AAAA,EAGhB,UAAA,EAAY,YAAA;AAAA;AAAA,EAGZ,gBAAA,EAAkB,kBAAA;AAAA;AAAA,EAGlB,WAAA,EAAa;AACf","file":"index.js","sourcesContent":["/**\n * PULSE ID SDK Types\n *\n * Type definitions for the PULSE ID API.\n */\n\n// =============================================================================\n// CLIENT CONFIGURATION\n// =============================================================================\n\n/**\n * Configuration for the PULSE ID client.\n */\nexport type PulseIdConfig = {\n /**\n * The PULSE ID issuer URL (e.g., 'https://id.pulserunning.at').\n * Do not include a trailing slash.\n */\n issuer: string;\n\n /**\n * OAuth client ID registered with PULSE ID.\n */\n clientId: string;\n\n /**\n * OAuth client secret. Only required for server-side operations.\n * Never expose this in client-side code.\n */\n clientSecret?: string;\n\n /**\n * Custom fetch implementation. Defaults to global fetch.\n * Useful for testing or custom HTTP handling.\n */\n fetch?: typeof fetch;\n\n /**\n * Request timeout in milliseconds. Defaults to 30000 (30 seconds).\n */\n timeout?: number;\n};\n\n// =============================================================================\n// USER PROFILE\n// =============================================================================\n\n/**\n * User profile returned by PULSE ID.\n */\nexport type Profile = {\n /** Unique user identifier (UUID) */\n id: string;\n\n /** User's email address (requires 'email' scope) */\n email?: string;\n\n /** Whether the email has been verified */\n emailVerified?: boolean;\n\n /** User's first name */\n firstName?: string | null;\n\n /** User's last name */\n lastName?: string | null;\n\n /** Display name (how the user appears to others) */\n displayName?: string | null;\n\n /** URL to the user's avatar image */\n avatar?: string | null;\n\n /** User's birthday in ISO date format (YYYY-MM-DD) */\n birthday?: string | null;\n\n /** User's gender */\n gender?: 'male' | 'female' | 'other' | null;\n\n /** Height in centimeters */\n height?: number | null;\n\n /** Weight in kilograms */\n weight?: number | null;\n\n /** User's address (requires 'address' scope) */\n address?: {\n street?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n };\n\n /** User's phone number (requires 'phone' scope) */\n phone?: string | null;\n\n /** When the account was created */\n createdAt: string;\n\n /** When the profile was last updated */\n updatedAt: string;\n};\n\n/**\n * Fields that can be updated on a user profile.\n */\nexport type ProfileUpdate = {\n firstName?: string | null;\n lastName?: string | null;\n displayName?: string | null;\n avatar?: string | null;\n birthday?: string | null;\n gender?: 'male' | 'female' | 'other' | null;\n height?: number | null;\n weight?: number | null;\n phone?: string | null;\n street?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n};\n\n// =============================================================================\n// OIDC USERINFO\n// =============================================================================\n\n/**\n * Standard OIDC userinfo response from PULSE ID.\n */\nexport type UserInfo = {\n /** Subject identifier (user ID) */\n sub: string;\n /** User's email address (requires 'email' scope) */\n email?: string;\n /** Whether the email has been verified */\n email_verified?: boolean;\n /** Full name */\n name?: string;\n /** Given name */\n given_name?: string;\n /** Family name */\n family_name?: string;\n /** Preferred username */\n preferred_username?: string;\n /** Profile picture URL */\n picture?: string;\n /** Locale */\n locale?: string;\n /** When the userinfo was last updated (seconds since epoch) */\n updated_at?: number;\n};\n\n// =============================================================================\n// OAUTH / TOKENS\n// =============================================================================\n\n/**\n * OAuth token response from PULSE ID.\n */\nexport type TokenResponse = {\n /** The access token for API requests */\n access_token: string;\n\n /** Token type (always 'Bearer') */\n token_type: 'Bearer';\n\n /** Time until the access token expires (in seconds) */\n expires_in: number;\n\n /** Refresh token for obtaining new access tokens */\n refresh_token?: string;\n\n /** ID token containing user claims (JWT) */\n id_token?: string;\n\n /** Granted scopes (space-separated) */\n scope?: string;\n};\n\n/**\n * Token refresh options.\n */\nexport type RefreshOptions = {\n /** The refresh token to exchange */\n refreshToken: string;\n\n /** Optionally request a subset of the original scopes */\n scope?: string;\n};\n\n// =============================================================================\n// API ERRORS\n// =============================================================================\n\n/**\n * Standard OAuth error response.\n */\nexport type ApiErrorResponse = {\n /** Error code (e.g., 'invalid_token', 'insufficient_scope') */\n error: string;\n\n /** Human-readable error description */\n error_description: string;\n\n /** Required scope (for 'insufficient_scope' errors) */\n required_scope?: string;\n};\n\n/**\n * Error codes returned by PULSE ID.\n */\nexport type ErrorCode =\n | 'invalid_request'\n | 'invalid_token'\n | 'expired_token'\n | 'insufficient_scope'\n | 'not_found'\n | 'server_error';\n\n// =============================================================================\n// SCOPES\n// =============================================================================\n\n/**\n * Available OAuth scopes for PULSE ID.\n */\nexport const SCOPES = {\n /** Required for OIDC. Returns the user's ID. */\n OPENID: 'openid',\n\n /** Read profile information (name, avatar, etc.) */\n PROFILE: 'profile',\n\n /** Update profile information */\n PROFILE_WRITE: 'profile:write',\n\n /** Read email address */\n EMAIL: 'email',\n\n /** Read address information */\n ADDRESS: 'address',\n\n /** Read phone number */\n PHONE: 'phone',\n\n /** Request a refresh token */\n OFFLINE_ACCESS: 'offline_access',\n\n /** Read activities (future) */\n ACTIVITIES: 'activities',\n\n /** Create/update activities (future) */\n ACTIVITIES_WRITE: 'activities:write',\n\n /** Manage external sync connections (future) */\n CONNECTIONS: 'connections',\n} as const;\n\nexport type Scope = (typeof SCOPES)[keyof typeof SCOPES];\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"names":[],"mappings":";;;AAiOO,IAAM,MAAA,GAAS;AAAA;AAAA,EAEpB,MAAA,EAAQ,QAAA;AAAA;AAAA,EAGR,OAAA,EAAS,SAAA;AAAA;AAAA,EAGT,aAAA,EAAe,eAAA;AAAA;AAAA,EAGf,KAAA,EAAO,OAAA;AAAA;AAAA,EAGP,OAAA,EAAS,SAAA;AAAA;AAAA,EAGT,KAAA,EAAO,OAAA;AAAA;AAAA,EAGP,cAAA,EAAgB,gBAAA;AAAA;AAAA,EAGhB,UAAA,EAAY,YAAA;AAAA;AAAA,EAGZ,gBAAA,EAAkB,kBAAA;AAAA;AAAA,EAGlB,WAAA,EAAa,aAAA;AAAA;AAAA,EAGb,UAAA,EAAY,YAAA;AAAA;AAAA,EAGZ,WAAA,EAAa;AACf","file":"index.js","sourcesContent":["/**\n * PULSE ID SDK Types\n *\n * Type definitions for the PULSE ID API.\n */\n\n// =============================================================================\n// CLIENT CONFIGURATION\n// =============================================================================\n\n/**\n * Configuration for the PULSE ID client.\n */\nexport type PulseIdConfig = {\n /**\n * The PULSE ID issuer URL (e.g., 'https://id.pulserunning.at').\n * Do not include a trailing slash.\n */\n issuer: string;\n\n /**\n * OAuth client ID registered with PULSE ID.\n */\n clientId: string;\n\n /**\n * OAuth client secret. Only required for server-side operations.\n * Never expose this in client-side code.\n */\n clientSecret?: string;\n\n /**\n * Custom fetch implementation. Defaults to global fetch.\n * Useful for testing or custom HTTP handling.\n */\n fetch?: typeof fetch;\n\n /**\n * Request timeout in milliseconds. Defaults to 30000 (30 seconds).\n */\n timeout?: number;\n};\n\n// =============================================================================\n// USER PROFILE\n// =============================================================================\n\n/**\n * User profile returned by PULSE ID.\n */\nexport type Profile = {\n /** Unique user identifier (UUID) */\n id: string;\n\n /** User's email address (requires 'email' scope) */\n email?: string;\n\n /** Whether the email has been verified */\n emailVerified?: boolean;\n\n /** User's first name */\n firstName?: string | null;\n\n /** User's last name */\n lastName?: string | null;\n\n /** Display name (how the user appears to others) */\n displayName?: string | null;\n\n /** URL to the user's avatar image */\n avatar?: string | null;\n\n /** User's birthday in ISO date format (YYYY-MM-DD) */\n birthday?: string | null;\n\n /** User's gender */\n gender?: 'male' | 'female' | 'other' | null;\n\n /** Height in centimeters */\n height?: number | null;\n\n /** Weight in kilograms */\n weight?: number | null;\n\n /** User's address (requires 'address' scope) */\n address?: {\n street?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n };\n\n /** User's phone number (requires 'phone' scope) */\n phone?: string | null;\n\n /** When the account was created */\n createdAt: string;\n\n /** When the profile was last updated */\n updatedAt: string;\n};\n\n/**\n * Fields that can be updated on a user profile.\n */\nexport type ProfileUpdate = {\n firstName?: string | null;\n lastName?: string | null;\n displayName?: string | null;\n avatar?: string | null;\n birthday?: string | null;\n gender?: 'male' | 'female' | 'other' | null;\n height?: number | null;\n weight?: number | null;\n phone?: string | null;\n street?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n};\n\n// =============================================================================\n// OIDC USERINFO\n// =============================================================================\n\n/**\n * Standard OIDC userinfo response from PULSE ID.\n */\nexport type UserInfo = {\n /** Subject identifier (user ID) */\n sub: string;\n /** User's email address (requires 'email' scope) */\n email?: string;\n /** Whether the email has been verified */\n email_verified?: boolean;\n /** Full name */\n name?: string;\n /** Given name */\n given_name?: string;\n /** Family name */\n family_name?: string;\n /** Preferred username */\n preferred_username?: string;\n /** Profile picture URL */\n picture?: string;\n /** Locale */\n locale?: string;\n /** When the userinfo was last updated (seconds since epoch) */\n updated_at?: number;\n};\n\n// =============================================================================\n// OAUTH / TOKENS\n// =============================================================================\n\n/**\n * OAuth token response from PULSE ID.\n */\nexport type TokenResponse = {\n /** The access token for API requests */\n access_token: string;\n\n /** Token type (always 'Bearer') */\n token_type: 'Bearer';\n\n /** Time until the access token expires (in seconds) */\n expires_in: number;\n\n /** Refresh token for obtaining new access tokens */\n refresh_token?: string;\n\n /** ID token containing user claims (JWT) */\n id_token?: string;\n\n /** Granted scopes (space-separated) */\n scope?: string;\n};\n\n/**\n * Token refresh options.\n */\nexport type RefreshOptions = {\n /** The refresh token to exchange */\n refreshToken: string;\n\n /** Optionally request a subset of the original scopes */\n scope?: string;\n};\n\n// =============================================================================\n// API ERRORS\n// =============================================================================\n\n/**\n * Standard OAuth error response.\n */\nexport type ApiErrorResponse = {\n /** Error code (e.g., 'invalid_token', 'insufficient_scope') */\n error: string;\n\n /** Human-readable error description */\n error_description: string;\n\n /** Required scope (for 'insufficient_scope' errors) */\n required_scope?: string;\n};\n\n/**\n * Error codes returned by PULSE ID.\n */\nexport type ErrorCode =\n | 'invalid_request'\n | 'invalid_token'\n | 'expired_token'\n | 'insufficient_scope'\n | 'not_found'\n | 'server_error';\n\n// =============================================================================\n// SCOPES\n// =============================================================================\n\n/**\n * Available OAuth scopes for PULSE ID.\n */\nexport const SCOPES = {\n /** Required for OIDC. Returns the user's ID. */\n OPENID: 'openid',\n\n /** Read profile information (name, avatar, etc.) */\n PROFILE: 'profile',\n\n /** Update profile information */\n PROFILE_WRITE: 'profile:write',\n\n /** Read email address */\n EMAIL: 'email',\n\n /** Read address information */\n ADDRESS: 'address',\n\n /** Read phone number */\n PHONE: 'phone',\n\n /** Request a refresh token */\n OFFLINE_ACCESS: 'offline_access',\n\n /** Read activities (future) */\n ACTIVITIES: 'activities',\n\n /** Create/update activities (future) */\n ACTIVITIES_WRITE: 'activities:write',\n\n /** Manage external sync connections (future) */\n CONNECTIONS: 'connections',\n\n /** Read public profiles of users who use this app */\n USERS_READ: 'users:read',\n\n /** Read extended user data (email, dates, status) - requires client credentials */\n USERS_ADMIN: 'users:admin',\n} as const;\n\nexport type Scope = (typeof SCOPES)[keyof typeof SCOPES];\n\n// =============================================================================\n// PUBLIC USER PROFILES\n// =============================================================================\n\n/**\n * Public user profile - visible to apps with users:read scope.\n * Only includes non-sensitive, public-facing information.\n */\nexport type PublicUserProfile = {\n /** Unique user identifier (UUID) */\n id: string;\n\n /** Username (unique handle) */\n username: string | null;\n\n /** Display name */\n displayName: string | null;\n\n /** First name */\n firstName: string | null;\n\n /** Last name */\n lastName: string | null;\n\n /** Avatar URL */\n avatar: string | null;\n};\n\n/**\n * Admin user profile - extended data for apps with users:admin scope.\n * Includes all public fields plus sensitive/admin data.\n */\nexport type AdminUserProfile = PublicUserProfile & {\n /** User's email address */\n email: string;\n\n /** Whether the email has been verified */\n emailVerified: boolean;\n\n /** When the account was created (ISO 8601) */\n createdAt: string;\n\n /** When the user last logged in (ISO 8601), or null if never */\n lastLoginAt: string | null;\n\n /** Whether the account is active */\n isActive: boolean;\n};\n\n/**\n * Pagination metadata for list responses.\n */\nexport type Pagination = {\n /** Total number of matching items */\n total: number;\n\n /** Number of items per page */\n limit: number;\n\n /** Current offset */\n offset: number;\n\n /** Whether there are more items after this page */\n hasMore: boolean;\n};\n\n/**\n * Options for querying users.\n */\nexport type UsersQueryOptions = {\n /** Maximum results to return (1-100, default: 20) */\n limit?: number;\n\n /** Offset for pagination (default: 0) */\n offset?: number;\n\n /** Search term - matches username, displayName, firstName, lastName */\n search?: string;\n};\n\n/**\n * Response from the users list endpoint.\n */\nexport type UsersListResponse<T extends PublicUserProfile = PublicUserProfile> = {\n /** Array of user profiles */\n data: T[];\n\n /** Pagination metadata */\n pagination: Pagination;\n};\n"]}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { Profile, ProfileUpdate, PulseIdClient, PulseIdConfig, UserInfo, TokenResponse, RefreshOptions } from './index.js';
|
|
2
|
-
export { InsufficientScopeError, InvalidTokenError, PulseIdError } from './index.js';
|
|
1
|
+
import { Profile, ProfileUpdate, PulseIdClient, PulseIdConfig, UsersQueryOptions, UsersListResponse, AdminUserProfile, UserInfo, PublicUserProfile, TokenResponse, RefreshOptions } from './index.js';
|
|
2
|
+
export { InsufficientScopeError, InvalidTokenError, Pagination, PulseIdError } from './index.js';
|
|
3
3
|
|
|
4
4
|
type ServerConfig = PulseIdConfig & {
|
|
5
5
|
clientSecret: string;
|
|
6
6
|
storage?: TokenStorage;
|
|
7
7
|
};
|
|
8
|
+
type ClientCredentialsToken = {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
expiresAt: Date;
|
|
11
|
+
scope: string;
|
|
12
|
+
};
|
|
8
13
|
interface TokenStorage {
|
|
9
14
|
getTokens(userId: string): Promise<StoredTokens | null>;
|
|
10
15
|
setTokens(userId: string, tokens: StoredTokens): Promise<void>;
|
|
@@ -23,14 +28,72 @@ interface ProfileResource {
|
|
|
23
28
|
interface UserInfoResource {
|
|
24
29
|
get(): Promise<UserInfo>;
|
|
25
30
|
}
|
|
31
|
+
interface EmailResource {
|
|
32
|
+
resendVerification(): Promise<{
|
|
33
|
+
success: true;
|
|
34
|
+
message: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
interface UsersResource {
|
|
38
|
+
list(options?: UsersQueryOptions): Promise<UsersListResponse>;
|
|
39
|
+
get(userId: string): Promise<PublicUserProfile>;
|
|
40
|
+
getByIds(ids: string[]): Promise<UsersListResponse>;
|
|
41
|
+
}
|
|
26
42
|
interface UserClient {
|
|
27
43
|
profile: ProfileResource;
|
|
28
44
|
userInfo: UserInfoResource;
|
|
45
|
+
email: EmailResource;
|
|
46
|
+
users: UsersResource;
|
|
29
47
|
}
|
|
30
48
|
declare class PulseIdServer extends PulseIdClient {
|
|
31
49
|
private readonly clientSecret;
|
|
32
50
|
private readonly storage;
|
|
51
|
+
private clientCredentialsToken;
|
|
33
52
|
constructor(config: ServerConfig);
|
|
53
|
+
/**
|
|
54
|
+
* Get an access token using client credentials grant.
|
|
55
|
+
* This is for server-to-server communication without a user context.
|
|
56
|
+
*
|
|
57
|
+
* The token is cached in memory and automatically refreshed when expired.
|
|
58
|
+
*
|
|
59
|
+
* @param scope - Space-separated scopes to request (e.g., 'users:admin')
|
|
60
|
+
* @returns Access token that can be used for API requests
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const token = await server.getClientCredentialsToken('users:admin');
|
|
65
|
+
* const users = await server.getUsers(token.accessToken, { limit: 50 });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
getClientCredentialsToken(scope: string): Promise<ClientCredentialsToken>;
|
|
69
|
+
/**
|
|
70
|
+
* Get users with admin privileges using client credentials.
|
|
71
|
+
* Returns extended user data including email, creation date, etc.
|
|
72
|
+
*
|
|
73
|
+
* @param options - Query options (limit, offset, search, ids)
|
|
74
|
+
* @returns List of admin user profiles with pagination
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const result = await server.getAdminUsers({ limit: 50, search: 'john' });
|
|
79
|
+
* result.data.forEach(user => console.log(user.email));
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
getAdminUsers(options?: UsersQueryOptions): Promise<UsersListResponse<AdminUserProfile>>;
|
|
83
|
+
/**
|
|
84
|
+
* Get a single user with admin privileges using client credentials.
|
|
85
|
+
*
|
|
86
|
+
* @param userId - The user's UUID
|
|
87
|
+
* @returns Admin user profile with extended data
|
|
88
|
+
*/
|
|
89
|
+
getAdminUser(userId: string): Promise<AdminUserProfile>;
|
|
90
|
+
/**
|
|
91
|
+
* Get multiple users by ID with admin privileges using client credentials.
|
|
92
|
+
*
|
|
93
|
+
* @param userIds - Array of user UUIDs (max 100)
|
|
94
|
+
* @returns List of admin user profiles
|
|
95
|
+
*/
|
|
96
|
+
getAdminUsersByIds(userIds: string[]): Promise<UsersListResponse<AdminUserProfile>>;
|
|
34
97
|
forUser(userId: string): UserClient;
|
|
35
98
|
exchangeCode(code: string, redirectUri: string, codeVerifier?: string): Promise<TokenResponse>;
|
|
36
99
|
refreshTokens(options: RefreshOptions): Promise<TokenResponse>;
|
|
@@ -38,7 +101,14 @@ declare class PulseIdServer extends PulseIdClient {
|
|
|
38
101
|
getProfileWithRefresh(storage: TokenStorage, userId: string): Promise<Profile>;
|
|
39
102
|
updateProfileWithRefresh(storage: TokenStorage, userId: string, data: ProfileUpdate): Promise<Profile>;
|
|
40
103
|
getUserInfoWithRefresh(storage: TokenStorage, userId: string): Promise<UserInfo>;
|
|
104
|
+
resendVerificationWithRefresh(storage: TokenStorage, userId: string): Promise<{
|
|
105
|
+
success: true;
|
|
106
|
+
message: string;
|
|
107
|
+
}>;
|
|
108
|
+
getUsersWithRefresh(storage: TokenStorage, userId: string, options?: UsersQueryOptions): Promise<UsersListResponse>;
|
|
109
|
+
getUserWithRefresh(storage: TokenStorage, userId: string, targetUserId: string): Promise<PublicUserProfile>;
|
|
110
|
+
getUsersByIdsWithRefresh(storage: TokenStorage, userId: string, ids: string[]): Promise<UsersListResponse>;
|
|
41
111
|
private ensureValidTokens;
|
|
42
112
|
}
|
|
43
113
|
|
|
44
|
-
export { Profile, type ProfileResource, ProfileUpdate, PulseIdConfig, PulseIdServer, type ServerConfig, type StoredTokens, TokenResponse, type TokenStorage, type UserClient, UserInfo, type UserInfoResource };
|
|
114
|
+
export { AdminUserProfile, type ClientCredentialsToken, type EmailResource, Profile, type ProfileResource, ProfileUpdate, PublicUserProfile, PulseIdConfig, PulseIdServer, type ServerConfig, type StoredTokens, TokenResponse, type TokenStorage, type UserClient, UserInfo, type UserInfoResource, UsersListResponse, UsersQueryOptions, type UsersResource };
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { PulseIdClient, PulseIdError } from './chunk-
|
|
2
|
-
export { InsufficientScopeError, InvalidTokenError, PulseIdError } from './chunk-
|
|
1
|
+
import { PulseIdClient, PulseIdError } from './chunk-6WAMTQKC.js';
|
|
2
|
+
export { InsufficientScopeError, InvalidTokenError, PulseIdError } from './chunk-6WAMTQKC.js';
|
|
3
3
|
|
|
4
4
|
// src/server.ts
|
|
5
5
|
var PulseIdServer = class extends PulseIdClient {
|
|
6
6
|
clientSecret;
|
|
7
7
|
storage;
|
|
8
|
+
clientCredentialsToken = null;
|
|
8
9
|
constructor(config) {
|
|
9
10
|
if (!config.clientSecret) {
|
|
10
11
|
throw new Error("clientSecret is required for server-side operations");
|
|
@@ -13,6 +14,85 @@ var PulseIdServer = class extends PulseIdClient {
|
|
|
13
14
|
this.clientSecret = config.clientSecret;
|
|
14
15
|
this.storage = config.storage;
|
|
15
16
|
}
|
|
17
|
+
// ===========================================================================
|
|
18
|
+
// CLIENT CREDENTIALS (Machine-to-Machine)
|
|
19
|
+
// ===========================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Get an access token using client credentials grant.
|
|
22
|
+
* This is for server-to-server communication without a user context.
|
|
23
|
+
*
|
|
24
|
+
* The token is cached in memory and automatically refreshed when expired.
|
|
25
|
+
*
|
|
26
|
+
* @param scope - Space-separated scopes to request (e.g., 'users:admin')
|
|
27
|
+
* @returns Access token that can be used for API requests
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const token = await server.getClientCredentialsToken('users:admin');
|
|
32
|
+
* const users = await server.getUsers(token.accessToken, { limit: 50 });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
async getClientCredentialsToken(scope) {
|
|
36
|
+
const bufferMs = 5 * 60 * 1e3;
|
|
37
|
+
if (this.clientCredentialsToken && this.clientCredentialsToken.scope === scope && this.clientCredentialsToken.expiresAt.getTime() - bufferMs > Date.now()) {
|
|
38
|
+
return this.clientCredentialsToken;
|
|
39
|
+
}
|
|
40
|
+
const response = await this.http.postForm("/token", {
|
|
41
|
+
grant_type: "client_credentials",
|
|
42
|
+
client_id: this.config.clientId,
|
|
43
|
+
client_secret: this.clientSecret,
|
|
44
|
+
scope
|
|
45
|
+
});
|
|
46
|
+
this.clientCredentialsToken = {
|
|
47
|
+
accessToken: response.access_token,
|
|
48
|
+
expiresAt: new Date(Date.now() + response.expires_in * 1e3),
|
|
49
|
+
scope: response.scope
|
|
50
|
+
};
|
|
51
|
+
return this.clientCredentialsToken;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get users with admin privileges using client credentials.
|
|
55
|
+
* Returns extended user data including email, creation date, etc.
|
|
56
|
+
*
|
|
57
|
+
* @param options - Query options (limit, offset, search, ids)
|
|
58
|
+
* @returns List of admin user profiles with pagination
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const result = await server.getAdminUsers({ limit: 50, search: 'john' });
|
|
63
|
+
* result.data.forEach(user => console.log(user.email));
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
async getAdminUsers(options) {
|
|
67
|
+
const token = await this.getClientCredentialsToken("users:admin");
|
|
68
|
+
return this.getUsers(token.accessToken, options);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get a single user with admin privileges using client credentials.
|
|
72
|
+
*
|
|
73
|
+
* @param userId - The user's UUID
|
|
74
|
+
* @returns Admin user profile with extended data
|
|
75
|
+
*/
|
|
76
|
+
async getAdminUser(userId) {
|
|
77
|
+
const token = await this.getClientCredentialsToken("users:admin");
|
|
78
|
+
return this.getUser(token.accessToken, userId);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get multiple users by ID with admin privileges using client credentials.
|
|
82
|
+
*
|
|
83
|
+
* @param userIds - Array of user UUIDs (max 100)
|
|
84
|
+
* @returns List of admin user profiles
|
|
85
|
+
*/
|
|
86
|
+
async getAdminUsersByIds(userIds) {
|
|
87
|
+
if (userIds.length === 0) {
|
|
88
|
+
return {
|
|
89
|
+
data: [],
|
|
90
|
+
pagination: { total: 0, limit: 0, offset: 0, hasMore: false }
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const token = await this.getClientCredentialsToken("users:admin");
|
|
94
|
+
return this.getUsersByIds(token.accessToken, userIds);
|
|
95
|
+
}
|
|
16
96
|
forUser(userId) {
|
|
17
97
|
if (!this.storage) {
|
|
18
98
|
throw new Error("storage must be configured to use forUser()");
|
|
@@ -25,6 +105,14 @@ var PulseIdServer = class extends PulseIdClient {
|
|
|
25
105
|
},
|
|
26
106
|
userInfo: {
|
|
27
107
|
get: () => this.getUserInfoWithRefresh(storage, userId)
|
|
108
|
+
},
|
|
109
|
+
email: {
|
|
110
|
+
resendVerification: () => this.resendVerificationWithRefresh(storage, userId)
|
|
111
|
+
},
|
|
112
|
+
users: {
|
|
113
|
+
list: (options) => this.getUsersWithRefresh(storage, userId, options),
|
|
114
|
+
get: (targetUserId) => this.getUserWithRefresh(storage, userId, targetUserId),
|
|
115
|
+
getByIds: (ids) => this.getUsersByIdsWithRefresh(storage, userId, ids)
|
|
28
116
|
}
|
|
29
117
|
};
|
|
30
118
|
}
|
|
@@ -76,6 +164,22 @@ var PulseIdServer = class extends PulseIdClient {
|
|
|
76
164
|
const tokens = await this.ensureValidTokens(storage, userId);
|
|
77
165
|
return this.getUserInfo(tokens.accessToken);
|
|
78
166
|
}
|
|
167
|
+
async resendVerificationWithRefresh(storage, userId) {
|
|
168
|
+
const tokens = await this.ensureValidTokens(storage, userId);
|
|
169
|
+
return this.resendVerificationEmail(tokens.accessToken);
|
|
170
|
+
}
|
|
171
|
+
async getUsersWithRefresh(storage, userId, options) {
|
|
172
|
+
const tokens = await this.ensureValidTokens(storage, userId);
|
|
173
|
+
return this.getUsers(tokens.accessToken, options);
|
|
174
|
+
}
|
|
175
|
+
async getUserWithRefresh(storage, userId, targetUserId) {
|
|
176
|
+
const tokens = await this.ensureValidTokens(storage, userId);
|
|
177
|
+
return this.getUser(tokens.accessToken, targetUserId);
|
|
178
|
+
}
|
|
179
|
+
async getUsersByIdsWithRefresh(storage, userId, ids) {
|
|
180
|
+
const tokens = await this.ensureValidTokens(storage, userId);
|
|
181
|
+
return this.getUsersByIds(tokens.accessToken, ids);
|
|
182
|
+
}
|
|
79
183
|
async ensureValidTokens(storage, userId) {
|
|
80
184
|
const tokens = await storage.getTokens(userId);
|
|
81
185
|
if (!tokens) {
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["import { PulseIdClient } from './client.js';\nimport { PulseIdError } from './errors.js';\nimport type {\n Profile,\n ProfileUpdate,\n PulseIdConfig,\n RefreshOptions,\n TokenResponse,\n UserInfo,\n} from './types.js';\n\nexport type ServerConfig = PulseIdConfig & {\n clientSecret: string;\n storage?: TokenStorage;\n};\n\nexport interface TokenStorage {\n getTokens(userId: string): Promise<StoredTokens | null>;\n setTokens(userId: string, tokens: StoredTokens): Promise<void>;\n deleteTokens(userId: string): Promise<void>;\n}\n\nexport type StoredTokens = {\n accessToken: string;\n refreshToken: string;\n expiresAt: Date;\n scope?: string;\n};\n\nexport interface ProfileResource {\n get(): Promise<Profile>;\n update(data: ProfileUpdate): Promise<Profile>;\n}\n\nexport interface UserInfoResource {\n get(): Promise<UserInfo>;\n}\n\nexport interface UserClient {\n profile: ProfileResource;\n userInfo: UserInfoResource;\n}\n\nexport class PulseIdServer extends PulseIdClient {\n private readonly clientSecret: string;\n private readonly storage: TokenStorage | undefined;\n\n constructor(config: ServerConfig) {\n if (!config.clientSecret) {\n throw new Error('clientSecret is required for server-side operations');\n }\n super(config);\n this.clientSecret = config.clientSecret;\n this.storage = config.storage;\n }\n\n forUser(userId: string): UserClient {\n if (!this.storage) {\n throw new Error('storage must be configured to use forUser()');\n }\n const storage = this.storage;\n\n return {\n profile: {\n get: () => this.getProfileWithRefresh(storage, userId),\n update: (data: ProfileUpdate) => this.updateProfileWithRefresh(storage, userId, data),\n },\n userInfo: {\n get: () => this.getUserInfoWithRefresh(storage, userId),\n },\n };\n }\n\n async exchangeCode(code: string, redirectUri: string, codeVerifier?: string): Promise<TokenResponse> {\n const data: Record<string, string> = {\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (codeVerifier) {\n data['code_verifier'] = codeVerifier;\n }\n return this.http.postForm<TokenResponse>('/token', data);\n }\n\n async refreshTokens(options: RefreshOptions): Promise<TokenResponse> {\n const data: Record<string, string> = {\n grant_type: 'refresh_token',\n refresh_token: options.refreshToken,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (options.scope) {\n data['scope'] = options.scope;\n }\n return this.http.postForm<TokenResponse>('/token', data);\n }\n\n async revokeToken(token: string, tokenTypeHint?: 'access_token' | 'refresh_token'): Promise<void> {\n const data: Record<string, string> = {\n token,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (tokenTypeHint) {\n data['token_type_hint'] = tokenTypeHint;\n }\n await this.http.postForm<void>('/revoke', data);\n }\n\n async getProfileWithRefresh(storage: TokenStorage, userId: string): Promise<Profile> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getProfile(tokens.accessToken);\n }\n\n async updateProfileWithRefresh(storage: TokenStorage, userId: string, data: ProfileUpdate): Promise<Profile> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.updateProfile(tokens.accessToken, data);\n }\n\n async getUserInfoWithRefresh(storage: TokenStorage, userId: string): Promise<UserInfo> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getUserInfo(tokens.accessToken);\n }\n\n private async ensureValidTokens(storage: TokenStorage, userId: string): Promise<StoredTokens> {\n const tokens = await storage.getTokens(userId);\n if (!tokens) {\n throw new PulseIdError('invalid_token', 'No tokens found. User needs to re-authenticate.', 401);\n }\n\n const bufferMs = 5 * 60 * 1000;\n const isExpired = tokens.expiresAt.getTime() - bufferMs < Date.now();\n if (!isExpired) {\n return tokens;\n }\n\n try {\n const newTokens = await this.refreshTokens({ refreshToken: tokens.refreshToken });\n const updatedTokens: StoredTokens = {\n accessToken: newTokens.access_token,\n refreshToken: newTokens.refresh_token ?? tokens.refreshToken,\n expiresAt: new Date(Date.now() + newTokens.expires_in * 1000),\n ...(newTokens.scope ?? tokens.scope ? { scope: newTokens.scope ?? tokens.scope } : {}),\n };\n await storage.setTokens(userId, updatedTokens);\n return updatedTokens;\n } catch {\n await storage.deleteTokens(userId);\n throw new PulseIdError('invalid_token', 'Token refresh failed. User needs to re-authenticate.', 401);\n }\n }\n}\n\nexport type { TokenResponse, Profile, ProfileUpdate, PulseIdConfig, UserInfo } from './types.js';\nexport { PulseIdError, InvalidTokenError, InsufficientScopeError } from './errors.js';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["import { PulseIdClient } from './client.js';\nimport { PulseIdError } from './errors.js';\nimport type {\n AdminUserProfile,\n Profile,\n ProfileUpdate,\n PublicUserProfile,\n PulseIdConfig,\n RefreshOptions,\n TokenResponse,\n UserInfo,\n UsersListResponse,\n UsersQueryOptions,\n} from './types.js';\n\nexport type ServerConfig = PulseIdConfig & {\n clientSecret: string;\n storage?: TokenStorage;\n};\n\nexport type ClientCredentialsToken = {\n accessToken: string;\n expiresAt: Date;\n scope: string;\n};\n\nexport interface TokenStorage {\n getTokens(userId: string): Promise<StoredTokens | null>;\n setTokens(userId: string, tokens: StoredTokens): Promise<void>;\n deleteTokens(userId: string): Promise<void>;\n}\n\nexport type StoredTokens = {\n accessToken: string;\n refreshToken: string;\n expiresAt: Date;\n scope?: string;\n};\n\nexport interface ProfileResource {\n get(): Promise<Profile>;\n update(data: ProfileUpdate): Promise<Profile>;\n}\n\nexport interface UserInfoResource {\n get(): Promise<UserInfo>;\n}\n\nexport interface EmailResource {\n resendVerification(): Promise<{ success: true; message: string }>;\n}\n\nexport interface UsersResource {\n list(options?: UsersQueryOptions): Promise<UsersListResponse>;\n get(userId: string): Promise<PublicUserProfile>;\n getByIds(ids: string[]): Promise<UsersListResponse>;\n}\n\nexport interface UserClient {\n profile: ProfileResource;\n userInfo: UserInfoResource;\n email: EmailResource;\n users: UsersResource;\n}\n\nexport class PulseIdServer extends PulseIdClient {\n private readonly clientSecret: string;\n private readonly storage: TokenStorage | undefined;\n private clientCredentialsToken: ClientCredentialsToken | null = null;\n\n constructor(config: ServerConfig) {\n if (!config.clientSecret) {\n throw new Error('clientSecret is required for server-side operations');\n }\n super(config);\n this.clientSecret = config.clientSecret;\n this.storage = config.storage;\n }\n\n // ===========================================================================\n // CLIENT CREDENTIALS (Machine-to-Machine)\n // ===========================================================================\n\n /**\n * Get an access token using client credentials grant.\n * This is for server-to-server communication without a user context.\n *\n * The token is cached in memory and automatically refreshed when expired.\n *\n * @param scope - Space-separated scopes to request (e.g., 'users:admin')\n * @returns Access token that can be used for API requests\n *\n * @example\n * ```typescript\n * const token = await server.getClientCredentialsToken('users:admin');\n * const users = await server.getUsers(token.accessToken, { limit: 50 });\n * ```\n */\n async getClientCredentialsToken(scope: string): Promise<ClientCredentialsToken> {\n // Return cached token if valid (with 5 minute buffer)\n const bufferMs = 5 * 60 * 1000;\n if (\n this.clientCredentialsToken &&\n this.clientCredentialsToken.scope === scope &&\n this.clientCredentialsToken.expiresAt.getTime() - bufferMs > Date.now()\n ) {\n return this.clientCredentialsToken;\n }\n\n // Request new token\n const response = await this.http.postForm<{\n access_token: string;\n token_type: string;\n expires_in: number;\n scope: string;\n }>('/token', {\n grant_type: 'client_credentials',\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n scope,\n });\n\n this.clientCredentialsToken = {\n accessToken: response.access_token,\n expiresAt: new Date(Date.now() + response.expires_in * 1000),\n scope: response.scope,\n };\n\n return this.clientCredentialsToken;\n }\n\n /**\n * Get users with admin privileges using client credentials.\n * Returns extended user data including email, creation date, etc.\n *\n * @param options - Query options (limit, offset, search, ids)\n * @returns List of admin user profiles with pagination\n *\n * @example\n * ```typescript\n * const result = await server.getAdminUsers({ limit: 50, search: 'john' });\n * result.data.forEach(user => console.log(user.email));\n * ```\n */\n async getAdminUsers(options?: UsersQueryOptions): Promise<UsersListResponse<AdminUserProfile>> {\n const token = await this.getClientCredentialsToken('users:admin');\n return this.getUsers(token.accessToken, options) as Promise<UsersListResponse<AdminUserProfile>>;\n }\n\n /**\n * Get a single user with admin privileges using client credentials.\n *\n * @param userId - The user's UUID\n * @returns Admin user profile with extended data\n */\n async getAdminUser(userId: string): Promise<AdminUserProfile> {\n const token = await this.getClientCredentialsToken('users:admin');\n return this.getUser(token.accessToken, userId) as Promise<AdminUserProfile>;\n }\n\n /**\n * Get multiple users by ID with admin privileges using client credentials.\n *\n * @param userIds - Array of user UUIDs (max 100)\n * @returns List of admin user profiles\n */\n async getAdminUsersByIds(userIds: string[]): Promise<UsersListResponse<AdminUserProfile>> {\n if (userIds.length === 0) {\n return {\n data: [],\n pagination: { total: 0, limit: 0, offset: 0, hasMore: false },\n };\n }\n const token = await this.getClientCredentialsToken('users:admin');\n return this.getUsersByIds(token.accessToken, userIds) as Promise<UsersListResponse<AdminUserProfile>>;\n }\n\n forUser(userId: string): UserClient {\n if (!this.storage) {\n throw new Error('storage must be configured to use forUser()');\n }\n const storage = this.storage;\n\n return {\n profile: {\n get: () => this.getProfileWithRefresh(storage, userId),\n update: (data: ProfileUpdate) => this.updateProfileWithRefresh(storage, userId, data),\n },\n userInfo: {\n get: () => this.getUserInfoWithRefresh(storage, userId),\n },\n email: {\n resendVerification: () => this.resendVerificationWithRefresh(storage, userId),\n },\n users: {\n list: (options) => this.getUsersWithRefresh(storage, userId, options),\n get: (targetUserId) => this.getUserWithRefresh(storage, userId, targetUserId),\n getByIds: (ids) => this.getUsersByIdsWithRefresh(storage, userId, ids),\n },\n };\n }\n\n async exchangeCode(code: string, redirectUri: string, codeVerifier?: string): Promise<TokenResponse> {\n const data: Record<string, string> = {\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (codeVerifier) {\n data['code_verifier'] = codeVerifier;\n }\n return this.http.postForm<TokenResponse>('/token', data);\n }\n\n async refreshTokens(options: RefreshOptions): Promise<TokenResponse> {\n const data: Record<string, string> = {\n grant_type: 'refresh_token',\n refresh_token: options.refreshToken,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (options.scope) {\n data['scope'] = options.scope;\n }\n return this.http.postForm<TokenResponse>('/token', data);\n }\n\n async revokeToken(token: string, tokenTypeHint?: 'access_token' | 'refresh_token'): Promise<void> {\n const data: Record<string, string> = {\n token,\n client_id: this.config.clientId,\n client_secret: this.clientSecret,\n };\n if (tokenTypeHint) {\n data['token_type_hint'] = tokenTypeHint;\n }\n await this.http.postForm<void>('/revoke', data);\n }\n\n async getProfileWithRefresh(storage: TokenStorage, userId: string): Promise<Profile> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getProfile(tokens.accessToken);\n }\n\n async updateProfileWithRefresh(storage: TokenStorage, userId: string, data: ProfileUpdate): Promise<Profile> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.updateProfile(tokens.accessToken, data);\n }\n\n async getUserInfoWithRefresh(storage: TokenStorage, userId: string): Promise<UserInfo> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getUserInfo(tokens.accessToken);\n }\n\n async resendVerificationWithRefresh(\n storage: TokenStorage,\n userId: string\n ): Promise<{ success: true; message: string }> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.resendVerificationEmail(tokens.accessToken);\n }\n\n async getUsersWithRefresh(\n storage: TokenStorage,\n userId: string,\n options?: UsersQueryOptions\n ): Promise<UsersListResponse> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getUsers(tokens.accessToken, options);\n }\n\n async getUserWithRefresh(\n storage: TokenStorage,\n userId: string,\n targetUserId: string\n ): Promise<PublicUserProfile> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getUser(tokens.accessToken, targetUserId);\n }\n\n async getUsersByIdsWithRefresh(\n storage: TokenStorage,\n userId: string,\n ids: string[]\n ): Promise<UsersListResponse> {\n const tokens = await this.ensureValidTokens(storage, userId);\n return this.getUsersByIds(tokens.accessToken, ids);\n }\n\n private async ensureValidTokens(storage: TokenStorage, userId: string): Promise<StoredTokens> {\n const tokens = await storage.getTokens(userId);\n if (!tokens) {\n throw new PulseIdError('invalid_token', 'No tokens found. User needs to re-authenticate.', 401);\n }\n\n const bufferMs = 5 * 60 * 1000;\n const isExpired = tokens.expiresAt.getTime() - bufferMs < Date.now();\n if (!isExpired) {\n return tokens;\n }\n\n try {\n const newTokens = await this.refreshTokens({ refreshToken: tokens.refreshToken });\n const updatedTokens: StoredTokens = {\n accessToken: newTokens.access_token,\n refreshToken: newTokens.refresh_token ?? tokens.refreshToken,\n expiresAt: new Date(Date.now() + newTokens.expires_in * 1000),\n ...(newTokens.scope ?? tokens.scope ? { scope: newTokens.scope ?? tokens.scope } : {}),\n };\n await storage.setTokens(userId, updatedTokens);\n return updatedTokens;\n } catch {\n await storage.deleteTokens(userId);\n throw new PulseIdError('invalid_token', 'Token refresh failed. User needs to re-authenticate.', 401);\n }\n }\n}\n\nexport type {\n TokenResponse,\n Profile,\n ProfileUpdate,\n PulseIdConfig,\n UserInfo,\n PublicUserProfile,\n AdminUserProfile,\n UsersListResponse,\n UsersQueryOptions,\n Pagination,\n} from './types.js';\nexport { PulseIdError, InvalidTokenError, InsufficientScopeError } from './errors.js';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulseid/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "TypeScript SDK for PULSE ID - Your athlete identity",
|
|
5
5
|
"author": "Patrick Hübl-Neschkudla <patrick@pulserunning.at>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,22 +25,6 @@
|
|
|
25
25
|
"engines": {
|
|
26
26
|
"node": ">=22.0.0"
|
|
27
27
|
},
|
|
28
|
-
"scripts": {
|
|
29
|
-
"build": "tsup",
|
|
30
|
-
"dev": "tsup --watch",
|
|
31
|
-
"test": "vitest run",
|
|
32
|
-
"test:watch": "vitest",
|
|
33
|
-
"test:coverage": "vitest run --coverage",
|
|
34
|
-
"lint": "eslint src --ext .ts",
|
|
35
|
-
"typecheck": "tsc --noEmit",
|
|
36
|
-
"clean": "rm -rf dist",
|
|
37
|
-
"changeset": "changeset",
|
|
38
|
-
"version": "changeset version",
|
|
39
|
-
"release": "pnpm build && changeset publish",
|
|
40
|
-
"prepublishOnly": "npm run build",
|
|
41
|
-
"docs:dev": "cd docs && pnpm dev",
|
|
42
|
-
"docs:build": "cd docs && pnpm build"
|
|
43
|
-
},
|
|
44
28
|
"devDependencies": {
|
|
45
29
|
"@changesets/changelog-github": "0.5.2",
|
|
46
30
|
"@changesets/cli": "2.29.8",
|
|
@@ -48,7 +32,7 @@
|
|
|
48
32
|
"@vitest/coverage-v8": "4.0.18",
|
|
49
33
|
"eslint": "9.39.2",
|
|
50
34
|
"tsup": "8.5.1",
|
|
51
|
-
"typescript": "5.
|
|
35
|
+
"typescript": "5.9.3",
|
|
52
36
|
"vitest": "4.0.18"
|
|
53
37
|
},
|
|
54
38
|
"keywords": [
|
|
@@ -71,5 +55,20 @@
|
|
|
71
55
|
"homepage": "https://github.com/flipace/pulse-id-sdk-ts#readme",
|
|
72
56
|
"directories": {
|
|
73
57
|
"doc": "docs"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsup",
|
|
61
|
+
"dev": "tsup --watch",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"test:watch": "vitest",
|
|
64
|
+
"test:coverage": "vitest run --coverage",
|
|
65
|
+
"lint": "eslint src --ext .ts",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"clean": "rm -rf dist",
|
|
68
|
+
"changeset": "changeset",
|
|
69
|
+
"version": "changeset version",
|
|
70
|
+
"release": "pnpm build && changeset publish",
|
|
71
|
+
"docs:dev": "cd docs && pnpm dev",
|
|
72
|
+
"docs:build": "cd docs && pnpm build"
|
|
74
73
|
}
|
|
75
|
-
}
|
|
74
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/client.ts"],"names":[],"mappings":"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","file":"chunk-BRQ2T53Z.js","sourcesContent":["/**\n * PULSE ID SDK Errors\n *\n * Custom error classes for better error handling.\n */\n\nimport type { ApiErrorResponse, ErrorCode } from './types.js';\n\n/**\n * Base error class for PULSE ID SDK errors.\n */\nexport class PulseIdError extends Error {\n readonly code: ErrorCode;\n readonly statusCode: number;\n\n constructor(code: ErrorCode, message: string, statusCode = 400) {\n super(message);\n this.name = 'PulseIdError';\n this.code = code;\n this.statusCode = statusCode;\n\n // Maintains proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PulseIdError);\n }\n }\n\n /**\n * Create an error from an API response.\n */\n static fromResponse(response: ApiErrorResponse, statusCode: number): PulseIdError {\n const code = mapErrorCode(response.error);\n\n switch (code) {\n case 'invalid_token':\n case 'expired_token':\n return new InvalidTokenError(response.error_description);\n case 'insufficient_scope':\n return new InsufficientScopeError(\n response.error_description,\n response.required_scope\n );\n case 'not_found':\n return new NotFoundError(response.error_description);\n default:\n return new PulseIdError(code, response.error_description, statusCode);\n }\n }\n}\n\n/**\n * Error thrown when the access token is invalid or expired.\n */\nexport class InvalidTokenError extends PulseIdError {\n constructor(message = 'Invalid or expired access token') {\n super('invalid_token', message, 401);\n this.name = 'InvalidTokenError';\n }\n}\n\n/**\n * Error thrown when the access token lacks required scopes.\n */\nexport class InsufficientScopeError extends PulseIdError {\n readonly requiredScope: string | undefined;\n\n constructor(message: string, requiredScope?: string) {\n super('insufficient_scope', message, 403);\n this.name = 'InsufficientScopeError';\n this.requiredScope = requiredScope;\n }\n}\n\n/**\n * Error thrown when a resource is not found.\n */\nexport class NotFoundError extends PulseIdError {\n constructor(message = 'Resource not found') {\n super('not_found', message, 404);\n this.name = 'NotFoundError';\n }\n}\n\n/**\n * Error thrown when a network request fails.\n */\nexport class NetworkError extends PulseIdError {\n readonly cause: Error | undefined;\n\n constructor(message: string, cause?: Error) {\n super('server_error', message, 0);\n this.name = 'NetworkError';\n this.cause = cause;\n }\n}\n\n/**\n * Error thrown when a request times out.\n */\nexport class TimeoutError extends PulseIdError {\n constructor(message = 'Request timed out') {\n super('server_error', message, 0);\n this.name = 'TimeoutError';\n }\n}\n\n/**\n * Map API error codes to SDK error codes.\n */\nfunction mapErrorCode(apiError: string): ErrorCode {\n switch (apiError) {\n case 'invalid_token':\n return 'invalid_token';\n case 'expired_token':\n return 'expired_token';\n case 'insufficient_scope':\n return 'insufficient_scope';\n case 'not_found':\n return 'not_found';\n case 'invalid_request':\n return 'invalid_request';\n default:\n return 'server_error';\n }\n}\n","/**\n * HTTP Client Utilities\n *\n * Internal utilities for making HTTP requests with proper error handling.\n */\n\nimport { NetworkError, PulseIdError, TimeoutError } from './errors.js';\nimport type { ApiErrorResponse, PulseIdConfig } from './types.js';\n\nconst DEFAULT_TIMEOUT = 30_000; // 30 seconds\n\ntype HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';\n\ntype RequestOptions = {\n method: HttpMethod;\n headers?: Record<string, string>;\n body?: unknown;\n timeout?: number;\n};\n\n/**\n * Validate that a URL is a valid HTTPS URL.\n * Prevents SSRF and ensures secure communication.\n */\nfunction validateIssuerUrl(issuer: string): string {\n let url: URL;\n try {\n url = new URL(issuer);\n } catch {\n throw new Error(`Invalid issuer URL: ${issuer}`);\n }\n\n if (url.username || url.password) {\n throw new Error(`Issuer URL must not include credentials: ${issuer}`);\n }\n\n // Only allow HTTPS in production (allow HTTP for localhost in development)\n const isLocalhost =\n url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '::1';\n if (url.protocol !== 'https:' && !isLocalhost) {\n throw new Error(`Issuer URL must use HTTPS: ${issuer}`);\n }\n\n // Remove trailing slash for consistent URL building\n return url.origin + url.pathname.replace(/\\/$/, '');\n}\n\n/**\n * Create a configured HTTP client for the PULSE ID API.\n */\nexport function createHttpClient(config: PulseIdConfig) {\n const baseUrl = validateIssuerUrl(config.issuer);\n const fetchFn = config.fetch ?? globalThis.fetch;\n const timeout = config.timeout ?? DEFAULT_TIMEOUT;\n\n /**\n * Make an HTTP request to the PULSE ID API.\n */\n async function request<T>(path: string, options: RequestOptions): Promise<T> {\n const url = `${baseUrl}${path}`;\n const controller = new AbortController();\n\n // Set up timeout\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, options.timeout ?? timeout);\n\n try {\n const response = await fetchFn(url, {\n method: options.method,\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...options.headers,\n },\n ...(options.body !== undefined && { body: JSON.stringify(options.body) }),\n signal: controller.signal,\n });\n\n // Clear the timeout\n clearTimeout(timeoutId);\n\n // Parse response body\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n if (isJson) {\n const errorBody = (await response.json()) as ApiErrorResponse;\n throw PulseIdError.fromResponse(errorBody, response.status);\n }\n\n const errorText = await response.text();\n throw new PulseIdError(\n 'server_error',\n errorText || `HTTP ${response.status}`,\n response.status\n );\n }\n\n // Return parsed JSON or empty object for 204\n if (response.status === 204 || !isJson) {\n return {} as T;\n }\n\n return (await response.json()) as T;\n } catch (error) {\n clearTimeout(timeoutId);\n\n // Re-throw SDK errors as-is\n if (error instanceof PulseIdError) {\n throw error;\n }\n\n // Handle abort (timeout)\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new TimeoutError(`Request to ${path} timed out after ${timeout}ms`);\n }\n\n // Handle network errors\n if (error instanceof TypeError) {\n throw new NetworkError(`Network request to ${path} failed`, error);\n }\n\n // Unknown error\n throw new NetworkError(\n `Unknown error during request to ${path}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n return {\n /**\n * Make a GET request.\n */\n get<T>(path: string, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'GET', ...(headers && { headers }) });\n },\n\n /**\n * Make a POST request.\n */\n post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'POST', body, ...(headers && { headers }) });\n },\n\n /**\n * Make a PATCH request.\n */\n patch<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'PATCH', body, ...(headers && { headers }) });\n },\n\n /**\n * Make a DELETE request.\n */\n delete<T>(path: string, headers?: Record<string, string>): Promise<T> {\n return request<T>(path, { method: 'DELETE', ...(headers && { headers }) });\n },\n\n /**\n * Make a form-encoded POST request (for OAuth token endpoints).\n */\n async postForm<T>(\n path: string,\n data: Record<string, string>,\n headers?: Record<string, string>\n ): Promise<T> {\n const url = `${baseUrl}${path}`;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n try {\n const response = await fetchFn(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n ...headers,\n },\n body: new URLSearchParams(data).toString(),\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n const contentType = response.headers.get('content-type');\n const isJson = contentType?.includes('application/json');\n\n if (!response.ok) {\n if (isJson) {\n const errorBody = (await response.json()) as ApiErrorResponse;\n throw PulseIdError.fromResponse(errorBody, response.status);\n }\n // Handle non-JSON error responses (e.g., HTML from proxy)\n const errorText = await response.text();\n throw new PulseIdError(\n 'server_error',\n errorText || `HTTP ${response.status}`,\n response.status\n );\n }\n\n return (await response.json()) as T;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof PulseIdError) throw error;\n if (error instanceof DOMException && error.name === 'AbortError') {\n throw new TimeoutError();\n }\n throw new NetworkError(\n `Request to ${path} failed`,\n error instanceof Error ? error : undefined\n );\n }\n },\n };\n}\n\nexport type HttpClient = ReturnType<typeof createHttpClient>;\n","/**\n * PULSE ID Client\n *\n * Client-side SDK for interacting with the PULSE ID API.\n * Use this in browser environments where you have an access token.\n *\n * For server-side usage with refresh token management, use `PulseIdServer` from './server'.\n */\n\nimport { createHttpClient, type HttpClient } from './http.js';\nimport type { Profile, ProfileUpdate, PulseIdConfig, UserInfo } from './types.js';\n\n/**\n * PULSE ID API Client.\n *\n * @example\n * ```typescript\n * const client = new PulseIdClient({\n * issuer: 'https://id.pulserunning.at',\n * clientId: 'your-client-id',\n * });\n *\n * const profile = await client.getProfile(accessToken);\n * console.log(profile.displayName);\n * ```\n */\nexport class PulseIdClient {\n protected readonly http: HttpClient;\n protected readonly config: PulseIdConfig;\n\n constructor(config: PulseIdConfig) {\n this.config = config;\n this.http = createHttpClient(config);\n }\n\n // ===========================================================================\n // PROFILE\n // ===========================================================================\n\n /**\n * Get the authenticated user's profile.\n *\n * The fields returned depend on the scopes granted to the access token:\n * - `profile`: name, avatar, birthday, etc.\n * - `email`: email address and verification status\n * - `address`: address information\n * - `phone`: phone number\n *\n * @param accessToken - A valid access token\n * @returns The user's profile\n *\n * @example\n * ```typescript\n * const profile = await client.getProfile(accessToken);\n * console.log(`Hello, ${profile.displayName}!`);\n * ```\n */\n async getProfile(accessToken: string): Promise<Profile> {\n return this.http.get<Profile>('/api/v1/me', {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n /**\n * Update the authenticated user's profile.\n *\n * Requires the `profile:write` scope.\n *\n * @param accessToken - A valid access token with `profile:write` scope\n * @param data - The profile fields to update\n * @returns The updated profile\n *\n * @example\n * ```typescript\n * const updated = await client.updateProfile(accessToken, {\n * displayName: 'Max Runner',\n * height: 180,\n * });\n * ```\n */\n async updateProfile(accessToken: string, data: ProfileUpdate): Promise<Profile> {\n return this.http.patch<Profile>('/api/v1/me', data, {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n /**\n * Get the authenticated user's OIDC userinfo.\n *\n * Requires the `openid` scope. Additional fields depend on granted scopes.\n *\n * @param accessToken - A valid access token\n * @returns The user's OIDC userinfo\n *\n * @example\n * ```typescript\n * const info = await client.getUserInfo(accessToken);\n * console.log(info.sub);\n * ```\n */\n async getUserInfo(accessToken: string): Promise<UserInfo> {\n return this.http.get<UserInfo>('/userinfo', {\n Authorization: `Bearer ${accessToken}`,\n });\n }\n\n // ===========================================================================\n // UTILITY METHODS\n // ===========================================================================\n\n /**\n * Get the configured issuer URL.\n */\n get issuer(): string {\n return this.config.issuer;\n }\n\n /**\n * Get the configured client ID.\n */\n get clientId(): string {\n return this.config.clientId;\n }\n\n /**\n * Build an authorization URL for the OAuth flow.\n *\n * @param options - Authorization options\n * @returns The authorization URL to redirect the user to\n *\n * @example\n * ```typescript\n * const authUrl = client.buildAuthorizationUrl({\n * redirectUri: 'https://myapp.com/callback',\n * scope: 'openid profile email',\n * state: 'random-state-string',\n * });\n * window.location.href = authUrl;\n * ```\n */\n buildAuthorizationUrl(options: {\n redirectUri: string;\n scope: string;\n state: string;\n nonce?: string;\n codeChallenge: string;\n codeChallengeMethod?: 'S256';\n prompt?: 'none' | 'login' | 'consent' | 'create';\n loginHint?: string;\n }): string {\n if (options.codeChallengeMethod && options.codeChallengeMethod !== 'S256') {\n throw new Error('code_challenge_method must be S256');\n }\n\n if (!options.state) {\n throw new Error('state is required for authorization requests');\n }\n\n if (!options.codeChallenge) {\n throw new Error('code_challenge is required for authorization requests');\n }\n\n const scopes = options.scope.split(' ').filter(Boolean);\n if (scopes.includes('openid') && !options.nonce) {\n throw new Error('nonce is required when requesting openid scope');\n }\n\n const url = new URL(`${this.config.issuer}/authorize`);\n\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', this.config.clientId);\n url.searchParams.set('redirect_uri', options.redirectUri);\n url.searchParams.set('scope', options.scope);\n\n url.searchParams.set('state', options.state);\n if (options.nonce) {\n url.searchParams.set('nonce', options.nonce);\n }\n url.searchParams.set('code_challenge', options.codeChallenge);\n url.searchParams.set('code_challenge_method', options.codeChallengeMethod ?? 'S256');\n if (options.prompt) {\n url.searchParams.set('prompt', options.prompt);\n }\n if (options.loginHint) {\n url.searchParams.set('login_hint', options.loginHint);\n }\n\n return url.toString();\n }\n\n /**\n * Build a logout URL for ending the session.\n *\n * @param options - Logout options\n * @returns The logout URL to redirect the user to\n *\n * @example\n * ```typescript\n * const logoutUrl = client.buildLogoutUrl({\n * idTokenHint: idToken,\n * postLogoutRedirectUri: 'https://myapp.com',\n * });\n * window.location.href = logoutUrl;\n * ```\n */\n buildLogoutUrl(options?: {\n idTokenHint?: string;\n postLogoutRedirectUri?: string;\n state?: string;\n }): string {\n const url = new URL(`${this.config.issuer}/logout`);\n\n if (options?.idTokenHint) {\n url.searchParams.set('id_token_hint', options.idTokenHint);\n }\n if (options?.postLogoutRedirectUri) {\n url.searchParams.set('post_logout_redirect_uri', options.postLogoutRedirectUri);\n }\n if (options?.state) {\n url.searchParams.set('state', options.state);\n }\n\n return url.toString();\n }\n}\n"]}
|