From 3ec22c1e69a7d41e09c00eb2e413357d29f40622 Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Mon, 4 Sep 2023 12:21:47 -0700 Subject: [PATCH] Add option to enable experimental debugger frontend (#39227) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/39227 Changelog: [Internal] 1. Adds an `unstable_experiments` option to `createDevMiddleware` in `react-native/dev-middleware`. 2. Adds `enableCustomDebuggerFrontend` (default `false` for now) as an experiment flag controlling whether the new debugger frontend (D48680624, D48682302) is in use. We plan to enable this by default in RN 0.73 after additional testing. If enabled, the new debugger frontend will only be used for the `/open-debugger` flow Reviewed By: huntie Differential Revision: D48602725 fbshipit-source-id: 598865b559478df1f19420daf3633ee6c233362a --- flow-typed/npm/actual-request-url_v1.x.x.js | 39 +++++++++++++++++++ packages/dev-middleware/package.json | 3 ++ .../dev-middleware/src/createDevMiddleware.js | 23 +++++++++++ .../src/inspector-proxy/InspectorProxy.js | 10 ++++- .../src/middleware/openDebuggerMiddleware.js | 13 ++++++- .../dev-middleware/src/types/Experiments.js | 15 +++++++ .../src/utils/getDevServerUrl.js | 20 +++++++++- .../src/utils/getDevToolsFrontendUrl.js | 15 +++++-- yarn.lock | 5 +++ 9 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 flow-typed/npm/actual-request-url_v1.x.x.js create mode 100644 packages/dev-middleware/src/types/Experiments.js diff --git a/flow-typed/npm/actual-request-url_v1.x.x.js b/flow-typed/npm/actual-request-url_v1.x.x.js new file mode 100644 index 00000000000..5658a87b76b --- /dev/null +++ b/flow-typed/npm/actual-request-url_v1.x.x.js @@ -0,0 +1,39 @@ +/** + * 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 + */ + +declare module 'actual-request-url' { + declare interface ActualRequestUrl$Sock { + +encrypted?: boolean; + +localPort?: number; + } + + declare export interface Req { + +url?: string | URL | null; + +headers?: Object; + +socket?: ActualRequestUrl$Sock; + } + + declare function actualRequestUrl(req: Req): URL | null; + declare function getForwardVal(req: Req): string | null; + declare function getHost(req: Req): string; + declare function getPath(req: Req): string; + declare function getPort(req: Req): string | null; + declare function getProto(req: Req): string; + + declare export { + actualRequestUrl, + getForwardVal, + getHost, + getPath, + getPort, + getProto, + }; +} diff --git a/packages/dev-middleware/package.json b/packages/dev-middleware/package.json index 36b0a29a1da..246729babd7 100644 --- a/packages/dev-middleware/package.json +++ b/packages/dev-middleware/package.json @@ -23,11 +23,14 @@ ], "dependencies": { "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "^0.73.0", + "actual-request-url": "^1.0.4", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^1.0.0", "connect": "^3.6.5", "debug": "^2.2.0", "node-fetch": "^2.2.0", + "serve-static": "^1.13.1", "temp-dir": "^2.0.0" }, "engines": { diff --git a/packages/dev-middleware/src/createDevMiddleware.js b/packages/dev-middleware/src/createDevMiddleware.js index 96922243ec4..0968b4ef597 100644 --- a/packages/dev-middleware/src/createDevMiddleware.js +++ b/packages/dev-middleware/src/createDevMiddleware.js @@ -12,9 +12,14 @@ import type {NextHandleFunction} from 'connect'; import type {BrowserLauncher} from './types/BrowserLauncher'; import type {EventReporter} from './types/EventReporter'; +import type {Experiments, ExperimentsConfig} from './types/Experiments'; import type {Logger} from './types/Logger'; +import reactNativeDebuggerFrontendPath from '@react-native/debugger-frontend'; import connect from 'connect'; +import path from 'path'; +// $FlowFixMe[untyped-import] TODO: type serve-static +import serveStaticMiddleware from 'serve-static'; import openDebuggerMiddleware from './middleware/openDebuggerMiddleware'; import InspectorProxy from './inspector-proxy/InspectorProxy'; import DefaultBrowserLauncher from './utils/DefaultBrowserLauncher'; @@ -26,6 +31,7 @@ type Options = $ReadOnly<{ logger?: Logger, unstable_browserLauncher?: BrowserLauncher, unstable_eventReporter?: EventReporter, + unstable_experiments?: ExperimentsConfig, }>; type DevMiddlewareAPI = $ReadOnly<{ @@ -40,10 +46,14 @@ export default function createDevMiddleware({ logger, unstable_browserLauncher = DefaultBrowserLauncher, unstable_eventReporter, + unstable_experiments: experimentConfig = {}, }: Options): DevMiddlewareAPI { + const experiments = getExperiments(experimentConfig); + const inspectorProxy = new InspectorProxy( projectRoot, unstable_eventReporter, + experiments, ); const middleware = connect() @@ -53,6 +63,13 @@ export default function createDevMiddleware({ logger, browserLauncher: unstable_browserLauncher, eventReporter: unstable_eventReporter, + experiments, + }), + ) + .use( + '/debugger-frontend', + serveStaticMiddleware(path.join(reactNativeDebuggerFrontendPath), { + fallthrough: false, }), ) .use((...args) => inspectorProxy.processRequest(...args)); @@ -64,3 +81,9 @@ export default function createDevMiddleware({ ), }; } + +function getExperiments(config: ExperimentsConfig): Experiments { + return { + enableCustomDebuggerFrontend: config.enableCustomDebuggerFrontend ?? false, + }; +} diff --git a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js index 0027d7342de..dd690cf654e 100644 --- a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js +++ b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js @@ -16,6 +16,7 @@ import type { PageDescription, } from './types'; import type {EventReporter} from '../types/EventReporter'; +import type {Experiments} from '../types/Experiments'; import type {IncomingMessage, ServerResponse} from 'http'; import url from 'url'; @@ -52,10 +53,17 @@ export default class InspectorProxy { _eventReporter: ?EventReporter; - constructor(projectRoot: string, eventReporter: ?EventReporter) { + _experiments: Experiments; + + constructor( + projectRoot: string, + eventReporter: ?EventReporter, + experiments: Experiments, + ) { this._projectRoot = projectRoot; this._devices = new Map(); this._eventReporter = eventReporter; + this._experiments = experiments; } // Process HTTP request sent to server. We only respond to 2 HTTP requests: diff --git a/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js b/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js index 501d46c04ee..610f04dea46 100644 --- a/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js +++ b/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js @@ -13,6 +13,7 @@ import type {NextHandleFunction} from 'connect'; import type {IncomingMessage, ServerResponse} from 'http'; import type {BrowserLauncher, LaunchedBrowser} from '../types/BrowserLauncher'; import type {EventReporter} from '../types/EventReporter'; +import type {Experiments} from '../types/Experiments'; import type {Logger} from '../types/Logger'; import url from 'url'; @@ -26,6 +27,7 @@ type Options = $ReadOnly<{ browserLauncher: BrowserLauncher, logger?: Logger, eventReporter?: EventReporter, + experiments: Experiments, }>; /** @@ -39,6 +41,7 @@ type Options = $ReadOnly<{ export default function openDebuggerMiddleware({ browserLauncher, eventReporter, + experiments, logger, }: Options): NextHandleFunction { return async ( @@ -50,7 +53,9 @@ export default function openDebuggerMiddleware({ const {query} = url.parse(req.url, true); const {appId} = query; - const targets = await queryInspectorTargets(getDevServerUrl(req)); + const targets = await queryInspectorTargets( + getDevServerUrl(req, 'local'), + ); let target; if (typeof appId === 'string') { @@ -80,7 +85,11 @@ export default function openDebuggerMiddleware({ debuggerInstances.set( appId, await browserLauncher.launchDebuggerAppWindow( - getDevToolsFrontendUrl(target.webSocketDebuggerUrl), + getDevToolsFrontendUrl( + target.webSocketDebuggerUrl, + getDevServerUrl(req, 'public'), + experiments, + ), ), ); res.end(); diff --git a/packages/dev-middleware/src/types/Experiments.js b/packages/dev-middleware/src/types/Experiments.js new file mode 100644 index 00000000000..dc5ec41665f --- /dev/null +++ b/packages/dev-middleware/src/types/Experiments.js @@ -0,0 +1,15 @@ +/** + * 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 + */ + +export type Experiments = $ReadOnly<{ + enableCustomDebuggerFrontend: boolean, +}>; + +export type ExperimentsConfig = Partial; diff --git a/packages/dev-middleware/src/utils/getDevServerUrl.js b/packages/dev-middleware/src/utils/getDevServerUrl.js index 3a62134da51..551995f7184 100644 --- a/packages/dev-middleware/src/utils/getDevServerUrl.js +++ b/packages/dev-middleware/src/utils/getDevServerUrl.js @@ -11,13 +11,31 @@ import type {IncomingMessage} from 'http'; +import {getProto, getHost} from 'actual-request-url'; import net from 'net'; import {TLSSocket} from 'tls'; /** * Get the base URL to address the current development server. */ -export default function getDevServerUrl(req: IncomingMessage): string { +export default function getDevServerUrl( + /** The current HTTP request. */ + req: IncomingMessage, + + /** + * 'public' for a URL accessible by the same client that sent the request, or + * 'local' for for a URL accessible from the machine running the dev server. + */ + kind: 'public' | 'local', +): string { + if (kind === 'public') { + const host = getHost(req); + if (host != null) { + const scheme = getProto(req); + return `${scheme}://${host}`; + } + // If we can't determine a public URL, fall back to a local URL, which *might* still work. + } const scheme = req.socket instanceof TLSSocket && req.socket.encrypted === true ? 'https' diff --git a/packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js b/packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js index d39d835965e..874b45e7b23 100644 --- a/packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js +++ b/packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js @@ -4,11 +4,13 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow strict + * @flow strict-local * @format * @oncall react_native */ +import type {Experiments} from '../types/Experiments'; + /** * The Chrome DevTools frontend revision to use. This should be set to the * latest version known to be compatible with Hermes. @@ -23,9 +25,16 @@ const DEVTOOLS_FRONTEND_REV = 'd9568d04d7dd79269c5a655d7ada69650c5a8336'; // Chr */ export default function getDevToolsFrontendUrl( webSocketDebuggerUrl: string, + devServerUrl: string, + experiments: Experiments, ): string { - const urlBase = `https://chrome-devtools-frontend.appspot.com/serve_rev/@${DEVTOOLS_FRONTEND_REV}/devtools_app.html`; const ws = webSocketDebuggerUrl.replace(/^ws:\/\//, ''); - + if (experiments.enableCustomDebuggerFrontend) { + const urlBase = `${devServerUrl}/debugger-frontend/rn_inspector.html`; + return `${urlBase}?ws=${encodeURIComponent( + ws, + )}&sources.hide_add_folder=true`; + } + const urlBase = `https://chrome-devtools-frontend.appspot.com/serve_rev/@${DEVTOOLS_FRONTEND_REV}/devtools_app.html`; return `${urlBase}?panel=console&ws=${encodeURIComponent(ws)}`; } diff --git a/yarn.lock b/yarn.lock index 0ea467e71cc..8eabf6d1a61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3513,6 +3513,11 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +actual-request-url@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/actual-request-url/-/actual-request-url-1.0.4.tgz#54454514e3715a4c60a1e26e9a49423e376a7563" + integrity sha512-9AOnrTOkog3eM7l4Y702+BKTYL1Tvxcl4EBbKrhDTB87npkss+I1dirUox2OyZVVz95BnavGgSZanWWas43j7A== + adbkit-apkreader@^3.1.2: version "3.2.0" resolved "https://registry.yarnpkg.com/adbkit-apkreader/-/adbkit-apkreader-3.2.0.tgz#8d0bb1f733969e959992095ed7f2a8d658ec97a5"