Files
react-native/packages/dev-middleware/src/__tests__/ServerUtils.js
T
Sam Zhou cf664c65e2 Standardize subtyping error code into incompatible-type in react native and metro (#53312)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53312

Changelog: [Internal]

Reviewed By: jbrown215

Differential Revision: D80400976

fbshipit-source-id: 196af69c0b9621b2a2675b232406639773e04933
2025-08-18 09:04:31 -07:00

162 lines
4.7 KiB
JavaScript

/**
* 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
*/
import type {JSONSerializable} from '../inspector-proxy/types';
import type {HandleFunction} from 'connect';
import {createDevMiddleware} from '../';
import connect from 'connect';
import http from 'http';
import https from 'https';
import * as selfsigned from 'selfsigned';
import url from 'url';
type CreateDevMiddlewareOptions = Parameters<typeof createDevMiddleware>[0];
type CreateServerOptions = {
...Omit<CreateDevMiddlewareOptions, 'serverBaseUrl'>,
secure?: boolean,
};
type ConnectApp = ReturnType<typeof connect>;
export function withServerForEachTest(options: CreateServerOptions): $ReadOnly<{
serverBaseUrl: string,
serverBaseWsUrl: string,
app: ConnectApp,
port: number,
}> {
const EAGER_ACCESS_ERROR_MESSAGE =
'The return value of withServerForEachTest is lazily initialized and can only be accessed in tests.';
const ref: {
serverBaseUrl: string,
serverBaseWsUrl: string,
app: ConnectApp,
port: number,
} = {
// $FlowFixMe[unsafe-getters-setters]
get serverBaseUrl() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowFixMe[unsafe-getters-setters]
get serverBaseWsUrl() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowFixMe[unsafe-getters-setters]
get app() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowFixMe[unsafe-getters-setters]
get port() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
};
let server: http$Server | https$Server;
let app: ConnectApp;
beforeEach(async () => {
({server, app} = await createServer(options));
const serverBaseUrl = baseUrlForServer(
server,
(options.secure ?? false) ? 'https' : 'http',
);
const serverBaseWsUrl = baseUrlForServer(
server,
(options.secure ?? false) ? 'wss' : 'ws',
);
Object.defineProperty(ref, 'serverBaseUrl', {value: serverBaseUrl});
Object.defineProperty(ref, 'serverBaseWsUrl', {value: serverBaseWsUrl});
Object.defineProperty(ref, 'app', {value: app});
Object.defineProperty(ref, 'port', {value: server.address().port});
});
afterEach(done => {
server.close(() => done());
});
return ref;
}
export async function createServer(options: CreateServerOptions): Promise<{
server: http$Server | https$Server,
app: ReturnType<typeof connect>,
}> {
const app = connect();
const {secure = false, ...devMiddlewareOptions} = options;
let httpServer;
if (secure) {
const {cert, private: key} = selfsigned.generate(
[{name: 'commonName', value: 'localhost'}],
{days: 1},
);
httpServer = https.createServer(
{cert, key},
// $FlowFixMe[incompatible-type] The types for `connect` and `https` are subtly incompatible as written.
app,
);
} else {
httpServer = http.createServer(app);
}
return new Promise((resolve, reject) => {
httpServer.once('error', reject);
httpServer.listen(() => {
const {middleware, websocketEndpoints} = createDevMiddleware({
...devMiddlewareOptions,
serverBaseUrl: baseUrlForServer(httpServer, secure ? 'https' : 'http'),
});
app.use(middleware);
httpServer.on('upgrade', (request, socket, head) => {
const {pathname} = url.parse(request.url);
if (pathname != null && websocketEndpoints[pathname]) {
websocketEndpoints[pathname].handleUpgrade(
request,
socket,
head,
ws => {
websocketEndpoints[pathname].emit('connection', ws, request);
},
);
} else {
socket.destroy();
}
});
resolve({server: httpServer, app});
});
});
}
export function baseUrlForServer(
server: http$Server | https$Server,
scheme: string,
): string {
const address = server.address();
// Assumption: `server` is local and listening on `localhost`. We can't use
// the IP address because HTTPS requires a hostname.
return `${scheme}://localhost:${address.port}`;
}
export function serveStaticJson(json: JSONSerializable): HandleFunction {
return (req, res, next) => {
if (req.method !== 'GET') {
next();
return;
}
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(json));
};
}
export function serveStaticText(text: string): HandleFunction {
return (req, res, next) => {
if (req.method !== 'GET') {
next();
return;
}
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(text);
};
}