Files
flipper/desktop/flipper-dump/src/index.tsx
Andres Orozco Gonzalez b2dae4da81 Add support to load settings from options (flag) and default to specific options
Summary: This change gives priority to a user option settingsString to set up the flipper-server configuration and load them from a json string. Also giving the user the chance to avoid flipper-server looking at the launcher config files in the computer

Reviewed By: mweststrate

Differential Revision: D34210110

fbshipit-source-id: 9e852b79da106b5140c59116fd7d0c0f3155e620
2022-02-16 06:23:45 -08:00

276 lines
7.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 fs from 'fs';
import os from 'os';
import yargs from 'yargs';
import {
FlipperServerImpl,
getEnvironmentInfo,
loadLauncherSettings,
loadProcessConfig,
loadSettings,
} from 'flipper-server-core';
import {
ClientDescription,
Logger,
DeviceDescription,
setLoggerInstance,
parseEnvironmentVariables,
} from 'flipper-common';
import path from 'path';
import {stdout} from 'process';
// eslint-disable-next-line
const packageJson = JSON.parse(fs.readFileSync('../package.json', 'utf-8'));
const argv = yargs
.usage('$0 [args]')
.options({
device: {
describe: 'The device name or serial/udid to listen to',
type: 'string',
demandOption: true,
},
client: {
describe: 'The application name to listen to',
type: 'string',
demandOption: true,
},
plugin: {
describe: 'Plugin id to listen to',
type: 'string',
demandOption: true,
},
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,
},
// TODO: support filtering events
// TODO: support verbose mode
// TODO: support post processing messages
})
.version(packageJson.version)
.help()
// .strict()
.parse(process.argv.slice(1));
async function start(deviceQuery: string, appName: string, pluginId: string) {
return new Promise(async (_resolve, reject) => {
const staticPath = path.resolve(__dirname, '..', '..', 'static');
let device: DeviceDescription | undefined;
let deviceResolver: () => void;
const devicePromise: Promise<void> = new Promise((resolve) => {
deviceResolver = resolve;
});
let client: ClientDescription | undefined;
const logger = createLogger();
setLoggerInstance(logger);
// avoid logging to STDOUT!
console.log = console.error;
console.debug = () => {};
console.info = console.error;
const environmentInfo = await getEnvironmentInfo(staticPath, false);
// TODO: initialise FB user manager to be able to do certificate exchange
const server = new FlipperServerImpl(
{
environmentInfo,
env: parseEnvironmentVariables(process.env),
gatekeepers: {},
paths: {
staticPath,
tempPath: os.tmpdir(),
appPath: `/dev/null`,
homePath: `/dev/null`,
execPath: process.execPath,
desktopPath: `/dev/null`,
},
launcherSettings: await loadLauncherSettings(argv.launcherSettings),
processConfig: loadProcessConfig(process.env),
settings: await loadSettings(argv.settingsString),
validWebSocketOrigins: [],
},
logger,
);
logger.info(
`Waiting for device '${deviceQuery}' client '${appName}' plugin '${pluginId}' ...`,
);
server.on('notification', ({type, title, description}) => {
if (type === 'error') {
reject(new Error(`${title}: ${description}`));
}
});
server.on('server-error', reject);
server.on('server-state', ({state, error}) => {
if (state === 'error') {
reject(error);
}
});
server.on('device-connected', (deviceInfo) => {
logger.info(
`Detected device [${deviceInfo.os}] ${deviceInfo.title} ${deviceInfo.serial}`,
);
if (deviceInfo.serial == deviceQuery) {
logger.info(`Device matched on device serial ${deviceQuery}`);
device = deviceInfo;
deviceResolver();
} else if (deviceInfo.title === deviceQuery) {
logger.info(`Device matched on device title ${deviceQuery}`);
device = deviceInfo;
deviceResolver();
}
});
server.on('device-disconnected', (deviceInfo) => {
if (device && deviceInfo.serial === device.serial) {
reject(new Error('Device disconnected: ' + deviceInfo.serial));
}
});
server.on('client-setup', (client) => {
logger.info(
`Connection attempt: ${client.appName} on ${client.deviceName}`,
);
});
server.on(
'client-connected',
async (clientDescription: ClientDescription) => {
// device matching is promisified, as we clients can arrive before device is detected
await devicePromise;
if (clientDescription.query.app === appName) {
if (clientDescription.query.device_id === device!.serial) {
logger.info(`Client matched: ${clientDescription.id}`);
client = clientDescription;
try {
// fetch plugins
const response = await server.exec(
'client-request-response',
client.id,
{
method: 'getPlugins',
},
);
logger.info(JSON.stringify(response));
if (response.error) {
reject(response.error);
return;
}
const plugins: string[] = (response.success as any).plugins;
logger.info('Detected plugins ' + plugins.join(','));
if (!plugins.includes(pluginId)) {
// TODO: what if it only registers later?
throw new Error(
`Plugin ${pluginId} was not registered on client ${client.id}`,
);
}
logger.info(`Starting plugin ` + pluginId);
const response2 = await server.exec(
'client-request-response',
client.id,
{
method: 'init',
params: {plugin: pluginId},
},
);
if (response2.error) {
reject(response2.error);
}
logger.info('Plugin initialised');
} catch (e) {
reject(e);
}
}
}
},
);
server.on('client-disconnected', ({id}) => {
if (id === client?.id) {
// TODO: maybe we need a flag to signal that this might be undesired?
logger.info('Target application disconnected, exiting...');
process.exit(0);
}
});
server.on('client-message', ({id, message}) => {
if (id === client?.id) {
const parsed = JSON.parse(message);
if (parsed.method === 'execute') {
if (parsed.params.api === pluginId) {
// TODO: customizable format
stdout.write(
`\n\n\n[${parsed.params.method}]\n${JSON.stringify(
parsed.params.params,
null,
2,
)}\n`,
);
}
} else {
logger.warn('Dropping message ', message);
}
}
});
server
.connect()
.then(() => {
logger.info(
'Flipper server started and accepting device / client connections',
);
})
.catch(reject);
});
}
function createLogger(): Logger {
return {
track() {
// no-op
},
trackTimeSince() {
// no-op
},
debug() {
// TODO: support this with a --verbose flag
},
error(...args: any[]) {
console.error(...args);
},
warn(...args: any[]) {
console.warn(...args);
},
info(...args: any[]) {
// we want to redirect info level logging to STDERR! So that STDOUT is used merely for plugin output
console.error(...args);
},
};
}
start(argv.device!, argv.client!, argv.plugin!).catch((e) => {
// eslint-disable-next-line
console.error(e);
process.exit(1);
});