diff --git a/desktop/.eslintrc.js b/desktop/.eslintrc.js index d38cd3a66..5d7ed4d84 100644 --- a/desktop/.eslintrc.js +++ b/desktop/.eslintrc.js @@ -15,6 +15,7 @@ const pattern = /^\*\r?\n[\S\s]*Facebook[\S\s]* \* @format\r?\n/; const builtInModules = [ 'flipper', 'flipper-plugin', + 'flipper-plugin-lib', 'react', 'react-dom', 'electron', diff --git a/desktop/app/src/Client.tsx b/desktop/app/src/Client.tsx index 81d60dc06..122efd522 100644 --- a/desktop/app/src/Client.tsx +++ b/desktop/app/src/Client.tsx @@ -7,7 +7,7 @@ * @format */ -import {FlipperPlugin, FlipperDevicePlugin} from './plugin'; +import {PluginDefinition, ClientPluginDefinition} from './plugin'; import BaseDevice, {OS} from './devices/BaseDevice'; import {App} from './App'; import {Logger} from './fb-interfaces/Logger'; @@ -32,6 +32,7 @@ import {sideEffect} from './utils/sideEffect'; import {emitBytesReceived} from './dispatcher/tracking'; import {debounce} from 'lodash'; import {batch} from 'react-redux'; +import {SandyPluginDefinition} from 'flipper-plugin'; type Plugins = Array; @@ -130,7 +131,7 @@ export default class Client extends EventEmitter { messageBuffer: Record< string /*pluginKey*/, { - plugin: typeof FlipperPlugin | typeof FlipperDevicePlugin; + plugin: PluginDefinition; 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); } @@ -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 emitBytesReceived(params.api, bytes); - const persistingPlugin: - | typeof FlipperPlugin - | typeof FlipperDevicePlugin - | undefined = + const persistingPlugin: PluginDefinition | undefined = this.store.getState().plugins.clientPlugins.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( this.id, {serial: this.query.device_id}, @@ -417,16 +422,18 @@ export default class Client extends EventEmitter { this.flushMessageBufferDebounced(); } const apiCallbacks = this.broadcastCallbacks.get(params.api); - if (!apiCallbacks) { - return; - } - - const methodCallbacks = apiCallbacks.get(params.method); - if (methodCallbacks) { - for (const callback of methodCallbacks) { - callback(params.params); + if (apiCallbacks) { + const methodCallbacks = apiCallbacks.get(params.method); + if (methodCallbacks) { + for (const callback of methodCallbacks) { + handled = true; + callback(params.params); + } } } + if (!handled) { + console.warn(`Unhandled message ${params.api}.${params.method}`); + } } return; // method === 'execute' } diff --git a/desktop/app/src/MenuBar.tsx b/desktop/app/src/MenuBar.tsx index e028dc6c7..82b5f4eeb 100644 --- a/desktop/app/src/MenuBar.tsx +++ b/desktop/app/src/MenuBar.tsx @@ -7,7 +7,7 @@ * @format */ -import {FlipperPlugin, FlipperDevicePlugin} from './plugin'; +import {FlipperPlugin, FlipperDevicePlugin, PluginDefinition} from './plugin'; import { showOpenDialog, startFileExport, @@ -71,7 +71,7 @@ function actionHandler(action: string) { } export function setupMenuBar( - plugins: Array, + plugins: PluginDefinition[], store: Store, logger: Logger, ) { diff --git a/desktop/app/src/NotificationsHub.tsx b/desktop/app/src/NotificationsHub.tsx index b8dc501c3..ee7c9e1b9 100644 --- a/desktop/app/src/NotificationsHub.tsx +++ b/desktop/app/src/NotificationsHub.tsx @@ -7,7 +7,7 @@ * @format */ -import {SearchableProps, FlipperBasePlugin, FlipperPlugin} from 'flipper'; +import {SearchableProps} from 'flipper'; import {Logger} from './fb-interfaces/Logger'; import { Searchable, @@ -21,7 +21,7 @@ import { styled, colors, } from 'flipper'; -import {FlipperDevicePlugin} from './plugin'; +import {PluginDefinition, DevicePluginMap, ClientPluginMap} from './plugin'; import {connect} from 'react-redux'; import React, {Component, Fragment} from 'react'; import {clipboard} from 'electron'; @@ -47,8 +47,8 @@ type StateFromProps = { invalidatedNotifications: Array; blacklistedPlugins: Array; blacklistedCategories: Array; - devicePlugins: Map; - clientPlugins: Map; + devicePlugins: DevicePluginMap; + clientPlugins: ClientPluginMap; }; type DispatchFromProps = { @@ -417,7 +417,7 @@ type ItemProps = { deepLinkPayload: string | null; }) => any; logger?: Logger; - plugin: typeof FlipperBasePlugin | null | undefined; + plugin: PluginDefinition | null | undefined; }; type ItemState = { diff --git a/desktop/app/src/PluginContainer.tsx b/desktop/app/src/PluginContainer.tsx index af71bfa1b..765611996 100644 --- a/desktop/app/src/PluginContainer.tsx +++ b/desktop/app/src/PluginContainer.tsx @@ -11,6 +11,7 @@ import { FlipperPlugin, FlipperDevicePlugin, Props as PluginProps, + PluginDefinition, } from './plugin'; import {Logger} from './fb-interfaces/Logger'; import BaseDevice from './devices/BaseDevice'; @@ -45,6 +46,7 @@ import {Message} from './reducers/pluginMessageQueue'; import {Idler} from './utils/Idler'; import {processMessageQueue} from './utils/messageQueue'; import {ToggleButton, SmallText} from './ui'; +import {SandyPluginDefinition} from 'flipper-plugin'; const Container = styled(FlexColumn)({ width: 0, @@ -95,7 +97,7 @@ type OwnProps = { type StateFromProps = { pluginState: Object; - activePlugin: typeof FlipperPlugin | typeof FlipperDevicePlugin | null; + activePlugin: PluginDefinition | undefined; target: Client | BaseDevice | null; pluginKey: string | null; deepLinkPayload: string | null; @@ -190,6 +192,8 @@ class PluginContainer extends PureComponent { if ( pluginIsEnabled && activePlugin && + // TODO: support sandy: T68683442 + !(activePlugin instanceof SandyPluginDefinition) && activePlugin.persistedStateReducer && pluginKey && pendingMessages?.length @@ -328,6 +332,10 @@ class PluginContainer extends PureComponent { console.warn(`No selected plugin. Rendering empty!`); return null; } + if (activePlugin instanceof SandyPluginDefinition) { + // TODO: + return null; + } const props: PluginProps & { key: string; ref: ( @@ -404,14 +412,11 @@ export default connect( }) => { let pluginKey = null; let target = null; - let activePlugin: - | typeof FlipperDevicePlugin - | typeof FlipperPlugin - | null = null; + let activePlugin: PluginDefinition | undefined; let pluginIsEnabled = false; if (selectedPlugin) { - activePlugin = devicePlugins.get(selectedPlugin) || null; + activePlugin = devicePlugins.get(selectedPlugin); target = selectedDevice; if (selectedDevice && activePlugin) { pluginKey = getPluginKey(selectedDevice.serial, activePlugin.id); @@ -419,7 +424,7 @@ export default connect( } else { target = clients.find((client: Client) => client.id === selectedApp) || null; - activePlugin = clientPlugins.get(selectedPlugin) || null; + activePlugin = clientPlugins.get(selectedPlugin); if (activePlugin && target) { pluginKey = getPluginKey(target.id, activePlugin.id); pluginIsEnabled = pluginIsStarred( diff --git a/desktop/app/src/chrome/mainsidebar/MainSidebar2.tsx b/desktop/app/src/chrome/mainsidebar/MainSidebar2.tsx index 2d598812d..510893960 100644 --- a/desktop/app/src/chrome/mainsidebar/MainSidebar2.tsx +++ b/desktop/app/src/chrome/mainsidebar/MainSidebar2.tsx @@ -20,8 +20,6 @@ import { Glyph, styled, GK, - FlipperPlugin, - FlipperDevicePlugin, ArchivedDevice, SmallText, Info, @@ -62,8 +60,9 @@ import { getFavoritePlugins, } from './sidebarUtils'; import {useLocalStorage} from '../../utils/useLocalStorage'; +import {PluginDefinition, ClientPluginMap, DevicePluginMap} from '../../plugin'; -type FlipperPlugins = typeof FlipperPlugin[]; +type FlipperPlugins = PluginDefinition[]; type PluginsByCategory = [string, FlipperPlugins][]; type SectionLevel = 1 | 2 | 3; @@ -184,8 +183,8 @@ type StateFromProps = { deviceId?: string; errorMessage?: string; }>; - devicePlugins: Map; - clientPlugins: Map; + devicePlugins: DevicePluginMap; + clientPlugins: ClientPluginMap; }; type SelectPlugin = (payload: { @@ -469,7 +468,7 @@ const PluginList = memo(function PluginList({ }: { client: Client; device: BaseDevice; - clientPlugins: Map; + clientPlugins: ClientPluginMap; starPlugin: typeof starPluginAction; userStarredPlugins: Store['connections']['userStarredPlugins']; selectedPlugin?: null | string; @@ -497,7 +496,7 @@ const PluginList = memo(function PluginList({ ); 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( device, diff --git a/desktop/app/src/chrome/mainsidebar/sidebarUtils.tsx b/desktop/app/src/chrome/mainsidebar/sidebarUtils.tsx index 36064679f..f5a5a79d4 100644 --- a/desktop/app/src/chrome/mainsidebar/sidebarUtils.tsx +++ b/desktop/app/src/chrome/mainsidebar/sidebarUtils.tsx @@ -14,9 +14,7 @@ import { Text, Glyph, styled, - FlipperPlugin, FlexColumn, - FlipperBasePlugin, ToggleButton, brandColors, Spacer, @@ -27,8 +25,9 @@ import { } from 'flipper'; import {BackgroundColorProperty} from 'csstype'; import {getPluginTitle} from '../../utils/pluginUtils'; +import {PluginDefinition} from '../../plugin'; -export type FlipperPlugins = typeof FlipperPlugin[]; +export type FlipperPlugins = PluginDefinition[]; export type PluginsByCategory = [string, FlipperPlugins][]; export const ListItem = styled.div<{active?: boolean; disabled?: boolean}>( @@ -133,7 +132,7 @@ export const Plugins = styled(FlexColumn)({ export const PluginSidebarListItem: React.FC<{ onClick: () => void; isActive: boolean; - plugin: typeof FlipperBasePlugin; + plugin: PluginDefinition; app?: string | null | undefined; helpRef?: any; provided?: any; diff --git a/desktop/app/src/chrome/plugin-manager/PluginDebugger.tsx b/desktop/app/src/chrome/plugin-manager/PluginDebugger.tsx index 09553cc3b..9cc1d175c 100644 --- a/desktop/app/src/chrome/plugin-manager/PluginDebugger.tsx +++ b/desktop/app/src/chrome/plugin-manager/PluginDebugger.tsx @@ -12,18 +12,10 @@ import Client from '../../Client'; import {TableBodyRow} from '../../ui/components/table/types'; import React, {Component, Fragment} from 'react'; import {connect} from 'react-redux'; -import { - Text, - ManagedTable, - styled, - colors, - Link, - FlipperPlugin, - FlipperDevicePlugin, - Bordered, -} from 'flipper'; +import {Text, ManagedTable, styled, colors, Link, Bordered} from 'flipper'; import StatusIndicator from '../../ui/components/StatusIndicator'; import {State as Store} from '../../reducers'; +import {DevicePluginDefinition, ClientPluginDefinition} from '../../plugin'; const InfoText = styled(Text)({ lineHeight: '130%', @@ -51,8 +43,8 @@ type StateFromProps = { failedPlugins: Array<[PluginDetails, string]>; clients: Array; selectedDevice: string | null | undefined; - devicePlugins: Array; - clientPlugins: Array; + devicePlugins: DevicePluginDefinition[]; + clientPlugins: ClientPluginDefinition[]; }; type DispatchFromProps = {}; @@ -299,10 +291,8 @@ export default connect( }, connections: {clients, selectedDevice}, }) => ({ - devicePlugins: Array.from( - devicePlugins.values(), - ), - clientPlugins: Array.from(clientPlugins.values()), + devicePlugins: Array.from(devicePlugins.values()), + clientPlugins: Array.from(clientPlugins.values()), gatekeepedPlugins, clients, disabledPlugins, diff --git a/desktop/app/src/dispatcher/__tests__/plugins.node.tsx b/desktop/app/src/dispatcher/__tests__/plugins.node.tsx index 8a62425e3..961db6852 100644 --- a/desktop/app/src/dispatcher/__tests__/plugins.node.tsx +++ b/desktop/app/src/dispatcher/__tests__/plugins.node.tsx @@ -162,6 +162,7 @@ test('requirePlugin loads plugin', () => { version: '1.0.0', }); expect(plugin).not.toBeNull(); + // @ts-ignore expect(plugin!.prototype).toBeInstanceOf(FlipperPlugin); expect(plugin!.id).toBe(TestPlugin.id); }); diff --git a/desktop/app/src/dispatcher/notifications.tsx b/desktop/app/src/dispatcher/notifications.tsx index 0343a5ef9..08dfaa757 100644 --- a/desktop/app/src/dispatcher/notifications.tsx +++ b/desktop/app/src/dispatcher/notifications.tsx @@ -10,7 +10,7 @@ import {Store} from '../reducers/index'; import {Logger} from '../fb-interfaces/Logger'; import {PluginNotification} from '../reducers/notifications'; -import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; +import {PluginDefinition} from '../plugin'; import isHeadless from '../utils/isHeadless'; import {setStaticView, setDeeplinkPayload} from '../reducers/connections'; import {ipcRenderer, IpcRendererEvent} from 'electron'; @@ -25,6 +25,7 @@ import {deconstructPluginKey} from '../utils/clientUtils'; import NotificationScreen from '../chrome/NotificationScreen'; import {getPluginTitle} from '../utils/pluginUtils'; import {sideEffect} from '../utils/sideEffect'; +import {SandyPluginDefinition} from 'flipper-plugin'; type NotificationEvents = 'show' | 'click' | 'close' | 'reply' | 'action'; const NOTIFICATION_THROTTLE = 5 * 1000; // in milliseconds @@ -110,11 +111,15 @@ export default (store: Store, logger: Logger) => { return; } - const persistingPlugin: - | undefined - | typeof FlipperPlugin - | typeof FlipperDevicePlugin = getPlugin(pluginName); - if (persistingPlugin && persistingPlugin.getActiveNotifications) { + const persistingPlugin: undefined | PluginDefinition = getPlugin( + pluginName, + ); + // TODO: add support for Sandy plugins T68683442 + if ( + persistingPlugin && + !(persistingPlugin instanceof SandyPluginDefinition) && + persistingPlugin.getActiveNotifications + ) { try { const notifications = persistingPlugin.getActiveNotifications( pluginStates[key], diff --git a/desktop/app/src/dispatcher/plugins.tsx b/desktop/app/src/dispatcher/plugins.tsx index e6e573b6e..b14c2ed5a 100644 --- a/desktop/app/src/dispatcher/plugins.tsx +++ b/desktop/app/src/dispatcher/plugins.tsx @@ -9,7 +9,7 @@ import {Store} from '../reducers/index'; import {Logger} from '../fb-interfaces/Logger'; -import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; +import {PluginDefinition} from '../plugin'; import React from 'react'; import ReactDOM from 'react-dom'; import adbkit from 'adbkit'; @@ -58,9 +58,10 @@ export default (store: Store, logger: Logger) => { const defaultPluginsIndex = getPluginIndex(); - const initialPlugins: Array< - typeof FlipperPlugin | typeof FlipperDevicePlugin - > = filterNewestVersionOfEachPlugin(getBundledPlugins(), getDynamicPlugins()) + const initialPlugins: PluginDefinition[] = filterNewestVersionOfEachPlugin( + getBundledPlugins(), + getDynamicPlugins(), + ) .map(reportVersion) .filter(checkDisabled(disabledPlugins)) .filter(checkGK(gatekeepedPlugins)) @@ -247,9 +248,7 @@ export const requirePlugin = ( defaultPluginsIndex: any, reqFn: Function = global.electronRequire, ) => { - return ( - pluginDetails: PluginDetails, - ): typeof FlipperPlugin | typeof FlipperDevicePlugin | null => { + return (pluginDetails: PluginDetails): PluginDefinition | null => { try { return tryCatchReportPluginFailures( () => requirePluginInternal(pluginDetails, defaultPluginsIndex, reqFn), @@ -281,6 +280,7 @@ const requirePluginInternal = ( plugin.id = plugin.id || pluginDetails.id; plugin.packageName = pluginDetails.name; + plugin.flipperSDKVersion = pluginDetails.flipperSDKVersion; plugin.details = pluginDetails; // set values from package.json as static variables on class diff --git a/desktop/app/src/plugin.tsx b/desktop/app/src/plugin.tsx index 324288b75..b9e60646f 100644 --- a/desktop/app/src/plugin.tsx +++ b/desktop/app/src/plugin.tsx @@ -22,8 +22,21 @@ import {State as ReduxState} from './reducers'; import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue'; import {PluginDetails} from 'flipper-plugin-lib'; import {Settings} from './reducers/settings'; +import {SandyPluginDefinition} from 'flipper-plugin'; 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; +export type DevicePluginMap = Map; + // 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( @@ -100,6 +113,7 @@ export abstract class FlipperBasePlugin< static category: string | null = null; static id: string = ''; static packageName: string = ''; + static flipperSDKVersion: string | undefined = undefined; static version: string = ''; static icon: string | null = null; static gatekeeper: string | null = null; @@ -113,7 +127,7 @@ export abstract class FlipperBasePlugin< static maxQueueSize: number = DEFAULT_MAX_QUEUE_SIZE; static metricsReducer: | ((persistedState: StaticPersistedState) => Promise) - | null; + | undefined; static exportPersistedState: | (( callClient: (method: string, params?: any) => Promise, @@ -123,10 +137,10 @@ export abstract class FlipperBasePlugin< statusUpdate?: (msg: string) => void, supportsMethod?: (method: string) => Promise, ) => Promise) - | null; + | undefined; static getActiveNotifications: | ((persistedState: StaticPersistedState) => Array) - | null; + | undefined; static onRegisterDevice: | (( store: Store, diff --git a/desktop/app/src/reducers/connections.tsx b/desktop/app/src/reducers/connections.tsx index fe97666b2..e9d6a7980 100644 --- a/desktop/app/src/reducers/connections.tsx +++ b/desktop/app/src/reducers/connections.tsx @@ -14,7 +14,6 @@ import MacDevice from '../devices/MacDevice'; import Client from '../Client'; import {UninitializedClient} from '../UninitializedClient'; import {isEqual} from 'lodash'; -import iosUtil from '../utils/iOSContainerUtility'; import {performance} from 'perf_hooks'; import isHeadless from '../utils/isHeadless'; import {Actions} from '.'; @@ -31,6 +30,7 @@ import { import {deconstructClientId} from '../utils/clientUtils'; import {FlipperDevicePlugin} from '../plugin'; import {RegisterPluginAction} from './plugins'; +import {SandyPluginDefinition} from 'flipper-plugin'; export type StaticView = | 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 const plugins = action.payload; 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 state.devices.forEach((device) => { // @ts-ignore diff --git a/desktop/app/src/reducers/plugins.tsx b/desktop/app/src/reducers/plugins.tsx index fe29f8065..9dc450958 100644 --- a/desktop/app/src/reducers/plugins.tsx +++ b/desktop/app/src/reducers/plugins.tsx @@ -7,25 +7,24 @@ * @format */ -import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; +import {DevicePluginMap, ClientPluginMap, PluginDefinition} from '../plugin'; import {PluginDetails} from 'flipper-plugin-lib'; import {Actions} from '.'; import produce from 'immer'; +import {isDevicePluginDefinition} from '../utils/pluginUtils'; export type State = { - devicePlugins: Map; - clientPlugins: Map; + devicePlugins: DevicePluginMap; + clientPlugins: ClientPluginMap; gatekeepedPlugins: Array; disabledPlugins: Array; failedPlugins: Array<[PluginDetails, string]>; selectedPlugins: Array; }; -type PluginClass = typeof FlipperPlugin | typeof FlipperDevicePlugin; - export type RegisterPluginAction = { type: 'REGISTER_PLUGINS'; - payload: Array; + payload: PluginDefinition[]; }; export type Action = @@ -63,16 +62,14 @@ export default function reducer( if (action.type === 'REGISTER_PLUGINS') { return produce(state, (draft) => { const {devicePlugins, clientPlugins} = draft; - action.payload.forEach((p: PluginClass) => { + action.payload.forEach((p) => { if (devicePlugins.has(p.id) || clientPlugins.has(p.id)) { return; } - if (p.prototype instanceof FlipperDevicePlugin) { - // @ts-ignore doesn't know p must be typeof FlipperDevicePlugin here + if (isDevicePluginDefinition(p)) { devicePlugins.set(p.id, p); - } else if (p.prototype instanceof FlipperPlugin) { - // @ts-ignore doesn't know p must be typeof FlipperPlugin here + } else { clientPlugins.set(p.id, p); } }); @@ -107,7 +104,7 @@ export const selectedPlugins = (payload: Array): Action => ({ payload, }); -export const registerPlugins = (payload: Array): Action => ({ +export const registerPlugins = (payload: PluginDefinition[]): Action => ({ type: 'REGISTER_PLUGINS', payload, }); diff --git a/desktop/app/src/utils/exportData.tsx b/desktop/app/src/utils/exportData.tsx index 43534c3a8..066c88085 100644 --- a/desktop/app/src/utils/exportData.tsx +++ b/desktop/app/src/utils/exportData.tsx @@ -20,10 +20,12 @@ import Client, {ClientExport, ClientQuery} from '../Client'; import {pluginKey} from '../reducers/pluginStates'; import { FlipperDevicePlugin, - FlipperPlugin, callClient, supportsMethod, FlipperBasePlugin, + PluginDefinition, + DevicePluginMap, + ClientPluginMap, } from '../plugin'; import {default as BaseDevice} from '../devices/BaseDevice'; import {default as ArchivedDevice} from '../devices/ArchivedDevice'; @@ -47,6 +49,7 @@ import {processMessageQueue} from './messageQueue'; import {getPluginTitle} from './pluginUtils'; import {capture} from './screenshot'; import {uploadFlipperMedia} from '../fb-stubs/user'; +import {SandyPluginDefinition} from 'flipper-plugin'; export const IMPORT_FLIPPER_TRACE_EVENT = 'import-flipper-trace'; export const EXPORT_FLIPPER_TRACE_EVENT = 'export-flipper-trace'; @@ -93,7 +96,7 @@ type PluginsToProcess = { pluginKey: string; pluginId: string; pluginName: string; - pluginClass: typeof FlipperPlugin | typeof FlipperDevicePlugin; + pluginClass: PluginDefinition; client: Client; }[]; @@ -212,14 +215,17 @@ export function processNotificationStates( const serializePluginStates = async ( pluginStates: PluginStatesState, - clientPlugins: Map, - devicePlugins: Map, + clientPlugins: ClientPluginMap, + devicePlugins: DevicePluginMap, statusUpdate?: (msg: string) => void, idler?: Idler, ): Promise => { const pluginsMap: Map = new Map([]); 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) => { pluginsMap.set(key, val); @@ -248,12 +254,13 @@ const serializePluginStates = async ( const deserializePluginStates = ( pluginStatesExportState: PluginStatesExportState, - clientPlugins: Map, - devicePlugins: Map, + clientPlugins: ClientPluginMap, + devicePlugins: DevicePluginMap, ): PluginStatesState => { const pluginsMap: Map = new Map([]); clientPlugins.forEach((val, key) => { - pluginsMap.set(key, val); + // TODO: Support Sandy T68683449 + if (!(val instanceof SandyPluginDefinition)) pluginsMap.set(key, val); }); devicePlugins.forEach((val, key) => { pluginsMap.set(key, val); @@ -358,8 +365,8 @@ type ProcessStoreOptions = { device: BaseDevice | null; pluginStates: PluginStatesState; clients: Array; - devicePlugins: Map; - clientPlugins: Map; + devicePlugins: DevicePluginMap; + clientPlugins: ClientPluginMap; salt: string; selectedPlugins: Array; statusUpdate?: (msg: string) => void; @@ -514,7 +521,11 @@ async function processQueues( pluginKey, pluginClass, } 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`; performance.mark(processQueueMarker); diff --git a/desktop/app/src/utils/exportMetrics.tsx b/desktop/app/src/utils/exportMetrics.tsx index 8a5a30ad8..7542019c5 100644 --- a/desktop/app/src/utils/exportMetrics.tsx +++ b/desktop/app/src/utils/exportMetrics.tsx @@ -7,7 +7,6 @@ * @format */ -import {FlipperPlugin, FlipperDevicePlugin} from 'flipper'; import {serialize} from './serialization'; import {State as PluginStatesState} from '../reducers/pluginStates'; import {Store} from '../reducers'; @@ -20,6 +19,8 @@ import { import {deserializeObject} from './serialization'; import {deconstructPluginKey} from './clientUtils'; import {pluginsClassMap} from './pluginUtils'; +import {PluginDefinition} from '../plugin'; +import {SandyPluginDefinition} from 'flipper-plugin'; export type MetricType = {[metricName: string]: number}; type MetricPluginType = {[pluginID: string]: MetricType}; @@ -27,7 +28,7 @@ export type ExportMetricType = {[clientID: string]: MetricPluginType}; async function exportMetrics( pluginStates: PluginStatesState, - pluginsMap: Map, + pluginsMap: Map, selectedPlugins: Array, ): Promise { const metrics: ExportMetricType = {}; @@ -46,8 +47,10 @@ async function exportMetrics( const pluginClass = pluginsMap.get(pluginName); const metricsReducer: | ((persistedState: U) => Promise) - | null - | undefined = pluginClass && pluginClass.metricsReducer; + | undefined = + pluginClass && !(pluginClass instanceof SandyPluginDefinition) + ? pluginClass.metricsReducer + : undefined; if (pluginsMap.has(pluginName) && metricsReducer) { const metricsObject = await metricsReducer(pluginStateData); const pluginObject: MetricPluginType = {}; @@ -67,10 +70,7 @@ export async function exportMetricsWithoutTrace( store: Store, pluginStates: PluginStatesState, ): Promise { - const pluginsMap: Map< - string, - typeof FlipperDevicePlugin | typeof FlipperPlugin - > = pluginsClassMap(store.getState().plugins); + const pluginsMap = pluginsClassMap(store.getState().plugins); const {clients, selectedDevice} = store.getState().connections; const pluginsToProcess = determinePluginsToProcess( clients, @@ -107,7 +107,7 @@ function parseJSON(str: string): any { export async function exportMetricsFromTrace( trace: string, - pluginsMap: Map, + pluginsMap: Map, selectedPlugins: Array, ): Promise { 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); } diff --git a/desktop/app/src/utils/messageQueue.tsx b/desktop/app/src/utils/messageQueue.tsx index 303e77e35..617d402f2 100644 --- a/desktop/app/src/utils/messageQueue.tsx +++ b/desktop/app/src/utils/messageQueue.tsx @@ -21,6 +21,7 @@ import {pluginIsStarred, getSelectedPluginKey} from '../reducers/connections'; import {deconstructPluginKey} from './clientUtils'; import {onBytesReceived} from '../dispatcher/tracking'; import {defaultEnabledBackgroundPlugins} from './pluginUtils'; +import {SandyPluginDefinition} from 'flipper-plugin'; const MAX_BACKGROUND_TASK_TIME = 25; @@ -189,14 +190,22 @@ export function processMessagesImmediately( export function processMessagesLater( store: MiddlewareAPI, pluginKey: string, - plugin: { - defaultPersistedState: any; - id: string; - persistedStateReducer: PersistedStateReducer | null; - maxQueueSize?: number; - }, + plugin: + | { + defaultPersistedState: any; + id: string; + persistedStateReducer: PersistedStateReducer | null; + maxQueueSize?: number; + } + | SandyPluginDefinition, messages: Message[], ) { + if (plugin instanceof SandyPluginDefinition) { + // TODO: + throw new Error( + 'Receiving messages is not yet supported for Sandy plugins', + ); + } const isSelected = pluginKey === getSelectedPluginKey(store.getState().connections); switch (true) { diff --git a/desktop/app/src/utils/onRegisterDevice.tsx b/desktop/app/src/utils/onRegisterDevice.tsx index 94a3f235d..067fa6076 100644 --- a/desktop/app/src/utils/onRegisterDevice.tsx +++ b/desktop/app/src/utils/onRegisterDevice.tsx @@ -8,21 +8,21 @@ */ import {Store} from '../reducers/index'; -import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; +import {ClientPluginMap, DevicePluginMap, PluginDefinition} from '../plugin'; import {setPluginState} from '../reducers/pluginStates'; import BaseDevice from '../devices/BaseDevice'; import {getPersistedState} from '../utils/pluginUtils'; +import {SandyPluginDefinition} from 'flipper-plugin'; export function registerDeviceCallbackOnPlugins( store: Store, - devicePlugins: Map, - clientPlugins: Map, + devicePlugins: DevicePluginMap, + clientPlugins: ClientPluginMap, device: BaseDevice, ) { - const callRegisterDeviceHook = ( - plugin: typeof FlipperDevicePlugin | typeof FlipperPlugin, - ) => { - if (plugin.onRegisterDevice) { + const callRegisterDeviceHook = (plugin: PluginDefinition) => { + // This hook is not registered for Sandy plugins, let's see in the future if it is needed + if (!(plugin instanceof SandyPluginDefinition) && plugin.onRegisterDevice) { plugin.onRegisterDevice( store, device, diff --git a/desktop/app/src/utils/pluginStateRecorder.tsx b/desktop/app/src/utils/pluginStateRecorder.tsx index 8725a1b71..8d507163a 100644 --- a/desktop/app/src/utils/pluginStateRecorder.tsx +++ b/desktop/app/src/utils/pluginStateRecorder.tsx @@ -12,6 +12,7 @@ import fs from 'fs'; import {Store, State} from '../reducers'; import {getPluginKey} from './pluginUtils'; import {serialize} from './serialization'; +import {SandyPluginDefinition} from 'flipper-plugin'; let pluginRecordingState: { 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 // device state, and is used for creating Flipper Exports. pluginRecordingState.startState = await serialize( - state.pluginStates[pluginKey] || plugin.defaultPersistedState, + state.pluginStates[pluginKey] || + (plugin instanceof SandyPluginDefinition + ? {} + : plugin.defaultPersistedState), ); console.log( diff --git a/desktop/app/src/utils/pluginUtils.tsx b/desktop/app/src/utils/pluginUtils.tsx index 899d6a955..71df4acea 100644 --- a/desktop/app/src/utils/pluginUtils.tsx +++ b/desktop/app/src/utils/pluginUtils.tsx @@ -7,12 +7,18 @@ * @format */ -import {FlipperDevicePlugin, FlipperPlugin, FlipperBasePlugin} from '../plugin'; +import { + FlipperDevicePlugin, + FlipperBasePlugin, + PluginDefinition, + DevicePluginDefinition, +} from '../plugin'; import {State as PluginStatesState} from '../reducers/pluginStates'; import {State as PluginsState} from '../reducers/plugins'; import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue'; import {PluginDetails} from 'flipper-plugin-lib'; import {deconstructPluginKey, deconstructClientId} from './clientUtils'; +import {SandyPluginDefinition} from 'flipper-plugin'; type Client = import('../Client').default; @@ -20,11 +26,8 @@ export const defaultEnabledBackgroundPlugins = ['Navigation']; // The navigation export function pluginsClassMap( plugins: PluginsState, -): Map { - const pluginsMap: Map< - string, - typeof FlipperDevicePlugin | typeof FlipperPlugin - > = new Map([]); +): Map { + const pluginsMap: Map = new Map([]); plugins.clientPlugins.forEach((val, key) => { pluginsMap.set(key, val); }); @@ -83,10 +86,7 @@ export function getEnabledOrExportPersistedStatePlugins( plugins: PluginsState, ): Array<{id: string; label: string}> { const appName = deconstructClientId(client.id).app; - const pluginsMap: Map< - string, - typeof FlipperDevicePlugin | typeof FlipperPlugin - > = pluginsClassMap(plugins); + const pluginsMap: Map = pluginsClassMap(plugins); // Enabled Plugins with no exportPersistedState function defined const enabledPlugins = starredPlugin[appName] ? starredPlugin[appName] @@ -141,10 +141,7 @@ export function getActivePersistentPlugins( plugins: PluginsState, selectedClient?: Client, ): {id: string; label: string}[] { - const pluginsMap: Map< - string, - typeof FlipperDevicePlugin | typeof FlipperPlugin - > = pluginsClassMap(plugins); + const pluginsMap: Map = pluginsClassMap(plugins); return getPersistentPlugins(plugins) .map((pluginName) => pluginsMap.get(pluginName)!) .sort(sortPluginsByName) @@ -183,10 +180,7 @@ export function getActivePersistentPlugins( } export function getPersistentPlugins(plugins: PluginsState): Array { - const pluginsMap: Map< - string, - typeof FlipperDevicePlugin | typeof FlipperPlugin - > = pluginsClassMap(plugins); + const pluginsMap: Map = pluginsClassMap(plugins); const arr: Array = plugins.disabledPlugins.concat( plugins.gatekeepedPlugins, @@ -210,32 +204,36 @@ export function getPersistentPlugins(plugins: PluginsState): Array { return ( plugin == 'DeviceLogs' || (pluginClass && + // TODO: support Sandy plugin T68683449 + !(pluginClass instanceof SandyPluginDefinition) && (pluginClass.defaultPersistedState != undefined || pluginClass.exportPersistedState != undefined)) ); }); } -export function getPluginTitle(pluginClass: typeof FlipperBasePlugin) { +export function getPluginTitle(pluginClass: PluginDefinition) { return pluginClass.title || pluginClass.id; } export function sortPluginsByName( - a: typeof FlipperBasePlugin, - b: typeof FlipperBasePlugin, + a: PluginDefinition, + b: PluginDefinition, ): number { // make sure Device plugins are sorted before normal plugins - if ( - a.prototype instanceof FlipperDevicePlugin && - !(b.prototype instanceof FlipperDevicePlugin) - ) { + if (isDevicePluginDefinition(a) && !isDevicePluginDefinition(b)) { return -1; } - if ( - b.prototype instanceof FlipperDevicePlugin && - !(a.prototype instanceof FlipperDevicePlugin) - ) { + if (isDevicePluginDefinition(b) && !isDevicePluginDefinition(a)) { return 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; +} diff --git a/desktop/flipper-plugin/src/index.ts b/desktop/flipper-plugin/src/index.ts index 02e501ce7..b001bc7c8 100644 --- a/desktop/flipper-plugin/src/index.ts +++ b/desktop/flipper-plugin/src/index.ts @@ -7,6 +7,4 @@ * @format */ -export function hello() { - return 'universe '; -} +export * from './plugin/Plugin'; diff --git a/desktop/flipper-plugin/src/plugin/Plugin.tsx b/desktop/flipper-plugin/src/plugin/Plugin.tsx new file mode 100644 index 000000000..40b39a945 --- /dev/null +++ b/desktop/flipper-plugin/src/plugin/Plugin.tsx @@ -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; +type MethodsContract = Record Promise>; + +/** + * 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) => object; + +export type FlipperPluginComponent = React.FC<{}>; + +export type FlipperPluginModule = { + /** the factory function that initializes a plugin instance */ + plugin: FlipperPluginFactory; + /** 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; + /** 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, + persistedState: any, // TODO: type StaticPersistedState | undefined, + store: any, // TODO: ReduxState | undefined, + idler?: any, // TODO: Idler, + statusUpdate?: (msg: string) => void, + supportsMethod?: (method: string) => Promise, + ) => Promise) + | 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 []; + } +} diff --git a/desktop/flipper-plugin/tsconfig.json b/desktop/flipper-plugin/tsconfig.json index ecd4c16fb..9c303bc5a 100644 --- a/desktop/flipper-plugin/tsconfig.json +++ b/desktop/flipper-plugin/tsconfig.json @@ -4,11 +4,7 @@ "outDir": "lib", "rootDir": "src" }, - "include": [ - "src" - ], - "exclude": [ - "node_modules", - "**/__tests__/*" - ] + "references": [{"path": "../plugin-lib"}], + "include": ["src"], + "exclude": ["node_modules", "**/__tests__/*"] }