mirror of
https://github.com/morluto/rea
synced 2026-10-28 23:11:11 -04:00
d23dc5d188
Require explicit shape output admission before allocating expanded paths. Share capture retention across console entries, WebSocket frames and selected request/response shapes, with exact shape replacement and removal receipts. Preserve malformed-input versus capacity outcomes and peer facts. Validated deep, wide, Unicode, repeated-row, replacement, redirect, reset and mixed-retention regressions.
146 lines
4.8 KiB
TypeScript
146 lines
4.8 KiB
TypeScript
import { inferJsonShape, type AdmittedJsonShape } from "../domain/jsonShape.js";
|
|
import { WEB_RUNTIME_LIMITS } from "../domain/webRuntime.js";
|
|
import { decodePaddedBase64 } from "../domain/paddedBase64.js";
|
|
import {
|
|
recordValue,
|
|
cdpStringValue,
|
|
type UnknownRecord,
|
|
} from "./CdpCaptureValues.js";
|
|
import { isJsonContentType } from "./CdpCaptureEventHelpers.js";
|
|
import type { CdpCaptureEventsState } from "./CdpCaptureEventState.js";
|
|
import type { NetworkState } from "./CdpCaptureEventTypes.js";
|
|
|
|
export const ingestResponseBodyShape = (
|
|
state: CdpCaptureEventsState,
|
|
requestId: string,
|
|
value: unknown,
|
|
): void => {
|
|
if (!state.network.has(requestId)) return;
|
|
const result = recordValue(value);
|
|
const body = cdpStringValue(result?.body);
|
|
const base64Encoded = result?.base64Encoded;
|
|
if (body === undefined || typeof base64Encoded !== "boolean") {
|
|
invalidResponseBodyShape(state, requestId);
|
|
return;
|
|
}
|
|
const decoded = base64Encoded ? decodePaddedBase64(body) : body;
|
|
if (decoded === undefined) {
|
|
invalidResponseBodyShape(state, requestId);
|
|
return;
|
|
}
|
|
const previousBytes =
|
|
state.bodyShapeBytesByRequestId.get(requestId)?.response ?? 0;
|
|
const parsed = inferJsonShape(
|
|
decoded,
|
|
WEB_RUNTIME_LIMITS.retainedEventBytes -
|
|
state.retainedEventBytes +
|
|
previousBytes,
|
|
);
|
|
if (!parsed.ok) {
|
|
if (parsed.error.kind === "capacity")
|
|
state.completeness.exclude(
|
|
"json_body_shapes",
|
|
"resource_budget_exhausted",
|
|
);
|
|
else state.completeness.unavailable("json_body_shapes");
|
|
}
|
|
updateResponseBodyShape(state, requestId, parsed.ok ? parsed.value : null);
|
|
};
|
|
|
|
export const requestBodyShape = (
|
|
state: CdpCaptureEventsState,
|
|
requestId: string,
|
|
request: UnknownRecord,
|
|
): NetworkState["body_shapes"] => {
|
|
releaseBodyShapes(state, requestId);
|
|
if (!state.input.include_json_body_shapes)
|
|
return { status: "not_approved", request: null, response: null };
|
|
const headers = recordValue(request.headers);
|
|
const json = isJsonContentType(headers);
|
|
const body = cdpStringValue(request.postData);
|
|
// CDP may omit a long body from postData while reporting hasPostData.
|
|
const parsed =
|
|
json && body !== undefined
|
|
? inferJsonShape(
|
|
body,
|
|
WEB_RUNTIME_LIMITS.retainedEventBytes - state.retainedEventBytes,
|
|
)
|
|
: undefined;
|
|
const shape = parsed?.ok ? parsed.value.shape : null;
|
|
const expected = json && (body !== undefined || request.hasPostData === true);
|
|
if (expected && shape === null) {
|
|
state.missingRequestBodyShapeIds.add(requestId);
|
|
if (parsed !== undefined && !parsed.ok && parsed.error.kind === "capacity")
|
|
state.completeness.exclude(
|
|
"json_body_shapes",
|
|
"resource_budget_exhausted",
|
|
);
|
|
else state.completeness.unavailable("json_body_shapes");
|
|
}
|
|
if (parsed?.ok) {
|
|
state.bodyShapeBytesByRequestId.set(requestId, {
|
|
request: parsed.value.serializedBytes,
|
|
response: 0,
|
|
});
|
|
state.retainedEventBytes += parsed.value.serializedBytes;
|
|
}
|
|
return {
|
|
status: shape === null ? "unavailable" : "included",
|
|
request: shape,
|
|
response: null,
|
|
};
|
|
};
|
|
|
|
export const updateResponseBodyShape = (
|
|
state: CdpCaptureEventsState,
|
|
requestId: string,
|
|
response: AdmittedJsonShape | null,
|
|
): void => {
|
|
const current = state.network.get(requestId);
|
|
if (current === undefined) return;
|
|
const previous = state.bodyShapeBytesByRequestId.get(requestId);
|
|
const responseBytes = response?.serializedBytes ?? 0;
|
|
const requestBytes = previous?.request ?? 0;
|
|
state.retainedEventBytes += responseBytes - (previous?.response ?? 0);
|
|
if (requestBytes + responseBytes === 0)
|
|
state.bodyShapeBytesByRequestId.delete(requestId);
|
|
else
|
|
state.bodyShapeBytesByRequestId.set(requestId, {
|
|
request: requestBytes,
|
|
response: responseBytes,
|
|
});
|
|
const request = current.body_shapes.request;
|
|
const status =
|
|
response !== null
|
|
? state.missingRequestBodyShapeIds.has(requestId)
|
|
? "partial"
|
|
: "included"
|
|
: request !== null
|
|
? "partial"
|
|
: "unavailable";
|
|
state.network.set(requestId, {
|
|
...current,
|
|
body_shapes: { status, request, response: response?.shape ?? null },
|
|
});
|
|
};
|
|
|
|
/** Replacing or excluding a network record retires exactly the shapes it retained. */
|
|
export const releaseBodyShapes = (
|
|
state: CdpCaptureEventsState,
|
|
requestId: string,
|
|
): void => {
|
|
state.missingRequestBodyShapeIds.delete(requestId);
|
|
const previous = state.bodyShapeBytesByRequestId.get(requestId);
|
|
if (previous === undefined) return;
|
|
state.retainedEventBytes -= previous.request + previous.response;
|
|
state.bodyShapeBytesByRequestId.delete(requestId);
|
|
};
|
|
|
|
const invalidResponseBodyShape = (
|
|
state: CdpCaptureEventsState,
|
|
requestId: string,
|
|
): void => {
|
|
updateResponseBodyShape(state, requestId, null);
|
|
state.completeness.exclude("json_body_shapes", "invalid_protocol_value");
|
|
};
|