Skip to main content

max / makenotwork

2.0 KB · 53 lines History Blame Raw
1 // Fetch + CSRF primitives ported from mnw.js:8,399. `postJson`/`getJson` are new
2 // consolidations that replace the ~20 hand-rolled fetch+CSRF+error blocks across
3 // the legacy feature files.
4
5 /**
6 * CSRF header for a manual `fetch()`. Read live on every call — the token
7 * rotates mid-session (e.g. the 2FA cycle), so a snapshot would go stale and
8 * 403. Ported from mnw.js:8.
9 */
10 export function csrfHeaders(): Record<string, string> {
11 const token = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content;
12 return token ? { 'X-CSRF-Token': token } : {};
13 }
14
15 /**
16 * Read the server-supplied error message from a non-2xx fetch `Response`. API
17 * routes return `{"error": "..."}` via the json-error middleware; use this so
18 * users see the real reason instead of a generic "Failed". Ported from
19 * mnw.js:399 (`apiErrorMessage`).
20 */
21 export async function apiError(
22 response: Response | null | undefined,
23 fallback = 'Request failed',
24 ): Promise<string> {
25 if (!response || typeof response.json !== 'function') return fallback;
26 try {
27 const d = (await response.json()) as { error?: unknown } | null;
28 return d && typeof d.error === 'string' && d.error ? d.error : fallback;
29 } catch {
30 return fallback;
31 }
32 }
33
34 /** POST JSON with CSRF; resolve the parsed body or reject with the server's
35 * error message. Replaces the hand-rolled fetch+CSRF+error idiom. */
36 export async function postJson<T>(url: string, body: unknown): Promise<T> {
37 const res = await fetch(url, {
38 method: 'POST',
39 headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
40 body: JSON.stringify(body),
41 credentials: 'same-origin',
42 });
43 if (!res.ok) throw new Error(await apiError(res));
44 return (await res.json()) as T;
45 }
46
47 /** GET JSON; resolve the parsed body or reject with the server's error message. */
48 export async function getJson<T>(url: string): Promise<T> {
49 const res = await fetch(url, { credentials: 'same-origin' });
50 if (!res.ok) throw new Error(await apiError(res));
51 return (await res.json()) as T;
52 }
53