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 +7 -0
- package/package.json +1 -1
- package/src/shared/signaling-client.ts +57 -1
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
|
@@ -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
|
-
|
|
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 {
|