feat(push): smart push queueing with page-visibility fast path and app-ping/pong fallback

This commit is contained in:
2026-06-04 22:10:48 -04:00
parent fc0527e9e7
commit 4e6dfb4726
16 changed files with 192 additions and 60 deletions
+14 -3
View File
@@ -41,6 +41,8 @@ export class WsClient {
if (this.activeSessionId) {
this.send({ type: WS.RECONNECT, sessionId: this.activeSessionId, adapter: this.activeAdapter });
}
// Tell server our current visibility state immediately on (re)connect
this.send({ type: WS.PAGE_VISIBILITY, visible: document.visibilityState === 'visible' });
};
this.ws.onmessage = (event) => {
@@ -50,6 +52,14 @@ export class WsClient {
this.activeSessionId = msg.sessionId;
if (msg.adapter) this.activeAdapter = msg.adapter;
}
// Respond to server app-ping only when page is visible.
// If hidden, no pong → server fires push after 2s timeout.
if (msg.type === WS.APP_PING) {
if (document.visibilityState === 'visible') {
this.send({ type: WS.APP_PONG, sessionId: msg.sessionId });
}
return;
}
this.onMessage(msg);
} catch (err) {
console.error('[ws] Failed to parse message:', err);
@@ -109,12 +119,13 @@ export class WsClient {
private _startVisibilityWatch() {
if (this.visibilityHandler) return;
this.visibilityHandler = () => {
if (document.visibilityState === 'hidden') {
const visible = document.visibilityState === 'visible';
this.send({ type: WS.PAGE_VISIBILITY, visible });
if (!visible) {
this.hiddenSince = Date.now();
} else if (document.visibilityState === 'visible' && this.shouldReconnect) {
} else if (this.shouldReconnect) {
const elapsed = this.hiddenSince ? Date.now() - this.hiddenSince : 0;
this.hiddenSince = null;
// Only force reconnect if page was hidden long enough for the WS to go stale
if (elapsed >= VISIBILITY_RECONNECT_THRESHOLD) {
this.forceReconnect();
}