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"