// ClawdDotNet — API-Zugriffsschicht // // Verwaltet das Zugangstoken im sessionStorage und kapselt fetch-Aufrufe. export const TOKEN_SCHLUESSEL = "clawd.token"; export const INSTANZ_SCHLUESSEL = "clawd.instanz"; let token = sessionStorage.getItem(TOKEN_SCHLUESSEL) ?? ""; let aktiveInstanz = sessionStorage.getItem(INSTANZ_SCHLUESSEL) ?? ""; const unberechtigtCallbacks = new Set(); const instanzWechselCallbacks = new Set(); /** * Gibt das aktuell gespeicherte Token zurück. * @returns {string} */ export function holeToken() { return token; } /** * Speichert ein neues Token im Speicher und sessionStorage. * @param {string} neuesToken */ export function setzeToken(neuesToken) { token = (neuesToken || "").trim(); if (token) { sessionStorage.setItem(TOKEN_SCHLUESSEL, token); } else { sessionStorage.removeItem(TOKEN_SCHLUESSEL); } } /** * Löscht das Token und die aktive Instanz (Abmeldung). */ export function loescheToken() { token = ""; sessionStorage.removeItem(TOKEN_SCHLUESSEL); loescheAktiveInstanz(); } /** * Gibt die aktuell ausgewählte Instanz-ID zurück. * @returns {string} */ export function holeAktiveInstanz() { return aktiveInstanz; } /** * Speichert die aktive Instanz im Speicher und sessionStorage. * @param {string} neueInstanzId */ export function setzeAktiveInstanz(neueInstanzId) { const id = (neueInstanzId || "").trim(); if (aktiveInstanz === id) return; aktiveInstanz = id; if (aktiveInstanz) { sessionStorage.setItem(INSTANZ_SCHLUESSEL, aktiveInstanz); } else { sessionStorage.removeItem(INSTANZ_SCHLUESSEL); } for (const cb of instanzWechselCallbacks) { try { cb(aktiveInstanz); } catch (err) { console.error("Fehler im InstanzWechsel-Callback:", err); } } } /** * Entfernt die aktive Instanz. */ export function loescheAktiveInstanz() { setzeAktiveInstanz(""); } /** * Registriert einen Rückruf für Instanzwechsel. * @param {(instanzId: string) => void} callback */ export function beiInstanzWechsel(callback) { instanzWechselCallbacks.add(callback); return () => instanzWechselCallbacks.delete(callback); } /** * Prüft, ob ein Token im Speicher liegt. * @returns {boolean} */ export function hatToken() { return Boolean(token && token.length > 0); } /** * Registriert einen Rückruf für den Fall einer 401-Ablehnung (sofortiger Login-Sprung). * @param {() => void} callback */ export function beiUnberechtigt(callback) { unberechtigtCallbacks.add(callback); return () => unberechtigtCallbacks.delete(callback); } function meldeUnberechtigt() { loescheToken(); for (const cb of unberechtigtCallbacks) { try { cb(); } catch (err) { console.error("Fehler im Unberechtigt-Callback:", err); } } } /** * Führt einen Aufruf gegen die API aus. * Sendet bei gesetzter aktiver Instanz automatisch die X-Instanz-Kopfzeile mit. * @param {string} pfad Pfad relativ zu /api, z.B. "/info" oder "/agents" * @param {RequestInit} [optionen={}] * @returns {Promise} */ export async function api(pfad, optionen = {}) { const kopfzeilen = { ...(optionen.headers ?? {}), Authorization: `Bearer ${token}` }; if (aktiveInstanz && !kopfzeilen["X-Instanz"] && !pfad.startsWith("/verwaltung")) { kopfzeilen["X-Instanz"] = aktiveInstanz; } if (optionen.body && typeof optionen.body === "object" && !(optionen.body instanceof FormData)) { kopfzeilen["Content-Type"] = "application/json"; optionen.body = JSON.stringify(optionen.body); } const antwort = await fetch(`/api${pfad}`, { ...optionen, headers: kopfzeilen }); if (antwort.status === 401) { meldeUnberechtigt(); const fehler = new Error("Kein gültiges Token. Bitte neu anmelden."); fehler.abgelehnt = true; fehler.status = 401; throw fehler; } if (!antwort.ok) { let meldung = `HTTP ${antwort.status}`; try { const rumpf = await antwort.json(); meldung = rumpf?.message ?? rumpf?.meldung ?? rumpf?.fehler ?? rumpf?.detail ?? meldung; } catch { // Wenn kein JSON geliefert wird, bleibt es beim HTTP-Code } const fehler = new Error(meldung); fehler.status = antwort.status; if (antwort.status === 402) fehler.lizenzFehlt = true; if (antwort.status === 409) fehler.instanzKonflikt = true; if (antwort.status === 502) fehler.instanzUnerreichbar = true; throw fehler; } if (antwort.status === 204) { return null; } return antwort.json(); }