shraga 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/server/boot.ts +22 -18
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shraga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "The teammate you delegate coding to — a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
package/src/server/boot.ts
CHANGED
|
@@ -842,26 +842,30 @@ function proxySidecarWebSocket(req: import('node:http').IncomingMessage, socket:
|
|
|
842
842
|
const targetWs = new WebSocket(targetUrl);
|
|
843
843
|
let opened = false;
|
|
844
844
|
|
|
845
|
+
// Bound IMMEDIATELY, not inside targetWs 'open': the BROWSER's socket is OPEN the moment
|
|
846
|
+
// handleUpgrade returns, so it reports "connected" and starts sending while we're still dialing the
|
|
847
|
+
// sidecar. Registering the listener on open discarded everything typed in that window — silently.
|
|
848
|
+
// Queue instead, and flush in order once upstream is up.
|
|
849
|
+
const pending: Array<{ data: import('ws').RawData; isBinary: boolean }> = [];
|
|
850
|
+
clientWs.on('message', (data, isBinary) => {
|
|
851
|
+
// App-level liveness probe (Layer 2): the client can't read protocol pongs from JS, so it sends
|
|
852
|
+
// `{type:'ping'}` and expects `{type:'pong'}`. We RELAY it — we must not answer it here. A
|
|
853
|
+
// proxy-local reply only proves THIS hop is alive: if the proxy→sidecar leg is half-open, or the
|
|
854
|
+
// sidecar has already dropped this client from its subscriber set, the browser still gets pongs,
|
|
855
|
+
// keeps `readyState === OPEN`, shows a green "connected" dot, and every keystroke disappears.
|
|
856
|
+
// The probe is only worth anything end-to-end, so the sidecar owns the reply (para-pty answers in
|
|
857
|
+
// its ws message handler); a sidecar that doesn't reply fails the probe, which is the honest
|
|
858
|
+
// outcome — the client then reconnects rather than trusting a dead pipe.
|
|
859
|
+
if (targetWs.readyState === WebSocket.OPEN) targetWs.send(data, { binary: isBinary });
|
|
860
|
+
else if (!opened && pending.length < 256) pending.push({ data, isBinary }); // bounded: never buffer unboundedly
|
|
861
|
+
});
|
|
862
|
+
|
|
845
863
|
targetWs.on('open', () => {
|
|
846
864
|
opened = true;
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
// Cheap prefilter (small + contains "ping") so we don't JSON-parse every keystroke/paste frame.
|
|
852
|
-
if (!isBinary && (data as Buffer).length < 64) {
|
|
853
|
-
const s = data.toString();
|
|
854
|
-
if (s.includes('"ping"')) {
|
|
855
|
-
try {
|
|
856
|
-
if (JSON.parse(s).type === 'ping') {
|
|
857
|
-
if (clientWs.readyState === WebSocket.OPEN) clientWs.send(JSON.stringify({ type: 'pong' }));
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
} catch { /* not JSON — fall through to relay */ }
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
if (targetWs.readyState === WebSocket.OPEN) targetWs.send(data, { binary: isBinary });
|
|
864
|
-
});
|
|
865
|
+
for (const m of pending) {
|
|
866
|
+
if (targetWs.readyState === WebSocket.OPEN) targetWs.send(m.data, { binary: m.isBinary });
|
|
867
|
+
}
|
|
868
|
+
pending.length = 0;
|
|
865
869
|
targetWs.on('message', (data, isBinary) => {
|
|
866
870
|
if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data, { binary: isBinary });
|
|
867
871
|
});
|