relay: add control channel and heartbeat stream
This commit is contained in:
@@ -1,73 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
|
||||
export type HeartbeatEvent = {
|
||||
type: "heartbeat";
|
||||
ts: number; // epoch ms
|
||||
status: "sent" | "ok-empty" | "ok-token" | "skipped" | "failed";
|
||||
to?: string;
|
||||
preview?: string;
|
||||
durationMs?: number;
|
||||
hasMedia?: boolean;
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
const EVENT_FILENAME = "heartbeat-events.jsonl";
|
||||
const STATE_FILENAME = "heartbeat-state.json";
|
||||
|
||||
function baseDir() {
|
||||
const dir = path.join(os.homedir(), ".clawdis");
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
return dir;
|
||||
}
|
||||
|
||||
export function heartbeatEventsPath() {
|
||||
return path.join(baseDir(), EVENT_FILENAME);
|
||||
}
|
||||
|
||||
export function heartbeatStatePath() {
|
||||
return path.join(baseDir(), STATE_FILENAME);
|
||||
}
|
||||
|
||||
export function writeHeartbeatEvent(evt: HeartbeatEvent) {
|
||||
const line = JSON.stringify(evt);
|
||||
fs.appendFileSync(heartbeatEventsPath(), `${line}\n`, { encoding: "utf8" });
|
||||
fs.writeFileSync(heartbeatStatePath(), line, { encoding: "utf8" });
|
||||
}
|
||||
|
||||
export function readLatestHeartbeat(): HeartbeatEvent | null {
|
||||
try {
|
||||
const txt = fs.readFileSync(heartbeatStatePath(), "utf8");
|
||||
return JSON.parse(txt) as HeartbeatEvent;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Tail the events file and invoke the callback for every new parsed event.
|
||||
export function tailHeartbeatEvents(onEvent: (evt: HeartbeatEvent) => void) {
|
||||
const file = heartbeatEventsPath();
|
||||
if (!fs.existsSync(file)) {
|
||||
fs.writeFileSync(file, "", { encoding: "utf8" });
|
||||
}
|
||||
|
||||
const stream = fs.createReadStream(file, { encoding: "utf8", flags: "a+" });
|
||||
const rl = readline.createInterface({ input: stream });
|
||||
rl.on("line", (line) => {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) return;
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed) as HeartbeatEvent;
|
||||
if (parsed?.type === "heartbeat") onEvent(parsed);
|
||||
} catch {
|
||||
// ignore malformed
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
rl.close();
|
||||
stream.close();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user