const BASE = '/api/experience'; async function request(method: string, path: string, body?: unknown): Promise { const res = await fetch(`${BASE}${path}`, { method, headers: body ? { 'Content-Type': 'application/json' } : undefined, body: body ? JSON.stringify(body) : undefined, credentials: 'same-origin', }); return res; } // --- Shared --- export async function identifyUser(verificationId: string): Promise { const res = await request('POST', '/identification', { verificationId }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.message || `Identification failed (${res.status})`); } } export async function submitInteraction(): Promise { const res = await request('POST', '/submit'); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.message || `Submit failed (${res.status})`); } const data = await res.json(); return data.redirectTo; } // --- Sign-in --- export async function initInteraction(): Promise { const res = await request('PUT', '', { interactionEvent: 'SignIn' }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.message || `Failed to initialize sign-in (${res.status})`); } } function detectIdentifierType(input: string): 'email' | 'username' { return input.includes('@') ? 'email' : 'username'; } export async function verifyPassword( identifier: string, password: string ): Promise { const type = detectIdentifierType(identifier); const res = await request('POST', '/verification/password', { identifier: { type, value: identifier }, password, }); if (!res.ok) { const err = await res.json().catch(() => ({})); if (res.status === 422) { throw new Error('Invalid credentials'); } throw new Error(err.message || `Authentication failed (${res.status})`); } const data = await res.json(); return data.verificationId; } export async function signIn(identifier: string, password: string): Promise { await initInteraction(); const verificationId = await verifyPassword(identifier, password); await identifyUser(verificationId); return submitInteraction(); } // --- Registration --- export async function initRegistration(): Promise { const res = await request('PUT', '', { interactionEvent: 'Register' }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.message || `Failed to initialize registration (${res.status})`); } } export async function sendVerificationCode(email: string): Promise { const res = await request('POST', '/verification/verification-code', { identifier: { type: 'email', value: email }, interactionEvent: 'Register', }); if (!res.ok) { const err = await res.json().catch(() => ({})); if (res.status === 422) { throw new Error('This email is already registered'); } throw new Error(err.message || `Failed to send verification code (${res.status})`); } const data = await res.json(); return data.verificationId; } export async function verifyCode( email: string, verificationId: string, code: string ): Promise { const res = await request('POST', '/verification/verification-code/verify', { identifier: { type: 'email', value: email }, verificationId, code, }); if (!res.ok) { const err = await res.json().catch(() => ({})); if (res.status === 422) { throw new Error('Invalid or expired verification code'); } throw new Error(err.message || `Verification failed (${res.status})`); } const data = await res.json(); return data.verificationId; } export async function addProfile(type: string, value: string): Promise { const res = await request('POST', '/profile', { type, value }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.message || `Failed to update profile (${res.status})`); } } /** Phase 1: init registration + send verification email. Returns verificationId for phase 2. */ export async function startRegistration(email: string): Promise { await initRegistration(); return sendVerificationCode(email); } /** Phase 2: verify code, set password, create user, submit. Returns redirect URL. */ export async function completeRegistration( email: string, password: string, verificationId: string, code: string ): Promise { const verifiedId = await verifyCode(email, verificationId, code); await addProfile('password', password); await identifyUser(verifiedId); return submitInteraction(); }