diff --git a/desktop/app/src/init.tsx b/desktop/app/src/init.tsx index 7dba9d674..5d268a825 100644 --- a/desktop/app/src/init.tsx +++ b/desktop/app/src/init.tsx @@ -19,9 +19,12 @@ import { FlipperServerState, } from 'flipper-server-client'; import { + checkPortInUse, FlipperServerImpl, + getAuthToken, getEnvironmentInfo, getGatekeepers, + hasAuthToken, loadLauncherSettings, loadProcessConfig, loadSettings, @@ -77,7 +80,7 @@ async function getKeytarModule(staticPath: string): Promise { return keytar; } -async function getExternalServer(url: string) { +async function getExternalServer(url: URL) { const options = { WebSocket: class WSWithUnixDomainSocketSupport extends WS { constructor(url: string, protocols: string | string[]) { @@ -87,11 +90,12 @@ async function getExternalServer(url: string) { } }, }; - const socket = new ReconnectingWebSocket(url, [], options); + const socket = new ReconnectingWebSocket(url.toString(), [], options); const server = await createFlipperServerWithSocket( socket as WebSocket, (_state: FlipperServerState) => {}, ); + return server; } @@ -117,19 +121,51 @@ async function getFlipperServer( const settings = await loadSettings(); const socketPath = await makeSocketPath(); - let externalServerConnectionURL = `ws+unix://${socketPath}`; + const port = 52342; + /** + * Only attempt to use the auth token if one is available. Otherwise, + * trying to get the auth token will try to generate one if it does not exist. + * At this state, it would be impossible to generate it as our certificates + * may not be available yet. + */ + let token: string | undefined; + if (await hasAuthToken()) { + token = await getAuthToken(); + } + // check first with the actual TCP socket + const searchParams = new URLSearchParams(token ? {token} : {}); + const TCPconnectionURL = new URL(`ws://localhost:${port}?${searchParams}`); + const UDSconnectionURL = new URL(`ws+unix://${socketPath}`); - // On Windows this is going to return false at all times as we do not use domain sockets there. - let serverRunning = await checkSocketInUse(socketPath); - if (serverRunning) { - console.info( - 'flipper-server: currently running/listening, attempt to shutdown', - ); - const server = await getExternalServer(externalServerConnectionURL); + /** + * Attempt to shutdown a running instance of Flipper server. + * @param url The URL used for connection. + */ + async function shutdown(url: URL) { + console.info('[flipper-server] Attempt to shutdown.'); + + const server = await getExternalServer(url); await server.exec('shutdown').catch(() => { /** shutdown will ultimately make this request fail, ignore error. */ + console.info('[flipper-server] Shutdown may have succeeded'); }); - serverRunning = false; + } + + /** + * In this case, order matters. First, check if the TCP port is in use. If so, + * then shut down the TCP socket. If not, then try the UDS socket. + * + * UDS doesn't accept arguments in the query string, this effectively creates a different + * socket path which then doesn't match the one used by the server. + */ + if (await checkPortInUse(port)) { + console.warn(`[flipper-server] TCP port ${port} is already in use.`); + + await shutdown(TCPconnectionURL); + } else if (await checkSocketInUse(socketPath)) { + console.warn(`[flipper-server] UDS socket is already in use.`); + + await shutdown(UDSconnectionURL); } const getEmbeddedServer = async () => { @@ -158,40 +194,32 @@ async function getFlipperServer( return server; }; - // Failed to start Flipper desktop: Error: flipper-server disconnected + if (serverUsageEnabled && (!settings.server || settings.server.enabled)) { - if (!serverRunning) { - console.info('flipper-server: not running/listening, start'); + console.info('flipper-server: not running/listening, start'); - const port = 52342; - if (os.platform() === 'win32') { - externalServerConnectionURL = `ws://localhost:${port}`; - } + const {readyForIncomingConnections} = await startServer({ + staticPath, + entry: 'index.web.dev.html', + tcp: false, + port, + }); - const {readyForIncomingConnections} = await startServer({ - staticPath, - entry: 'index.web.dev.html', - tcp: false, - port, - }); + const server = await startFlipperServer( + appPath, + staticPath, + '', + false, + keytar, + 'embedded', + environmentInfo, + ); - const server = await startFlipperServer( - appPath, - staticPath, - '', - false, - keytar, - 'embedded', - environmentInfo, - ); + const companionEnv = await initCompanionEnv(server); + await server.connect(); + await readyForIncomingConnections(server, companionEnv); - const companionEnv = await initCompanionEnv(server); - await server.connect(); - - await readyForIncomingConnections(server, companionEnv); - } - - return getExternalServer(externalServerConnectionURL); + return getExternalServer(UDSconnectionURL); } return getEmbeddedServer(); } diff --git a/desktop/flipper-server-core/src/index.tsx b/desktop/flipper-server-core/src/index.tsx index a9cbf3e45..627d651f4 100644 --- a/desktop/flipper-server-core/src/index.tsx +++ b/desktop/flipper-server-core/src/index.tsx @@ -22,4 +22,4 @@ export {isFBBuild} from './fb-stubs/constants'; export {WEBSOCKET_MAX_MESSAGE_SIZE} from './comms/ServerWebSocket'; -export {getAuthToken} from './utils/certificateUtils'; +export {getAuthToken, hasAuthToken} from './utils/certificateUtils'; diff --git a/desktop/flipper-server-core/src/utils/certificateUtils.tsx b/desktop/flipper-server-core/src/utils/certificateUtils.tsx index d6ba92c74..4e06b9329 100644 --- a/desktop/flipper-server-core/src/utils/certificateUtils.tsx +++ b/desktop/flipper-server-core/src/utils/certificateUtils.tsx @@ -266,10 +266,6 @@ const writeToTempFile = async (content: string): Promise => { return path; }; -const tokenFilename = 'auth.token'; -const getTokenPath = (config: FlipperServerConfig): string => { - return getFilePath(tokenFilename); -}; const manifestFilename = 'manifest.json'; const getManifestPath = (config: FlipperServerConfig): string => { return path.resolve(config.paths.staticPath, manifestFilename); @@ -315,8 +311,8 @@ export const generateAuthToken = async () => { return token; }; -export const getAuthToken = async () => { - if (!(await fs.pathExists(serverAuthToken))) { +export const getAuthToken = async (): Promise => { + if (!(await hasAuthToken())) { return generateAuthToken(); } @@ -324,6 +320,10 @@ export const getAuthToken = async () => { return token.toString(); }; +export const hasAuthToken = async (): Promise => { + return fs.pathExists(serverAuthToken); +}; + export const validateAuthToken = (token: string) => { if (!serverConfig) { throw new Error(