Summary:
This diff solves the problem where the export for the graphql plugin was super super super sloooooowwww...... The reason being that the graphql plugin had chunky graphql responses which were json blob which was being serialized by our custom serializer. Instead of serializing those with custom serializer we can directly serialize them as they won't have any map's, sets, classes etc.
This diff adds the two static functions on the plugin which will provide the serialized and deserialized object for the persistedstate. As the plugin knows the structure of its state it can optimize the serialization and deserialization of its data.
This change solves the slow export issue and makes it blazing fast..... 🏎
Bug:
{F206550514}
Reviewed By: danielbuechele
Differential Revision: D17166054
fbshipit-source-id: 058b903c03c12c9194702162c46763ef5b5e7283
267 lines
7.0 KiB
TypeScript
267 lines
7.0 KiB
TypeScript
/**
|
|
* 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 {KeyboardActions} from './MenuBar';
|
|
import {App} from './App';
|
|
import {Logger} from './fb-interfaces/Logger';
|
|
import Client from './Client';
|
|
import {Store, MiddlewareAPI} from './reducers/index';
|
|
import {MetricType} from './utils/exportMetrics';
|
|
import {ReactNode, Component} from 'react';
|
|
import BaseDevice from './devices/BaseDevice';
|
|
import {serialize, deserialize} from './utils/serialization';
|
|
import {Idler} from './utils/Idler';
|
|
type Parameters = any;
|
|
|
|
// This function is intended to be called from outside of the plugin.
|
|
// If you want to `call` from the plugin use, this.client.call
|
|
export function callClient(
|
|
client: Client,
|
|
id: string,
|
|
): (method: string, params: Parameters) => Promise<any> {
|
|
return (method, params) => client.call(id, method, false, params);
|
|
}
|
|
|
|
export interface PluginClient {
|
|
// eslint-disable-next-line
|
|
send(method: string, params?: Parameters): void;
|
|
// eslint-disable-next-line
|
|
call(method: string, params?: Parameters): Promise<any>;
|
|
// eslint-disable-next-line
|
|
subscribe(method: string, callback: (params: Parameters) => void): void;
|
|
// eslint-disable-next-line
|
|
supportsMethod(method: string): Promise<boolean>;
|
|
}
|
|
|
|
type PluginTarget = BaseDevice | Client;
|
|
|
|
export type Notification = {
|
|
id: string;
|
|
title: string;
|
|
message: string | ReactNode;
|
|
severity: 'warning' | 'error';
|
|
timestamp?: number;
|
|
category?: string;
|
|
action?: string;
|
|
};
|
|
|
|
export type Props<T> = {
|
|
logger: Logger;
|
|
persistedState: T;
|
|
setPersistedState: (state: Partial<T>) => void;
|
|
target: PluginTarget;
|
|
deepLinkPayload: string | null;
|
|
selectPlugin: (pluginID: string, deepLinkPayload: string | null) => boolean;
|
|
isArchivedDevice: boolean;
|
|
selectedApp: string | null;
|
|
};
|
|
|
|
export type BaseAction = {
|
|
type: string;
|
|
};
|
|
|
|
type StaticPersistedState = any;
|
|
|
|
export abstract class FlipperBasePlugin<
|
|
State,
|
|
Actions extends BaseAction,
|
|
PersistedState
|
|
> extends Component<Props<PersistedState>, State> {
|
|
abstract ['constructor']: any;
|
|
static title: string | null = null;
|
|
static id: string = '';
|
|
static icon: string | null = null;
|
|
static gatekeeper: string | null = null;
|
|
static entry: string | null = null;
|
|
static bugs:
|
|
| ({
|
|
email?: string;
|
|
url?: string;
|
|
})
|
|
| null = null;
|
|
static keyboardActions: KeyboardActions | null;
|
|
static screenshot: string | null;
|
|
static defaultPersistedState: any;
|
|
static persistedStateReducer:
|
|
| ((
|
|
persistedState: StaticPersistedState,
|
|
method: string,
|
|
data: any,
|
|
) => StaticPersistedState)
|
|
| null;
|
|
static metricsReducer:
|
|
| ((persistedState: StaticPersistedState) => Promise<MetricType>)
|
|
| null;
|
|
static exportPersistedState:
|
|
| ((
|
|
callClient: (method: string, params?: any) => Promise<any>,
|
|
persistedState: StaticPersistedState | undefined,
|
|
store: MiddlewareAPI | undefined,
|
|
) => Promise<StaticPersistedState | undefined>)
|
|
| null;
|
|
static getActiveNotifications:
|
|
| ((persistedState: StaticPersistedState) => Array<Notification>)
|
|
| null;
|
|
static onRegisterDevice:
|
|
| ((
|
|
store: Store,
|
|
baseDevice: BaseDevice,
|
|
setPersistedState: (
|
|
pluginKey: string,
|
|
newPluginState: StaticPersistedState | null,
|
|
) => void,
|
|
) => void)
|
|
| null;
|
|
// forbid instance properties that should be static
|
|
title: never;
|
|
id: never;
|
|
persist: never;
|
|
icon: never;
|
|
keyboardActions: never;
|
|
screenshot: never;
|
|
|
|
reducers: {
|
|
[actionName: string]: (state: State, actionData: any) => Partial<State>;
|
|
} = {};
|
|
app: App;
|
|
onKeyboardAction: ((action: string) => void) | null;
|
|
|
|
toJSON() {
|
|
return `<${this.constructor.name}#${this.constructor.id}>`;
|
|
}
|
|
|
|
// methods to be overriden by plugins
|
|
init(): void {}
|
|
static serializePersistedState: (
|
|
persistedState: StaticPersistedState,
|
|
statusUpdate?: (msg: string) => void,
|
|
idler?: Idler,
|
|
) => Promise<string> = (
|
|
persistedState: StaticPersistedState,
|
|
statusUpdate?: (msg: string) => void,
|
|
idler?: Idler,
|
|
) => {
|
|
return serialize(persistedState, idler, statusUpdate);
|
|
};
|
|
static deserializePersistedState: (
|
|
serializedString: string,
|
|
) => StaticPersistedState = (serializedString: string) => {
|
|
return deserialize(serializedString);
|
|
};
|
|
teardown(): void {}
|
|
computeNotifications(
|
|
_props: Props<PersistedState>,
|
|
_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 extends BaseAction,
|
|
P
|
|
> extends FlipperBasePlugin<S, A, P> {
|
|
['constructor']: typeof FlipperPlugin;
|
|
device: BaseDevice;
|
|
|
|
constructor(props: Props<P>) {
|
|
super(props);
|
|
// @ts-ignore props.target will be instance of Device
|
|
this.device = props.target;
|
|
}
|
|
|
|
_init() {
|
|
this.init();
|
|
}
|
|
|
|
_teardown() {
|
|
this.teardown();
|
|
}
|
|
|
|
static supportsDevice(_device: BaseDevice) {
|
|
throw new Error(
|
|
'supportsDevice is unimplemented in FlipperDevicePlugin class',
|
|
);
|
|
}
|
|
}
|
|
|
|
export class FlipperPlugin<
|
|
S,
|
|
A extends BaseAction,
|
|
P
|
|
> extends FlipperBasePlugin<S, A, P> {
|
|
['constructor']: typeof FlipperPlugin;
|
|
constructor(props: Props<P>) {
|
|
super(props);
|
|
const {id} = this.constructor;
|
|
this.subscriptions = [];
|
|
// @ts-ignore props.target will be instance of Client
|
|
this.realClient = props.target;
|
|
this.client = {
|
|
call: (method, params) => this.realClient.call(id, method, true, params),
|
|
send: (method, params) => this.realClient.send(id, method, params),
|
|
subscribe: (method, callback) => {
|
|
this.subscriptions.push({
|
|
method,
|
|
callback,
|
|
});
|
|
this.realClient.subscribe(id, method, callback);
|
|
},
|
|
supportsMethod: method => this.realClient.supportsMethod(id, method),
|
|
};
|
|
}
|
|
|
|
subscriptions: Array<{
|
|
method: string;
|
|
callback: Function;
|
|
}>;
|
|
|
|
client: PluginClient;
|
|
realClient: Client;
|
|
|
|
getDevice(): Promise<BaseDevice> {
|
|
return this.realClient.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();
|
|
}
|
|
}
|