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

@@ -20,7 +20,10 @@ import {
import {FlipperPlugin, FlipperDevicePlugin} from '../../plugin';
import {default as Client, ClientExport} from '../../Client';
import {selectedPlugins, State as PluginsState} from '../../reducers/plugins';
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
import {
createMockFlipperWithPlugin,
wrapSandy,
} from '../../test-utils/createMockFlipperWithPlugin';
import {
Notification,
TestUtils,
@@ -39,12 +42,16 @@ function testOnStatusMessage() {
// emtpy stub
}
class TestPlugin extends FlipperPlugin<any, any, any> {}
TestPlugin.title = 'TestPlugin';
TestPlugin.id = 'TestPlugin';
class TestDevicePlugin extends FlipperDevicePlugin<any, any, any> {}
TestDevicePlugin.title = 'TestDevicePlugin';
TestDevicePlugin.id = 'TestDevicePlugin';
class TestPluginOrig extends FlipperPlugin<any, any, any> {}
TestPluginOrig.title = 'TestPlugin';
TestPluginOrig.id = 'TestPlugin';
const TestPlugin = wrapSandy(TestPluginOrig);
class TestDevicePluginOrig extends FlipperDevicePlugin<any, any, any> {}
TestDevicePluginOrig.title = 'TestDevicePlugin';
TestDevicePluginOrig.id = 'TestDevicePlugin';
const TestDevicePlugin = wrapSandy(TestDevicePluginOrig);
const logger = {
track: () => {},
info: () => {},
@@ -193,7 +200,6 @@ test('test processStore function for empty state', async () => {
processStore({
activeNotifications: [],
device: null,
pluginStates: {},
clients: [],
devicePlugins: new Map(),
clientPlugins: new Map(),
@@ -216,7 +222,6 @@ test('test processStore function for an iOS device connected', async () => {
os: 'iOS',
screenshotHandle: null,
}),
pluginStates: {},
pluginStates2: {},
clients: [],
devicePlugins: new Map(),
@@ -238,8 +243,8 @@ test('test processStore function for an iOS device connected', async () => {
expect(deviceType).toEqual('emulator');
expect(title).toEqual('TestiPhone');
expect(os).toEqual('iOS');
const {pluginStates, activeNotifications} = json.store;
expect(pluginStates).toEqual({});
const {activeNotifications} = json.store;
expect(json.pluginStates2).toEqual({});
expect(activeNotifications).toEqual([]);
});
@@ -256,11 +261,11 @@ test('test processStore function for an iOS device connected with client plugin
const json = await processStore({
activeNotifications: [],
device,
pluginStates: {
[`${clientIdentifier}#TestPlugin`]: {msg: 'Test plugin'},
},
pluginStates2: {
[`${clientIdentifier}`]: {TestPlugin2: [{msg: 'Test plugin2'}]},
[clientIdentifier]: {
TestPlugin2: [{msg: 'Test plugin2'}],
TestPlugin: {msg: 'Test plugin'},
},
},
clients: [client],
devicePlugins: new Map(),
@@ -271,25 +276,16 @@ test('test processStore function for an iOS device connected with client plugin
if (!json) {
fail('json is undefined');
}
const {pluginStates} = json.store;
const expectedPluginState = {
[`${generateClientIdentifierWithSalt(
clientIdentifier,
'salt',
)}#TestPlugin`]: JSON.stringify({
msg: 'Test plugin',
}),
};
const expectedPluginState2 = {
[`${generateClientIdentifierWithSalt(clientIdentifier, 'salt')}`]: {
[generateClientIdentifierWithSalt(clientIdentifier, 'salt')]: {
TestPlugin2: [
{
msg: 'Test plugin2',
},
],
TestPlugin: {msg: 'Test plugin'},
},
};
expect(pluginStates).toEqual(expectedPluginState);
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({
activeNotifications: [],
device: selectedDevice,
pluginStates: {
[unselectedDeviceClientIdentifier + '#TestDevicePlugin']: {
msg: 'Test plugin unselected device',
pluginStates2: {
[unselectedDeviceClientIdentifier]: {
TestDevicePlugin: {
msg: 'Test plugin unselected device',
},
},
[selectedDeviceClientIdentifier + '#TestDevicePlugin']: {
msg: 'Test plugin selected device',
[selectedDeviceClientIdentifier]: {
TestDevicePlugin: {
msg: 'Test plugin selected device',
},
},
},
pluginStates2: {},
clients: [
selectedDeviceClient,
generateClientFromDevice(unselectedDevice, 'testapp'),
@@ -347,17 +346,18 @@ test('test processStore function to have only the client for the selected device
fail('json is undefined');
}
const {clients} = json;
const {pluginStates} = json.store;
const expectedPluginState = {
[generateClientIdentifierWithSalt(selectedDeviceClientIdentifier, 'salt') +
'#TestDevicePlugin']: JSON.stringify({
msg: 'Test plugin selected device',
}),
[generateClientIdentifierWithSalt(selectedDeviceClientIdentifier, 'salt')]:
{
TestDevicePlugin: {
msg: 'Test plugin selected device',
},
},
};
expect(clients).toEqual([
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 () => {
@@ -384,15 +384,18 @@ test('test processStore function to have multiple clients for the selected devic
const json = await processStore({
activeNotifications: [],
device: selectedDevice,
pluginStates: {
[clientIdentifierApp1 + '#TestPlugin']: {
msg: 'Test plugin App1',
pluginStates2: {
[clientIdentifierApp1]: {
TestPlugin: {
msg: 'Test plugin App1',
},
},
[clientIdentifierApp2 + '#TestPlugin']: {
msg: 'Test plugin App2',
[clientIdentifierApp2]: {
TestPlugin: {
msg: 'Test plugin App2',
},
},
},
pluginStates2: {},
clients: [
generateClientFromDevice(selectedDevice, 'testapp1'),
generateClientFromDevice(selectedDevice, 'testapp2'),
@@ -407,22 +410,23 @@ test('test processStore function to have multiple clients for the selected devic
fail('json is undefined');
}
const {clients} = json;
const {pluginStates} = json.store;
const expectedPluginState = {
[generateClientIdentifierWithSalt(clientIdentifierApp1, 'salt') +
'#TestPlugin']: JSON.stringify({
msg: 'Test plugin App1',
}),
[generateClientIdentifierWithSalt(clientIdentifierApp2, 'salt') +
'#TestPlugin']: JSON.stringify({
msg: 'Test plugin App2',
}),
[generateClientIdentifierWithSalt(clientIdentifierApp1, 'salt')]: {
TestPlugin: {
msg: 'Test plugin App1',
},
},
[generateClientIdentifierWithSalt(clientIdentifierApp2, 'salt')]: {
TestPlugin: {
msg: 'Test plugin App2',
},
},
};
expect(clients).toEqual([
generateClientFromClientWithSalt(client1, '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 () => {
@@ -437,12 +441,13 @@ test('test processStore function for device plugin state and no clients', async
const json = await processStore({
activeNotifications: [],
device: selectedDevice,
pluginStates: {
'serial#TestDevicePlugin': {
msg: 'Test Device plugin',
pluginStates2: {
serial: {
TestDevicePlugin: {
msg: 'Test Device plugin',
},
},
},
pluginStates2: {},
clients: [],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
clientPlugins: new Map(),
@@ -453,12 +458,11 @@ test('test processStore function for device plugin state and no clients', async
if (!json) {
fail('json is undefined');
}
const {pluginStates} = json.store;
const {clients} = json;
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([]);
});
@@ -474,12 +478,13 @@ test('test processStore function for unselected device plugin state and no clien
const json = await processStore({
activeNotifications: [],
device: selectedDevice,
pluginStates: {
'unselectedDeviceIdentifier#TestDevicePlugin': {
msg: 'Test Device plugin',
pluginStates2: {
unselectedDeviceIdentifier: {
TestDevicePlugin: {
msg: 'Test Device plugin',
},
},
},
pluginStates2: {},
clients: [],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
clientPlugins: new Map(),
@@ -489,9 +494,8 @@ test('test processStore function for unselected device plugin state and no clien
if (!json) {
fail('json is undefined');
}
const {pluginStates} = json.store;
const {clients} = json;
expect(pluginStates).toEqual({});
expect(json.pluginStates2).toEqual({});
expect(clients).toEqual([]);
});
@@ -519,7 +523,6 @@ test('test processStore function for notifications for selected device', async (
const json = await processStore({
activeNotifications: [activeNotification],
device: selectedDevice,
pluginStates: {},
pluginStates2: {},
clients: [client],
devicePlugins: new Map([['TestDevicePlugin', TestDevicePlugin]]),
@@ -531,9 +534,8 @@ test('test processStore function for notifications for selected device', async (
if (!json) {
fail('json is undefined');
}
const {pluginStates} = json.store;
const {clients} = json;
expect(pluginStates).toEqual({});
expect(json.pluginStates2).toEqual({});
expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]);
const {activeNotifications} = json.store;
const expectedActiveNotification = {
@@ -580,7 +582,6 @@ test('test processStore function for notifications for unselected device', async
const json = await processStore({
activeNotifications: [activeNotification],
device: selectedDevice,
pluginStates: {},
pluginStates2: {},
clients: [client, unselectedclient],
devicePlugins: new Map(),
@@ -591,9 +592,8 @@ test('test processStore function for notifications for unselected device', async
if (!json) {
fail('json is undefined');
}
const {pluginStates} = json.store;
const {clients} = json;
expect(pluginStates).toEqual({});
expect(json.pluginStates2).toEqual({});
expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]);
const {activeNotifications} = json.store;
expect(activeNotifications).toEqual([]);
@@ -610,18 +610,21 @@ test('test processStore function for selected plugins', async () => {
const client = generateClientFromDevice(selectedDevice, 'app');
const pluginstates = {
[generateClientIdentifier(selectedDevice, 'app') + '#TestDevicePlugin1']: {
msg: 'Test plugin1',
[generateClientIdentifier(selectedDevice, 'app')]: {
TestDevicePlugin1: {
msg: 'Test plugin1',
},
},
[generateClientIdentifier(selectedDevice, 'app') + '#TestDevicePlugin2']: {
msg: 'Test plugin2',
[generateClientIdentifier(selectedDevice, 'app')]: {
TestDevicePlugin2: {
msg: 'Test plugin2',
},
},
};
const json = await processStore({
activeNotifications: [],
device: selectedDevice,
pluginStates: pluginstates,
pluginStates2: {},
pluginStates2: pluginstates as any,
clients: [client],
devicePlugins: new Map([
['TestDevicePlugin1', TestDevicePlugin],
@@ -634,15 +637,16 @@ test('test processStore function for selected plugins', async () => {
if (!json) {
fail('json is undefined');
}
const {pluginStates} = json.store;
const {clients} = json;
expect(pluginStates).toEqual({
expect(json.pluginStates2).toEqual({
[generateClientIdentifierWithSalt(
generateClientIdentifier(selectedDevice, 'app'),
'salt',
) + '#TestDevicePlugin2']: JSON.stringify({
msg: 'Test plugin2',
}),
)]: {
TestDevicePlugin2: {
msg: 'Test plugin2',
},
},
});
expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]);
const {activeNotifications} = json.store;
@@ -659,18 +663,19 @@ test('test processStore function for no selected plugins', async () => {
});
const client = generateClientFromDevice(selectedDevice, 'app');
const pluginstates = {
[generateClientIdentifier(selectedDevice, 'app') + '#TestDevicePlugin1']: {
msg: 'Test plugin1',
},
[generateClientIdentifier(selectedDevice, 'app') + '#TestDevicePlugin2']: {
msg: 'Test plugin2',
[generateClientIdentifier(selectedDevice, 'app')]: {
TestDevicePlugin1: {
msg: 'Test plugin1',
},
TestDevicePlugin2: {
msg: 'Test plugin2',
},
},
};
const json = await processStore({
activeNotifications: [],
device: selectedDevice,
pluginStates: pluginstates,
pluginStates2: {},
pluginStates2: pluginstates as any,
clients: [client],
devicePlugins: new Map([
['TestDevicePlugin1', TestDevicePlugin],
@@ -684,21 +689,20 @@ test('test processStore function for no selected plugins', async () => {
if (!json) {
fail('json is undefined');
}
const {pluginStates} = json.store;
const {clients} = json;
expect(pluginStates).toEqual({
expect(json.pluginStates2).toEqual({
[generateClientIdentifierWithSalt(
generateClientIdentifier(selectedDevice, 'app'),
'salt',
) + '#TestDevicePlugin2']: JSON.stringify({
msg: 'Test plugin2',
}),
[generateClientIdentifierWithSalt(
generateClientIdentifier(selectedDevice, 'app'),
'salt',
) + '#TestDevicePlugin1']: JSON.stringify({
msg: 'Test plugin1',
}),
)]: {
TestDevicePlugin2: {
msg: 'Test plugin2',
},
TestDevicePlugin1: {
msg: 'Test plugin1',
},
},
});
expect(clients).toEqual([generateClientFromClientWithSalt(client, 'salt')]);
const {activeNotifications} = json.store;
@@ -781,14 +785,12 @@ test('test determinePluginsToProcess for mutilple clients having plugins present
pluginKey: `${client1.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1,
},
{
pluginKey: `${client3.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client3,
},
]);
@@ -905,7 +907,6 @@ test('test determinePluginsToProcess for multiple clients on same device', async
pluginKey: `${client1.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1,
},
]);
@@ -999,7 +1000,6 @@ test('test determinePluginsToProcess for multiple clients on different device',
pluginKey: `${client1Device2.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
client: client1Device2,
},
]);
@@ -1085,7 +1085,6 @@ test('test determinePluginsToProcess to ignore archived clients', async () => {
pluginKey: `${client.id}#TestPlugin`,
pluginId: 'TestPlugin',
pluginName: 'TestPlugin',
pluginClass: TestPlugin,
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
*/
import {FlipperDevicePlugin} from '../../plugin';
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
import {FlipperPlugin} from '../../plugin';
import {
createMockFlipperWithPlugin,
wrapSandy,
} from '../../test-utils/createMockFlipperWithPlugin';
import {Store, Client, sleep} from '../../';
import {
selectPlugin,
@@ -26,6 +29,10 @@ import {
_SandyPluginInstance,
} from 'flipper-plugin';
import {switchPlugin} from '../../reducers/pluginManager';
import pluginMessageQueue, {
State,
queueMessages,
} from '../../reducers/pluginMessageQueue';
type Events = {
inc: {
@@ -124,7 +131,6 @@ test('queue - events are NOT processed immediately if plugin is NOT selected (bu
sendMessage('inc', {});
sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3});
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
expect(getTestPluginState(client).count).toBe(0);
// the first message is already visible cause of the leading debounce
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 () => {
class StubDeviceLogs extends FlipperDevicePlugin<any, any, any> {
static id = 'DevicePlugin';
static supportsDevice() {
return true;
}
class StubPlugin extends FlipperPlugin<any, any, any> {
static id = 'StubPlugin';
static persistedStateReducer = jest.fn();
}
const StubPluginWrapped = wrapSandy(StubPlugin);
const {client, store, device, sendMessage, pluginKey} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
additionalPlugins: [StubPluginWrapped],
});
selectDeviceLogs(store);
store.dispatch(registerPlugins([StubDeviceLogs]));
store.dispatch(registerPlugins([StubPluginWrapped]));
sendMessage('inc', {});
sendMessage('inc', {delta: 2});
sendMessage('inc', {delta: 3});
@@ -506,14 +512,13 @@ test('client - incoming messages are buffered and flushed together', async () =>
JSON.stringify({
method: 'execute',
params: {
api: 'DevicePlugin',
api: 'StubPlugin',
method: 'log',
params: {line: 'suff'},
},
}),
);
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
expect(getTestPluginState(client).count).toBe(0);
// the first message is already visible cause of the leading debounce
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
@@ -529,17 +534,17 @@ test('client - incoming messages are buffered and flushed together', async () =>
`);
expect(client.messageBuffer).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Object {
"TestApp#Android#MockAndroidDevice#serial#StubPlugin": Object {
"messages": Array [
Object {
"api": "DevicePlugin",
"api": "StubPlugin",
"method": "log",
"params": Object {
"line": "suff",
},
},
],
"plugin": [Function],
"plugin": "[SandyPluginInstance]",
},
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
"messages": Array [
@@ -569,9 +574,9 @@ test('client - incoming messages are buffered and flushed together', async () =>
await sleep(500);
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Array [
"TestApp#Android#MockAndroidDevice#serial#StubPlugin": Array [
Object {
"api": "DevicePlugin",
"api": "StubPlugin",
"method": "log",
"params": Object {
"line": "suff",
@@ -602,19 +607,22 @@ test('client - incoming messages are buffered and flushed together', async () =>
}
`);
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
expect(StubDeviceLogs.persistedStateReducer.mock.calls).toMatchInlineSnapshot(
expect(StubPlugin.persistedStateReducer.mock.calls).toMatchInlineSnapshot(
`Array []`,
);
// tigger processing the queue
const pluginKeyDevice = getPluginKey(client.id, device, StubDeviceLogs.id);
await processMessageQueue(StubDeviceLogs, pluginKeyDevice, store);
const pluginKeyDevice = getPluginKey(client.id, device, StubPlugin.id);
await processMessageQueue(
client.sandyPluginStates.get(StubPlugin.id)!,
pluginKeyDevice,
store,
);
expect(StubDeviceLogs.persistedStateReducer.mock.calls)
.toMatchInlineSnapshot(`
expect(StubPlugin.persistedStateReducer.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
Object {},
undefined,
"log",
Object {
"line": "suff",
@@ -625,7 +633,7 @@ test('client - incoming messages are buffered and flushed together', async () =>
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#DevicePlugin": Array [],
"TestApp#Android#MockAndroidDevice#serial#StubPlugin": Array [],
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
Object {
"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});
});
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 () => {
const {store, device, client} = await createMockFlipperWithPlugin(
const {store} = await createMockFlipperWithPlugin(
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
{
additionalPlugins: [
@@ -85,11 +85,6 @@ test('getActivePersistentPlugins, where the non persistent plugins getting exclu
);
const state = store.getState();
state.pluginStates = {
[getPluginKey(client.id, device, 'ClientPlugin1')]: {msg: 'DevicePlugin1'},
[getPluginKey(client.id, device, 'ClientPlugin4')]: {msg: 'ClientPlugin2'},
};
const list = getExportablePlugins(state);
expect(list).toEqual([
{
@@ -97,23 +92,23 @@ test('getActivePersistentPlugins, where the non persistent plugins getting exclu
label: 'ClientPlugin1',
},
{
id: 'ClientPlugin4',
label: 'ClientPlugin4',
id: 'ClientPlugin2',
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(
createMockFlipperPluginWithDefaultPersistedState('Plugin1'),
{
additionalPlugins: [
createMockDeviceFlipperPlugin('DevicePlugin2'),
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin1'),
createMockFlipperPluginWithNoPersistedState('ClientPlugin1'),
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin2'),
createMockFlipperPluginWithDefaultPersistedState('ClientPlugin3'),
],
@@ -122,9 +117,6 @@ test('getActivePersistentPlugins, where the plugins not in pluginState or queue
const state = store.getState();
state.pluginStates = {
[getPluginKey(client.id, device, 'ClientPlugin2')]: {msg: 'ClientPlugin2'},
};
state.pluginMessageQueue = {
[getPluginKey(client.id, device, 'ClientPlugin3')]: [
{method: 'msg', params: {msg: 'ClientPlugin3'}},

View File

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

View File

@@ -11,21 +11,14 @@ import os from 'os';
import path from 'path';
import electron from 'electron';
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 {State as PluginStatesState} from '../reducers/pluginStates';
import {State as PluginsState} from '../reducers/plugins';
import {PluginNotification} from '../reducers/notifications';
import Client, {ClientExport, ClientQuery} from '../Client';
import {getAppVersion} from './info';
import {pluginKey} from '../reducers/pluginStates';
import {
callClient,
supportsMethod,
PluginDefinition,
DevicePluginMap,
ClientPluginMap,
} from '../plugin';
import {pluginKey} from '../utils/pluginUtils';
import {DevicePluginMap, ClientPluginMap} from '../plugin';
import {default as BaseDevice} from '../devices/BaseDevice';
import {default as ArchivedDevice} from '../devices/ArchivedDevice';
import fs from 'fs';
@@ -34,7 +27,6 @@ import {remote, OpenDialogOptions} from 'electron';
import {readCurrentRevision} from './packageMetadata';
import {tryCatchReportPlatformFailures} from './metrics';
import {promisify} from 'util';
import promiseTimeout from './promiseTimeout';
import {TestIdler} from './Idler';
import {setStaticView} from '../reducers/connections';
import {
@@ -42,10 +34,10 @@ import {
SupportFormRequestDetailsState,
} from '../reducers/supportForm';
import {setSelectPluginsToExportActiveSheet} from '../reducers/application';
import {deconstructClientId, deconstructPluginKey} from '../utils/clientUtils';
import {deconstructClientId} from '../utils/clientUtils';
import {performance} from 'perf_hooks';
import {processMessageQueue} from './messageQueue';
import {getPluginTitle, isSandyPlugin} from './pluginUtils';
import {getPluginTitle} from './pluginUtils';
import {capture} from './screenshot';
import {uploadFlipperMedia} from '../fb-stubs/user';
import {Idler} from 'flipper-plugin';
@@ -71,7 +63,6 @@ export type ExportType = {
device: DeviceExport | null;
deviceScreenshot: string | null;
store: {
pluginStates: PluginStatesExportState;
activeNotifications: Array<PluginNotification>;
};
// The GraphQL plugin relies on this format for generating
@@ -80,15 +71,6 @@ export type ExportType = {
supportRequestDetails?: SupportFormRequestDetailsState;
};
type ProcessPluginStatesOptions = {
clients: Array<ClientExport>;
serial: string;
allPluginStates: PluginStatesState;
devicePlugins: DevicePluginMap;
selectedPlugins: Array<string>;
statusUpdate?: (msg: string) => void;
};
type ProcessNotificationStatesOptions = {
clients: Array<ClientExport>;
serial: string;
@@ -101,7 +83,6 @@ type PluginsToProcess = {
pluginKey: string;
pluginId: string;
pluginName: string;
pluginClass: PluginDefinition;
client: Client;
}[];
@@ -110,7 +91,6 @@ type AddSaltToDeviceSerialOptions = {
device: BaseDevice;
deviceScreenshot: string | null;
clients: Array<ClientExport>;
pluginStates: PluginStatesExportState;
pluginStates2: SandyPluginStates;
devicePluginStates: Record<string, any>;
pluginNotification: Array<PluginNotification>;
@@ -152,51 +132,6 @@ export function processClients(
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(
options: ProcessNotificationStatesOptions,
): Array<PluginNotification> {
@@ -216,41 +151,6 @@ export function processNotificationStates(
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(
pluginsToProcess: PluginsToProcess,
idler: Idler,
@@ -258,8 +158,8 @@ async function exportSandyPluginStates(
): Promise<SandyPluginStates> {
const res: SandyPluginStates = {};
for (const key in pluginsToProcess) {
const {pluginId, client, pluginClass} = pluginsToProcess[key];
if (isSandyPlugin(pluginClass) && client.sandyPluginStates.has(pluginId)) {
const {pluginId, client} = pluginsToProcess[key];
if (client.sandyPluginStates.has(pluginId)) {
if (!res[client.id]) {
res[client.id] = {};
}
@@ -276,33 +176,6 @@ async function exportSandyPluginStates(
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>>(
collection: T,
baseSerial: string,
@@ -311,9 +184,7 @@ function replaceSerialsInKeys<T extends Record<string, any>>(
const result: Record<string, any> = {};
for (const key in collection) {
if (!key.includes(baseSerial)) {
throw new Error(
`Error while exporting, plugin state (${key}) does not have ${baseSerial} in its key`,
);
continue;
}
result[key.replace(baseSerial, newSerial)] = collection[key];
}
@@ -325,7 +196,6 @@ async function addSaltToDeviceSerial({
device,
deviceScreenshot,
clients,
pluginStates,
pluginNotification,
statusUpdate,
pluginStates2,
@@ -354,11 +224,6 @@ async function addSaltToDeviceSerial({
statusUpdate(
'Adding salt to the selected device id in the plugin states...',
);
const updatedPluginStates = replaceSerialsInKeys(
pluginStates,
serial,
newSerial,
);
const updatedPluginStates2 = replaceSerialsInKeys(
pluginStates2,
serial,
@@ -385,7 +250,6 @@ async function addSaltToDeviceSerial({
device: {...newDevice.toJSON(), pluginStates: devicePluginStates},
deviceScreenshot: deviceScreenshot,
store: {
pluginStates: updatedPluginStates,
activeNotifications: updatedPluginNotifications,
},
pluginStates2: updatedPluginStates2,
@@ -395,7 +259,6 @@ async function addSaltToDeviceSerial({
type ProcessStoreOptions = {
activeNotifications: Array<PluginNotification>;
device: BaseDevice | null;
pluginStates: PluginStatesState;
pluginStates2: SandyPluginStates;
clients: Array<ClientExport>;
devicePlugins: DevicePluginMap;
@@ -409,11 +272,9 @@ export async function processStore(
{
activeNotifications,
device,
pluginStates,
pluginStates2,
clients,
devicePlugins,
clientPlugins,
salt,
selectedPlugins,
statusUpdate,
@@ -435,14 +296,7 @@ export async function processStore(
})
: null;
const processedClients = processClients(clients, serial, statusUpdate);
const processedPluginStates = processPluginStates({
clients: processedClients,
serial,
allPluginStates: pluginStates,
devicePlugins,
selectedPlugins,
statusUpdate,
});
const processedActiveNotifications = processNotificationStates({
clients: processedClients,
serial,
@@ -451,14 +305,6 @@ export async function processStore(
statusUpdate,
});
const exportPluginState = await serializePluginStates(
processedPluginStates,
clientPlugins,
devicePlugins,
statusUpdate,
idler,
);
const devicePluginStates = await device.exportState(
idler,
statusUpdate,
@@ -478,7 +324,6 @@ export async function processStore(
device,
deviceScreenshot: deviceScreenshotLink,
clients: processedClients,
pluginStates: exportPluginState,
pluginNotification: processedActiveNotifications,
statusUpdate,
selectedPlugins,
@@ -492,116 +337,35 @@ export async function processStore(
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(
store: MiddlewareAPI,
pluginsToProcess: PluginsToProcess,
statusUpdate?: (msg: string) => void,
idler?: Idler,
) {
for (const {
pluginName,
pluginId,
pluginKey,
pluginClass,
client,
} of pluginsToProcess) {
if (isSandyPlugin(pluginClass) || pluginClass.persistedStateReducer) {
client.flushMessageBuffer();
const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`;
performance.mark(processQueueMarker);
const plugin = isSandyPlugin(pluginClass)
? client.sandyPluginStates.get(pluginId)
: pluginClass;
if (!plugin) continue;
await processMessageQueue(
plugin,
pluginKey,
store,
({current, total}) => {
statusUpdate?.(
`Processing event ${current} / ${total} (${Math.round(
(current / total) * 100,
)}%) for plugin ${pluginName}`,
);
},
idler,
);
for (const {pluginName, pluginId, pluginKey, client} of pluginsToProcess) {
client.flushMessageBuffer();
const processQueueMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:process-queue-per-plugin`;
performance.mark(processQueueMarker);
const plugin = client.sandyPluginStates.get(pluginId);
if (!plugin) continue;
await processMessageQueue(
plugin,
pluginKey,
store,
({current, total}) => {
statusUpdate?.(
`Processing event ${current} / ${total} (${Math.round(
(current / total) * 100,
)}%) for plugin ${pluginName}`,
);
},
idler,
);
getLogger().trackTimeSince(processQueueMarker, processQueueMarker, {
pluginId,
});
}
getLogger().trackTimeSince(processQueueMarker, processQueueMarker, {
pluginId,
});
}
}
@@ -638,7 +402,6 @@ export function determinePluginsToProcess(
client,
pluginId: plugin,
pluginName: getPluginTitle(pluginClass),
pluginClass,
});
}
}
@@ -671,14 +434,6 @@ async function getStoreExport(
performance.mark(fetchMetaDataMarker);
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
? await exportSandyPluginStates(pluginsToProcess, idler, statusUpdate)
@@ -687,7 +442,6 @@ async function getStoreExport(
getLogger().trackTimeSince(fetchMetaDataMarker, fetchMetaDataMarker, {
plugins: state.plugins.selectedPlugins,
});
const {errors} = metadata;
const {activeNotifications} = state.notifications;
const {devicePlugins, clientPlugins} = state.plugins;
@@ -695,7 +449,6 @@ async function getStoreExport(
{
activeNotifications,
device: selectedDevice,
pluginStates: newPluginState,
pluginStates2,
clients: client ? [client.toJSON()] : [],
devicePlugins,
@@ -706,7 +459,7 @@ async function getStoreExport(
},
idler,
);
return {exportData, fetchMetaDataErrors: errors};
return {exportData, fetchMetaDataErrors: null};
}
export async function exportStore(
@@ -810,34 +563,9 @@ export function importDataToStore(source: string, data: string, store: Store) {
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}) => {
const sandyPluginStates = json.pluginStates2[client.id] || {};
const clientPlugins: Set<string> = new Set([
...keys
.filter((key) => {
const plugin = deconstructPluginKey(key);
return plugin.type === 'client' && client.id === plugin.client;
})
.map((pluginKey) => deconstructPluginKey(pluginKey).pluginName),
...Object.keys(sandyPluginStates),
]);
const clientPlugins = new Set(Object.keys(sandyPluginStates));
store.dispatch({
type: 'NEW_CLIENT',
payload: new Client(

View File

@@ -7,9 +7,8 @@
* @format
*/
import {PersistedStateReducer, FlipperDevicePlugin} from '../plugin';
import type {State, MiddlewareAPI} from '../reducers/index';
import {setPluginState} from '../reducers/pluginStates';
import {FlipperDevicePlugin} from '../plugin';
import type {MiddlewareAPI} from '../reducers/index';
import {
clearMessageQueue,
queueMessages,
@@ -23,31 +22,7 @@ import {defaultEnabledBackgroundPlugins} from './pluginUtils';
import {batch, Idler, _SandyPluginInstance} from 'flipper-plugin';
import {addBackgroundStat} from './pluginStats';
function processMessageClassic(
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(
function processMessagesImmediately(
plugin: _SandyPluginInstance,
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(
store: MiddlewareAPI,
pluginKey: string,
plugin:
| {
defaultPersistedState: any;
id: string;
persistedStateReducer: PersistedStateReducer | null;
maxQueueSize?: number;
}
| _SandyPluginInstance,
plugin: _SandyPluginInstance,
messages: Message[],
) {
const pluginId =
plugin instanceof _SandyPluginInstance ? plugin.definition.id : plugin.id;
const pluginId = plugin.definition.id;
const isSelected =
pluginKey === getSelectedPluginKey(store.getState().connections);
switch (true) {
// Navigation events are always processed immediately, to make sure the navbar stays up to date, see also T69991064
case pluginId === 'Navigation':
case isSelected && getPendingMessages(store, pluginKey).length === 0:
processMessagesImmediately(store, pluginKey, plugin, messages);
processMessagesImmediately(plugin, messages);
break;
case isSelected:
case plugin instanceof _SandyPluginInstance:
@@ -129,13 +64,7 @@ export function processMessagesLater(
pluginId,
):
store.dispatch(
queueMessages(
pluginKey,
messages,
plugin instanceof _SandyPluginInstance
? DEFAULT_MAX_QUEUE_SIZE
: plugin.maxQueueSize,
),
queueMessages(pluginKey, messages, DEFAULT_MAX_QUEUE_SIZE),
);
break;
default:
@@ -148,21 +77,12 @@ export function processMessagesLater(
}
export async function processMessageQueue(
plugin:
| {
defaultPersistedState: any;
id: string;
persistedStateReducer: PersistedStateReducer | null;
}
| _SandyPluginInstance,
plugin: _SandyPluginInstance,
pluginKey: string,
store: MiddlewareAPI,
progressCallback?: (progress: {current: number; total: number}) => void,
idler: Idler = new IdlerImpl(),
): Promise<boolean> {
if (!_SandyPluginInstance.is(plugin) && !plugin.persistedStateReducer) {
return true;
}
const total = getPendingMessages(store, pluginKey).length;
let progress = 0;
do {
@@ -171,25 +91,11 @@ export async function processMessageQueue(
break;
}
// 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 newPluginState = persistedState;
batch(() => {
do {
if (_SandyPluginInstance.is(plugin)) {
// Optimization: we could send a batch of messages here
processMessagesSandy(plugin, [messages[offset]]);
} else {
newPluginState = processMessageClassic(
newPluginState,
pluginKey,
plugin,
messages[offset],
);
}
// Optimization: we could send a batch of messages here
processMessagesImmediately(plugin, [messages[offset]]);
offset++;
progress++;
@@ -203,17 +109,6 @@ export async function processMessageQueue(
// resistent to kicking off this process twice; grabbing, processing messages, saving state is done synchronosly
// until the idler has to break
store.dispatch(clearMessageQueue(pluginKey, offset));
if (
!_SandyPluginInstance.is(plugin) &&
newPluginState !== persistedState
) {
store.dispatch(
setPluginState({
pluginKey,
state: newPluginState,
}),
);
}
});
if (idler.isCancelled()) {
@@ -232,15 +127,3 @@ function getPendingMessages(
): Message[] {
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
*/
import {
FlipperDevicePlugin,
FlipperBasePlugin,
PluginDefinition,
DevicePluginDefinition,
ClientPluginDefinition,
} from '../plugin';
import {PluginDefinition} from '../plugin';
import type {State} from '../reducers';
import type {State as PluginStatesState} from '../reducers/pluginStates';
import type {State as PluginsState} from '../reducers/plugins';
import {_SandyPluginDefinition} from 'flipper-plugin';
import type BaseDevice from '../devices/BaseDevice';
import type Client from '../Client';
import type {
@@ -29,9 +21,9 @@ import type {
import {getLatestCompatibleVersionOfEachPlugin} from '../dispatcher/plugins';
export type PluginLists = {
devicePlugins: DevicePluginDefinition[];
metroPlugins: DevicePluginDefinition[];
enabledPlugins: ClientPluginDefinition[];
devicePlugins: PluginDefinition[];
metroPlugins: PluginDefinition[];
enabledPlugins: PluginDefinition[];
disabledPlugins: PluginDefinition[];
unavailablePlugins: [plugin: PluginDetails, reason: string][];
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
@@ -86,33 +78,12 @@ export function getPluginKey(
return `unknown#${pluginID}`;
}
export function isSandyPlugin(
plugin?: PluginDefinition | null,
): 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 const pluginKey = (serial: string, pluginName: string): string => {
return `${serial}#${pluginName}`;
};
export function computeExportablePlugins(
state: Pick<
State,
'plugins' | 'connections' | 'pluginStates' | 'pluginMessageQueue'
>,
state: Pick<State, 'plugins' | 'connections' | 'pluginMessageQueue'>,
device: BaseDevice | null,
client: Client | null,
availablePlugins: PluginLists,
@@ -131,25 +102,14 @@ export function computeExportablePlugins(
}
function isExportablePlugin(
{
pluginStates,
pluginMessageQueue,
}: Pick<State, 'pluginStates' | 'pluginMessageQueue'>,
{pluginMessageQueue}: Pick<State, 'pluginMessageQueue'>,
device: BaseDevice | null,
client: Client | null,
plugin: PluginDefinition,
): boolean {
// can generate an export when requested
if (!isSandyPlugin(plugin) && plugin.exportPersistedState) {
return true;
}
const pluginKey = isDevicePluginDefinition(plugin)
? getPluginKey(undefined, device, plugin.id)
: getPluginKey(client?.id, undefined, plugin.id);
// plugin has exportable redux state
if (pluginStates[pluginKey]) {
return true;
}
// plugin has exportable sandy state
if (client?.sandyPluginStates.get(plugin.id)?.isPersistable()) {
return true;
@@ -158,10 +118,7 @@ function isExportablePlugin(
return true;
}
// plugin has pending messages and a persisted state reducer or isSandy
if (
pluginMessageQueue[pluginKey] &&
((plugin as any).defaultPersistedState || isSandyPlugin(plugin))
) {
if (pluginMessageQueue[pluginKey]) {
return true;
}
// nothing to serialize
@@ -201,11 +158,8 @@ export function isDevicePlugin(activePlugin: ActivePluginListItem) {
export function isDevicePluginDefinition(
definition: PluginDefinition,
): definition is DevicePluginDefinition {
return (
(definition as any).prototype instanceof FlipperDevicePlugin ||
(definition instanceof _SandyPluginDefinition && definition.isDevicePlugin)
);
): boolean {
return definition.isDevicePlugin;
}
export function getPluginTooltip(details: PluginDetails): string {
@@ -234,9 +188,9 @@ export function computePluginLists(
metroDevice: BaseDevice | null,
client: Client | null,
): {
devicePlugins: DevicePluginDefinition[];
metroPlugins: DevicePluginDefinition[];
enabledPlugins: ClientPluginDefinition[];
devicePlugins: PluginDefinition[];
metroPlugins: PluginDefinition[];
enabledPlugins: PluginDefinition[];
disabledPlugins: PluginDefinition[];
unavailablePlugins: [plugin: PluginDetails, reason: string][];
downloadablePlugins: (DownloadablePluginDetails | BundledPluginDetails)[];
@@ -247,17 +201,13 @@ export function computePluginLists(
...plugins.bundledPlugins.values(),
...plugins.marketplacePlugins,
]).filter((p) => !plugins.loadedPlugins.has(p.id));
const devicePlugins: DevicePluginDefinition[] = [
...plugins.devicePlugins.values(),
]
const devicePlugins: PluginDefinition[] = [...plugins.devicePlugins.values()]
.filter((p) => device?.supportsPlugin(p))
.filter((p) => enabledDevicePluginsState.has(p.id));
const metroPlugins: DevicePluginDefinition[] = [
...plugins.devicePlugins.values(),
]
const metroPlugins: PluginDefinition[] = [...plugins.devicePlugins.values()]
.filter((p) => metroDevice?.supportsPlugin(p))
.filter((p) => enabledDevicePluginsState.has(p.id));
const enabledPlugins: ClientPluginDefinition[] = [];
const enabledPlugins: PluginDefinition[] = [];
const disabledPlugins: PluginDefinition[] = [
...plugins.devicePlugins.values(),
]