Add exec Node API to FlipperLib

Summary: Changelog: Allow flipper plugins to run "exec" Node API on Flipper server.

Reviewed By: mweststrate

Differential Revision: D32881149

fbshipit-source-id: 46486a47ee9824ca68897c19fd86b4afc7f8bf1d
This commit is contained in:
Andrey Goncharov
2021-12-10 06:34:37 -08:00
committed by Facebook GitHub Bot
parent 8ca2c59499
commit e458ae76f9
10 changed files with 121 additions and 3 deletions

View File

@@ -132,6 +132,13 @@ export type IOSDeviceParams = {
};
export type FlipperServerCommands = {
/**
* @throws ExecError
*/
'node-api-exec': (
command: string,
options?: ExecOptions & {encoding?: BufferEncoding},
) => Promise<ExecOut<string>>;
'get-config': () => Promise<FlipperServerConfig>;
'get-changelog': () => Promise<string>;
'device-list': () => Promise<DeviceDescription[]>;
@@ -270,6 +277,38 @@ type ENVIRONMENT_PATHS =
| 'tempPath'
| 'desktopPath';
export interface ExecOptions {
maxBuffer?: number;
timeout?: number;
}
export interface ExecError {
message: string;
stdout: string;
stderr: string;
stack?: string;
cmd?: string;
killed?: boolean;
code?: number;
}
export interface ExecOut<StdOutErrType> {
stdout: StdOutErrType;
stderr: StdOutErrType;
}
export type BufferEncoding =
| 'ascii'
| 'utf8'
| 'utf-8'
| 'utf16le'
| 'ucs2'
| 'ucs-2'
| 'base64'
| 'base64url'
| 'latin1'
| 'binary'
| 'hex';
export type FlipperServerConfig = {
gatekeepers: Record<string, boolean>;
env: Partial<Record<ENVIRONMENT_VARIABLES, string>>;

View File

@@ -112,6 +112,7 @@ test('Correct top level API exposed', () => {
"NormalizedMenuEntry",
"Notification",
"PluginClient",
"RemoteNodeAPI",
]
`);
});

View File

@@ -39,6 +39,7 @@ export {
setFlipperLibImplementation as _setFlipperLibImplementation,
FileDescriptor,
FileEncoding,
RemoteNodeAPI,
} from './plugin/FlipperLib';
export {
MenuEntry,

View File

@@ -13,6 +13,7 @@ import {NormalizedMenuEntry} from './MenuEntry';
import {RealFlipperClient} from './Plugin';
import {Notification} from './Notification';
import {DetailSidebarProps} from '../ui/DetailSidebar';
import {ExecOptions, ExecOut, BufferEncoding} from 'flipper-common';
export type FileEncoding = 'utf-8' | 'base64';
@@ -22,6 +23,18 @@ export interface FileDescriptor {
path?: string;
}
export type RemoteNodeAPI = {
childProcess: {
exec(
command: string,
options?: {encoding?: BufferEncoding} & ExecOptions,
): Promise<ExecOut<string>>;
};
fs: {
// TODO: Fill me
};
};
/**
* This interface exposes all global methods for which an implementation will be provided by Flipper itself
*/
@@ -105,6 +118,7 @@ export interface FlipperLib {
homePath: string;
appPath: string;
};
removeNodeAPI: RemoteNodeAPI;
}
export let flipperLibInstance: FlipperLib | undefined;

View File

@@ -389,6 +389,12 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib {
appPath: process.cwd(),
homePath: `/dev/null`,
},
removeNodeAPI: {
childProcess: {
exec: jest.fn(),
},
fs: {},
},
};
}

View File

@@ -8,7 +8,6 @@
*/
import EventEmitter from 'events';
import {Logger} from 'flipper-common';
import ServerController from './comms/ServerController';
import {CertificateExchangeMedium} from './utils/CertificateProvider';
import {AndroidDeviceManager} from './devices/android/androidDeviceManager';
@@ -25,6 +24,7 @@ import {
FlipperServer,
UninitializedClient,
FlipperServerConfig,
Logger,
} from 'flipper-common';
import {ServerDevice} from './devices/ServerDevice';
import {Base64} from 'js-base64';
@@ -43,6 +43,7 @@ import {
internGraphGETAPIRequest,
internGraphPOSTAPIRequest,
} from './fb-stubs/internRequests';
import {commandNodeApiExec} from './commands/NodeApiExec';
export const SERVICE_FLIPPER = 'flipper.oAuthToken';
@@ -213,6 +214,7 @@ export class FlipperServerImpl implements FlipperServer {
}
private commandHandler: FlipperServerCommands = {
'node-api-exec': commandNodeApiExec,
'get-config': async () => this.config,
'get-changelog': getChangelog,
'device-list': async () => {

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) Facebook, Inc. and its 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 {ExecError, FlipperServerCommands} from 'flipper-common';
import {exec} from 'child_process';
import assert from 'assert';
export const commandNodeApiExec: FlipperServerCommands['node-api-exec'] =
async (command, options) =>
new Promise((resolve, reject) =>
exec(command, options, (error, stdout, stderr) => {
assert(typeof stdout === 'string');
assert(typeof stderr === 'string');
if (error) {
const wrappedError: ExecError = {
message: error.message,
stdout,
stderr,
cmd: error.cmd,
killed: error.killed,
code: error.code,
stack: error.stack,
};
reject(wrappedError);
return;
}
resolve({
stdout,
stderr,
});
}),
);

View File

@@ -36,6 +36,7 @@ export function startSocketServer(
})
.catch((error: any) => {
if (connected) {
// TODO: Serialize error
client.emit('exec-response-error', id, error.toString());
}
});

View File

@@ -69,6 +69,7 @@ export function createFlipperServer(): Promise<FlipperServer> {
});
socket.on('exec-response-error', (id: number, error: any) => {
// TODO: Deserialize error
console.debug('exec <<< [SERVER ERROR]', id, error);
const entry = pendingRequests.get(id);
if (!entry) {

View File

@@ -7,8 +7,8 @@
* @format
*/
import {_setFlipperLibImplementation} from 'flipper-plugin';
import type {Logger} from 'flipper-common';
import {_setFlipperLibImplementation, RemoteNodeAPI} from 'flipper-plugin';
import type {BufferEncoding, ExecOptions, Logger} from 'flipper-common';
import type {Store} from '../reducers';
import createPaste from '../fb-stubs/createPaste';
import type BaseDevice from '../devices/BaseDevice';
@@ -63,5 +63,19 @@ export function initializeFlipperLibImplementation(
appPath: renderHost.serverConfig.paths.appPath,
homePath: renderHost.serverConfig.paths.homePath,
},
removeNodeAPI: {
childProcess: {
exec: (async (
command: string,
options?: ExecOptions & {encoding?: BufferEncoding},
) =>
renderHost.flipperServer.exec(
'node-api-exec',
command,
options,
)) as RemoteNodeAPI['childProcess']['exec'],
},
fs: {},
},
});
}