fix: reconnect SSE immediately on OS wake-from-sleep (#1066)
* fix: reconnect SSE immediately on OS wake-from-sleep When the desktop app resumes from OS sleep, TCP connections are dead but timers were paused during sleep so the heartbeat watchdog doesn't fire until ~30s after wake. Add Electron powerMonitor.resume → renderer notification → event-pipeline immediate abort, cutting reconnection delay from ~30s to ~0ms. Changes: - electron/main.mjs: import powerMonitor, emit openchamber:system-resume to all renderer windows on OS resume - ui/sync/event-pipeline.ts: listen for openchamber:system-resume, set attemptAbortReason and abort the active SSE/WS attempt to trigger immediate reconnection with retryDelayMs=0 and lastEventId preservation * fix: reconnect SSE immediately on OS wake-from-sleep When the desktop app resumes from OS sleep, TCP connections are dead but timers were paused during sleep so the heartbeat watchdog doesn't fire until ~30s after wake. Add Electron powerMonitor.resume → renderer notification → event-pipeline immediate abort, cutting reconnection delay from ~30s to ~0ms. Changes: - electron/main.mjs: import powerMonitor, emit openchamber:system-resume to all renderer windows on OS resume - ui/sync/event-pipeline.ts: listen for openchamber:system-resume via globalThis.window, set attemptAbortReason and abort the active SSE/WS attempt to trigger immediate reconnection with retryDelayMs=0 and lastEventId preservation - Test: event-pipeline-resume.test.js verifies abort → reconnect flow
This commit is contained in:
@@ -152,6 +152,8 @@ type AttemptAbortReason =
|
||||
| "pipeline_stopped"
|
||||
| "ws_heartbeat_timeout"
|
||||
| "sse_heartbeat_timeout"
|
||||
| "ws_system_resume"
|
||||
| "sse_system_resume"
|
||||
| null
|
||||
|
||||
export function createEventPipeline(input: EventPipelineInput) {
|
||||
@@ -616,16 +618,33 @@ export function createEventPipeline(input: EventPipelineInput) {
|
||||
attempt?.abort()
|
||||
}
|
||||
|
||||
// OS wake-from-sleep (Electron powerMonitor.resume). The SSE connection
|
||||
// is almost certainly dead after sleep — abort immediately so the
|
||||
// reconnect loop fires on the next tick with retryDelayMs = 0.
|
||||
const onSystemResume = () => {
|
||||
attemptAbortReason = `${activeTransport}_system_resume`
|
||||
attempt?.abort()
|
||||
}
|
||||
|
||||
if (typeof document !== "undefined") {
|
||||
document.addEventListener("visibilitychange", onVisibility)
|
||||
window.addEventListener("pageshow", onPageShow)
|
||||
}
|
||||
|
||||
// Use globalThis (not window) for the system-resume listener so that
|
||||
// test environments can replace globalThis.window with a stub.
|
||||
if (typeof globalThis.window !== "undefined") {
|
||||
globalThis.window.addEventListener("openchamber:system-resume", onSystemResume)
|
||||
}
|
||||
|
||||
const cleanup = () => {
|
||||
if (typeof document !== "undefined") {
|
||||
document.removeEventListener("visibilitychange", onVisibility)
|
||||
window.removeEventListener("pageshow", onPageShow)
|
||||
}
|
||||
if (typeof globalThis.window !== "undefined") {
|
||||
globalThis.window.removeEventListener("openchamber:system-resume", onSystemResume)
|
||||
}
|
||||
abort.abort()
|
||||
flushAll()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user