diff --git a/desktop/flipper-server-core/src/server/startServer.tsx b/desktop/flipper-server-core/src/server/startServer.tsx index 8d89945c8..56f374c31 100644 --- a/desktop/flipper-server-core/src/server/startServer.tsx +++ b/desktop/flipper-server-core/src/server/startServer.tsx @@ -72,7 +72,17 @@ const verifyAuthToken = (req: http.IncomingMessage): boolean => { return true; }; +/** + * The following two variables are used to control when + * the server is ready to accept incoming connections. + * - isReady, is used to synchronously check if the server is + * ready or not. + * - isReadyWaitable achieves the same but is used by + * asynchronous functions which may want to wait until the + * server is ready. + */ let isReady = false; +let isReadyWaitable: Promise | undefined; /** * Orchestrates the creation of the HTTP server, proxy, and WS server. @@ -134,6 +144,17 @@ async function startHTTPServer(config: Config): Promise<{ server.listen(config.port); + /** + * Create the promise which can be waited on. In this case, + * a reference to resolve is kept outside of the body of the promise + * so that other asynchronous functions can resolve the promise + * on its behalf. + */ + let isReadyResolver: (value: void | PromiseLike) => void; + isReadyWaitable = new Promise((resolve, _reject) => { + isReadyResolver = resolve; + }); + return new Promise((resolve) => { console.log(`Starting server on http://localhost:${config.port}`); const readyForIncomingConnections = ( @@ -141,11 +162,18 @@ async function startHTTPServer(config: Config): Promise<{ companionEnv: FlipperServerCompanionEnv, ): Promise => { attachSocketServer(socket, serverImpl, companionEnv); + /** + * At this point, the server is ready to accept incoming + * connections. Change the isReady state and resolve the + * promise so that other asychronous function become unblocked. + */ isReady = true; + isReadyResolver(); return new Promise((resolve) => { tracker.track('server-started', { port: config.port, }); + resolve(); }); }; @@ -214,8 +242,14 @@ function attachWS(server: http.Server, config: Config) { }; const wss = new WebSocketServer(options); - server.on('upgrade', function upgrade(request, socket, head) { - const {pathname} = parse(request.url!); + server.on('upgrade', async function upgrade(request, socket, head) { + if (!request.url) { + console.log('[flipper-server] No request URL available'); + socket.destroy(); + return; + } + + const {pathname} = parse(request.url); // Handled by Metro if (pathname === '/hot') { @@ -223,13 +257,18 @@ function attachWS(server: http.Server, config: Config) { } if (pathname === '/') { + // Wait until the server is ready to accept incoming connections. + await isReadyWaitable; wss.handleUpgrade(request, socket, head, function done(ws) { wss.emit('connection', ws, request); }); return; } - console.error('addWebsocket.upgrade -> unknown pathname', pathname); + console.error( + '[flipper-server] Unable to upgrade, unknown pathname', + pathname, + ); socket.destroy(); });