fix(desktop): Link-Klicks vor dem Opener-Skript des Clients abfangen
Tessera CI/CD / Lint & Type Check (push) Successful in 53s
Tessera CI/CD / Tests (push) Successful in 1m33s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m13s

Die erste Fassung lauschte auf window und lief damit nach dem von
tauri-plugin-opener eingeschleusten Link-Skript. Dieses ruft bei
target=_blank preventDefault und plugin:opener|open_url auf, was von der
Server-Seite aus nicht freigegeben ist - der Klick verpuffte weiter
(auf VM 8233 per Klick-Protokoll gemessen). Der Helfer lauscht jetzt auf
document: nach Reacts Handlern, vor dem Opener-Skript.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-01 10:20:42 +02:00
parent 7edaf8c00b
commit 5919a55cbf
3 changed files with 41 additions and 13 deletions
@@ -12,3 +12,8 @@ commit: 61a971c
- 4 Tests (desktop-external-links.test.tsx), tsc + biome sauber. CHANGELOG „Unveröffentlicht → Behoben“.
- Reine Web-Aenderung: kein neuer Client noetig, wirkt nach Pull des web-Images.
- Offen: Nachweis auf VM nach alpha-Pull (User).
## Nachtrag (gleicher Tag): erste Fassung wirkte nicht
- Nach alpha-Pull weiter ohne Wirkung. Diagnose per temporaerem Klick-Protokoll (lokaler Stack, VM-Client per portproxy auf localhost:3000): `preventDefault` kam aus `<anonymous>:1:442` = Link-Skript von tauri-plugin-opener (init-iife.js, Listener auf `window`): faengt `target=_blank`-Klicks ab und ruft `plugin:opener|open_url` – von der Server-Seite nicht freigegeben, Klick verpufft. Unser Listener auf `window` lief danach und sah den Klick als verhindert.
- Fix: Listener auf `document` (Bubble) – nach React (Wurzel document), vor dem Opener-Skript. Auf VM nachgewiesen: Favorit oeffnet Edge, im Bearbeiten-Modus nichts (React-onClick verhindert).
- Commit siehe git log; Test „kommt dem Link-Skript des Clients auf window zuvor“.
@@ -56,6 +56,25 @@ describe('DesktopExternalLinks', () => {
expect(openSpy).toHaveBeenCalledWith('https://chatgpt.com/', '_blank', 'noopener,noreferrer');
});
it('mit Cookie: kommt dem Link-Skript des Clients auf window zuvor', async () => {
setCookie();
render(<DesktopExternalLinks />);
await act(async () => {});
// Nachbau von tauri-plugin-opener (init-iife.js): bricht ab, wenn der
// Klick schon verhindert ist, sonst preventDefault + IPC-Aufruf.
const openerSaw: boolean[] = [];
const opener = (e: MouseEvent) => openerSaw.push(e.defaultPrevented);
window.addEventListener('click', opener);
try {
fire(link('https://chatgpt.com/', '_blank'));
} finally {
window.removeEventListener('click', opener);
}
expect(openSpy).toHaveBeenCalledTimes(1);
expect(openerSaw).toEqual([true]);
});
it('mit Cookie: Mittelklick auf _blank-Link oeffnet ebenfalls', async () => {
setCookie();
render(<DesktopExternalLinks />);
@@ -6,16 +6,20 @@ import { useIsDesktopClient } from '@/lib/desktop-client';
/**
* DesktopExternalLinks (quick-261001-cxo) — Links mit `target="_blank"`
* (Favoriten, „In neuem Tab öffnen“ bei XFrame/eigenen Modulen, Quellen im
* Ausschreibungs-Radar) taten im Desktop-Client unter Windows beim Klick
* nichts, `window.open` (Such-Widget) dagegen landet ueber `on_new_window`
* im System-Browser (VM 8233, 01.10.2026: Favorit ohne Wirkung, Suche oeffnet
* Edge). Im Client leitet dieser Helfer solche Klicks deshalb auf
* `window.open` um.
* Ausschreibungs-Radar) taten im Desktop-Client beim Klick nichts, waehrend
* `window.open` (Such-Widget) ueber `on_new_window` im System-Browser landet.
*
* Lauscht auf `window` in der Bubble-Phase, also NACH Reacts Handlern am
* Dokument: ein Klick, den die Seite selbst verhindert (Favoriten im
* Bearbeiten-Modus), bleibt verhindert. Nur http/https -- alles andere
* verwirft der Client ohnehin.
* Ursache (VM 8233, 01.10.2026, gemessen): tauri-plugin-opener schleust ein
* Skript ein, das auf `window` solche Klicks abfaengt, `preventDefault()`
* ruft und `plugin:opener|open_url` aufruft -- von der Server-Seite aus ist
* dieser Aufruf nicht freigegeben, der Klick verpufft. Dieser Helfer kommt
* ihm zuvor und oeffnet per `window.open`; das Opener-Skript sieht den Klick
* dann als verhindert und tut nichts.
*
* Lauscht deshalb auf `document` in der Bubble-Phase: NACH Reacts Handlern
* (Wurzel `document`, frueher registriert) -- ein Klick, den die Seite selbst
* verhindert (Favoriten im Bearbeiten-Modus), bleibt verhindert --, aber VOR
* dem Opener-Skript auf `window`. Nur http/https.
*/
function externalTarget(event: MouseEvent): HTMLAnchorElement | null {
if (event.defaultPrevented) return null;
@@ -45,11 +49,11 @@ export function DesktopExternalLinks() {
window.open(anchor.href, '_blank', 'noopener,noreferrer');
};
window.addEventListener('click', handler);
window.addEventListener('auxclick', handler);
document.addEventListener('click', handler);
document.addEventListener('auxclick', handler);
return () => {
window.removeEventListener('click', handler);
window.removeEventListener('auxclick', handler);
document.removeEventListener('click', handler);
document.removeEventListener('auxclick', handler);
};
}, [isDesktop]);