Introduce types for Sandy plugins through code base

Summary:
So far there were 2 types of plugins: `FlipperPlugin` and `FlipperDevicePlugin`. This introduces a third kind: `SandyPluginDefinition`.

Unlike with the old plugins, the export of the module is not directly exposed as the plugin definition. Rather, we use class `SandyPluginDefinition` (instance) that holds a loaded definition and its meta data separately (`PluginDetails`). This means that we don't have to mix in and mutate loaded definitions, and that for unit tests we can avoid needing to provide a bunch of meta data. This also prevents a bunch of meta data existing on two places: on the loaded classes as static fields, and in the meta data field of the loaded class as well. Finally, we can now freely extends the `PluginDetails` interface in flipper, without needing to store it on the loaded classes and we are sure that no naming conflicts are caused by this in the future.

For compatibility with the existing code base, common fields are delegated from the `SandyPluginDefinition` class to the meta data.

Also cleaned up types around plugins a little bit and removed some unnecessary casts.

For all features that reason about plugins in general (such as exports), sandy plugins are ignored for now.

`SandyPluginInstance` is worked out in further diffs

The `instanceof` calls are replaced by a utility function in later diffs.

{F241363645}

Reviewed By: jknoxville

Differential Revision: D22091432

fbshipit-source-id: 3aa6b12fda5925268913779f3c3c9e84494438f8
This commit is contained in:
Michel Weststrate
2020-07-01 08:58:40 -07:00
committed by Facebook GitHub Bot
parent 845c9b67f5
commit 1029a6c97c
23 changed files with 332 additions and 160 deletions

View File

@@ -15,6 +15,7 @@ const pattern = /^\*\r?\n[\S\s]*Facebook[\S\s]* \* @format\r?\n/;
const builtInModules = [ const builtInModules = [
'flipper', 'flipper',
'flipper-plugin', 'flipper-plugin',
'flipper-plugin-lib',
'react', 'react',
'react-dom', 'react-dom',
'electron', 'electron',

View File

@@ -7,7 +7,7 @@
* @format * @format
*/ */
import {FlipperPlugin, FlipperDevicePlugin} from './plugin'; import {PluginDefinition, ClientPluginDefinition} from './plugin';
import BaseDevice, {OS} from './devices/BaseDevice'; import BaseDevice, {OS} from './devices/BaseDevice';
import {App} from './App'; import {App} from './App';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
@@ -32,6 +32,7 @@ import {sideEffect} from './utils/sideEffect';
import {emitBytesReceived} from './dispatcher/tracking'; import {emitBytesReceived} from './dispatcher/tracking';
import {debounce} from 'lodash'; import {debounce} from 'lodash';
import {batch} from 'react-redux'; import {batch} from 'react-redux';
import {SandyPluginDefinition} from 'flipper-plugin';
type Plugins = Array<string>; type Plugins = Array<string>;
@@ -130,7 +131,7 @@ export default class Client extends EventEmitter {
messageBuffer: Record< messageBuffer: Record<
string /*pluginKey*/, string /*pluginKey*/,
{ {
plugin: typeof FlipperPlugin | typeof FlipperDevicePlugin; plugin: PluginDefinition;
messages: Params[]; messages: Params[];
} }
> = {}; > = {};
@@ -244,7 +245,7 @@ export default class Client extends EventEmitter {
}); });
} }
supportsPlugin(Plugin: typeof FlipperPlugin): boolean { supportsPlugin(Plugin: ClientPluginDefinition): boolean {
return this.plugins.includes(Plugin.id); return this.plugins.includes(Plugin.id);
} }
@@ -393,14 +394,18 @@ export default class Client extends EventEmitter {
const bytes = msg.length * 2; // string lengths are measured in UTF-16 units (not characters), so 2 bytes per char const bytes = msg.length * 2; // string lengths are measured in UTF-16 units (not characters), so 2 bytes per char
emitBytesReceived(params.api, bytes); emitBytesReceived(params.api, bytes);
const persistingPlugin: const persistingPlugin: PluginDefinition | undefined =
| typeof FlipperPlugin
| typeof FlipperDevicePlugin
| undefined =
this.store.getState().plugins.clientPlugins.get(params.api) || this.store.getState().plugins.clientPlugins.get(params.api) ||
this.store.getState().plugins.devicePlugins.get(params.api); this.store.getState().plugins.devicePlugins.get(params.api);
if (persistingPlugin && persistingPlugin.persistedStateReducer) { let handled = false; // This is just for analysis
// TODO: support Sandy plugins T68683442
if (
persistingPlugin &&
!(persistingPlugin instanceof SandyPluginDefinition) &&
persistingPlugin.persistedStateReducer
) {
handled = true;
const pluginKey = getPluginKey( const pluginKey = getPluginKey(
this.id, this.id,
{serial: this.query.device_id}, {serial: this.query.device_id},
@@ -417,16 +422,18 @@ export default class Client extends EventEmitter {
this.flushMessageBufferDebounced(); this.flushMessageBufferDebounced();
} }
const apiCallbacks = this.broadcastCallbacks.get(params.api); const apiCallbacks = this.broadcastCallbacks.get(params.api);
if (!apiCallbacks) { if (apiCallbacks) {
return; const methodCallbacks = apiCallbacks.get(params.method);
} if (methodCallbacks) {
for (const callback of methodCallbacks) {
const methodCallbacks = apiCallbacks.get(params.method); handled = true;
if (methodCallbacks) { callback(params.params);
for (const callback of methodCallbacks) { }
callback(params.params);
} }
} }
if (!handled) {
console.warn(`Unhandled message ${params.api}.${params.method}`);
}
} }
return; // method === 'execute' return; // method === 'execute'
} }

View File

