# WebSocket Sync Peers

This directory contains the WebSocket client (sender) and server (receiver) peer implementations.

## WebSocket Resource Management & Teardown

Since WebSockets manage active OS-level TCP connections, they must be cleanly closed to prevent socket descriptor leaks on the client and resource exhaustion (DoS) on the server.

### Analysis (2026-06-12T09:45:21-05:00)

Our analysis highlights several areas where WebSocket connections are currently left open:

1. **Sender Connect Failures**: If connection handshake fails or times out, the client rejects the promise but does not close the socket.
2. **Sender Runtime Failures**: If runtime message validation fails, we throw an error but leave the socket open.
3. **Sender Saga Completion**: When the sync loop finishes successfully, the socket is left open indefinitely because there is no `disconnect` method.
4. **Receiver Message Handling Failures**: If incoming messages fail validation/authentication, the server sends a `sync_error` or `auth_fail` frame, but relies on the client to close the connection. A buggy/malicious client can leave it open forever.

#### Design Questions & Ideas
* **Idempotent `disconnect()`**: The disconnect method on the sender should clear `this.handshakeMessageListener` and set it to `undefined` so that multiple calls to `disconnect()` are safe.
* **Reuse `disconnect()`**: In connection failure handlers (like `handleHandshakeAuthFail`), we can call `this.disconnect()` to ensure uniform cleanup.
* **Server-side Close on Error (best-effort `send`)**: If a message fails, we want to notify the client first and then close the connection. If the `send()` itself throws, we want a safe cleanup. In JavaScript, we can nest `try..catch..finally` inside a `catch` block, or use a short `setTimeout` to push the socket close operation to the next event loop tick, ensuring the outgoing frame is written before the socket is terminated.

---

### Implementation TODOs

- [x] **Implement `disconnect` method on `SyncPeerWebSocketSender_V1`**
  - Implement a synchronous or asynchronous `disconnect()` method.
  - Safely remove the message event listener `this.handshakeMessageListener`.
  - Set `this.handshakeMessageListener = undefined` to make it idempotent.
  - Close the WebSocket `this.ws.close()`.
  
- [x] **Clean up socket on Sender connection failures**
  - Call `this.disconnect()` inside `handleHandshakeAuthFail`.
  - Call `this.disconnect()` inside `handleHandshakeSyncError`.
  
- [x] **Close socket on Sender runtime message validation failures**
  - In `handleRuntimeMessage` and `handleRuntimeSyncFrameResponse`, catch validation errors, trigger `this.disconnect()`, and rethrow.

- [x] **Close socket on Receiver message handling failures**
  - In the catch block of `handleIncomingMessage`, call `this.socketWrapper?.close()` (or equivalent wrapper cleanup) to force-close connections after sending error frames.
  - Wrap the `send()` call in a nested try/catch or use `setTimeout` to ensure best-effort message transmission before terminating the socket wrapper.
