Bug
When _healthCheck() finds the EventSource in CLOSED state, it creates a new EventSource but returns before clearing _hiddenSince. This causes a second reconnect when both visibilitychange and focus fire on tab return.
Steps to reproduce
- Open a page with
persistConnection: true
- Switch to another tab/app for >30 seconds (so
_hiddenSince exceeds the 30s threshold)
- The browser may silently close the EventSource while the tab is hidden
- Return to the tab
What happens
Both visibilitychange (visible) and focus call _onPageVisible() → _healthCheck():
- First call (
visibilitychange): _es.readyState === CLOSED → _es = null → _connect() creates new ES → returns early before _hiddenSince = null
- Second call (
focus): new ES exists, not CLOSED → checks _hiddenSince → stale value > 30s → _disconnect() kills the just-created ES → _connect() creates another ES
This double-connect causes two "connected" status events, which downstream code (in our case, E2EE session management) interprets as two reconnects, triggering duplicate teardown+reinit cycles.
Fix
Clear _hiddenSince in the CLOSED branch before returning:
_healthCheck() {
if (!this._es) {
this._connect();
return;
}
if (this._es.readyState === EventSource.CLOSED) {
this._hiddenSince = null; // ← Add this line
this._es = null;
this._connect();
return;
}
const hiddenDuration = this._hiddenSince
? Date.now() - this._hiddenSince
: 0;
this._hiddenSince = null;
if (hiddenDuration > 30000) {
this._disconnect();
this._connect();
}
}
Workaround
We're currently monkey-patching _healthCheck from the consuming app. Would love to remove that once this is fixed upstream.
Bug
When
_healthCheck()finds the EventSource inCLOSEDstate, it creates a new EventSource but returns before clearing_hiddenSince. This causes a second reconnect when bothvisibilitychangeandfocusfire on tab return.Steps to reproduce
persistConnection: true_hiddenSinceexceeds the 30s threshold)What happens
Both
visibilitychange(visible) andfocuscall_onPageVisible()→_healthCheck():visibilitychange):_es.readyState === CLOSED→_es = null→_connect()creates new ES → returns early before_hiddenSince = nullfocus): new ES exists, not CLOSED → checks_hiddenSince→ stale value > 30s →_disconnect()kills the just-created ES →_connect()creates another ESThis double-connect causes two
"connected"status events, which downstream code (in our case, E2EE session management) interprets as two reconnects, triggering duplicate teardown+reinit cycles.Fix
Clear
_hiddenSincein the CLOSED branch before returning:Workaround
We're currently monkey-patching
_healthCheckfrom the consuming app. Would love to remove that once this is fixed upstream.