Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
[dev server] refactor middleware into other files (#3666)
Browse files Browse the repository at this point in the history
* refactor middleware into other files

* Simplify symbolicator

* Update MetroDevServer.ts
  • Loading branch information
EvanBacon authored Jul 14, 2021
1 parent 766aee0 commit 98ca186
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 75 deletions.
81 changes: 6 additions & 75 deletions packages/dev-server/src/MetroDevServer.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import Log from '@expo/bunyan';
import type Log from '@expo/bunyan';
import { ExpoConfig, getConfigFilePaths } from '@expo/config';
import * as ExpoMetroConfig from '@expo/metro-config';
import {
createDevServerMiddleware,
securityHeadersMiddleware,
} from '@react-native-community/cli-server-api';
import bodyParser from 'body-parser';
import type { Server as ConnectServer, HandleFunction } from 'connect';
import http from 'http';
import type { IncomingMessage, ServerResponse } from 'http';
import type { Server as ConnectServer } from 'connect';
import type http from 'http';
import type Metro from 'metro';
import path from 'path';
import resolveFrom from 'resolve-from';
import semver from 'semver';
import { parse as parseUrl } from 'url';

import {
buildHermesBundleAsync,
Expand All @@ -23,6 +21,9 @@ import {
import LogReporter from './LogReporter';
import clientLogsMiddleware from './middleware/clientLogsMiddleware';
import createJsInspectorMiddleware from './middleware/createJsInspectorMiddleware';
import { remoteDevtoolsCorsMiddleware } from './middleware/remoteDevtoolsCorsMiddleware';
import { remoteDevtoolsSecurityHeadersMiddleware } from './middleware/remoteDevtoolsSecurityHeadersMiddleware';
import { replaceMiddlewareWith } from './middleware/replaceMiddlewareWith';

export type MetroDevServerOptions = ExpoMetroConfig.LoadOptions & {
logger: Log;
Expand Down Expand Up @@ -257,76 +258,6 @@ function importMetroServerFromProject(projectRoot: string): typeof Metro.Server
return require(resolvedPath);
}

function replaceMiddlewareWith(
app: ConnectServer,
sourceMiddleware: HandleFunction,
targetMiddleware: HandleFunction
) {
const item = app.stack.find(middleware => middleware.handle === sourceMiddleware);
if (item) {
item.handle = targetMiddleware;
}
}

// Like securityHeadersMiddleware but further allow cross-origin requests
// from https://chrome-devtools-frontend.appspot.com/
function remoteDevtoolsSecurityHeadersMiddleware(
req: IncomingMessage,
res: ServerResponse,
next: (err?: Error) => void
) {
// Block any cross origin request.
if (
typeof req.headers.origin === 'string' &&
!req.headers.origin.match(/^https?:\/\/localhost:/) &&
!req.headers.origin.match(/^https:\/\/chrome-devtools-frontend\.appspot\.com/)
) {
next(
new Error(
`Unauthorized request from ${req.headers.origin}. ` +
'This may happen because of a conflicting browser extension to intercept HTTP requests. ' +
'Please try again without browser extensions or using incognito mode.'
)
);
return;
}

// Block MIME-type sniffing.
res.setHeader('X-Content-Type-Options', 'nosniff');

next();
}

// Middleware that accepts multiple Access-Control-Allow-Origin for processing *.map.
// This is a hook middleware before metro processing *.map,
// which originally allow only devtools://devtools
function remoteDevtoolsCorsMiddleware(
req: IncomingMessage,
res: ServerResponse,
next: (err?: Error) => void
) {
if (req.url) {
const url = parseUrl(req.url);
const origin = req.headers.origin;
const isValidOrigin =
origin &&
['devtools://devtools', 'https://chrome-devtools-frontend.appspot.com'].includes(origin);
if (url.pathname?.endsWith('.map') && origin && isValidOrigin) {
res.setHeader('Access-Control-Allow-Origin', origin);

// Prevent metro overwrite Access-Control-Allow-Origin header
const setHeader = res.setHeader.bind(res);
res.setHeader = (key, ...args) => {
if (key === 'Access-Control-Allow-Origin') {
return;
}
setHeader(key, ...args);
};
}
}
next();
}

// Cloned from xdl/src/Versions.ts, we cannot use that because of circular dependency
function gteSdkVersion(expJson: Pick<ExpoConfig, 'sdkVersion'>, sdkVersion: string): boolean {
if (!expJson.sdkVersion) {
Expand Down
32 changes: 32 additions & 0 deletions packages/dev-server/src/middleware/remoteDevtoolsCorsMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { IncomingMessage, ServerResponse } from 'http';
import { parse as parseUrl } from 'url';

// Middleware that accepts multiple Access-Control-Allow-Origin for processing *.map.
// This is a hook middleware before metro processing *.map,
// which originally allow only devtools://devtools
export function remoteDevtoolsCorsMiddleware(
req: IncomingMessage,
res: ServerResponse,
next: (err?: Error) => void
) {
if (req.url) {
const url = parseUrl(req.url);
const origin = req.headers.origin;
const isValidOrigin =
origin &&
['devtools://devtools', 'https://chrome-devtools-frontend.appspot.com'].includes(origin);
if (url.pathname?.endsWith('.map') && origin && isValidOrigin) {
res.setHeader('Access-Control-Allow-Origin', origin);

// Prevent metro overwrite Access-Control-Allow-Origin header
const setHeader = res.setHeader.bind(res);
res.setHeader = (key, ...args) => {
if (key === 'Access-Control-Allow-Origin') {
return;
}
setHeader(key, ...args);
};
}
}
next();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { IncomingMessage, ServerResponse } from 'http';

// Like securityHeadersMiddleware but further allow cross-origin requests
// from https://chrome-devtools-frontend.appspot.com/
export function remoteDevtoolsSecurityHeadersMiddleware(
req: IncomingMessage,
res: ServerResponse,
next: (err?: Error) => void
) {
// Block any cross origin request.
if (
typeof req.headers.origin === 'string' &&
!req.headers.origin.match(/^https?:\/\/localhost:/) &&
!req.headers.origin.match(/^https:\/\/chrome-devtools-frontend\.appspot\.com/)
) {
next(
new Error(
`Unauthorized request from ${req.headers.origin}. ` +
'This may happen because of a conflicting browser extension to intercept HTTP requests. ' +
'Please try again without browser extensions or using incognito mode.'
)
);
return;
}

// Block MIME-type sniffing.
res.setHeader('X-Content-Type-Options', 'nosniff');

next();
}
12 changes: 12 additions & 0 deletions packages/dev-server/src/middleware/replaceMiddlewareWith.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Server as ConnectServer, HandleFunction } from 'connect';

export function replaceMiddlewareWith(
app: ConnectServer,
sourceMiddleware: HandleFunction,
targetMiddleware: HandleFunction
) {
const item = app.stack.find(middleware => middleware.handle === sourceMiddleware);
if (item) {
item.handle = targetMiddleware;
}
}

0 comments on commit 98ca186

Please sign in to comment.