Files
2026-11-02 16:30:45 +01:00

206 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SwyxTray – Leitfaden für Coding-Agenten
## Projektüberblick
SwyxTray ist eine Windows-Desktop-Anwendung ohne Hauptfenster. Sie läuft als normale Benutzeranwendung in derselben interaktiven Windows-Sitzung wie SwyxIt!, verbindet sich über das Swyx Client SDK mit dem bereits laufenden Client Line Manager (`CLMgr.exe`) und zeigt den Telefoniezustand als farbiges Symbol im Infobereich an.
Zusätzlich stellt die Anwendung einen JSON-WebSocket-Zugang für Browser bereit. Darüber lassen sich unter anderem Anrufe steuern, Zustände und Anrufereignisse empfangen, Kontakte abfragen, Firefox-Tabs über ein externes Plugin verwalten sowie Gesprächsaufnahmen starten, stoppen und als WAV übertragen. Das aktuelle WebSocket-Protokoll ist Version 11.
SwyxIt!, das Firefox-Plugin SwyxFFPlugin, SwyxWeb und SwyxTrans sind nicht Teil dieses Repositories. Die ausführliche fachliche und Protokolldokumentation steht in `README.md`.
## Technologie und zentrale Konfiguration
- C# mit .NET 10; die Anwendung zielt auf `net10.0-windows`, die Checks auf `net10.0`.
- Windows Forms mit `OutputType=WinExe`; sichtbar sind primär `NotifyIcon`, Kontextmenü, Sprechblasen und ein kleiner Einstellungsdialog.
- Einzige externe NuGet-Abhängigkeit in beiden Projekten: `Swyx.Client.ClmgrAPI` 14.21.0. Diese Version ist auf die verwendete SwyxIt!-Version 14.21 abgestimmt und sollte bei einem Client-Update mitgeführt werden.
- Das SDK stellt die COM-Interop-Typen aus `IpPbx.CLMgrLib` bereit. Es wählt die Interop-Assembly anhand von `$(Platform)` aus; `AnyCPU` ist daher nicht zulässig.
- Die Anwendung ist standardmäßig auf `x64`, Runtime Identifier `win-x64` und framework-abhängige Ausführung eingestellt. `x86` ist in `Platforms` aufgeführt, der vorhandene Runtime Identifier bleibt jedoch `win-x64`; x64 ist der dokumentierte und geprüfte Weg.
- Nullable Reference Types und Implicit Usings sind aktiviert. Globalisierung ist invariant.
- Es gibt keine `.sln`, kein `global.json`, kein `.editorconfig`, kein Paket-Lockfile und keine CI-Konfiguration im Repository.
Wichtige Dateien:
| Pfad | Aufgabe |
|---|---|
| `src/SwyxTray/SwyxTray.csproj` | Zielframework, WinForms, Plattform, Runtime und SDK-Paket |
| `src/SwyxTray/app.manifest` | `asInvoker`, Windows-10/11-Kompatibilität, PerMonitorV2-DPI und Long-Path-Unterstützung |
| `tests/SwyxTray.RecordingChecks/SwyxTray.RecordingChecks.csproj` | Eigenständiges Konsolenprogramm; bindet ausgewählte Produktionsdateien direkt ein |
| `.vscode/tasks.json` | Debug-/Release-Builds und Start in der Desktop-Sitzung |
| `.vscode/launch.json` | Direkter Start beziehungsweise Anhängen des Debuggers |
| `.vscode/start-desktop.ps1` | Temporäre geplante Aufgabe für den Start in der interaktiven Sitzung |
| `README.md` | Verhalten, WebSocket-Protokoll, Sicherheit, Betrieb und bisherige Prüfungen gegen den realen Client |
## Codeorganisation und Laufzeitarchitektur
### Anwendung und Oberfläche
- `src/SwyxTray/Program.cs`: STA-Einstiegspunkt, sitzungslokale Einzelinstanz-Sperre (`Local\SwyxTray.SingleInstance`), `--verbose`/`-v`, COM-Sicherheitsinitialisierung und globale Fehlerprotokollierung.
- `src/SwyxTray/TrayApplicationContext.cs`: Anwendungsrumpf, Tray-Menü, Timer, Verbindungsaufbau, Einstellungen und Weitergabe von Zustandsänderungen an Oberfläche und WebSocket-Server.
- `src/SwyxTray/TrayIcons.cs`: Erzeugt und cached die farbigen Symbole zur Laufzeit; es gibt keine ICO-Ressourcen.
- `src/SwyxTray/SettingsDialog.cs`: Einstellung der Verzögerung, mit der Firefox bei eingehenden Rufen in den Vordergrund geholt wird.
- `src/SwyxTray/FirefoxWindow.cs`: Native Windows-Aufrufe zum Aktivieren des Firefox-Fensters nach der Tab-Aktivierung durch das Plugin.
- `src/SwyxTray/NativeMethods.cs`: COM-Sicherheit und Freigabe nativer Icon-Handles.
- `src/SwyxTray/Log.cs`: Schlankes, threadübergreifend gesperrtes Dateiprotokoll ohne Logging-Framework.
### Swyx-/COM-Schicht (`src/SwyxTray/Swyx/`)
- `SwyxClient.cs` kapselt CLMgr-Verbindung, Ereignissenke, Snapshots, Telefoniebefehle, Kontaktsuche, Telefonbuchcache, Aufnahmebefehle und COM-Lebenszyklus.
- `SwyxModels.cs` enthält `SwyxSnapshot`, `SwyxLineInfo`, `SwyxContact` und den aggregierten Zustand `SwyxOverallState`.
- `LineStateExtensions.cs` klassifiziert die SDK-Leitungszustände und liefert deutsche Anzeigetexte.
- `PubCLMgrMessages.cs` bildet die benötigten Werte aus dem C-Header `CLMgrPubTypes.h` nach.
- `CallRecordingTracker.cs` verfolgt erfolgreich an das SDK übergebene Aufnahmewünsche pro Leitung und `callId`. Dies ist ausdrücklich kein Nachweis, dass tatsächlich eine Audiodatei geschrieben wurde.
Zentrale Invarianten:
1. **Alle COM-Zugriffe bleiben im WinForms-STA-Thread.** Netzwerk- und Hintergrund-Threads dürfen CLMgr nicht direkt aufrufen. `UiDispatcher` und `CommandExecutor` schleusen Befehle mit einer Zeitgrenze von zehn Sekunden in den UI-Thread.
2. `NativeMethods.InitializeComSecurity()` muss vor dem ersten COM-Aufruf erfolgen, damit CLMgr Ereignisse in den Prozess zurückrufen kann.
3. Zustandsänderungen sind primär ereignisgesteuert. Bei jeder CLMgr-Meldung wird der vollständige Zustand neu gelesen. Ein Sicherheitszyklus läuft alle fünf Sekunden, bei fehlendem Ereignisrückkanal jede Sekunde.
4. COM-Objekte werden an den vorhandenen Stellen mit `Marshal.FinalReleaseComObject` freigegeben. Neue COM-Pfade müssen Ereignisabmeldung, Fehlerfälle und Freigabe ebenso behandeln.
5. CLMgr-Leitungen sind intern 0-basiert, im Tray-Menü und WebSocket-Protokoll dagegen 1-basiert.
6. Telefonbücher kommen als reine IDispatch-Objekte an und werden über `IEnumerable` sowie `dynamic`/Late Binding gelesen, nicht über die typisierten Telefonbuch-Interop-Schnittstellen.
7. Der Adresscache wird beim Verbinden und anschließend alle 60 Minuten neu gelesen. Bei Lesefehlern bleibt der letzte erfolgreiche Stand erhalten.
### WebSocket-Schicht (`src/SwyxTray/Web/`)
- `LocalWebSocketServer.cs`: Mehrere `TcpListener`, Verbindungsverwaltung und Broadcasts. Der Hauptport bedient Browser; ein zweiter Port ist ausschließlich für das Firefox-Plugin vorgesehen.
- `WebSocketHandshake.cs`: Manueller HTTP-Upgrade nach RFC 6455, einschließlich Token-, Origin- und Host-Prüfung. WebSocket-Frames, Maskierung, Ping/Pong und Close übernimmt `System.Net.WebSockets`.
- `WebSocketSession.cs`: Empfang und serialisiertes Senden einer Verbindung. Eingehende Textnachrichten sind auf 64 KiB begrenzt; Sendevorgänge sind durch eine Sperre serialisiert. Normale Kommandos werden pro Sitzung nacheinander verarbeitet.
- `CommandExecutor.cs`: Übersetzt Kommandos in `SwyxClient`- beziehungsweise Plugin-Aufrufe und validiert unter anderem Rufnummern und absolute HTTP(S)-URLs.
- `UiDispatcher.cs`: Übergabe von Arbeit an den UI-`SynchronizationContext`.
- `PluginTabChannel.cs`: Anfrage-/Antwortzuordnung für `list`, `open`, `close` und `activate` mit fünf Sekunden Antwortzeit. Der Plugin-Port erlaubt nur `tabresult` und `ping`.
- `Protocol.cs`: JSON-DTOs, Snapshot-Abbildung und Ableitung der Anrufereignisse. JSON verwendet Web-Defaults (camelCase) und lässt Nullwerte weg.
- `WebSocketConfig.cs`: Protokollversion, Konfiguration, Token-Erzeugung und Sicherheitsschalter.
Bei Broadcasts wird zuerst der Snapshot und unmittelbar danach die daraus abgeleiteten `call`-Ereignisse gesendet. Ein Verbindungs- oder Serverausfall allein gilt nicht als nachgewiesenes Gesprächsende.
### Aufnahme-Warteschlange (`src/SwyxTray/Recording/RecordingOutbox.cs`)
`RecordingOutbox` verbindet SDK-Aufnahmeabschnitte mit neu erzeugten WAV-Dateien und hält deren Zustand persistent. Wichtige Regeln:
- Manifeste liegen als UUID-benannte JSON-Dateien in der Warteschlange; Schreibvorgänge erfolgen über temporäre Dateien und anschließendes Ersetzen.
- Nur eindeutig zuordenbare, neue, geschlossene WAVs mit stabiler Länge und gültiger RIFF/WAVE-Struktur werden übernommen. Die maximale Größe beträgt 500 MB.
- Die ursprüngliche SwyxIt!-Aufnahme wird weder verändert noch gelöscht; übertragen wird eine temporäre Kopie.
- Ein Aufnahmestopp allein gibt die Datei nicht frei. Halten oder ein COM-/Serverausfall gelten nicht als Gesprächsende. Erst ein gesunder Snapshot mit beendeter oder ersetzter Verbindung bestätigt das Ende.
- Offene Aufnahmen überstehen einen Neustart. Erst nach einer dauerhaft gespeicherten `recording.ack`-Bestätigung mit 32-stelliger hexadezimaler `jobId` wird die temporäre Kopie entfernt.
- `recording.download` überträgt binäre Blöcke mit `SWYX`-Magic, Kommando-ID und Offset (jeweils Int32 Little Endian) sowie höchstens 64 KiB Audiodaten. Die normale Empfangsschleife bleibt für Steuerkommandos verfügbar.
## Bauen, Starten und Prüfen
Alle Befehle werden aus dem Repository-Stamm ausgeführt. Vorausgesetzt werden Windows, ein .NET-10-SDK und für den realen Betrieb ein laufendes SwyxIt! in derselben Benutzersitzung.
### Build
```powershell
dotnet build src/SwyxTray/SwyxTray.csproj -p:Platform=x64
dotnet build src/SwyxTray/SwyxTray.csproj -c Release -p:Platform=x64
```
Die Debug-Ausgabe liegt unter:
```text
src/SwyxTray/bin/x64/Debug/net10.0-windows/win-x64/SwyxTray.exe
```
`bin/` und `obj/` sind ignorierte Build-Artefakte und gehören nicht in Änderungen.
### Automatische Checks
```powershell
dotnet run --project tests/SwyxTray.RecordingChecks -p:Platform=x64
```
Dies ist kein MSTest-/xUnit-/NUnit-Projekt und nicht mit `dotnet test` gleichzusetzen. `tests/SwyxTray.RecordingChecks/Program.cs` ist ein ausführbares Prüfprogramm mit eigenen `Check`-/`Reject`-Hilfen; jede nicht bestandene Bedingung wirft eine Ausnahme. Erfolg wird auf der Konsole mit der Anzahl bestandener Checks gemeldet.
Die Checks umfassen derzeit:
- Aufnahmezustand, idempotentes Starten, Fehlerfälle und veraltete `callId`-Werte;
- Snapshot-/Ereignisfelder und Unterscheidung von Gesprächsende, Halten und Verbindungsverlust;
- WAV-Erkennung, Dateisperren, unvollständige Dateien, Mehrdeutigkeit, Gesprächsende ohne lokalen Auflegebefehl und persistente Bestätigungen;
- echten Loopback-WebSocket-Binärtransport einschließlich Übertragungsabbruch, Wiederholung und parallelem `ping`.
Das Testprojekt kompiliert Produktionsdateien direkt ein und ersetzt nur COM-Executor und Plugin-Kanal durch `TransportStubs.cs`. Temporäre WAVs und Warteschlangen liegen unter dem System-Tempverzeichnis und werden aufgeräumt. Es finden keine COM-Aufrufe oder Telefonate statt.
Für einen manuellen Browser-Fixture-Server existiert zusätzlich:
```powershell
dotnet run --project tests/SwyxTray.RecordingChecks -p:Platform=x64 -- --serve
```
Dieser Modus läuft dauerhaft auf Loopback-Port 17658 und stellt eine synthetische WAV bereit; er ist kein Bestandteil des normalen Prüflaufs.
### Anwendung starten
```powershell
dotnet build src/SwyxTray/SwyxTray.csproj -c Release -p:Platform=x64
.\src\SwyxTray\bin\x64\Release\net10.0-windows\win-x64\SwyxTray.exe --verbose
```
`--verbose` protokolliert zusätzlich jede CLMgr-Meldung. Eine zweite Instanz in derselben Sitzung beendet sich wegen der Einzelinstanz-Sperre.
In VS Code ist **„SwyxTray debuggen (Desktop-Sitzung)"** der geeignete Weg, besonders bei Remote-VS-Code. Die Aufgabe beendet zunächst eine laufende Instanz, baut Debug und startet die Anwendung über eine geplante Aufgabe im interaktiven Desktop. Der direkte Start kann bei einer Remote-Sitzung in Sitzung 0 landen; dort läuft der Prozess ohne sichtbares Tray-Symbol.
### Verifizierungsumfang und manuelle Restprüfungen
Bei Erstellung dieses Leitfadens wurden der Debug-Build und der normale Prüflauf ausgeführt: Build erfolgreich mit 0 Warnungen und 0 Fehlern, **204 recording/protocol checks passed (no COM calls)**.
Die automatischen Checks ersetzen keine Prüfung mit realem SwyxIt!. COM-Aktivierung, Ereignisrückkanal, echte Zustandswechsel, Telefoniebefehle, tatsächliche Swyx-Aufnahmen, Tray-Darstellung und Firefox-Fokusverhalten erfordern eine interaktive Windows-Sitzung und gegebenenfalls kontrollierte Testanrufe. Keine realen Anrufe allein zur Verifikation auslösen.
## Entwicklungskonventionen
- Sprache der Dokumentation, UI und Protokollmeldungen ist Deutsch. Einige neuere Aufnahme- und Testdateien verwenden englische Kommentare; an der jeweiligen Datei orientieren.
- Vier Leerzeichen Einrückung, dateibezogene Namespaces, geschweifte Klammern und die vorhandenen Benennungsmuster beibehalten. Es gibt keinen separaten Formatter- oder Analyzer-Workflow.
- Asynchroner Netzwerkcode verwendet durchgehend `ConfigureAwait(false)` außerhalb des UI-Kontexts. Fortsetzungen werden bei bedarfsgesteuerten `TaskCompletionSource`-Instanzen asynchron ausgeführt.
- Ressourcen und Abonnements werden explizit aufgeräumt: `IDisposable`, CancellationTokens, Sendesperren, COM-Freigaben, Tray-Icons und Event-Handler beachten.
- Die vorhandene schlanke Architektur beibehalten und keine neuen Frameworks oder Abhängigkeiten ohne konkreten Bedarf einführen.
- Protokolländerungen müssen `WebSocketConfig.ProtocolVersion`, DTOs, Verhalten, Checks und `README.md` konsistent aktualisieren. Bestehende Felder wie `id`, `line`, `callId`, `recordingId`, Snapshot/Ereignis-Reihenfolge und das Binärformat sind öffentliche Schnittstellen.
- Neue Aufnahme- oder Transportregeln gehören in das ausführbare Check-Programm. Für gemeinsam genutzte Produktionsdateien muss gegebenenfalls auch die `<Compile Include=...>`-Liste des Check-Projekts angepasst werden.
- Die im README verlinkte Datei `examples/browser-client.html` ist im aktuellen Repository nicht vorhanden. Nicht annehmen, dass ein Browserbeispiel oder das externe Plugin lokal verfügbar ist.
## Laufzeitdaten und Konfiguration
Die von SwyxTray selbst angelegten Laufzeitdaten liegen unter `%LOCALAPPDATA%\SwyxTray` (die ursprünglichen SwyxIt!-Aufnahmen bleiben dagegen im konfigurierten Swyx-Aufnahmeordner):
- `swyxtray.log`: Dateiprotokoll, Rotation bei 1 MiB mit einer Sicherungsdatei `swyxtray.log.1`;
- `websocket.json`: WebSocket-Konfiguration;
- `recordings/`: persistente JSON-Manifeste und temporäre WAV-Kopien der Aufnahme-Warteschlange.
Beim ersten Start wird `websocket.json` mit zufälligem Token erzeugt. Die aktuellen Standardwerte sind:
- Hauptport `17654`, Plugin-Port `17655` (`0` deaktiviert den Plugin-Port);
- `Enabled=true`, `MaxConnections=8`;
- `AllowedOrigins=[]` und `AllowRemoteAccess=false`;
- `IncomingCallTabTitle="SwyxWeb"` und `IncomingCallFocusDelayMs=1000`.
Eine defekte Konfiguration führt bewusst dazu, dass der Server nicht startet, statt still ein neues Token zu erzeugen. Änderungen an der JSON-Datei erfordern grundsätzlich einen Neustart. Die Fokusverzögerung kann über den Einstellungsdialog geändert werden; dieser speichert sofort in dieselbe geladene Konfiguration. Beim Speichern gehen manuell eingefügte JSON-Kommentare verloren.
## Sicherheitsaspekte
**Kritischer Ist-Zustand:** `WebSocketConfig.SecurityDisabled` ist in `src/SwyxTray/Web/WebSocketConfig.cs` auf `true` gesetzt – **in jedem Build**. Token-, Origin- und Host-Prüfung sind derzeit abgeschaltet. Wer den Haupt- oder Plugin-Port erreicht, kann die angebotenen Aktionen ausführen und Daten mitlesen. Dieser Zustand ist laut README ausschließlich für die Erprobung vorgesehen und darf nicht ausgeliefert werden.
Standardmäßig bindet die Anwendung nur an `127.0.0.1` und `::1`. Mit `AllowRemoteAccess=true` bindet sie an alle IPv4-/IPv6-Schnittstellen. In Kombination mit den abgeschalteten Prüfungen kann dann grundsätzlich jeder erreichbare Netzwerkteilnehmer telefonieren und mitlesen. Vor einer Auslieferung müssen die Prüfungen wieder aktiviert und der gewünschte Bindingscope bewusst festgelegt werden.
Bei aktivierter Sicherheit bestehen die vorgesehenen Schranken aus:
- zufälligem Token, zeitkonstant verglichen, per Query-Parameter oder `token.…`-Unterprotokoll;
- Origin-Allowlist, da die Same-Origin-Policy WebSockets nicht schützt;
- Host-Prüfung gegen DNS-Rebinding bei Loopback-Betrieb;
- Loopback-Bindung beziehungsweise bewusster Fernzugriff mit Firewall-Konfiguration.
Bei Fernzugriff entfällt die Host-Prüfung auch im abgesicherten Modus. Es gibt nur `ws://`, kein `wss://`; HTTPS-Seiten können entfernte unverschlüsselte WebSockets wegen Mixed-Content-Regeln gegebenenfalls nicht verwenden.
Die Eingabevalidierung nicht abschwächen: Rufnummern werden bereinigt und begrenzt, Browser-URLs müssen absolutes HTTP(S) verwenden, JSON- und Headergrößen sind begrenzt, WAVs werden strukturell geprüft und Downloads/Bestätigungen benötigen den passenden persistenten Zustand. Token und Aufnahmeinhalte nicht unnötig in neue Protokollmeldungen oder Commits aufnehmen.
Die Anwendung läuft mit `asInvoker` und benötigt im Normalbetrieb keine Administratorrechte. `CoInitializeSecurity` verwendet derzeit die dokumentierte, wenig restriktive COM-Ereignissenken-Konfiguration; Änderungen daran müssen gegen den realen CLMgr-Rückruf geprüft werden. Anwendungssteuerungsrichtlinien wie WDAC/Smart App Control müssen außerdem den von Swyx benötigten COM-Proxy `CLMgrPs64.dll` zulassen.
## Deployment und Betrieb
Der Standardbuild ist framework-abhängig und benötigt die .NET-10-Desktop-Laufzeit. Das README dokumentiert alternativ eine eigenständige Veröffentlichung:
```powershell
dotnet publish src/SwyxTray/SwyxTray.csproj -c Release -r win-x64 --self-contained true -p:Platform=x64
```
Es gibt weder Installer noch automatisierte Release-/Deployment-Pipeline im Repository. Für Autostart wird eine Verknüpfung zur EXE in `shell:startup` beziehungsweise `%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup` abgelegt.
SwyxTray darf nicht einfach als Windows-Dienst betrieben werden: Dienste laufen in Sitzung 0, während SwyxIt!/CLMgr zur interaktiven Benutzersitzung gehören. Anwendung und Client müssen in derselben Sitzung und mit kompatiblem Integritätslevel laufen. Bei Netzwerkzugriff kann zusätzlich eine eingehende Windows-Firewall-Regel für den Haupt- und gegebenenfalls Plugin-Port erforderlich sein.