mirror of
https://github.com/morluto/rea
synced 2026-10-27 22:45:19 -04:00
49b4edfe77
Share padded decoding across CDP response shapes and cache fingerprints, and count binary WebSocket bytes without allocating a decoded buffer. Preserve HAR canonical pad bits and validate before allocation. Replace repeated alphabet quartets in canonical schemas with separate alphabet, padding and fixed-width length assertions. Cover near-32MiB encodings under ordinary, Unicode and Unicode-set modes, plus complete 4MiB CLI and MCP retained-evidence export workflows. Fixes #5253
114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
import {
|
|
isHttpUrl,
|
|
numberValue,
|
|
recordValue,
|
|
recordsValue,
|
|
cdpStringValue,
|
|
type UnknownRecord,
|
|
} from "./CdpCaptureValues.js";
|
|
import type { CdpEvent } from "./CdpConnection.js";
|
|
|
|
/** Identify a top-level navigation event from its untrusted CDP envelope. */
|
|
export const isMainFrameNavigation = (
|
|
event: CdpEvent,
|
|
mainFrameId: string,
|
|
): boolean => {
|
|
const params = recordValue(event.params);
|
|
if (params === undefined) return false;
|
|
const frameId =
|
|
event.method === "Page.frameNavigated"
|
|
? cdpStringValue(recordValue(params.frame)?.id)
|
|
: event.method === "Page.frameRequestedNavigation" ||
|
|
event.method === "Page.navigatedWithinDocument"
|
|
? cdpStringValue(params.frameId)
|
|
: undefined;
|
|
return frameId === mainFrameId;
|
|
};
|
|
|
|
export const firstCallFrame = (
|
|
value: UnknownRecord | undefined,
|
|
): UnknownRecord | undefined => recordsValue(value?.callFrames)[0];
|
|
|
|
export const initiatorLocation = (
|
|
initiator: UnknownRecord | undefined,
|
|
): UnknownRecord | undefined => {
|
|
let stack = recordValue(initiator?.stack);
|
|
for (let depth = 0; depth < 32 && stack !== undefined; depth += 1) {
|
|
const frame = firstCallFrame(stack);
|
|
if (frame !== undefined) return frame;
|
|
stack = recordValue(stack.parent);
|
|
}
|
|
return cdpStringValue(initiator?.url) === undefined ? undefined : initiator;
|
|
};
|
|
|
|
export const exclusionReasonForUrl = (
|
|
value: string | undefined,
|
|
): "disallowed_origin" | "unsupported_url" | "unattributed_origin" =>
|
|
value === undefined || value === ""
|
|
? "unattributed_origin"
|
|
: isHttpUrl(value)
|
|
? "disallowed_origin"
|
|
: "unsupported_url";
|
|
|
|
export const integerOrNull = (value: unknown): number | null => {
|
|
const number = numberValue(value);
|
|
return number === undefined ? null : Math.max(0, Math.trunc(number));
|
|
};
|
|
|
|
export const executionContextKey = (value: unknown): string | null => {
|
|
const identifier = numberValue(value);
|
|
return identifier !== undefined && Number.isSafeInteger(identifier)
|
|
? String(identifier)
|
|
: null;
|
|
};
|
|
|
|
export const isJsonContentType = (
|
|
headers: UnknownRecord | undefined,
|
|
): boolean => {
|
|
if (headers === undefined) return false;
|
|
for (const [name, value] of Object.entries(headers))
|
|
if (
|
|
name.toLowerCase() === "content-type" &&
|
|
isJsonMediaType(cdpStringValue(value))
|
|
)
|
|
return true;
|
|
return false;
|
|
};
|
|
|
|
export const isJsonMediaType = (value: string | null | undefined): boolean => {
|
|
const mediaType = value?.split(";", 1)[0]?.trim().toLowerCase();
|
|
return (
|
|
mediaType === "application/json" || mediaType?.endsWith("+json") === true
|
|
);
|
|
};
|
|
|
|
export const consolePrimitive = (
|
|
value: UnknownRecord,
|
|
): { readonly type: string; readonly text: string } | undefined => {
|
|
const type = cdpStringValue(value.type);
|
|
switch (type) {
|
|
case "string":
|
|
return typeof value.value === "string"
|
|
? { type, text: value.value }
|
|
: undefined;
|
|
case "boolean":
|
|
return typeof value.value === "boolean"
|
|
? { type, text: String(value.value) }
|
|
: undefined;
|
|
case "number":
|
|
return typeof value.value === "number"
|
|
? { type, text: String(value.value) }
|
|
: typeof value.unserializableValue === "string"
|
|
? { type, text: value.unserializableValue }
|
|
: undefined;
|
|
case "bigint":
|
|
return typeof value.unserializableValue === "string"
|
|
? { type, text: value.unserializableValue }
|
|
: undefined;
|
|
case "undefined":
|
|
return { type, text: "undefined" };
|
|
default:
|
|
return undefined;
|
|
}
|
|
};
|