Summary: Plugins had their custom setup method which needed to be called externally. That is what consturctors are for. This removes the setup method and moves ths logic to the constructor. The setup method was called to late which caused the graphQL plugin to crash. With the logic now being in the constructor, it is ensured that it is called at the initialization. Reviewed By: jknoxville Differential Revision: D8769807 fbshipit-source-id: 7b4ab4815bbe397c80998adcb89ca361df6970d3
171 lines
4.1 KiB
JavaScript
171 lines
4.1 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 React from 'react';
|
|
import BaseDevice from './devices/BaseDevice.js';
|
|
import {AndroidDevice, IOSDevice} from 'sonar';
|
|
|
|
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 Props<T> = {
|
|
logger: Logger,
|
|
persistedState: T,
|
|
setPersistedState: (state: $Shape<T>) => void,
|
|
target: PluginTarget,
|
|
};
|
|
|
|
export class SonarBasePlugin<
|
|
State = *,
|
|
Actions = *,
|
|
PersistedState = *,
|
|
> extends React.Component<Props<PersistedState>, State> {
|
|
static title: string = 'Unknown';
|
|
static id: string = 'Unknown';
|
|
static icon: string = 'apps';
|
|
static keyboardActions: ?KeyboardActions;
|
|
static screenshot: ?string;
|
|
|
|
// 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.title}>`;
|
|
}
|
|
|
|
// methods to be overriden by plugins
|
|
init(): void {}
|
|
teardown(): void {}
|
|
// 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 SonarDevicePlugin<S = *, A = *, P = *> extends SonarBasePlugin<
|
|
S,
|
|
A,
|
|
P,
|
|
> {
|
|
device: BaseDevice;
|
|
|
|
constructor(props: Props<*>) {
|
|
super(props);
|
|
this.device = props.target;
|
|
}
|
|
|
|
_init() {
|
|
this.init();
|
|
}
|
|
}
|
|
|
|
export class SonarPlugin<S = *, A = *, P = *> extends SonarBasePlugin<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();
|
|
}
|
|
}
|