aboutsummaryrefslogtreecommitdiff
path: root/src/cdp/events.ts
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmail.com>2026-07-27 23:46:25 +0530
committerAman Kumar <145339995+amankumarsingh77@users.noreply.github.com>2026-08-02 19:12:46 +0530
commit753c2ad615fa9827e168be256e1a7c7384605855 (patch)
tree03d62eb1816f4ad0ad3b634b87c4dcdb6fdbd83c /src/cdp/events.ts
parentd54d8e9e2dfbd31358b1f48e17338d74f5d270c0 (diff)
refactor(cdp): validate event params against a typed table
Malformed or unknown events are now dropped rather than hand-narrowed; consumers get typed params. Unknown methods are ignored, since the transport delivers every event Chrome emits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src/cdp/events.ts')
-rw-r--r--src/cdp/events.ts170
1 files changed, 170 insertions, 0 deletions
diff --git a/src/cdp/events.ts b/src/cdp/events.ts
new file mode 100644
index 0000000..0c65069
--- /dev/null
+++ b/src/cdp/events.ts
@@ -0,0 +1,170 @@
+import { Type, type Static, type TSchema } from "typebox";
+import { Compile } from "typebox/compile";
+import { type Result, err, ok } from "../util/result";
+import { type CdpError, cdpError } from "./errors";
+
+const ev = <P extends TSchema>(params: P) => ({ params, validate: Compile(params) });
+
+const open = { additionalProperties: true } as const;
+
+const Anything = Type.Object({}, open);
+
+const StackTrace = Type.Object(
+ {
+ callFrames: Type.Optional(
+ Type.Array(
+ Type.Object(
+ {
+ url: Type.Optional(Type.String()),
+ lineNumber: Type.Optional(Type.Number()),
+ functionName: Type.Optional(Type.String()),
+ },
+ open,
+ ),
+ ),
+ ),
+ },
+ open,
+);
+
+const ConsoleArg = Type.Object(
+ {
+ type: Type.Optional(Type.String()),
+ value: Type.Optional(Type.Unknown()),
+ description: Type.Optional(Type.String()),
+ unserializableValue: Type.Optional(Type.String()),
+ },
+ open,
+);
+
+export const EVENTS = {
+ "Inspector.detached": ev(Anything),
+
+ "Page.javascriptDialogOpening": ev(
+ Type.Object(
+ {
+ type: Type.String(),
+ message: Type.String(),
+ defaultPrompt: Type.Optional(Type.String()),
+ },
+ open,
+ ),
+ ),
+ "Page.loadEventFired": ev(Anything),
+ "Page.frameNavigated": ev(Anything),
+
+ "Runtime.consoleAPICalled": ev(
+ Type.Object(
+ {
+ type: Type.Optional(Type.String()),
+ args: Type.Optional(Type.Array(ConsoleArg)),
+ stackTrace: Type.Optional(StackTrace),
+ },
+ open,
+ ),
+ ),
+
+ "Log.entryAdded": ev(
+ Type.Object(
+ {
+ entry: Type.Object(
+ {
+ level: Type.Optional(Type.String()),
+ text: Type.Optional(Type.String()),
+ url: Type.Optional(Type.String()),
+ lineNumber: Type.Optional(Type.Number()),
+ stackTrace: Type.Optional(StackTrace),
+ },
+ open,
+ ),
+ },
+ open,
+ ),
+ ),
+
+ "Network.requestWillBeSent": ev(
+ Type.Object(
+ {
+ requestId: Type.String(),
+ request: Type.Object(
+ {
+ url: Type.String(),
+ method: Type.String(),
+ postData: Type.Optional(Type.String()),
+ },
+ open,
+ ),
+ type: Type.Optional(Type.String()),
+ },
+ open,
+ ),
+ ),
+ "Network.responseReceived": ev(
+ Type.Object(
+ {
+ requestId: Type.String(),
+ response: Type.Optional(
+ Type.Object(
+ {
+ status: Type.Optional(Type.Number()),
+ statusText: Type.Optional(Type.String()),
+ mimeType: Type.Optional(Type.String()),
+ },
+ open,
+ ),
+ ),
+ type: Type.Optional(Type.String()),
+ },
+ open,
+ ),
+ ),
+ "Network.loadingFinished": ev(
+ Type.Object(
+ { requestId: Type.String(), encodedDataLength: Type.Optional(Type.Number()) },
+ open,
+ ),
+ ),
+ "Network.loadingFailed": ev(
+ Type.Object(
+ { requestId: Type.String(), errorText: Type.Optional(Type.String()) },
+ open,
+ ),
+ ),
+
+ "Target.targetCreated": ev(
+ Type.Object(
+ {
+ targetInfo: Type.Object(
+ {
+ targetId: Type.String(),
+ type: Type.String(),
+ openerId: Type.Optional(Type.String()),
+ },
+ open,
+ ),
+ },
+ open,
+ ),
+ ),
+ "Target.targetDestroyed": ev(Type.Object({ targetId: Type.String() }, open)),
+};
+
+export type CdpEventName = keyof typeof EVENTS;
+export type EventParamsOf<E extends CdpEventName> = Static<(typeof EVENTS)[E]["params"]>;
+
+export const isKnownEvent = (name: string): name is CdpEventName => Object.hasOwn(EVENTS, name);
+
+export const decodeEvent = <E extends CdpEventName>(
+ name: E,
+ raw: unknown,
+): Result<EventParamsOf<E>, CdpError> => {
+ const spec = EVENTS[name];
+ if (spec === undefined) {
+ return err(cdpError("invalid_response", "unknown CDP event", name));
+ }
+ if (!spec.validate.Check(raw)) {
+ const why = spec.validate.Errors(raw).map((e) => e.message).join("; ");
+ return err(cdpError("invalid_response", why, name));
+ }
+ return ok(raw as EventParamsOf<E>);
+};