{"version":3,"file":"heartbeat.cjs","names":["accessor: ConnectionAccessor","wakeSignal: WakeSignal","logger: Logger","WAKE_REASON","ensureUnsharedArrayBuffer","ConnectMessage","GatewayMessageType"],"sources":["../../../../../src/components/connect/strategies/core/heartbeat.ts"],"sourcesContent":["/**\n * Heartbeat management for the active WebSocket connection.\n *\n * Sends periodic heartbeat pings and marks the connection as dead when\n * two consecutive heartbeats go unacknowledged, waking the reconcile loop\n * to trigger reconnection.\n */\n\nimport type { Logger } from \"../../../../middleware/logger.ts\";\nimport {\n  ConnectMessage,\n  GatewayMessageType,\n} from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport { ensureUnsharedArrayBuffer } from \"../../buffer.ts\";\nimport {\n  type ConnectionAccessor,\n  WAKE_REASON,\n  type WakeSignal,\n} from \"./types.ts\";\n\nexport class HeartbeatManager {\n  private interval: ReturnType<typeof setInterval> | undefined;\n  private intervalMs = 10_000;\n  onHeartbeatSent: (() => void) | undefined;\n\n  constructor(\n    private readonly accessor: ConnectionAccessor,\n    private readonly wakeSignal: WakeSignal,\n    private readonly logger: Logger,\n  ) {}\n\n  /**\n   * Update the heartbeat interval. Restarts the timer if the interval changed\n   * or if it wasn't running yet.\n   */\n  updateInterval(ms: number): void {\n    if (ms === this.intervalMs && this.interval) return;\n    this.intervalMs = ms;\n    this.stop();\n    this.start();\n  }\n\n  /** Stop the heartbeat timer. */\n  stop(): void {\n    clearInterval(this.interval);\n    this.interval = undefined;\n  }\n\n  private start(): void {\n    if (this.interval) return;\n    this.interval = setInterval(() => this.tick(), this.intervalMs);\n  }\n\n  private tick(): void {\n    const conn = this.accessor.activeConnection;\n    if (!conn || conn.ws.readyState !== WebSocket.OPEN) return;\n\n    if (conn.pendingHeartbeats >= 2) {\n      this.logger.warn(\n        { connectionId: conn.id },\n        \"Consecutive heartbeats missed, reconnecting\",\n      );\n      conn.dead = true;\n      this.wakeSignal.wake(WAKE_REASON.HeartbeatMissed);\n      return;\n    }\n\n    conn.pendingHeartbeats++;\n    conn.ws.send(\n      ensureUnsharedArrayBuffer(\n        ConnectMessage.encode(\n          ConnectMessage.create({\n            kind: GatewayMessageType.WORKER_HEARTBEAT,\n          }),\n        ).finish(),\n      ),\n    );\n\n    this.logger.debug({ connectionId: conn.id }, \"Heartbeat sent\");\n    this.onHeartbeatSent?.();\n  }\n}\n"],"mappings":";;;;;AAoBA,IAAa,mBAAb,MAA8B;CAC5B,AAAQ;CACR,AAAQ,aAAa;CACrB;CAEA,YACE,AAAiBA,UACjB,AAAiBC,YACjB,AAAiBC,QACjB;EAHiB;EACA;EACA;;;;;;CAOnB,eAAe,IAAkB;AAC/B,MAAI,OAAO,KAAK,cAAc,KAAK,SAAU;AAC7C,OAAK,aAAa;AAClB,OAAK,MAAM;AACX,OAAK,OAAO;;;CAId,OAAa;AACX,gBAAc,KAAK,SAAS;AAC5B,OAAK,WAAW;;CAGlB,AAAQ,QAAc;AACpB,MAAI,KAAK,SAAU;AACnB,OAAK,WAAW,kBAAkB,KAAK,MAAM,EAAE,KAAK,WAAW;;CAGjE,AAAQ,OAAa;EACnB,MAAM,OAAO,KAAK,SAAS;AAC3B,MAAI,CAAC,QAAQ,KAAK,GAAG,eAAe,UAAU,KAAM;AAEpD,MAAI,KAAK,qBAAqB,GAAG;AAC/B,QAAK,OAAO,KACV,EAAE,cAAc,KAAK,IAAI,EACzB,8CACD;AACD,QAAK,OAAO;AACZ,QAAK,WAAW,KAAKC,0BAAY,gBAAgB;AACjD;;AAGF,OAAK;AACL,OAAK,GAAG,KACNC,yCACEC,+BAAe,OACbA,+BAAe,OAAO,EACpB,MAAMC,mCAAmB,kBAC1B,CAAC,CACH,CAAC,QAAQ,CACX,CACF;AAED,OAAK,OAAO,MAAM,EAAE,cAAc,KAAK,IAAI,EAAE,iBAAiB;AAC9D,OAAK,mBAAmB"}