Summary: To avoid the cases whereas Flipper opens up in engineer's default browser which may be different than Chrome. Reviewed By: aigoncharov Differential Revision: D46682220 fbshipit-source-id: 38d0ddefbc67989c5ec97a66e4a419318a66bc95
367 lines
9.8 KiB
TypeScript
367 lines
9.8 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
import './fb-stubs';
|
|
import './electronRequire';
|
|
import process from 'process';
|
|
import chalk from 'chalk';
|
|
import path from 'path';
|
|
import {attachDevServer} from './attachDevServer';
|
|
import {initializeLogger} from './logger';
|
|
import fs from 'fs-extra';
|
|
import yargs from 'yargs';
|
|
import open from 'open';
|
|
import os from 'os';
|
|
import {initCompanionEnv} from 'flipper-server-companion';
|
|
import {
|
|
checkPortInUse,
|
|
getEnvironmentInfo,
|
|
hasAuthToken,
|
|
startFlipperServer,
|
|
startServer,
|
|
tracker,
|
|
} from 'flipper-server-core';
|
|
import {isTest} from 'flipper-common';
|
|
import exitHook from 'exit-hook';
|
|
import {getAuthToken} from 'flipper-server-core';
|
|
import {findInstallation} from './findInstallation';
|
|
import ReconnectingWebSocket from 'reconnecting-websocket';
|
|
import {
|
|
createFlipperServerWithSocket,
|
|
FlipperServerState,
|
|
} from 'flipper-server-client';
|
|
import WS from 'ws';
|
|
|
|
const argv = yargs
|
|
.usage('yarn flipper-server [args]')
|
|
.options({
|
|
port: {
|
|
describe: 'TCP port to serve on',
|
|
type: 'number',
|
|
default: 52342,
|
|
},
|
|
bundler: {
|
|
describe:
|
|
'Serve the UI bundle from source. This option only works for source checkouts',
|
|
type: 'boolean',
|
|
default: false,
|
|
},
|
|
open: {
|
|
describe: 'Open Flipper in the default browser after starting',
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
failFast: {
|
|
describe:
|
|
'Exit the process immediately if the server cannot start, for example due to an incorrect configuration.',
|
|
type: 'boolean',
|
|
default: false,
|
|
},
|
|
settingsString: {
|
|
describe: `override the existing defaults settings of flipper (settings.json file) e.g "{"androidHome":"/usr/local/bin","enableAndroid":true}"`,
|
|
type: 'string',
|
|
default: '',
|
|
},
|
|
launcherSettings: {
|
|
describe:
|
|
'Open Flipper with the configuration stored in .config folder for the launcher',
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
tcp: {
|
|
describe:
|
|
'Open a TCP port (--no-tcp can be specified as to use unix-domain-socket exclusively)',
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
replace: {
|
|
describe: 'Replaces any running instance, if any.',
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
})
|
|
.version('DEV')
|
|
.help()
|
|
.parse(process.argv.slice(1));
|
|
|
|
console.log(
|
|
`[flipper-server] Starting flipper server with ${
|
|
argv.bundler ? 'UI bundle from source' : 'pre-bundled UI'
|
|
}`,
|
|
);
|
|
|
|
/**
|
|
* When running as a standlone app not run from the terminal, the process itself
|
|
* doesn't inherit the user's terminal PATH environment variable.
|
|
* The PATH, when NOT launched from terminal is `/usr/bin:/bin:/usr/sbin:/sbin`
|
|
* which is missing `/usr/local/bin`.
|
|
*/
|
|
if (os.platform() !== 'win32') {
|
|
process.env.PATH = '/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin';
|
|
}
|
|
|
|
const rootPath = argv.bundler
|
|
? path.resolve(__dirname, '..', '..')
|
|
: path.resolve(__dirname, '..'); // in pre packaged versions of the server, static is copied inside the package
|
|
const staticPath = path.join(rootPath, 'static');
|
|
|
|
async function connectToRunningServer(url: URL) {
|
|
console.info(`[flipper-server] Obtain connection to existing server.`);
|
|
console.info(`[flipper-server] URL: ${url}`);
|
|
const options = {
|
|
WebSocket: class WSWithUnixDomainSocketSupport extends WS {
|
|
constructor(url: string, protocols: string | string[]) {
|
|
// Flipper exports could be large, and we snd them over the wire
|
|
// Setting this limit fairly high (1GB) to allow any reasonable Flipper export to be loaded
|
|
super(url, protocols, {maxPayload: 1024 * 1024 * 1024});
|
|
}
|
|
},
|
|
};
|
|
const socket = new ReconnectingWebSocket(url.toString(), [], options);
|
|
const server = await createFlipperServerWithSocket(
|
|
socket as WebSocket,
|
|
(_state: FlipperServerState) => {},
|
|
);
|
|
return server;
|
|
}
|
|
|
|
async function shutdown() {
|
|
console.info('[flipper-server] Attempt to shutdown.');
|
|
|
|
let token: string | undefined;
|
|
if (await hasAuthToken()) {
|
|
token = await getAuthToken();
|
|
}
|
|
|
|
const searchParams = new URLSearchParams(token ? {token} : {});
|
|
const url = new URL(`ws://localhost:${argv.port}?${searchParams}`);
|
|
const server = await connectToRunningServer(url);
|
|
await server.exec('shutdown').catch(() => {
|
|
/** shutdown will ultimately make this request fail, ignore error. */
|
|
console.info('[flipper-server] Shutdown may have succeeded');
|
|
});
|
|
server.close();
|
|
}
|
|
|
|
async function start() {
|
|
console.info('[flipper-server][bootstrap] Booting up');
|
|
const t0 = performance.now();
|
|
|
|
const enhanceLogger = await initializeLogger(staticPath);
|
|
|
|
const t1 = performance.now();
|
|
const loggerInitializedMS = t1 - t0;
|
|
console.info(
|
|
`[flipper-server][bootstrap] Logger initialised (${loggerInitializedMS} ms)`,
|
|
);
|
|
|
|
let keytar: any = undefined;
|
|
try {
|
|
if (!isTest()) {
|
|
const keytarPath = path.join(
|
|
staticPath,
|
|
'native-modules',
|
|
`keytar-${process.platform}-${process.arch}.node`,
|
|
);
|
|
if (!(await fs.pathExists(keytarPath))) {
|
|
throw new Error(
|
|
`Keytar binary does not exist for platform ${process.platform}-${process.arch}`,
|
|
);
|
|
}
|
|
keytar = electronRequire(keytarPath);
|
|
}
|
|
} catch (e) {
|
|
console.error('[flipper-server] Failed to load keytar:', e);
|
|
}
|
|
|
|
const t2 = performance.now();
|
|
const keytarLoadedMS = t2 - t1;
|
|
console.info(
|
|
`[flipper-server][bootstrap] Keytar loaded (${keytarLoadedMS} ms)`,
|
|
);
|
|
|
|
const isProduction =
|
|
process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test';
|
|
|
|
const environmentInfo = await getEnvironmentInfo(
|
|
rootPath,
|
|
isProduction,
|
|
true,
|
|
);
|
|
|
|
if (await checkPortInUse(argv.port)) {
|
|
console.warn(`[flipper-server] Port ${argv.port} is already in use`);
|
|
if (!argv.replace) {
|
|
console.info(`[flipper-server] Not replacing existing instance, exiting`);
|
|
return;
|
|
}
|
|
await shutdown();
|
|
}
|
|
|
|
const t3 = performance.now();
|
|
const runningInstanceShutdownMS = t3 - t2;
|
|
console.info(
|
|
`[flipper-server][bootstrap] Check for running instances completed (${runningInstanceShutdownMS} ms)`,
|
|
);
|
|
|
|
const {app, server, socket, readyForIncomingConnections} = await startServer({
|
|
staticPath,
|
|
entry: `index.web${argv.bundler ? '.dev' : ''}.html`,
|
|
port: argv.port,
|
|
tcp: argv.tcp,
|
|
});
|
|
|
|
const t4 = performance.now();
|
|
const httpServerStartedMS = t4 - t3;
|
|
|
|
console.info(
|
|
`[flipper-server][bootstrap] HTTP server started (${httpServerStartedMS} ms)`,
|
|
);
|
|
|
|
const flipperServer = await startFlipperServer(
|
|
rootPath,
|
|
staticPath,
|
|
argv.settingsString,
|
|
argv.launcherSettings,
|
|
keytar,
|
|
'external',
|
|
environmentInfo,
|
|
);
|
|
|
|
const t5 = performance.now();
|
|
const serverCreatedMS = t5 - t4;
|
|
console.info(
|
|
`[flipper-server][bootstrap] FlipperServer created (${serverCreatedMS} ms)`,
|
|
);
|
|
|
|
exitHook(async () => {
|
|
await flipperServer.close();
|
|
});
|
|
|
|
enhanceLogger((logEntry) => {
|
|
flipperServer.emit('server-log', logEntry);
|
|
});
|
|
|
|
const companionEnv = await initCompanionEnv(flipperServer);
|
|
|
|
const t6 = performance.now();
|
|
const companionEnvironmentInitializedMS = t6 - t5;
|
|
|
|
console.info(
|
|
`[flipper-server][bootstrap] Companion environment initialised (${companionEnvironmentInitializedMS} ms)`,
|
|
);
|
|
|
|
if (argv.failFast) {
|
|
flipperServer.on('server-state', ({state}) => {
|
|
if (state === 'error') {
|
|
console.error(
|
|
'[flipper-server] state changed to error, process will exit.',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
}
|
|
await flipperServer.connect();
|
|
|
|
const t7 = performance.now();
|
|
const appServerStartedMS = t7 - t6;
|
|
console.info(
|
|
`[flipper-server][bootstrap] Ready for app connections (${appServerStartedMS} ms)`,
|
|
);
|
|
|
|
if (argv.bundler) {
|
|
await attachDevServer(app, server, socket, rootPath);
|
|
}
|
|
|
|
const t8 = performance.now();
|
|
const developmentServerAttachedMS = t8 - t7;
|
|
console.info(
|
|
`[flipper-server][bootstrap] Development server attached (${developmentServerAttachedMS} ms)`,
|
|
);
|
|
readyForIncomingConnections(flipperServer, companionEnv);
|
|
|
|
const t9 = performance.now();
|
|
const serverStartedMS = t9 - t8;
|
|
console.info(
|
|
`[flipper-server][bootstrap] Listening at port ${chalk.green(
|
|
argv.port,
|
|
)} (${serverStartedMS} ms)`,
|
|
);
|
|
|
|
tracker.track('server-bootstrap-performance', {
|
|
loggerInitializedMS,
|
|
keytarLoadedMS,
|
|
runningInstanceShutdownMS,
|
|
httpServerStartedMS,
|
|
serverCreatedMS,
|
|
companionEnvironmentInitializedMS,
|
|
appServerStartedMS,
|
|
developmentServerAttachedMS,
|
|
serverStartedMS,
|
|
});
|
|
}
|
|
|
|
async function launch() {
|
|
if (!argv.tcp) {
|
|
return;
|
|
}
|
|
|
|
let token: string | undefined;
|
|
if (await hasAuthToken()) {
|
|
token = await getAuthToken();
|
|
}
|
|
|
|
const searchParams = new URLSearchParams({token: token ?? ''});
|
|
const url = new URL(`http://localhost:${argv.port}?${searchParams}`);
|
|
|
|
console.log('Go to: ' + chalk.green(chalk.bold(url)));
|
|
if (!argv.open) {
|
|
return;
|
|
}
|
|
|
|
const openInBrowser = () => {
|
|
open(url.toString(), {app: {name: open.apps.chrome}});
|
|
};
|
|
|
|
if (argv.bundler) {
|
|
openInBrowser();
|
|
} else {
|
|
const path = await findInstallation();
|
|
if (path) {
|
|
open(path);
|
|
} else {
|
|
openInBrowser();
|
|
}
|
|
}
|
|
}
|
|
|
|
process.on('uncaughtException', (error) => {
|
|
console.error(
|
|
'[flipper-server] uncaught exception, process will exit.',
|
|
error,
|
|
);
|
|
process.exit(1);
|
|
});
|
|
|
|
process.on('unhandledRejection', (reason, promise) => {
|
|
console.warn(
|
|
'[flipper-server] unhandled rejection for:',
|
|
promise,
|
|
'reason:',
|
|
reason,
|
|
);
|
|
});
|
|
|
|
start()
|
|
.then(launch)
|
|
.catch((e) => {
|
|
console.error(chalk.red('Server startup error: '), e);
|
|
process.exit(1);
|
|
});
|