This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/web/src/reconnect.ts
T
Miguel Palhas 938dd62648 fix(maestro): stop retrying a WS attach that never opened
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.
2026-09-04 09:19:09 +01:00

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";
}