From 4704c177ec4980ce5f2ff514fb7817569941946a Mon Sep 17 00:00:00 2001 From: Lorenzo Blasa Date: Wed, 24 May 2023 06:50:05 -0700 Subject: [PATCH] Check if server is already listening Summary: Right now, this simply avoids trying to create a server instance if one appears to be already running. In a future change, we may decide to kill the existing instance and replace it with a new one. But, in this case, running this as is will be useful as it will provide the connection URL with the auth token. Also remove flipper server dependency from finding installation. In this case, we don't need it and can use `os.getPlatform()` instead. Reviewed By: antonk52 Differential Revision: D46144843 fbshipit-source-id: 2922843b916d37e0126e43ae65a622f87a6920ec --- .../src/server/utilities.tsx | 28 +++++++++++ .../flipper-server/src/findInstallation.tsx | 7 +-- desktop/flipper-server/src/index.tsx | 50 ++++++++++++------- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/desktop/flipper-server-core/src/server/utilities.tsx b/desktop/flipper-server-core/src/server/utilities.tsx index adfe3815d..3ef9822f3 100644 --- a/desktop/flipper-server-core/src/server/utilities.tsx +++ b/desktop/flipper-server-core/src/server/utilities.tsx @@ -12,6 +12,34 @@ import xdgBasedir from 'xdg-basedir'; import net from 'net'; import fs from 'fs-extra'; +/** + * Checks if a port is in use. + * @param port The port to check. + * @returns True if the port is in use. Otherwise, false. + */ +export async function checkPortInUse(port: number): Promise { + interface HttpError extends Error { + code?: string; + } + + return new Promise((resolve, reject) => { + const tester = net + .createServer() + .once('error', function (err: HttpError) { + if (err.code != 'EADDRINUSE') return reject(err); + resolve(true); + }) + .once('listening', function () { + tester + .once('close', function () { + resolve(false); + }) + .close(); + }) + .listen(port); + }); +} + /** * Checks if a socket is in used for given path. * If the path doesn't exist then is not in use. If it does, diff --git a/desktop/flipper-server/src/findInstallation.tsx b/desktop/flipper-server/src/findInstallation.tsx index a06152b40..63b4469be 100644 --- a/desktop/flipper-server/src/findInstallation.tsx +++ b/desktop/flipper-server/src/findInstallation.tsx @@ -7,15 +7,12 @@ * @format */ -import {FlipperServerImpl} from 'flipper-server-core'; import path from 'path'; import fs from 'fs-extra'; import os from 'os'; -export async function findInstallation( - server: FlipperServerImpl, -): Promise { - if (server.config.environmentInfo.os.platform !== 'darwin') { +export async function findInstallation(): Promise { + if (os.platform() !== 'darwin') { return; } diff --git a/desktop/flipper-server/src/index.tsx b/desktop/flipper-server/src/index.tsx index 012e97baf..47d166709 100644 --- a/desktop/flipper-server/src/index.tsx +++ b/desktop/flipper-server/src/index.tsx @@ -19,6 +19,7 @@ import yargs from 'yargs'; import open from 'open'; import {initCompanionEnv} from 'flipper-server-companion'; import { + checkPortInUse, getEnvironmentInfo, startFlipperServer, startServer, @@ -76,7 +77,7 @@ const argv = yargs .parse(process.argv.slice(1)); console.log( - `Starting flipper server with ${ + `[flipper-server] Starting flipper server with ${ argv.bundler ? 'UI bundle from source' : 'pre-bundled UI' }`, ); @@ -105,16 +106,9 @@ async function start() { keytar = electronRequire(keytarPath); } } catch (e) { - console.error('Failed to load keytar:', e); + console.error('[flipper-server] Failed to load keytar:', e); } - const {app, server, socket, readyForIncomingConnections} = await startServer({ - staticPath, - entry: `index.web${argv.bundler ? '.dev' : ''}.html`, - port: argv.port, - tcp: argv.tcp, - }); - const isProduction = process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test'; @@ -124,6 +118,18 @@ async function start() { true, ); + if (await checkPortInUse(argv.port)) { + console.warn(`[flipper-server] Port ${argv.port} is already in use`); + return; + } + + const {app, server, socket, readyForIncomingConnections} = await startServer({ + staticPath, + entry: `index.web${argv.bundler ? '.dev' : ''}.html`, + port: argv.port, + tcp: argv.tcp, + }); + const flipperServer = await startFlipperServer( rootPath, staticPath, @@ -147,7 +153,7 @@ async function start() { flipperServer.on('server-state', ({state}) => { if (state === 'error') { console.error( - '[flipper-server-process-exit] state changed to error, process will exit.', + '[flipper-server] state changed to error, process will exit.', ); process.exit(1); } @@ -165,7 +171,7 @@ async function start() { process.on('uncaughtException', (error) => { console.error( - '[flipper-server-process-exit] uncaught exception, process will exit.', + '[flipper-server] uncaught exception, process will exit.', error, ); process.exit(1); @@ -183,17 +189,23 @@ process.on('unhandledRejection', (reason, promise) => { start() .then(async (flipperServer) => { if (!argv.tcp) { - console.log('Flipper server started'); return; } - console.log( - 'Flipper server started and is listening at port ' + - chalk.green(argv.port), - ); - const token = await getAuthToken(); + + console.log('[flipper-server] listening at port ' + chalk.green(argv.port)); + + let token: string | undefined; + if (flipperServer) { + token = await getAuthToken(); + } else { + const tokenPath = path.resolve(staticPath, 'auth.token'); + token = await fs.readFile(tokenPath, 'utf-8'); + } + const searchParams = new URLSearchParams({token: token ?? ''}); const url = new URL(`http://localhost:${argv.port}?${searchParams}`); - console.log('Go to: ' + chalk.green(chalk.bold(url))); + + console.log('[flipper-server] Go to: ' + chalk.green(chalk.bold(url))); if (!argv.open) { return; } @@ -201,7 +213,7 @@ start() if (argv.bundler) { open(url.toString()); } else { - const path = await findInstallation(flipperServer); + const path = await findInstallation(); open(path ?? url.toString()); } })