1// Copyright © 2023 650 Industries. 2// Copyright (c) Meta Platforms, Inc. and affiliates. 3// 4// Forks https://github.com/facebook/metro/blob/b80d9a0f638ee9fb82ff69cd3c8d9f4309ca1da2/packages/metro/src/index.flow.js#L57 5// and adds the ability to access the bundler instance. 6import assert from 'assert'; 7import http from 'http'; 8import https from 'https'; 9import { RunServerOptions, Server } from 'metro'; 10import { ConfigT } from 'metro-config'; 11import type { InspectorProxy } from 'metro-inspector-proxy'; 12import { parse } from 'url'; 13 14import { MetroBundlerDevServer } from './MetroBundlerDevServer'; 15import { createInspectorProxy, ExpoInspectorProxy } from './inspector-proxy'; 16import { 17 importMetroCreateWebsocketServerFromProject, 18 importMetroFromProject, 19 importMetroHmrServerFromProject, 20 importMetroInspectorProxyFromProject, 21} from './resolveFromProject'; 22import { env } from '../../../utils/env'; 23import type { ConnectAppType } from '../middleware/server.types'; 24 25export const runServer = async ( 26 metroBundler: MetroBundlerDevServer, 27 config: ConfigT, 28 { 29 hasReducedPerformance = false, 30 host, 31 onError, 32 onReady, 33 secureServerOptions, 34 waitForBundler = false, 35 websocketEndpoints = {}, 36 watch, 37 }: RunServerOptions 38): Promise<{ server: http.Server | https.Server; metro: Server }> => { 39 const projectRoot = metroBundler.projectRoot; 40 41 const Metro = importMetroFromProject(projectRoot); 42 43 const createWebsocketServer = importMetroCreateWebsocketServerFromProject(projectRoot); 44 45 const MetroHmrServer = importMetroHmrServerFromProject(projectRoot); 46 47 // await earlyPortCheck(host, config.server.port); 48 49 // if (secure != null || secureCert != null || secureKey != null) { 50 // // eslint-disable-next-line no-console 51 // console.warn( 52 // chalk.inverse.yellow.bold(' DEPRECATED '), 53 // 'The `secure`, `secureCert`, and `secureKey` options are now deprecated. ' + 54 // 'Please use the `secureServerOptions` object instead to pass options to ' + 55 // "Metro's https development server.", 56 // ); 57 // } 58 59 const { middleware, end, metroServer } = await Metro.createConnectMiddleware(config, { 60 hasReducedPerformance, 61 waitForBundler, 62 watch, 63 }); 64 65 assert(typeof (middleware as any).use === 'function'); 66 const serverApp = middleware as ConnectAppType; 67 68 let inspectorProxy: InspectorProxy | ExpoInspectorProxy | null = null; 69 if (config.server.runInspectorProxy && !env.EXPO_NO_INSPECTOR_PROXY) { 70 inspectorProxy = createInspectorProxy(metroBundler, config.projectRoot); 71 } else if (config.server.runInspectorProxy) { 72 const { InspectorProxy } = importMetroInspectorProxyFromProject(projectRoot); 73 inspectorProxy = new InspectorProxy(config.projectRoot); 74 } 75 76 let httpServer: http.Server | https.Server; 77 78 if (secureServerOptions != null) { 79 httpServer = https.createServer(secureServerOptions, serverApp); 80 } else { 81 httpServer = http.createServer(serverApp); 82 } 83 return new Promise<{ server: http.Server | https.Server; metro: Server }>((resolve, reject) => { 84 httpServer.on('error', (error) => { 85 if (onError) { 86 onError(error); 87 } 88 reject(error); 89 end(); 90 }); 91 92 httpServer.listen(config.server.port, host, () => { 93 if (onReady) { 94 onReady(httpServer); 95 } 96 97 Object.assign(websocketEndpoints, { 98 ...(inspectorProxy ? { ...inspectorProxy.createWebSocketListeners(httpServer) } : {}), 99 '/hot': createWebsocketServer({ 100 websocketServer: new MetroHmrServer( 101 metroServer.getBundler(), 102 metroServer.getCreateModuleId(), 103 config 104 ), 105 }), 106 }); 107 108 httpServer.on('upgrade', (request, socket, head) => { 109 const { pathname } = parse(request.url!); 110 if (pathname != null && websocketEndpoints[pathname]) { 111 websocketEndpoints[pathname].handleUpgrade(request, socket, head, (ws) => { 112 websocketEndpoints[pathname].emit('connection', ws, request); 113 }); 114 } else { 115 socket.destroy(); 116 } 117 }); 118 119 if (inspectorProxy) { 120 // TODO(hypuk): Refactor inspectorProxy.processRequest into separate request handlers 121 // so that we could provide routes (/json/list and /json/version) here. 122 // Currently this causes Metro to give warning about T31407894. 123 // $FlowFixMe[method-unbinding] added when improving typing for this parameters 124 serverApp.use(inspectorProxy.processRequest.bind(inspectorProxy)); 125 } 126 127 resolve({ server: httpServer, metro: metroServer }); 128 }); 129 130 // Disable any kind of automatic timeout behavior for incoming 131 // requests in case it takes the packager more than the default 132 // timeout of 120 seconds to respond to a request. 133 httpServer.timeout = 0; 134 135 httpServer.on('close', () => { 136 end(); 137 }); 138 }); 139}; 140