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 http from 'http'; 7import https from 'https'; 8import { RunServerOptions, Server } from 'metro'; 9import { ConfigT } from 'metro-config'; 10import { InspectorProxy } from 'metro-inspector-proxy'; 11import { parse } from 'url'; 12 13import { env } from '../../../utils/env'; 14import { MetroBundlerDevServer } from './MetroBundlerDevServer'; 15import { createInspectorProxy, ExpoInspectorProxy } from './inspector-proxy'; 16import { 17 importMetroCreateWebsocketServerFromProject, 18 importMetroFromProject, 19 importMetroHmrServerFromProject, 20 importMetroInspectorProxyFromProject, 21} from './resolveFromProject'; 22 23export const runServer = async ( 24 metroBundler: MetroBundlerDevServer, 25 config: ConfigT, 26 { 27 hasReducedPerformance = false, 28 host, 29 onError, 30 onReady, 31 secureServerOptions, 32 waitForBundler = false, 33 websocketEndpoints = {}, 34 watch, 35 }: RunServerOptions 36): Promise<{ server: http.Server | https.Server; metro: Server }> => { 37 const projectRoot = metroBundler.projectRoot; 38 39 const Metro = importMetroFromProject(projectRoot); 40 41 const createWebsocketServer = importMetroCreateWebsocketServerFromProject(projectRoot); 42 43 const { InspectorProxy } = importMetroInspectorProxyFromProject(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 // Lazy require 59 const connect = require('connect'); 60 61 const serverApp = connect(); 62 63 const { middleware, end, metroServer } = await Metro.createConnectMiddleware(config, { 64 // @ts-expect-error 65 hasReducedPerformance, 66 waitForBundler, 67 watch, 68 }); 69 70 serverApp.use(middleware); 71 72 let inspectorProxy: InspectorProxy | ExpoInspectorProxy | null = null; 73 if (config.server.runInspectorProxy && env.EXPO_USE_CUSTOM_INSPECTOR_PROXY) { 74 inspectorProxy = createInspectorProxy(metroBundler, config.projectRoot); 75 } else if (config.server.runInspectorProxy) { 76 inspectorProxy = new InspectorProxy(config.projectRoot); 77 } 78 79 let httpServer: http.Server | https.Server; 80 81 if (secureServerOptions != null) { 82 httpServer = https.createServer(secureServerOptions, serverApp); 83 } else { 84 httpServer = http.createServer(serverApp); 85 } 86 return new Promise<{ server: http.Server | https.Server; metro: Server }>((resolve, reject) => { 87 httpServer.on('error', (error) => { 88 if (onError) { 89 onError(error); 90 } 91 reject(error); 92 end(); 93 }); 94 95 httpServer.listen(config.server.port, host, () => { 96 if (onReady) { 97 onReady(httpServer); 98 } 99 100 Object.assign(websocketEndpoints, { 101 ...(inspectorProxy ? { ...inspectorProxy.createWebSocketListeners(httpServer) } : {}), 102 '/hot': createWebsocketServer({ 103 websocketServer: new MetroHmrServer( 104 metroServer.getBundler(), 105 metroServer.getCreateModuleId(), 106 config 107 ), 108 }), 109 }); 110 111 httpServer.on('upgrade', (request, socket, head) => { 112 const { pathname } = parse(request.url!); 113 if (pathname != null && websocketEndpoints[pathname]) { 114 websocketEndpoints[pathname].handleUpgrade(request, socket, head, (ws) => { 115 websocketEndpoints[pathname].emit('connection', ws, request); 116 }); 117 } else { 118 socket.destroy(); 119 } 120 }); 121 122 if (inspectorProxy) { 123 // TODO(hypuk): Refactor inspectorProxy.processRequest into separate request handlers 124 // so that we could provide routes (/json/list and /json/version) here. 125 // Currently this causes Metro to give warning about T31407894. 126 // $FlowFixMe[method-unbinding] added when improving typing for this parameters 127 serverApp.use(inspectorProxy.processRequest.bind(inspectorProxy)); 128 } 129 130 resolve({ server: httpServer, metro: metroServer }); 131 }); 132 133 // Disable any kind of automatic timeout behavior for incoming 134 // requests in case it takes the packager more than the default 135 // timeout of 120 seconds to respond to a request. 136 httpServer.timeout = 0; 137 138 httpServer.on('close', () => { 139 end(); 140 }); 141 }); 142}; 143