diff --git a/desktop/app/src/dispatcher/plugins.tsx b/desktop/app/src/dispatcher/plugins.tsx index 83aed4787..a1ae4fed7 100644 --- a/desktop/app/src/dispatcher/plugins.tsx +++ b/desktop/app/src/dispatcher/plugins.tsx @@ -9,7 +9,7 @@ import type {Store} from '../reducers/index'; import type {Logger} from '../fb-interfaces/Logger'; -import type {PluginDefinition} from '../plugin'; +import {PluginDefinition} from '../plugin'; import React from 'react'; import ReactDOM from 'react-dom'; import adbkit from 'adbkit'; @@ -54,6 +54,7 @@ import {isDevicePluginDefinition} from '../utils/pluginUtils'; import isPluginCompatible from '../utils/isPluginCompatible'; import isPluginVersionMoreRecent from '../utils/isPluginVersionMoreRecent'; import {getStaticPath} from '../utils/pathUtils'; +import {createSandyPluginWrapper} from '../utils/createSandyPluginWrapper'; let defaultPluginsIndex: any = null; export default async (store: Store, logger: Logger) => { @@ -317,6 +318,13 @@ const requirePluginInternal = ( plugin.packageName = pluginDetails.name; plugin.details = pluginDetails; + if (GK.get('flipper_use_sandy_plugin_wrapper')) { + return new _SandyPluginDefinition( + pluginDetails, + createSandyPluginWrapper(plugin), + ); + } + // set values from package.json as static variables on class Object.keys(pluginDetails).forEach((key) => { if (key !== 'name' && key !== 'id') { diff --git a/desktop/app/src/plugin.tsx b/desktop/app/src/plugin.tsx index 432df4b84..db21c101d 100644 --- a/desktop/app/src/plugin.tsx +++ b/desktop/app/src/plugin.tsx @@ -10,7 +10,6 @@ import {KeyboardActions} from './MenuBar'; import {Logger} from './fb-interfaces/Logger'; import Client from './Client'; -import {Store} from './reducers/index'; import {Component} from 'react'; import BaseDevice from './devices/BaseDevice'; import {serialize, deserialize} from './utils/serialization'; @@ -74,7 +73,7 @@ export type Props = { setPersistedState: (state: Partial) => void; target: PluginTarget; deepLinkPayload: unknown; - selectPlugin: (pluginID: string, deepLinkPayload: unknown) => boolean; + selectPlugin: (pluginID: string, deepLinkPayload: unknown) => void; isArchivedDevice: boolean; selectedApp: string | null; setStaticView: (payload: StaticView) => void; @@ -168,13 +167,6 @@ export abstract class FlipperBasePlugin< teardown(): void {} - computeNotifications( - _props: Props, - _state: State, - ): Array { - return []; - } - // methods to be overridden by subclasses _init(): void {} diff --git a/desktop/app/src/utils/createSandyPluginWrapper.tsx b/desktop/app/src/utils/createSandyPluginWrapper.tsx new file mode 100644 index 000000000..f0e123957 --- /dev/null +++ b/desktop/app/src/utils/createSandyPluginWrapper.tsx @@ -0,0 +1,158 @@ +/** + * 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 * as React from 'react'; +import { + createState, + useLogger, + usePlugin, + useValue, + _SandyPluginDefinition, + PluginClient, + DevicePluginClient, +} from 'flipper-plugin'; +import {useEffect, useRef} from 'react'; +import { + BaseAction, + FlipperDevicePlugin, + FlipperPlugin, + Props as PluginProps, +} from '../plugin'; +import {useDispatch, useStore} from './useStore'; +import {setStaticView, StaticView} from '../reducers/connections'; + +export type SandyPluginModule = ConstructorParameters< + typeof _SandyPluginDefinition +>[1]; + +// Wrapped features +// keyboardActions +// defaultPersistedState +// persistedStateReducer +// exportPersistedState +// getActiveNotifications +// reducers? +// onKeyboardAction +// call _init (not .init) in onactivate / deactivate +// serializePersistedState +// static deserializePersistedState: ( +// call _teardown +// dispatchAction + +// Device: +// .device property + +// Client +// this.client + +export function createSandyPluginWrapper( + Plugin: typeof FlipperPlugin | typeof FlipperDevicePlugin, +): SandyPluginModule { + const isDevicePlugin = Plugin.prototype instanceof FlipperDevicePlugin; + console.warn( + `Loading ${isDevicePlugin ? 'device' : 'client'} plugin ${ + Plugin.id + } in legacy mode. Please visit https://fbflipper.com/docs/extending/sandy-migration to learn how to migrate this plugin to the new Sandy architecture`, + ); + + function plugin(client: PluginClient | DevicePluginClient) { + const appClient = isDevicePlugin ? undefined : (client as PluginClient); + + const persistedState = createState

(Plugin.defaultPersistedState, { + persist: 'persistedState', + }); + const deeplink = createState(); + + client.onDeepLink((link) => { + deeplink.set(link); + }); + + appClient?.onUnhandledMessage((event, params) => { + if (Plugin.persistedStateReducer) { + persistedState.set( + Plugin.persistedStateReducer(persistedState.get(), event, params), + ); + } + }); + + return { + device: client.device.realDevice, + persistedState, + deeplink, + selectPlugin: client.selectPlugin, + setPersistedState(state: Partial

) { + persistedState.set({...persistedState.get(), ...state}); + }, + get appId() { + return appClient?.appId; + }, + get appName() { + return appClient?.appName ?? null; + }, + get isArchived() { + return client.device.isArchived; + }, + }; + } + + function Component() { + const instance = usePlugin(plugin); + const logger = useLogger(); + const pluginInstanceRef = useRef>(null); + const persistedState = useValue(instance.persistedState); + const deepLinkPayload = useValue(instance.deeplink); + const dispatch = useDispatch(); + + const target = isDevicePlugin + ? instance.device + : // eslint-disable-next-line + useStore((state) => + state.connections.clients.find((c) => c.id === instance.appId), + ); + if (!target) { + throw new Error('Illegal state: missing target'); + } + + const settingsState = useStore((state) => state.settingsState); + + useEffect(function triggerInitAndTeardown() { + const ref = pluginInstanceRef.current!; + ref._init(); + return () => { + ref._teardown(); + }; + }, []); + + const props: PluginProps

= { + logger, + persistedState, + target, + deepLinkPayload, + settingsState, + setPersistedState: instance.setPersistedState, + selectPlugin: instance.selectPlugin, + isArchivedDevice: instance.isArchived, + selectedApp: instance.appName, + setStaticView(payload: StaticView) { + dispatch(setStaticView(payload)); + }, + // @ts-ignore ref is not on Props + ref: pluginInstanceRef as any, + }; + + return React.createElement(Plugin, props); + } + + return isDevicePlugin + ? {devicePlugin: plugin, Component} + : { + plugin, + Component, + }; +}