const ext = globalThis.browser || globalThis.chrome; const csrfCache = new Map(); function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); const chunkSize = 0x8000; let binary = ''; for (let offset = 0; offset < bytes.length; offset += chunkSize) binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize)); return btoa(binary); } async function getCsrf(serverUrl) { if (csrfCache.has(serverUrl)) return csrfCache.get(serverUrl); const response = await fetch(`${serverUrl}/api/me`, { credentials: 'include', cache: 'no-store' }); if (!response.ok) return ''; const data = await response.json(); const token = data.csrf || ''; csrfCache.set(serverUrl, token); return token; } ext.runtime.onMessage.addListener((msg, _sender, sendResponse) => { if (msg.type === 'api') { (async () => { try { const { serverUrl, path, method = 'GET', body } = msg; const opts = { method, headers: {}, credentials: 'include', cache: 'no-store' }; if (body !== undefined) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(body); } if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) { const csrf = await getCsrf(serverUrl); if (csrf) opts.headers['X-CSRF-Token'] = csrf; } let response = await fetch(`${serverUrl}${path}`, opts); if (response.status === 403 && !['GET', 'HEAD', 'OPTIONS'].includes(method)) { csrfCache.delete(serverUrl); const csrf = await getCsrf(serverUrl); if (csrf) opts.headers['X-CSRF-Token'] = csrf; response = await fetch(`${serverUrl}${path}`, opts); } const contentType = response.headers.get('content-type') || ''; const data = contentType.includes('application/json') ? await response.json() : { error: await response.text() }; sendResponse({ ok: response.ok, status: response.status, data }); } catch (error) { sendResponse({ ok: false, status: 0, error: error.message }); } })(); return true; } if (msg.type === 'fetch-image') { (async () => { try { const response = await fetch(msg.url, { cache: 'no-store', credentials: 'include' }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const blob = await response.blob(); if (!blob.type.startsWith('image/')) throw new Error('Antwort ist kein Bild'); const buffer = await blob.arrayBuffer(); sendResponse({ ok: true, dataUrl: `data:${blob.type};base64,${arrayBufferToBase64(buffer)}`, mime: blob.type, size: blob.size }); } catch (error) { sendResponse({ ok: false, error: error.message }); } })(); return true; } });