export const base = '/api/v1/remote'; let csrf = ''; export async function api(path, data, method = data === undefined ? 'GET' : 'POST') { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 15000); try { const response = await fetch(base + path, {method, credentials: 'same-origin', cache: 'no-store', signal: controller.signal, headers: data === undefined ? {} : {'Content-Type': 'application/json', 'X-Remote-CSRF': csrf}, body: data === undefined ? undefined : JSON.stringify(data)}); if (response.redirected || (response.ok && !response.headers.get('Content-Type')?.includes('application/json'))) { throw new Error('Hub sendte ikke det forventede svar. Prøver igen…'); } const result = await response.json(); if (!response.ok) { const error = new Error(typeof result.detail === 'string' ? result.detail : 'Kontrollér oplysningerne og prøv igen'); error.status = response.status; error.retry = Number(response.headers.get('Retry-After') || 0); throw error; } return result; } catch (error) { if (error.status || error.message?.includes('forventede svar')) throw error; throw new Error('Forbindelsen til Hub er afbrudt. Prøver igen…'); } finally { clearTimeout(timeout); } } export async function bootstrap() { const result = await api('/bootstrap'); csrf = result.csrf; return result; } export function element(tag, text, className) { const node = document.createElement(tag); node.textContent = text; if (className) node.className = className; return node; } // getRandomValues also works on HTTP local-network origins. export function requestId() { const bytes = crypto.getRandomValues(new Uint8Array(16)); bytes[6] = (bytes[6] & 0x0f) | 0x40; bytes[8] = (bytes[8] & 0x3f) | 0x80; const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join(''); return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`; }