Introduce Sandy wrapper for legacy plugins
Summary: This diff introduces loading classic Flipper plugins in a Sandy container. By wrapping plugins into Sandy we will be able to remove a lot of code / logic duplication related to state, queue processing, serialization etc. This will allow us to remove most or all of the complex plugin logic from the old system, only keeping onto the legacy components which have a lower maintenance burden. Until all plugins are Sandy. This diff is not feature complete but only implements the core mechanisms for (persisted) state and communication. Keyboard support, serialization, and rewiring tests etc will be added in next diff. The feature is introduced behind GK flipper_use_sandy_plugin_wrapper to have kill switch. Tests will be added later in this diff by redirection a part of the current mechanisms to wrapped plugins. (Will land the stack as a whole) Reviewed By: passy Differential Revision: D29165866 fbshipit-source-id: 57f84794a4a5f898bf765ce2de13cc759267fbc6
This commit is contained in:
committed by
Facebook GitHub Bot
parent
07199323d1
commit
c1860ec19c
@@ -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') {
|
||||
|
||||
@@ -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<T> = {
|
||||
setPersistedState: (state: Partial<T>) => 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<PersistedState>,
|
||||
_state: State,
|
||||
): Array<Notification> {
|
||||
return [];
|
||||
}
|
||||
|
||||
// methods to be overridden by subclasses
|
||||
_init(): void {}
|
||||
|
||||
|
||||
158
desktop/app/src/utils/createSandyPluginWrapper.tsx
Normal file
158
desktop/app/src/utils/createSandyPluginWrapper.tsx
Normal file
@@ -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<S, A extends BaseAction, P>(
|
||||
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<P>(Plugin.defaultPersistedState, {
|
||||
persist: 'persistedState',
|
||||
});
|
||||
const deeplink = createState<unknown>();
|
||||
|
||||
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<P>) {
|
||||
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<FlipperPlugin<S, A, P>>(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<P> = {
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user