45 lines
1.7 KiB
JavaScript
45 lines
1.7 KiB
JavaScript
// Fetch wrapper + live event stream.
|
|||
|
|
export const clientId = Math.random().toString(36).slice(2);
|
||
|
|
|
||
|
|
export class ApiError extends Error {
|
||
|
|
constructor(status, message) { super(message); this.status = status; }
|
||
|
|
}
|
||
|
|
|
||
|
|
async function request(method, url, body, opts = {}) {
|
||
|
|
const headers = { 'X-Client-Id': clientId };
|
||
|
|
let payload = body;
|
||
|
|
if (body !== undefined && !(body instanceof FormData) && typeof body !== 'string') {
|
||
|
|
headers['Content-Type'] = 'application/json';
|
||
|
|
payload = JSON.stringify(body);
|
||
|
|
} else if (typeof body === 'string') headers['Content-Type'] = opts.contentType || 'text/plain';
|
||
|
|
const res = await fetch(url, { method, headers, body: payload, credentials: 'same-origin' });
|
||
|
|
if (res.status === 401 && !url.startsWith('/api/auth')) {
|
||
|
|
window.dispatchEvent(new CustomEvent('wb:signed-out'));
|
||
|
|
}
|
||
|
|
const ct = res.headers.get('content-type') || '';
|
||
|
|
const data = ct.includes('json') ? await res.json() : await res.text();
|
||
|
|
if (!res.ok) throw new ApiError(res.status, data?.error || res.statusText);
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const api = {
|
||
|
|
get: (u) => request('GET', u),
|
||
|
|
post: (u, b = {}, o) => request('POST', u, b, o),
|
||
|
|
patch: (u, b = {}) => request('PATCH', u, b),
|
||
|
|
del: (u) => request('DELETE', u),
|
||
|
|
};
|
||
|
|
|
||
|
|
let es = null;
|
||
|
|
const listeners = new Set();
|
||
|
|
export function onEvent(fn) { listeners.add(fn); return () => listeners.delete(fn); }
|
||
|
|
export function connectEvents() {
|
||
|
|
if (es) return;
|
||
|
|
es = new EventSource(`/api/events?client=${clientId}`);
|
||
|
|
es.onmessage = (m) => {
|
||
|
|
let ev; try { ev = JSON.parse(m.data); } catch { return; }
|
||
|
|
for (const fn of listeners) fn(ev);
|
||
|
|
};
|
||
|
|
es.onopen = () => { for (const fn of listeners) fn({ type: 'reconnected' }); };
|
||
|
|
}
|
||
|
|
export function disconnectEvents() { es?.close(); es = null; }
|