Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[0.73] Backport JSC-safe request URLs #994

Merged
merged 2 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ The possibility to add custom middleware to the server response chain.

Type: `string => string`

A function that will be called every time Metro processes a URL. Metro will use the return value of this function as if it were the original URL provided by the client. This applies to all incoming HTTP requests (after any custom middleware), as well as bundle URLs in `/symbolicate` request payloads and within the hot reloading protocol.
A function that will be called every time Metro processes a URL, after normalization of non-standard query-string delimiters using [`jsc-safe-url`](https://www.npmjs.com/package/jsc-safe-url). Metro will use the return value of this function as if it were the original URL provided by the client. This applies to all incoming HTTP requests (after any custom middleware), as well as bundle URLs in `/symbolicate` request payloads and within the hot reloading protocol.

#### `runInspectorProxy`

Expand Down
1 change: 1 addition & 0 deletions packages/metro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"image-size": "^0.6.0",
"invariant": "^2.2.4",
"jest-worker": "^27.2.0",
"jsc-safe-url": "^0.2.2",
"lodash.throttle": "^4.1.1",
"metro-babel-transformer": "0.73.9",
"metro-cache": "0.73.9",
Expand Down
3 changes: 2 additions & 1 deletion packages/metro/src/DeltaBundler/Serializers/hmrJSBundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {DeltaResult, Module, ReadOnlyGraph} from '../types.flow';
import type {HmrModule} from 'metro-runtime/src/modules/types.flow';

const {isJsModule, wrapModule} = require('./helpers/js');
const jscSafeUrl = require('jsc-safe-url');
const {addParamsToDefineCall} = require('metro-transform-plugins');
const path = require('path');
const url = require('url');
Expand Down Expand Up @@ -53,7 +54,7 @@ function generateModules(
};

const sourceMappingURL = getURL('map');
const sourceURL = getURL('bundle');
const sourceURL = jscSafeUrl.toJscSafeUrl(getURL('bundle'));
const code =
prepareModule(module, graph, options) +
`\n//# sourceMappingURL=${sourceMappingURL}\n` +
Expand Down
58 changes: 41 additions & 17 deletions packages/metro/src/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ const {codeFrameColumns} = require('@babel/code-frame');
const MultipartResponse = require('./Server/MultipartResponse');
const debug = require('debug')('Metro:Server');
const fs = require('graceful-fs');
const invariant = require('invariant');
const jscSafeUrl = require('jsc-safe-url');
const {
Logger,
Logger: {createActionStartEntry, createActionEndEntry, log},
Expand Down Expand Up @@ -487,14 +489,19 @@ class Server {
);
}

_rewriteAndNormalizeUrl(requestUrl: string): string {
return jscSafeUrl.toNormalUrl(
this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl)),
);
}

async _processRequest(
req: IncomingMessage,
res: ServerResponse,
next: (?Error) => mixed,
) {
const originalUrl = req.url;
req.url = this._config.server.rewriteRequestUrl(req.url);

req.url = this._rewriteAndNormalizeUrl(req.url);
const urlObj = url.parse(req.url, true);
const {host} = req.headers;
debug(
Expand Down Expand Up @@ -899,7 +906,7 @@ class Server {
bundle: bundleCode,
};
},
finish({req, mres, result}) {
finish({req, mres, serializerOptions, result}) {
if (
// We avoid parsing the dates since the client should never send a more
// recent date than the one returned by the Delta Bundler (if that's the
Expand All @@ -916,6 +923,9 @@ class Server {
String(result.numModifiedFiles),
);
mres.setHeader(DELTA_ID_HEADER, String(result.nextRevId));
if (serializerOptions?.sourceUrl != null) {
mres.setHeader('Content-Location', serializerOptions.sourceUrl);
}
mres.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
mres.setHeader('Last-Modified', result.lastModifiedDate.toUTCString());
mres.setHeader(
Expand Down Expand Up @@ -1223,19 +1233,34 @@ class Server {
debug('Start symbolication');
/* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
const body = await req.rawBody;
const stack = JSON.parse(body).stack.map(frame => {
if (frame.file && frame.file.includes('://')) {
const parsedBody = JSON.parse(body);

const rewriteAndNormalizeStackFrame = <T>(
frame: T,
lineNumber: number,
): T => {
invariant(
frame != null && typeof frame === 'object',
'Bad stack frame at line %d, expected object, received: %s',
lineNumber,
typeof frame,
);
const frameFile = frame.file;
if (typeof frameFile === 'string' && frameFile.includes('://')) {
return {
...frame,
file: this._config.server.rewriteRequestUrl(frame.file),
file: this._rewriteAndNormalizeUrl(frameFile),
};
}
return frame;
});
};

const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
// In case of multiple bundles / HMR, some stack frames can have different URLs from others
const urls = new Set<string>();

stack.forEach(frame => {
// These urls have been rewritten and normalized above.
const sourceUrl = frame.file;
// Skip `/debuggerWorker.js` which does not need symbolication.
if (
Expand All @@ -1250,8 +1275,11 @@ class Server {

debug('Getting source maps for symbolication');
const sourceMaps = await Promise.all(
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
Array.from(urls.values()).map(this._explodedSourceMapForURL, this),
Array.from(urls.values()).map(normalizedUrl =>
this._explodedSourceMapForBundleOptions(
this._parseOptions(normalizedUrl),
),
),
);

debug('Performing fast symbolication');
Expand All @@ -1278,21 +1306,17 @@ class Server {
}
}

async _explodedSourceMapForURL(reqUrl: string): Promise<ExplodedSourceMap> {
const options = parseOptionsFromUrl(
reqUrl,
new Set(this._config.resolver.platforms),
getBytecodeVersion(),
);

async _explodedSourceMapForBundleOptions(
bundleOptions: BundleOptions,
): Promise<ExplodedSourceMap> {
const {
entryFile,
graphOptions,
onProgress,
resolverOptions,
serializerOptions,
transformOptions,
} = splitBundleOptions(options);
} = splitBundleOptions(bundleOptions);

/**
* `entryFile` is relative to projectRoot, we need to use resolution function
Expand Down