// helpers/nats/getNatsConfig.ts - Fetch NATS connection params (url + token) // from the backend, using the same baseUrl already used for /api/tts and /api/stt. /** * Connection parameters returned by `GET /api/nats?sessionId=`. */ export interface NatsConfig { /** WebSocket URL of the NATS server (e.g. wss://nats.hz.slnode.net:8080). */ url: string; /** Bearer token used to authenticate the WebSocket connection. */ token: string; /** * JetStream stream that stores session events. When set, the client consumes * via JetStream instead of core NATS subscribe. */ stream?: string; /** * Optional durable consumer name. When omitted, an ordered ephemeral consumer * filtered to the session subject is used. */ consumer?: string; /** * Subject to subscribe or filter on. Defaults to `sessions.` when omitted. */ subject?: string; } function readStringField( raw: Record, ...keys: string[] ): string | undefined { for (const key of keys) { const value = raw[key]; if (typeof value === 'string' && value.length > 0) { return value; } } return undefined; } function readBooleanField( raw: Record, ...keys: string[] ): boolean | undefined { for (const key of keys) { const value = raw[key]; if (typeof value === 'boolean') { return value; } } return undefined; } /** * Normalizes raw `/api/nats` payloads (camelCase or snake_case) into {@link NatsConfig}. */ export function parseNatsConfig(raw: Record): NatsConfig { const url = readStringField(raw, 'url'); const token = readStringField(raw, 'token'); if (!url || !token) { throw new Error('Invalid response from NATS config service'); } const useJetStream = readBooleanField(raw, 'useJetStream', 'use_jetstream'); const stream = readStringField(raw, 'stream', 'streamName', 'stream_name'); const consumer = readStringField( raw, 'consumer', 'consumerName', 'consumer_name', 'durableName', 'durable_name' ); const subject = readStringField( raw, 'subject', 'filterSubject', 'filter_subject' ); if (useJetStream && !stream) { throw new Error('Invalid response from NATS config service: JetStream enabled but stream missing'); } return { url, token, ...(stream ? { stream } : {}), ...(consumer ? { consumer } : {}), ...(subject ? { subject } : {}), }; } /** * Fetch the NATS connection config for a given session. * * Mirrors the error-handling style of the tts/stt helpers: the backend may * answer with 400 (sessionId missing), 404 (invalid session) or 500 (NATS * config missing). Any non-ok response throws with a descriptive message. * * @param baseUrl Same baseUrl used for `/api/tts` and `/api/stt`. * @param sessionId Current session UUID. * @param signal Optional AbortSignal to cancel the request. */ export async function getNatsConfig( baseUrl: string, sessionId: string, signal?: AbortSignal ): Promise { if (!sessionId) { throw new Error('Missing sessionId for NATS config request'); } const response = await fetch( `${baseUrl}/api/nats?sessionId=${encodeURIComponent(sessionId)}`, { signal } ); if (!response.ok) { const errorData = await response.json().catch(() => ({} as any)); switch (response.status) { case 400: throw new Error( errorData.error || 'NATS config error: missing sessionId' ); case 404: throw new Error( errorData.error || 'NATS config error: invalid session' ); case 500: throw new Error( errorData.error || 'NATS config error: NATS configuration missing' ); default: throw new Error( errorData.error || `NATS config error: ${response.status}` ); } } const data = (await response.json()) as Record; return parseNatsConfig(data); }