fix(clipboard): route copy actions through Electron

Replace renderer clipboard calls with the validated main-process IPC writer for link names, URLs, package batches, backup keys, diagnostics, error details, and masked account identifiers. Raise the validated payload limit to one MiB so complete link packages copy without truncation while preserving empty and oversized input rejection.
This commit is contained in:
Sucukdeluxe
2026-08-24 00:19:00 +02:00
parent 581736e02b
commit 20be2a9a03
7 changed files with 253 additions and 57 deletions
+24 -41
View File
@@ -60,6 +60,7 @@ import { BackupPassphraseDialog } from "./ui/BackupPassphraseDialog";
import { Dialog } from "./ui/Dialog";
import { Icon } from "./ui/Icon";
import { Toast } from "./ui/Toast";
import { LinkAddressesDialog } from "./ui/LinkAddressesDialog";
import {
buildCollectorViewModel,
type CollectorSourceTab
@@ -4438,7 +4439,7 @@ export function App(): ReactElement {
const onCopyOnlineBackupKey = async (): Promise<void> => {
if (!onlineBackupDialog?.key) return;
try {
await navigator.clipboard.writeText(onlineBackupDialog.key);
if (!(await window.rd.writeClipboardText(onlineBackupDialog.key))) throw new Error("clipboard_write_rejected");
showToast("Online-Schlüssel kopiert", 2200);
} catch {
showToast("Schlüssel konnte nicht kopiert werden", 2600);
@@ -4505,13 +4506,13 @@ export function App(): ReactElement {
cancelLabel: "Schließen",
details: details || undefined,
detailsLabel: "Einträge anzeigen"
});
if (copy && entries.length > 0) {
await navigator.clipboard.writeText(details);
showToast("Fehlerliste kopiert", 2600);
}
} catch (error) {
showToast(`Fehler-Ansicht fehlgeschlagen: ${String(error)}`, 3000);
});
if (copy && entries.length > 0) {
if (!(await window.rd.writeClipboardText(details))) throw new Error("clipboard_write_rejected");
showToast("Fehlerliste kopiert", 2600);
}
} catch (error) {
showToast(String(error).includes("clipboard_write_rejected") ? "Kopieren fehlgeschlagen" : `Fehler-Ansicht fehlgeschlagen: ${String(error)}`, 3000);
}
};
@@ -4605,13 +4606,13 @@ export function App(): ReactElement {
}
};
const onCopyRemoteDiagnosticsCode = async (): Promise<void> => {
const onCopyRemoteDiagnosticsCode = async (): Promise<void> => {
if (!remoteDiag?.code) {
return;
}
try {
await navigator.clipboard.writeText(remoteDiag.code);
showToast("Verbindungscode kopiert", 2200);
}
try {
if (!(await window.rd.writeClipboardText(remoteDiag.code))) throw new Error("clipboard_write_rejected");
showToast("Verbindungscode kopiert", 2200);
} catch {
showToast("Kopieren fehlgeschlagen", 2200);
}
@@ -6635,8 +6636,8 @@ export function App(): ReactElement {
type="button"
title={`${key.masked}\nMaskierte Kennung kopieren`}
onClick={() => {
void navigator.clipboard.writeText(key.masked)
.then(() => showToast("Maskierte Kennung kopiert", 1800))
void window.rd.writeClipboardText(key.masked)
.then((copied) => copied ? showToast("Maskierte Kennung kopiert", 1800) : showToast("Kopieren fehlgeschlagen", 2200))
.catch(() => showToast("Kopieren fehlgeschlagen", 2200));
}}
>
@@ -6688,32 +6689,14 @@ export function App(): ReactElement {
/>
) : null}
{linkPopup ? (
<Dialog actions={null} className="link-popup" onClose={() => setLinkPopup(null)} open size="wide" title="Linkadressen anzeigen">
<p>{linkPopup.title}</p>
<div className="link-popup-list">
{linkPopup.links.map((link, i) => (
<div key={i} className="link-popup-row">
<button aria-label={`${link.name} kopieren`} className="link-popup-name link-popup-click" type="button" title={`${link.name}\nKlicken zum Kopieren`} onClick={() => { void navigator.clipboard.writeText(link.name).then(() => showToast("Name kopiert")).catch(() => showToast("Kopieren fehlgeschlagen")); }}>{link.name}</button>
<button aria-label="Link kopieren" className="link-popup-url link-popup-click" type="button" title={`${link.url}\nKlicken zum Kopieren`} onClick={() => { void navigator.clipboard.writeText(link.url).then(() => showToast("Link kopiert")).catch(() => showToast("Kopieren fehlgeschlagen")); }}>{link.url}</button>
</div>
))}
</div>
<div className="modal-actions">
{linkPopup.isPackage && (
<button className="btn" onClick={() => {
const text = linkPopup.links.map((l) => l.name).join("\n");
void navigator.clipboard.writeText(text).then(() => showToast("Alle Namen kopiert")).catch(() => showToast("Kopieren fehlgeschlagen"));
}}>Alle Namen kopieren</button>
)}
{linkPopup.isPackage && (
<button className="btn" onClick={() => {
const text = linkPopup.links.map((l) => l.url).join("\n");
void navigator.clipboard.writeText(text).then(() => showToast("Alle Links kopiert")).catch(() => showToast("Kopieren fehlgeschlagen"));
}}>Alle Links kopieren</button>
)}
<button className="btn" onClick={() => setLinkPopup(null)}>Schließen</button>
</div>
</Dialog>
<LinkAddressesDialog
isPackage={linkPopup.isPackage}
links={linkPopup.links}
onClose={() => setLinkPopup(null)}
onToast={showToast}
title={linkPopup.title}
writeClipboardText={window.rd.writeClipboardText}
/>
) : null}
</>
)}
+72
View File
@@ -0,0 +1,72 @@
import type { ReactElement } from "react";
import { Dialog } from "./Dialog";
export interface LinkAddressesDialogProps {
title: string;
links: Array<{ name: string; url: string }>;
isPackage: boolean;
onClose: () => void;
writeClipboardText: (text: string) => Promise<boolean>;
onToast: (message: string) => void;
}
export function LinkAddressesDialog({
title,
links,
isPackage,
onClose,
writeClipboardText,
onToast
}: LinkAddressesDialogProps): ReactElement {
const copy = async (text: string, successMessage: string): Promise<void> => {
try {
const copied = await writeClipboardText(text);
onToast(copied === true ? successMessage : "Kopieren fehlgeschlagen");
} catch {
onToast("Kopieren fehlgeschlagen");
}
};
return (
<Dialog actions={null} className="link-popup" onClose={onClose} open size="wide" title="Linkadressen anzeigen">
<p>{title}</p>
<div className="link-popup-list">
{links.map((link, index) => (
<div key={index} className="link-popup-row">
<button
aria-label={`${link.name} kopieren`}
className="link-popup-name link-popup-click"
onClick={() => copy(link.name, "Name kopiert")}
title={`${link.name}\nKlicken zum Kopieren`}
type="button"
>
{link.name}
</button>
<button
aria-label="Link kopieren"
className="link-popup-url link-popup-click"
onClick={() => copy(link.url, "Link kopiert")}
title={`${link.url}\nKlicken zum Kopieren`}
type="button"
>
{link.url}
</button>
</div>
))}
</div>
<div className="modal-actions">
{isPackage ? (
<button className="btn" onClick={() => copy(links.map((link) => link.name).join("\n"), "Alle Namen kopiert")} type="button">
Alle Namen kopieren
</button>
) : null}
{isPackage ? (
<button className="btn" onClick={() => copy(links.map((link) => link.url).join("\n"), "Alle Links kopiert")} type="button">
Alle Links kopieren
</button>
) : null}
<button className="btn" onClick={onClose} type="button">Schließen</button>
</div>
</Dialog>
);
}