import { writable } from 'svelte/store'; import { api, type MeResponse } from './api'; interface SessionState { loading: boolean; me: MeResponse | null; error: string | null; } export const session = writable({ loading: true, me: null, error: null, }); export async function loadSession(): Promise { session.update((s) => ({ ...s, loading: true, error: null })); try { const me = await api.me(); session.set({ loading: false, me, error: null }); } catch (err) { const status = (err as { status?: number }).status; if (status === 401) { redirectToLogin(); return; } session.set({ loading: false, me: null, error: err instanceof Error ? err.message : String(err), }); } } /** * Resolve org slug for org-scoped Feishu OAuth (`GET /auth/feishu/:orgSlug`). * Unscoped `/auth/feishu` is disabled unless allowLegacyFeishuOAuth is on. * Path no longer carries tenancy: prefer hostname silo slug, then ?org=. */ export function resolveLoginOrgSlug(): string | null { const q = new URLSearchParams(window.location.search).get('org'); if (q && q.trim() !== '') return q.trim(); // Alpha Silo public host: .educraft.paradigm-edu.net (or educraft-dev) const host = window.location.hostname.toLowerCase(); const m = host.match(/^([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)\.educraft(?:-dev)?\./); if (m?.[1]) return m[1]; // Legacy path while old bookmarks still land briefly before redirect. const path = window.location.pathname.split('/').filter(Boolean); if (path[0] === 'admin' && path[1] === 'org' && path[2]) { return decodeURIComponent(path[2]); } return null; } export function redirectToLogin(): void { if (window.location.pathname === '/admin/login') return; const ret = encodeURIComponent( window.location.pathname + window.location.search + window.location.hash, ); const slug = resolveLoginOrgSlug(); if (slug === null) { // Need an org slug for scoped OAuth — backend login helper can prompt. window.location.href = `/admin/login?returnTo=${ret}`; return; } window.location.href = `/auth/feishu/${encodeURIComponent(slug)}?returnTo=${ret}`; } export async function logout(): Promise { await api.logout(); redirectToLogin(); }