diff --git a/packages/community-cli-plugin/src/commands/start/runServer.js b/packages/community-cli-plugin/src/commands/start/runServer.js index f8ab07d980..f3bb9d3b76 100644 --- a/packages/community-cli-plugin/src/commands/start/runServer.js +++ b/packages/community-cli-plugin/src/commands/start/runServer.js @@ -114,6 +114,7 @@ async function runServer( }); const {middleware, websocketEndpoints} = createDevMiddleware({ projectRoot, + serverBaseUrl: devServerUrl, logger, unstable_experiments: { // NOTE: Only affects the /open-debugger endpoint diff --git a/packages/dev-middleware/README.md b/packages/dev-middleware/README.md index 217c4a19aa..6edfa45dcf 100644 --- a/packages/dev-middleware/README.md +++ b/packages/dev-middleware/README.md @@ -16,6 +16,7 @@ function myDevServerImpl(args) { const {middleware, websocketEndpoints} = createDevMiddleware({ projectRoot: metroConfig.projectRoot, + serverBaseUrl: `http://${args.host}:${args.port}`, logger, }); diff --git a/packages/dev-middleware/package.json b/packages/dev-middleware/package.json index 246729babd..cf44092dc1 100644 --- a/packages/dev-middleware/package.json +++ b/packages/dev-middleware/package.json @@ -24,7 +24,6 @@ "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", diff --git a/packages/dev-middleware/src/createDevMiddleware.js b/packages/dev-middleware/src/createDevMiddleware.js index 6f10965f2b..2e5cf12b1c 100644 --- a/packages/dev-middleware/src/createDevMiddleware.js +++ b/packages/dev-middleware/src/createDevMiddleware.js @@ -26,9 +26,38 @@ import DefaultBrowserLauncher from './utils/DefaultBrowserLauncher'; type Options = $ReadOnly<{ projectRoot: string, + + /** + * The base URL to the dev server, as addressible from the local developer + * machine. This is used in responses which return URLs to other endpoints, + * e.g. the debugger frontend and inspector proxy targets. + * + * Example: `'http://localhost:8081'`. + */ + serverBaseUrl: string, + logger?: Logger, + + /** + * An interface for integrators to provide a custom implementation for + * opening URLs in a web browser. + * + * This is an unstable API with no semver guarantees. + */ unstable_browserLauncher?: BrowserLauncher, + + /** + * An interface for logging events. + * + * This is an unstable API with no semver guarantees. + */ unstable_eventReporter?: EventReporter, + + /** + * The set of experimental features to enable. + * + * This is an unstable API with no semver guarantees. + */ unstable_experiments?: ExperimentsConfig, }>; @@ -39,6 +68,7 @@ type DevMiddlewareAPI = $ReadOnly<{ export default function createDevMiddleware({ projectRoot, + serverBaseUrl, logger, unstable_browserLauncher = DefaultBrowserLauncher, unstable_eventReporter, @@ -48,6 +78,7 @@ export default function createDevMiddleware({ const inspectorProxy = new InspectorProxy( projectRoot, + serverBaseUrl, unstable_eventReporter, experiments, ); @@ -56,10 +87,11 @@ export default function createDevMiddleware({ .use( '/open-debugger', openDebuggerMiddleware({ + serverBaseUrl, + inspectorProxy, browserLauncher: unstable_browserLauncher, eventReporter: unstable_eventReporter, experiments, - inspectorProxy, logger, }), ) diff --git a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js index 6a00308dca..0cfb55a05f 100644 --- a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js +++ b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js @@ -22,7 +22,6 @@ import type {IncomingMessage, ServerResponse} from 'http'; import url from 'url'; import WS from 'ws'; import Device from './Device'; -import getDevServerUrl from '../utils/getDevServerUrl'; const debug = require('debug')('Metro:InspectorProxy'); @@ -35,11 +34,7 @@ const PAGES_LIST_JSON_VERSION_URL = '/json/version'; const INTERNAL_ERROR_CODE = 1011; export interface InspectorProxyQueries { - getPageDescriptions( - options: $ReadOnly<{ - wsPublicBaseUrl: string, - }>, - ): Array; + getPageDescriptions(): Array; } /** @@ -49,6 +44,9 @@ export default class InspectorProxy implements InspectorProxyQueries { // Root of the project used for relative to absolute source path conversion. _projectRoot: string; + /** The base URL to the dev server from the developer machine. */ + _serverBaseUrl: string; + // Maps device ID to Device instance. _devices: Map; @@ -61,20 +59,18 @@ export default class InspectorProxy implements InspectorProxyQueries { constructor( projectRoot: string, + serverBaseUrl: string, eventReporter: ?EventReporter, experiments: Experiments, ) { this._projectRoot = projectRoot; + this._serverBaseUrl = serverBaseUrl; this._devices = new Map(); this._eventReporter = eventReporter; this._experiments = experiments; } - getPageDescriptions({ - wsPublicBaseUrl, - }: $ReadOnly<{ - wsPublicBaseUrl: string, - }>): Array { + getPageDescriptions(): Array { // Build list of pages from all devices. let result: Array = []; Array.from(this._devices.entries()).forEach(([deviceId, device]) => { @@ -82,7 +78,7 @@ export default class InspectorProxy implements InspectorProxyQueries { device .getPagesList() .map((page: Page) => - this._buildPageDescription(deviceId, device, page, wsPublicBaseUrl), + this._buildPageDescription(deviceId, device, page), ), ); }); @@ -102,14 +98,7 @@ export default class InspectorProxy implements InspectorProxyQueries { request.url === PAGES_LIST_JSON_URL || request.url === PAGES_LIST_JSON_URL_2 ) { - const wsPublicBaseUrl = getDevServerUrl(request, 'public', 'ws'); - - this._sendJsonResponse( - response, - this.getPageDescriptions({ - wsPublicBaseUrl, - }), - ); + this._sendJsonResponse(response, this.getPageDescriptions()); } else if (request.url === PAGES_LIST_JSON_VERSION_URL) { this._sendJsonResponse(response, { Browser: 'Mobile JavaScript', @@ -135,21 +124,18 @@ export default class InspectorProxy implements InspectorProxyQueries { deviceId: string, device: Device, page: Page, - wsServerBaseUrl: string, ): PageDescription { - const webSocketDebuggerUrl = `${wsServerBaseUrl}${WS_DEBUGGER_URL}?device=${deviceId}&page=${page.id}`; + const {host, protocol} = new URL(this._serverBaseUrl); + const webSocketScheme = protocol === 'https:' ? 'wss' : 'ws'; + + const webSocketUrlWithoutProtocol = `${host}${WS_DEBUGGER_URL}?device=${deviceId}&page=${page.id}`; + const webSocketDebuggerUrl = `${webSocketScheme}://${webSocketUrlWithoutProtocol}`; - const isSecure = webSocketDebuggerUrl.startsWith('wss://'); - const webSocketUrlWithoutProtocol = webSocketDebuggerUrl.replace( - /^wss?:\/\//, - '', - ); - const scheme = isSecure ? 'wss' : 'ws'; // For now, `/json/list` returns the legacy built-in `devtools://` URL, to // preserve existing handling by Flipper. This may return a placeholder in // future -- please use the `/open-debugger` endpoint. const devtoolsFrontendUrl = - `devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&${scheme}=` + + `devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&${webSocketScheme}=` + encodeURIComponent(webSocketUrlWithoutProtocol); return { diff --git a/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js b/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js index aeb77aa76c..2e3e1f5594 100644 --- a/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js +++ b/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js @@ -18,14 +18,14 @@ import type {Experiments} from '../types/Experiments'; import type {Logger} from '../types/Logger'; import url from 'url'; -import getDevServerUrl from '../utils/getDevServerUrl'; import getDevToolsFrontendUrl from '../utils/getDevToolsFrontendUrl'; const debuggerInstances = new Map(); type Options = $ReadOnly<{ - browserLauncher: BrowserLauncher, + serverBaseUrl: string, logger?: Logger, + browserLauncher: BrowserLauncher, eventReporter?: EventReporter, experiments: Experiments, inspectorProxy: InspectorProxyQueries, @@ -40,11 +40,12 @@ type Options = $ReadOnly<{ * @see https://chromedevtools.github.io/devtools-protocol/ */ export default function openDebuggerMiddleware({ + serverBaseUrl, + logger, browserLauncher, eventReporter, experiments, inspectorProxy, - logger, }: Options): NextHandleFunction { return async ( req: IncomingMessage, @@ -58,15 +59,11 @@ export default function openDebuggerMiddleware({ const {query} = url.parse(req.url, true); const {appId} = query; - const targets = inspectorProxy - .getPageDescriptions({ - wsPublicBaseUrl: getDevServerUrl(req, 'public', 'ws'), - }) - .filter( - // Only use targets with better reloading support - app => - app.title === 'React Native Experimental (Improved Chrome Reloads)', - ); + const targets = inspectorProxy.getPageDescriptions().filter( + // Only use targets with better reloading support + app => + app.title === 'React Native Experimental (Improved Chrome Reloads)', + ); let target; const launchType: 'launch' | 'redirect' = @@ -110,7 +107,7 @@ export default function openDebuggerMiddleware({ await browserLauncher.launchDebuggerAppWindow( getDevToolsFrontendUrl( target.webSocketDebuggerUrl, - getDevServerUrl(req, 'public'), + serverBaseUrl, experiments, ), ), diff --git a/packages/dev-middleware/src/utils/getDevServerUrl.js b/packages/dev-middleware/src/utils/getDevServerUrl.js deleted file mode 100644 index 6401a26f98..0000000000 --- a/packages/dev-middleware/src/utils/getDevServerUrl.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * 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 - * @format - * @oncall react_native - */ - -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( - /** 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', - - /** - * If 'ws', returns a WebSocket URL corresponding to the current server, - * with the appropriate scheme (ws or wss) for HTTP / HTTPS connections. - */ - protocolType: 'http' | 'ws' = 'http', -): string { - if (kind === 'public') { - const host = getHost(req); - if (host != null) { - let scheme = getProto(req); - if (protocolType === 'ws') { - scheme = httpSchemeToWsScheme(scheme); - } - return `${scheme}://${host}`; - } - // If we can't determine a public URL, fall back to a local URL, which *might* still work. - } - let scheme = - req.socket instanceof TLSSocket && req.socket.encrypted === true - ? 'https' - : 'http'; - if (protocolType === 'ws') { - scheme = httpSchemeToWsScheme(scheme); - } - const {localAddress, localPort} = req.socket; - const address = - localAddress && net.isIPv6(localAddress) - ? `[${localAddress}]` - : localAddress; - - return `${scheme}://${address}:${localPort}`; -} - -function httpSchemeToWsScheme(scheme: string) { - switch (scheme) { - case 'https': - return 'wss'; - case 'http': - return 'ws'; - default: - throw new Error(`Expected http or https but received ${scheme}`); - } -} diff --git a/packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js b/packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js index 0547f857ee..4d95f18bf4 100644 --- a/packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js +++ b/packages/dev-middleware/src/utils/getDevToolsFrontendUrl.js @@ -28,18 +28,19 @@ export default function getDevToolsFrontendUrl( devServerUrl: string, experiments: Experiments, ): string { - const isSecure = webSocketDebuggerUrl.startsWith('wss://'); + const scheme = new URL(webSocketDebuggerUrl).protocol.slice(0, -1); const webSocketUrlWithoutProtocol = webSocketDebuggerUrl.replace( /^wss?:\/\//, '', ); - const scheme = isSecure ? 'wss' : 'ws'; + if (experiments.enableCustomDebuggerFrontend) { const urlBase = `${devServerUrl}/debugger-frontend/rn_inspector.html`; return `${urlBase}?${scheme}=${encodeURIComponent( webSocketUrlWithoutProtocol, )}&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&${scheme}=${encodeURIComponent( webSocketUrlWithoutProtocol, diff --git a/yarn.lock b/yarn.lock index 80dc2fe134..b1892071b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3021,11 +3021,6 @@ 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== - agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"