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