Files
waters-boards/server/public/js/api.js
T
christianandClaude Opus 5.5 fcf3805626
Deploy Waters Boards to NAS / deploy (push) Canceled after 0s
Waters Boards: self-hosted work management to replace monday.com
Boards with groups, items, subitems and 18 column types; table, kanban,
calendar, timeline, chart and files views; updates with @mentions,
replies and attachments; inbox, My work, search, automations; Google
sign-in restricted to company domains; live updates over SSE; a
monday.com API importer; phone layout and installable PWA; Docker +
Gitea Actions deploy to the NAS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:34:47 -04:00

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; }