Integration review (#168): a rejected attach handshake — a dormant session's 409 not_attachable, most likely — looks identical to a dropped stream from the WebSocket API (close code 1006, no reason). shouldRetryClose kept retrying it on the catalog's Running/Dead check alone, which loops for as long as the catalog hasn't caught up with the session going dormant. Track whether the socket ever reached `open`; a handshake that never did is a rejection, not a blip, so it is never retried regardless of catalog status. The caller (wired up in the next commit) gets a distinct onNeverAttached callback instead of onClosed, so it can offer the resume affordance rather than assuming the session is gone.
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
// Attach-socket reconnect policy. Split out of attach.ts so it can be tested
|
|
// without a DOM or an xterm instance.
|
|
|
|
import type { Session, Uuid } from "./types.js";
|
|
|
|
/** First retry delay after an unexpected close, doubling up to the cap. */
|
|
export const INITIAL_RECONNECT_MS = 500;
|
|
export const MAX_RECONNECT_MS = 10_000;
|
|
|
|
/** Close codes at or above this are application-defined; the daemon uses
|
|
* them to signal a rejection, which retrying would only loop on. */
|
|
const APPLICATION_CLOSE = 4000;
|
|
|
|
/** Normal Closure — the peer ended the stream deliberately. */
|
|
const CLEAN_CLOSE = 1000;
|
|
|
|
/**
|
|
* Should a closed attach socket be retried?
|
|
*
|
|
* The catalog is the authority: a session still `Running` lost its link, so
|
|
* retry; one that is `Dead` has had its PTY stream sealed and will never send
|
|
* again, and one absent from the catalog is gone. Before this existed, any
|
|
* dropped frame destroyed the pane permanently.
|
|
*/
|
|
export function shouldRetryClose(
|
|
sessionId: Uuid,
|
|
code: number,
|
|
sessions: ReadonlyMap<Uuid, Session>,
|
|
/** Did this socket ever reach `open`? A handshake that never completed is
|
|
* the daemon rejecting the attach (dormant/unknown) rather than a dropped
|
|
* stream — retrying it spins in place until the catalog updates, which for
|
|
* a dormant session it never will (#168 review). Defaults to `true` so
|
|
* existing callers that only track drops of an established link are
|
|
* unaffected. */
|
|
everOpened = true,
|
|
): boolean {
|
|
if (code >= APPLICATION_CLOSE || code === CLEAN_CLOSE) return false;
|
|
if (!everOpened) return false;
|
|
return sessions.get(sessionId)?.status === "Running";
|
|
}
|