133643b60SEvan Bacon// Copyright © 2023 650 Industries. 233643b60SEvan Bacon// Copyright (c) Meta Platforms, Inc. and affiliates. 333643b60SEvan Bacon// 433643b60SEvan Bacon// Forks https://github.com/facebook/metro/blob/b80d9a0f638ee9fb82ff69cd3c8d9f4309ca1da2/packages/metro/src/index.flow.js#L57 533643b60SEvan Bacon// and adds the ability to access the bundler instance. 6c799f270SKudo Chienimport assert from 'assert'; 733643b60SEvan Baconimport http from 'http'; 833643b60SEvan Baconimport https from 'https'; 933643b60SEvan Baconimport { RunServerOptions, Server } from 'metro'; 1033643b60SEvan Baconimport { ConfigT } from 'metro-config'; 11c799f270SKudo Chienimport type { InspectorProxy } from 'metro-inspector-proxy'; 1233643b60SEvan Baconimport { parse } from 'url'; 1333643b60SEvan Bacon 1403d43e7dSCedric van Puttenimport { MetroBundlerDevServer } from './MetroBundlerDevServer'; 155234fe38SCedric van Puttenimport { createInspectorProxy, ExpoInspectorProxy } from './inspector-proxy'; 1633643b60SEvan Baconimport { 1733643b60SEvan Bacon importMetroCreateWebsocketServerFromProject, 1833643b60SEvan Bacon importMetroFromProject, 1933643b60SEvan Bacon importMetroHmrServerFromProject, 2033643b60SEvan Bacon importMetroInspectorProxyFromProject, 2133643b60SEvan Bacon} from './resolveFromProject'; 22*8a424bebSJames Ideimport { env } from '../../../utils/env'; 23*8a424bebSJames Ideimport type { ConnectAppType } from '../middleware/server.types'; 2433643b60SEvan Bacon 2533643b60SEvan Baconexport const runServer = async ( 2603d43e7dSCedric van Putten metroBundler: MetroBundlerDevServer, 2733643b60SEvan Bacon config: ConfigT, 2833643b60SEvan Bacon { 2933643b60SEvan Bacon hasReducedPerformance = false, 3033643b60SEvan Bacon host, 3133643b60SEvan Bacon onError, 3233643b60SEvan Bacon onReady, 3333643b60SEvan Bacon secureServerOptions, 3433643b60SEvan Bacon waitForBundler = false, 3533643b60SEvan Bacon websocketEndpoints = {}, 3633643b60SEvan Bacon watch, 3733643b60SEvan Bacon }: RunServerOptions 3833643b60SEvan Bacon): Promise<{ server: http.Server | https.Server; metro: Server }> => { 3903d43e7dSCedric van Putten const projectRoot = metroBundler.projectRoot; 4003d43e7dSCedric van Putten 4133643b60SEvan Bacon const Metro = importMetroFromProject(projectRoot); 4233643b60SEvan Bacon 4333643b60SEvan Bacon const createWebsocketServer = importMetroCreateWebsocketServerFromProject(projectRoot); 4433643b60SEvan Bacon 4533643b60SEvan Bacon const MetroHmrServer = importMetroHmrServerFromProject(projectRoot); 4633643b60SEvan Bacon 4733643b60SEvan Bacon // await earlyPortCheck(host, config.server.port); 4833643b60SEvan Bacon 4933643b60SEvan Bacon // if (secure != null || secureCert != null || secureKey != null) { 5033643b60SEvan Bacon // // eslint-disable-next-line no-console 5133643b60SEvan Bacon // console.warn( 5233643b60SEvan Bacon // chalk.inverse.yellow.bold(' DEPRECATED '), 5333643b60SEvan Bacon // 'The `secure`, `secureCert`, and `secureKey` options are now deprecated. ' + 5433643b60SEvan Bacon // 'Please use the `secureServerOptions` object instead to pass options to ' + 5533643b60SEvan Bacon // "Metro's https development server.", 5633643b60SEvan Bacon // ); 5733643b60SEvan Bacon // } 5833643b60SEvan Bacon 5933643b60SEvan Bacon const { middleware, end, metroServer } = await Metro.createConnectMiddleware(config, { 6033643b60SEvan Bacon hasReducedPerformance, 6133643b60SEvan Bacon waitForBundler, 6233643b60SEvan Bacon watch, 6333643b60SEvan Bacon }); 6433643b60SEvan Bacon 65c799f270SKudo Chien assert(typeof (middleware as any).use === 'function'); 66c799f270SKudo Chien const serverApp = middleware as ConnectAppType; 6733643b60SEvan Bacon 685234fe38SCedric van Putten let inspectorProxy: InspectorProxy | ExpoInspectorProxy | null = null; 6933bd1a45SCedric van Putten if (config.server.runInspectorProxy && !env.EXPO_NO_INSPECTOR_PROXY) { 7003d43e7dSCedric van Putten inspectorProxy = createInspectorProxy(metroBundler, config.projectRoot); 715234fe38SCedric van Putten } else if (config.server.runInspectorProxy) { 72c799f270SKudo Chien const { InspectorProxy } = importMetroInspectorProxyFromProject(projectRoot); 7333643b60SEvan Bacon inspectorProxy = new InspectorProxy(config.projectRoot); 7433643b60SEvan Bacon } 7533643b60SEvan Bacon 7633643b60SEvan Bacon let httpServer: http.Server | https.Server; 7733643b60SEvan Bacon 7833643b60SEvan Bacon if (secureServerOptions != null) { 7933643b60SEvan Bacon httpServer = https.createServer(secureServerOptions, serverApp); 8033643b60SEvan Bacon } else { 8133643b60SEvan Bacon httpServer = http.createServer(serverApp); 8233643b60SEvan Bacon } 8333643b60SEvan Bacon return new Promise<{ server: http.Server | https.Server; metro: Server }>((resolve, reject) => { 8433643b60SEvan Bacon httpServer.on('error', (error) => { 8533643b60SEvan Bacon if (onError) { 8633643b60SEvan Bacon onError(error); 8733643b60SEvan Bacon } 8833643b60SEvan Bacon reject(error); 8933643b60SEvan Bacon end(); 9033643b60SEvan Bacon }); 9133643b60SEvan Bacon 9233643b60SEvan Bacon httpServer.listen(config.server.port, host, () => { 9333643b60SEvan Bacon if (onReady) { 9433643b60SEvan Bacon onReady(httpServer); 9533643b60SEvan Bacon } 9633643b60SEvan Bacon 9733643b60SEvan Bacon Object.assign(websocketEndpoints, { 9833643b60SEvan Bacon ...(inspectorProxy ? { ...inspectorProxy.createWebSocketListeners(httpServer) } : {}), 9933643b60SEvan Bacon '/hot': createWebsocketServer({ 10033643b60SEvan Bacon websocketServer: new MetroHmrServer( 10133643b60SEvan Bacon metroServer.getBundler(), 10233643b60SEvan Bacon metroServer.getCreateModuleId(), 10333643b60SEvan Bacon config 10433643b60SEvan Bacon ), 10533643b60SEvan Bacon }), 10633643b60SEvan Bacon }); 10733643b60SEvan Bacon 10833643b60SEvan Bacon httpServer.on('upgrade', (request, socket, head) => { 10933643b60SEvan Bacon const { pathname } = parse(request.url!); 11033643b60SEvan Bacon if (pathname != null && websocketEndpoints[pathname]) { 11133643b60SEvan Bacon websocketEndpoints[pathname].handleUpgrade(request, socket, head, (ws) => { 11233643b60SEvan Bacon websocketEndpoints[pathname].emit('connection', ws, request); 11333643b60SEvan Bacon }); 11433643b60SEvan Bacon } else { 11533643b60SEvan Bacon socket.destroy(); 11633643b60SEvan Bacon } 11733643b60SEvan Bacon }); 11833643b60SEvan Bacon 11933643b60SEvan Bacon if (inspectorProxy) { 12033643b60SEvan Bacon // TODO(hypuk): Refactor inspectorProxy.processRequest into separate request handlers 12133643b60SEvan Bacon // so that we could provide routes (/json/list and /json/version) here. 12233643b60SEvan Bacon // Currently this causes Metro to give warning about T31407894. 12333643b60SEvan Bacon // $FlowFixMe[method-unbinding] added when improving typing for this parameters 12433643b60SEvan Bacon serverApp.use(inspectorProxy.processRequest.bind(inspectorProxy)); 12533643b60SEvan Bacon } 12633643b60SEvan Bacon 12733643b60SEvan Bacon resolve({ server: httpServer, metro: metroServer }); 12833643b60SEvan Bacon }); 12933643b60SEvan Bacon 13033643b60SEvan Bacon // Disable any kind of automatic timeout behavior for incoming 13133643b60SEvan Bacon // requests in case it takes the packager more than the default 13233643b60SEvan Bacon // timeout of 120 seconds to respond to a request. 13333643b60SEvan Bacon httpServer.timeout = 0; 13433643b60SEvan Bacon 13533643b60SEvan Bacon httpServer.on('close', () => { 13633643b60SEvan Bacon end(); 13733643b60SEvan Bacon }); 13833643b60SEvan Bacon }); 13933643b60SEvan Bacon}; 140