first commit
This commit is contained in:
97
apps/gateway/src/debug/terminalFrameCapture.ts
Normal file
97
apps/gateway/src/debug/terminalFrameCapture.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
export type TerminalCaptureFrameType = "stdin" | "stdout" | "stderr";
|
||||
|
||||
export interface TerminalFrameCaptureRecorder {
|
||||
filePath: string;
|
||||
record(type: TerminalCaptureFrameType, data: string): void;
|
||||
close(reason?: string): void;
|
||||
}
|
||||
|
||||
interface TerminalFrameCaptureMeta {
|
||||
ip: string;
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
clientSessionKey?: string;
|
||||
captureDir?: string;
|
||||
}
|
||||
|
||||
function sanitizePathToken(input: string): string {
|
||||
return String(input || "")
|
||||
.trim()
|
||||
.replace(/[^a-zA-Z0-9._-]+/g, "_")
|
||||
.replace(/^_+|_+$/g, "") || "unknown";
|
||||
}
|
||||
|
||||
function resolveCaptureDir(preferredDir?: string): string {
|
||||
return String(preferredDir || process.env.RC_TERMINAL_CAPTURE_DIR || "").trim();
|
||||
}
|
||||
|
||||
export function createTerminalFrameCaptureRecorder(
|
||||
meta: TerminalFrameCaptureMeta
|
||||
): TerminalFrameCaptureRecorder | null {
|
||||
const captureDir = resolveCaptureDir(meta.captureDir);
|
||||
if (!captureDir) {
|
||||
return null;
|
||||
}
|
||||
|
||||
fs.mkdirSync(captureDir, { recursive: true });
|
||||
const startedAt = Date.now();
|
||||
const fileName = [
|
||||
new Date(startedAt).toISOString().replace(/[:.]/g, "-"),
|
||||
sanitizePathToken(meta.username),
|
||||
"@",
|
||||
sanitizePathToken(meta.host),
|
||||
`-${Number(meta.port) || 22}`,
|
||||
`-${randomUUID().slice(0, 8)}.jsonl`
|
||||
].join("");
|
||||
const filePath = path.join(captureDir, fileName);
|
||||
let closed = false;
|
||||
|
||||
const writeLine = (payload: Record<string, unknown>): void => {
|
||||
fs.appendFileSync(filePath, `${JSON.stringify(payload)}\n`, "utf8");
|
||||
};
|
||||
|
||||
writeLine({
|
||||
kind: "meta",
|
||||
version: 1,
|
||||
startedAt,
|
||||
ip: meta.ip,
|
||||
host: meta.host,
|
||||
port: meta.port,
|
||||
username: meta.username,
|
||||
clientSessionKey: meta.clientSessionKey || ""
|
||||
});
|
||||
|
||||
return {
|
||||
filePath,
|
||||
record(type, data) {
|
||||
if (closed || !data) {
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
writeLine({
|
||||
kind: "frame",
|
||||
at: now,
|
||||
offsetMs: Math.max(0, now - startedAt),
|
||||
type,
|
||||
data
|
||||
});
|
||||
},
|
||||
close(reason) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
writeLine({
|
||||
kind: "close",
|
||||
at: Date.now(),
|
||||
offsetMs: Math.max(0, Date.now() - startedAt),
|
||||
reason: reason || ""
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user