refactor: extract WebSocketClientBase from Gateway/Node clients (#63)
Extract ~200 lines of duplicated WebSocket lifecycle code into a shared
abstract base class. Both OpenClawGatewayClient and WindowsNodeClient
now inherit from WebSocketClientBase.
Shared in base class:
- Connection lifecycle: ConnectAsync, ListenForMessagesAsync, ReconnectWithBackoffAsync
- SendRawAsync (thread-safe with TOCTOU protection)
- Dispose (defensive pattern, skip CTS dispose)
- Fields: WebSocket, URL, token, credentials, CTS, backoff array
- StatusChanged event with RaiseStatusChanged helper
- Constructor: URL normalization, credential extraction, validation
Subclass hooks (template method pattern):
- ProcessMessageAsync (abstract) - Gateway wraps sync, Node uses async
- ReceiveBufferSize (abstract) - Gateway 16KB, Node 64KB
- ClientRole (abstract) - for log messages
- OnConnectedAsync, OnDisconnected, OnError, OnDisposing (virtual)
Safety improvements (Node's safer patterns adopted everywhere):
- ObjectDisposedException catch in listen loop
- Post-delay CTS check in reconnect
- Try-catch around reconnect guard
- Constructor argument validation
20 new tests via TestWebSocketClient test double.
596 total tests pass (503 shared + 93 tray).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>