Files
flipper/src/plugin.js
Pritesh Nandgaonkar 9d4bb45dbc Refactor CrashReporter plugin
Summary: This diff refactors CrashReporter Plugin. The business logic of crash reporter plugin was placed in the `iOSDevice.js` as it gets the hook to the initialisation of iOSDevice. This diff moves the business logic to the Crash Reporter plugin files under the plugins folder. To get the hook, so that we can add our watcher, I have added a static function over `FlipperBasePlugin`, which if exists, will be called once `REGISTER_PLUGIN` event is dispatched

Reviewed By: danielbuechele

Differential Revision: D13529035

fbshipit-source-id: 28216b273b4032727859107d8a6751c6465af9ac
2018-12-21 07:03:21 -08:00

219 lines
5.3 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-stubs/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, IOSDevice} from 'flipper';
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 onRegisterPlugin: ?(
store: Store,
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.rawSend('deinit', {plugin: this.constructor.id});
}
}
_init() {
this.realClient.rawSend('init', {plugin: this.constructor.id});
this.init();
}
}