You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/apps/hq-web/src/api.ts

64 lines
2.3 KiB
TypeScript

/** HQ web ↔ hq-server client; token survives reloads via localStorage['hq.token']. */
const TOKEN_KEY = 'hq.token'
const NAME_KEY = 'hq.name'
const ROLE_KEY = 'hq.role'
export function hasSession(): boolean {
return (localStorage.getItem(TOKEN_KEY) ?? '') !== ''
}
export function displayName(): string {
return localStorage.getItem(NAME_KEY) ?? ''
}
/** 'owner' | 'staff' — Modules page renders read-only for staff. */
export function role(): string {
return localStorage.getItem(ROLE_KEY) ?? 'staff'
}
export function clearSession(): void {
localStorage.removeItem(TOKEN_KEY)
localStorage.removeItem(NAME_KEY)
localStorage.removeItem(ROLE_KEY)
}
/**
* Fetch `/api${path}` with the session bearer token. On 401 (outside the login
* call itself) the session is cleared and the app redirects to /login.
*/
export async function apiFetch<T>(path: string, opts?: RequestInit): Promise<T> {
const token = localStorage.getItem(TOKEN_KEY) ?? ''
const res = await fetch(`/api${path}`, {
...opts,
headers: {
'content-type': 'application/json',
...(token !== '' ? { authorization: `Bearer ${token}` } : {}),
...(opts?.headers ?? {}),
},
}).catch(() => undefined)
if (res === undefined) throw new Error('HQ server unreachable — is it running on port 5182?')
if (res.status === 401 && !path.startsWith('/auth/')) {
clearSession()
window.location.hash = '#/login'
throw new Error('Not signed in')
}
const body = (await res.json().catch(() => ({}))) as T & { error?: string }
if (!res.ok) throw new Error(body.error ?? `Server error HTTP ${res.status}`)
return body
}
export async function login(email: string, password: string): Promise<{ displayName: string; role: string }> {
const out = await apiFetch<{ token: string; staff: { id: string; displayName: string; role: string } }>(
'/auth/login',
{ method: 'POST', body: JSON.stringify({ email, password }) },
)
localStorage.setItem(TOKEN_KEY, out.token)
localStorage.setItem(NAME_KEY, out.staff.displayName)
localStorage.setItem(ROLE_KEY, out.staff.role)
return { displayName: out.staff.displayName, role: out.staff.role }
}
export interface EmailStatus { connected: boolean; address?: string; dead: boolean }
export const getEmailStatus = (): Promise<EmailStatus> => apiFetch<EmailStatus>('/email/status')