54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
import { WS_CODES, WsError } from "../error-codes/error-codes.js";
|
|
|
|
/**
|
|
* Policy describing the engine's ping/pong cadence and failure threshold.
|
|
*
|
|
* Defaults match design doc §11.4: ping every 30000ms, declare the
|
|
* connection dead after two consecutive missed pongs.
|
|
*/
|
|
export interface HeartbeatPolicy {
|
|
readonly heartbeatMs: number;
|
|
readonly maxMissedPongs: number;
|
|
}
|
|
|
|
/** Default policy applied when no override is supplied. */
|
|
export const DEFAULT_HEARTBEAT_POLICY: HeartbeatPolicy = Object.freeze({
|
|
heartbeatMs: 30000,
|
|
maxMissedPongs: 2,
|
|
});
|
|
|
|
/** Action returned by {@link evaluateHeartbeat}. */
|
|
export type HeartbeatAction = "ok" | "ping" | "abort";
|
|
|
|
/**
|
|
* Stateless evaluator: given the engine's current view of "ms since last
|
|
* pong" + "consecutive missed pongs", returns the action the engine must
|
|
* take next.
|
|
*
|
|
* - `ok` — within tolerance, nothing to do.
|
|
* - `ping` — heartbeat interval elapsed; engine should send a ping.
|
|
* - `abort` — `maxMissedPongs` reached; engine MUST close the connection
|
|
* with `ABNORMAL` and emit `WS.HEARTBEAT_FAILED`.
|
|
*/
|
|
export function evaluateHeartbeat(
|
|
msSinceLastPong: number,
|
|
consecutiveMissedPongs: number,
|
|
policy: HeartbeatPolicy = DEFAULT_HEARTBEAT_POLICY
|
|
): HeartbeatAction {
|
|
if (consecutiveMissedPongs >= policy.maxMissedPongs) {
|
|
return "abort";
|
|
}
|
|
if (msSinceLastPong >= policy.heartbeatMs) {
|
|
return "ping";
|
|
}
|
|
return "ok";
|
|
}
|
|
|
|
/** Helper that throws the canonical `WsError`. */
|
|
export function raiseHeartbeatFailed(connectionId: string): never {
|
|
throw new WsError(
|
|
WS_CODES.HEARTBEAT_FAILED,
|
|
`connection ${connectionId} failed heartbeat: two missed pongs`
|
|
);
|
|
}
|