import { config } from "../config.js"; import type { components } from "../generated/server-api.js"; export type ProductCredential = { authorization: string; projectId: string; expiresAt?: string | null; }; export type AgentAuthContext = components["schemas"]["AgentAuthContextResponse"]; export class ProductServerClient { constructor(private readonly baseUrl = config.TJWATER_SERVER_BASE_URL) {} async authenticateUser(authorization: string): Promise<{ user_id: string }> { let response: Response; try { response = await fetch(new URL("/api/v1/access-context", this.baseUrl), { headers: { Accept: "application/json", Authorization: authorization }, }); } catch { throw new ProductServerClientError(503, "TJWater Server unavailable"); } const parsed = parseJson(await response.text()); if (!response.ok) throw new ProductServerClientError(response.status, "TJWater Server rejected identity verification"); if (!parsed || typeof parsed !== "object" || !("user_id" in parsed) || typeof parsed.user_id !== "string" || !parsed.user_id.trim()) { throw new ProductServerClientError(502, "TJWater Server returned invalid identity"); } return { user_id: parsed.user_id }; } async authenticate(credential: ProductCredential): Promise { const url = new URL("/api/v1/agent-auth-context", this.baseUrl); let response: Response; try { response = await fetch(url, { headers: { Accept: "application/json", Authorization: credential.authorization, "X-Project-Id": credential.projectId, }, }); } catch { throw new ProductServerClientError(503, "TJWater Server unavailable"); } const text = await response.text(); const parsed = parseJson(text); if (!response.ok) { throw new ProductServerClientError( response.status, readErrorMessage(parsed, "TJWater Server rejected identity verification"), ); } if (!parsed || typeof parsed !== "object") { throw new ProductServerClientError(502, "TJWater Server returned invalid JSON"); } return parsed as AgentAuthContext; } } export class ProductServerClientError extends Error { constructor(readonly status: number, message: string) { super(message); } } const parseJson = (text: string): unknown => { if (!text.trim()) return null; try { return JSON.parse(text); } catch { return null; } }; const readErrorMessage = (value: unknown, fallback: string) => { if (!value || typeof value !== "object") return fallback; const record = value as Record; if (typeof record.detail === "string") return record.detail; if (typeof record.message === "string") return record.message; return fallback; };