# Swyx Tab Bridge — Firefox-Add-on Verwaltet Firefox-Tabs über die WebSocket-Verbindung zu SwyxTray: Tabs auslesen, öffnen und schließen. ## Rollenverteilung Ein Browser-Add-on kann **keinen** WebSocket-Server betreiben — es kann nur Client sein. Das Add-on **verbindet sich** daher zu SwyxTray (Default `ws://127.0.0.1:17655`) und beantwortet die dort eintreffenden Aufträge. ``` [ SwyxTray, Port 17655 ] <--- verbindet sich --- [ Firefox-Add-on: WS-Client ] | {"type":"tab","id":1,"action":"list"} -> <- {"cmd":"tabresult","id":1,"ok":true,"tabs":[…]} ``` Zuständig ist das Add-on **ausschließlich für Tab-Nachrichten** (`"type":"tab"`). Alles andere auf der Verbindung — Telefonie-Kommandos, Events, Nachrichten an andere Komponenten — wird ignoriert: keine Antwort, kein Fehler. ## Protokoll Alle Nachrichten sind JSON-Textframes. Die `id` der Anfrage wird unverändert in die Antwort gespiegelt; die Antwort trägt immer `"cmd":"tabresult"`. ### 1. Offene Tabs auslesen (`list`) ```json → {"type":"tab","id":1,"action":"list"} ← {"cmd":"tabresult","id":1,"ok":true, "tabs":[{"id":42,"title":"Kundenakte Muster GmbH","url":"https://crm.example.local/kunden/4711","active":false}, {"id":43,"title":"Posteingang","url":"https://mail.example.local/","active":true}]} ``` Optionale Felder zum Einschränken: `currentWindowOnly` (bool), `windowId` (number), `url` / `title` (Match-Pattern, z.B. `"*://*.example.local/*"`). ### 2. Tab mit mitgegebener URL öffnen (`open`) ```json → {"type":"tab","id":2,"action":"open","url":"https://crm.example.local/kunden/4711"} ← {"cmd":"tabresult","id":2,"ok":true,"tabId":44} ``` Optionale Felder: | Feld | Typ | Default | Beschreibung | |-------------|---------|---------|-----------------------------------------------------------------| | `active` | boolean | `true` | `false` = im Hintergrund öffnen | | `reuse` | boolean | `false` | ist die URL schon offen, wird der Tab aktiviert statt neu geöffnet (Antwort enthält dann `"reused":true`) | | `newWindow` | boolean | `false` | in neuem Fenster öffnen | | `windowId` | number | – | Zielfenster | | `index` | number | – | Position in der Tableiste | | `pinned` | boolean | `false` | angeheftet öffnen | Erlaubt sind `http`, `https`, `ftp`, `file`; `javascript:`, `data:` u.ä. werden abgewiesen. ### 3. Tab löschen (`close`) ```json → {"type":"tab","id":3,"action":"close","tabId":42} ← {"cmd":"tabresult","id":3,"ok":true} ``` `tabId` darf auch ein Array sein (`[42,43]`); die Antwort enthält dann `"closed":[42,43]`. ### Anmeldung (`hello`) Direkt nach dem Verbindungsaufbau meldet sich das Add-on einmal an — immer, nicht nur mit Token: ```json ← {"cmd":"hello","client":"swyx-tab-bridge","version":"1.0.0", "actions":["list","open","close","activate","focus"]} ``` Ist unter *Einstellungen* ein Token gesetzt, kommt es als Feld `token` mit. Eine Antwort wird nicht erwartet. SwyxTray kann die Gegenstelle erst dadurch identifizieren und den Verbindungsaufbau protokollieren; ohne `hello` ist auf der Leitung nicht unterscheidbar, wer sich verbunden hat. ### Fehler ```json ← {"cmd":"tabresult","id":5,"ok":false,"error":"Feld 'url' fehlt."} ``` `error` ist ein zusätzliches Klartextfeld zur Diagnose — maßgeblich ist `"ok":false`. Fehlerfälle: fehlendes/ungültiges `url` bzw. `tabId`, unerlaubtes URL-Protokoll, unbekannte `action`, nicht existierender Tab. SwyxTray wertet einen Auftrag nach 5 s ohne Antwort als gescheitert. Das Add-on bricht deshalb jede Aktion nach 4 s ab (`ACTION_TIMEOUT_MS` in [`background.js`](background.js)) und antwortet dann mit `"ok":false` — es bleibt also nie stumm. ### Zusätzliche Aktionen Über die drei SwyxTray-Aktionen hinaus versteht das Add-on: ```json → {"type":"tab","id":4,"action":"activate","tabId":42} ← {"cmd":"tabresult","id":4,"ok":true,"tabId":42} → {"type":"tab","id":5,"action":"focus","url":"https://crm.example.local/kunden/4711"} ← {"cmd":"tabresult","id":5,"ok":true,"focused":true,"opened":false,"tabId":42} ``` - `activate` holt einen bekannten Tab per `tabId` in den Vordergrund (inkl. Fenster). - `focus` sucht die passende Seite und aktiviert sie; ist sie nicht offen, wird sie geöffnet (`focused:false, opened:true`). Optional: `match` (`prefix` | `exact` | `origin`, Default `prefix`), `open:false` (nicht öffnen), `activate:false`, oder Suche per `title` statt `url`. Fragment (`#…`) und abschließende Slashes werden beim Vergleich ignoriert. ### Ignorierte Nachrichten | Nachricht | Verhalten | |----------------------------------------------------|--------------------| | alles ohne `"type":"tab"` (z.B. `{"cmd":"call",…}`) | wird ignoriert | | ungültiges JSON | wird ignoriert | ## Installation (temporär, zum Entwickeln) 1. `about:debugging#/runtime/this-firefox` öffnen 2. **„Temporäres Add-on laden…”** → `manifest.json` in diesem Ordner auswählen 3. Server-Adresse ggf. unter *Einstellungen* im Popup anpassen (Default `ws://127.0.0.1:17655`) Temporär geladene Add-ons verschwinden beim Firefox-Neustart. Für den Dauerbetrieb muss das Add-on signiert werden (`web-ext sign`, AMO-Account) oder es wird Firefox ESR / Developer Edition mit `xpinstall.signatures.required = false` verwendet. Paket bauen: ```bash npx web-ext lint npx web-ext build # erzeugt web-ext-artifacts/*.zip -> signieren bzw. in .xpi umbenennen ``` ## Signierung und Datenerhebung Seit dem 3. November 2025 müssen neue Erweiterungen auf AMO Mozillas eingebautes Einwilligungssystem bedienen. Im Manifest steht deshalb: ```json "data_collection_permissions": { "required": ["browsingActivity"] } ``` Mozilla definiert Übermittlung als Daten, die *außerhalb des Add-ons oder des lokalen Browsers* verarbeitet werden. Das Add-on gibt Tab-Titel und URLs an SwyxTray weiter — also aus dem Browser heraus, wenn auch nur an eine lokale Anwendung auf demselben Rechner und nicht ins Netz. `"none"` wäre eine Untertreibung, `browsingActivity` beschreibt genau das (besuchte Seiten, URLs). `strict_min_version` bleibt bewusst bei `115.0`, obwohl der Schlüssel erst ab Firefox 140 ausgewertet wird. Ältere Versionen ignorieren ihn folgenlos; ein Anheben auf `140.0` würde ESR 115 und ESR 128 ausschließen. `web-ext lint` meldet dafür zwei Warnungen — die blockieren die Signierung nicht. ## Installation auf Windows (per SSH) `deploy/deploy-windows.sh` zählt die Version hoch, baut das `.xpi`, überträgt es per `scp` und führt `deploy/Install-SwyxTabBridge.ps1` auf dem Zielrechner aus: ```bash ./deploy/deploy-windows.sh # Testrechner, ohne Parameter ./deploy/deploy-windows.sh --keep-firefox # laufenden Firefox nicht beenden ./deploy/deploy-windows.sh --uninstall # rückgängig machen ``` Der nackte Aufruf erledigt alles: Version hochzählen, bei AMO signieren, übertragen, Policy schreiben und den laufenden Firefox beenden (nur den aus dem Zielverzeichnis — eine zweite Installation daneben bleibt offen). Ziel ist die Release-Installation, die ausschließlich signierte Add-ons annimmt; jeder Lauf wird deshalb signiert. Ziel-Rechner, Firefox-Verzeichnis sowie Signatur- und Neustart-Verhalten sind als Konstanten oben im Skript voreingestellt. Standardmäßig wird eine Enterprise-Policy (`force_installed`) geschrieben, sodass das Add-on beim nächsten Firefox-Start ohne Rückfrage für alle Profile installiert wird. Details, Signierung und Optionen: [deploy/README.md](deploy/README.md). ## Tests Selbsttest der Kommandoverarbeitung ohne Firefox — `background.js` läuft mit gestubbten `browser`-/`WebSocket`-APIs: ```bash node test-server/test-background.mjs ``` Testserver, der SwyxTray simuliert (ohne Abhängigkeiten, nur Node): ```bash node test-server/server.js # lauscht auf ws://127.0.0.1:17655 node test-server/server.js --log /pfad/x.log # anderes Protokoll node test-server/server.js --no-log # nur Konsole ``` Der Server protokolliert Verbindungsaufbau, Anmeldung und Trennung mit Zeitstempel auf der Konsole **und** in `test-server/swyx-tray.log`: ``` [2026-08-17 16:16:09.809] Verbindung geöffnet von 127.0.0.1. [2026-08-17 16:16:09.809] Angemeldet: swyx-tab-bridge v1.0.12 (Aktionen: list, open, close, activate, focus; ohne Token) [2026-08-17 16:16:12.314] Verbindung getrennt: swyx-tab-bridge v1.0.12 (Dauer 3 s). ``` Meldet sich eine Gegenstelle binnen drei Sekunden nicht mit `hello` an, wird das als „unbekannte Gegenstelle" vermerkt. Das ist die Vorlage dafür, was SwyxTray mitschreiben sollte. | Eingabe | gesendete Nachricht | |------------------------------------|--------------------------------------------------------| | `list` / `list current` | `{"type":"tab","action":"list"}` | | `open [background]` | `{"type":"tab","action":"open","url":"…"}` | | `close ` | `{"type":"tab","action":"close","tabId":42}` | | `activate ` | `{"type":"tab","action":"activate","tabId":42}` | | `focus [titel…]` | `{"type":"tab","action":"focus","url":"…"}` | | `raw {…}` | beliebiges JSON (ohne `type:"tab"` bleibt es unbeantwortet) | | `quit` | beenden | ## Einstellungen Die Einstellungsseite zeigt oben, ob das Add-on mit der Tray-App verbunden ist — Zustand, Serveradresse, Zeitpunkt des Verbindungsaufbaus und der letzte Fehler, laufend aktualisiert. Dieselbe Anzeige gibt es kompakt im Popup. Über das Symbol in der Symbolleiste → *Einstellungen*: - **WebSocket-Server** — `ws://…` oder `wss://…` (Default `ws://127.0.0.1:17655`) - **Token** — optionales Shared Secret. Ist es gesetzt, geht es in der `hello`-Nachricht mit. - **Automatisch verbinden** — Auto-Reconnect mit exponentiellem Backoff (1 s → max. 30 s) Das Symbol zeigt den Verbindungszustand: kein Badge = verbunden, `…` = Verbindungsaufbau, `!` = getrennt. ## Dateien | Datei | Zweck | |-----------------------------------|---------------------------------------------------| | `manifest.json` | Add-on-Manifest (MV2, persistenter Hintergrund) | | `background.js` | WebSocket-Client, Reconnect, Aktionen | | `popup.html/js` | Statusanzeige, Verbinden/Trennen | | `options.html/js` | Server-URL, Token, Auto-Connect | | `test-server/server.js` | Testserver (simuliert SwyxTray) | | `test-server/test-background.mjs` | Selbsttest der Kommandoverarbeitung | | `deploy/deploy-windows.sh` | Version hochzaehlen, bauen, per SSH ausrollen | | `deploy/Install-SwyxTabBridge.ps1`| Installation auf dem Windows-Rechner | ## Hinweise - **Manifest V2** ist bewusst gewählt: Nur mit persistentem Hintergrundskript bleibt die WebSocket-Verbindung dauerhaft offen. Unter MV3 (nicht-persistente Event-Page) würde die Verbindung nach Leerlauf abgebaut. Firefox unterstützt MV2 weiterhin. - `ws://127.0.0.1` ist erlaubt (das Add-on läuft im `moz-extension:`-Kontext, keine Mixed-Content-Blockade). Für Verbindungen über das Netzwerk `wss://` verwenden. - SwyxTray sollte nur auf `127.0.0.1` lauschen — sonst kann jeder im Netz Tabs öffnen, schließen und die Tab-Liste mitlesen. - Die Permission `tabs` ist nötig, um `title` und `url` der Tabs zu lesen.