thunderbird2docuware/background/background.js
sylyx e201e6c654 Ein Ablagefenster für Mehrfachauswahl + Sitzungs-/Retry-Fixes
Mehrfachauswahl öffnet jetzt EIN Ablagefenster (?messageIds=1,2,3) statt eines
Fensters je Nachricht. Der dafür schon vorhandene, bisher unerreichbare Multi-Pfad
in dialog.js hatte drei Fehler, die dabei aufgeschlagen wären:
- Der Anhang-Haken wurde aus der ERSTEN Mail abgeleitet: hatte die keine Anhänge,
  fielen die Anhänge aller übrigen Mails still weg.
- Die Anhangsliste zeigte die Anhänge von Mail 1 mit wirkungslosen Haken.
- Der „bereits abgelegt"-Banner galt nur für Mail 1, sah aber nach einer Aussage
  über die ganze Auswahl aus.

Weiter:
- Server-/Zugangsdaten-Wechsel verwirft jetzt auch die serverspezifischen Caches.
  auth:logout leert zusätzlich ARCHIVED_CACHE; mit scope:"server" fliegen die
  gecachte Schrankliste sowie defaultCabinetId/dialogByCabinet raus, weil deren
  GUIDs nur auf dem alten Server existieren.
- Retry nach einem Teilfehler lud bereits übertragene Teile erneut hoch und
  erzeugte Dubletten. Jeder Teil (eml/pdf/Anhang) wird nun je Ziel einmal
  hochgeladen; dieselbe Mail lässt sich dadurch auch bewusst in ein zweites
  Archiv ablegen.
- ARCHIVED_CACHE wächst nicht mehr unbegrenzt (500 Einträge, ältester fliegt).
- HTTP 401/403 liefern statt des nackten Statuscodes eine verständliche Meldung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:16:35 +02:00

