// Logik der Einstellungsseite. const $ = (id) => document.getElementById(id); // Passwort ist bewusst NICHT dabei – es wird nie gespeichert, nur zum Testen/Login // transient verwendet (siehe "Verbindung testen"). const FIELDS = ["serverUrl", "organization", "username"]; const CHECKS = ["storeAttachments", "tagOnSuccess"]; function setStatus(msg, kind) { const el = $("status"); el.textContent = msg; el.className = kind || ""; } // Warnt, wenn der Server über unverschlüsseltes http:// läuft – dann ginge das // Passwort im Klartext über die Leitung. Liefert einen anzuhängenden Hinweis. function insecureWarning(url) { return /^http:\/\//i.test(String(url || "").trim()) ? " ⚠ Achtung: http:// ist unverschlüsselt – bitte https:// verwenden." : ""; } // Schnappschuss der auth-relevanten Felder (Server/Org/Benutzer) zum Zeitpunkt // des letzten Speicherns/Ladens. Ändert sich davon etwas, ist ein evtl. noch im // Broker liegender Token ungültig (er gehört zu den alten Daten) -> verwerfen. let authSnapshot = { serverUrl: "", organization: "", username: "" }; const AUTH_FIELDS = ["serverUrl", "organization", "username"]; // Server-URLs nur für den Vergleich vereinheitlichen, damit ein rein kosmetischer // Edit (Schrägstrich am Ende, Groß-/Kleinschreibung) nicht als Wechsel zählt. const normUrl = (u) => String(u || "").trim().replace(/\/+$/, "").toLowerCase(); function snapshotAuth(s) { authSnapshot = { serverUrl: s.serverUrl || "", organization: s.organization || "", username: s.username || "" }; } function authChanged(s) { return AUTH_FIELDS.some((k) => (s[k] || "") !== (authSnapshot[k] || "")); } // Nur ein echter Server-Wechsel entwertet die Schrank-GUIDs (Schrankliste, // Standardziel, gemerkte Ablagedialoge). Org-/Benutzer-Wechsel tut das nicht. function serverChanged(s) { return normUrl(s.serverUrl) !== normUrl(authSnapshot.serverUrl); } /** * Nach dem Speichern aufräumen, wenn sich Zugangsdaten geändert haben: * Token immer verwerfen; bei Server-Wechsel zusätzlich alles wegwerfen, was * Schrank-GUIDs des alten Servers enthält (sonst zeigt der Ablage-Dialog fremde * Archive bzw. legt gegen eine nicht mehr existierende GUID ab). * @returns {Promise} die ggf. bereinigten Einstellungen */ async function invalidateSession(s) { if (!authChanged(s)) { snapshotAuth(s); return s; } const server = serverChanged(s); await browser.runtime .sendMessage({ type: "auth:logout", scope: server ? "server" : "auth" }) .catch(() => {}); let next = s; if (server) { next = await Settings.set({ defaultCabinetId: "", dialogByCabinet: {} }); fillCabinets([], ""); // Dropdowns leeren – die alte Liste gilt nicht mehr } snapshotAuth(next); return next; } async function load() { const s = await Settings.get(); FIELDS.forEach((f) => ($(f).value = s[f] || "")); snapshotAuth(s); CHECKS.forEach((c) => ($(c).checked = !!s[c])); setMailFormat(s.mailFormat === "pdf" ? "pdf" : "eml"); $("mappingUrl").value = s.mappingUrl || ""; loadMapping(s.fieldMapping && Object.keys(s.fieldMapping).length ? s.fieldMapping : DEFAULT_FIELD_MAP); // Schrankliste, falls vorher schon getestet (im Storage gecached) const cached = (await browser.storage.local.get("cabinets")).cabinets || []; fillCabinets(cached, s.defaultCabinetId); // Best-effort: Felder des Standard-Archivs gleich laden – nur wenn schon ein // Sitzungs-Token da ist (sonst wählt der Nutzer das Archiv manuell). if (s.defaultCabinetId && [...$("mapCabinet").options].some((o) => o.value === s.defaultCabinetId)) { $("mapCabinet").value = s.defaultCabinetId; try { const stt = await browser.runtime.sendMessage({ type: "auth:status" }); if (stt && stt.hasToken && stt.token) { Auth.setToken(stt.token); availableFields = (await DocuWare.cabinetFieldNames(s, s.defaultCabinetId)) .filter((n) => n && !/^DW[A-Z]/.test(n)).sort(); refreshFieldSelects(); $("mapFieldStatus").textContent = `${availableFields.length} Felder zur Auswahl geladen.`; } } catch (_) { /* egal – Archiv kann manuell gewählt werden */ } } } function setMailFormat(v) { const el = document.querySelector(`input[name="mailFormat"][value="${v}"]`); if (el) el.checked = true; } function getMailFormat() { const el = document.querySelector('input[name="mailFormat"]:checked'); return el ? el.value : "eml"; } function fillCabinets(cabinets, selectedId) { // Standard-Ziel (Archive + Briefkörbe) const sel = $("defaultCabinet"); sel.innerHTML = ''; const addGroup = (label, list) => { if (!list.length) return; const g = document.createElement("optgroup"); g.label = label; list.forEach((c) => { const o = document.createElement("option"); o.value = c.id; o.textContent = c.name; if (String(c.id) === String(selectedId)) o.selected = true; g.appendChild(o); }); sel.appendChild(g); }; addGroup("Archive", cabinets.filter((c) => !c.isBasket)); addGroup("Briefkörbe", cabinets.filter((c) => c.isBasket)); // Mapping-Helfer: nur Archive (Briefkörbe haben keine Indexfelder). const mc = $("mapCabinet"); const keep = mc.value; mc.innerHTML = ''; cabinets.filter((c) => !c.isBasket).forEach((c) => { const o = document.createElement("option"); o.value = c.id; o.textContent = c.name; mc.appendChild(o); }); if (keep) mc.value = keep; } // Besorgt einen Sitzungs-Token: vorhandenen vom Broker nutzen, sonst mit dem // (transienten) Passwortfeld anmelden. Liefert false, wenn kein Passwort da ist. async function ensureToken() { const pw = $("password").value; // Wurde ein Passwort eingegeben, IMMER frisch anmelden: ein evtl. noch im Broker // liegender Token könnte zu früheren Zugangsdaten/Server gehören und würde sonst // zu „GET /FileCabinets -> HTTP 401" führen (bis zum Thunderbird-Neustart). // Der Broker (background.js) ruft bei auth:logon zuerst Auth.reset() und liest die // gerade gespeicherten Einstellungen neu. if (pw) { const res = await browser.runtime.sendMessage({ type: "auth:logon", password: pw }); if (!res || !res.ok) throw new Error(res && res.error ? res.error : "Anmeldung fehlgeschlagen."); Auth.setToken(res.token); $("password").value = ""; // Passwort nicht behalten return true; } // Kein Passwort getippt: vorhandenen Sitzungs-Token vom Broker übernehmen. const st = await browser.runtime.sendMessage({ type: "auth:status" }); if (st && st.hasToken && st.token) { Auth.setToken(st.token); return true; } return false; } // --- Feld-Mapping-Editor --------------------------------------------------- const CONST_OPT = "__const__"; // Sentinel im Quelle-Dropdown für „Fester Wert" let availableFields = []; // Feldnamen aus dem zuletzt gewählten Archiv // Zerlegt einen Mapping-Wert (Token-String ODER { const } / { token }) in den UI-Zustand. function mapValueState(value) { if (value && typeof value === "object") { if (value.const != null) return { sel: CONST_OPT, konst: String(value.const) }; if (value.token) return { sel: value.token, konst: "" }; } if (typeof value === "string" && value) return { sel: value, konst: "" }; return { sel: "", konst: "" }; } // Vorschläge für die Feldname-Eingaben: Felder des gewählten Archivs, dazu die // Standard-Feldnamen und alles schon Eingetragene. Der Archiv-Teil ist erst nach // Anmeldung + Archivwahl da – ohne die beiden anderen Quellen stünde man vorher // vor einer leeren Liste. function fieldSuggestions() { const used = [...document.querySelectorAll("#mapBody .map-field")].map((i) => i.value); const all = [...(availableFields || []), ...Object.keys(DEFAULT_FIELD_MAP), ...used]; return [...new Set(all.filter(Boolean))].sort(); } // Baut die gemeinsame neu auf (alle Zeilen hängen daran). function refreshFieldSelects() { const dl = $("fieldNameList"); if (!dl) return; dl.innerHTML = ""; fieldSuggestions().forEach((n) => { const o = document.createElement("option"); o.value = n; dl.appendChild(o); }); } function addMapRow(field = "", value = "") { const st = mapValueState(value); const row = document.createElement("div"); row.className = "map-row" + (st.sel === CONST_OPT ? " has-const" : ""); // Links gestapelt: Feldname (Eingabe mit Vorschlägen) über Mail-Attribut. const left = document.createElement("div"); left.className = "map-left"; // Freie Eingabe mit Vorschlagsliste – bewusst KEIN