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

18 KiB
Raw Permalink Blame History

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

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:

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

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:

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

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:

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.