From d325e1913576190818c46909ed1876e93558d2d9 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Tue, 6 Feb 2024 02:43:58 -0800 Subject: [PATCH] [skip ci] Improve CDP Flow types (#42816) Summary: Some refactoring to formalise CDP protocol types (`protocol.js` is aligned with [Chrome's `protocol.d.ts` source](https://github.com/ChromeDevTools/devtools-protocol/blob/master/types/protocol.d.ts)), and to demarcate these from other types in the Inspector Proxy prototol. Changelog: [Internal] Reviewed By: robhogan Differential Revision: D53352243 --- .../src/inspector-proxy/Device.js | 59 +++++++------ .../inspector-proxy/DeviceEventReporter.js | 7 +- .../src/inspector-proxy/cdp-types/messages.js | 45 ++++++++++ .../src/inspector-proxy/cdp-types/protocol.js | 88 +++++++++++++++++++ .../src/inspector-proxy/types.js | 43 --------- 5 files changed, 168 insertions(+), 74 deletions(-) create mode 100644 packages/dev-middleware/src/inspector-proxy/cdp-types/messages.js create mode 100644 packages/dev-middleware/src/inspector-proxy/cdp-types/protocol.js diff --git a/packages/dev-middleware/src/inspector-proxy/Device.js b/packages/dev-middleware/src/inspector-proxy/Device.js index a0d4855abc3e8c..725cc80f3a1711 100644 --- a/packages/dev-middleware/src/inspector-proxy/Device.js +++ b/packages/dev-middleware/src/inspector-proxy/Device.js @@ -11,15 +11,11 @@ import type {EventReporter} from '../types/EventReporter'; import type { - DebuggerRequest, - ErrorResponse, - GetScriptSourceRequest, - GetScriptSourceResponse, - MessageFromDevice, - MessageToDevice, - Page, - SetBreakpointByUrlRequest, -} from './types'; + CDPClientMessage, + CDPRequest, + CDPResponse, +} from './cdp-types/messages'; +import type {MessageFromDevice, MessageToDevice, Page} from './types'; import DeviceEventReporter from './DeviceEventReporter'; import * as fs from 'fs'; @@ -581,27 +577,32 @@ export default class Device { } } - // Allows to make changes in incoming messages from debugger. Returns a boolean - // indicating whether the message has been handled locally (i.e. does not need - // to be forwarded to the target). + /** + * Intercept an incoming message from a connected debugger. Returns either an + * original/replacement CDP message object, or `null` (will forward nothing + * to the target). + */ #interceptMessageFromDebuggerLegacy( - req: DebuggerRequest, + req: CDPClientMessage, debuggerInfo: DebuggerInfo, socket: WS, - ): ?DebuggerRequest { - if (req.method === 'Debugger.setBreakpointByUrl') { - return this.#processDebuggerSetBreakpointByUrl(req, debuggerInfo); - } else if (req.method === 'Debugger.getScriptSource') { - this.#processDebuggerGetScriptSource(req, socket); - return null; + ): CDPClientMessage | null { + switch (req.method) { + case 'Debugger.setBreakpointByUrl': + return this.#processDebuggerSetBreakpointByUrl(req, debuggerInfo); + case 'Debugger.getScriptSource': + // Sends response to debugger via side-effect + this.#processDebuggerGetScriptSource(req, socket); + return null; + default: + return req; } - return req; } #processDebuggerSetBreakpointByUrl( - req: SetBreakpointByUrlRequest, + req: CDPRequest<'Debugger.setBreakpointByUrl'>, debuggerInfo: DebuggerInfo, - ): SetBreakpointByUrlRequest { + ): CDPRequest<'Debugger.setBreakpointByUrl'> { // If we replaced Android emulator's address to localhost we need to change it back. if (debuggerInfo.originalSourceURLAddress != null) { const processedReq = {...req, params: {...req.params}}; @@ -635,10 +636,16 @@ export default class Device { return req; } - #processDebuggerGetScriptSource(req: GetScriptSourceRequest, socket: WS) { + #processDebuggerGetScriptSource( + req: CDPRequest<'Debugger.getScriptSource'>, + socket: WS, + ): void { const sendSuccessResponse = (scriptSource: string) => { - const result: GetScriptSourceResponse = {scriptSource}; - const response = {id: req.id, result}; + const result = {scriptSource}; + const response: CDPResponse<'Debugger.getScriptSource'> = { + id: req.id, + result, + }; socket.send(JSON.stringify(response)); this.#deviceEventReporter?.logResponse(response, 'proxy', { pageId: this.#debuggerConnection?.pageId ?? null, @@ -647,7 +654,7 @@ export default class Device { }; const sendErrorResponse = (error: string) => { // Tell the client that the request failed - const result: ErrorResponse = {error: {message: error}}; + const result = {error: {message: error}}; const response = {id: req.id, result}; socket.send(JSON.stringify(response)); diff --git a/packages/dev-middleware/src/inspector-proxy/DeviceEventReporter.js b/packages/dev-middleware/src/inspector-proxy/DeviceEventReporter.js index fb5fd9aac9ceb7..57462b9df56240 100644 --- a/packages/dev-middleware/src/inspector-proxy/DeviceEventReporter.js +++ b/packages/dev-middleware/src/inspector-proxy/DeviceEventReporter.js @@ -9,6 +9,7 @@ */ import type {EventReporter} from '../types/EventReporter'; +import type {CDPResponse} from './cdp-types/messages'; import TTLCache from '@isaacs/ttlcache'; @@ -69,11 +70,7 @@ class DeviceEventReporter { } logResponse( - res: $ReadOnly<{ - id: number, - error?: {message: string, data?: mixed}, - ... - }>, + res: CDPResponse<>, origin: 'device' | 'proxy', metadata: $ReadOnly<{ pageId: string | null, diff --git a/packages/dev-middleware/src/inspector-proxy/cdp-types/messages.js b/packages/dev-middleware/src/inspector-proxy/cdp-types/messages.js new file mode 100644 index 00000000000000..3743e9082c9280 --- /dev/null +++ b/packages/dev-middleware/src/inspector-proxy/cdp-types/messages.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import type {Commands, Events} from './protocol'; + +// Note: A CDP event is a JSON-RPC notification with no `id` member. +export type CDPEvent = 'unknown'> = $ReadOnly<{ + method: TEvent, + params: Events[TEvent], +}>; + +export type CDPRequest = 'unknown'> = $ReadOnly<{ + method: TCommand, + params: Commands[TCommand]['paramsType'], + id: number, +}>; + +export type CDPResponse = 'unknown'> = + | $ReadOnly<{ + result: Commands[TCommand]['resultType'], + id: number, + }> + | $ReadOnly<{ + error: CDPRequestError, + id: number, + }>; + +export type CDPRequestError = $ReadOnly<{ + code: number, + message: string, + data?: mixed, +}>; + +export type CDPClientMessage = + | CDPRequest<'Debugger.getScriptSource'> + | CDPRequest<'Debugger.setBreakpointByUrl'> + | CDPRequest<>; diff --git a/packages/dev-middleware/src/inspector-proxy/cdp-types/protocol.js b/packages/dev-middleware/src/inspector-proxy/cdp-types/protocol.js new file mode 100644 index 00000000000000..aed9fe31bf33cd --- /dev/null +++ b/packages/dev-middleware/src/inspector-proxy/cdp-types/protocol.js @@ -0,0 +1,88 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +// Adapted from https://github.com/ChromeDevTools/devtools-protocol/blob/master/types/protocol.d.ts + +export interface Debugger { + GetScriptSourceParams: $ReadOnly<{ + /** + * Id of the script to get source for. + */ + scriptId: string, + }>; + + GetScriptSourceResult: $ReadOnly<{ + /** + * Script source (empty in case of Wasm bytecode). + */ + scriptSource: string, + + /** + * Wasm bytecode. (Encoded as a base64 string when passed over JSON) + */ + bytecode?: string, + }>; + + SetBreakpointByUrlParams: $ReadOnly<{ + /** + * Line number to set breakpoint at. + */ + lineNumber: number, + + /** + * URL of the resources to set breakpoint on. + */ + url?: string, + + /** + * Regex pattern for the URLs of the resources to set breakpoints on. Either `url` or + * `urlRegex` must be specified. + */ + urlRegex?: string, + + /** + * Script hash of the resources to set breakpoint on. + */ + scriptHash?: string, + + /** + * Offset in the line to set breakpoint at. + */ + columnNumber?: number, + + /** + * Expression to use as a breakpoint condition. When specified, debugger will only stop on the + * breakpoint if this expression evaluates to true. + */ + condition?: string, + }>; +} + +export type Events = { + [method: string]: mixed, +}; + +export type Commands = { + 'Debugger.getScriptSource': { + paramsType: Debugger['GetScriptSourceParams'], + resultType: Debugger['GetScriptSourceResult'], + }, + + 'Debugger.setBreakpointByUrl': { + paramsType: Debugger['SetBreakpointByUrlParams'], + resultType: void, + }, + + [method: string]: { + paramsType: mixed, + resultType: mixed, + }, +}; diff --git a/packages/dev-middleware/src/inspector-proxy/types.js b/packages/dev-middleware/src/inspector-proxy/types.js index e8835032ad66e8..e6ae2478a83aa3 100644 --- a/packages/dev-middleware/src/inspector-proxy/types.js +++ b/packages/dev-middleware/src/inspector-proxy/types.js @@ -95,49 +95,6 @@ export type JsonVersionResponse = $ReadOnly<{ 'Protocol-Version': string, }>; -/** - * Types were exported from https://github.com/ChromeDevTools/devtools-protocol/blob/master/types/protocol.d.ts - */ - -export type SetBreakpointByUrlRequest = $ReadOnly<{ - id: number, - method: 'Debugger.setBreakpointByUrl', - params: $ReadOnly<{ - lineNumber: number, - url?: string, - urlRegex?: string, - scriptHash?: string, - columnNumber?: number, - condition?: string, - }>, -}>; - -export type GetScriptSourceRequest = $ReadOnly<{ - id: number, - method: 'Debugger.getScriptSource', - params: { - scriptId: string, - }, -}>; - -export type GetScriptSourceResponse = $ReadOnly<{ - scriptSource: string, - /** - * Wasm bytecode. - */ - bytecode?: string, -}>; - -export type ErrorResponse = $ReadOnly<{ - error: $ReadOnly<{ - message: string, - }>, -}>; - -export type DebuggerRequest = - | SetBreakpointByUrlRequest - | GetScriptSourceRequest; - export type JSONSerializable = | boolean | number