Optional Domain mit Port und ohne für Sharelinks

This commit is contained in:
2026-09-05 23:59:21 +02:00
parent 3ae0ab0a88
commit 172dd95387
7 changed files with 100 additions and 17 deletions

View File

@@ -14,7 +14,7 @@
import { PaperlessClient, PaperlessError } from "./lib/client.js";
import {
ladeEinstellungen, vollstaendig, sichererName, versionslabel,
titelAusBetreff, t
titelAusBetreff, zugriffsmuster, t
} from "./lib/settings.js";
/**
@@ -90,6 +90,12 @@ function alsDatum(wert) {
return Number.isNaN(d.getTime()) ? undefined : d.toISOString().slice(0, 10);
}
// Beim Laden eine Zeile ausgeben. Fehlt sie in der Konsole, ist das
// Skript gar nicht erst gestartet - und dann kommt bei jedem Aufruf aus
// einem Dialog nur "An unexpected error occurred".
console.info("[paperless] Hintergrundskript geladen",
browser.runtime.getManifest().version);
// Offene Dialoge, damit sie ihre Aufgabe abholen können.
const auftraege = new Map();
let naechsteAuftragsnummer = 1;
@@ -322,7 +328,7 @@ async function client() {
// Fehlersuche beim Netzwerk.
let origin;
try {
origin = new URL(s.baseUrl).origin + "/*";
origin = zugriffsmuster(s.baseUrl);
} catch {
throw new PaperlessError(t("errBadUrl"));
}
@@ -333,7 +339,7 @@ async function client() {
+ `In den Einstellungen erneut auf Speichern klicken - die Abfrage `
+ `kann nur aus einer Benutzeraktion heraus erscheinen.`);
}
return new PaperlessClient(s.baseUrl, s.token);
return new PaperlessClient(s.baseUrl, s.token, s.shareBaseUrl);
}
/**
@@ -347,7 +353,7 @@ async function client() {
async function berechtigungPruefen() {
const s = await ladeEinstellungen();
if (!vollstaendig(s)) return { noetig: false, erteilt: false };
const muster = new URL(s.baseUrl).origin + "/*";
const muster = zugriffsmuster(s.baseUrl);
const erteilt = await browser.permissions.contains({ origins: [muster] });
return { noetig: true, erteilt, muster };
}

View File

@@ -34,7 +34,7 @@ const TASK_POLL = 1500;
export class PaperlessError extends Error {}
export class PaperlessClient {
constructor(baseUrl, token) {
constructor(baseUrl, token, shareBaseUrl = "") {
let parsed;
try {
parsed = new URL(String(baseUrl || "").trim());
@@ -48,6 +48,13 @@ export class PaperlessClient {
}
this.base = String(baseUrl).trim().replace(/\/+$/, "");
this.token = String(token || "").trim();
// Adresse für Freigabelinks. Paperless liefert nur den Slug, die
// vollständige Adresse wird hier zusammengesetzt. Führt der interne
// Zugang über einen abweichenden Port, wäre ein daraus gebauter Link
// für den Empfänger wertlos - deshalb getrennt konfigurierbar.
this.shareBase = String(shareBaseUrl || "").trim().replace(/\/+$/, "")
|| this.base;
}
get origin() {
@@ -313,7 +320,7 @@ export class PaperlessClient {
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (res?.slug) return `${this.base}/share/${res.slug}`;
if (res?.slug) return `${this.shareBase}/share/${res.slug}`;
return null;
} catch (err) {
last = err;

View File

@@ -10,9 +10,23 @@
const VORGABEN = {
baseUrl: "",
token: "",
// Adresse, unter der das Archiv von aussen erreichbar ist.
//
// Wird nur für Freigabelinks verwendet. Paperless liefert in der
// Antwort lediglich den Slug; die vollständige Adresse setzt die
// Erweiterung zusammen. Führt der interne Zugang über einen
// abweichenden Port oder einen anderen Namen, wäre ein daraus gebauter
// Link für den Empfänger wertlos.
//
// Leer = baseUrl verwenden.
shareBaseUrl: "",
versionLabel: "Thunderbird {datum}",
shareDays: 7,
shareTag: "Sharelink",
// Beschriftung der eingefügten Linkblöcke. Leer lassen für keine
// Nummerierung ist nicht vorgesehen - eine Nummer hilft beim Bezugnehmen
// im Fließtext ("siehe Datei 2").
linkBezeichner: "",
// Anhänge, die üblicherweise keine Dokumente sind. Sie werden im
// Auswahldialog vorab abgewählt, lassen sich aber anhaken.
ignorierteTypen: ["image/gif", "image/png", "image/jpeg", "text/vcard",
@@ -34,6 +48,21 @@ export function vollstaendig(s) {
return Boolean(s?.baseUrl) && Boolean(s?.token);
}
/**
* Zugriffsmuster für browser.permissions aus einer Basisadresse.
*
* WICHTIG: Muster dürfen KEINEN Port enthalten. "https://rechner:8443/*"
* ist ungültig, und permissions.request wirft dann eine Ausnahme mit
* einer Meldung, die nichts über die Ursache verrät.
*
* Ein Zugriffsrecht gilt ohnehin für alle Ports desselben Rechners - die
* Einschränkung kostet also nichts.
*/
export function zugriffsmuster(baseUrl) {
const u = new URL(String(baseUrl).trim());
return `${u.protocol}//${u.hostname}/*`;
}
/**
* Beliebige Zeichenkette in einen unbedenklichen Dateinamen überführen.
*

View File

@@ -2,13 +2,13 @@
"manifest_version": 3,
"name": "__MSG_extName__",
"description": "__MSG_extDescription__",
"version": "1.5.1",
"author": "Marco Morath",
"homepage_url": "https://gitea.cloud.mmorath.de/marco.morath/thunderbird-paperless-connector",
"version": "1.6.1",
"author": "Name des Herausgebers",
"homepage_url": "https://example.org/paperless-thunderbird",
"default_locale": "de",
"browser_specific_settings": {
"gecko": {
"id": "webmaster@mmorath.de",
"id": "paperless@example.org",
"strict_min_version": "128.0"
}
},

View File

@@ -2,9 +2,33 @@
* gemeinsam.js - Hilfsmittel für alle Dialogseiten.
*/
/** Ruft die Ereignisseite und wirft bei Fehlern eine Ausnahme. */
/**
* Ruft die Ereignisseite und wirft bei Fehlern eine Ausnahme.
*
* Unterscheidet zwei Fälle, die sonst dieselbe nichtssagende Meldung
* ergeben ("An unexpected error occurred"):
*
* * Die Nachricht erreicht keinen Empfänger - das Hintergrundskript
* ist nicht geladen oder abgestürzt.
* * Der Empfänger meldet einen Fehler zurück.
*/
export async function ruf(befehl, daten = {}) {
const antwort = await browser.runtime.sendMessage({ befehl, ...daten });
let antwort;
try {
antwort = await browser.runtime.sendMessage({ befehl, ...daten });
} catch (err) {
console.error("[paperless] sendMessage", befehl, err);
throw new Error(
`Das Hintergrundskript antwortet nicht (${befehl}).\n`
+ `Ursprüngliche Meldung: ${err?.message || err}\n\n`
+ `Einzelheiten in der Konsole: Extras -> Entwicklerwerkzeuge -> `
+ `Debuggen von Add-ons -> Inspizieren.`);
}
if (antwort === undefined) {
throw new Error(
`Keine Antwort auf ${befehl}. Das Hintergrundskript ist `
+ `vermutlich nicht geladen.`);
}
if (!antwort?.ok) {
throw new Error(antwort?.fehler || `Aufruf ${befehl} fehlgeschlagen`);
}

View File

@@ -18,6 +18,17 @@
Ohne abschließenden Schrägstrich. Nur http und https.
</div>
<label for="shareBaseUrl" data-i18n="fieldShareBaseUrl">
Adresse für Freigabelinks (optional)
</label>
<input type="text" id="shareBaseUrl"
placeholder="https://dms.example.org">
<div class="hinweis" data-i18n="optionsHintShareUrl">
Nur nötig, wenn der interne Zugang über einen abweichenden Port oder
Namen läuft. Freigabelinks werden dann unter dieser Adresse gebaut,
damit sie auch von außen funktionieren. Leer = Basisadresse.
</div>
<label for="token" data-i18n="fieldToken">API-Token</label>
<input type="password" id="token">
<div class="hinweis" data-i18n="optionsHintToken">
@@ -54,6 +65,10 @@
<input type="text" id="shareTag" style="width:160px">
</div>
<div class="hinweis" data-i18n="optionsHintTag">
Tag beim Teilen leer lassen, um keine Kennzeichnung zu setzen.
</div>
<label for="ignorierteKleiner" style="margin-top:8px"
data-i18n="fieldIgnoreSmaller">
Anhänge vorab abwählen unter … Bytes

View File

@@ -7,13 +7,14 @@
* darf, ist eine unnötig grosse Angriffsfläche.
*/
import { ladeEinstellungen, speichereEinstellungen } from "../lib/settings.js";
import { ladeEinstellungen, speichereEinstellungen, zugriffsmuster }
from "../lib/settings.js";
import { uebersetzeSeite, ruf, fehlerAnzeigen, meldungAnzeigen }
from "./gemeinsam.js";
const $ = (id) => document.getElementById(id);
const FELDER = ["baseUrl", "token", "versionLabel", "shareDays", "shareTag",
"ignorierteKleiner"];
const FELDER = ["baseUrl", "shareBaseUrl", "token", "versionLabel",
"shareDays", "shareTag", "ignorierteKleiner"];
document.addEventListener("DOMContentLoaded", async () => {
uebersetzeSeite();
@@ -42,7 +43,7 @@ async function rechtAnzeigen() {
}
let origin;
try {
origin = new URL(url).origin + "/*";
origin = zugriffsmuster(url);
} catch {
ziel.className = "fehler";
ziel.textContent = browser.i18n.getMessage("errBadUrl") || "Ungültige Adresse.";
@@ -58,6 +59,7 @@ async function rechtAnzeigen() {
function gelesen() {
return {
baseUrl: $("baseUrl").value.trim().replace(/\/+$/, ""),
shareBaseUrl: ($("shareBaseUrl")?.value || "").trim().replace(/\/+$/, ""),
token: $("token").value.trim(),
versionLabel: $("versionLabel").value.trim() || "Thunderbird {datum}",
shareDays: Number($("shareDays").value || 0),
@@ -83,7 +85,7 @@ function speichern() {
let origin;
try {
origin = new URL(werte.baseUrl).origin + "/*";
origin = zugriffsmuster(werte.baseUrl);
} catch {
fehlerAnzeigen(browser.i18n.getMessage("errBadUrl")
|| "Ungültige Adresse. Erwartet wird https://rechner oder http://rechner.");