codehost 0.18.1 → 0.18.2

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.2](https://github.com/snomiao/codehost/compare/v0.18.1...v0.18.2) (2026-06-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **signaling:** recover instantly when a throttled tab wakes (visibility/focus/online) ([d459132](https://github.com/snomiao/codehost/commit/d45913267f053ee1fee062bae550cfe60a193207))
7
+
1
8
  ## [0.18.1](https://github.com/snomiao/codehost/compare/v0.18.0...v0.18.1) (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.1",
3
+ "version": "0.18.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -53,6 +53,7 @@ export class SignalingClient {
53
53
  private ws: WebSocket | null = null;
54
54
  private closed = false;
55
55
  private reconnectDelay = 1000;
56
+ private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
56
57
  private heartbeat: ReturnType<typeof setInterval> | null = null;
57
58
  /** Fires STABLE_MS after a socket opens; only then is the backoff reset. */
58
59
  private stableTimer: ReturnType<typeof setTimeout> | null = null;
@@ -65,9 +66,53 @@ export class SignalingClient {
65
66
 
66
67
  connect(): void {
67
68
  this.closed = false;
69
+ this.attachWakeListeners();
68
70
  this.open();
69
71
  }
70
72
 
73
+ // ---- background-tab recovery -------------------------------------------
74
+ // Chrome throttles timers in hidden tabs to minutes, so the backoff retry
75
+ // (and the connect-timeout abort) may be arbitrarily far away even though a
76
+ // fresh socket would connect in milliseconds. When the tab becomes visible /
77
+ // focused / back online, recover NOW instead of waiting for a timer.
78
+
79
+ private onWake = (): void => {
80
+ if (this.closed) return;
81
+ const state = this.ws?.readyState;
82
+ if (state === 1 /* OPEN */) return;
83
+ if (state === 0 /* CONNECTING */) {
84
+ // Stuck handshake: abort — onclose reschedules, and timers run normally
85
+ // now that the tab is active.
86
+ try {
87
+ this.ws?.close();
88
+ } catch {
89
+ // ignore
90
+ }
91
+ return;
92
+ }
93
+ // Closed and waiting out the (throttled) backoff: skip the wait.
94
+ if (this.reconnectTimer != null) {
95
+ this.clearReconnectTimer();
96
+ this.open();
97
+ }
98
+ };
99
+
100
+ private attachWakeListeners(): void {
101
+ const doc = (globalThis as { document?: EventTarget }).document;
102
+ doc?.addEventListener("visibilitychange", this.onWake);
103
+ const win = (globalThis as { window?: EventTarget }).window;
104
+ win?.addEventListener("focus", this.onWake);
105
+ win?.addEventListener("online", this.onWake);
106
+ }
107
+
108
+ private detachWakeListeners(): void {
109
+ const doc = (globalThis as { document?: EventTarget }).document;
110
+ doc?.removeEventListener("visibilitychange", this.onWake);
111
+ const win = (globalThis as { window?: EventTarget }).window;
112
+ win?.removeEventListener("focus", this.onWake);
113
+ win?.removeEventListener("online", this.onWake);
114
+ }
115
+
71
116
  private roomUrl(): string {
72
117
  const base = this.opts.url.replace(/\/+$/, "");
73
118
  return `${base}/room/${encodeURIComponent(this.opts.token)}`;
@@ -172,11 +217,20 @@ export class SignalingClient {
172
217
  private scheduleReconnect(): void {
173
218
  const delay = this.reconnectDelay;
174
219
  this.reconnectDelay = Math.min(delay * 2, 15000);
175
- setTimeout(() => {
220
+ this.clearReconnectTimer();
221
+ this.reconnectTimer = setTimeout(() => {
222
+ this.reconnectTimer = null;
176
223
  if (!this.closed) this.open();
177
224
  }, delay);
178
225
  }
179
226
 
227
+ private clearReconnectTimer(): void {
228
+ if (this.reconnectTimer != null) {
229
+ clearTimeout(this.reconnectTimer);
230
+ this.reconnectTimer = null;
231
+ }
232
+ }
233
+
180
234
  sendSignal(to: string, data: unknown): void {
181
235
  const msg: ClientMessage = { type: "signal", to, data };
182
236
  this.ws?.send(JSON.stringify(msg));
@@ -194,6 +248,8 @@ export class SignalingClient {
194
248
 
195
249
  close(): void {
196
250
  this.closed = true;
251
+ this.detachWakeListeners();
252
+ this.clearReconnectTimer();
197
253
  this.stopHeartbeat();
198
254
  this.clearStableTimer();
199
255
  try {