api + plugins examples

Summary:
Initial commit for Flipper API for JS apps:
1) Plugin, Connection, Client
2) a bridge to work with WebView proxy app
3) examples of ported plugins: AnalyticsLogging and Fury

Reviewed By: danielbuechele

Differential Revision: D16753405

fbshipit-source-id: cdd4b863db236b2043f09833aed130035f6738de
This commit is contained in:
Timur Valiev
2019-08-14 03:26:04 -07:00
committed by Facebook Github Bot
parent 83f00f2485
commit 46e0abecdf
7 changed files with 361 additions and 0 deletions

113
src/utils/js-client/api.js Normal file
View File

@@ -0,0 +1,113 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
export type FlipperPluginID = string;
export type FlipperMethodID = string;
export class FlipperBridge {
registerPlugins: (plugins: Array<FlipperPluginID>) => void;
start: () => void;
stop: () => void;
sendData: (
plugin: FlipperPluginID,
method: FlipperMethodID,
data: any,
) => void;
subscribe: (
plugin: FlipperPluginID,
method: FlipperMethodID,
handler: (any) => void,
) => void;
isAvailable: () => boolean;
}
export class FlipperResponder {
pluginId: FlipperPluginID;
methodId: FlipperMethodID;
_bridge: FlipperBridge;
constructor(
pluginId: FlipperPluginID,
methodId: FlipperMethodID,
bridge: FlipperBridge,
) {
this.pluginId = pluginId;
this.methodId = methodId;
this._bridge = bridge;
}
success(response: any) {}
error(response: any) {}
}
export type FlipperReceiver<T> = (
params: T,
responder: FlipperResponder,
) => void;
export class FlipperConnection {
pluginId: FlipperPluginID;
_bridge: FlipperBridge;
constructor(pluginId: FlipperPluginID, bridge: FlipperBridge) {
this.pluginId = pluginId;
this._bridge = bridge;
}
send(method: FlipperMethodID, data: any) {
this._bridge.sendData(this.pluginId, method, data);
}
receive(method: FlipperMethodID, receiver: FlipperReceiver<*>) {
this._bridge.subscribe(this.pluginId, method, data => {
receiver(data, new FlipperResponder(this.pluginId, method, this._bridge));
});
}
}
export class FlipperPlugin {
id: FlipperPluginID;
_connection: ?FlipperConnection;
onConnect(connection: FlipperConnection) {
this._connection = connection;
}
}
export class FlipperClient {
_bridge: FlipperBridge;
plugins: Map<FlipperPluginID, FlipperPlugin> = new Map();
constructor(bridge: FlipperBridge) {
this._bridge = bridge;
}
addPlugin(plugin: FlipperPlugin) {
plugin.onConnect(new FlipperConnection(plugin.id, this._bridge));
this.plugins.set(plugin.id, plugin);
}
getPlugin(id: FlipperPluginID): ?FlipperPlugin {
return this.plugins.get(id);
}
start() {
this._bridge.registerPlugins([...this.plugins.keys()]);
this._bridge.start();
}
stop() {
this._bridge.stop();
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import {FlipperClient} from './api';
import {newWebviewClient} from './webviewImpl';
import {AnalyticsLoggingFlipperPlugin} from './plugins/analyticsLogging';
import {FuryPlugin} from './plugins/fury';
class FlipperManager {
flipperClient: FlipperClient;
analyticsPlugin: AnalyticsLoggingFlipperPlugin;
furyPlugin: FuryPlugin;
constructor() {
this.flipperClient = newWebviewClient();
this.analyticsPlugin = new AnalyticsLoggingFlipperPlugin();
this.furyPlugin = new FuryPlugin();
this.flipperClient.addPlugin(this.analyticsPlugin);
this.flipperClient.addPlugin(this.furyPlugin);
this.flipperClient.start();
}
}
let flipperManager: ?FlipperManager;
export function init() {
if (!flipperManager) {
flipperManager = new FlipperManager();
}
}
export function flipper(): ?FlipperManager {
return flipperManager;
}

View File

@@ -0,0 +1,11 @@
{
"name": "flipper-js-client",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"title": "Flipper JS Client",
"icon": "apps",
"bugs": {
"email": "timurvaliev@fb.com"
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import {FlipperPlugin} from '../api';
import type {FlipperPluginID} from '../api';
type EventId = string;
export type AnalyticsEvent = {
id: EventId,
module: string,
name: string,
time: number,
filter: ?string,
highpri: ?boolean,
extras: any,
};
export class AnalyticsLoggingFlipperPlugin extends FlipperPlugin {
id: FlipperPluginID = 'AnalyticsLogging';
sendEvent(event: AnalyticsEvent) {
this._connection && this._connection.send('reportEvent', event);
}
}

View File

@@ -0,0 +1,104 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
* @flow
*/
import {FlipperPlugin, FlipperResponder, FlipperConnection} from '../api';
import type {FlipperPluginID} from '../api';
type EventId = string;
type EventType = number;
type ThreadID = number;
type SeqID = number;
type StackTraceElement = {
className: string,
methodName: string,
fileName: string,
lineNumber: number,
};
type FuryTaskEvent = {
id: EventId,
time: number,
eventType: EventType,
callStack: StackTraceElement[],
extras: any,
tag: string,
parentTid: ThreadID,
currentTid: ThreadID,
parentSeqId: SeqID,
currentSeqId: SeqID,
isDirect: boolean,
isPoint: boolean,
isOnActivated: boolean,
};
export type ReqContext = {
tag: string,
parentSeqId: SeqID,
currentSeqId: SeqID,
isDirect: boolean,
isPoint: boolean,
};
const structuredTraceFun = (error, structuredStackTrace) => {
return structuredStackTrace;
};
function getStackTrace(): CallSite[] {
const oldPrep = Error.prepareStackTrace;
Error.prepareStackTrace = structuredTraceFun;
const error = {};
Error.captureStackTrace(error, getStackTrace);
const stack = error.stack;
Error.prepareStackTrace = oldPrep;
return stack;
}
export class FuryPlugin extends FlipperPlugin {
id: FlipperPluginID = 'Fury';
onConnect(connection: FlipperConnection) {
super.onConnect(connection);
connection.receive(
'toggleRecording',
(data: any, responder: FlipperResponder) => {
window.console.log(data);
},
);
}
processEvent(reqContext: ReqContext, isOnActivated: boolean) {
const stack = getStackTrace();
const eventType = reqContext.isPoint ? 2 : isOnActivated ? 0 : 1;
const event: FuryTaskEvent = {
id: reqContext.currentSeqId + '/' + eventType,
time: new Date().getTime(),
eventType: eventType,
callStack: stack.map(frame => {
return {
className: frame.getTypeName() || '',
methodName: frame.getFunctionName() || '',
fileName: frame.getFileName() || '',
lineNumber: frame.getLineNumber() || 0,
};
}),
extras: {},
tag: reqContext.tag,
parentTid: -1,
currentTid: -1,
parentSeqId: reqContext.parentSeqId,
currentSeqId: reqContext.currentSeqId,
isDirect: reqContext.isDirect,
isPoint: reqContext.isPoint,
isOnActivated: isOnActivated,
};
this._connection && this._connection.send('reportEvent', event);
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import {FlipperClient, FlipperBridge} from './api';
import type {FlipperPluginID, FlipperMethodID} from './api';
class FlipperWebviewBridgeImpl extends FlipperBridge {
_subscriptions: Map<string, (any) => void> = new Map();
registerPlugins = (plugins: Array<FlipperPluginID>) => {
window.FlipperWebviewBridge &&
window.FlipperWebviewBridge.registerPlugins(plugins);
};
start = () => {
window.FlipperWebviewBridge && window.FlipperWebviewBridge.start();
};
stop = () => {
window.FlipperWebviewBridge && window.FlipperWebviewBridge.stop();
};
sendData = (plugin: FlipperPluginID, method: FlipperMethodID, data: any) => {
window.FlipperWebviewBridge &&
window.FlipperWebviewBridge.sendFlipperObject(
plugin,
method,
JSON.stringify(data),
);
};
subscribe = (
plugin: FlipperPluginID,
method: FlipperMethodID,
handler: any => void,
) => {
this._subscriptions.set(plugin + method, handler);
window.FlipperWebviewBridge &&
window.FlipperWebviewBridge.subscribe(plugin, method);
};
isAvailable = () => {
return window.FlipperWebviewBridge != null;
};
handleMessage(plugin: FlipperPluginID, method: FlipperMethodID, data: any) {
const handler: ?(any) => void = this._subscriptions.get(plugin + method);
handler && handler(data);
}
}
export function newWebviewClient(): FlipperClient {
const bridge = new FlipperWebviewBridgeImpl();
window.FlipperBridgeClientSide = bridge;
return new FlipperClient(bridge);
}

View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1