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 hasReducedPerformance, 65 waitForBundler, 66 watch, 67 }); 68 69 serverApp.use(middleware); 70 71 let inspectorProxy: InspectorProxy | ExpoInspectorProxy | null = null; 72 if (config.server.runInspectorProxy && !env.EXPO_NO_INSPECTOR_PROXY) { 73 inspectorProxy = createInspectorProxy(metroBundler, config.projectRoot); 74 } else if (config.server.runInspectorProxy) { 75 inspectorProxy = new InspectorProxy(config.projectRoot); 76 } 77 78 let httpServer: http.Server | https.Server; 79 80 if (secureServerOptions != null) { 81 httpServer = https.createServer(secureServerOptions, serverApp); 82 } else { 83 httpServer = http.createServer(serverApp); 84 } 85 return new Promise<{ server: http.Server | https.Server; metro: Server }>((resolve, reject) => { 86 httpServer.on('error', (error) => { 87 if (onError) { 88 onError(error); 89 } 90 reject(error); 91 end(); 92 }); 93 94 httpServer.listen(config.server.port, host, () => { 95 if (onReady) { 96 onReady(httpServer); 97 } 98 99 Object.assign(websocketEndpoints, { 100 ...(inspectorProxy ? { ...inspectorProxy.createWebSocketListeners(httpServer) } : {}), 101 '/hot': createWebsocketServer({ 102 websocketServer: new MetroHmrServer( 103 metroServer.getBundler(), 104 metroServer.getCreateModuleId(), 105 config 106 ), 107 }), 108 }); 109 110 httpServer.on('upgrade', (request, socket, head) => { 111 const { pathname } = parse(request.url!); 112 if (pathname != null && websocketEndpoints[pathname]) { 113 websocketEndpoints[pathname].handleUpgrade(request, socket, head, (ws) => { 114 websocketEndpoints[pathname].emit('connection', ws, request); 115 }); 116 } else { 117 socket.destroy(); 118 } 119 }); 120 121 if (inspectorProxy) { 122 // TODO(hypuk): Refactor inspectorProxy.processRequest into separate request handlers 123 // so that we could provide routes (/json/list and /json/version) here. 124 // Currently this causes Metro to give warning about T31407894. 125 // $FlowFixMe[method-unbinding] added when improving typing for this parameters 126 serverApp.use(inspectorProxy.processRequest.bind(inspectorProxy)); 127 } 128 129 resolve({ server: httpServer, metro: metroServer }); 130 }); 131 132 // Disable any kind of automatic timeout behavior for incoming 133 // requests in case it takes the packager more than the default 134 // timeout of 120 seconds to respond to a request. 135 httpServer.timeout = 0; 136 137 httpServer.on('close', () => { 138 end(); 139 }); 140 }); 141}; 142