codehost 0.18.0 → 0.18.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [0.18.1](https://github.com/snomiao/codehost/compare/v0.18.0...v0.18.1) (2026-06-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **signaling:** abort connect attempts stuck in CONNECTING; guard tunnel stream enqueue ([b3ae0d1](https://github.com/snomiao/codehost/commit/b3ae0d11299a195b8d09e42355cd14a3b09b8d2f))
7
+
1
8
  # [0.18.0](https://github.com/snomiao/codehost/compare/v0.17.0...v0.18.0) (2026-06-11)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codehost",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,6 +37,12 @@ export interface CloseInfo {
37
37
  * open/close cycle becomes a sub-second reconnect storm. */
38
38
  const STABLE_MS = 10_000;
39
39
 
40
+ /** Abort a connect attempt that hasn't opened by this deadline. Observed in the
41
+ * field (Chrome, page-load burst): a socket can sit in CONNECTING for minutes
42
+ * and never fire close — so without this, no retry ever runs, even though a
43
+ * freshly-created socket to the same room opens instantly. */
44
+ const CONNECT_TIMEOUT_MS = 10_000;
45
+
40
46
  /**
41
47
  * Thin WebSocket client for the signaling room. Runs unchanged in the browser
42
48
  * and in Bun (both expose a global `WebSocket`). Auto-reconnects with backoff
@@ -71,7 +77,21 @@ export class SignalingClient {
71
77
  const ws = new WebSocket(this.roomUrl());
72
78
  this.ws = ws;
73
79
 
80
+ // A stuck CONNECTING socket never fires close on its own — abort it so the
81
+ // normal onclose -> backoff -> retry path takes over.
82
+ const connectTimer = setTimeout(() => {
83
+ if (ws.readyState === 0 /* CONNECTING */) {
84
+ try {
85
+ ws.close();
86
+ } catch {
87
+ // closing an unopened socket may throw in some runtimes — the
88
+ // onerror/onclose path still runs
89
+ }
90
+ }
91
+ }, CONNECT_TIMEOUT_MS);
92
+
74
93
  ws.onopen = () => {
94
+ clearTimeout(connectTimer);
75
95
  this.openedAt = Date.now();
76
96
  // Don't reset the backoff yet — only once the socket proves stable (see
77
97
  // STABLE_MS). A handshake-then-drop network never reaches this timer, so
@@ -103,6 +123,7 @@ export class SignalingClient {
103
123
  };
104
124
 
105
125
  ws.onclose = (ev) => {
126
+ clearTimeout(connectTimer);
106
127
  this.clearStableTimer();
107
128
  this.stopHeartbeat();
108
129
  const ms = this.openedAt ? Date.now() - this.openedAt : 0;
@@ -129,7 +129,13 @@ export class TunnelClient {
129
129
  }),
130
130
  );
131
131
  },
132
- onBody: (b) => controller?.enqueue(b),
132
+ onBody: (b) => {
133
+ try {
134
+ controller?.enqueue(b);
135
+ } catch {
136
+ // stream already closed/cancelled (consumer went away mid-body)
137
+ }
138
+ },
133
139
  onEnd: () => {
134
140
  try {
135
141
  controller?.close();