fix(remote): harden WS stream lifecycle, auth precedence, and proxy error handling

- Destroy Docker stats stream on WS close to prevent orphaned daemon polling
- Guard all ws.send() calls with readyState === OPEN check
- Add .catch() to unawaited streamStats/execContainer calls to prevent
  unhandled rejections crashing the process (Node >= 15)
- Close per-connection WebSocket.Server instances after handleUpgrade to
  prevent listener accumulation over many connections
- Invert auth token precedence to bearerToken || cookieToken in both
  authMiddleware and the WS upgrade handler so node-to-node Bearer tokens
  are never shadowed by a stale browser cookie
- Narrow proxyRes type in remoteNodeProxy error handler before calling
  .status() to avoid throwing on raw Socket (WS/TCP-level proxy errors)
This commit is contained in:
SaelixCode
2026-03-20 07:24:44 -04:00
parent 43436c15c1
commit abefd5e1f6
3 changed files with 46 additions and 10 deletions
+15 -3
View File
@@ -425,15 +425,27 @@ class DockerController {
const stats = await container.stats({ stream: true });
stats.on('data', (chunk: Buffer) => {
ws.send(chunk.toString());
if (ws.readyState === WebSocket.OPEN) {
ws.send(chunk.toString());
}
});
stats.on('error', (err: Error) => {
ws.send(JSON.stringify({ error: err.message }));
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ error: err.message }));
}
});
stats.on('end', () => {
ws.send(JSON.stringify({ end: true }));
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ end: true }));
}
});
// Destroy the Docker stats stream when the WebSocket closes to prevent
// orphaned streams polling the daemon after client disconnect.
ws.on('close', () => {
try { (stats as any).destroy(); } catch { /* stream already ended */ }
});
}