// Platform Shell API client. Student identity is pilot-grade (roster pick-your-name, no // password) — see docs/PHASE2A_GATEWAY_AND_STUDENT_FLOW.md "Student identity limitation." // student_id lives in localStorage only, never a cookie, never sent anywhere except as the // X-Student-Id header on these specific requests. const STORAGE_KEY = 'rootnode_student' export function getStoredStudent() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null') } catch { return null } } export function storeStudent(student) { localStorage.setItem(STORAGE_KEY, JSON.stringify(student)) } export function clearStudent() { localStorage.removeItem(STORAGE_KEY) } function authHeaders() { const student = getStoredStudent() // Prevents accidental anonymous progress writes (Phase 2A requirement): every call that // needs identity throws instead of silently going out unauthenticated. if (!student?.student_id) throw new Error('No student session') return { 'X-Student-Id': student.student_id } } async function json(res) { if (!res.ok) { const body = await res.json().catch(() => ({})) throw new Error(body.message || body.error || `Request failed (${res.status})`) } return res.json() } export const api = { cohorts: () => fetch('/api/roster/cohorts').then(json), roster: (cohort) => fetch(`/api/roster?cohort=${encodeURIComponent(cohort)}`).then(json), login: (student_id) => fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ student_id }), }).then(json), curriculumPath: () => fetch('/api/curriculum/path', { headers: authHeaders() }).then(json), environmentStatus: () => fetch('/api/environment/status', { headers: authHeaders() }).then(json), launchEnvironment: () => fetch('/api/environment/launch', { method: 'POST', headers: authHeaders() }).then(json), resetEnvironment: () => fetch('/api/environment/reset', { method: 'POST', headers: authHeaders() }).then(json), }