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 { createInspectorProxy, ExpoInspectorProxy } from './inspector-proxy'; 15import { 16 importMetroCreateWebsocketServerFromProject, 17 importMetroFromProject, 18 importMetroHmrServerFromProject, 19 importMetroInspectorProxyFromProject, 20} from './resolveFromProject'; 21 22export const runServer = async ( 23 projectRoot: string, 24 config: ConfigT, 25 { 26 hasReducedPerformance = false, 27 host, 28 onError, 29 onReady, 30 secureServerOptions, 31 waitForBundler = false, 32 websocketEndpoints = {}, 33 watch, 34 }: RunServerOptions 35): Promise<{ server: http.Server | https.Server; metro: Server }> => { 36 const Metro = importMetroFromProject(projectRoot); 37 38 const createWebsocketServer = importMetroCreateWebsocketServerFromProject(projectRoot); 39 40 const { InspectorProxy } = importMetroInspectorProxyFromProject(projectRoot); 41 42 const MetroHmrServer = importMetroHmrServerFromProject(projectRoot); 43 44 // await earlyPortCheck(host, config.server.port); 45 46 // if (secure != null || secureCert != null || secureKey != null) { 47 // // eslint-disable-next-line no-console 48 // console.warn( 49 // chalk.inverse.yellow.bold(' DEPRECATED '), 50 // 'The `secure`, `secureCert`, and `secureKey` options are now deprecated. ' + 51 // 'Please use the `secureServerOptions` object instead to pass options to ' + 52 // "Metro's https development server.", 53 // ); 54 // } 55 // Lazy require 56 const connect = require('connect'); 57 58 const serverApp = connect(); 59 60 const { middleware, end, metroServer } = await Metro.createConnectMiddleware(config, { 61 // @ts-expect-error 62 hasReducedPerformance, 63 waitForBundler, 64 watch, 65 }); 66 67 serverApp.use(middleware); 68 69 let inspectorProxy: InspectorProxy | ExpoInspectorProxy | null = null; 70 if (config.server.runInspectorProxy && env.EXPO_USE_CUSTOM_INSPECTOR_PROXY) { 71 inspectorProxy = createInspectorProxy(config.projectRoot); 72 } else if (config.server.runInspectorProxy) { 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