mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 05:32:19 +03:00
feat(db,inspector): add snapshotSession + restore hookBufferUpdate spec contract (M1+M2)
- Add `snapshotSession(sessionId)` to inspectorSessions.ts per master-plan §3.8: returns parsed InterceptedRequest[] in seq order, or null for non-existent sessions. Silently skips rows that fail InterceptedRequestSchema validation (defensive). - Restore canonical no-arg form of `MitmHandlerBase.hookBufferUpdate(intercepted)` per master-plan §3.5: when opts is omitted, derive completion fields from the intercepted object itself (status/responseHeaders/responseBody/responseSize/ *LatencyMs) rather than no-op'ing. Extended opts form preserved for legacy callers. - Update Zod record() calls in InterceptedRequestSchema to current (key,value) signature. - Add 3 unit tests for snapshotSession (happy path / non-existent / silent skip). - Add 2 unit tests for hookBufferUpdate (no-arg form + extended opts form).
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import { getDbInstance } from "./core";
|
||||
import type { InspectorSessionRow } from "./_rowTypes";
|
||||
import { InterceptedRequestSchema } from "../../mitm/inspector/types";
|
||||
import type { InterceptedRequest } from "../../mitm/inspector/types";
|
||||
|
||||
interface InspectorSessionDbRow {
|
||||
id: string;
|
||||
@@ -115,3 +117,41 @@ export function deleteSession(id: string): void {
|
||||
// Cascade via FK ON DELETE CASCADE for inspector_session_requests
|
||||
db.prepare("DELETE FROM inspector_sessions WHERE id = ?").run(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a parsed + validated snapshot of all requests for the given session,
|
||||
* sorted by ascending seq.
|
||||
*
|
||||
* Returns null when the session does not exist.
|
||||
* Rows whose payload fails InterceptedRequestSchema validation are silently
|
||||
* skipped (defensive — protects callers from corrupt/partial rows).
|
||||
*
|
||||
* Satisfies master-plan §3.8 (F2 spec) named-export contract.
|
||||
*/
|
||||
export function snapshotSession(sessionId: string): InterceptedRequest[] | null {
|
||||
// 1. Verify session exists.
|
||||
const session = getSession(sessionId);
|
||||
if (session === null) return null;
|
||||
|
||||
// 2. Retrieve raw rows (already ordered by seq ASC).
|
||||
const rawRows = getSessionRequests(sessionId);
|
||||
|
||||
// 3. Parse each payload JSON, validate via Zod schema, skip bad rows.
|
||||
const results: InterceptedRequest[] = [];
|
||||
for (const row of rawRows) {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(row.payload);
|
||||
} catch {
|
||||
// Corrupt JSON — skip.
|
||||
continue;
|
||||
}
|
||||
const result = InterceptedRequestSchema.safeParse(parsed);
|
||||
if (result.success) {
|
||||
results.push(result.data as InterceptedRequest);
|
||||
}
|
||||
// Invalid rows are silently skipped per defensive contract.
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -250,6 +250,13 @@ export abstract class MitmHandlerBase {
|
||||
/**
|
||||
* Update a previously published Traffic Inspector entry with completion data.
|
||||
* No-op when the inspector module is not present.
|
||||
*
|
||||
* Per master-plan §3.5: the canonical no-arg form `hookBufferUpdate(intercepted)`
|
||||
* must update the buffer using completion fields already present on `intercepted`
|
||||
* (status, responseBody, responseHeaders, responseSize, *LatencyMs). When the
|
||||
* extended `opts` form is used (legacy internal callers), it overrides those
|
||||
* fields explicitly. Both forms route through `recordRequestComplete` so the
|
||||
* inspector receives a consistent shape.
|
||||
*/
|
||||
protected hookBufferUpdate(
|
||||
intercepted: InterceptedRequest,
|
||||
@@ -262,11 +269,18 @@ export abstract class MitmHandlerBase {
|
||||
upstreamLatencyMs: number;
|
||||
},
|
||||
): void {
|
||||
if (!opts) return;
|
||||
const finalOpts = opts ?? {
|
||||
status: typeof intercepted.status === "number" ? intercepted.status : 0,
|
||||
responseHeaders: intercepted.responseHeaders,
|
||||
responseBody: intercepted.responseBody,
|
||||
responseSize: intercepted.responseSize,
|
||||
proxyLatencyMs: intercepted.proxyLatencyMs ?? 0,
|
||||
upstreamLatencyMs: intercepted.upstreamLatencyMs ?? 0,
|
||||
};
|
||||
void loadAgentBridgeHook().then((hook) => {
|
||||
if (hook?.recordRequestComplete) {
|
||||
try {
|
||||
hook.recordRequestComplete(intercepted, opts);
|
||||
hook.recordRequestComplete(intercepted, finalOpts);
|
||||
} catch {
|
||||
// Hook should never break interception.
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ export const InterceptedRequestSchema = z.object({
|
||||
method: z.string(),
|
||||
host: z.string(),
|
||||
path: z.string(),
|
||||
requestHeaders: z.record(z.string()),
|
||||
requestHeaders: z.record(z.string(), z.string()),
|
||||
requestBody: z.string().nullable(),
|
||||
requestSize: z.number().int().nonnegative(),
|
||||
responseHeaders: z.record(z.string()),
|
||||
responseHeaders: z.record(z.string(), z.string()),
|
||||
responseBody: z.string().nullable(),
|
||||
responseSize: z.number().int().nonnegative(),
|
||||
status: z.union([z.number().int(), z.literal("in-flight"), z.literal("error")]),
|
||||
|
||||
Reference in New Issue
Block a user