2026-04-06 11:43:22 +02:00
|
|
|
const BASE = '/api/experience';
|
|
|
|
|
|
|
|
|
|
async function request(method: string, path: string, body?: unknown): Promise<Response> {
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 00:21:07 +02:00
|
|
|
// --- Shared ---
|
|
|
|
|
|
|
|
|
|
export async function identifyUser(verificationId: string): Promise<void> {
|
|
|
|
|
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<string> {
|
|
|
|
|
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 ---
|
|
|
|
|
|
2026-04-06 11:43:22 +02:00
|
|
|
export async function initInteraction(): Promise<void> {
|
|
|
|
|
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})`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 00:21:07 +02:00
|
|
|
function detectIdentifierType(input: string): 'email' | 'username' {
|
|
|
|
|
return input.includes('@') ? 'email' : 'username';
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 11:43:22 +02:00
|
|
|
export async function verifyPassword(
|
2026-04-25 00:21:07 +02:00
|
|
|
identifier: string,
|
2026-04-06 11:43:22 +02:00
|
|
|
password: string
|
|
|
|
|
): Promise<string> {
|
2026-04-25 00:21:07 +02:00
|
|
|
const type = detectIdentifierType(identifier);
|
2026-04-06 11:43:22 +02:00
|
|
|
const res = await request('POST', '/verification/password', {
|
2026-04-25 00:21:07 +02:00
|
|
|
identifier: { type, value: identifier },
|
2026-04-06 11:43:22 +02:00
|
|
|
password,
|
|
|
|
|
});
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
const err = await res.json().catch(() => ({}));
|
|
|
|
|
if (res.status === 422) {
|
2026-04-25 00:21:07 +02:00
|
|
|
throw new Error('Invalid credentials');
|
2026-04-06 11:43:22 +02:00
|
|
|
}
|
|
|
|
|
throw new Error(err.message || `Authentication failed (${res.status})`);
|
|
|
|
|
}
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
return data.verificationId;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 00:21:07 +02:00
|
|
|
export async function signIn(identifier: string, password: string): Promise<string> {
|
|
|
|
|
await initInteraction();
|
|
|
|
|
const verificationId = await verifyPassword(identifier, password);
|
|
|
|
|
await identifyUser(verificationId);
|
|
|
|
|
return submitInteraction();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Registration ---
|
|
|
|
|
|
|
|
|
|
export async function initRegistration(): Promise<void> {
|
|
|
|
|
const res = await request('PUT', '', { interactionEvent: 'Register' });
|
2026-04-06 11:43:22 +02:00
|
|
|
if (!res.ok) {
|
|
|
|
|
const err = await res.json().catch(() => ({}));
|
2026-04-25 00:21:07 +02:00
|
|
|
throw new Error(err.message || `Failed to initialize registration (${res.status})`);
|
2026-04-06 11:43:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 00:21:07 +02:00
|
|
|
export async function sendVerificationCode(email: string): Promise<string> {
|
|
|
|
|
const res = await request('POST', '/verification/verification-code', {
|
|
|
|
|
identifier: { type: 'email', value: email },
|
|
|
|
|
interactionEvent: 'Register',
|
|
|
|
|
});
|
2026-04-06 11:43:22 +02:00
|
|
|
if (!res.ok) {
|
|
|
|
|
const err = await res.json().catch(() => ({}));
|
2026-04-25 00:21:07 +02:00
|
|
|
if (res.status === 422) {
|
|
|
|
|
throw new Error('This email is already registered');
|
|
|
|
|
}
|
|
|
|
|
throw new Error(err.message || `Failed to send verification code (${res.status})`);
|
2026-04-06 11:43:22 +02:00
|
|
|
}
|
|
|
|
|
const data = await res.json();
|
2026-04-25 00:21:07 +02:00
|
|
|
return data.verificationId;
|
2026-04-06 11:43:22 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-25 00:21:07 +02:00
|
|
|
export async function verifyCode(
|
|
|
|
|
email: string,
|
|
|
|
|
verificationId: string,
|
|
|
|
|
code: string
|
|
|
|
|
): Promise<string> {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<string> {
|
|
|
|
|
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<string> {
|
|
|
|
|
const verifiedId = await verifyCode(email, verificationId, code);
|
|
|
|
|
await addProfile('password', password);
|
|
|
|
|
await identifyUser(verifiedId);
|
2026-04-06 11:43:22 +02:00
|
|
|
return submitInteraction();
|
|
|
|
|
}
|