Remove classic plugin infra

Summary:
This removes all code duplication / old plugin infra that isn't needed anymore when all plugin run on the Sandy plugin infra structure.

The diff is quite large, but the minimal one that passes tests and compiles. Existing tests are preserved by wrapping all remaining tests in `wrapSandy` for classic plugins where needed

Reviewed By: passy

Differential Revision: D29394738

fbshipit-source-id: 1315fabd9f048576aed15ed5f1cb6414d5fdbd40
This commit is contained in:
Michel Weststrate
2021-06-30 10:40:50 -07:00
committed by Facebook GitHub Bot
parent 9d6abd62c6
commit 16154e1343
31 changed files with 564 additions and 2330 deletions

View File

@@ -7,7 +7,7 @@
* @format * @format
*/ */
import {PluginDefinition, FlipperPlugin, FlipperDevicePlugin} from './plugin'; import {PluginDefinition} from './plugin';
import BaseDevice, {OS} from './devices/BaseDevice'; import BaseDevice, {OS} from './devices/BaseDevice';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
import {Store} from './reducers/index'; import {Store} from './reducers/index';
@@ -21,7 +21,6 @@ import invariant from 'invariant';
import { import {
getPluginKey, getPluginKey,
defaultEnabledBackgroundPlugins, defaultEnabledBackgroundPlugins,
isSandyPlugin,
} from './utils/pluginUtils'; } from './utils/pluginUtils';
import {processMessagesLater} from './utils/messageQueue'; import {processMessagesLater} from './utils/messageQueue';
import {emitBytesReceived} from './dispatcher/tracking'; import {emitBytesReceived} from './dispatcher/tracking';
@@ -118,10 +117,7 @@ export default class Client extends EventEmitter {
messageBuffer: Record< messageBuffer: Record<
string /*pluginKey*/, string /*pluginKey*/,
{ {
plugin: plugin: _SandyPluginInstance;
| typeof FlipperPlugin
| typeof FlipperDevicePlugin
| _SandyPluginInstance;
messages: Params[]; messages: Params[];
} }
> = {}; > = {};
@@ -220,7 +216,7 @@ export default class Client extends EventEmitter {
initFromImport(initialStates: Record<string, Record<string, any>>): this { initFromImport(initialStates: Record<string, Record<string, any>>): this {
this.plugins.forEach((pluginId) => { this.plugins.forEach((pluginId) => {
const plugin = this.getPlugin(pluginId); const plugin = this.getPlugin(pluginId);
if (isSandyPlugin(plugin)) { if (plugin) {
// TODO: needs to be wrapped in error tracking T68955280 // TODO: needs to be wrapped in error tracking T68955280
this.sandyPluginStates.set( this.sandyPluginStates.set(
plugin.id, plugin.id,
@@ -253,7 +249,7 @@ export default class Client extends EventEmitter {
) { ) {
// start a plugin on start if it is a SandyPlugin, which is enabled, and doesn't have persisted state yet // start a plugin on start if it is a SandyPlugin, which is enabled, and doesn't have persisted state yet
if ( if (
isSandyPlugin(plugin) && plugin &&
(isEnabled || defaultEnabledBackgroundPlugins.includes(plugin.id)) && (isEnabled || defaultEnabledBackgroundPlugins.includes(plugin.id)) &&
!this.sandyPluginStates.has(plugin.id) !this.sandyPluginStates.has(plugin.id)
) { ) {

View File

@@ -7,14 +7,10 @@
* @format * @format
*/ */
import { import {FlipperPlugin, FlipperDevicePlugin} from './plugin';
FlipperPlugin,
FlipperDevicePlugin,
Props as PluginProps,
} from './plugin';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
import BaseDevice from './devices/BaseDevice'; import BaseDevice from './devices/BaseDevice';
import {pluginKey as getPluginKey} from './reducers/pluginStates'; import {pluginKey as getPluginKey} from './utils/pluginUtils';
import Client from './Client'; import Client from './Client';
import { import {
ErrorBoundary, ErrorBoundary,
@@ -31,8 +27,6 @@ import {StaticView, setStaticView} from './reducers/connections';
import {switchPlugin} from './reducers/pluginManager'; import {switchPlugin} from './reducers/pluginManager';
import React, {PureComponent} from 'react'; import React, {PureComponent} from 'react';
import {connect, ReactReduxContext} from 'react-redux'; import {connect, ReactReduxContext} from 'react-redux';
import {setPluginState} from './reducers/pluginStates';
import {Settings} from './reducers/settings';
import {selectPlugin} from './reducers/connections'; import {selectPlugin} from './reducers/connections';
import {State as Store, MiddlewareAPI} from './reducers/index'; import {State as Store, MiddlewareAPI} from './reducers/index';
import {activateMenuItems} from './MenuBar'; import {activateMenuItems} from './MenuBar';
@@ -40,12 +34,11 @@ import {Message} from './reducers/pluginMessageQueue';
import {IdlerImpl} from './utils/Idler'; import {IdlerImpl} from './utils/Idler';
import {processMessageQueue} from './utils/messageQueue'; import {processMessageQueue} from './utils/messageQueue';
import {Layout} from './ui'; import {Layout} from './ui';
import {theme, TrackingScope, _SandyPluginRenderer} from 'flipper-plugin'; import {theme, _SandyPluginRenderer} from 'flipper-plugin';
import { import {
ActivePluginListItem, ActivePluginListItem,
isDevicePlugin, isDevicePlugin,
isDevicePluginDefinition, isDevicePluginDefinition,
isSandyPlugin,
} from './utils/pluginUtils'; } from './utils/pluginUtils';
import {ContentContainer} from './sandy-chrome/ContentContainer'; import {ContentContainer} from './sandy-chrome/ContentContainer';
import {Alert, Typography} from 'antd'; import {Alert, Typography} from 'antd';
@@ -59,13 +52,6 @@ import {getActiveClient, getActivePlugin} from './selectors/connections';
const {Text, Link} = Typography; const {Text, Link} = Typography;
const Container = styled(FlexColumn)({
width: 0,
flexGrow: 1,
flexShrink: 1,
backgroundColor: colors.white,
});
export const SidebarContainer = styled(FlexRow)({ export const SidebarContainer = styled(FlexRow)({
backgroundColor: theme.backgroundWash, backgroundColor: theme.backgroundWash,
height: '100%', height: '100%',
@@ -103,19 +89,14 @@ const ProgressBarBar = styled.div<{progress: number}>(({progress}) => ({
type OwnProps = { type OwnProps = {
logger: Logger; logger: Logger;
isSandy?: boolean;
}; };
type StateFromProps = { type StateFromProps = {
pluginState: Object;
activePlugin: ActivePluginListItem | null; activePlugin: ActivePluginListItem | null;
target: Client | BaseDevice | null; target: Client | BaseDevice | null;
pluginKey: string | null; pluginKey: string | null;
deepLinkPayload: unknown; deepLinkPayload: unknown;
selectedApp: string | null;
isArchivedDevice: boolean;
pendingMessages: Message[] | undefined; pendingMessages: Message[] | undefined;
settingsState: Settings;
latestInstalledVersion: InstalledPluginDetails | undefined; latestInstalledVersion: InstalledPluginDetails | undefined;
}; };
@@ -125,7 +106,6 @@ type DispatchFromProps = {
selectedApp?: string | null; selectedApp?: string | null;
deepLinkPayload: unknown; deepLinkPayload: unknown;
}) => any; }) => any;
setPluginState: (payload: {pluginKey: string; state: any}) => void;
setStaticView: (payload: StaticView) => void; setStaticView: (payload: StaticView) => void;
enablePlugin: typeof switchPlugin; enablePlugin: typeof switchPlugin;
loadPlugin: typeof loadPlugin; loadPlugin: typeof loadPlugin;
@@ -228,17 +208,13 @@ class PluginContainer extends PureComponent<Props, State> {
if ( if (
target instanceof Client && target instanceof Client &&
activePlugin && activePlugin &&
(isSandyPlugin(activePlugin.definition) ||
activePlugin.definition.persistedStateReducer) &&
pluginKey && pluginKey &&
pendingMessages?.length pendingMessages?.length
) { ) {
const start = Date.now(); const start = Date.now();
this.idler = new IdlerImpl(); this.idler = new IdlerImpl();
processMessageQueue( processMessageQueue(
isSandyPlugin(activePlugin.definition) target.sandyPluginStates.get(activePlugin.definition.id)!,
? target.sandyPluginStates.get(activePlugin.definition.id)!
: activePlugin.definition,
pluginKey, pluginKey,
this.store, this.store,
(progress) => { (progress) => {
@@ -360,18 +336,8 @@ class PluginContainer extends PureComponent<Props, State> {
} }
renderPlugin() { renderPlugin() {
const { const {activePlugin, pluginKey, target, latestInstalledVersion} =
pluginState, this.props;
setPluginState,
activePlugin,
pluginKey,
target,
isArchivedDevice,
selectedApp,
settingsState,
isSandy,
latestInstalledVersion,
} = this.props;
if ( if (
!activePlugin || !activePlugin ||
!target || !target ||
@@ -381,7 +347,6 @@ class PluginContainer extends PureComponent<Props, State> {
console.warn(`No selected plugin. Rendering empty!`); console.warn(`No selected plugin. Rendering empty!`);
return this.renderNoPluginActive(); return this.renderNoPluginActive();
} }
let pluginElement: null | React.ReactElement<any>;
const showUpdateAlert = const showUpdateAlert =
latestInstalledVersion && latestInstalledVersion &&
activePlugin && activePlugin &&
@@ -392,71 +357,14 @@ class PluginContainer extends PureComponent<Props, State> {
latestInstalledVersion.version, latestInstalledVersion.version,
activePlugin.definition.version, activePlugin.definition.version,
); );
if (isSandyPlugin(activePlugin.definition)) { // Make sure we throw away the container for different pluginKey!
// Make sure we throw away the container for different pluginKey! const instance = target.sandyPluginStates.get(activePlugin.definition.id);
const instance = target.sandyPluginStates.get(activePlugin.definition.id); if (!instance) {
if (!instance) { // happens if we selected a plugin that is not enabled on a specific app or not supported on a specific device.
// happens if we selected a plugin that is not enabled on a specific app or not supported on a specific device. return this.renderNoPluginActive();
return this.renderNoPluginActive();
}
pluginElement = (
<_SandyPluginRenderer key={pluginKey} plugin={instance} />
);
} else {
const props: PluginProps<Object> & {
key: string;
ref: (
ref:
| FlipperPlugin<any, any, any>
| FlipperDevicePlugin<any, any, any>
| null
| undefined,
) => void;
} = {
key: pluginKey,
logger: this.props.logger,
selectedApp,
persistedState: activePlugin.definition.defaultPersistedState
? {
...activePlugin.definition.defaultPersistedState,
...pluginState,
}
: pluginState,
setStaticView: (payload: StaticView) =>
this.props.setStaticView(payload),
setPersistedState: (state) => setPluginState({pluginKey, state}),
target,
deepLinkPayload: this.props.deepLinkPayload,
selectPlugin: (pluginID: string, deepLinkPayload: unknown) => {
const {target} = this.props;
// check if plugin will be available
if (target instanceof Client && target.plugins.has(pluginID)) {
this.props.selectPlugin({
selectedPlugin: pluginID,
deepLinkPayload,
});
return true;
} else if (target instanceof BaseDevice) {
this.props.selectPlugin({
selectedPlugin: pluginID,
deepLinkPayload,
});
return true;
} else {
return false;
}
},
ref: this.refChanged,
isArchivedDevice,
settingsState,
};
pluginElement = (
<TrackingScope scope={'plugin:' + activePlugin.definition.id}>
{React.createElement(activePlugin.definition, props)}
</TrackingScope>
);
} }
return isSandy ? (
return (
<Layout.Top> <Layout.Top>
<div> <div>
{showUpdateAlert && ( {showUpdateAlert && (
@@ -490,23 +398,13 @@ class PluginContainer extends PureComponent<Props, State> {
heading={`Plugin "${ heading={`Plugin "${
activePlugin.definition.title || 'Unknown' activePlugin.definition.title || 'Unknown'
}" encountered an error during render`}> }" encountered an error during render`}>
<ContentContainer>{pluginElement}</ContentContainer> <ContentContainer>
<_SandyPluginRenderer key={pluginKey} plugin={instance} />
</ContentContainer>
</ErrorBoundary> </ErrorBoundary>
<SidebarContainer id="detailsSidebar" /> <SidebarContainer id="detailsSidebar" />
</Layout.Right> </Layout.Right>
</Layout.Top> </Layout.Top>
) : (
<React.Fragment>
<Container key="plugin">
<ErrorBoundary
heading={`Plugin "${
activePlugin.definition.title || 'Unknown'
}" encountered an error during render`}>
{pluginElement}
</ErrorBoundary>
</Container>
<SidebarContainer id="detailsSidebar" />
</React.Fragment>
); );
} }
} }
@@ -516,11 +414,9 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
let pluginKey: string | null = null; let pluginKey: string | null = null;
let target: BaseDevice | Client | null = null; let target: BaseDevice | Client | null = null;
const { const {
connections: {selectedDevice, selectedApp, deepLinkPayload}, connections: {selectedDevice, deepLinkPayload},
pluginStates,
plugins: {installedPlugins}, plugins: {installedPlugins},
pluginMessageQueue, pluginMessageQueue,
settingsState,
} = state; } = state;
const selectedClient = getActiveClient(state); const selectedClient = getActiveClient(state);
const activePlugin = getActivePlugin(state); const activePlugin = getActivePlugin(state);
@@ -536,24 +432,17 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
pluginKey = getPluginKey(selectedClient.id, activePlugin.details.id); pluginKey = getPluginKey(selectedClient.id, activePlugin.details.id);
} }
} }
const isArchivedDevice = !selectedDevice
? false
: selectedDevice.isArchived;
const pendingMessages = pluginKey const pendingMessages = pluginKey
? pluginMessageQueue[pluginKey] ? pluginMessageQueue[pluginKey]
: undefined; : undefined;
const s: StateFromProps = { const s: StateFromProps = {
pluginState: pluginStates[pluginKey as string],
activePlugin, activePlugin,
target, target,
deepLinkPayload, deepLinkPayload,
pluginKey, pluginKey,
isArchivedDevice,
selectedApp: selectedApp || null,
pendingMessages, pendingMessages,
settingsState,
latestInstalledVersion: installedPlugins.get( latestInstalledVersion: installedPlugins.get(
activePlugin?.details?.name ?? '', activePlugin?.details?.name ?? '',
), ),
@@ -561,7 +450,6 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
return s; return s;
}, },
{ {
setPluginState,
selectPlugin, selectPlugin,
setStaticView, setStaticView,
enablePlugin: switchPlugin, enablePlugin: switchPlugin,

View File

@@ -72,22 +72,31 @@ test('Plugin container can render plugin and receive updates', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<h1> <div />
Hello: <div
class="css-1knrt0j-SandySplitContainer e1hsqii10"
<span >
data-testid="counter" <div
class="css-1woty6b-Container"
> >
0 <h1>
</span> Hello:
</h1>
<span
data-testid="counter"
>
0
</span>
</h1>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -164,17 +173,26 @@ test('PluginContainer can render Sandy plugins', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<div> <div />
Hello from Sandy <div
0 class="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div
class="css-1woty6b-Container"
>
<div>
Hello from Sandy
0
</div>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -195,17 +213,26 @@ test('PluginContainer can render Sandy plugins', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<div> <div />
Hello from Sandy <div
2 class="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div
class="css-1woty6b-Container"
>
<div>
Hello from Sandy
2
</div>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -262,17 +289,26 @@ test('PluginContainer can render Sandy plugins', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<div> <div />
Hello from Sandy <div
9 class="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div
class="css-1woty6b-Container"
>
<div>
Hello from Sandy
9
</div>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -501,17 +537,26 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<h1> <div />
hello <div
world class="css-1knrt0j-SandySplitContainer e1hsqii10"
</h1> >
<div
class="css-1woty6b-Container"
>
<h1>
hello
world
</h1>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -532,17 +577,26 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<h1> <div />
hello <div
universe! class="css-1knrt0j-SandySplitContainer e1hsqii10"
</h1> >
<div
class="css-1woty6b-Container"
>
<h1>
hello
universe!
</h1>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -655,16 +709,25 @@ test('PluginContainer can render Sandy device plugins', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<div> <div />
Hello from Sandy: <div
class="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div
class="css-1woty6b-Container"
>
<div>
Hello from Sandy:
</div>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -686,17 +749,26 @@ test('PluginContainer can render Sandy device plugins', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<div> <div />
Hello from Sandy: <div
helleuh class="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div
class="css-1woty6b-Container"
>
<div>
Hello from Sandy:
helleuh
</div>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -778,17 +850,26 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<h1> <div />
hello <div
world class="css-1knrt0j-SandySplitContainer e1hsqii10"
</h1> >
<div
class="css-1woty6b-Container"
>
<h1>
hello
world
</h1>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -809,17 +890,26 @@ test('PluginContainer + Sandy device plugin supports deeplink', async () => {
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<h1> <div />
hello <div
{"thisIs":"theUniverse"} class="css-1knrt0j-SandySplitContainer e1hsqii10"
</h1> >
<div
class="css-1woty6b-Container"
>
<h1>
hello
{"thisIs":"theUniverse"}
</h1>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -1076,17 +1166,26 @@ test('PluginContainer can render Sandy plugins for archived devices', async () =
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<div> <div />
Hello from Sandy <div
0 class="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div
class="css-1woty6b-Container"
>
<div>
Hello from Sandy
0
</div>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);
@@ -1136,17 +1235,26 @@ test('PluginContainer can render Sandy plugins for archived devices', async () =
<body> <body>
<div> <div>
<div <div
class="css-w6yhx2-View-FlexBox-FlexColumn" class="css-1x2cmzz-SandySplitContainer e1hsqii10"
> >
<div> <div />
Hello from Sandy <div
0 class="css-1knrt0j-SandySplitContainer e1hsqii10"
>
<div
class="css-1woty6b-Container"
>
<div>
Hello from Sandy
0
</div>
</div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</div> </div>
<div
class="css-724x97-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div> </div>
</body> </body>
`); `);

View File

@@ -1,77 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`can create a Fake flipper 1`] = `
Object {
"androidEmulators": Array [],
"clients": Array [
Object {
"id": "TestApp#Android#MockAndroidDevice#serial",
"query": Object {
"app": "TestApp",
"device": "MockAndroidDevice",
"device_id": "serial",
"os": "Android",
"sdk_version": 4,
},
},
],
"deepLinkPayload": null,
"devices": Array [
Object {
"deviceType": "physical",
"os": "Android",
"serial": "serial",
"title": "MockAndroidDevice",
},
],
"enabledDevicePlugins": Set {
"DeviceLogs",
"CrashReporter",
"MobileBuilds",
"Hermesdebuggerrn",
"React",
},
"enabledPlugins": Object {
"TestApp": Array [
"TestPlugin",
],
},
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
"selectedAppPluginListRevision": 0,
"selectedDevice": Object {
"deviceType": "physical",
"os": "Android",
"serial": "serial",
"title": "MockAndroidDevice",
},
"selectedPlugin": "TestPlugin",
"staticView": null,
"uninitializedClients": Array [],
"userPreferredApp": "TestApp#Android#MockAndroidDevice#serial",
"userPreferredDevice": "MockAndroidDevice",
"userPreferredPlugin": "TestPlugin",
}
`;
exports[`can create a Fake flipper 2`] = `
Object {
"bundledPlugins": Map {},
"clientPlugins": Map {
"TestPlugin" => [Function],
},
"devicePlugins": Map {},
"disabledPlugins": Array [],
"failedPlugins": Array [],
"gatekeepedPlugins": Array [],
"initialised": false,
"installedPlugins": Map {},
"loadedPlugins": Map {},
"marketplacePlugins": Array [],
"selectedPlugins": Array [],
"uninstalledPluginNames": Set {},
}
`;
exports[`can create a Fake flipper with legacy wrapper 1`] = ` exports[`can create a Fake flipper with legacy wrapper 1`] = `
Object { Object {
"androidEmulators": Array [], "androidEmulators": Array [],

View File

@@ -40,26 +40,6 @@ class TestPlugin extends FlipperPlugin<any, any, any> {
} }
} }
test('can create a Fake flipper', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin, {disableLegacyWrapper: true});
expect(client).toBeTruthy();
expect(device).toBeTruthy();
expect(store).toBeTruthy();
expect(sendMessage).toBeTruthy();
expect(client.plugins.has(TestPlugin.id)).toBe(true);
expect(store.getState().connections).toMatchSnapshot();
expect(store.getState().plugins).toMatchSnapshot();
sendMessage('inc', {});
expect(store.getState().pluginStates).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
"count": 1,
},
}
`);
});
const testIdler = new TestIdler(); const testIdler = new TestIdler();
function testOnStatusMessage() { function testOnStatusMessage() {

View File

@@ -1,139 +0,0 @@
/**
* 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 React from 'react';
import {create, act, ReactTestRenderer} from 'react-test-renderer';
import {Provider} from 'react-redux';
import ExportDataPluginSheet from '../ExportDataPluginSheet';
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
import {getPluginKey} from '../../utils/pluginUtils';
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
import {setPluginState} from '../../reducers/pluginStates';
import {getExportablePlugins} from '../../selectors/connections';
class TestPlugin extends FlipperPlugin<any, any, any> {
static details = {
title: 'TestPlugin',
id: 'TestPlugin',
} as any;
}
TestPlugin.title = 'TestPlugin';
TestPlugin.id = 'TestPlugin';
TestPlugin.defaultPersistedState = {msg: 'Test plugin'};
class TestDevicePlugin extends FlipperDevicePlugin<any, any, any> {
static details = {
title: 'TestDevicePlugin',
id: 'TestDevicePlugin',
} as any;
static supportsDevice() {
return true;
}
}
TestDevicePlugin.title = 'TestDevicePlugin';
TestDevicePlugin.id = 'TestDevicePlugin';
TestDevicePlugin.defaultPersistedState = {msg: 'TestDevicePlugin'};
test('SettingsSheet snapshot with nothing enabled', async () => {
let root: ReactTestRenderer;
const {store, togglePlugin, client, device, pluginKey} =
await createMockFlipperWithPlugin(TestPlugin, {
additionalPlugins: [TestDevicePlugin],
});
togglePlugin();
store.dispatch(
setPluginState({
pluginKey,
state: {test: '1'},
}),
);
expect(getExportablePlugins(store.getState())).toEqual([]);
// makes device plugin visible
store.dispatch(
setPluginState({
pluginKey: getPluginKey(undefined, device, 'TestDevicePlugin'),
state: {test: '1'},
}),
);
expect(getExportablePlugins(store.getState())).toEqual([
{
id: 'TestDevicePlugin',
label: 'TestDevicePlugin',
},
]);
await act(async () => {
root = create(
<Provider store={store}>
<ExportDataPluginSheet onHide={() => {}} />
</Provider>,
);
});
expect(root!.toJSON()).toMatchSnapshot();
});
test('SettingsSheet snapshot with one plugin enabled', async () => {
let root: ReactTestRenderer;
const {store, device, pluginKey} = await createMockFlipperWithPlugin(
TestPlugin,
{
additionalPlugins: [TestDevicePlugin],
},
);
// enabled
// in Sandy wrapper, a plugin is either persistable or not, but it doesn't depend on the current state.
// So this plugin will show up, even though its state is still the default
expect(getExportablePlugins(store.getState())).toEqual([
{
id: 'TestPlugin',
label: 'TestPlugin',
},
]);
store.dispatch(
setPluginState({
pluginKey,
state: {test: '1'},
}),
);
store.dispatch(
setPluginState({
pluginKey: getPluginKey(undefined, device, 'TestDevicePlugin'),
state: {test: '1'},
}),
);
expect(getExportablePlugins(store.getState())).toEqual([
{
id: 'TestDevicePlugin',
label: 'TestDevicePlugin',
},
{
id: 'TestPlugin',
label: 'TestPlugin',
},
]);
await act(async () => {
root = create(
<Provider store={store}>
<ExportDataPluginSheet onHide={() => {}} />
</Provider>,
);
});
expect(root!.toJSON()).toMatchSnapshot();
});

View File

@@ -1,252 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SettingsSheet snapshot with nothing enabled 1`] = `
<div
className="css-p0wmbe-View-FlexBox-FlexColumn"
>
<div
className="css-1edwc8r-View-FlexBox-FlexColumn"
>
<div
className="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
>
<span
className="css-meh2qi-Text"
>
Select the plugins for which you want to export the data
</span>
<div
className="css-1houjzq-View-FlexBox-FlexColumn"
>
<div
className="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
>
<div
className="css-auhar3-TooltipContainer e1abcqbd0"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<div
className="css-1jrm6r3"
>
<div
className="css-wospjg-View-FlexBox-FlexRow epz0qe20"
style={
Object {
"alignItems": "center",
}
}
>
<span
className="css-xsnw23-Text e19o3fcp0"
>
TestDevicePlugin
</span>
<div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
/>
<input
checked={false}
className="css-1pxrk7-CheckboxContainer e28aqfo0"
disabled={false}
onChange={[Function]}
type="checkbox"
/>
</div>
</div>
<div
className="css-1p0wwd3-View"
/>
</div>
</div>
</div>
</div>
<div
className="css-1yqvjo0"
>
<div
className="css-wospjg-View-FlexBox-FlexRow epz0qe20"
>
<div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
/>
<div
className="css-12n892b"
>
<button
className="ant-btn ant-btn-default"
onClick={[Function]}
type="button"
>
<span>
Close
</span>
</button>
</div>
<div
className="css-auhar3-TooltipContainer e1abcqbd0"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
className="ant-btn ant-btn-primary"
disabled={true}
onClick={[Function]}
type="button"
>
<span>
Submit
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;
exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
<div
className="css-p0wmbe-View-FlexBox-FlexColumn"
>
<div
className="css-1edwc8r-View-FlexBox-FlexColumn"
>
<div
className="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
>
<span
className="css-meh2qi-Text"
>
Select the plugins for which you want to export the data
</span>
<div
className="css-1houjzq-View-FlexBox-FlexColumn"
>
<div
className="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
>
<div
className="css-auhar3-TooltipContainer e1abcqbd0"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<div
className="css-1jrm6r3"
>
<div
className="css-wospjg-View-FlexBox-FlexRow epz0qe20"
style={
Object {
"alignItems": "center",
}
}
>
<span
className="css-xsnw23-Text e19o3fcp0"
>
TestDevicePlugin
</span>
<div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
/>
<input
checked={false}
className="css-1pxrk7-CheckboxContainer e28aqfo0"
disabled={false}
onChange={[Function]}
type="checkbox"
/>
</div>
</div>
<div
className="css-1p0wwd3-View"
/>
</div>
</div>
<div
className="css-18abd42-View-FlexBox-FlexColumn ecr18to0"
>
<div
className="css-auhar3-TooltipContainer e1abcqbd0"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<div
className="css-1jrm6r3"
>
<div
className="css-wospjg-View-FlexBox-FlexRow epz0qe20"
style={
Object {
"alignItems": "center",
}
}
>
<span
className="css-xsnw23-Text e19o3fcp0"
>
TestPlugin
</span>
<div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
/>
<input
checked={false}
className="css-1pxrk7-CheckboxContainer e28aqfo0"
disabled={false}
onChange={[Function]}
type="checkbox"
/>
</div>
</div>
<div
className="css-1p0wwd3-View"
/>
</div>
</div>
</div>
</div>
<div
className="css-1yqvjo0"
>
<div
className="css-wospjg-View-FlexBox-FlexRow epz0qe20"
>
<div
className="css-t4wmtk-View-FlexBox-Spacer e13mj6h80"
/>
<div
className="css-12n892b"
>
<button
className="ant-btn ant-btn-default"
onClick={[Function]}
type="button"
>
<span>
Close
</span>
</button>
</div>
<div
className="css-auhar3-TooltipContainer e1abcqbd0"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
className="ant-btn ant-btn-primary"
disabled={true}
onClick={[Function]}
type="button"
>
<span>
Submit
</span>
</button>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -15,7 +15,7 @@ import {connect} from 'react-redux';
import {Text, ManagedTable, styled, colors, Link, Bordered} from '../../ui'; import {Text, ManagedTable, styled, colors, Link, Bordered} from '../../ui';
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'; import {PluginDefinition} from '../../plugin';
const InfoText = styled(Text)({ const InfoText = styled(Text)({
lineHeight: '130%', lineHeight: '130%',
@@ -43,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: DevicePluginDefinition[]; devicePlugins: PluginDefinition[];
clientPlugins: ClientPluginDefinition[]; clientPlugins: PluginDefinition[];
}; };
type DispatchFromProps = {}; type DispatchFromProps = {};

View File

@@ -18,11 +18,7 @@ import {
createState, createState,
_getFlipperLibImplementation, _getFlipperLibImplementation,
} from 'flipper-plugin'; } from 'flipper-plugin';
import { import {PluginDefinition, DevicePluginMap} from '../plugin';
DevicePluginDefinition,
DevicePluginMap,
FlipperDevicePlugin,
} from '../plugin';
import {DeviceSpec, OS as PluginOS, PluginDetails} from 'flipper-plugin-lib'; import {DeviceSpec, OS as PluginOS, PluginDetails} from 'flipper-plugin-lib';
export type DeviceShell = { export type DeviceShell = {
@@ -191,9 +187,9 @@ export default class BaseDevice {
return null; return null;
} }
supportsPlugin(plugin: DevicePluginDefinition | PluginDetails) { supportsPlugin(plugin: PluginDefinition | PluginDetails) {
let pluginDetails: PluginDetails; let pluginDetails: PluginDetails;
if (isDevicePluginDefinition(plugin)) { if (plugin instanceof _SandyPluginDefinition) {
pluginDetails = plugin.details; pluginDetails = plugin.details;
if (!pluginDetails.pluginType && !pluginDetails.supportedDevices) { if (!pluginDetails.pluginType && !pluginDetails.supportedDevices) {
// TODO T84453692: this branch is to support plugins defined with the legacy approach. Need to remove this branch after some transition period when // TODO T84453692: this branch is to support plugins defined with the legacy approach. Need to remove this branch after some transition period when
@@ -205,7 +201,7 @@ export default class BaseDevice {
false) false)
); );
} else { } else {
return plugin.supportsDevice(this); return (plugin as any).supportsDevice(this);
} }
} }
} else { } else {
@@ -240,7 +236,7 @@ export default class BaseDevice {
} }
} }
loadDevicePlugin(plugin: DevicePluginDefinition, initialState?: any) { loadDevicePlugin(plugin: PluginDefinition, initialState?: any) {
if (!this.supportsPlugin(plugin)) { if (!this.supportsPlugin(plugin)) {
return; return;
} }
@@ -286,12 +282,3 @@ export default class BaseDevice {
this.sandyPluginStates.clear(); this.sandyPluginStates.clear();
} }
} }
function isDevicePluginDefinition(
definition: any,
): definition is DevicePluginDefinition {
return (
(definition as any).prototype instanceof FlipperDevicePlugin ||
(definition instanceof _SandyPluginDefinition && definition.isDevicePlugin)
);
}

View File

@@ -10,16 +10,13 @@
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 {PluginDefinition} from '../plugin';
import {ipcRenderer, IpcRendererEvent} from 'electron'; import {ipcRenderer, IpcRendererEvent} from 'electron';
import { import {
setActiveNotifications,
updatePluginBlocklist, updatePluginBlocklist,
updateCategoryBlocklist, updateCategoryBlocklist,
} from '../reducers/notifications'; } from '../reducers/notifications';
import {textContent} from '../utils/index'; import {textContent} from '../utils/index';
import {deconstructPluginKey} from '../utils/clientUtils'; import {getPluginTitle} from '../utils/pluginUtils';
import {getPluginTitle, isSandyPlugin} from '../utils/pluginUtils';
import {sideEffect} from '../utils/sideEffect'; import {sideEffect} from '../utils/sideEffect';
import {openNotification} from '../sandy-chrome/notification/Notification'; import {openNotification} from '../sandy-chrome/notification/Notification';
@@ -28,7 +25,6 @@ const NOTIFICATION_THROTTLE = 5 * 1000; // in milliseconds
export default (store: Store, logger: Logger) => { export default (store: Store, logger: Logger) => {
const knownNotifications: Set<string> = new Set(); const knownNotifications: Set<string> = new Set();
const knownPluginStates: Map<string, Object> = new Map();
const lastNotificationTime: Map<string, number> = new Map(); const lastNotificationTime: Map<string, number> = new Map();
ipcRenderer.on( ipcRenderer.on(
@@ -78,56 +74,16 @@ export default (store: Store, logger: Logger) => {
sideEffect( sideEffect(
store, store,
{name: 'notifications', throttleMs: 500}, {name: 'notifications', throttleMs: 500},
({notifications, pluginStates, plugins}) => ({ ({notifications, plugins}) => ({
notifications, notifications,
pluginStates,
devicePlugins: plugins.devicePlugins, devicePlugins: plugins.devicePlugins,
clientPlugins: plugins.clientPlugins, clientPlugins: plugins.clientPlugins,
}), }),
({notifications, pluginStates, devicePlugins, clientPlugins}, store) => { ({notifications, devicePlugins, clientPlugins}, store) => {
function getPlugin(name: string) { function getPlugin(name: string) {
return devicePlugins.get(name) ?? clientPlugins.get(name); return devicePlugins.get(name) ?? clientPlugins.get(name);
} }
Object.keys(pluginStates).forEach((key) => {
if (knownPluginStates.get(key) !== pluginStates[key]) {
knownPluginStates.set(key, pluginStates[key]);
const plugin = deconstructPluginKey(key);
const pluginName = plugin.pluginName;
const client = plugin.client;
if (!pluginName) {
return;
}
const persistingPlugin: undefined | PluginDefinition =
getPlugin(pluginName);
if (
persistingPlugin &&
!isSandyPlugin(persistingPlugin) &&
persistingPlugin.getActiveNotifications
) {
try {
const notifications = persistingPlugin.getActiveNotifications(
pluginStates[key],
);
store.dispatch(
setActiveNotifications({
notifications,
client,
pluginId: pluginName,
}),
);
} catch (e) {
console.error(
'Failed to compute notifications for plugin ' + pluginName,
e,
);
}
}
}
});
const {activeNotifications, blocklistedPlugins, blocklistedCategories} = const {activeNotifications, blocklistedPlugins, blocklistedCategories} =
notifications; notifications;

View File

@@ -8,7 +8,6 @@
*/ */
import type {Store} from '../reducers/index'; import type {Store} from '../reducers/index';
import {clearPluginState} from '../reducers/pluginStates';
import type {Logger} from '../fb-interfaces/Logger'; import type {Logger} from '../fb-interfaces/Logger';
import { import {
LoadPluginActionPayload, LoadPluginActionPayload,
@@ -27,12 +26,7 @@ import {
import {sideEffect} from '../utils/sideEffect'; import {sideEffect} from '../utils/sideEffect';
import {requirePlugin} from './plugins'; import {requirePlugin} from './plugins';
import {showErrorNotification} from '../utils/notifications'; import {showErrorNotification} from '../utils/notifications';
import { import {PluginDefinition} from '../plugin';
ClientPluginDefinition,
DevicePluginDefinition,
FlipperPlugin,
PluginDefinition,
} from '../plugin';
import type Client from '../Client'; import type Client from '../Client';
import {unloadModule} from '../utils/electronModuleCache'; import {unloadModule} from '../utils/electronModuleCache';
import { import {
@@ -148,7 +142,6 @@ function uninstallPlugin(store: Store, {plugin}: UninstallPluginActionPayload) {
clients.forEach((client) => { clients.forEach((client) => {
stopPlugin(client, plugin.id); stopPlugin(client, plugin.id);
}); });
store.dispatch(clearPluginState({pluginId: plugin.id}));
if (!plugin.details.isBundled) { if (!plugin.details.isBundled) {
unloadPluginModule(plugin.details); unloadPluginModule(plugin.details);
} }
@@ -194,7 +187,7 @@ function switchPlugin(
function switchClientPlugin( function switchClientPlugin(
store: Store, store: Store,
plugin: ClientPluginDefinition, plugin: PluginDefinition,
selectedApp: string | undefined, selectedApp: string | undefined,
) { ) {
selectedApp = selectedApp ?? getSelectedAppId(store); selectedApp = selectedApp ?? getSelectedAppId(store);
@@ -224,7 +217,7 @@ function switchClientPlugin(
} }
} }
function switchDevicePlugin(store: Store, plugin: DevicePluginDefinition) { function switchDevicePlugin(store: Store, plugin: PluginDefinition) {
const {connections} = store.getState(); const {connections} = store.getState();
const devicesWithPlugin = connections.devices.filter((d) => const devicesWithPlugin = connections.devices.filter((d) =>
d.supportsPlugin(plugin.details), d.supportsPlugin(plugin.details),
@@ -244,7 +237,7 @@ function switchDevicePlugin(store: Store, plugin: DevicePluginDefinition) {
function updateClientPlugin( function updateClientPlugin(
store: Store, store: Store,
plugin: typeof FlipperPlugin, plugin: PluginDefinition,
enable: boolean, enable: boolean,
) { ) {
const clients = store.getState().connections.clients; const clients = store.getState().connections.clients;
@@ -266,7 +259,6 @@ function updateClientPlugin(
clientsWithEnabledPlugin.forEach((client) => { clientsWithEnabledPlugin.forEach((client) => {
stopPlugin(client, plugin.id); stopPlugin(client, plugin.id);
}); });
store.dispatch(clearPluginState({pluginId: plugin.id}));
clientsWithEnabledPlugin.forEach((client) => { clientsWithEnabledPlugin.forEach((client) => {
startPlugin(client, plugin, true); startPlugin(client, plugin, true);
}); });
@@ -279,7 +271,7 @@ function updateClientPlugin(
function updateDevicePlugin( function updateDevicePlugin(
store: Store, store: Store,
plugin: DevicePluginDefinition, plugin: PluginDefinition,
enable: boolean, enable: boolean,
) { ) {
if (enable) { if (enable) {
@@ -292,7 +284,6 @@ function updateDevicePlugin(
devicesWithEnabledPlugin.forEach((d) => { devicesWithEnabledPlugin.forEach((d) => {
d.unloadDevicePlugin(plugin.id); d.unloadDevicePlugin(plugin.id);
}); });
store.dispatch(clearPluginState({pluginId: plugin.id}));
const previousVersion = store.getState().plugins.devicePlugins.get(plugin.id); const previousVersion = store.getState().plugins.devicePlugins.get(plugin.id);
if (previousVersion) { if (previousVersion) {
// unload previous version from Electron cache // unload previous version from Electron cache

View File

@@ -39,8 +39,9 @@ export {default as constants} from './fb-stubs/constants';
export {connect} from 'react-redux'; export {connect} from 'react-redux';
export {selectPlugin, StaticView} from './reducers/connections'; export {selectPlugin, StaticView} from './reducers/connections';
export {writeBufferToFile, bufferToBlob} from './utils/screenshot'; export {writeBufferToFile, bufferToBlob} from './utils/screenshot';
export {getPluginKey, getPersistedState} from './utils/pluginUtils'; export {getPluginKey} from './utils/pluginUtils';
export {Idler, Notification} from 'flipper-plugin'; export {Notification, Idler} from 'flipper-plugin';
export {IdlerImpl} from './utils/Idler';
export {Store, MiddlewareAPI, State as ReduxState} from './reducers/index'; export {Store, MiddlewareAPI, State as ReduxState} from './reducers/index';
export {default as BaseDevice} from './devices/BaseDevice'; export {default as BaseDevice} from './devices/BaseDevice';
export {DeviceLogEntry, LogLevel, DeviceLogListener} from 'flipper-plugin'; export {DeviceLogEntry, LogLevel, DeviceLogListener} from 'flipper-plugin';

View File

@@ -27,18 +27,10 @@ import {
type Parameters = {[key: string]: any}; type Parameters = {[key: string]: any};
export type PluginDefinition = ClientPluginDefinition | DevicePluginDefinition; export type PluginDefinition = _SandyPluginDefinition;
export type DevicePluginDefinition = export type ClientPluginMap = Map<string, PluginDefinition>;
| typeof FlipperDevicePlugin export type DevicePluginMap = Map<string, PluginDefinition>;
| _SandyPluginDefinition;
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
@@ -211,6 +203,10 @@ export abstract class FlipperBasePlugin<
} }
} }
/**
* @deprecated Please use the newer "Sandy" plugin APIs!
* https://fbflipper.com/docs/extending/sandy-migration
*/
export class FlipperDevicePlugin< export class FlipperDevicePlugin<
S, S,
A extends BaseAction, A extends BaseAction,
@@ -240,6 +236,10 @@ export class FlipperDevicePlugin<
} }
} }
/**
* @deprecated Please use the newer "Sandy" plugin APIs!
* https://fbflipper.com/docs/extending/sandy-migration
*/
export class FlipperPlugin< export class FlipperPlugin<
S, S,
A extends BaseAction, A extends BaseAction,

View File

@@ -13,7 +13,17 @@ import BaseDevice from '../../devices/BaseDevice';
import MacDevice from '../../devices/MacDevice'; import MacDevice from '../../devices/MacDevice';
import {FlipperDevicePlugin} from '../../plugin'; import {FlipperDevicePlugin} from '../../plugin';
import MetroDevice from '../../devices/MetroDevice'; import MetroDevice from '../../devices/MetroDevice';
import {TestUtils} from 'flipper-plugin'; import {TestUtils, _setFlipperLibImplementation} from 'flipper-plugin';
import {wrapSandy} from '../../test-utils/createMockFlipperWithPlugin';
import {createMockFlipperLib} from 'flipper-plugin/src/test-utils/test-utils';
beforeEach(() => {
_setFlipperLibImplementation(createMockFlipperLib());
});
afterEach(() => {
_setFlipperLibImplementation(undefined);
});
test('doing a double REGISTER_DEVICE keeps the last', () => { test('doing a double REGISTER_DEVICE keeps the last', () => {
const device1 = new BaseDevice('serial', 'physical', 'title', 'Android'); const device1 = new BaseDevice('serial', 'physical', 'title', 'Android');

View File

@@ -1,33 +0,0 @@
/**
* 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 {default as reducer, setPluginState, Action} from '../pluginStates';
test('reduce setPluginState', () => {
const result = reducer(
{},
setPluginState({pluginKey: 'myPlugin', state: {a: 1}}),
);
expect(result).toEqual({myPlugin: {a: 1}});
});
test('CLEAR_CLIENT_PLUGINS_STATE removes plugin state', () => {
const clientId = 'app1#device1';
const pluginKey = 'app1#device1#plugin1';
const action: Action = {
type: 'CLEAR_CLIENT_PLUGINS_STATE',
payload: {clientId: clientId, devicePlugins: new Set()},
};
const result = reducer(
{[pluginKey]: {a: 1}, 'anotherPlugin#key': {b: 2}},
action,
);
expect(result).toEqual({'anotherPlugin#key': {b: 2}});
});

View File

@@ -15,18 +15,21 @@ import {
} from '../plugins'; } from '../plugins';
import {FlipperPlugin, FlipperDevicePlugin, BaseAction} from '../../plugin'; import {FlipperPlugin, FlipperDevicePlugin, BaseAction} from '../../plugin';
import {InstalledPluginDetails} from 'flipper-plugin-lib'; import {InstalledPluginDetails} from 'flipper-plugin-lib';
import {wrapSandy} from '../../test-utils/createMockFlipperWithPlugin';
const testPlugin = class extends FlipperPlugin<any, BaseAction, any> { const testPluginOrig = class extends FlipperPlugin<any, BaseAction, any> {
static id = 'TestPlugin'; static id = 'TestPlugin';
}; };
const testPlugin = wrapSandy(testPluginOrig);
const testDevicePlugin = class extends FlipperDevicePlugin< const testDevicePluginOrig = class extends FlipperDevicePlugin<
any, any,
BaseAction, BaseAction,
any any
> { > {
static id = 'TestDevicePlugin'; static id = 'TestDevicePlugin';
}; };
const testDevicePlugin = wrapSandy(testDevicePluginOrig);
test('add clientPlugin', () => { test('add clientPlugin', () => {
const res = reducer( const res = reducer(

View File

@@ -18,10 +18,6 @@ import connections, {
persistMigrations as devicesPersistMigrations, persistMigrations as devicesPersistMigrations,
persistVersion as devicesPersistVersion, persistVersion as devicesPersistVersion,
} from './connections'; } from './connections';
import pluginStates, {
State as PluginStatesState,
Action as PluginStatesAction,
} from './pluginStates';
import pluginMessageQueue, { import pluginMessageQueue, {
State as PluginMessageQueueState, State as PluginMessageQueueState,
Action as PluginMessageQueueAction, Action as PluginMessageQueueAction,
@@ -81,7 +77,6 @@ import {TransformConfig} from 'redux-persist/es/createTransform';
export type Actions = export type Actions =
| ApplicationAction | ApplicationAction
| DevicesAction | DevicesAction
| PluginStatesAction
| PluginMessageQueueAction | PluginMessageQueueAction
| NotificationsAction | NotificationsAction
| PluginsAction | PluginsAction
@@ -98,7 +93,6 @@ export type Actions =
export type State = { export type State = {
application: ApplicationState; application: ApplicationState;
connections: DevicesState & PersistPartial; connections: DevicesState & PersistPartial;
pluginStates: PluginStatesState;
pluginMessageQueue: PluginMessageQueueState; pluginMessageQueue: PluginMessageQueueState;
notifications: NotificationsState & PersistPartial; notifications: NotificationsState & PersistPartial;
plugins: PluginsState & PersistPartial; plugins: PluginsState & PersistPartial;
@@ -158,7 +152,6 @@ export function createRootReducer() {
}, },
connections, connections,
), ),
pluginStates,
pluginMessageQueue: pluginMessageQueue as any, pluginMessageQueue: pluginMessageQueue as any,
notifications: persistReducer( notifications: persistReducer(
{ {

View File

@@ -1,97 +0,0 @@
/**
* 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 {produce} from 'immer';
import {Actions} from '.';
import {deconstructPluginKey} from '../utils/clientUtils';
export type State = {
[pluginKey: string]: any;
};
export const pluginKey = (serial: string, pluginName: string): string => {
return `${serial}#${pluginName}`;
};
export type Action =
| {
type: 'SET_PLUGIN_STATE';
payload: {
pluginKey: string;
state: Object;
};
}
| {
type: 'CLEAR_CLIENT_PLUGINS_STATE';
payload: {clientId: string; devicePlugins: Set<string>};
}
| {
type: 'CLEAR_PLUGIN_STATE';
payload: {pluginId: string};
};
export default function reducer(
state: State | undefined = {},
action: Actions,
): State {
if (action.type === 'SET_PLUGIN_STATE') {
const newPluginState = action.payload.state;
if (newPluginState && newPluginState !== state[action.payload.pluginKey]) {
return {
...state,
[action.payload.pluginKey]: {
...state[action.payload.pluginKey],
...newPluginState,
},
};
}
return {...state};
} else if (action.type === 'CLEAR_CLIENT_PLUGINS_STATE') {
const {payload} = action;
return Object.keys(state).reduce((newState: State, pluginKey) => {
// Only add the pluginState, if its from a plugin other than the one that
// was removed. pluginKeys are in the form of ${clientID}#${pluginID}.
const plugin = deconstructPluginKey(pluginKey);
const clientId = plugin.client;
const pluginId = plugin.pluginName;
if (
clientId !== payload.clientId ||
(pluginId && payload.devicePlugins.has(pluginId))
) {
newState[pluginKey] = state[pluginKey];
}
return newState;
}, {});
} else if (action.type === 'CLEAR_PLUGIN_STATE') {
const {pluginId} = action.payload;
return produce(state, (draft) => {
Object.keys(draft).forEach((pluginKey) => {
const pluginKeyParts = deconstructPluginKey(pluginKey);
if (pluginKeyParts.pluginName === pluginId) {
delete draft[pluginKey];
}
});
});
} else {
return state;
}
}
export const setPluginState = (payload: {
pluginKey: string;
state: Object;
}): Action => ({
type: 'SET_PLUGIN_STATE',
payload,
});
export const clearPluginState = (payload: {pluginId: string}): Action => ({
type: 'CLEAR_PLUGIN_STATE',
payload,
});

View File

@@ -169,7 +169,7 @@ export function SandyApp() {
)} )}
</TrackingScope> </TrackingScope>
) : ( ) : (
<PluginContainer logger={logger} isSandy /> <PluginContainer logger={logger} />
)} )}
{outOfContentsContainer} {outOfContentsContainer}
</MainContainer> </MainContainer>

View File

@@ -108,10 +108,9 @@ export const getPluginLists = createSelector(
); );
export const getExportablePlugins = createSelector( export const getExportablePlugins = createSelector(
({plugins, connections, pluginStates, pluginMessageQueue}: State) => ({ ({plugins, connections, pluginMessageQueue}: State) => ({
plugins, plugins,
connections, connections,
pluginStates,
pluginMessageQueue, pluginMessageQueue,
}), }),
getActiveDevice, getActiveDevice,

View File

@@ -27,7 +27,7 @@ import {Store} from '../reducers/index';
import Client, {ClientQuery} from '../Client'; import Client, {ClientQuery} from '../Client';
import {Logger} from '../fb-interfaces/Logger'; import {Logger} from '../fb-interfaces/Logger';
import {FlipperDevicePlugin, PluginDefinition} from '../plugin'; import {FlipperDevicePlugin, FlipperPlugin, PluginDefinition} from '../plugin';
import PluginContainer from '../PluginContainer'; import PluginContainer from '../PluginContainer';
import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils'; import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils';
import MockFlipper from './MockFlipper'; import MockFlipper from './MockFlipper';
@@ -66,13 +66,12 @@ type MockOptions = Partial<{
* the base implementation will be used * the base implementation will be used
*/ */
onSend?: (pluginId: string, method: string, params?: object) => any; onSend?: (pluginId: string, method: string, params?: object) => any;
additionalPlugins?: PluginDefinition[]; additionalPlugins?: (PluginDefinition | LegacyPluginDefinition)[];
dontEnableAdditionalPlugins?: true; dontEnableAdditionalPlugins?: true;
asBackgroundPlugin?: true; asBackgroundPlugin?: true;
supportedPlugins?: string[]; supportedPlugins?: string[];
device?: BaseDevice; device?: BaseDevice;
archivedDevice?: boolean; archivedDevice?: boolean;
disableLegacyWrapper?: boolean;
}>; }>;
function isPluginEnabled( function isPluginEnabled(
@@ -90,34 +89,38 @@ function isPluginEnabled(
); );
} }
export type LegacyPluginDefinition =
| typeof FlipperDevicePlugin
| typeof FlipperPlugin;
export function wrapSandy(
clazz: PluginDefinition | LegacyPluginDefinition,
): PluginDefinition {
return clazz instanceof _SandyPluginDefinition
? clazz
: createSandyPluginFromClassicPlugin(
createMockActivatablePluginDetails({
id: clazz.id,
title: clazz.title ?? clazz.id,
pluginType:
clazz.prototype instanceof FlipperDevicePlugin
? 'device'
: 'client',
}),
clazz,
);
}
export async function createMockFlipperWithPlugin( export async function createMockFlipperWithPlugin(
pluginClazz: PluginDefinition, pluginClazzOrig: PluginDefinition | LegacyPluginDefinition,
options?: MockOptions, options?: MockOptions,
): Promise<MockFlipperResult> { ): Promise<MockFlipperResult> {
function wrapSandy(clazz: PluginDefinition) { const pluginClazz = wrapSandy(pluginClazzOrig);
return clazz instanceof _SandyPluginDefinition || const additionalPlugins = options?.additionalPlugins?.map(wrapSandy) ?? [];
options?.disableLegacyWrapper
? clazz
: createSandyPluginFromClassicPlugin(
createMockActivatablePluginDetails({
id: clazz.id,
title: clazz.title ?? clazz.id,
pluginType:
clazz.prototype instanceof FlipperDevicePlugin
? 'device'
: 'client',
}),
clazz,
);
}
pluginClazz = wrapSandy(pluginClazz);
const mockFlipper = new MockFlipper(); const mockFlipper = new MockFlipper();
await mockFlipper.init({ await mockFlipper.init({
plugins: [ plugins: [pluginClazz, ...additionalPlugins],
pluginClazz,
...(options?.additionalPlugins?.map(wrapSandy) ?? []),
],
}); });
const logger = mockFlipper.logger; const logger = mockFlipper.logger;
const store = mockFlipper.store; const store = mockFlipper.store;
@@ -150,7 +153,7 @@ export async function createMockFlipperWithPlugin(
} }
if (!options?.dontEnableAdditionalPlugins) { if (!options?.dontEnableAdditionalPlugins) {
options?.additionalPlugins?.forEach((plugin) => { additionalPlugins.forEach((plugin) => {
if (!isPluginEnabled(store, plugin, name)) { if (!isPluginEnabled(store, plugin, name)) {
store.dispatch( store.dispatch(
switchPlugin({ switchPlugin({
@@ -246,7 +249,7 @@ export async function createMockFlipperWithPlugin(
type Renderer = RenderResult<typeof queries>; type Renderer = RenderResult<typeof queries>;
export async function renderMockFlipperWithPlugin( export async function renderMockFlipperWithPlugin(
pluginClazz: PluginDefinition, pluginClazzOrig: PluginDefinition | LegacyPluginDefinition,
options?: MockOptions, options?: MockOptions,
): Promise< ): Promise<
MockFlipperResult & { MockFlipperResult & {
@@ -254,6 +257,7 @@ export async function renderMockFlipperWithPlugin(
act: (cb: () => void) => void; act: (cb: () => void) => void;
} }
> { > {
const pluginClazz = wrapSandy(pluginClazzOrig);
const args = await createMockFlipperWithPlugin(pluginClazz, options); const args = await createMockFlipperWithPlugin(pluginClazz, options);
function selectTestPlugin(store: Store, client: Client) { function selectTestPlugin(store: Store, client: Client) {

View File

@@ -20,7 +20,10 @@ import {
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin'; import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
import {default as Client, ClientExport} from '../../Client'; import {default as Client, ClientExport} from '../../Client';
import {selectedPlugins, State as PluginsState} from '../../reducers/plugins'; import {selectedPlugins, State as PluginsState} from '../../reducers/plugins';
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin'; import {
createMockFlipperWithPlugin,
wrapSandy,
} from '../../test-utils/createMockFlipperWithPlugin';
import { import {
Notification, Notification,
TestUtils, TestUtils,
@@ -39,12 +42,16 @@ function testOnStatusMessage() {
// emtpy stub // emtpy stub
} }
class TestPlugin extends FlipperPlugin<any, any, any> {} class TestPluginOrig extends FlipperPlugin<any, any, any> {}
TestPlugin.title = 'TestPlugin'; TestPluginOrig.title = 'TestPlugin';
TestPlugin.id = 'TestPlugin'; TestPluginOrig.id = 'TestPlugin';
class TestDevicePlugin extends FlipperDevicePlugin<any, any, any> {} const TestPlugin = wrapSandy(TestPluginOrig);
TestDevicePlugin.title = 'TestDevicePlugin';
TestDevicePlugin.id = 'TestDevicePlugin'; class TestDevicePluginOrig extends FlipperDevicePlugin<any, any, any> {}
TestDevicePluginOrig.title = 'TestDevicePlugin';
TestDevicePluginOrig.id = 'TestDevicePlugin';
const TestDevicePlugin = wrapSandy(TestDevicePluginOrig);
const logger = { const logger = {
track: () => {}, track: () => {},
info: () => {}, info: () => {},
@@ -193,7 +200,6 @@ test('test processStore function for empty state', async () => {
processStore({ processStore({
activeNotifications: [], activeNotifications: [],
device: null, device: null,
pluginStates: {},
clients: [], clients: [],
devicePlugins: new Map(), devicePlugins: new Map(),
clientPlugins: new Map(), clientPlugins: new Map(),
@@ -216,7 +222,6 @@ test('test processStore function for an iOS device connected', async () => {
os: 'iOS', os: 'iOS',
screenshotHandle: null, screenshotHandle: null,
}), }),
pluginStates: {},
pluginStates2: {}, pluginStates2: {},
clients: [], clients: [],
devicePlugins: new Map(), devicePlugins: new Map(),
@@ -238,8 +243,8 @@ test('test processStore function for an iOS device connected', async () => {
expect(deviceType).toEqual('emulator'); expect(deviceType).toEqual('emulator');
expect(title).toEqual('TestiPhone'); expect(title).toEqual('TestiPhone');
expect(os).toEqual('iOS'); expect(os).toEqual('iOS');
const {pluginStates, activeNotifications} = json.store; const {activeNotifications} = json.store;
expect(pluginStates).toEqual({}); expect(json.pluginStates2).toEqual({});
expect(activeNotifications).toEqual([]); expect(activeNotifications).toEqual([]);
}); });
@@ -256,11 +261,11 @@ test('test processStore function for an iOS device connected with client plugin
const json = await processStore({ const json = await processStore({
activeNotifications: [], activeNotifications: [],
device, device,
pluginStates: {
[`${clientIdentifier}#TestPlugin`]: {msg: 'Test plugin'},
},
pluginStates2: { pluginStates2: {
[`${clientIdentifier}`]: {TestPlugin2: [{msg: 'Test plugin2'}]}, [clientIdentifier]: {
TestPlugin2: [{msg: 'Test plugin2'}],
TestPlugin: {msg: 'Test plugin'},
},
}, },
clients: [client], clients: [client],
devicePlugins: new Map(), devicePlugins: new Map(),
@@ -271,25 +276,16 @@ test('test processStore function for an iOS device connected with client plugin
if (!json) { if (!json) {
fail('json is undefined'); fail('json is undefined');
} }
const {pluginStates} = json.store;
const expectedPluginState = {
[`${generateClientIdentifierWithSalt(
clientIdentifier,
'salt',
)}#TestPlugin`]: JSON.stringify({
msg: 'Test plugin',
}),
};
const expectedPluginState2 = { const expectedPluginState2 = {
[`${generateClientIdentifierWithSalt(clientIdentifier, 'salt')}`]: { [generateClientIdentifierWithSalt(clientIdentifier, 'salt')]: {
TestPlugin2: [ TestPlugin2: [
{ {
msg: 'Test plugin2', msg: 'Test plugin2',
}, },
], ],
TestPlugin: {msg: 'Test plugin'},
}, },
}; };
expect(pluginStates).toEqual(expectedPluginState);
expect(json.pluginStates2).toEqual(expectedPluginState2); expect(json.pluginStates2).toEqual(expectedPluginState2);
}); });
@@ -324,15 +320,18 @@ test('test processStore function to have only the client for the selected device
const json = await processStore({ const json = await processStore({
activeNotifications: [], activeNotifications: [],
device: selectedDevice, device: selectedDevice,
pluginStates: { pluginStates2: {
[unselectedDeviceClientIdentifier + '#TestDevicePlugin']: { [unselectedDeviceClientIdentifier]: {
msg: 'Test plugin unselected device', TestDevicePlugin: {
msg: 'Test plugin unselected device',
},
}, },
[selectedDeviceClientIdentifier + '#TestDevicePlugin']: { [selectedDeviceClientIdentifier]: {
msg: 'Test plugin selected device', TestDevicePlugin: {
msg: 'Test plugin selected device',
},
}, },
}, },
pluginStates2: {},
clients: [ clients: [
selectedDeviceClient, selectedDeviceClient,
generateClientFromDevice(unselectedDevice, 'testapp'), generateClientFromDevice(unselectedDevice, 'testapp'),
@@ -347,17 +346,18 @@ test('test processStore function to have only the client for the selected device
fail('json is undefined'); fail('json is undefined');
} }
const {clients} = json; const {clients} = json;
const {pluginStates} = json.store;
const expectedPluginState = { const expectedPluginState = {
[generateClientIdentifierWithSalt(selectedDeviceClientIdentifier, 'salt') + [generateClientIdentifierWithSalt(selectedDeviceClientIdentifier, 'salt')]:
'#TestDevicePlugin']: JSON.stringify({ {
msg: 'Test plugin selected device', TestDevicePlugin: {
}), msg: 'Test plugin selected device',
},
},
}; };
expect(clients).toEqual([ expect(clients).toEqual([
generateClientFromClientWithSalt(selectedDeviceClient, 'salt'), generateClientFromClientWithSalt(selectedDeviceClient, 'salt'),
]); ]);
expect(pluginStates).toEqual(expectedPluginState); expect(json.pluginStates2).toEqual(expectedPluginState);
}); });
test('test processStore function to have multiple clients for the selected device', async () => { test('test processStore function to have multiple clients for the selected device', async () => {
@@ -384,15 +384,18 @@ test('test processStore function to have multiple clients for the selected devic
const json = await processStore({ const json = await processStore({
activeNotifications: [], activeNotifications: [],
device: selectedDevice, device: selectedDevice,
pluginStates: { pluginStates2: {
[clientIdentifierApp1 + '#TestPlugin']: { [clientIdentifierApp1]: {
msg: 'Test plugin App1', TestPlugin: {
msg: 'Test plugin App1',
},
}, },
[clientIdentifierApp2 + '#TestPlugin']: { [clientIdentifierApp2]: {
msg: 'Test plugin App2', TestPlugin: {
msg: 'Test plugin App2',
},
}, },
}, },
pluginStates2: {},
clients: [ clients: [
generateClientFromDevice(selectedDevice, 'testapp1'), generateClientFromDevice(selectedDevice, 'testapp1'),
generateClientFromDevice(selectedDevice, 'testapp2'), generateClientFromDevice(selectedDevice, 'testapp2'),
@@ -407,22 +410,23 @@ test('test processStore function to have multiple clients for the selected devic
fail('json is undefined'); fail('json is undefined');
} }
const {clients} = json; const {clients} = json;
const {pluginStates} = json.store;
const expectedPluginState = { const expectedPluginState = {
[generateClientIdentifierWithSalt(clientIdentifierApp1, 'salt') + [generateClientIdentifierWithSalt(clientIdentifierApp1, 'salt')]: {
'#TestPlugin']: JSON.stringify({ TestPlugin: {
msg: 'Test plugin App1', msg: 'Test plugin App1',
}), },
[generateClientIdentifierWithSalt(clientIdentifierApp2, 'salt') + },
'#TestPlugin']: JSON.stringify({ [generateClientIdentifierWithSalt(clientIdentifierApp2, 'salt')]: {
msg: 'Test plugin App2', TestPlugin: {
}), msg: 'Test plugin App2',
},
},
}; };
expect(clients).toEqual([ expect(clients).toEqual([
generateClientFromClientWithSalt(client1, 'salt'), generateClientFromClientWithSalt(client1, 'salt'),
generateClientFromClientWithSalt(client2, 'salt'), generateClientFromClientWithSalt(client2, 'salt'),
]); ]);
expect(pluginStates).toEqual(expectedPluginState); expect(json.pluginStates2).toEqual(expectedPluginState);
}); });
test('test processStore function for device plugin state and no clients', async () => { test('test processStore function for device plugin state and no clients', async () => {
@@ -437,12 +441,13 @@ test('test processStore function for device plugin state and no clients', async
const json = await processStore({ const json = await processStore({
activeNotifications: [], activeNotifications: [],
device: selectedDevice, device: selectedDevice,
pluginStates: { pluginStates2: {
'serial#TestDevicePlugin': { serial: {
msg: 'Test Device plugin', TestDevicePlugin: {
msg: 'Test Device plugin',
},
}, },
}, },
pluginStates2: {},
clients: [], clients: [],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]), devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
clientPlugins: new Map(), clientPlugins: new Map(),
@@ -453,12 +458,11 @@ test('test processStore function for device plugin state and no clients', async
if (!json) { if (!json) {
fail('json is undefined'); fail('json is undefined');
} }
const {pluginStates} = json.store;
const {clients} = json; const {clients} = json;
const expectedPluginState = { const expectedPluginState = {
'salt-serial#TestDevicePlugin': JSON.stringify({msg: 'Test Device plugin'}), 'salt-serial': {TestDevicePlugin: {msg: 'Test Device plugin'}},
}; };
expect(pluginStates).toEqual(expectedPluginState); expect(json.pluginStates2).toEqual(expectedPluginState);
expect(clients).toEqual([]); expect(clients).toEqual([]);
}); });
@@ -474,12 +478,13 @@ test('test processStore function for unselected device plugin state and no clien
const json = await processStore({ const json = await processStore({
activeNotifications: [], activeNotifications: [],
device: selectedDevice, device: selectedDevice,
pluginStates: { pluginStates2: {
'unselectedDeviceIdentifier#TestDevicePlugin': { unselectedDeviceIdentifier: {
msg: 'Test Device plugin', TestDevicePlugin: {
msg: 'Test Device plugin',
},
}, },
}, },
pluginStates2: {},
clients: [], clients: [],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]), devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
clientPlugins: new Map(), clientPlugins: new Map(),
@@ -489,9 +494,8 @@ test('test processStore function for unselected device plugin state and no clien
if (!json) { if (!json) {
fail('json is undefined'); fail('json is undefined');
} }
const {pluginStates} = json.store;
const {clients} = json; const {clients} = json;
expect(pluginStates).toEqual({}); expect(json.pluginStates2).toEqual({});
expect(clients).toEqual([]); expect(clients).toEqual([]);
}); });
@@ -519,7 +523,6 @@ test('test processStore function for notifications for selected device', async (
const json = await processStore({ const json = await processStore({
activeNotifications: [activeNotification], activeNotifications: [activeNotification],
device: selectedDevice, device: selectedDevice,
pluginStates: {},
pluginStates2: {}, pluginStates2: {},
clients: [client], clients: [client],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]), devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
@@ -531,9 +534,8 @@ test('test processStore function for notifications for selected device', async (
if (!json) { if (!json) {
fail('json is undefined'); fail('json is undefined');
} }
const {pluginStates} = json.store;
const {clients} = json; const {clients} = json;
expect(pluginStates).toEqual({}); expect(json.pluginStates2).toEqual({});
expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]); expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]);
const {activeNotifications} = json.store; const {activeNotifications} = json.store;
const expectedActiveNotification = { const expectedActiveNotification = {
@@ -580,7 +582,6 @@ test('test processStore function for notifications for unselected device', async
const json = await processStore({ const json = await processStore({
activeNotifications: [activeNotification], activeNotifications: [activeNotification],
device: selectedDevice, device: selectedDevice,
pluginStates: {},
pluginStates2: {}, pluginStates2: {},
clients: [client, unselectedclient], clients: [client, unselectedclient],
devicePlugins: new Map(), devicePlugins: new Map(),
@@ -591,9 +592,8 @@ test('test processStore function for notifications for unselected device', async
if (!json) { if (!json) {
fail('json is undefined'); fail('json is undefined');
} }
const {pluginStates} = json.store;
const {clients} = json; const {clients} = json;
expect(pluginStates).toEqual({}); expect(json.pluginStates2).toEqual({});
expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]); expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]);
const {activeNotifications} = json.store; const {activeNotifications} = json.store;
expect(activeNotifications).toEqual([]); expect(activeNotifications).toEqual([]);
@@ -610,18 +610,21 @@ test('test processStore function for selected plugins', async () => {
const client = generateClientFromDevice(selectedDevice, 'app'); const client = generateClientFromDevice(selectedDevice, 'app');
const pluginstates = { const pluginstates = {
[generateClientIdentifier(selectedDevice, 'app') + '#TestDevicePlugin1']: { [generateClientIdentifier(selectedDevice, 'app')]: {
msg: 'Test plugin1', TestDevicePlugin1: {
msg: 'Test plugin1',
},
}, },
[generateClientIdentifier(selectedDevice, 'app') + '#TestDevicePlugin2']: { [generateClientIdentifier(selectedDevice, 'app')]: {
msg: 'Test plugin2', TestDevicePlugin2: {
msg: 'Test plugin2',
},
}, },
}; };
const json = await processStore({ const json = await processStore({
activeNotifications: [], activeNotifications: [],
device: selectedDevice, device: selectedDevice,
pluginStates: pluginstates, pluginStates2: pluginstates as any,
pluginStates2: {},
clients: [client], clients: [client],
devicePlugins: new Map([ devicePlugins: new Map([
['TestDevicePlugin1', TestDevicePlugin], ['TestDevicePlugin1', TestDevicePlugin],
@@ -634,15 +637,16 @@ test('test processStore function for selected plugins', async () => {
if (!json) { if (!json) {
fail('json is undefined'); fail('json is undefined');
} }
const {pluginStates} = json.store;
const {clients} = json; const {clients} = json;
expect(pluginStates).toEqual({ expect(json.pluginStates2).toEqual({
[generateClientIdentifierWithSalt( [generateClientIdentifierWithSalt(
generateClientIdentifier(selectedDevice, 'app'), generateClientIdentifier(selectedDevice, 'app'),
'salt', 'salt',
) + '#TestDevicePlugin2']: JSON.stringify({ )]: {
msg: 'Test plugin2', TestDevicePlugin2: {
}), msg: 'Test plugin2',
},
},
}); });
expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]); expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]);
const {activeNotifications} = json.store; const {activeNotifications} = json.store;
@@ -659,18 +663,19 @@ test('test processStore function for no selected plugins', async () => {
}); });
const client = generateClientFromDevice(selectedDevice, 'app'); const client = generateClientFromDevice(selectedDevice, 'app');
const pluginstates = { const pluginstates = {
[generateClientIdentifier(selectedDevice, 'app') + '#TestDevicePlugin1']: { [generateClientIdentifier(selectedDevice, 'app')]: {
msg: 'Test plugin1', TestDevicePlugin1: {
}, msg: 'Test plugin1',
[generateClientIdentifier(selectedDevice, 'app') + '#TestDevicePlugin2']: { },
msg: 'Test plugin2', TestDevicePlugin2: {
msg: 'Test plugin2',
},
}, },
}; };
const json = await processStore({ const json = await processStore({
activeNotifications: [], activeNotifications: [],
device: selectedDevice, device: selectedDevice,
pluginStates: pluginstates, pluginStates2: pluginstates as any,
pluginStates2: {},
clients: [client], clients: [client],
devicePlugins: new Map([ devicePlugins: new Map([
['TestDevicePlugin1', TestDevicePlugin], ['TestDevicePlugin1', TestDevicePlugin],
@@ -684,21 +689,20 @@ test('test processStore function for no selected plugins', async () => {
if (!json) { if (!json) {
fail('json is undefined'); fail('json is undefined');
} }
const {pluginStates} = json.store;
const {clients} = json; const {clients} = json;
expect(pluginStates).toEqual({ expect(json.pluginStates2).toEqual({
[generateClientIdentifierWithSalt( [generateClientIdentifierWithSalt(
generateClientIdentifier(selectedDevice, 'app'), generateClientIdentifier(selectedDevice, 'app'),
'salt', 'salt',
) + '#TestDevicePlugin2']: JSON.stringify({ )]: {
msg: 'Test plugin2', TestDevicePlugin2: {
}), msg: 'Test plugin2',
[generateClientIdentifierWithSalt( },
generateClientIdentifier(selectedDevice, 'app'),
'salt', TestDevicePlugin1: {
) + '#TestDevicePlugin1']: JSON.stringify({ msg: 'Test plugin1',
msg: 'Test plugin1', },
}), },
}); });
expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]); expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]);
const {activeNotifications} = json.store; const {activeNotifications} = json.store;
@@ -781,14 +785,12 @@ test('test determinePluginsToProcess for mutilple clients having plugins present
pluginKey: `${client1.id}#TestPlugin`, pluginKey: `${client1.id}#TestPlugin`,
pluginId: 'TestPlugin', pluginId: 'TestPlugin',
pluginName: 'TestPlugin', pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1, client: client1,
}, },
{ {
pluginKey: `${client3.id}#TestPlugin`, pluginKey: `${client3.id}#TestPlugin`,
pluginId: 'TestPlugin', pluginId: 'TestPlugin',
pluginName: 'TestPlugin', pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client3, client: client3,
}, },
]); ]);
@@ -905,7 +907,6 @@ test('test determinePluginsToProcess for multiple clients on same device', async
pluginKey: `${client1.id}#TestPlugin`, pluginKey: `${client1.id}#TestPlugin`,
pluginId: 'TestPlugin', pluginId: 'TestPlugin',
pluginName: 'TestPlugin', pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1, client: client1,
}, },
]); ]);
@@ -999,7 +1000,6 @@ test('test determinePluginsToProcess for multiple clients on different device',
pluginKey: `${client1Device2.id}#TestPlugin`, pluginKey: `${client1Device2.id}#TestPlugin`,
pluginId: 'TestPlugin', pluginId: 'TestPlugin',
pluginName: 'TestPlugin', pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1Device2, client: client1Device2,
}, },
]); ]);
@@ -1085,7 +1085,6 @@ test('test determinePluginsToProcess to ignore archived clients', async () => {
pluginKey: `${client.id}#TestPlugin`, pluginKey: `${client.id}#TestPlugin`,
pluginId: 'TestPlugin', pluginId: 'TestPlugin',
pluginName: 'TestPlugin', pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client, client: client,
}, },
]); ]);

View File

@@ -1,684 +0,0 @@
/**
* 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 {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
import {Store, Client, sleep} from '../../';
import {
selectPlugin,
selectClient,
selectDevice,
} from '../../reducers/connections';
import {processMessageQueue} from '../messageQueue';
import {getPluginKey} from '../pluginUtils';
import {TestIdler} from '../Idler';
import pluginMessageQueue, {
State,
queueMessages,
} from '../../reducers/pluginMessageQueue';
import {registerPlugins} from '../../reducers/plugins';
import {switchPlugin} from '../../reducers/pluginManager';
interface PersistedState {
count: 1;
}
class TestPlugin extends FlipperPlugin<any, any, any> {
static id = 'TestPlugin';
static defaultPersistedState = {
count: 0,
};
static persistedStateReducer(
persistedState: PersistedState,
method: string,
payload: {delta?: number},
) {
if (method === 'inc') {
return Object.assign({}, persistedState, {
count: persistedState.count + ((payload && payload?.delta) || 1),
});
}
return persistedState;
}
render() {
return null;
}
}
function switchTestPlugin(store: Store, client: Client) {
store.dispatch(
switchPlugin({
plugin: TestPlugin,
selectedApp: client.query.app,
}),
);
}
function selectDeviceLogs(store: Store) {
store.dispatch(
selectPlugin({
selectedPlugin: 'DeviceLogs',
selectedApp: null,
deepLinkPayload: null,
selectedDevice: store.getState().connections.selectedDevice!,
}),
);
}
function selectTestPlugin(store: Store, client: Client) {
store.dispatch(
selectPlugin({
selectedPlugin: TestPlugin.id,
selectedApp: client.query.app,
deepLinkPayload: null,
selectedDevice: store.getState().connections.selectedDevice!,
}),
);
}
test('queue - events are processed immediately if plugin is selected', async () => {
const {store, client, sendMessage} = await createMockFlipperWithPlugin(
TestPlugin,
{
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
},
);
expect(store.getState().connections.selectedPlugin).toBe('TestPlugin');
sendMessage('noop', {});
sendMessage('noop', {});
sendMessage('inc', {});
sendMessage('inc', {delta: 4});
sendMessage('noop', {});
client.flushMessageBuffer();
expect(store.getState().pluginStates).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
"count": 5,
},
}
`);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(
`Object {}`,
);
});
test('queue - events are NOT processed immediately if plugin is NOT selected (but enabled)', async () => {
const {store, client, sendMessage, device} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
sendMessage('inc', {});
sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3});
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
// the first message is already visible cause of the leading debounce
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {},
},
],
}
`);
client.flushMessageBuffer();
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {},
},
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 2,
},
},
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 3,
},
},
],
}
`);
// process the message
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
await processMessageQueue(TestPlugin, pluginKey, store);
expect(store.getState().pluginStates).toEqual({
[pluginKey]: {
count: 6,
},
});
expect(store.getState().pluginMessageQueue).toEqual({
[pluginKey]: [],
});
// disable, but, messages still arrives because selected
switchTestPlugin(store, client);
selectTestPlugin(store, client);
sendMessage('inc', {delta: 3});
client.flushMessageBuffer();
// active, immediately processed
expect(store.getState().pluginStates).toEqual({
[pluginKey]: {
count: 9,
},
});
// different plugin, and not enabled, message will never arrive
selectDeviceLogs(store);
sendMessage('inc', {delta: 4});
client.flushMessageBuffer();
expect(store.getState().pluginMessageQueue).toEqual({});
// star again, plugin still not selected, message is queued
switchTestPlugin(store, client);
sendMessage('inc', {delta: 5});
client.flushMessageBuffer();
expect(store.getState().pluginMessageQueue).toEqual({
[pluginKey]: [{api: 'TestPlugin', method: 'inc', params: {delta: 5}}],
});
});
test('queue - events are queued for plugins that are favorite when app is not selected', async () => {
const {device, store, sendMessage, createClient} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
const client2 = await createClient(device, 'TestApp2');
store.dispatch(selectClient(client2.id));
// Now we send a message to the second client, it should arrive,
// as the plugin was enabled already on the first client as well
sendMessage('inc', {delta: 2});
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 2,
},
},
],
}
`);
});
test('queue - events are queued for plugins that are favorite when app is selected on different device', async () => {
const {client, store, sendMessage, createDevice, createClient} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
const device2 = createDevice('serial2');
const client2 = await createClient(device2, client.query.app); // same app id
store.dispatch(selectDevice(device2));
store.dispatch(selectClient(client2.id));
// Now we send a message to the first and second client, it should arrive,
// as the plugin was enabled already on the first client as well
sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3}, client2);
client.flushMessageBuffer();
client2.flushMessageBuffer();
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 2,
},
},
],
"TestApp#Android#MockAndroidDevice#serial2#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 3,
},
},
],
}
`);
});
test('queue - events processing will be paused', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
sendMessage('inc', {delta: 3});
sendMessage('inc', {delta: 5});
client.flushMessageBuffer();
// process the message
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
// controlled idler will signal and and off that idling is needed
const idler = new TestIdler();
const p = processMessageQueue(TestPlugin, pluginKey, store, undefined, idler);
expect(store.getState().pluginStates).toEqual({
[pluginKey]: {
count: 4,
},
});
expect(store.getState().pluginMessageQueue).toEqual({
[pluginKey]: [{api: 'TestPlugin', method: 'inc', params: {delta: 5}}],
});
await idler.next();
expect(store.getState().pluginStates).toEqual({
[pluginKey]: {
count: 9,
},
});
expect(store.getState().pluginMessageQueue).toEqual({
[pluginKey]: [],
});
// don't idle anymore
idler.run();
await p;
});
test('queue - messages that arrive during processing will be queued', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3});
client.flushMessageBuffer();
// process the message
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
const idler = new TestIdler();
const p = processMessageQueue(TestPlugin, pluginKey, store, undefined, idler);
// first message is consumed
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(1);
expect(store.getState().pluginStates[pluginKey].count).toBe(3);
// Select the current plugin as active, still, messages should end up in the queue
store.dispatch(
selectPlugin({
selectedPlugin: TestPlugin.id,
selectedApp: client.id,
deepLinkPayload: null,
selectedDevice: device,
}),
);
expect(store.getState().connections.selectedPlugin).toBe('TestPlugin');
sendMessage('inc', {delta: 4});
client.flushMessageBuffer();
// should not be processed yet
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(2);
expect(store.getState().pluginStates[pluginKey].count).toBe(3);
await idler.next();
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(0);
expect(store.getState().pluginStates[pluginKey].count).toBe(10);
idler.run();
await p;
});
test('queue - processing can be cancelled', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3});
sendMessage('inc', {delta: 4});
sendMessage('inc', {delta: 5});
client.flushMessageBuffer();
// process the message
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
const idler = new TestIdler();
const p = processMessageQueue(TestPlugin, pluginKey, store, undefined, idler);
// first message is consumed
await idler.next();
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(1);
expect(store.getState().pluginStates[pluginKey].count).toBe(10);
idler.cancel();
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(1);
expect(store.getState().pluginStates[pluginKey].count).toBe(10);
await p;
});
test('queue - make sure resetting plugin state clears the message queue', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
sendMessage('inc', {delta: 2});
client.flushMessageBuffer();
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(2);
store.dispatch({
type: 'CLEAR_CLIENT_PLUGINS_STATE',
payload: {clientId: client.id, devicePlugins: new Set()},
});
expect(store.getState().pluginMessageQueue[pluginKey]).toBe(undefined);
});
test('queue will be cleaned up when it exceeds maximum size', () => {
let state: State = {};
const pluginKey = 'test';
const queueSize = 5000;
let i = 0;
for (i = 0; i < queueSize; i++) {
state = pluginMessageQueue(
state,
queueMessages(pluginKey, [{method: 'test', params: {i}}], queueSize),
);
}
// almost full
expect(state[pluginKey][0]).toEqual({method: 'test', params: {i: 0}});
expect(state[pluginKey].length).toBe(queueSize); // ~5000
expect(state[pluginKey][queueSize - 1]).toEqual({
method: 'test',
params: {i: queueSize - 1}, // ~4999
});
state = pluginMessageQueue(
state,
queueMessages(pluginKey, [{method: 'test', params: {i: ++i}}], queueSize),
);
const newLength = Math.ceil(0.9 * queueSize) + 1; // ~4500
expect(state[pluginKey].length).toBe(newLength);
expect(state[pluginKey][0]).toEqual({
method: 'test',
params: {i: queueSize - newLength + 1}, // ~500
});
expect(state[pluginKey][newLength - 1]).toEqual({
method: 'test',
params: {i: i}, // ~50001
});
});
test('client - incoming messages are buffered and flushed together', async () => {
class StubDeviceLogs extends FlipperDevicePlugin<any, any, any> {
static id = 'DevicePlugin';
static supportsDevice() {
return true;
}
static persistedStateReducer = jest.fn();
}
const {client, store, device, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
store.dispatch(registerPlugins([StubDeviceLogs]));
sendMessage('inc', {});
sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3});
// send a message to device logs
client.onMessage(
JSON.stringify({
method: 'execute',
params: {
api: 'DevicePlugin',
method: 'log',
params: {line: 'suff'},
},
}),
);
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
// the first message is already visible cause of the leading debounce
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {},
},
],
}
`);
expect(client.messageBuffer).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Object {
"messages": Array [
Object {
"api": "DevicePlugin",
"method": "log",
"params": Object {
"line": "suff",
},
},
],
"plugin": [Function],
},
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
"messages": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 2,
},
},
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 3,
},
},
],
"plugin": [Function],
},
}
`);
await sleep(500);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Array [
Object {
"api": "DevicePlugin",
"method": "log",
"params": Object {
"line": "suff",
},
},
],
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {},
},
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 2,
},
},
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 3,
},
},
],
}
`);
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
expect(StubDeviceLogs.persistedStateReducer.mock.calls).toMatchInlineSnapshot(
`Array []`,
);
// tigger processing the queue
const pluginKey = getPluginKey(client.id, device, StubDeviceLogs.id);
await processMessageQueue(StubDeviceLogs, pluginKey, store);
expect(StubDeviceLogs.persistedStateReducer.mock.calls)
.toMatchInlineSnapshot(`
Array [
Array [
Object {},
"log",
Object {
"line": "suff",
},
],
]
`);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Array [],
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {},
},
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 2,
},
},
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 3,
},
},
],
}
`);
});
test('queue - messages that have not yet flushed be lost when disabling the plugin', async () => {
const {client, store, sendMessage, pluginKey} =
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
sendMessage('inc', {delta: 2});
expect(client.messageBuffer).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
"messages": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {
"delta": 2,
},
},
],
"plugin": [Function],
},
}
`);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"api": "TestPlugin",
"method": "inc",
"params": Object {},
},
],
}
`);
// disable
switchTestPlugin(store, client);
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(
`Object {}`,
);
// re-enable, no messages arrive
switchTestPlugin(store, client);
client.flushMessageBuffer();
processMessageQueue(TestPlugin, pluginKey, store);
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
});

View File

@@ -7,8 +7,11 @@
* @format * @format
*/ */
import {FlipperDevicePlugin} from '../../plugin'; import {FlipperPlugin} from '../../plugin';
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin'; import {
createMockFlipperWithPlugin,
wrapSandy,
} from '../../test-utils/createMockFlipperWithPlugin';
import {Store, Client, sleep} from '../../'; import {Store, Client, sleep} from '../../';
import { import {
selectPlugin, selectPlugin,
@@ -26,6 +29,10 @@ import {
_SandyPluginInstance, _SandyPluginInstance,
} from 'flipper-plugin'; } from 'flipper-plugin';
import {switchPlugin} from '../../reducers/pluginManager'; import {switchPlugin} from '../../reducers/pluginManager';
import pluginMessageQueue, {
State,
queueMessages,
} from '../../reducers/pluginMessageQueue';
type Events = { type Events = {
inc: { inc: {
@@ -124,7 +131,6 @@ test('queue - events are NOT processed immediately if plugin is NOT selected (bu
sendMessage('inc', {}); sendMessage('inc', {});
sendMessage('inc', {delta: 2}); sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3}); sendMessage('inc', {delta: 3});
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
expect(getTestPluginState(client).count).toBe(0); expect(getTestPluginState(client).count).toBe(0);
// the first message is already visible cause of the leading debounce // the first message is already visible cause of the leading debounce
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(` expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
@@ -482,21 +488,21 @@ test('queue - make sure resetting plugin state clears the message queue', async
}); });
test('client - incoming messages are buffered and flushed together', async () => { test('client - incoming messages are buffered and flushed together', async () => {
class StubDeviceLogs extends FlipperDevicePlugin<any, any, any> { class StubPlugin extends FlipperPlugin<any, any, any> {
static id = 'DevicePlugin'; static id = 'StubPlugin';
static supportsDevice() {
return true;
}
static persistedStateReducer = jest.fn(); static persistedStateReducer = jest.fn();
} }
const StubPluginWrapped = wrapSandy(StubPlugin);
const {client, store, device, sendMessage, pluginKey} = const {client, store, device, sendMessage, pluginKey} =
await createMockFlipperWithPlugin(TestPlugin); await createMockFlipperWithPlugin(TestPlugin, {
additionalPlugins: [StubPluginWrapped],
});
selectDeviceLogs(store); selectDeviceLogs(store);
store.dispatch(registerPlugins([StubDeviceLogs])); store.dispatch(registerPlugins([StubPluginWrapped]));
sendMessage('inc', {}); sendMessage('inc', {});
sendMessage('inc', {delta: 2}); sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3}); sendMessage('inc', {delta: 3});
@@ -506,14 +512,13 @@ test('client - incoming messages are buffered and flushed together', async () =>
JSON.stringify({ JSON.stringify({
method: 'execute', method: 'execute',
params: { params: {
api: 'DevicePlugin', api: 'StubPlugin',
method: 'log', method: 'log',
params: {line: 'suff'}, params: {line: 'suff'},
}, },
}), }),
); );
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
expect(getTestPluginState(client).count).toBe(0); expect(getTestPluginState(client).count).toBe(0);
// the first message is already visible cause of the leading debounce // the first message is already visible cause of the leading debounce
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(` expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
@@ -529,17 +534,17 @@ test('client - incoming messages are buffered and flushed together', async () =>
`); `);
expect(client.messageBuffer).toMatchInlineSnapshot(` expect(client.messageBuffer).toMatchInlineSnapshot(`
Object { Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Object { "TestApp#Android#MockAndroidDevice#serial#StubPlugin": Object {
"messages": Array [ "messages": Array [
Object { Object {
"api": "DevicePlugin", "api": "StubPlugin",
"method": "log", "method": "log",
"params": Object { "params": Object {
"line": "suff", "line": "suff",
}, },
}, },
], ],
"plugin": [Function], "plugin": "[SandyPluginInstance]",
}, },
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object { "TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
"messages": Array [ "messages": Array [
@@ -569,9 +574,9 @@ test('client - incoming messages are buffered and flushed together', async () =>
await sleep(500); await sleep(500);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(` expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object { Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Array [ "TestApp#Android#MockAndroidDevice#serial#StubPlugin": Array [
Object { Object {
"api": "DevicePlugin", "api": "StubPlugin",
"method": "log", "method": "log",
"params": Object { "params": Object {
"line": "suff", "line": "suff",
@@ -602,19 +607,22 @@ test('client - incoming messages are buffered and flushed together', async () =>
} }
`); `);
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`); expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
expect(StubDeviceLogs.persistedStateReducer.mock.calls).toMatchInlineSnapshot( expect(StubPlugin.persistedStateReducer.mock.calls).toMatchInlineSnapshot(
`Array []`, `Array []`,
); );
// tigger processing the queue // tigger processing the queue
const pluginKeyDevice = getPluginKey(client.id, device, StubDeviceLogs.id); const pluginKeyDevice = getPluginKey(client.id, device, StubPlugin.id);
await processMessageQueue(StubDeviceLogs, pluginKeyDevice, store); await processMessageQueue(
client.sandyPluginStates.get(StubPlugin.id)!,
pluginKeyDevice,
store,
);
expect(StubDeviceLogs.persistedStateReducer.mock.calls) expect(StubPlugin.persistedStateReducer.mock.calls).toMatchInlineSnapshot(`
.toMatchInlineSnapshot(`
Array [ Array [
Array [ Array [
Object {}, undefined,
"log", "log",
Object { Object {
"line": "suff", "line": "suff",
@@ -625,7 +633,7 @@ test('client - incoming messages are buffered and flushed together', async () =>
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(` expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object { Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Array [], "TestApp#Android#MockAndroidDevice#serial#StubPlugin": Array [],
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [ "TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object { Object {
"api": "TestPlugin", "api": "TestPlugin",
@@ -704,3 +712,39 @@ test('queue - messages that have not yet flushed be lost when disabling the plug
); );
expect(getTestPluginState(client)).toEqual({count: 0}); expect(getTestPluginState(client)).toEqual({count: 0});
}); });
test('queue will be cleaned up when it exceeds maximum size', () => {
let state: State = {};
const pluginKey = 'test';
const queueSize = 5000;
let i = 0;
for (i = 0; i < queueSize; i++) {
state = pluginMessageQueue(
state,
queueMessages(pluginKey, [{method: 'test', params: {i}}], queueSize),
);
}
// almost full
expect(state[pluginKey][0]).toEqual({method: 'test', params: {i: 0}});
expect(state[pluginKey].length).toBe(queueSize); // ~5000
expect(state[pluginKey][queueSize - 1]).toEqual({
method: 'test',
params: {i: queueSize - 1}, // ~4999
});
state = pluginMessageQueue(
state,
queueMessages(pluginKey, [{method: 'test', params: {i: ++i}}], queueSize),
);
const newLength = Math.ceil(0.9 * queueSize) + 1; // ~4500
expect(state[pluginKey].length).toBe(newLength);
expect(state[pluginKey][0]).toEqual({
method: 'test',
params: {i: queueSize - newLength + 1}, // ~500
});
expect(state[pluginKey][newLength - 1]).toEqual({
method: 'test',
params: {i: i}, // ~50001
});
});

View File

@@ -72,7 +72,7 @@ function createMockFlipperPluginWithNoPersistedState(id: string) {
} }
test('getActivePersistentPlugins, where the non persistent plugins getting excluded', async () => { test('getActivePersistentPlugins, where the non persistent plugins getting excluded', async () => {
const {store, device, client} = await createMockFlipperWithPlugin( const {store} = await createMockFlipperWithPlugin(
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'), createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
{ {
additionalPlugins: [ additionalPlugins: [
@@ -85,11 +85,6 @@ test('getActivePersistentPlugins, where the non persistent plugins getting exclu
); );
const state = store.getState(); const state = store.getState();
state.pluginStates = {
[getPluginKey(client.id, device, 'ClientPlugin1')]: {msg: 'DevicePlugin1'},
[getPluginKey(client.id, device, 'ClientPlugin4')]: {msg: 'ClientPlugin2'},
};
const list = getExportablePlugins(state); const list = getExportablePlugins(state);
expect(list).toEqual([ expect(list).toEqual([
{ {
@@ -97,23 +92,23 @@ test('getActivePersistentPlugins, where the non persistent plugins getting exclu
label: 'ClientPlugin1', label: 'ClientPlugin1',
}, },
{ {
id: 'ClientPlugin4', id: 'ClientPlugin2',
label: 'ClientPlugin4', label: 'ClientPlugin2',
},
{
id: 'ClientPlugin5',
label: 'ClientPlugin5',
}, },
// { Never activated, and no data received
// id: 'ClientPlugin5',
// label: 'ClientPlugin5',
// },
]); ]);
}); });
test('getActivePersistentPlugins, where the plugins not in pluginState or queue gets excluded', async () => { test('getActivePersistentPlugins, with message queue', async () => {
const {store, device, client} = await createMockFlipperWithPlugin( const {store, device, client} = await createMockFlipperWithPlugin(
createMockFlipperPluginWithDefaultPersistedState('Plugin1'), createMockFlipperPluginWithDefaultPersistedState('Plugin1'),
{ {
additionalPlugins: [ additionalPlugins: [
createMockDeviceFlipperPlugin('DevicePlugin2'), createMockDeviceFlipperPlugin('DevicePlugin2'),
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'), createMockFlipperPluginWithNoPersistedState('ClientPlugin1'),
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'), createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'),
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin3'), createMockFlipperPluginWithDefaultPersistedState('ClientPlugin3'),
], ],
@@ -122,9 +117,6 @@ test('getActivePersistentPlugins, where the plugins not in pluginState or queue
const state = store.getState(); const state = store.getState();
state.pluginStates = {
[getPluginKey(client.id, device, 'ClientPlugin2')]: {msg: 'ClientPlugin2'},
};
state.pluginMessageQueue = { state.pluginMessageQueue = {
[getPluginKey(client.id, device, 'ClientPlugin3')]: [ [getPluginKey(client.id, device, 'ClientPlugin3')]: [
{method: 'msg', params: {msg: 'ClientPlugin3'}}, {method: 'msg', params: {msg: 'ClientPlugin3'}},

View File

@@ -70,8 +70,7 @@ export function createSandyPluginWrapper<S, A extends BaseAction, P>(
if ( if (
Plugin.persistedStateReducer || Plugin.persistedStateReducer ||
Plugin.exportPersistedState || Plugin.exportPersistedState ||
Plugin.defaultPersistedState || Plugin.defaultPersistedState
Plugin.serializePersistedState
) { ) {
client.onExport(async (idler, onStatusMessage) => { client.onExport(async (idler, onStatusMessage) => {
const state = Plugin.exportPersistedState const state = Plugin.exportPersistedState

View File

@@ -11,21 +11,14 @@ import os from 'os';
import path from 'path'; import path from 'path';
import electron from 'electron'; import electron from 'electron';
import {getInstance as getLogger} from '../fb-stubs/Logger'; import {getInstance as getLogger} from '../fb-stubs/Logger';
import {Store, State as ReduxState, MiddlewareAPI} from '../reducers'; import {Store, MiddlewareAPI} from '../reducers';
import {DeviceExport} from '../devices/BaseDevice'; import {DeviceExport} from '../devices/BaseDevice';
import {State as PluginStatesState} from '../reducers/pluginStates';
import {State as PluginsState} from '../reducers/plugins'; import {State as PluginsState} from '../reducers/plugins';
import {PluginNotification} from '../reducers/notifications'; import {PluginNotification} from '../reducers/notifications';
import Client, {ClientExport, ClientQuery} from '../Client'; import Client, {ClientExport, ClientQuery} from '../Client';
import {getAppVersion} from './info'; import {getAppVersion} from './info';
import {pluginKey} from '../reducers/pluginStates'; import {pluginKey} from '../utils/pluginUtils';
import { import {DevicePluginMap, ClientPluginMap} from '../plugin';
callClient,
supportsMethod,
PluginDefinition,
DevicePluginMap,
ClientPluginMap,
} 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';
import fs from 'fs'; import fs from 'fs';
@@ -34,7 +27,6 @@ import {remote, OpenDialogOptions} from 'electron';
import {readCurrentRevision} from './packageMetadata'; import {readCurrentRevision} from './packageMetadata';
import {tryCatchReportPlatformFailures} from './metrics'; import {tryCatchReportPlatformFailures} from './metrics';
import {promisify} from 'util'; import {promisify} from 'util';
import promiseTimeout from './promiseTimeout';
import {TestIdler} from './Idler'; import {TestIdler} from './Idler';
import {setStaticView} from '../reducers/connections'; import {setStaticView} from '../reducers/connections';
import { import {
@@ -42,10 +34,10 @@ import {
SupportFormRequestDetailsState, SupportFormRequestDetailsState,
} from '../reducers/supportForm'; } from '../reducers/supportForm';
import {setSelectPluginsToExportActiveSheet} from '../reducers/application'; import {setSelectPluginsToExportActiveSheet} from '../reducers/application';
import {deconstructClientId, deconstructPluginKey} from '../utils/clientUtils'; import {deconstructClientId} from '../utils/clientUtils';
import {performance} from 'perf_hooks'; import {performance} from 'perf_hooks';
import {processMessageQueue} from './messageQueue'; import {processMessageQueue} from './messageQueue';
import {getPluginTitle, isSandyPlugin} 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 {Idler} from 'flipper-plugin'; import {Idler} from 'flipper-plugin';
@@ -71,7 +63,6 @@ export type ExportType = {
device: DeviceExport | null; device: DeviceExport | null;
deviceScreenshot: string | null; deviceScreenshot: string | null;
store: { store: {
pluginStates: PluginStatesExportState;
activeNotifications: Array<PluginNotification>; activeNotifications: Array<PluginNotification>;
}; };
// The GraphQL plugin relies on this format for generating // The GraphQL plugin relies on this format for generating
@@ -80,15 +71,6 @@ export type ExportType = {
supportRequestDetails?: SupportFormRequestDetailsState; supportRequestDetails?: SupportFormRequestDetailsState;
}; };
type ProcessPluginStatesOptions = {
clients: Array<ClientExport>;
serial: string;
allPluginStates: PluginStatesState;
devicePlugins: DevicePluginMap;
selectedPlugins: Array<string>;
statusUpdate?: (msg: string) => void;
};
type ProcessNotificationStatesOptions = { type ProcessNotificationStatesOptions = {
clients: Array<ClientExport>; clients: Array<ClientExport>;
serial: string; serial: string;
@@ -101,7 +83,6 @@ type PluginsToProcess = {
pluginKey: string; pluginKey: string;
pluginId: string; pluginId: string;
pluginName: string; pluginName: string;
pluginClass: PluginDefinition;
client: Client; client: Client;
}[]; }[];
@@ -110,7 +91,6 @@ type AddSaltToDeviceSerialOptions = {
device: BaseDevice; device: BaseDevice;
deviceScreenshot: string | null; deviceScreenshot: string | null;
clients: Array<ClientExport>; clients: Array<ClientExport>;
pluginStates: PluginStatesExportState;
pluginStates2: SandyPluginStates; pluginStates2: SandyPluginStates;
devicePluginStates: Record<string, any>; devicePluginStates: Record<string, any>;
pluginNotification: Array<PluginNotification>; pluginNotification: Array<PluginNotification>;
@@ -152,51 +132,6 @@ export function processClients(
return filteredClients; return filteredClients;
} }
export function processPluginStates(
options: ProcessPluginStatesOptions,
): PluginStatesState {
const {
clients,
serial,
allPluginStates,
devicePlugins,
selectedPlugins,
statusUpdate,
} = options;
let pluginStates: PluginStatesState = {};
statusUpdate &&
statusUpdate('Filtering the plugin states for the filtered Clients...');
for (const key in allPluginStates) {
const plugin = deconstructPluginKey(key);
const pluginName = plugin.pluginName;
if (
pluginName &&
selectedPlugins.length > 0 &&
!selectedPlugins.includes(pluginName)
) {
continue;
}
if (plugin.type === 'client') {
if (!clients.some((c) => c.id.includes(plugin.client))) {
continue;
}
}
if (plugin.type === 'device') {
if (
!pluginName ||
!devicePlugins.has(pluginName) ||
serial !== plugin.client
) {
continue;
}
}
pluginStates = {...pluginStates, [key]: allPluginStates[key]};
}
return pluginStates;
}
export function processNotificationStates( export function processNotificationStates(
options: ProcessNotificationStatesOptions, options: ProcessNotificationStatesOptions,
): Array<PluginNotification> { ): Array<PluginNotification> {
@@ -216,41 +151,6 @@ export function processNotificationStates(
return activeNotifications; return activeNotifications;
} }
const serializePluginStates = async (
pluginStates: PluginStatesState,
clientPlugins: ClientPluginMap,
devicePlugins: DevicePluginMap,
statusUpdate?: (msg: string) => void,
idler?: Idler,
): Promise<PluginStatesExportState> => {
const pluginsMap = new Map<string, PluginDefinition>([
...clientPlugins.entries(),
...devicePlugins.entries(),
]);
const pluginExportState: PluginStatesExportState = {};
for (const key in pluginStates) {
const pluginName = deconstructPluginKey(key).pluginName;
statusUpdate && statusUpdate(`Serialising ${pluginName}...`);
const serializationMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:serialization-per-plugin`;
performance.mark(serializationMarker);
const pluginClass = pluginName ? pluginsMap.get(pluginName) : null;
if (isSandyPlugin(pluginClass)) {
continue; // Those are already processed by `exportSandyPluginStates`
} else if (pluginClass) {
pluginExportState[key] = await pluginClass.serializePersistedState(
pluginStates[key],
statusUpdate,
idler,
pluginName,
);
getLogger().trackTimeSince(serializationMarker, serializationMarker, {
plugin: pluginName,
});
}
}
return pluginExportState;
};
async function exportSandyPluginStates( async function exportSandyPluginStates(
pluginsToProcess: PluginsToProcess, pluginsToProcess: PluginsToProcess,
idler: Idler, idler: Idler,
@@ -258,8 +158,8 @@ async function exportSandyPluginStates(
): Promise<SandyPluginStates> { ): Promise<SandyPluginStates> {
const res: SandyPluginStates = {}; const res: SandyPluginStates = {};
for (const key in pluginsToProcess) { for (const key in pluginsToProcess) {
const {pluginId, client, pluginClass} = pluginsToProcess[key]; const {pluginId, client} = pluginsToProcess[key];
if (isSandyPlugin(pluginClass) && client.sandyPluginStates.has(pluginId)) { if (client.sandyPluginStates.has(pluginId)) {
if (!res[client.id]) { if (!res[client.id]) {
res[client.id] = {}; res[client.id] = {};
} }
@@ -276,33 +176,6 @@ async function exportSandyPluginStates(
return res; return res;
} }
const deserializePluginStates = (
pluginStatesExportState: PluginStatesExportState,
clientPlugins: ClientPluginMap,
devicePlugins: DevicePluginMap,
): PluginStatesState => {
const pluginsMap = new Map<string, PluginDefinition>([
...clientPlugins.entries(),
...devicePlugins.entries(),
]);
const pluginsState: PluginStatesState = {};
for (const key in pluginStatesExportState) {
const pluginName = deconstructPluginKey(key).pluginName;
if (!pluginName || !pluginsMap.get(pluginName)) {
continue;
}
const pluginClass = pluginsMap.get(pluginName);
if (isSandyPlugin(pluginClass)) {
pluginsState[key] = pluginStatesExportState[key];
} else if (pluginClass) {
pluginsState[key] = pluginClass.deserializePersistedState(
pluginStatesExportState[key],
);
}
}
return pluginsState;
};
function replaceSerialsInKeys<T extends Record<string, any>>( function replaceSerialsInKeys<T extends Record<string, any>>(
collection: T, collection: T,
baseSerial: string, baseSerial: string,
@@ -311,9 +184,7 @@ function replaceSerialsInKeys<T extends Record<string, any>>(
const result: Record<string, any> = {}; const result: Record<string, any> = {};
for (const key in collection) { for (const key in collection) {
if (!key.includes(baseSerial)) { if (!key.includes(baseSerial)) {
throw new Error( continue;
`Error while exporting, plugin state (${key}) does not have ${baseSerial} in its key`,
);
} }
result[key.replace(baseSerial, newSerial)] = collection[key]; result[key.replace(baseSerial, newSerial)] = collection[key];
} }
@@ -325,7 +196,6 @@ async function addSaltToDeviceSerial({
device, device,
deviceScreenshot, deviceScreenshot,
clients, clients,
pluginStates,
pluginNotification, pluginNotification,
statusUpdate, statusUpdate,
pluginStates2, pluginStates2,
@@ -354,11 +224,6 @@ async function addSaltToDeviceSerial({
statusUpdate( statusUpdate(
'Adding salt to the selected device id in the plugin states...', 'Adding salt to the selected device id in the plugin states...',
); );
const updatedPluginStates = replaceSerialsInKeys(
pluginStates,
serial,
newSerial,
);
const updatedPluginStates2 = replaceSerialsInKeys( const updatedPluginStates2 = replaceSerialsInKeys(
pluginStates2, pluginStates2,
serial, serial,
@@ -385,7 +250,6 @@ async function addSaltToDeviceSerial({
device: {...newDevice.toJSON(), pluginStates: devicePluginStates}, device: {...newDevice.toJSON(), pluginStates: devicePluginStates},
deviceScreenshot: deviceScreenshot, deviceScreenshot: deviceScreenshot,
store: { store: {
pluginStates: updatedPluginStates,
activeNotifications: updatedPluginNotifications, activeNotifications: updatedPluginNotifications,
}, },
pluginStates2: updatedPluginStates2, pluginStates2: updatedPluginStates2,
@@ -395,7 +259,6 @@ async function addSaltToDeviceSerial({
type ProcessStoreOptions = { type ProcessStoreOptions = {
activeNotifications: Array<PluginNotification>; activeNotifications: Array<PluginNotification>;
device: BaseDevice | null; device: BaseDevice | null;
pluginStates: PluginStatesState;
pluginStates2: SandyPluginStates; pluginStates2: SandyPluginStates;
clients: Array<ClientExport>; clients: Array<ClientExport>;
devicePlugins: DevicePluginMap; devicePlugins: DevicePluginMap;
@@ -409,11 +272,9 @@ export async function processStore(
{ {
activeNotifications, activeNotifications,
device, device,
pluginStates,
pluginStates2, pluginStates2,
clients, clients,
devicePlugins, devicePlugins,
clientPlugins,
salt, salt,
selectedPlugins, selectedPlugins,
statusUpdate, statusUpdate,
@@ -435,14 +296,7 @@ export async function processStore(
}) })
: null; : null;
const processedClients = processClients(clients, serial, statusUpdate); const processedClients = processClients(clients, serial, statusUpdate);
const processedPluginStates = processPluginStates({
clients: processedClients,
serial,
allPluginStates: pluginStates,
devicePlugins,
selectedPlugins,
statusUpdate,
});
const processedActiveNotifications = processNotificationStates({ const processedActiveNotifications = processNotificationStates({
clients: processedClients, clients: processedClients,
serial, serial,
@@ -451,14 +305,6 @@ export async function processStore(
statusUpdate, statusUpdate,
}); });
const exportPluginState = await serializePluginStates(
processedPluginStates,
clientPlugins,
devicePlugins,
statusUpdate,
idler,
);
const devicePluginStates = await device.exportState( const devicePluginStates = await device.exportState(
idler, idler,
statusUpdate, statusUpdate,
@@ -478,7 +324,6 @@ export async function processStore(
device, device,
deviceScreenshot: deviceScreenshotLink, deviceScreenshot: deviceScreenshotLink,
clients: processedClients, clients: processedClients,
pluginStates: exportPluginState,
pluginNotification: processedActiveNotifications, pluginNotification: processedActiveNotifications,
statusUpdate, statusUpdate,
selectedPlugins, selectedPlugins,
@@ -492,116 +337,35 @@ export async function processStore(
throw new Error('Selected device is null, please select a device'); throw new Error('Selected device is null, please select a device');
} }
export async function fetchMetadata(
pluginsToProcess: PluginsToProcess,
pluginStates: PluginStatesState,
state: ReduxState,
statusUpdate: (msg: string) => void,
idler: Idler,
): Promise<{
pluginStates: PluginStatesState;
errors: {[plugin: string]: Error} | null;
}> {
const newPluginState = {...pluginStates};
let errorObject: {[plugin: string]: Error} | null = null;
for (const {
pluginName,
pluginId,
pluginClass,
client,
pluginKey,
} of pluginsToProcess) {
const exportState =
pluginClass && !isSandyPlugin(pluginClass)
? pluginClass.exportPersistedState
: null;
if (exportState) {
const fetchMetaDataMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:fetch-meta-data-per-plugin`;
const isConnected = client.connected.get();
performance.mark(fetchMetaDataMarker);
try {
statusUpdate &&
statusUpdate(`Fetching metadata for plugin ${pluginName}...`);
const data = await promiseTimeout(
240000, // Fetching MobileConfig data takes ~ 3 mins, thus keeping timeout at 4 mins.
exportState(
isConnected ? callClient(client, pluginId) : undefined,
newPluginState[pluginKey],
state,
idler,
statusUpdate,
isConnected
? supportsMethod(client, pluginId)
: () => Promise.resolve(false),
),
`Timed out while collecting data for ${pluginName}`,
);
if (!data) {
throw new Error(
`Metadata returned by the ${pluginName} is undefined`,
);
}
getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, {
pluginId,
});
newPluginState[pluginKey] = data;
} catch (e) {
if (!errorObject) {
errorObject = {};
}
errorObject[pluginName] = e;
getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, {
pluginId,
error: e,
});
continue;
}
}
}
return {pluginStates: newPluginState, errors: errorObject};
}
async function processQueues( async function processQueues(
store: MiddlewareAPI, store: MiddlewareAPI,
pluginsToProcess: PluginsToProcess, pluginsToProcess: PluginsToProcess,
statusUpdate?: (msg: string) => void, statusUpdate?: (msg: string) => void,
idler?: Idler, idler?: Idler,
) { ) {
for (const { for (const {pluginName, pluginId, pluginKey, client} of pluginsToProcess) {
pluginName, client.flushMessageBuffer();
pluginId, const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`;
pluginKey, performance.mark(processQueueMarker);
pluginClass, const plugin = client.sandyPluginStates.get(pluginId);
client, if (!plugin) continue;
} of pluginsToProcess) { await processMessageQueue(
if (isSandyPlugin(pluginClass) || pluginClass.persistedStateReducer) { plugin,
client.flushMessageBuffer(); pluginKey,
const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`; store,
performance.mark(processQueueMarker); ({current, total}) => {
const plugin = isSandyPlugin(pluginClass) statusUpdate?.(
? client.sandyPluginStates.get(pluginId) `Processing event ${current} / ${total} (${Math.round(
: pluginClass; (current / total) * 100,
if (!plugin) continue; )}%) for plugin ${pluginName}`,
await processMessageQueue( );
plugin, },
pluginKey, idler,
store, );
({current, total}) => {
statusUpdate?.(
`Processing event ${current} / ${total} (${Math.round(
(current / total) * 100,
)}%) for plugin ${pluginName}`,
);
},
idler,
);
getLogger().trackTimeSince(processQueueMarker, processQueueMarker, { getLogger().trackTimeSince(processQueueMarker, processQueueMarker, {
pluginId, pluginId,
}); });
}
} }
} }
@@ -638,7 +402,6 @@ export function determinePluginsToProcess(
client, client,
pluginId: plugin, pluginId: plugin,
pluginName: getPluginTitle(pluginClass), pluginName: getPluginTitle(pluginClass),
pluginClass,
}); });
} }
} }
@@ -671,14 +434,6 @@ async function getStoreExport(
performance.mark(fetchMetaDataMarker); performance.mark(fetchMetaDataMarker);
const client = clients.find((client) => client.id === selectedApp); const client = clients.find((client) => client.id === selectedApp);
const metadata = await fetchMetadata(
pluginsToProcess,
state.pluginStates,
state,
statusUpdate,
idler,
);
const newPluginState = metadata.pluginStates;
const pluginStates2 = pluginsToProcess const pluginStates2 = pluginsToProcess
? await exportSandyPluginStates(pluginsToProcess, idler, statusUpdate) ? await exportSandyPluginStates(pluginsToProcess, idler, statusUpdate)
@@ -687,7 +442,6 @@ async function getStoreExport(
getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, { getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, {
plugins: state.plugins.selectedPlugins, plugins: state.plugins.selectedPlugins,
}); });
const {errors} = metadata;
const {activeNotifications} = state.notifications; const {activeNotifications} = state.notifications;
const {devicePlugins, clientPlugins} = state.plugins; const {devicePlugins, clientPlugins} = state.plugins;
@@ -695,7 +449,6 @@ async function getStoreExport(
{ {
activeNotifications, activeNotifications,
device: selectedDevice, device: selectedDevice,
pluginStates: newPluginState,
pluginStates2, pluginStates2,
clients: client ? [client.toJSON()] : [], clients: client ? [client.toJSON()] : [],
devicePlugins, devicePlugins,
@@ -706,7 +459,7 @@ async function getStoreExport(
}, },
idler, idler,
); );
return {exportData, fetchMetaDataErrors: errors}; return {exportData, fetchMetaDataErrors: null};
} }
export async function exportStore( export async function exportStore(
@@ -810,34 +563,9 @@ export function importDataToStore(source: string, data: string, store: Store) {
payload: archivedDevice, payload: archivedDevice,
}); });
const {pluginStates} = json.store;
const processedPluginStates: PluginStatesState = deserializePluginStates(
pluginStates,
store.getState().plugins.clientPlugins,
store.getState().plugins.devicePlugins,
);
const keys = Object.keys(processedPluginStates);
keys.forEach((key) => {
store.dispatch({
type: 'SET_PLUGIN_STATE',
payload: {
pluginKey: key,
state: processedPluginStates[key],
},
});
});
clients.forEach((client: {id: string; query: ClientQuery}) => { clients.forEach((client: {id: string; query: ClientQuery}) => {
const sandyPluginStates = json.pluginStates2[client.id] || {}; const sandyPluginStates = json.pluginStates2[client.id] || {};
const clientPlugins: Set<string> = new Set([ const clientPlugins = new Set(Object.keys(sandyPluginStates));
...keys
.filter((key) => {
const plugin = deconstructPluginKey(key);
return plugin.type === 'client' && client.id === plugin.client;
})
.map((pluginKey) => deconstructPluginKey(pluginKey).pluginName),
...Object.keys(sandyPluginStates),
]);
store.dispatch({ store.dispatch({
type: 'NEW_CLIENT', type: 'NEW_CLIENT',
payload: new Client( payload: new Client(

View File

@@ -7,9 +7,8 @@
* @format * @format
*/ */
import {PersistedStateReducer, FlipperDevicePlugin} from '../plugin'; import {FlipperDevicePlugin} from '../plugin';
import type {State, MiddlewareAPI} from '../reducers/index'; import type {MiddlewareAPI} from '../reducers/index';
import {setPluginState} from '../reducers/pluginStates';
import { import {
clearMessageQueue, clearMessageQueue,
queueMessages, queueMessages,
@@ -23,31 +22,7 @@ import {defaultEnabledBackgroundPlugins} from './pluginUtils';
import {batch, Idler, _SandyPluginInstance} from 'flipper-plugin'; import {batch, Idler, _SandyPluginInstance} from 'flipper-plugin';
import {addBackgroundStat} from './pluginStats'; import {addBackgroundStat} from './pluginStats';
function processMessageClassic( function processMessagesImmediately(
state: State,
pluginKey: string,
plugin: {
id: string;
persistedStateReducer: PersistedStateReducer | null;
},
message: Message,
): State {
const reducerStartTime = Date.now();
try {
const newPluginState = plugin.persistedStateReducer!(
state,
message.method,
message.params,
);
addBackgroundStat(plugin.id, Date.now() - reducerStartTime);
return newPluginState;
} catch (e) {
console.error(`Failed to process event for plugin ${plugin.id}`, e);
return state;
}
}
function processMessagesSandy(
plugin: _SandyPluginInstance, plugin: _SandyPluginInstance,
messages: Message[], messages: Message[],
) { ) {
@@ -63,60 +38,20 @@ function processMessagesSandy(
} }
} }
export function processMessagesImmediately(
store: MiddlewareAPI,
pluginKey: string,
plugin:
| {
defaultPersistedState: any;
id: string;
persistedStateReducer: PersistedStateReducer | null;
}
| _SandyPluginInstance,
messages: Message[],
) {
if (plugin instanceof _SandyPluginInstance) {
processMessagesSandy(plugin, messages);
} else {
const persistedState = getCurrentPluginState(store, plugin, pluginKey);
const newPluginState = messages.reduce(
(state, message) =>
processMessageClassic(state, pluginKey, plugin, message),
persistedState,
);
if (persistedState !== newPluginState) {
store.dispatch(
setPluginState({
pluginKey,
state: newPluginState,
}),
);
}
}
}
export function processMessagesLater( export function processMessagesLater(
store: MiddlewareAPI, store: MiddlewareAPI,
pluginKey: string, pluginKey: string,
plugin: plugin: _SandyPluginInstance,
| {
defaultPersistedState: any;
id: string;
persistedStateReducer: PersistedStateReducer | null;
maxQueueSize?: number;
}
| _SandyPluginInstance,
messages: Message[], messages: Message[],
) { ) {
const pluginId = const pluginId = plugin.definition.id;
plugin instanceof _SandyPluginInstance ? plugin.definition.id : plugin.id;
const isSelected = const isSelected =
pluginKey === getSelectedPluginKey(store.getState().connections); pluginKey === getSelectedPluginKey(store.getState().connections);
switch (true) { switch (true) {
// Navigation events are always processed immediately, to make sure the navbar stays up to date, see also T69991064 // Navigation events are always processed immediately, to make sure the navbar stays up to date, see also T69991064
case pluginId === 'Navigation': case pluginId === 'Navigation':
case isSelected && getPendingMessages(store, pluginKey).length === 0: case isSelected && getPendingMessages(store, pluginKey).length === 0:
processMessagesImmediately(store, pluginKey, plugin, messages); processMessagesImmediately(plugin, messages);
break; break;
case isSelected: case isSelected:
case plugin instanceof _SandyPluginInstance: case plugin instanceof _SandyPluginInstance:
@@ -129,13 +64,7 @@ export function processMessagesLater(
pluginId, pluginId,
): ):
store.dispatch( store.dispatch(
queueMessages( queueMessages(pluginKey, messages, DEFAULT_MAX_QUEUE_SIZE),
pluginKey,
messages,
plugin instanceof _SandyPluginInstance
? DEFAULT_MAX_QUEUE_SIZE
: plugin.maxQueueSize,
),
); );
break; break;
default: default:
@@ -148,21 +77,12 @@ export function processMessagesLater(
} }
export async function processMessageQueue( export async function processMessageQueue(
plugin: plugin: _SandyPluginInstance,
| {
defaultPersistedState: any;
id: string;
persistedStateReducer: PersistedStateReducer | null;
}
| _SandyPluginInstance,
pluginKey: string, pluginKey: string,
store: MiddlewareAPI, store: MiddlewareAPI,
progressCallback?: (progress: {current: number; total: number}) => void, progressCallback?: (progress: {current: number; total: number}) => void,
idler: Idler = new IdlerImpl(), idler: Idler = new IdlerImpl(),
): Promise<boolean> { ): Promise<boolean> {
if (!_SandyPluginInstance.is(plugin) && !plugin.persistedStateReducer) {
return true;
}
const total = getPendingMessages(store, pluginKey).length; const total = getPendingMessages(store, pluginKey).length;
let progress = 0; let progress = 0;
do { do {
@@ -171,25 +91,11 @@ export async function processMessageQueue(
break; break;
} }
// there are messages to process! lets do so until we have to idle // there are messages to process! lets do so until we have to idle
// persistedState is irrelevant for SandyPlugins, as they store state locally
const persistedState = _SandyPluginInstance.is(plugin)
? undefined
: getCurrentPluginState(store, plugin, pluginKey);
let offset = 0; let offset = 0;
let newPluginState = persistedState;
batch(() => { batch(() => {
do { do {
if (_SandyPluginInstance.is(plugin)) { // Optimization: we could send a batch of messages here
// Optimization: we could send a batch of messages here processMessagesImmediately(plugin, [messages[offset]]);
processMessagesSandy(plugin, [messages[offset]]);
} else {
newPluginState = processMessageClassic(
newPluginState,
pluginKey,
plugin,
messages[offset],
);
}
offset++; offset++;
progress++; progress++;
@@ -203,17 +109,6 @@ export async function processMessageQueue(
// resistent to kicking off this process twice; grabbing, processing messages, saving state is done synchronosly // resistent to kicking off this process twice; grabbing, processing messages, saving state is done synchronosly
// until the idler has to break // until the idler has to break
store.dispatch(clearMessageQueue(pluginKey, offset)); store.dispatch(clearMessageQueue(pluginKey, offset));
if (
!_SandyPluginInstance.is(plugin) &&
newPluginState !== persistedState
) {
store.dispatch(
setPluginState({
pluginKey,
state: newPluginState,
}),
);
}
}); });
if (idler.isCancelled()) { if (idler.isCancelled()) {
@@ -232,15 +127,3 @@ function getPendingMessages(
): Message[] { ): Message[] {
return store.getState().pluginMessageQueue[pluginKey] || []; return store.getState().pluginMessageQueue[pluginKey] || [];
} }
function getCurrentPluginState(
store: MiddlewareAPI,
plugin: {defaultPersistedState: any},
pluginKey: string,
) {
// possible optimization: don't spread default state here by put proper default state when initializing clients
return {
...plugin.defaultPersistedState,
...store.getState().pluginStates[pluginKey],
};
}

View File

@@ -7,17 +7,9 @@
* @format * @format
*/ */
import { import {PluginDefinition} from '../plugin';
FlipperDevicePlugin,
FlipperBasePlugin,
PluginDefinition,
DevicePluginDefinition,
ClientPluginDefinition,
} from '../plugin';
import type {State} from '../reducers'; import type {State} from '../reducers';
import type {State as PluginStatesState} from '../reducers/pluginStates';
import type {State as PluginsState} from '../reducers/plugins'; import type {State as PluginsState} from '../reducers/plugins';
import {_SandyPluginDefinition} from 'flipper-plugin';
import type BaseDevice from '../devices/BaseDevice'; import type BaseDevice from '../devices/BaseDevice';
import type Client from '../Client'; import type Client from '../Client';
import type { import type {
@@ -29,9 +21,9 @@ import type {
import {getLatestCompatibleVersionOfEachPlugin} from '../dispatcher/plugins'; import {getLatestCompatibleVersionOfEachPlugin} from '../dispatcher/plugins';
export type PluginLists = { export type PluginLists = {
devicePlugins: DevicePluginDefinition[]; devicePlugins: PluginDefinition[];
metroPlugins: DevicePluginDefinition[]; metroPlugins: PluginDefinition[];
enabledPlugins: ClientPluginDefinition[]; enabledPlugins: PluginDefinition[];
disabledPlugins: PluginDefinition[]; disabledPlugins: PluginDefinition[];
unavailablePlugins: [plugin: PluginDetails, reason: string][]; unavailablePlugins: [plugin: PluginDetails, reason: string][];
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[]; downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
@@ -86,33 +78,12 @@ export function getPluginKey(
return `unknown#${pluginID}`; return `unknown#${pluginID}`;
} }
export function isSandyPlugin( export const pluginKey = (serial: string, pluginName: string): string => {
plugin?: PluginDefinition | null, return `${serial}#${pluginName}`;
): plugin is _SandyPluginDefinition { };
return plugin instanceof _SandyPluginDefinition;
}
export function getPersistedState<PersistedState>(
pluginKey: string,
persistingPlugin: typeof FlipperBasePlugin | null,
pluginStates: PluginStatesState,
): PersistedState | null {
if (!persistingPlugin) {
return null;
}
const persistedState: PersistedState = {
...persistingPlugin.defaultPersistedState,
...pluginStates[pluginKey],
};
return persistedState;
}
export function computeExportablePlugins( export function computeExportablePlugins(
state: Pick< state: Pick<State, 'plugins' | 'connections' | 'pluginMessageQueue'>,
State,
'plugins' | 'connections' | 'pluginStates' | 'pluginMessageQueue'
>,
device: BaseDevice | null, device: BaseDevice | null,
client: Client | null, client: Client | null,
availablePlugins: PluginLists, availablePlugins: PluginLists,
@@ -131,25 +102,14 @@ export function computeExportablePlugins(
} }
function isExportablePlugin( function isExportablePlugin(
{ {pluginMessageQueue}: Pick<State, 'pluginMessageQueue'>,
pluginStates,
pluginMessageQueue,
}: Pick<State, 'pluginStates' | 'pluginMessageQueue'>,
device: BaseDevice | null, device: BaseDevice | null,
client: Client | null, client: Client | null,
plugin: PluginDefinition, plugin: PluginDefinition,
): boolean { ): boolean {
// can generate an export when requested
if (!isSandyPlugin(plugin) && plugin.exportPersistedState) {
return true;
}
const pluginKey = isDevicePluginDefinition(plugin) const pluginKey = isDevicePluginDefinition(plugin)
? getPluginKey(undefined, device, plugin.id) ? getPluginKey(undefined, device, plugin.id)
: getPluginKey(client?.id, undefined, plugin.id); : getPluginKey(client?.id, undefined, plugin.id);
// plugin has exportable redux state
if (pluginStates[pluginKey]) {
return true;
}
// plugin has exportable sandy state // plugin has exportable sandy state
if (client?.sandyPluginStates.get(plugin.id)?.isPersistable()) { if (client?.sandyPluginStates.get(plugin.id)?.isPersistable()) {
return true; return true;
@@ -158,10 +118,7 @@ function isExportablePlugin(
return true; return true;
} }
// plugin has pending messages and a persisted state reducer or isSandy // plugin has pending messages and a persisted state reducer or isSandy
if ( if (pluginMessageQueue[pluginKey]) {
pluginMessageQueue[pluginKey] &&
((plugin as any).defaultPersistedState || isSandyPlugin(plugin))
) {
return true; return true;
} }
// nothing to serialize // nothing to serialize
@@ -201,11 +158,8 @@ export function isDevicePlugin(activePlugin: ActivePluginListItem) {
export function isDevicePluginDefinition( export function isDevicePluginDefinition(
definition: PluginDefinition, definition: PluginDefinition,
): definition is DevicePluginDefinition { ): boolean {
return ( return definition.isDevicePlugin;
(definition as any).prototype instanceof FlipperDevicePlugin ||
(definition instanceof _SandyPluginDefinition && definition.isDevicePlugin)
);
} }
export function getPluginTooltip(details: PluginDetails): string { export function getPluginTooltip(details: PluginDetails): string {
@@ -234,9 +188,9 @@ export function computePluginLists(
metroDevice: BaseDevice | null, metroDevice: BaseDevice | null,
client: Client | null, client: Client | null,
): { ): {
devicePlugins: DevicePluginDefinition[]; devicePlugins: PluginDefinition[];
metroPlugins: DevicePluginDefinition[]; metroPlugins: PluginDefinition[];
enabledPlugins: ClientPluginDefinition[]; enabledPlugins: PluginDefinition[];
disabledPlugins: PluginDefinition[]; disabledPlugins: PluginDefinition[];
unavailablePlugins: [plugin: PluginDetails, reason: string][]; unavailablePlugins: [plugin: PluginDetails, reason: string][];
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[]; downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
@@ -247,17 +201,13 @@ export function computePluginLists(
...plugins.bundledPlugins.values(), ...plugins.bundledPlugins.values(),
...plugins.marketplacePlugins, ...plugins.marketplacePlugins,
]).filter((p) => !plugins.loadedPlugins.has(p.id)); ]).filter((p) => !plugins.loadedPlugins.has(p.id));
const devicePlugins: DevicePluginDefinition[] = [ const devicePlugins: PluginDefinition[] = [...plugins.devicePlugins.values()]
...plugins.devicePlugins.values(),
]
.filter((p) => device?.supportsPlugin(p)) .filter((p) => device?.supportsPlugin(p))
.filter((p) => enabledDevicePluginsState.has(p.id)); .filter((p) => enabledDevicePluginsState.has(p.id));
const metroPlugins: DevicePluginDefinition[] = [ const metroPlugins: PluginDefinition[] = [...plugins.devicePlugins.values()]
...plugins.devicePlugins.values(),
]
.filter((p) => metroDevice?.supportsPlugin(p)) .filter((p) => metroDevice?.supportsPlugin(p))
.filter((p) => enabledDevicePluginsState.has(p.id)); .filter((p) => enabledDevicePluginsState.has(p.id));
const enabledPlugins: ClientPluginDefinition[] = []; const enabledPlugins: PluginDefinition[] = [];
const disabledPlugins: PluginDefinition[] = [ const disabledPlugins: PluginDefinition[] = [
...plugins.devicePlugins.values(), ...plugins.devicePlugins.values(),
] ]

View File

@@ -49,6 +49,6 @@ export function getFlipperLibImplementation(): FlipperLib {
return flipperLibInstance; return flipperLibInstance;
} }
export function setFlipperLibImplementation(impl: FlipperLib) { export function setFlipperLibImplementation(impl: FlipperLib | undefined) {
flipperLibInstance = impl; flipperLibInstance = impl;
} }

View File

@@ -14,7 +14,7 @@ import styled from '@emotion/styled';
import React, {MouseEvent, KeyboardEvent} from 'react'; import React, {MouseEvent, KeyboardEvent} from 'react';
import {theme} from '../theme'; import {theme} from '../theme';
import {Layout} from '../Layout'; import {Layout} from '../Layout';
import {tryGetFlipperLibImplementation} from 'flipper-plugin/src/plugin/FlipperLib'; import {_getFlipperLibImplementation} from 'flipper-plugin';
import {DownOutlined, RightOutlined} from '@ant-design/icons'; import {DownOutlined, RightOutlined} from '@ant-design/icons';
const {Text} = Typography; const {Text} = Typography;
@@ -221,7 +221,7 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
{ {
label: 'Copy', label: 'Copy',
click: () => { click: () => {
tryGetFlipperLibImplementation()?.writeTextToClipboard( _getFlipperLibImplementation()?.writeTextToClipboard(
props.onCopyExpandedTree(props.element, 0), props.onCopyExpandedTree(props.element, 0),
); );
}, },
@@ -229,7 +229,7 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
{ {
label: 'Copy expanded child elements', label: 'Copy expanded child elements',
click: () => click: () =>
tryGetFlipperLibImplementation()?.writeTextToClipboard( _getFlipperLibImplementation()?.writeTextToClipboard(
props.onCopyExpandedTree(props.element, 255), props.onCopyExpandedTree(props.element, 255),
), ),
}, },
@@ -253,7 +253,7 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
return { return {
label: `Copy ${o.name}`, label: `Copy ${o.name}`,
click: () => { click: () => {
tryGetFlipperLibImplementation()?.writeTextToClipboard(o.value); _getFlipperLibImplementation()?.writeTextToClipboard(o.value);
}, },
}; };
}), }),
@@ -555,7 +555,7 @@ export class Elements extends PureComponent<ElementsProps, ElementsState> {
) { ) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
tryGetFlipperLibImplementation()?.writeTextToClipboard( _getFlipperLibImplementation()?.writeTextToClipboard(
selectedElement.name, selectedElement.name,
); );
return; return;