import type { AuthApi, BlobsApi, PluginsApi, ProvidersApi, SharingApi, StorageApi, TurnsApi, } from '@scn-chat/server/api-types' import { type ClientResponse, hc } from 'hono/client' import type { SuccessStatusCode } from 'hono/utils/http-status' import { errorMessage } from './lib/response.ts' /** Clients for calling the hono server's /api routes. **/ export const api = { auth: hc('/api'), chats: hc('/api'), turns: hc('/api'), providers: hc('/api'), plugins: hc('/api/plugins'), blobs: hc('/api'), sharing: hc('/api'), } /** Build the request options for a JSON body with api calls. */ export const json = (body: unknown) => ({ init: { body: JSON.stringify(body), headers: { 'content-type': 'application/json' } }, }) const unauthorized = new Set<() => void>() /** Call a listener whenever a request finds the session has ended. */ export function onUnauthorized(listener: () => void): () => void { unauthorized.add(listener) return () => unauthorized.delete(listener) } export class ApiError extends Error { readonly status: number constructor(status: number, message: string) { super(message) this.name = 'ApiError' this.status = status } } /** The body of the successful responses among a route's possible responses. */ type SuccessBody = R extends ClientResponse ? S extends SuccessStatusCode ? T : never : never /** Read a response's JSON. Throws ApiError on failure. */ export async function read>( response: Promise, ): Promise> { const res = await response if (res.status === 401) for (const listener of unauthorized) listener() if (!res.ok) throw new ApiError(res.status, await errorMessage(res)) return (await res.json()) as SuccessBody }