Files
flipper/src/plugin.js
John Knox e33e2d4d06 Unify Logger interface usage
Summary:
Unified all imports of Logger. Some were called LogManager before.
Now the fb-stub and the fb variants use the same interface.
Constructor of Logger is no longer exposed so it can't be initialized twice, unless in the case you're explicitly using the fb variant, which has extra functionality.

Reviewed By: danielbuechele

Differential Revision: D14083929

fbshipit-source-id: 9b61a06e1264d5f142b5a9188465c99deaf18193
2019-02-14 09:23:41 -08:00

221 lines
5.4 KiB
JavaScript

/**
* 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 type {KeyboardActions} from './MenuBar.js';
import type {App} from './App.js';
import type {Logger} from './fb-interfaces/Logger.js';
import type Client from './Client.js';
import type {Store} from './reducers/index.js';
import React from 'react';
import type {Node} from 'react';
import BaseDevice from './devices/BaseDevice.js';
import AndroidDevice from './devices/AndroidDevice';
import IOSDevice from './devices/IOSDevice';
const invariant = require('invariant');
export type PluginClient = {|
send: (method: string, params?: Object) => void,
call: (method: string, params?: Object) => Promise<any>,
subscribe: (method: string, callback: (params: any) => void) => void,
|};
type PluginTarget = BaseDevice | Client;
export type Notification = {|
id: string,
title: string,
message: string | Node,
severity: 'warning' | 'error',
timestamp?: number,
category?: string,
action?: string,
|};
export type Props<T> = {
logger: Logger,
persistedState: T,
setPersistedState: (state: $Shape<T>) => void,
target: PluginTarget,
deepLinkPayload: ?string,
selectPlugin: (pluginID: string, deepLinkPayload: ?string) => boolean,
};
export class FlipperBasePlugin<
State = *,
Actions = *,
PersistedState = *,
> extends React.Component<Props<PersistedState>, State> {
static title: ?string = null;
static id: string = '';
static icon: ?string = null;
static gatekeeper: ?string = null;
static entry: ?string = null;
static bugs: ?{
email?: string,
url?: string,
} = null;
static keyboardActions: ?KeyboardActions;
static screenshot: ?string;
static defaultPersistedState: PersistedState;
static persistedStateReducer: ?(
persistedState: PersistedState,
method: string,
data: Object,
) => $Shape<PersistedState>;
static getActiveNotifications: ?(
persistedState: PersistedState,
) => Array<Notification>;
static onRegisterDevice: ?(
store: Store,
baseDevice: BaseDevice,
setPersistedState: (
pluginKey: string,
newPluginState: ?PersistedState,
) => void,
) => void;
// forbid instance properties that should be static
title: empty;
id: empty;
persist: empty;
icon: empty;
keyboardActions: empty;
screenshot: empty;
reducers: {
[actionName: string]: (state: State, actionData: Object) => $Shape<State>,
} = {};
app: App;
onKeyboardAction: ?(action: string) => void;
toJSON() {
return `<${this.constructor.name}#${this.constructor.id}>`;
}
// methods to be overriden by plugins
init(): void {}
teardown(): void {}
computeNotifications(props: Props<*>, state: State): Array<Notification> {
return [];
}
// methods to be overridden by subclasses
_init(): void {}
_teardown(): void {}
dispatchAction(actionData: Actions) {
// $FlowFixMe
const action = this.reducers[actionData.type];
if (!action) {
// $FlowFixMe
throw new ReferenceError(`Unknown action ${actionData.type}`);
}
if (typeof action === 'function') {
this.setState(action.call(this, this.state, actionData));
} else {
// $FlowFixMe
throw new TypeError(`Reducer ${actionData.type} isn't a function`);
}
}
}
export class FlipperDevicePlugin<S = *, A = *, P = *> extends FlipperBasePlugin<
S,
A,
P,
> {
device: BaseDevice;
constructor(props: Props<*>) {
super(props);
this.device = props.target;
}
_init() {
this.init();
}
static supportsDevice(device: BaseDevice) {
throw new Error(
'supportsDevice is unimplemented in FlipperDevicePlugin class',
);
}
}
export class FlipperPlugin<S = *, A = *, P = *> extends FlipperBasePlugin<
S,
A,
P,
> {
constructor(props: Props<*>) {
super(props);
const {id} = this.constructor;
this.subscriptions = [];
// $FlowFixMe props.target will be instance of Client
this.realClient = props.target;
this.client = {
call: (method, params) => this.realClient.call(id, method, params),
send: (method, params) => this.realClient.send(id, method, params),
subscribe: (method, callback) => {
this.subscriptions.push({
method,
callback,
});
this.realClient.subscribe(id, method, callback);
},
};
}
subscriptions: Array<{
method: string,
callback: Function,
}>;
client: PluginClient;
realClient: Client;
getDevice(): ?BaseDevice {
return this.realClient.getDevice();
}
getAndroidDevice(): AndroidDevice {
const device = this.getDevice();
invariant(
device != null && device instanceof AndroidDevice,
'expected android device',
);
return device;
}
getIOSDevice() {
const device = this.getDevice();
invariant(
device != null && device instanceof IOSDevice,
'expected ios device',
);
return device;
}
_teardown() {
// automatically unsubscribe subscriptions
for (const {method, callback} of this.subscriptions) {
this.realClient.unsubscribe(this.constructor.id, method, callback);
}
// run plugin teardown
this.teardown();
if (this.realClient.connected) {
this.realClient.deinitPlugin(this.constructor.id);
}
}
_init() {
this.realClient.initPlugin(this.constructor.id);
this.init();
}
}