Files
orca/src/shared/remote-runtime-shared-control-reconnect.ts
T
Brennan BensonandMerge Sim 91cc834584 fix(remote): preserve standing host reconnect intent (#17067)
* fix(remote): preserve standing host reconnect intent

* chore(lint): merge duplicate imports flagged by the native code-quality audit

* fix(remote): fence stale capability runtime identities

* fix(remote): release capability evidence on host removal

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-30 21:17:06 -07:00

101 lines
2.6 KiB
TypeScript

import { scheduleSharedControlReconnect } from './remote-runtime-shared-control-state'
const ACTIVE_RECONNECT_DELAYS_MS = [250, 500, 1000, 2000, 4000, 8000, 15_000, 30_000]
const IDLE_RECONNECT_DELAYS_MS = [...ACTIVE_RECONNECT_DELAYS_MS, 60_000, 120_000, 300_000]
export class SharedControlReconnectScheduler {
private timer: ReturnType<typeof setTimeout> | null = null
private attempt = 0
private pendingOpen: (() => void) | null = null
get isScheduled(): boolean {
return this.timer !== null
}
get attemptCount(): number {
return this.attempt
}
schedule(args: {
intentionallyClosed: boolean
delaysMs: readonly number[]
open: () => void
}): void {
if (this.timer || args.intentionallyClosed) {
return
}
// Why: a passive subscription owns recovery until its caller closes it; roaming outages are unbounded.
this.pendingOpen = args.open
const scheduled = scheduleSharedControlReconnect({
...args,
current: this.timer,
reconnectAttempt: this.attempt,
open: () => {
this.timer = null
this.pendingOpen = null
args.open()
}
})
this.timer = scheduled.timer
this.attempt = scheduled.reconnectAttempt
}
scheduleWithDefaultBackoff(intentionallyClosed: boolean, open: () => void): void {
this.schedule({
intentionallyClosed,
delaysMs: ACTIVE_RECONNECT_DELAYS_MS,
open
})
}
scheduleWithIdleBackoff(intentionallyClosed: boolean, open: () => void): void {
this.schedule({ intentionallyClosed, delaysMs: IDLE_RECONNECT_DELAYS_MS, open })
}
scheduleAfterSocketClose(args: {
intentionallyClosed: boolean
manuallyDisconnected: boolean
capabilityPaused: boolean
subscriptionCount: number
open: () => void
}): void {
if (
args.intentionallyClosed ||
args.manuallyDisconnected ||
(args.subscriptionCount === 0 && args.capabilityPaused)
) {
return
}
if (args.subscriptionCount > 0) {
this.scheduleWithDefaultBackoff(args.intentionallyClosed, args.open)
return
}
this.scheduleWithIdleBackoff(args.intentionallyClosed, args.open)
}
// Why: OS resume / browser online should advance an already-scheduled reconnect, not start a new one.
retryNow(): boolean {
if (!this.timer || !this.pendingOpen) {
return false
}
clearTimeout(this.timer)
this.timer = null
const open = this.pendingOpen
this.pendingOpen = null
open()
return true
}
clear(): void {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
this.pendingOpen = null
}
resetAttempt(): void {
this.attempt = 0
}
}