import type { AuthProviderAvailability, PasswordResetMode } from "./types"; type CreateAuthClientOptions = { apiUrl: (path: string) => string; authUrl?: (path: string) => string; fetchImpl?: typeof fetch; credentials?: RequestCredentials; defaultOAuthCallbackUrl?: string | (() => string); }; type LoginInput = { email: string; password: string; }; type RegisterInput = LoginInput & { name: string; }; type PasswordResetValidationPayload = { email?: string; mode?: PasswordResetMode; error?: string; }; type JsonErrorPayload = { error?: string; }; async function readJsonError(response: Response, fallback: string): Promise { const payload = (await response.json().catch(() => null)) as JsonErrorPayload | null; return new Error(payload?.error ?? fallback); } export function createAuthClient(options: CreateAuthClientOptions) { const fetchImpl = options.fetchImpl ?? fetch; const authUrl = options.authUrl ?? options.apiUrl; const credentials = options.credentials ?? "include"; function resolveDefaultOAuthCallbackUrl(): string { const configured = options.defaultOAuthCallbackUrl; if (typeof configured === "function") { return configured(); } if (typeof configured === "string" && configured.trim().length > 0) { return configured; } return `${window.location.origin}/chat`; } async function request(path: string, init?: RequestInit): Promise { return fetchImpl(options.apiUrl(path), { ...init, credentials, headers: { "Content-Type": "application/json", ...(init?.headers ?? {}) } }); } return { async getProviders(): Promise { const response = await request("/api/auth/providers"); if (!response.ok) { throw await readJsonError(response, "providers_unavailable"); } return (await response.json()) as AuthProviderAvailability; }, async getCurrentUser(): Promise { const response = await request("/api/me"); if (!response.ok) { throw await readJsonError(response, "Unauthorized"); } const payload = (await response.json()) as { user: TUser }; return payload.user; }, async register(input: RegisterInput): Promise { const response = await request("/api/auth/register", { method: "POST", body: JSON.stringify(input) }); if (!response.ok) { throw await readJsonError(response, "Registration failed"); } }, async login(input: LoginInput): Promise { const response = await request("/api/auth/login", { method: "POST", body: JSON.stringify(input) }); if (!response.ok) { throw await readJsonError(response, "Sign in failed"); } }, async requestPasswordReset(email: string): Promise { const response = await request("/api/auth/password-reset/request", { method: "POST", body: JSON.stringify({ email }) }); if (!response.ok) { throw await readJsonError(response, "Password reset request failed"); } }, async validatePasswordResetToken(token: string): Promise<{ email: string; mode: PasswordResetMode }> { const response = await request(`/api/auth/password-reset/validate?token=${encodeURIComponent(token)}`, { headers: {} }); const payload = (await response.json().catch(() => null)) as PasswordResetValidationPayload | null; if (!response.ok || !payload?.email || (payload.mode !== "reset" && payload.mode !== "create")) { throw new Error(payload?.error ?? "Invalid reset link"); } return { email: payload.email, mode: payload.mode }; }, async confirmPasswordReset(input: { token: string; password: string }): Promise { const response = await request("/api/auth/password-reset/confirm", { method: "POST", body: JSON.stringify(input) }); if (!response.ok) { throw await readJsonError(response, "Invalid reset link"); } }, async logout(): Promise { const response = await request("/api/auth/logout", { method: "POST" }); if (!response.ok) { throw await readJsonError(response, "Logout failed"); } }, async startOAuthSignIn(provider: string, callbackUrl = resolveDefaultOAuthCallbackUrl()): Promise { const response = await fetchImpl(authUrl("/auth/csrf"), { credentials }); if (!response.ok) { throw await readJsonError(response, "Sign in failed"); } const payload = (await response.json()) as { csrfToken?: string }; if (!payload.csrfToken) { throw new Error("Sign in failed"); } const form = document.createElement("form"); form.method = "POST"; form.action = authUrl(`/auth/signin/${provider}`); form.style.display = "none"; const csrfInput = document.createElement("input"); csrfInput.type = "hidden"; csrfInput.name = "csrfToken"; csrfInput.value = payload.csrfToken; form.appendChild(csrfInput); const callbackInput = document.createElement("input"); callbackInput.type = "hidden"; callbackInput.name = "callbackUrl"; callbackInput.value = callbackUrl; form.appendChild(callbackInput); document.body.appendChild(form); form.submit(); } }; }