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.
64 lines
2.3 KiB
TypeScript
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')
|