mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 05:32:19 +03:00
feat(inspector-ui): post snapshots to session requests endpoint while recording (R5-5 frontend half)
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { WsEvent } from "@/mitm/inspector/types";
|
||||
|
||||
const WS_PATH = "/api/tools/traffic-inspector/ws";
|
||||
const SNAPSHOT_FLUSH_MS = 500;
|
||||
const SNAPSHOT_FLUSH_BATCH = 10;
|
||||
|
||||
export interface SessionInfo {
|
||||
id: string;
|
||||
@@ -24,6 +29,10 @@ export function useSessionRecorder() {
|
||||
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const startTimeRef = useRef<number>(0);
|
||||
const mountedRef = useRef(true);
|
||||
const recordingWsRef = useRef<WebSocket | null>(null);
|
||||
const recordingSessionRef = useRef<SessionInfo | null>(null);
|
||||
const pendingSnapshotsRef = useRef<string[]>([]);
|
||||
const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
mountedRef.current = true;
|
||||
@@ -54,27 +63,101 @@ export function useSessionRecorder() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const start = useCallback(async (name?: string) => {
|
||||
try {
|
||||
const res = await fetch("/api/tools/traffic-inspector/sessions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const data = (await res.json()) as { session: SessionInfo };
|
||||
setSession(data.session);
|
||||
setRecording(true);
|
||||
startTimeRef.current = Date.now();
|
||||
setElapsed(0);
|
||||
timerRef.current = setInterval(() => {
|
||||
setElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
|
||||
}, 1000);
|
||||
} catch {
|
||||
// ignore
|
||||
const flushSnapshots = useCallback(async (sessionId: string) => {
|
||||
if (pendingSnapshotsRef.current.length === 0) return;
|
||||
const batch = pendingSnapshotsRef.current.splice(0, pendingSnapshotsRef.current.length);
|
||||
for (const payload of batch) {
|
||||
try {
|
||||
await fetch(
|
||||
`/api/tools/traffic-inspector/sessions/${encodeURIComponent(sessionId)}/requests`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ payload }),
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
// best-effort: don't break recording UI on network failure
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const scheduleFlush = useCallback(
|
||||
(sessionId: string) => {
|
||||
if (flushTimerRef.current) return;
|
||||
flushTimerRef.current = setTimeout(() => {
|
||||
flushTimerRef.current = null;
|
||||
void flushSnapshots(sessionId);
|
||||
}, SNAPSHOT_FLUSH_MS);
|
||||
},
|
||||
[flushSnapshots]
|
||||
);
|
||||
|
||||
const stopRecordingWs = useCallback(() => {
|
||||
if (flushTimerRef.current) {
|
||||
clearTimeout(flushTimerRef.current);
|
||||
flushTimerRef.current = null;
|
||||
}
|
||||
if (recordingWsRef.current) {
|
||||
recordingWsRef.current.onclose = null;
|
||||
recordingWsRef.current.close();
|
||||
recordingWsRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const start = useCallback(
|
||||
async (name?: string) => {
|
||||
try {
|
||||
const res = await fetch("/api/tools/traffic-inspector/sessions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const data = (await res.json()) as { session: SessionInfo };
|
||||
const newSession = data.session;
|
||||
setSession(newSession);
|
||||
recordingSessionRef.current = newSession;
|
||||
setRecording(true);
|
||||
startTimeRef.current = Date.now();
|
||||
setElapsed(0);
|
||||
timerRef.current = setInterval(() => {
|
||||
setElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
|
||||
}, 1000);
|
||||
|
||||
// Open a dedicated WS to capture traffic events during the recording window
|
||||
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const wsUrl = `${proto}//${window.location.host}${WS_PATH}`;
|
||||
const ws = new WebSocket(wsUrl);
|
||||
recordingWsRef.current = ws;
|
||||
|
||||
ws.onmessage = (ev: MessageEvent) => {
|
||||
if (!mountedRef.current) return;
|
||||
let event: WsEvent;
|
||||
try {
|
||||
event = JSON.parse(ev.data as string) as WsEvent;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (event.type !== "new") return;
|
||||
const sid = recordingSessionRef.current?.id;
|
||||
if (!sid) return;
|
||||
pendingSnapshotsRef.current.push(JSON.stringify(event.data));
|
||||
if (pendingSnapshotsRef.current.length >= SNAPSHOT_FLUSH_BATCH) {
|
||||
void flushSnapshots(sid);
|
||||
} else {
|
||||
scheduleFlush(sid);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = () => ws.close();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
[flushSnapshots, scheduleFlush]
|
||||
);
|
||||
|
||||
const stop = useCallback(async () => {
|
||||
if (!session) return;
|
||||
if (timerRef.current) {
|
||||
@@ -82,8 +165,15 @@ export function useSessionRecorder() {
|
||||
timerRef.current = null;
|
||||
}
|
||||
setRecording(false);
|
||||
// Flush any remaining pending snapshots before stopping
|
||||
const sid = session.id;
|
||||
stopRecordingWs();
|
||||
if (pendingSnapshotsRef.current.length > 0) {
|
||||
await flushSnapshots(sid);
|
||||
}
|
||||
recordingSessionRef.current = null;
|
||||
try {
|
||||
await fetch(`/api/tools/traffic-inspector/sessions/${encodeURIComponent(session.id)}`, {
|
||||
await fetch(`/api/tools/traffic-inspector/sessions/${encodeURIComponent(sid)}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ action: "stop" }),
|
||||
@@ -93,7 +183,7 @@ export function useSessionRecorder() {
|
||||
}
|
||||
await fetchSessions();
|
||||
setSession(null);
|
||||
}, [session, fetchSessions]);
|
||||
}, [session, fetchSessions, stopRecordingWs, flushSnapshots]);
|
||||
|
||||
const deleteSession = useCallback(async (id: string) => {
|
||||
try {
|
||||
@@ -109,6 +199,11 @@ export function useSessionRecorder() {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
if (flushTimerRef.current) clearTimeout(flushTimerRef.current);
|
||||
if (recordingWsRef.current) {
|
||||
recordingWsRef.current.onclose = null;
|
||||
recordingWsRef.current.close();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user