Skip to content

Commit

Permalink
dev-middleware: Use serverBaseUrl for local->server fetches (#47653)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #47653

## Context

Currently, when `nativeSourceCodeFetching == false`, `inspector-proxy` attempts to pre-fetch source maps, given the URL from a `Debugger.scriptParsed` event, and embeds them into `Debugger.scriptParsed`'s `sourceMapURL` using a data URI.

This was originally to support frontends that did not perform HTTP requests or were blocked (eg by CORS), but we're retaining it for the moment because it's more performant than lazy loading the source map.

Similarly, we perform middleware->server fetches to respond to `Debugger.getScriptSource` events.

To make these fetches for URLs that target `10.0.2.2` (ie, addressable from within an Android emulator) (etc), we rewrite `10.0.2.2`->`localhost` and perform a `fetch` from the Node process running dev-middleware.

## The problem

Consider a setup where:
 - Metro is running on a remote server, listening on `8081`.
 - Dev machine tunnels `localhost:8082` -> remote `8081`.
 - An app is running on an Android emulator on the dev machine, with bundle URL configured to `10.0.2.2:8082`.

In this case, we'll rewrite `10.0.2.2:8082` to `localhost:8082`, which *is* reachable and correct from the dev machine, but *not* from the machine where Metro is running, so the `fetch` of a source map from the inspector proxy will fail.

## Motivation

This might seem like a niche case, but it's part of fixing a series of unsafe assumptions that currently prevent us from running DevTools on an arbitrary port.

## This fix

Preserve the current behaviour (simple `10.0.2.2`<=>`localhost`) for URLs sent to the frontend, but construct a separate, server-relative URL, using the configured `serverBaseUrl`, for `fetch` calls within dev-middleware.

Changelog:
[General][Fixed] RN DevTools: Fix fetching sources and source maps when the dev-server is remote and not tunnelled via the same port+protocol.

Reviewed By: huntie

Differential Revision: D65993910

fbshipit-source-id: a0cdcf1644e97a2af3d8583f2da2aaa51276f68c
  • Loading branch information
robhogan authored and facebook-github-bot committed Nov 19, 2024
1 parent da62721 commit d1b0e9a
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 84 deletions.
137 changes: 71 additions & 66 deletions packages/dev-middleware/src/inspector-proxy/Device.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ type DebuggerConnection = {

const REACT_NATIVE_RELOADABLE_PAGE_ID = '-1';

export type DeviceOptions = $ReadOnly<{
id: string,
name: string,
app: string,
socket: WS,
projectRoot: string,
eventReporter: ?EventReporter,
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
serverRelativeBaseUrl: URL,
}>;

/**
* Device class represents single device connection to Inspector Proxy. Each device
* can have multiple inspectable pages.
Expand Down Expand Up @@ -125,41 +136,29 @@ export default class Device {

#connectedPageIds: Set<string> = new Set();

constructor(
id: string,
name: string,
app: string,
socket: WS,
projectRoot: string,
eventReporter: ?EventReporter,
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
serverBaseUrl?: URL,
) {
this.#dangerouslyConstruct(
id,
name,
app,
socket,
projectRoot,
eventReporter,
createMessageMiddleware,
);
// A base HTTP(S) URL to the server, relative to this server.
#serverRelativeBaseUrl: URL;

constructor(deviceOptions: DeviceOptions) {
this.#dangerouslyConstruct(deviceOptions);
}

#dangerouslyConstruct(
id: string,
name: string,
app: string,
socket: WS,
projectRoot: string,
eventReporter: ?EventReporter,
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
) {
#dangerouslyConstruct({
id,
name,
app,
socket,
projectRoot,
eventReporter,
createMessageMiddleware,
serverRelativeBaseUrl,
}: DeviceOptions) {
this.#id = id;
this.#name = name;
this.#app = app;
this.#deviceSocket = socket;
this.#projectRoot = projectRoot;
this.#serverRelativeBaseUrl = serverRelativeBaseUrl;
this.#deviceEventReporter = eventReporter
? new DeviceEventReporter(eventReporter, {
deviceId: id,
Expand Down Expand Up @@ -239,23 +238,15 @@ export default class Device {
* This hack attempts to allow users to reload the app, either as result of a
* crash, or manually reloading, without having to restart the debugger.
*/
dangerouslyRecreateDevice(
id: string,
name: string,
app: string,
socket: WS,
projectRoot: string,
eventReporter: ?EventReporter,
createMessageMiddleware: ?CreateCustomMessageHandlerFn,
) {
dangerouslyRecreateDevice(deviceOptions: DeviceOptions) {
invariant(
id === this.#id,
deviceOptions.id === this.#id,
'dangerouslyRecreateDevice() can only be used for the same device ID',
);

const oldDebugger = this.#debuggerConnection;

if (this.#app !== app || this.#name !== name) {
if (this.#app !== deviceOptions.app || this.#name !== deviceOptions.name) {
this.#deviceSocket.close();
this.#terminateDebuggerConnection();
}
Expand All @@ -270,15 +261,7 @@ export default class Device {
});
}

this.#dangerouslyConstruct(
id,
name,
app,
socket,
projectRoot,
eventReporter,
createMessageMiddleware,
);
this.#dangerouslyConstruct(deviceOptions);
}

getName(): string {
Expand Down Expand Up @@ -692,25 +675,29 @@ export default class Device {
) {
const params = payload.params;
if ('sourceMapURL' in params) {
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
if (params.sourceMapURL.includes(hostToRewrite)) {
payload.params.sourceMapURL = params.sourceMapURL.replace(
hostToRewrite,
'localhost',
);
debuggerInfo.originalSourceURLAddress = hostToRewrite;
}
}

const sourceMapURL = this.#tryParseHTTPURL(params.sourceMapURL);
if (sourceMapURL) {
const serverRelativeUrl = new URL(sourceMapURL.href);

for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
if (params.sourceMapURL.includes(hostToRewrite)) {
payload.params.sourceMapURL = params.sourceMapURL.replace(
hostToRewrite,
'localhost',
);
debuggerInfo.originalSourceURLAddress = hostToRewrite;
serverRelativeUrl.host = this.#serverRelativeBaseUrl.host;
serverRelativeUrl.protocol = this.#serverRelativeBaseUrl.protocol;
}
}

// Some debug clients do not support fetching HTTP URLs. If the
// message headed to the debug client identifies the source map with
// an HTTP URL, fetch the content here and convert the content to a
// Data URL (which is more widely supported) before passing the
// message to the debug client.
try {
const sourceMap = await this.#fetchText(sourceMapURL);
const sourceMap = await this.#fetchText(serverRelativeUrl);
payload.params.sourceMapURL =
'data:application/json;charset=utf-8;base64,' +
Buffer.from(sourceMap).toString('base64');
Expand All @@ -722,10 +709,23 @@ export default class Device {
}
}
if ('url' in params) {
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
if (params.url.includes(hostToRewrite)) {
payload.params.url = params.url.replace(hostToRewrite, 'localhost');
debuggerInfo.originalSourceURLAddress = hostToRewrite;
const originalParamsUrl = params.url;
let serverRelativeUrl = originalParamsUrl;
const parsedUrl = this.#tryParseHTTPURL(originalParamsUrl);
if (parsedUrl) {
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
if (parsedUrl.hostname === hostToRewrite) {
// URL is device-relative and points to the host - rewrite it to
// use localhost.
parsedUrl.hostname = 'localhost';
payload.params.url = parsedUrl.href;
debuggerInfo.originalSourceURLAddress = hostToRewrite;

// Determine the server-relative URL.
parsedUrl.host = this.#serverRelativeBaseUrl.host;
parsedUrl.protocol = this.#serverRelativeBaseUrl.protocol;
serverRelativeUrl = parsedUrl.href;
}
}
}

Expand All @@ -738,9 +738,13 @@ export default class Device {
debuggerInfo.prependedFilePrefix = true;
}

// $FlowFixMe[prop-missing]
if (params.scriptId != null) {
this.#scriptIdToSourcePathMapping.set(params.scriptId, params.url);
if ('scriptId' in params && params.scriptId != null) {
// Set a server-relative URL to locally fetch source by script ID
// on Debugger.getScriptSource.
this.#scriptIdToSourcePathMapping.set(
params.scriptId,
serverRelativeUrl,
);
}
}
}
Expand Down Expand Up @@ -907,6 +911,7 @@ export default class Device {
if (pathToSource != null) {
const httpURL = this.#tryParseHTTPURL(pathToSource);
if (httpURL) {
// URL is server-relatve, so we should be able to fetch it from here.
this.#fetchText(httpURL).then(
text => sendSuccessResponse(text),
err =>
Expand Down
32 changes: 14 additions & 18 deletions packages/dev-middleware/src/inspector-proxy/InspectorProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import type {EventReporter} from '../types/EventReporter';
import type {Experiments} from '../types/Experiments';
import type {CreateCustomMessageHandlerFn} from './CustomMessageHandler';
import type {DeviceOptions} from './Device';
import type {
JsonPagesListResponse,
JsonVersionResponse,
Expand Down Expand Up @@ -227,27 +228,22 @@ export default class InspectorProxy implements InspectorProxyQueries {

const oldDevice = this.#devices.get(deviceId);
let newDevice;
const deviceOptions: DeviceOptions = {
id: deviceId,
name: deviceName,
app: appName,
socket,
projectRoot: this.#projectRoot,
eventReporter: this.#eventReporter,
createMessageMiddleware: this.#customMessageHandler,
serverRelativeBaseUrl: this.#serverBaseUrl,
};

if (oldDevice) {
oldDevice.dangerouslyRecreateDevice(
deviceId,
deviceName,
appName,
socket,
this.#projectRoot,
this.#eventReporter,
this.#customMessageHandler,
);
oldDevice.dangerouslyRecreateDevice(deviceOptions);
newDevice = oldDevice;
} else {
newDevice = new Device(
deviceId,
deviceName,
appName,
socket,
this.#projectRoot,
this.#eventReporter,
this.#customMessageHandler,
);
newDevice = new Device(deviceOptions);
}

this.#devices.set(deviceId, newDevice);
Expand Down

0 comments on commit d1b0e9a

Please sign in to comment.