diff --git a/desktop/flipper-common/src/utils/Logger.tsx b/desktop/flipper-common/src/utils/Logger.tsx index f0e9383c0..b2e23c397 100644 --- a/desktop/flipper-common/src/utils/Logger.tsx +++ b/desktop/flipper-common/src/utils/Logger.tsx @@ -36,13 +36,13 @@ export interface Logger { data?: any, ): void; - info(data: any, category: string): void; + info(...args: any[]): void; - warn(data: any, category: string): void; + warn(...args: any[]): void; - error(data: any, category: string): void; + error(...args: any[]): void; - debug(data: any, category: string): void; + debug(...args: any[]): void; } let instance: Logger | null = null; diff --git a/desktop/flipper-dump/README.md b/desktop/flipper-dump/README.md index 9d7ce5a26..b014f2853 100644 --- a/desktop/flipper-dump/README.md +++ b/desktop/flipper-dump/README.md @@ -1,3 +1,20 @@ -# flipper-dump (TBD) +# flipper-dump Stand alone Flipper command, that uses flipper-server-core to connect to apps and dump all incoming messages. + +This package is currently a proof of concept and can be used like: + +`yarn start --device='iPhone 12' --client='Instagram' --plugin='AnalyticsLogging'` + +Or to capture all output to a file (meta messages will be printed on STDERR): + +`yarn --silent start --device='iPhone 12' --client='Instagram' --plugin='AnalyticsLogging' > out.txt` + +Future features: + +* [ ] Package so that it can be run using `npx` +* [ ] Support filter & transformation functions +* [ ] See `TODO`s in code +* [ ] Support better configuration +* [ ] Custom formatting +* [ ] [FB] Support Certificate Exchange clients diff --git a/desktop/flipper-dump/package.json b/desktop/flipper-dump/package.json index 5fb856f86..1e86e703e 100644 --- a/desktop/flipper-dump/package.json +++ b/desktop/flipper-dump/package.json @@ -9,10 +9,16 @@ "types": "lib/index.d.ts", "license": "MIT", "bugs": "https://github.com/facebook/flipper/issues", - "dependencies": {}, + "dependencies": { + "flipper-common": "0.0.0", + "flipper-server-core": "0.0.0", + "ts-node": "^10.2.1", + "yargs": "^17.2.1" + }, "devDependencies": {}, "peerDependencies": {}, "scripts": { + "start": "ts-node src/index.tsx", "reset": "rimraf lib *.tsbuildinfo", "build": "tsc -b", "prepack": "yarn reset && yarn build" diff --git a/desktop/flipper-dump/src/index.tsx b/desktop/flipper-dump/src/index.tsx index 90a375a48..215a42abc 100644 --- a/desktop/flipper-dump/src/index.tsx +++ b/desktop/flipper-dump/src/index.tsx @@ -7,6 +7,239 @@ * @format */ -export function helloWorld() { - return true; +import fs from 'fs'; +import os from 'os'; +import yargs from 'yargs'; +import {FlipperServerImpl, setFlipperServerConfig} from 'flipper-server-core'; +import { + ClientDescription, + Logger, + DeviceDescription, + setLoggerInstance, +} 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 to listen to', + type: 'string', + }, + client: { + describe: 'The application name to listen to', + type: 'string', + }, + plugin: { + describe: 'Plugin id to listen to', + type: 'string', + }, + // 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(deviceTitle: string, appName: string, pluginId: string) { + return new Promise((_resolve, reject) => { + let device: DeviceDescription | undefined; + let deviceResolver: () => void; + const devicePromise: Promise = 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; + + // TODO: make these better overridable + setFlipperServerConfig({ + enableAndroid: true, + androidHome: process.env.ANDROID_HOME || '/opt/android_sdk', + idbPath: '/usr/local/bin/idb', + enableIOS: true, + enablePhysicalIOS: true, + staticPath: path.resolve(__dirname, '..', '..', 'static'), + tmpPath: os.tmpdir(), + validWebSocketOrigins: [], + }); + // TODO: initialise FB user manager to be able to do certificate exchange + + const server = new FlipperServerImpl(logger); + + logger.info( + `Waiting for device '${deviceTitle}' 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('device-connected', (deviceInfo) => { + logger.info( + `Detected device [${deviceInfo.os}] ${deviceInfo.title} ${deviceInfo.serial}`, + ); + if (deviceInfo.title === deviceTitle) { + logger.info('Device matched'); + 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) { + reject(new Error('Application disconnected')); + } + }); + + 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 + .start() + .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); + }, + }; +} + +if (!argv.device) { + console.error('--device not specified'); + process.exit(1); +} +if (!argv.client) { + console.error('--client not specified'); + process.exit(1); +} +if (!argv.plugin) { + console.error('--plugin not specified'); + process.exit(1); +} +start(argv.device!, argv.client!, argv.plugin!).catch((e) => { + // eslint-disable-next-line + console.error(e); + process.exit(1); +}); diff --git a/desktop/flipper-dump/tsconfig.json b/desktop/flipper-dump/tsconfig.json index 9161fe7a6..1dbb2342f 100644 --- a/desktop/flipper-dump/tsconfig.json +++ b/desktop/flipper-dump/tsconfig.json @@ -8,5 +8,8 @@ { "path": "../flipper-server-core" } - ] + ], + "ts-node": { + "transpileOnly": true + } } diff --git a/desktop/yarn.lock b/desktop/yarn.lock index f579f9cbc..b66a29dba 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -1669,6 +1669,18 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz#118511f316e2e87ee4294761868e254d3da47960" + integrity sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + "@ctrl/tinycolor@^3.4.0": version "3.4.0" resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz#c3c5ae543c897caa9c2a68630bed355be5f9990f" @@ -2597,6 +2609,26 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + "@types/adobe__node-fetch-retry@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/adobe__node-fetch-retry/-/adobe__node-fetch-retry-1.0.2.tgz#4f9f174da6fc4775cbe209dcb087f7e7f763bf81" @@ -3541,6 +3573,11 @@ acorn-walk@^7.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" @@ -3556,6 +3593,11 @@ acorn@^8.2.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.3.0.tgz#1193f9b96c4e8232f00b11a9edff81b2c8b98b88" integrity sha512-tqPKHZ5CaBJw0Xmy0ZZvLs1qTV+BNFSyvn77ASXkpBNfIRk8ev26fKrD9iLGwGA9zedPao52GSHzq8lyZG0NUw== +acorn@^8.4.1: + version "8.5.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" + integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + adbkit-logcat@^1.1.0, adbkit-logcat@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/adbkit-logcat/-/adbkit-logcat-2.0.1.tgz#d4986b9fc7cfda42733389d46a52124abef43ca5" @@ -13007,6 +13049,24 @@ ts-jest@^26.5.6: semver "7.x" yargs-parser "20.x" +ts-node@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.2.1.tgz#4cc93bea0a7aba2179497e65bb08ddfc198b3ab5" + integrity sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw== + dependencies: + "@cspotcode/source-map-support" "0.6.1" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + yn "3.1.1" + ts-node@^9.1.1: version "9.1.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" @@ -13791,6 +13851,19 @@ yargs@^17.0.1: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.2.1: + version "17.2.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea" + integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yauzl@^2.10.0, yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"