@pylonsync/sync 0.3.177 → 0.3.178
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/package.json +1 -1
- package/src/index.ts +30 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -714,6 +714,7 @@ export class SyncEngine {
|
|
|
714
714
|
* the client can't hammer the server on auth failures.
|
|
715
715
|
*/
|
|
716
716
|
private wsStableTimer: ReturnType<typeof setTimeout> | null = null;
|
|
717
|
+
private pingTimer: ReturnType<typeof setInterval> | null = null;
|
|
717
718
|
|
|
718
719
|
/**
|
|
719
720
|
* Registered consumers for binary WebSocket frames. SyncEngine itself
|
|
@@ -986,6 +987,10 @@ export class SyncEngine {
|
|
|
986
987
|
clearInterval(this.pollTimer);
|
|
987
988
|
this.pollTimer = null;
|
|
988
989
|
}
|
|
990
|
+
if (this.pingTimer) {
|
|
991
|
+
clearInterval(this.pingTimer);
|
|
992
|
+
this.pingTimer = null;
|
|
993
|
+
}
|
|
989
994
|
if (this.visibilityHandler && typeof document !== "undefined") {
|
|
990
995
|
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
991
996
|
this.visibilityHandler = null;
|
|
@@ -1033,6 +1038,27 @@ export class SyncEngine {
|
|
|
1033
1038
|
this.reconnectAttempts = 0;
|
|
1034
1039
|
this.wsStableTimer = null;
|
|
1035
1040
|
}, 5_000);
|
|
1041
|
+
// Client-side keepalive ping. The server's per-client reader
|
|
1042
|
+
// thread blocks on a synchronous WS read for as long as no client
|
|
1043
|
+
// message arrives. On the HTTP-multiplexed `/api/sync/ws` path
|
|
1044
|
+
// tiny_http doesn't expose stream-level read timeouts, so the
|
|
1045
|
+
// reader's mutex hold is bounded only by client activity. Without
|
|
1046
|
+
// these pings the broadcaster contends for the same mutex and
|
|
1047
|
+
// wedges — Insert events never reach the tab. A 1s cadence makes
|
|
1048
|
+
// worst-case broadcast latency ~1s even when the user is idle.
|
|
1049
|
+
// Browsers don't expose WebSocket-level PING frames; a JSON
|
|
1050
|
+
// payload with type:"ping" achieves the same effect (the server
|
|
1051
|
+
// reads, looks up an unknown "ping" type, loops back releasing
|
|
1052
|
+
// the mutex; broadcaster grabs it during the gap).
|
|
1053
|
+
if (this.pingTimer) clearInterval(this.pingTimer);
|
|
1054
|
+
this.pingTimer = setInterval(() => {
|
|
1055
|
+
if (this.ws?.readyState !== WebSocket.OPEN) return;
|
|
1056
|
+
try {
|
|
1057
|
+
this.ws.send('{"type":"ping"}');
|
|
1058
|
+
} catch {
|
|
1059
|
+
// ignore — onclose will trigger reconnect
|
|
1060
|
+
}
|
|
1061
|
+
}, 1_000);
|
|
1036
1062
|
// Re-send any active CRDT subscriptions across the new socket.
|
|
1037
1063
|
// The server purged them on disconnect (`unsubscribe_all`), so
|
|
1038
1064
|
// without this resync a tab that was subscribed before a network
|
|
@@ -1162,6 +1188,10 @@ export class SyncEngine {
|
|
|
1162
1188
|
clearTimeout(this.wsStableTimer);
|
|
1163
1189
|
this.wsStableTimer = null;
|
|
1164
1190
|
}
|
|
1191
|
+
if (this.pingTimer) {
|
|
1192
|
+
clearInterval(this.pingTimer);
|
|
1193
|
+
this.pingTimer = null;
|
|
1194
|
+
}
|
|
1165
1195
|
// Surface the disconnect to UI consumers immediately. If
|
|
1166
1196
|
// `running` flipped to false (engine stopped), `stop()` already
|
|
1167
1197
|
// set "offline" — don't override that.
|