187 lines
7.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Hintergrund-Skript: Button + Kontextmenü, öffnet den Ablage-Dialog.
// Zusätzlich Auth-Broker: hält den OAuth-Token nur im Speicher dieser TB-Sitzung
// (siehe auth.js). Das Passwort wird einmalig zum Login durchgereicht, daraus ein
// Token geholt und sofort verworfen – es wird NIE gespeichert.
const DIALOG_URL = "dialog/dialog.html";
// Auth-Broker: Dialog-/Optionsfenster fragen hier den Sitzungs-Token an bzw.
// melden sich mit dem Passwort an. So muss das Passwort nur 1x pro Sitzung getippt
// werden und liegt nirgends auf der Platte.
browser.runtime.onMessage.addListener((msg) => {
if (!msg || !msg.type) return undefined;
if (msg.type === "auth:status") {
return Promise.resolve({ hasToken: Auth.hasValidToken(), token: Auth.currentToken() });
}
if (msg.type === "auth:logon") {
return (async () => {
try {
const settings = await Settings.get();
if (!settings.serverUrl) throw new Error("Keine DocuWare-Server-URL konfiguriert.");
Auth.reset();
const r = await Auth.logon({ ...settings, password: msg.password || "" });
// Passwort wird hier verworfen – nur der Token bleibt im Speicher.
return { ok: true, mode: r.mode, token: Auth.currentToken() };
} catch (e) {
return { ok: false, error: String((e && e.message) || e) };
}
})();
}
// Sitzung verwerfen. Immer: Token + Badge-Cache (der Cache ist serverspezifisch
// und würde sonst ein ✓ aus der alten Verbindung weiterzeigen).
// scope:"server" = die Server-URL hat sich geändert -> zusätzlich die gecachte
// Schrankliste wegwerfen, denn deren GUIDs gehören zum alten Server.
if (msg.type === "auth:logout") {
Auth.reset();
ARCHIVED_CACHE.clear();
if (msg.scope === "server") {
return browser.storage.local
.remove("cabinets")
.catch(() => {})
.then(() => ({ ok: true }));
}
return Promise.resolve({ ok: true });
}
// Dialog meldet eine erfolgreiche Ablage -> Badge-Cache aktualisieren, damit
// die Mail beim nächsten Anzeigen sofort als „abgelegt" erscheint.
if (msg.type === "archive:done") {
const mid = DocuWare.normalizeMessageId(msg.rfcMessageId);
if (mid && msg.cabinetId) {
cacheArchived(`${mid}|${msg.cabinetId}`, { found: true, docId: msg.docId || null });
}
return Promise.resolve({ ok: true });
}
return undefined;
});
// --- „Bereits abgelegt"-Badge am Nachrichten-Button ------------------------
// Beim Anzeigen einer Mail im Standard-Schrank nach der Message-ID suchen und das
// Ergebnis als Badge (✓) am messageDisplayAction-Button zeigen. Pro Message-ID +
// Schrank wird das Ergebnis im Speicher gecacht (eine Suche pro Mail).
const ARCHIVED_CACHE = new Map(); // "messageId|cabinetId" -> { found, docId }
const ARCHIVED_CACHE_MAX = 500; // Obergrenze: Thunderbird-Sitzungen laufen wochenlang
// Schreibt in den Badge-Cache und hält ihn klein (ältester Eintrag fliegt raus –
// Map-Iteration ist Einfügereihenfolge).
function cacheArchived(key, info) {
if (ARCHIVED_CACHE.size >= ARCHIVED_CACHE_MAX && !ARCHIVED_CACHE.has(key)) {
ARCHIVED_CACHE.delete(ARCHIVED_CACHE.keys().next().value);
}
ARCHIVED_CACHE.set(key, info);
}
async function rfcMessageIdOf(messageId) {
try {
const full = await browser.messages.getFull(messageId);
const h = full && full.headers && full.headers["message-id"];
if (Array.isArray(h) && h.length) return String(h[0]).trim();
} catch (_) {}
return "";
}
function setBadge(tabId, found) {
try {
browser.messageDisplayAction.setBadgeText({ tabId, text: found ? "✓" : "" });
browser.messageDisplayAction.setTitle({
tabId,
title: found
? "Bereits in DocuWare abgelegt – klicken zum Prüfen/Ablegen"
: "In DocuWare ablegen",
});
if (found && browser.messageDisplayAction.setBadgeBackgroundColor) {
browser.messageDisplayAction.setBadgeBackgroundColor({ tabId, color: "#0e8a8a" });
}
} catch (_) {}
}
async function updateArchivedBadge(tab, message) {
const tabId = tab.id;
try {
if (!Auth.hasValidToken()) return setBadge(tabId, false); // ohne Login keine Suche
const settings = await Settings.get();
if (!settings.serverUrl || !settings.defaultCabinetId) return setBadge(tabId, false);
const mid = DocuWare.normalizeMessageId(await rfcMessageIdOf(message.id));
if (!mid) return setBadge(tabId, false);
const key = `${mid}|${settings.defaultCabinetId}`;
let info = ARCHIVED_CACHE.get(key);
if (!info) {
const hit = await DocuWare.findByMessageId(settings, settings.defaultCabinetId, mid).catch(
() => null
);
info = { found: !!hit, docId: hit ? hit.id : null };
cacheArchived(key, info);
}
setBadge(tabId, info.found);
} catch (_) {
setBadge(tabId, false);
}
}
browser.messageDisplay.onMessagesDisplayed.addListener((tab, displayed) => {
const msgs = Array.isArray(displayed) ? displayed : (displayed && displayed.messages) || [];
if (msgs.length === 1) updateArchivedBadge(tab, msgs[0]);
else setBadge(tab.id, false); // bei Mehrfachauswahl keine eindeutige Aussage
});
function openDialog(messageIds) {
const ids = (Array.isArray(messageIds) ? messageIds : [messageIds])
.map((id) => parseInt(id, 10))
.filter((n) => !isNaN(n));
if (!ids.length) return;
// EIN Ablagefenster für die gesamte Auswahl – dialog.js arbeitet die Liste
// nacheinander ab (Fortschritt „Mail 3/20"). Früher wurde pro Nachricht ein
// eigenes Fenster geöffnet, was bei größerer Auswahl unbrauchbar war.
const url = browser.runtime.getURL(`${DIALOG_URL}?messageIds=${ids.join(",")}`);
browser.windows.create({ url, type: "popup", width: 720, height: 820, left: 100, top: 80 });
}
// Button in der geöffneten Nachricht
browser.messageDisplayAction.onClicked.addListener(async (tab) => {
const msg = await browser.messageDisplay.getDisplayedMessage(tab.id);
if (msg) openDialog([msg.id]);
});
// Kontextmenü-Eintrag in der Nachrichtenliste
browser.menus.create({
id: "docuware-archive",
title: "In DocuWare ablegen",
contexts: ["message_list"],
});
browser.menus.onClicked.addListener((info) => {
if (info.menuItemId !== "docuware-archive") return;
const msgs = info.selectedMessages && info.selectedMessages.messages;
if (msgs && msgs.length > 0) openDialog(msgs.map((m) => m.id));
});
// Tastenkürzel (Strg+Alt+I): angezeigte bzw. alle markierten Nachrichten ablegen.
browser.commands.onCommand.addListener(async (command) => {
if (command !== "open-archive-dialog") return;
const ids = await currentMessageIds();
if (ids.length) openDialog(ids);
});
// Ermittelt die "aktiven" Nachrichten: erst die im Reader angezeigte, sonst
// alle im Nachrichtenlisten-Tab markierten.
async function currentMessageIds() {
try {
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
if (tab) {
const shown = await browser.messageDisplay
.getDisplayedMessage(tab.id)
.catch(() => null);
if (shown) return [shown.id];
}
const sel = await browser.mailTabs.getSelectedMessages().catch(() => null);
if (sel && sel.messages && sel.messages.length) return sel.messages.map((m) => m.id);
} catch (_) {}
return [];
}