fix(desktop): Downloads aus der Seite (blob/data) in der App speichern
Tessera CI/CD / Lint & Type Check (push) Successful in 2m38s
Tessera CI/CD / Tests (push) Failing after 1m26s
Tessera CI/CD / Desktop-Pakete bauen (push) Has been skipped
Tessera CI/CD / Build & Publish Images (push) Has been skipped

Der Client reichte jeden Download an den System-Browser weiter. Dateien,
die die Seite selbst erzeugt (blob:/data:, z. B. alle Downloads im
Zertifikat-Manager), kann der Browser nicht abrufen - Windows zeigte
nur "Holen Sie sich eine App, um diesen blob-Link zu oeffnen" (VM 8233).
Solche Downloads speichert die App jetzt selbst (Ordner Downloads) und
meldet Dateiname und Ordner; http/https-Downloads gehen weiter an den
Browser.

Dazu CHANGELOG und Quick-Doku zu 261001-l4q.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-01 15:28:59 +02:00
parent c07b0cfaf0
commit dfc4e9b781
5 changed files with 124 additions and 4 deletions
+87 -4
View File
@@ -939,6 +939,41 @@ fn external_target(url: &tauri::Url) -> Option<String> {
}
}
/// Downloads, die die App selbst speichert statt sie an den System-Browser
/// zu geben: Inhalte, die die Seite im Speicher erzeugt hat (`blob:`,
/// `data:`) -- der Browser kann sie nicht abrufen.
fn saves_in_app(url: &tauri::Url) -> bool {
matches!(url.scheme(), "blob" | "data")
}
/// Titel und Text der Meldung nach einem in der App gespeicherten Download.
fn download_notice(path: Option<&std::path::Path>, success: bool) -> (String, String) {
let name = path
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().into_owned());
let folder = path
.and_then(|p| p.parent())
.and_then(|p| p.file_name())
.map(|n| n.to_string_lossy().into_owned());
match (success, name) {
(true, Some(name)) => (
"Download gespeichert".to_string(),
match folder {
Some(folder) => format!("„{name}“ liegt im Ordner {folder}."),
None => format!("„{name}“ wurde gespeichert."),
},
),
(true, None) => (
"Download gespeichert".to_string(),
"Die Datei wurde gespeichert.".to_string(),
),
(false, _) => (
"Download fehlgeschlagen".to_string(),
"Die Datei konnte nicht gespeichert werden.".to_string(),
),
}
}
/// Hauptfenster nach vorne holen. Ein minimiertes Fenster (z. B. per Win+D)
/// wird zuerst wiederhergestellt.
fn show_main_window(app: &AppHandle) {
@@ -1009,15 +1044,36 @@ pub fn run() {
tauri::webview::NewWindowResponse::Deny
}
})
.on_download(|webview, event| {
if let tauri::webview::DownloadEvent::Requested { url, .. } = event {
.on_download(|webview, event| match event {
// Dateien, die die Seite selbst erzeugt (blob:/data:, z. B.
// Zertifikats-Downloads), kennt der System-Browser nicht --
// Windows meldete „Holen Sie sich eine App, um diesen
// ‚blob‘-Link zu öffnen“ (VM 8233, 01.10.2026). Die speichert
// die App selbst im Ordner Downloads und meldet es danach.
tauri::webview::DownloadEvent::Requested { url, .. } => {
if saves_in_app(&url) {
return true;
}
let _ = webview
.app_handle()
.opener()
.open_url(url.to_string(), None::<&str>);
return false;
false
}
true
tauri::webview::DownloadEvent::Finished { url, path, success } => {
if saves_in_app(&url) {
let (title, body) = download_notice(path.as_deref(), success);
let _ = webview
.app_handle()
.notification()
.builder()
.title(title)
.body(body)
.show();
}
true
}
_ => true,
})
.build()?;
@@ -1500,6 +1556,33 @@ mod tests {
);
}
#[test]
fn saves_in_app_nur_blob_und_data() {
for (url, expected) in [
("blob:http://localhost:3000/0c1d-11", true),
("data:application/x-pem-file;base64,QUJD", true),
("https://alpha.tessera.ctl.de/desktop/download/x.exe", false),
("http://intranet.local/datei.pdf", false),
] {
let parsed = tauri::Url::parse(url).unwrap();
assert_eq!(saves_in_app(&parsed), expected, "{url}");
}
}
#[test]
fn download_notice_nennt_datei_und_ordner() {
let path = std::path::Path::new("/home/tessera/Downloads/www.example.de.crt");
let (title, body) = download_notice(Some(path), true);
assert_eq!(title, "Download gespeichert");
assert_eq!(body, "„www.example.de.crt“ liegt im Ordner Downloads.");
let (title, _) = download_notice(None, false);
assert_eq!(title, "Download fehlgeschlagen");
let (_, body) = download_notice(None, true);
assert_eq!(body, "Die Datei wurde gespeichert.");
}
#[test]
fn external_target_nur_http_und_https() {
let https = tauri::Url::parse("https://www.google.com/search?q=tessera").unwrap();