Create flipper-server-client package
Summary: FlipperServerClient is a useful abstraction for any JS-based client of headless Flipper. No need to bundle it with flipper-frontend-core, as the rest is not useful for external clients. Currently, I am planning to add it to jest-e2e to send commands to Flipper Reviewed By: lblasa Differential Revision: D40765668 fbshipit-source-id: af48710bb15444ac1ecd649fe9a2ab252f3088f3
This commit is contained in:
committed by
Facebook GitHub Bot
parent
0f5ba32736
commit
226ccf91f6
@@ -1,194 +0,0 @@
|
||||
/**
|
||||
* 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 EventEmitter from 'eventemitter3';
|
||||
import {
|
||||
ExecWebSocketMessage,
|
||||
FlipperServer,
|
||||
FlipperServerCommands,
|
||||
FlipperServerExecOptions,
|
||||
ServerWebSocketMessage,
|
||||
} from 'flipper-common';
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket';
|
||||
|
||||
const CONNECTION_TIMEOUT = 30 * 1000;
|
||||
const EXEC_TIMEOUT = 45 * 1000;
|
||||
|
||||
export enum FlipperServerState {
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTED,
|
||||
}
|
||||
|
||||
export function createFlipperServer(
|
||||
host: string,
|
||||
port: number,
|
||||
onStateChange: (state: FlipperServerState) => void,
|
||||
): Promise<FlipperServer> {
|
||||
const socket = new ReconnectingWebSocket(`ws://${host}:${port}`);
|
||||
return createFlipperServerWithSocket(socket, onStateChange);
|
||||
}
|
||||
|
||||
export function createFlipperServerWithSocket(
|
||||
socket: ReconnectingWebSocket,
|
||||
onStateChange: (state: FlipperServerState) => void,
|
||||
): Promise<FlipperServer> {
|
||||
onStateChange(FlipperServerState.CONNECTING);
|
||||
|
||||
return new Promise<FlipperServer>((resolve, reject) => {
|
||||
let initialConnectionTimeout: number | undefined = window.setTimeout(() => {
|
||||
reject(
|
||||
new Error('Failed to connect to flipper-server in a timely manner'),
|
||||
);
|
||||
}, CONNECTION_TIMEOUT);
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
const pendingRequests: Map<
|
||||
number,
|
||||
{
|
||||
resolve: (data: any) => void;
|
||||
reject: (data: any) => void;
|
||||
timeout: ReturnType<typeof setTimeout>;
|
||||
}
|
||||
> = new Map();
|
||||
let requestId = 0;
|
||||
let connected = false;
|
||||
|
||||
socket.addEventListener('open', () => {
|
||||
if (initialConnectionTimeout) {
|
||||
resolve(flipperServer);
|
||||
clearTimeout(initialConnectionTimeout);
|
||||
initialConnectionTimeout = undefined;
|
||||
}
|
||||
|
||||
onStateChange(FlipperServerState.CONNECTED);
|
||||
connected = true;
|
||||
});
|
||||
|
||||
socket.addEventListener('close', () => {
|
||||
onStateChange(FlipperServerState.DISCONNECTED);
|
||||
connected = false;
|
||||
pendingRequests.forEach((r) =>
|
||||
r.reject(new Error('flipper-server disconnected')),
|
||||
);
|
||||
pendingRequests.clear();
|
||||
});
|
||||
|
||||
socket.addEventListener('message', ({data}) => {
|
||||
const {event, payload} = JSON.parse(
|
||||
data.toString(),
|
||||
) as ServerWebSocketMessage;
|
||||
|
||||
switch (event) {
|
||||
case 'exec-response': {
|
||||
console.debug('flipper-server: exec <<<', payload);
|
||||
const entry = pendingRequests.get(payload.id);
|
||||
if (!entry) {
|
||||
console.warn(`Unknown request id `, payload.id);
|
||||
} else {
|
||||
pendingRequests.delete(payload.id);
|
||||
clearTimeout(entry.timeout);
|
||||
entry.resolve(payload.data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'exec-response-error': {
|
||||
// TODO: Deserialize error
|
||||
console.debug(
|
||||
'flipper-server: exec <<< [SERVER ERROR]',
|
||||
payload.id,
|
||||
payload.data,
|
||||
);
|
||||
const entry = pendingRequests.get(payload.id);
|
||||
if (!entry) {
|
||||
console.warn(`flipper-server: Unknown request id `, payload.id);
|
||||
} else {
|
||||
pendingRequests.delete(payload.id);
|
||||
clearTimeout(entry.timeout);
|
||||
entry.reject(payload.data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'server-event': {
|
||||
eventEmitter.emit(payload.event, payload.data);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.warn(
|
||||
'flipper-server: received unknown message type',
|
||||
data.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const commandOrOptionsIsOptions = (
|
||||
commandOrOptions: FlipperServerExecOptions | string,
|
||||
): commandOrOptions is FlipperServerExecOptions =>
|
||||
typeof commandOrOptions === 'object';
|
||||
|
||||
const flipperServer: FlipperServer = {
|
||||
async connect() {},
|
||||
close() {},
|
||||
exec(commandOrOptions, ...argsAmbiguous): any {
|
||||
let timeout: number;
|
||||
let command: string;
|
||||
let args: Parameters<
|
||||
FlipperServerCommands[keyof FlipperServerCommands]
|
||||
>;
|
||||
if (commandOrOptionsIsOptions(commandOrOptions)) {
|
||||
timeout = commandOrOptions.timeout;
|
||||
command = argsAmbiguous[0] as string;
|
||||
args = argsAmbiguous.slice(1) as typeof args;
|
||||
} else {
|
||||
timeout = EXEC_TIMEOUT;
|
||||
command = commandOrOptions;
|
||||
args = argsAmbiguous as typeof args;
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
const id = ++requestId;
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
console.debug('flipper-server: exec >>>', id, command, args);
|
||||
|
||||
pendingRequests.set(id, {
|
||||
resolve,
|
||||
reject,
|
||||
timeout: setInterval(() => {
|
||||
pendingRequests.delete(id);
|
||||
reject(
|
||||
new Error(`flipper-server: timeout for command '${command}'`),
|
||||
);
|
||||
}, timeout),
|
||||
});
|
||||
|
||||
const execMessage = {
|
||||
event: 'exec',
|
||||
payload: {
|
||||
id,
|
||||
command,
|
||||
args,
|
||||
},
|
||||
} as ExecWebSocketMessage;
|
||||
socket.send(JSON.stringify(execMessage));
|
||||
});
|
||||
} else {
|
||||
throw new Error('Not connected to Flipper server');
|
||||
}
|
||||
},
|
||||
on(event, callback) {
|
||||
eventEmitter.on(event, callback);
|
||||
},
|
||||
off(event, callback) {
|
||||
eventEmitter.off(event, callback);
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -21,5 +21,4 @@ export * from './globalObject';
|
||||
export * from './plugins';
|
||||
export {getPluginKey} from './utils/pluginKey';
|
||||
export * from './flipperLibImplementation';
|
||||
export * from './client/FlipperServerClient';
|
||||
export {default as frontendCoreConstants} from './fb-stubs/constants';
|
||||
|
||||
Reference in New Issue
Block a user