import type { ClientAuthAPI, PairingSessionCreateResult, PendingPairingRecord, RemoteClientCreateResult, RemoteClientPurgeRevokedResult, RemoteClientRecord, RemoteClientRevokeResult, } from '@openchamber/ui/lib/api/types'; import { runtimeFetch } from '@openchamber/ui/lib/runtime-fetch'; const jsonOrNull = async (response: Response): Promise => { return (await response.json().catch(() => null)) as T | null; }; export const createWebClientAuthAPI = (): ClientAuthAPI => ({ async listClients(): Promise { const response = await runtimeFetch('/api/client-auth/clients', { method: 'GET', headers: { Accept: 'application/json' }, }); const payload = await jsonOrNull<{ clients?: RemoteClientRecord[]; error?: string }>(response); if (!response.ok || !payload) { throw new Error(payload?.error || response.statusText || 'Failed to load remote clients'); } return Array.isArray(payload.clients) ? payload.clients : []; }, async createClient(input = {}): Promise { const response = await runtimeFetch('/api/client-auth/clients', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ label: input.label ?? '' }), }); const payload = await jsonOrNull(response); if (!response.ok || !payload?.client || typeof payload.token !== 'string') { throw new Error(payload?.error || response.statusText || 'Failed to create remote client token'); } return payload; }, async createPairingSession(input = {}): Promise { const response = await runtimeFetch('/api/client-auth/pairing/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ label: input.label ?? '', ...(input.allowedClientKinds ? { allowedClientKinds: input.allowedClientKinds } : {}), ...(input.serverUrl ? { serverUrl: input.serverUrl } : {}), ...(typeof input.includeRelay === 'boolean' ? { includeRelay: input.includeRelay } : {}), ...(typeof input.includeDirect === 'boolean' ? { includeDirect: input.includeDirect } : {}), }), }); const payload = await jsonOrNull(response); if (!response.ok || typeof payload?.pairing?.secret !== 'string' || !payload?.server) { throw new Error(payload?.error || response.statusText || 'Failed to create pairing session'); } return payload; }, async listPendingPairings(): Promise { const response = await runtimeFetch('/api/client-auth/pairing/sessions', { method: 'GET', headers: { Accept: 'application/json' }, }); const payload = await jsonOrNull<{ pending?: PendingPairingRecord[]; error?: string }>(response); if (!response.ok || !payload) { throw new Error(payload?.error || response.statusText || 'Failed to load pending pairings'); } return Array.isArray(payload.pending) ? payload.pending : []; }, async getPairingTransports(): Promise<{ local: string | null; lan: string | null; relayAvailable: boolean }> { const response = await runtimeFetch('/api/client-auth/pairing/transports', { method: 'GET', headers: { Accept: 'application/json' }, }); const payload = await jsonOrNull<{ local?: string | null; lan?: string | null; relayAvailable?: boolean; error?: string }>(response); if (!response.ok || !payload) { throw new Error(payload?.error || response.statusText || 'Failed to load pairing transports'); } return { local: payload.local ?? null, lan: payload.lan ?? null, relayAvailable: payload.relayAvailable !== false }; }, async cancelPairing(id: string): Promise<{ cancelled: boolean }> { const response = await runtimeFetch(`/api/client-auth/pairing/sessions/${encodeURIComponent(id)}`, { method: 'DELETE', headers: { Accept: 'application/json' }, }); const payload = await jsonOrNull<{ cancelled?: boolean; error?: string }>(response); if (!response.ok || !payload) { throw new Error(payload?.error || response.statusText || 'Failed to cancel pairing'); } return { cancelled: payload.cancelled === true }; }, async revokeClient(id: string): Promise { const response = await runtimeFetch(`/api/client-auth/clients/${encodeURIComponent(id)}`, { method: 'DELETE', headers: { Accept: 'application/json' }, }); const payload = await jsonOrNull(response); if (!response.ok || !payload) { throw new Error(payload?.error || response.statusText || 'Failed to revoke remote client'); } return payload; }, async purgeRevokedClients(): Promise { const response = await runtimeFetch('/api/client-auth/clients', { method: 'DELETE', headers: { Accept: 'application/json' }, }); const payload = await jsonOrNull(response); if (!response.ok || !payload) { throw new Error(payload?.error || response.statusText || 'Failed to clear revoked clients'); } return payload; }, });