@@ -7,7 +7,7 @@
* @format * @format
*/ */
import {FlipperPlugin, FlipperDevicePlugin} from './plugin'; import {FlipperPlugin, FlipperDevicePlugin, PluginDefinition} from './plugin';
import { import {
showOpenDialog, showOpenDialog,
startFileExport, startFileExport,
@@ -71,7 +71,7 @@ function actionHandler(action: string) {
} }
export function setupMenuBar( export function setupMenuBar(
plugins: Array<typeof FlipperPlugin | typeof FlipperDevicePlugin>, plugins: PluginDefinition[],
store: Store, store: Store,
logger: Logger, logger: Logger,
) { ) {

View File

@@ -7,7 +7,7 @@
* @format * @format
*/ */
import {SearchableProps, FlipperBasePlugin, FlipperPlugin} from 'flipper'; import {SearchableProps} from 'flipper';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
import { import {
Searchable, Searchable,
@@ -21,7 +21,7 @@ import {
styled, styled,
colors, colors,
} from 'flipper'; } from 'flipper';
import {FlipperDevicePlugin} from './plugin'; import {PluginDefinition, DevicePluginMap, ClientPluginMap} from './plugin';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import React, {Component, Fragment} from 'react'; import React, {Component, Fragment} from 'react';
import {clipboard} from 'electron'; import {clipboard} from 'electron';
@@ -47,8 +47,8 @@ type StateFromProps = {
invalidatedNotifications: Array<PluginNotification>; invalidatedNotifications: Array<PluginNotification>;
blacklistedPlugins: Array<string>; blacklistedPlugins: Array<string>;
blacklistedCategories: Array<string>; blacklistedCategories: Array<string>;
devicePlugins: Map<string, typeof FlipperDevicePlugin>; devicePlugins: DevicePluginMap;
clientPlugins: Map<string, typeof FlipperPlugin>; clientPlugins: ClientPluginMap;
}; };
type DispatchFromProps = { type DispatchFromProps = {
@@ -417,7 +417,7 @@ type ItemProps = {
deepLinkPayload: string | null; deepLinkPayload: string | null;
}) => any; }) => any;
logger?: Logger; logger?: Logger;
plugin: typeof FlipperBasePlugin | null | undefined; plugin: PluginDefinition | null | undefined;
}; };
type ItemState = { type ItemState = {

View File

@@ -11,6 +11,7 @@ import {
FlipperPlugin, FlipperPlugin,
FlipperDevicePlugin, FlipperDevicePlugin,
Props as PluginProps, Props as PluginProps,
PluginDefinition,
} from './plugin'; } from './plugin';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
import BaseDevice from './devices/BaseDevice'; import BaseDevice from './devices/BaseDevice';
@@ -45,6 +46,7 @@ import {Message} from './reducers/pluginMessageQueue';
import {Idler} from './utils/Idler'; import {Idler} from './utils/Idler';
import {processMessageQueue} from './utils/messageQueue'; import {processMessageQueue} from './utils/messageQueue';
import {ToggleButton, SmallText} from './ui'; import {ToggleButton, SmallText} from './ui';
import {SandyPluginDefinition} from 'flipper-plugin';
const Container = styled(FlexColumn)({ const Container = styled(FlexColumn)({
width: 0, width: 0,
@@ -95,7 +97,7 @@ type OwnProps = {
type StateFromProps = { type StateFromProps = {
pluginState: Object; pluginState: Object;
activePlugin: typeof FlipperPlugin | typeof FlipperDevicePlugin | null; activePlugin: PluginDefinition | undefined;
target: Client | BaseDevice | null; target: Client | BaseDevice | null;
pluginKey: string | null; pluginKey: string | null;
deepLinkPayload: string | null; deepLinkPayload: string | null;
@@ -190,6 +192,8 @@ class PluginContainer extends PureComponent<Props, State> {
if ( if (
pluginIsEnabled && pluginIsEnabled &&
activePlugin && activePlugin &&
// TODO: support sandy: T68683442
!(activePlugin instanceof SandyPluginDefinition) &&
activePlugin.persistedStateReducer && activePlugin.persistedStateReducer &&
pluginKey && pluginKey &&
pendingMessages?.length pendingMessages?.length
@@ -328,6 +332,10 @@ class PluginContainer extends PureComponent<Props, State> {
console.warn(`No selected plugin. Rendering empty!`); console.warn(`No selected plugin. Rendering empty!`);
return null; return null;
} }
if (activePlugin instanceof SandyPluginDefinition) {
// TODO:
return null;
}
const props: PluginProps<Object> & { const props: PluginProps<Object> & {
key: string; key: string;
ref: ( ref: (
@@ -404,14 +412,11 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
}) => { }) => {
let pluginKey = null; let pluginKey = null;
let target = null; let target = null;
let activePlugin: let activePlugin: PluginDefinition | undefined;
| typeof FlipperDevicePlugin
| typeof FlipperPlugin
| null = null;
let pluginIsEnabled = false; let pluginIsEnabled = false;
if (selectedPlugin) { if (selectedPlugin) {
activePlugin = devicePlugins.get(selectedPlugin) || null; activePlugin = devicePlugins.get(selectedPlugin);
target = selectedDevice; target = selectedDevice;
if (selectedDevice && activePlugin) { if (selectedDevice && activePlugin) {
pluginKey = getPluginKey(selectedDevice.serial, activePlugin.id); pluginKey = getPluginKey(selectedDevice.serial, activePlugin.id);
@@ -419,7 +424,7 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
} else { } else {
target = target =
clients.find((client: Client) => client.id === selectedApp) || null; clients.find((client: Client) => client.id === selectedApp) || null;
activePlugin = clientPlugins.get(selectedPlugin) || null; activePlugin = clientPlugins.get(selectedPlugin);
if (activePlugin && target) { if (activePlugin && target) {
pluginKey = getPluginKey(target.id, activePlugin.id); pluginKey = getPluginKey(target.id, activePlugin.id);
pluginIsEnabled = pluginIsStarred( pluginIsEnabled = pluginIsStarred(

View File

@@ -20,8 +20,6 @@ import {
Glyph, Glyph,
styled, styled,
GK, GK,
FlipperPlugin,
FlipperDevicePlugin,
ArchivedDevice, ArchivedDevice,
SmallText, SmallText,
Info, Info,
@@ -62,8 +60,9 @@ import {
getFavoritePlugins, getFavoritePlugins,
} from './sidebarUtils'; } from './sidebarUtils';
import {useLocalStorage} from '../../utils/useLocalStorage'; import {useLocalStorage} from '../../utils/useLocalStorage';
import {PluginDefinition, ClientPluginMap, DevicePluginMap} from '../../plugin';
type FlipperPlugins = typeof FlipperPlugin[]; type FlipperPlugins = PluginDefinition[];
type PluginsByCategory = [string, FlipperPlugins][]; type PluginsByCategory = [string, FlipperPlugins][];
type SectionLevel = 1 | 2 | 3; type SectionLevel = 1 | 2 | 3;
@@ -184,8 +183,8 @@ type StateFromProps = {
deviceId?: string; deviceId?: string;
errorMessage?: string; errorMessage?: string;
}>; }>;
devicePlugins: Map<string, typeof FlipperDevicePlugin>; devicePlugins: DevicePluginMap;
clientPlugins: Map<string, typeof FlipperPlugin>; clientPlugins: ClientPluginMap;
}; };
type SelectPlugin = (payload: { type SelectPlugin = (payload: {
@@ -469,7 +468,7 @@ const PluginList = memo(function PluginList({
}: { }: {
client: Client; client: Client;
device: BaseDevice; device: BaseDevice;
clientPlugins: Map<string, typeof FlipperPlugin>; clientPlugins: ClientPluginMap;
starPlugin: typeof starPluginAction; starPlugin: typeof starPluginAction;
userStarredPlugins: Store['connections']['userStarredPlugins']; userStarredPlugins: Store['connections']['userStarredPlugins'];
selectedPlugin?: null | string; selectedPlugin?: null | string;
@@ -497,7 +496,7 @@ const PluginList = memo(function PluginList({
); );
const allPlugins = Array.from(clientPlugins.values()).filter( const allPlugins = Array.from(clientPlugins.values()).filter(
(p: typeof FlipperPlugin) => client.plugins.indexOf(p.id) > -1, (p) => client.plugins.indexOf(p.id) > -1,
); );
const favoritePlugins: FlipperPlugins = getFavoritePlugins( const favoritePlugins: FlipperPlugins = getFavoritePlugins(
device, device,

View File

@@ -14,9 +14,7 @@ import {
Text, Text,
Glyph, Glyph,
styled, styled,
FlipperPlugin,
FlexColumn, FlexColumn,
FlipperBasePlugin,
ToggleButton, ToggleButton,
brandColors, brandColors,
Spacer, Spacer,
@@ -27,8 +25,9 @@ import {
} from 'flipper'; } from 'flipper';
import {BackgroundColorProperty} from 'csstype'; import {BackgroundColorProperty} from 'csstype';
import {getPluginTitle} from '../../utils/pluginUtils'; import {getPluginTitle} from '../../utils/pluginUtils';
import {PluginDefinition} from '../../plugin';
export type FlipperPlugins = typeof FlipperPlugin[]; export type FlipperPlugins = PluginDefinition[];
export type PluginsByCategory = [string, FlipperPlugins][]; export type PluginsByCategory = [string, FlipperPlugins][];
export const ListItem = styled.div<{active?: boolean; disabled?: boolean}>( export const ListItem = styled.div<{active?: boolean; disabled?: boolean}>(
@@ -133,7 +132,7 @@ export const Plugins = styled(FlexColumn)({
export const PluginSidebarListItem: React.FC<{ export const PluginSidebarListItem: React.FC<{
onClick: () => void; onClick: () => void;
isActive: boolean; isActive: boolean;
plugin: typeof FlipperBasePlugin; plugin: PluginDefinition;
app?: string | null | undefined; app?: string | null | undefined;
helpRef?: any; helpRef?: any;
provided?: any; provided?: any;

View File

@@ -12,18 +12,10 @@ import Client from '../../Client';
import {TableBodyRow} from '../../ui/components/table/types'; import {TableBodyRow} from '../../ui/components/table/types';
import React, {Component, Fragment} from 'react'; import React, {Component, Fragment} from 'react';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import { import {Text, ManagedTable, styled, colors, Link, Bordered} from 'flipper';
Text,
ManagedTable,
styled,
colors,
Link,
FlipperPlugin,
FlipperDevicePlugin,
Bordered,
} from 'flipper';
import StatusIndicator from '../../ui/components/StatusIndicator'; import StatusIndicator from '../../ui/components/StatusIndicator';
import {State as Store} from '../../reducers'; import {State as Store} from '../../reducers';
import {DevicePluginDefinition, ClientPluginDefinition} from '../../plugin';
const InfoText = styled(Text)({ const InfoText = styled(Text)({
lineHeight: '130%', lineHeight: '130%',
@@ -51,8 +43,8 @@ type StateFromProps = {
failedPlugins: Array<[PluginDetails, string]>; failedPlugins: Array<[PluginDetails, string]>;
clients: Array<Client>; clients: Array<Client>;
selectedDevice: string | null | undefined; selectedDevice: string | null | undefined;
devicePlugins: Array<typeof FlipperDevicePlugin>; devicePlugins: DevicePluginDefinition[];
clientPlugins: Array<typeof FlipperPlugin>; clientPlugins: ClientPluginDefinition[];
}; };
type DispatchFromProps = {}; type DispatchFromProps = {};
@@ -299,10 +291,8 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
}, },
connections: {clients, selectedDevice}, connections: {clients, selectedDevice},
}) => ({ }) => ({
devicePlugins: Array.from<typeof FlipperDevicePlugin>( devicePlugins: Array.from(devicePlugins.values()),
devicePlugins.values(), clientPlugins: Array.from(clientPlugins.values()),
),
clientPlugins: Array.from<typeof FlipperPlugin>(clientPlugins.values()),
gatekeepedPlugins, gatekeepedPlugins,
clients, clients,
disabledPlugins, disabledPlugins,

View File

@@ -162,6 +162,7 @@ test('requirePlugin loads plugin', () => {
version: '1.0.0', version: '1.0.0',
}); });
expect(plugin).not.toBeNull(); expect(plugin).not.toBeNull();
// @ts-ignore
expect(plugin!.prototype).toBeInstanceOf(FlipperPlugin); expect(plugin!.prototype).toBeInstanceOf(FlipperPlugin);
expect(plugin!.id).toBe(TestPlugin.id); expect(plugin!.id).toBe(TestPlugin.id);
}); });

View File

@@ -10,7 +10,7 @@
import {Store} from '../reducers/index'; import {Store} from '../reducers/index';
import {Logger} from '../fb-interfaces/Logger'; import {Logger} from '../fb-interfaces/Logger';
import {PluginNotification} from '../reducers/notifications'; import {PluginNotification} from '../reducers/notifications';
import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; import {PluginDefinition} from '../plugin';
import isHeadless from '../utils/isHeadless'; import isHeadless from '../utils/isHeadless';
import {setStaticView, setDeeplinkPayload} from '../reducers/connections'; import {setStaticView, setDeeplinkPayload} from '../reducers/connections';
import {ipcRenderer, IpcRendererEvent} from 'electron'; import {ipcRenderer, IpcRendererEvent} from 'electron';
@@ -25,6 +25,7 @@ import {deconstructPluginKey} from '../utils/clientUtils';
import NotificationScreen from '../chrome/NotificationScreen'; import NotificationScreen from '../chrome/NotificationScreen';
import {getPluginTitle} from '../utils/pluginUtils'; import {getPluginTitle} from '../utils/pluginUtils';
import {sideEffect} from '../utils/sideEffect'; import {sideEffect} from '../utils/sideEffect';
import {SandyPluginDefinition} from 'flipper-plugin';
type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action'; type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action';
const NOTIFICATION_THROTTLE = 5 * 1000; // in milliseconds const NOTIFICATION_THROTTLE = 5 * 1000; // in milliseconds
@@ -110,11 +111,15 @@ export default (store: Store, logger: Logger) => {
return; return;
} }
const persistingPlugin: const persistingPlugin: undefined | PluginDefinition = getPlugin(
| undefined pluginName,
| typeof FlipperPlugin );
| typeof FlipperDevicePlugin = getPlugin(pluginName); // TODO: add support for Sandy plugins T68683442
if (persistingPlugin && persistingPlugin.getActiveNotifications) { if (
persistingPlugin &&
!(persistingPlugin instanceof SandyPluginDefinition) &&
persistingPlugin.getActiveNotifications
) {
try { try {
const notifications = persistingPlugin.getActiveNotifications( const notifications = persistingPlugin.getActiveNotifications(
pluginStates[key], pluginStates[key],

View File

@@ -9,7 +9,7 @@
import {Store} from '../reducers/index'; import {Store} from '../reducers/index';
import {Logger} from '../fb-interfaces/Logger'; import {Logger} from '../fb-interfaces/Logger';
import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; import {PluginDefinition} from '../plugin';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import adbkit from 'adbkit'; import adbkit from 'adbkit';
@@ -58,9 +58,10 @@ export default (store: Store, logger: Logger) => {
const defaultPluginsIndex = getPluginIndex(); const defaultPluginsIndex = getPluginIndex();
const initialPlugins: Array< const initialPlugins: PluginDefinition[] = filterNewestVersionOfEachPlugin(
typeof FlipperPlugin | typeof FlipperDevicePlugin getBundledPlugins(),
> = filterNewestVersionOfEachPlugin(getBundledPlugins(), getDynamicPlugins()) getDynamicPlugins(),
)
.map(reportVersion) .map(reportVersion)
.filter(checkDisabled(disabledPlugins)) .filter(checkDisabled(disabledPlugins))
.filter(checkGK(gatekeepedPlugins)) .filter(checkGK(gatekeepedPlugins))
@@ -247,9 +248,7 @@ export const requirePlugin = (
defaultPluginsIndex: any, defaultPluginsIndex: any,
reqFn: Function = global.electronRequire, reqFn: Function = global.electronRequire,
) => { ) => {
return ( return (pluginDetails: PluginDetails): PluginDefinition | null => {
pluginDetails: PluginDetails,
): typeof FlipperPlugin | typeof FlipperDevicePlugin | null => {
try { try {
return tryCatchReportPluginFailures( return tryCatchReportPluginFailures(
() => requirePluginInternal(pluginDetails, defaultPluginsIndex, reqFn), () => requirePluginInternal(pluginDetails, defaultPluginsIndex, reqFn),
@@ -281,6 +280,7 @@ const requirePluginInternal = (
plugin.id = plugin.id || pluginDetails.id; plugin.id = plugin.id || pluginDetails.id;
plugin.packageName = pluginDetails.name; plugin.packageName = pluginDetails.name;
plugin.flipperSDKVersion = pluginDetails.flipperSDKVersion;
plugin.details = pluginDetails; plugin.details = pluginDetails;
// set values from package.json as static variables on class // set values from package.json as static variables on class

View File

@@ -22,8 +22,21 @@ import {State as ReduxState} from './reducers';
import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue'; import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue';
import {PluginDetails} from 'flipper-plugin-lib'; import {PluginDetails} from 'flipper-plugin-lib';
import {Settings} from './reducers/settings'; import {Settings} from './reducers/settings';
import {SandyPluginDefinition} from 'flipper-plugin';
type Parameters = {[key: string]: any}; type Parameters = {[key: string]: any};
export type PluginDefinition = ClientPluginDefinition | DevicePluginDefinition;
// TODO: T68738317 add SandyPluginDefinition
export type DevicePluginDefinition = typeof FlipperDevicePlugin;
export type ClientPluginDefinition =
| typeof FlipperPlugin
| SandyPluginDefinition;
export type ClientPluginMap = Map<string, ClientPluginDefinition>;
export type DevicePluginMap = Map<string, DevicePluginDefinition>;
// This function is intended to be called from outside of the plugin. // This function is intended to be called from outside of the plugin.
// If you want to `call` from the plugin use, this.client.call // If you want to `call` from the plugin use, this.client.call
export function callClient( export function callClient(
@@ -100,6 +113,7 @@ export abstract class FlipperBasePlugin<
static category: string | null = null; static category: string | null = null;
static id: string = ''; static id: string = '';
static packageName: string = ''; static packageName: string = '';
static flipperSDKVersion: string | undefined = undefined;
static version: string = ''; static version: string = '';
static icon: string | null = null; static icon: string | null = null;
static gatekeeper: string | null = null; static gatekeeper: string | null = null;
@@ -113,7 +127,7 @@ export abstract class FlipperBasePlugin<
static maxQueueSize: number = DEFAULT_MAX_QUEUE_SIZE; static maxQueueSize: number = DEFAULT_MAX_QUEUE_SIZE;
static metricsReducer: static metricsReducer:
| ((persistedState: StaticPersistedState) => Promise<MetricType>) | ((persistedState: StaticPersistedState) => Promise<MetricType>)
| null; | undefined;
static exportPersistedState: static exportPersistedState:
| (( | ((
callClient: (method: string, params?: any) => Promise<any>, callClient: (method: string, params?: any) => Promise<any>,
@@ -123,10 +137,10 @@ export abstract class FlipperBasePlugin<
statusUpdate?: (msg: string) => void, statusUpdate?: (msg: string) => void,
supportsMethod?: (method: string) => Promise<boolean>, supportsMethod?: (method: string) => Promise<boolean>,
) => Promise<StaticPersistedState | undefined>) ) => Promise<StaticPersistedState | undefined>)
| null; | undefined;
static getActiveNotifications: static getActiveNotifications:
| ((persistedState: StaticPersistedState) => Array<Notification>) | ((persistedState: StaticPersistedState) => Array<Notification>)
| null; | undefined;
static onRegisterDevice: static onRegisterDevice:
| (( | ((
store: Store, store: Store,

View File

@@ -14,7 +14,6 @@ import MacDevice from '../devices/MacDevice';
import Client from '../Client'; import Client from '../Client';
import {UninitializedClient} from '../UninitializedClient'; import {UninitializedClient} from '../UninitializedClient';
import {isEqual} from 'lodash'; import {isEqual} from 'lodash';
import iosUtil from '../utils/iOSContainerUtility';
import {performance} from 'perf_hooks'; import {performance} from 'perf_hooks';
import isHeadless from '../utils/isHeadless'; import isHeadless from '../utils/isHeadless';
import {Actions} from '.'; import {Actions} from '.';
@@ -31,6 +30,7 @@ import {
import {deconstructClientId} from '../utils/clientUtils'; import {deconstructClientId} from '../utils/clientUtils';
import {FlipperDevicePlugin} from '../plugin'; import {FlipperDevicePlugin} from '../plugin';
import {RegisterPluginAction} from './plugins'; import {RegisterPluginAction} from './plugins';
import {SandyPluginDefinition} from 'flipper-plugin';
export type StaticView = export type StaticView =
| null | null
@@ -423,7 +423,11 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
// plugins are registered after creating the base devices, so update them // plugins are registered after creating the base devices, so update them
const plugins = action.payload; const plugins = action.payload;
plugins.forEach((plugin) => { plugins.forEach((plugin) => {
if (plugin.prototype instanceof FlipperDevicePlugin) { // TODO: T68738317 support sandy device plugin
if (
!(plugin instanceof SandyPluginDefinition) &&
plugin.prototype instanceof FlipperDevicePlugin
) {
// smell: devices are mutable // smell: devices are mutable
state.devices.forEach((device) => { state.devices.forEach((device) => {
// @ts-ignore // @ts-ignore

View File

@@ -7,25 +7,24 @@
* @format * @format
*/ */
import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; import {DevicePluginMap, ClientPluginMap, PluginDefinition} from '../plugin';
import {PluginDetails} from 'flipper-plugin-lib'; import {PluginDetails} from 'flipper-plugin-lib';
import {Actions} from '.'; import {Actions} from '.';
import produce from 'immer'; import produce from 'immer';
import {isDevicePluginDefinition} from '../utils/pluginUtils';
export type State = { export type State = {
devicePlugins: Map<string, typeof FlipperDevicePlugin>; devicePlugins: DevicePluginMap;
clientPlugins: Map<string, typeof FlipperPlugin>; clientPlugins: ClientPluginMap;
gatekeepedPlugins: Array<PluginDetails>; gatekeepedPlugins: Array<PluginDetails>;
disabledPlugins: Array<PluginDetails>; disabledPlugins: Array<PluginDetails>;
failedPlugins: Array<[PluginDetails, string]>; failedPlugins: Array<[PluginDetails, string]>;
selectedPlugins: Array<string>; selectedPlugins: Array<string>;
}; };
type PluginClass = typeof FlipperPlugin | typeof FlipperDevicePlugin;
export type RegisterPluginAction = { export type RegisterPluginAction = {
type: 'REGISTER_PLUGINS'; type: 'REGISTER_PLUGINS';
payload: Array<PluginClass>; payload: PluginDefinition[];
}; };
export type Action = export type Action =
@@ -63,16 +62,14 @@ export default function reducer(
if (action.type === 'REGISTER_PLUGINS') { if (action.type === 'REGISTER_PLUGINS') {
return produce(state, (draft) => { return produce(state, (draft) => {
const {devicePlugins, clientPlugins} = draft; const {devicePlugins, clientPlugins} = draft;
action.payload.forEach((p: PluginClass) => { action.payload.forEach((p) => {
if (devicePlugins.has(p.id) || clientPlugins.has(p.id)) { if (devicePlugins.has(p.id) || clientPlugins.has(p.id)) {
return; return;
} }
if (p.prototype instanceof FlipperDevicePlugin) { if (isDevicePluginDefinition(p)) {
// @ts-ignore doesn't know p must be typeof FlipperDevicePlugin here
devicePlugins.set(p.id, p); devicePlugins.set(p.id, p);
} else if (p.prototype instanceof FlipperPlugin) { } else {
// @ts-ignore doesn't know p must be typeof FlipperPlugin here
clientPlugins.set(p.id, p); clientPlugins.set(p.id, p);
} }
}); });
@@ -107,7 +104,7 @@ export const selectedPlugins = (payload: Array<string>): Action => ({
payload, payload,
}); });
export const registerPlugins = (payload: Array<PluginClass>): Action => ({ export const registerPlugins = (payload: PluginDefinition[]): Action => ({
type: 'REGISTER_PLUGINS', type: 'REGISTER_PLUGINS',
payload, payload,
}); });

View File

@@ -20,10 +20,12 @@ import Client, {ClientExport, ClientQuery} from '../Client';
import {pluginKey} from '../reducers/pluginStates'; import {pluginKey} from '../reducers/pluginStates';
import { import {
FlipperDevicePlugin, FlipperDevicePlugin,
FlipperPlugin,
callClient, callClient,
supportsMethod, supportsMethod,
FlipperBasePlugin, FlipperBasePlugin,
PluginDefinition,
DevicePluginMap,
ClientPluginMap,
} from '../plugin'; } from '../plugin';
import {default as BaseDevice} from '../devices/BaseDevice'; import {default as BaseDevice} from '../devices/BaseDevice';
import {default as ArchivedDevice} from '../devices/ArchivedDevice'; import {default as ArchivedDevice} from '../devices/ArchivedDevice';
@@ -47,6 +49,7 @@ import {processMessageQueue} from './messageQueue';
import {getPluginTitle} from './pluginUtils'; import {getPluginTitle} from './pluginUtils';
import {capture} from './screenshot'; import {capture} from './screenshot';
import {uploadFlipperMedia} from '../fb-stubs/user'; import {uploadFlipperMedia} from '../fb-stubs/user';
import {SandyPluginDefinition} from 'flipper-plugin';
export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace'; export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace';
export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace'; export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace';
@@ -93,7 +96,7 @@ type PluginsToProcess = {
pluginKey: string; pluginKey: string;
pluginId: string; pluginId: string;
pluginName: string; pluginName: string;
pluginClass: typeof FlipperPlugin | typeof FlipperDevicePlugin; pluginClass: PluginDefinition;
client: Client; client: Client;
}[]; }[];
@@ -212,14 +215,17 @@ export function processNotificationStates(
const serializePluginStates = async ( const serializePluginStates = async (
pluginStates: PluginStatesState, pluginStates: PluginStatesState,
clientPlugins: Map<string, typeof FlipperPlugin>, clientPlugins: ClientPluginMap,
devicePlugins: Map<string, typeof FlipperDevicePlugin>, devicePlugins: DevicePluginMap,
statusUpdate?: (msg: string) => void, statusUpdate?: (msg: string) => void,
idler?: Idler, idler?: Idler,
): Promise<PluginStatesExportState> => { ): Promise<PluginStatesExportState> => {
const pluginsMap: Map<string, typeof FlipperBasePlugin> = new Map([]); const pluginsMap: Map<string, typeof FlipperBasePlugin> = new Map([]);
clientPlugins.forEach((val, key) => { clientPlugins.forEach((val, key) => {
pluginsMap.set(key, val); // TODO: Support Sandy T68683449 and use ClientPluginsMap
if (!(val instanceof SandyPluginDefinition)) {
pluginsMap.set(key, val);
}
}); });
devicePlugins.forEach((val, key) => { devicePlugins.forEach((val, key) => {
pluginsMap.set(key, val); pluginsMap.set(key, val);
@@ -248,12 +254,13 @@ const serializePluginStates = async (
const deserializePluginStates = ( const deserializePluginStates = (
pluginStatesExportState: PluginStatesExportState, pluginStatesExportState: PluginStatesExportState,
clientPlugins: Map<string, typeof FlipperPlugin>, clientPlugins: ClientPluginMap,
devicePlugins: Map<string, typeof FlipperDevicePlugin>, devicePlugins: DevicePluginMap,
): PluginStatesState => { ): PluginStatesState => {
const pluginsMap: Map<string, typeof FlipperBasePlugin> = new Map([]); const pluginsMap: Map<string, typeof FlipperBasePlugin> = new Map([]);
clientPlugins.forEach((val, key) => { clientPlugins.forEach((val, key) => {
pluginsMap.set(key, val); // TODO: Support Sandy T68683449
if (!(val instanceof SandyPluginDefinition)) pluginsMap.set(key, val);
}); });
devicePlugins.forEach((val, key) => { devicePlugins.forEach((val, key) => {
pluginsMap.set(key, val); pluginsMap.set(key, val);
@@ -358,8 +365,8 @@ type ProcessStoreOptions = {
device: BaseDevice | null; device: BaseDevice | null;
pluginStates: PluginStatesState; pluginStates: PluginStatesState;
clients: Array<ClientExport>; clients: Array<ClientExport>;
devicePlugins: Map<string, typeof FlipperDevicePlugin>; devicePlugins: DevicePluginMap;
clientPlugins: Map<string, typeof FlipperPlugin>; clientPlugins: ClientPluginMap;
salt: string; salt: string;
selectedPlugins: Array<string>; selectedPlugins: Array<string>;
statusUpdate?: (msg: string) => void; statusUpdate?: (msg: string) => void;
@@ -514,7 +521,11 @@ async function processQueues(
pluginKey, pluginKey,
pluginClass, pluginClass,
} of pluginsToProcess) { } of pluginsToProcess) {
if (pluginClass.persistedStateReducer) { // TODO: Support Sandy T68683449
if (
!(pluginClass instanceof SandyPluginDefinition) &&
pluginClass.persistedStateReducer
) {
const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`; const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`;
performance.mark(processQueueMarker); performance.mark(processQueueMarker);

View File

@@ -7,7 +7,6 @@
* @format * @format
*/ */
import {FlipperPlugin, FlipperDevicePlugin} from 'flipper';
import {serialize} from './serialization'; import {serialize} from './serialization';
import {State as PluginStatesState} from '../reducers/pluginStates'; import {State as PluginStatesState} from '../reducers/pluginStates';
import {Store} from '../reducers'; import {Store} from '../reducers';
@@ -20,6 +19,8 @@ import {
import {deserializeObject} from './serialization'; import {deserializeObject} from './serialization';
import {deconstructPluginKey} from './clientUtils'; import {deconstructPluginKey} from './clientUtils';
import {pluginsClassMap} from './pluginUtils'; import {pluginsClassMap} from './pluginUtils';
import {PluginDefinition} from '../plugin';
import {SandyPluginDefinition} from 'flipper-plugin';
export type MetricType = {[metricName: string]: number}; export type MetricType = {[metricName: string]: number};
type MetricPluginType = {[pluginID: string]: MetricType}; type MetricPluginType = {[pluginID: string]: MetricType};
@@ -27,7 +28,7 @@ export type ExportMetricType = {[clientID: string]: MetricPluginType};
async function exportMetrics( async function exportMetrics(
pluginStates: PluginStatesState, pluginStates: PluginStatesState,
pluginsMap: Map<string, typeof FlipperDevicePlugin | typeof FlipperPlugin>, pluginsMap: Map<string, PluginDefinition>,
selectedPlugins: Array<string>, selectedPlugins: Array<string>,
): Promise<string> { ): Promise<string> {
const metrics: ExportMetricType = {}; const metrics: ExportMetricType = {};
@@ -46,8 +47,10 @@ async function exportMetrics(
const pluginClass = pluginsMap.get(pluginName); const pluginClass = pluginsMap.get(pluginName);
const metricsReducer: const metricsReducer:
| (<U>(persistedState: U) => Promise<MetricType>) | (<U>(persistedState: U) => Promise<MetricType>)
| null | undefined =
| undefined = pluginClass && pluginClass.metricsReducer; pluginClass && !(pluginClass instanceof SandyPluginDefinition)
? pluginClass.metricsReducer
: undefined;
if (pluginsMap.has(pluginName) && metricsReducer) { if (pluginsMap.has(pluginName) && metricsReducer) {
const metricsObject = await metricsReducer(pluginStateData); const metricsObject = await metricsReducer(pluginStateData);
const pluginObject: MetricPluginType = {}; const pluginObject: MetricPluginType = {};
@@ -67,10 +70,7 @@ export async function exportMetricsWithoutTrace(
store: Store, store: Store,
pluginStates: PluginStatesState, pluginStates: PluginStatesState,
): Promise<string | null> { ): Promise<string | null> {
const pluginsMap: Map< const pluginsMap = pluginsClassMap(store.getState().plugins);
string,
typeof FlipperDevicePlugin | typeof FlipperPlugin
> = pluginsClassMap(store.getState().plugins);
const {clients, selectedDevice} = store.getState().connections; const {clients, selectedDevice} = store.getState().connections;
const pluginsToProcess = determinePluginsToProcess( const pluginsToProcess = determinePluginsToProcess(
clients, clients,
@@ -107,7 +107,7 @@ function parseJSON(str: string): any {
export async function exportMetricsFromTrace( export async function exportMetricsFromTrace(
trace: string, trace: string,
pluginsMap: Map<string, typeof FlipperDevicePlugin | typeof FlipperPlugin>, pluginsMap: Map<string, PluginDefinition>,
selectedPlugins: Array<string>, selectedPlugins: Array<string>,
): Promise<string> { ): Promise<string> {
const data = fs.readFileSync(trace, 'utf8'); const data = fs.readFileSync(trace, 'utf8');
@@ -136,5 +136,6 @@ export async function exportMetricsFromTrace(
), ),
); );
} }
// TODO: Support Sandy T68683449 and use ClientPluginsMap, or kill feature
return await exportMetrics(pluginStates, pluginsMap, selectedPlugins); return await exportMetrics(pluginStates, pluginsMap, selectedPlugins);
} }

View File

@@ -21,6 +21,7 @@ import {pluginIsStarred, getSelectedPluginKey} from '../reducers/connections';
import {deconstructPluginKey} from './clientUtils'; import {deconstructPluginKey} from './clientUtils';
import {onBytesReceived} from '../dispatcher/tracking'; import {onBytesReceived} from '../dispatcher/tracking';
import {defaultEnabledBackgroundPlugins} from './pluginUtils'; import {defaultEnabledBackgroundPlugins} from './pluginUtils';
import {SandyPluginDefinition} from 'flipper-plugin';
const MAX_BACKGROUND_TASK_TIME = 25; const MAX_BACKGROUND_TASK_TIME = 25;
@@ -189,14 +190,22 @@ export function processMessagesImmediately(
export function processMessagesLater( export function processMessagesLater(
store: MiddlewareAPI, store: MiddlewareAPI,
pluginKey: string, pluginKey: string,
plugin: { plugin:
defaultPersistedState: any; | {
id: string; defaultPersistedState: any;
persistedStateReducer: PersistedStateReducer | null; id: string;
maxQueueSize?: number; persistedStateReducer: PersistedStateReducer | null;
}, maxQueueSize?: number;
}
| SandyPluginDefinition,
messages: Message[], messages: Message[],
) { ) {
if (plugin instanceof SandyPluginDefinition) {
// TODO:
throw new Error(
'Receiving messages is not yet supported for Sandy plugins',
);
}
const isSelected = const isSelected =
pluginKey === getSelectedPluginKey(store.getState().connections); pluginKey === getSelectedPluginKey(store.getState().connections);
switch (true) { switch (true) {

View File

@@ -8,21 +8,21 @@
*/ */
import {Store} from '../reducers/index'; import {Store} from '../reducers/index';
import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; import {ClientPluginMap, DevicePluginMap, PluginDefinition} from '../plugin';
import {setPluginState} from '../reducers/pluginStates'; import {setPluginState} from '../reducers/pluginStates';
import BaseDevice from '../devices/BaseDevice'; import BaseDevice from '../devices/BaseDevice';
import {getPersistedState} from '../utils/pluginUtils'; import {getPersistedState} from '../utils/pluginUtils';
import {SandyPluginDefinition} from 'flipper-plugin';
export function registerDeviceCallbackOnPlugins( export function registerDeviceCallbackOnPlugins(
store: Store, store: Store,
devicePlugins: Map<string, typeof FlipperDevicePlugin>, devicePlugins: DevicePluginMap,
clientPlugins: Map<string, typeof FlipperPlugin>, clientPlugins: ClientPluginMap,
device: BaseDevice, device: BaseDevice,
) { ) {
const callRegisterDeviceHook = ( const callRegisterDeviceHook = (plugin: PluginDefinition) => {
plugin: typeof FlipperDevicePlugin | typeof FlipperPlugin, // This hook is not registered for Sandy plugins, let's see in the future if it is needed
) => { if (!(plugin instanceof SandyPluginDefinition) && plugin.onRegisterDevice) {
if (plugin.onRegisterDevice) {
plugin.onRegisterDevice( plugin.onRegisterDevice(
store, store,
device, device,

View File

@@ -12,6 +12,7 @@ import fs from 'fs';
import {Store, State} from '../reducers'; import {Store, State} from '../reducers';
import {getPluginKey} from './pluginUtils'; import {getPluginKey} from './pluginUtils';
import {serialize} from './serialization'; import {serialize} from './serialization';
import {SandyPluginDefinition} from 'flipper-plugin';
let pluginRecordingState: { let pluginRecordingState: {
recording: string; recording: string;
@@ -67,7 +68,10 @@ async function flipperStartPluginRecording(state: State) {
// Note that we don't use the plugin's own serializeState, as that might interact with the // Note that we don't use the plugin's own serializeState, as that might interact with the
// device state, and is used for creating Flipper Exports. // device state, and is used for creating Flipper Exports.
pluginRecordingState.startState = await serialize( pluginRecordingState.startState = await serialize(
state.pluginStates[pluginKey] || plugin.defaultPersistedState, state.pluginStates[pluginKey] ||
(plugin instanceof SandyPluginDefinition
? {}
: plugin.defaultPersistedState),
); );
console.log( console.log(

View File

@@ -7,12 +7,18 @@
* @format * @format
*/ */
import {FlipperDevicePlugin, FlipperPlugin, FlipperBasePlugin} from '../plugin'; import {
FlipperDevicePlugin,
FlipperBasePlugin,
PluginDefinition,
DevicePluginDefinition,
} from '../plugin';
import {State as PluginStatesState} from '../reducers/pluginStates'; import {State as PluginStatesState} from '../reducers/pluginStates';
import {State as PluginsState} from '../reducers/plugins'; import {State as PluginsState} from '../reducers/plugins';
import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue'; import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue';
import {PluginDetails} from 'flipper-plugin-lib'; import {PluginDetails} from 'flipper-plugin-lib';
import {deconstructPluginKey, deconstructClientId} from './clientUtils'; import {deconstructPluginKey, deconstructClientId} from './clientUtils';
import {SandyPluginDefinition} from 'flipper-plugin';
type Client = import('../Client').default; type Client = import('../Client').default;
@@ -20,11 +26,8 @@ export const defaultEnabledBackgroundPlugins = ['Navigation']; // The navigation
export function pluginsClassMap( export function pluginsClassMap(
plugins: PluginsState, plugins: PluginsState,
): Map<string, typeof FlipperDevicePlugin | typeof FlipperPlugin> { ): Map<string, PluginDefinition> {
const pluginsMap: Map< const pluginsMap: Map<string, PluginDefinition> = new Map([]);
string,
typeof FlipperDevicePlugin | typeof FlipperPlugin
> = new Map([]);
plugins.clientPlugins.forEach((val, key) => { plugins.clientPlugins.forEach((val, key) => {
pluginsMap.set(key, val); pluginsMap.set(key, val);
}); });
@@ -83,10 +86,7 @@ export function getEnabledOrExportPersistedStatePlugins(
plugins: PluginsState, plugins: PluginsState,
): Array<{id: string; label: string}> { ): Array<{id: string; label: string}> {
const appName = deconstructClientId(client.id).app; const appName = deconstructClientId(client.id).app;
const pluginsMap: Map< const pluginsMap: Map<string, PluginDefinition> = pluginsClassMap(plugins);
string,
typeof FlipperDevicePlugin | typeof FlipperPlugin
> = pluginsClassMap(plugins);
// Enabled Plugins with no exportPersistedState function defined // Enabled Plugins with no exportPersistedState function defined
const enabledPlugins = starredPlugin[appName] const enabledPlugins = starredPlugin[appName]
? starredPlugin[appName] ? starredPlugin[appName]
@@ -141,10 +141,7 @@ export function getActivePersistentPlugins(
plugins: PluginsState, plugins: PluginsState,
selectedClient?: Client, selectedClient?: Client,
): {id: string; label: string}[] { ): {id: string; label: string}[] {
const pluginsMap: Map< const pluginsMap: Map<string, PluginDefinition> = pluginsClassMap(plugins);
string,
typeof FlipperDevicePlugin | typeof FlipperPlugin
> = pluginsClassMap(plugins);
return getPersistentPlugins(plugins) return getPersistentPlugins(plugins)
.map((pluginName) => pluginsMap.get(pluginName)!) .map((pluginName) => pluginsMap.get(pluginName)!)
.sort(sortPluginsByName) .sort(sortPluginsByName)
@@ -183,10 +180,7 @@ export function getActivePersistentPlugins(
} }
export function getPersistentPlugins(plugins: PluginsState): Array<string> { export function getPersistentPlugins(plugins: PluginsState): Array<string> {
const pluginsMap: Map< const pluginsMap: Map<string, PluginDefinition> = pluginsClassMap(plugins);
string,
typeof FlipperDevicePlugin | typeof FlipperPlugin
> = pluginsClassMap(plugins);
const arr: Array<PluginDetails> = plugins.disabledPlugins.concat( const arr: Array<PluginDetails> = plugins.disabledPlugins.concat(
plugins.gatekeepedPlugins, plugins.gatekeepedPlugins,
@@ -210,32 +204,36 @@ export function getPersistentPlugins(plugins: PluginsState): Array<string> {
return ( return (
plugin == 'DeviceLogs' || plugin == 'DeviceLogs' ||
(pluginClass && (pluginClass &&
// TODO: support Sandy plugin T68683449
!(pluginClass instanceof SandyPluginDefinition) &&
(pluginClass.defaultPersistedState != undefined || (pluginClass.defaultPersistedState != undefined ||
pluginClass.exportPersistedState != undefined)) pluginClass.exportPersistedState != undefined))
); );
}); });
} }
export function getPluginTitle(pluginClass: typeof FlipperBasePlugin) { export function getPluginTitle(pluginClass: PluginDefinition) {
return pluginClass.title || pluginClass.id; return pluginClass.title || pluginClass.id;
} }
export function sortPluginsByName( export function sortPluginsByName(
a: typeof FlipperBasePlugin, a: PluginDefinition,
b: typeof FlipperBasePlugin, b: PluginDefinition,
): number { ): number {
// make sure Device plugins are sorted before normal plugins // make sure Device plugins are sorted before normal plugins
if ( if (isDevicePluginDefinition(a) && !isDevicePluginDefinition(b)) {
a.prototype instanceof FlipperDevicePlugin &&
!(b.prototype instanceof FlipperDevicePlugin)
) {
return -1; return -1;
} }
if ( if (isDevicePluginDefinition(b) && !isDevicePluginDefinition(a)) {
b.prototype instanceof FlipperDevicePlugin &&
!(a.prototype instanceof FlipperDevicePlugin)
) {
return 1; return 1;
} }
return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1; return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1;
} }
export function isDevicePluginDefinition(
definition: PluginDefinition,
): definition is DevicePluginDefinition {
// TODO: support Sandy device plugins T68738317
// @ts-ignore
return definition.prototype instanceof FlipperDevicePlugin;
}

View File

@@ -7,6 +7,4 @@
* @format * @format
*/ */
export function hello() { export * from './plugin/Plugin';
return 'universe ';
}

View File

@@ -0,0 +1,133 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {PluginDetails} from 'flipper-plugin-lib';
type EventsContract = Record<string, any>;
type MethodsContract = Record<string, (params: any) => Promise<any>>;
/**
* API available to a plugin factory
*/
export interface FlipperClient<
Events extends EventsContract,
Methods extends MethodsContract
> {}
/**
* Internal API exposed by Flipper, and wrapped by FlipperPluginInstance to be passed to the
* Plugin Factory
*/
interface RealFlipperClient {}
export type FlipperPluginFactory<
Events extends EventsContract,
Methods extends MethodsContract
> = (client: FlipperClient<Events, Methods>) => object;
export type FlipperPluginComponent = React.FC<{}>;
export type FlipperPluginModule = {
/** the factory function that initializes a plugin instance */
plugin: FlipperPluginFactory<any, any>;
/** the component type that can render this plugin */
Component: FlipperPluginComponent;
// TODO: support device plugins T68738317
// devicePlugin: FlipperPluginFactory
};
export class FlipperPluginInstance {
/** base client provided by Flipper */
realClient: RealFlipperClient;
/** client that is bound to this instance */
client: FlipperClient<any, any>;
/** the original plugin definition */
definition: FlipperPluginModule;
/** the plugin instance api as used inside components and such */
instanceApi: object;
constructor(realClient: RealFlipperClient, definition: FlipperPluginModule) {
this.realClient = realClient;
this.definition = definition;
this.client = {};
this.instanceApi = definition.plugin(this.client);
}
deactivate() {
// TODO:
}
}
export class SandyPluginDefinition {
id: string;
module: FlipperPluginModule;
details: PluginDetails;
// TODO: Implement T68683449
exportPersistedState:
| ((
callClient: (method: string, params?: any) => Promise<any>,
persistedState: any, // TODO: type StaticPersistedState | undefined,
store: any, // TODO: ReduxState | undefined,
idler?: any, // TODO: Idler,
statusUpdate?: (msg: string) => void,
supportsMethod?: (method: string) => Promise<boolean>,
) => Promise<any /* TODO: StaticPersistedState | undefined */>)
| undefined = undefined;
constructor(details: PluginDetails, module: FlipperPluginModule) {
this.id = details.id;
this.details = details;
if (!module.plugin || typeof module.plugin !== 'function') {
throw new Error(
`Sandy plugin ${this.id} doesn't export a named function called 'plugin'`,
);
}
if (!module.Component || typeof module.Component !== 'function') {
throw new Error(
`Sandy plugin ${this.id} doesn't export a named function called 'Component'`,
);
}
this.module = module;
this.module.Component.displayName = `FlipperPlugin(${this.id})`;
}
get packageName() {
return this.details.name;
}
get title() {
return this.details.title;
}
get icon() {
return this.details.icon;
}
get category() {
return this.details.category;
}
get gatekeeper() {
return this.details.gatekeeper;
}
get version() {
return this.details.version;
}
get isDefault() {
return this.details.isDefault;
}
get keyboardActions() {
// TODO: T68882551 support keyboard actions
return [];
}
}

View File

@@ -4,11 +4,7 @@
"outDir": "lib", "outDir": "lib",
"rootDir": "src" "rootDir": "src"
}, },
"include": [ "references": [{"path": "../plugin-lib"}],
"src" "include": ["src"],
], "exclude": ["node_modules", "**/__tests__/*"]
"exclude": [
"node_modules",
"**/__tests__/*"
]
} }