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:
jwcrystal
2026-04-29 12:19:31 +03:00
committed by GitHub
parent 21253d7fc2
commit 9424cff02c
3 changed files with 138 additions and 1 deletions
+19
View File
@@ -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()
}