diff options
| author | amankumarsingh77 <amankumarsingh7702@gmail.com> | 2026-07-27 23:46:25 +0530 |
|---|---|---|
| committer | Aman Kumar <145339995+amankumarsingh77@users.noreply.github.com> | 2026-08-02 19:12:46 +0530 |
| commit | 753c2ad615fa9827e168be256e1a7c7384605855 (patch) | |
| tree | 03d62eb1816f4ad0ad3b634b87c4dcdb6fdbd83c /src/cdp/events.ts | |
| parent | d54d8e9e2dfbd31358b1f48e17338d74f5d270c0 (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.ts | 170 |
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>); +}; |
