Store clients as Map rather than array

Summary: Refactor clients storage: array -> map. A lot of logic looks up clients by their id, which is currently done with an array.find operation, which is pretty inefficient. This diff changes it to a map, that is pretty important, as in the next diff the decoupled client message handing will need to find the client again for every message that arrives.

Reviewed By: timur-valiev

Differential Revision: D31303536

fbshipit-source-id: ca3f540a3de7665930d2354436d37cb0fbfd5546
This commit is contained in:
Michel Weststrate
2021-10-04 07:26:11 -07:00
committed by Facebook GitHub Bot
parent c9a34d3cc2
commit 026f8fc308
19 changed files with 105 additions and 123 deletions

View File

@@ -2,8 +2,8 @@
exports[`can create a Fake flipper with legacy wrapper 1`] = ` exports[`can create a Fake flipper with legacy wrapper 1`] = `
Object { Object {
"clients": Array [ "clients": Map {
Object { "TestApp#Android#MockAndroidDevice#serial" => Object {
"id": "TestApp#Android#MockAndroidDevice#serial", "id": "TestApp#Android#MockAndroidDevice#serial",
"query": Object { "query": Object {
"app": "TestApp", "app": "TestApp",
@@ -13,7 +13,7 @@ Object {
"sdk_version": 4, "sdk_version": 4,
}, },
}, },
], },
"deepLinkPayload": null, "deepLinkPayload": null,
"devices": Array [ "devices": Array [
Object { Object {

View File

@@ -10,6 +10,7 @@
import {createMockFlipperWithPlugin} from '../test-utils/createMockFlipperWithPlugin'; import {createMockFlipperWithPlugin} from '../test-utils/createMockFlipperWithPlugin';
import {FlipperPlugin} from '../plugin'; import {FlipperPlugin} from '../plugin';
import {TestIdler} from '../utils/Idler'; import {TestIdler} from '../utils/Idler';
import {getAllClients} from '../reducers/connections';
interface PersistedState { interface PersistedState {
count: 1; count: 1;
@@ -60,8 +61,8 @@ test('can create a Fake flipper with legacy wrapper', async () => {
expect(state.plugins).toMatchSnapshot(); expect(state.plugins).toMatchSnapshot();
sendMessage('inc', {}); sendMessage('inc', {});
expect( expect(
await state.connections.clients[0].sandyPluginStates await getAllClients(state.connections)[0]
.get(TestPlugin.id)! .sandyPluginStates.get(TestPlugin.id)!
.exportState(testIdler, testOnStatusMessage), .exportState(testIdler, testOnStatusMessage),
).toMatchInlineSnapshot(`"{\\"count\\":1}"`); ).toMatchInlineSnapshot(`"{\\"count\\":1}"`);
}); });

View File

@@ -41,7 +41,7 @@ type StateFromProps = {
gatekeepedPlugins: Array<PluginDetails>; gatekeepedPlugins: Array<PluginDetails>;
disabledPlugins: Array<PluginDetails>; disabledPlugins: Array<PluginDetails>;
failedPlugins: Array<[PluginDetails, string]>; failedPlugins: Array<[PluginDetails, string]>;
clients: Array<Client>; clients: Map<string, Client>;
selectedDevice: string | null | undefined; selectedDevice: string | null | undefined;
devicePlugins: PluginDefinition[]; devicePlugins: PluginDefinition[];
clientPlugins: PluginDefinition[]; clientPlugins: PluginDefinition[];
@@ -126,7 +126,7 @@ class PluginDebugger extends Component<Props> {
} }
getSupportedClients(id: string): string { getSupportedClients(id: string): string {
return this.props.clients return Array.from(this.props.clients.values())
.reduce((acc: Array<string>, cv: Client) => { .reduce((acc: Array<string>, cv: Client) => {
if (cv.plugins.has(id)) { if (cv.plugins.has(id)) {
acc.push(cv.query.app); acc.push(cv.query.app);

View File

@@ -16,7 +16,7 @@ import {Group, SUPPORTED_GROUPS} from './reducers/supportForm';
import {Logger} from './fb-interfaces/Logger'; import {Logger} from './fb-interfaces/Logger';
import {Store} from './reducers/index'; import {Store} from './reducers/index';
import {importDataToStore} from './utils/exportData'; import {importDataToStore} from './utils/exportData';
import {selectPlugin} from './reducers/connections'; import {selectPlugin, getAllClients} from './reducers/connections';
import {Dialog} from 'flipper-plugin'; import {Dialog} from 'flipper-plugin';
import {handleOpenPluginDeeplink} from './dispatcher/handleOpenPluginDeeplink'; import {handleOpenPluginDeeplink} from './dispatcher/handleOpenPluginDeeplink';
import {message} from 'antd'; import {message} from 'antd';
@@ -124,13 +124,11 @@ export async function handleDeeplink(
: undefined; : undefined;
// if a client is specified, find it, withing the device if applicable // if a client is specified, find it, withing the device if applicable
const selectedClient = store const selectedClient = getAllClients(store.getState().connections).find(
.getState() (c) =>
.connections.clients.find( c.query.app === match[0] &&
(c) => (selectedDevice == null || c.device === selectedDevice),
c.query.app === match[0] && );
(selectedDevice == null || c.device === selectedDevice),
);
store.dispatch( store.dispatch(
selectPlugin({ selectPlugin({

View File

@@ -144,7 +144,7 @@ export default async (store: Store, logger: Logger) => {
export async function handleClientConnected(store: Store, client: Client) { export async function handleClientConnected(store: Store, client: Client) {
const {connections} = store.getState(); const {connections} = store.getState();
const existingClient = connections.clients.find((c) => c.id === client.id); const existingClient = connections.clients.get(client.id);
if (existingClient) { if (existingClient) {
existingClient.destroy(); existingClient.destroy();

View File

@@ -28,6 +28,7 @@ import BaseDevice from '../devices/BaseDevice';
import Client from '../Client'; import Client from '../Client';
import {RocketOutlined} from '@ant-design/icons'; import {RocketOutlined} from '@ant-design/icons';
import {showEmulatorLauncher} from '../sandy-chrome/appinspect/LaunchEmulator'; import {showEmulatorLauncher} from '../sandy-chrome/appinspect/LaunchEmulator';
import {getAllClients} from '../reducers/connections';
type OpenPluginParams = { type OpenPluginParams = {
pluginId: string; pluginId: string;
@@ -432,9 +433,8 @@ async function selectDevicesAndClient(
// wait for valid client // wait for valid client
while (true) { while (true) {
const validClients = store const validClients = getAllClients(store.getState().connections)
.getState() .filter(
.connections.clients.filter(
// correct app name, or, if not set, an app that at least supports this plugin // correct app name, or, if not set, an app that at least supports this plugin
(c) => (c) =>
params.client params.client

View File

@@ -30,6 +30,7 @@ import {reportPlatformFailures, reportUsage} from '../utils/metrics';
import {loadPlugin} from '../reducers/pluginManager'; import {loadPlugin} from '../reducers/pluginManager';
import {showErrorNotification} from '../utils/notifications'; import {showErrorNotification} from '../utils/notifications';
import {pluginInstalled} from '../reducers/plugins'; import {pluginInstalled} from '../reducers/plugins';
import {getAllClients} from '../reducers/connections';
// Adapter which forces node.js implementation for axios instead of browser implementation // Adapter which forces node.js implementation for axios instead of browser implementation
// used by default in Electron. Node.js implementation is better, because it // used by default in Electron. Node.js implementation is better, because it
@@ -167,7 +168,7 @@ function pluginIsDisabledForAllConnectedClients(
) { ) {
return ( return (
!state.plugins.clientPlugins.has(plugin.id) || !state.plugins.clientPlugins.has(plugin.id) ||
!state.connections.clients.some((c) => !getAllClients(state.connections).some((c) =>
state.connections.enabledPlugins[c.query.app]?.includes(plugin.id), state.connections.enabledPlugins[c.query.app]?.includes(plugin.id),
) )
); );

View File

@@ -40,6 +40,8 @@ import {
setDevicePluginDisabled, setDevicePluginDisabled,
setPluginEnabled, setPluginEnabled,
setPluginDisabled, setPluginDisabled,
getClientsByAppName,
getAllClients,
} from '../reducers/connections'; } from '../reducers/connections';
import {deconstructClientId} from '../utils/clientUtils'; import {deconstructClientId} from '../utils/clientUtils';
import {clearMessageQueue} from '../reducers/pluginMessageQueue'; import {clearMessageQueue} from '../reducers/pluginMessageQueue';
@@ -195,9 +197,7 @@ function switchClientPlugin(
return; return;
} }
const {connections} = store.getState(); const {connections} = store.getState();
const clients = connections.clients.filter( const clients = getClientsByAppName(connections.clients, selectedApp);
(client) => client.query.app === selectedApp,
);
if (connections.enabledPlugins[selectedApp]?.includes(plugin.id)) { if (connections.enabledPlugins[selectedApp]?.includes(plugin.id)) {
clients.forEach((client) => { clients.forEach((client) => {
stopPlugin(client, plugin.id); stopPlugin(client, plugin.id);
@@ -240,7 +240,7 @@ function updateClientPlugin(
plugin: PluginDefinition, plugin: PluginDefinition,
enable: boolean, enable: boolean,
) { ) {
const clients = store.getState().connections.clients; const clients = getAllClients(store.getState().connections);
if (enable) { if (enable) {
const selectedApp = getSelectedAppName(store); const selectedApp = getSelectedAppName(store);
if (selectedApp) { if (selectedApp) {

View File

@@ -15,7 +15,6 @@ import {EventEmitter} from 'events';
import {State, Store} from '../reducers/index'; import {State, Store} from '../reducers/index';
import {Logger} from '../fb-interfaces/Logger'; import {Logger} from '../fb-interfaces/Logger';
import Client from '../Client';
import { import {
getPluginBackgroundStats, getPluginBackgroundStats,
resetPluginBackgroundStatsDelta, resetPluginBackgroundStatsDelta,
@@ -225,7 +224,7 @@ export default (store: Store, logger: Logger) => {
let sdkVersion: number | null = null; let sdkVersion: number | null = null;
if (selectedAppId) { if (selectedAppId) {
const client = clients.find((c: Client) => c.id === selectedAppId); const client = clients.get(selectedAppId);
if (client) { if (client) {
app = client.query.app; app = client.query.app;
sdkVersion = client.query.sdk_version || 0; sdkVersion = client.query.sdk_version || 0;

View File

@@ -128,7 +128,7 @@ test('can handle plugins that throw at start', async () => {
// not initialized // not initialized
expect(client.sandyPluginStates.get(TestPlugin.id)).toBe(undefined); expect(client.sandyPluginStates.get(TestPlugin.id)).toBe(undefined);
expect(store.getState().connections.clients.length).toBe(1); expect(store.getState().connections.clients.size).toBe(1);
expect(client.connected.get()).toBe(true); expect(client.connected.get()).toBe(true);
expect((console.error as any).mock.calls[0]).toMatchInlineSnapshot(` expect((console.error as any).mock.calls[0]).toMatchInlineSnapshot(`
@@ -147,7 +147,7 @@ test('can handle plugins that throw at start', async () => {
[Error: Broken plugin], [Error: Broken plugin],
] ]
`); `);
expect(store.getState().connections.clients.length).toBe(2); expect(store.getState().connections.clients.size).toBe(2);
expect(client2.connected.get()).toBe(true); expect(client2.connected.get()).toBe(true);
expect(client2.sandyPluginStates.size).toBe(0); expect(client2.sandyPluginStates.size).toBe(0);
}); });

View File

@@ -70,7 +70,7 @@ type StateV2 = {
userPreferredApp: null | string; // The name of the preferred app, e.g. Facebook userPreferredApp: null | string; // The name of the preferred app, e.g. Facebook
enabledPlugins: {[client: string]: string[]}; enabledPlugins: {[client: string]: string[]};
enabledDevicePlugins: Set<string>; enabledDevicePlugins: Set<string>;
clients: Array<Client>; clients: Map<string, Client>;
uninitializedClients: UninitializedClient[]; uninitializedClients: UninitializedClient[];
deepLinkPayload: unknown; deepLinkPayload: unknown;
staticView: StaticView; staticView: StaticView;
@@ -181,7 +181,7 @@ const INITAL_STATE: State = {
'Hermesdebuggerrn', 'Hermesdebuggerrn',
'React', 'React',
]), ]),
clients: [], clients: new Map(),
uninitializedClients: [], uninitializedClients: [],
deepLinkPayload: null, deepLinkPayload: null,
staticView: WelcomeScreenStaticView, staticView: WelcomeScreenStaticView,
@@ -253,14 +253,14 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
if (selectNewDevice) { if (selectNewDevice) {
// need to select a different app // need to select a different app
selectedAppId = selectedAppId =
state.clients.find( getAllClients(state).find(
(c) => (c) =>
c.device === payload && c.query.app === state.userPreferredApp, c.device === payload && c.query.app === state.userPreferredApp,
)?.id ?? null; )?.id ?? null;
// nothing found, try first app if any // nothing found, try first app if any
if (!selectedAppId) { if (!selectedAppId) {
selectedAppId = selectedAppId =
state.clients.find((c) => c.device === payload)?.id ?? null; getAllClients(state).find((c) => c.device === payload)?.id ?? null;
} }
} }
@@ -279,7 +279,7 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
performance.mark(`activePlugin-${selectedPlugin}`); performance.mark(`activePlugin-${selectedPlugin}`);
} }
const client = state.clients.find((c) => c.id === selectedAppId); const client = state.clients.get(selectedAppId!);
const device = action.payload.selectedDevice ?? client?.device; const device = action.payload.selectedDevice ?? client?.device;
if (!device) { if (!device) {
@@ -298,7 +298,7 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
: state.userPreferredDevice, : state.userPreferredDevice,
selectedAppId: selectedAppId ?? null, selectedAppId: selectedAppId ?? null,
userPreferredApp: userPreferredApp:
state.clients.find((c) => c.id === selectedAppId)?.query.app ?? state.clients.get(selectedAppId!)?.query.app ??
state.userPreferredApp, state.userPreferredApp,
selectedPlugin, selectedPlugin,
userPreferredPlugin: selectedPlugin, userPreferredPlugin: selectedPlugin,
@@ -309,42 +309,38 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
case 'NEW_CLIENT': { case 'NEW_CLIENT': {
const {payload} = action; const {payload} = action;
const newClients = state.clients.filter((client) => { return produce(state, (draft) => {
if (client.id === payload.id) { if (draft.clients.has(payload.id)) {
console.error( console.error(
`Received a new connection for client ${client.id}, but the old connection was not cleaned up`, `Received a new connection for client ${payload.id}, but the old connection was not cleaned up`,
); );
return false;
} }
return true; draft.clients.set(payload.id, payload);
// select new client if nothing select, this one is preferred, or the old one is offline
const selectNewClient =
!draft.selectedAppId ||
draft.userPreferredApp === payload.query.app ||
draft.clients.get(draft.selectedAppId!)?.connected.get() === false;
if (selectNewClient) {
draft.selectedAppId = payload.id;
draft.selectedDevice = payload.device;
}
const unitialisedIndex = draft.uninitializedClients.findIndex(
(c) =>
c.deviceName === payload.query.device ||
c.appName === payload.query.app,
);
if (unitialisedIndex !== -1)
draft.uninitializedClients.splice(unitialisedIndex, 1);
}); });
newClients.push(payload);
// select new client if nothing select, this one is preferred, or the old one is offline
const selectNewClient =
!state.selectedAppId ||
state.userPreferredApp === payload.query.app ||
state.clients
.find((c) => c.id === state.selectedAppId)
?.connected.get() === false;
return {
...state,
selectedAppId: selectNewClient ? payload.id : state.selectedAppId,
selectedDevice: selectNewClient ? payload.device : state.selectedDevice,
clients: newClients,
uninitializedClients: state.uninitializedClients.filter((c) => {
return (
c.deviceName !== payload.query.device ||
c.appName !== payload.query.app
);
}),
};
} }
case 'SELECT_CLIENT': { case 'SELECT_CLIENT': {
const {payload} = action; const {payload} = action;
const client = state.clients.find((c) => c.id === payload); const client = state.clients.get(payload);
if (!client) { if (!client) {
return state; return state;
@@ -369,15 +365,12 @@ export default (state: State = INITAL_STATE, action: Actions): State => {
case 'CLIENT_REMOVED': { case 'CLIENT_REMOVED': {
const {payload} = action; const {payload} = action;
const newClients = state.clients.filter( return produce(state, (draft) => {
(client) => client.id !== payload, draft.clients.delete(payload);
); if (draft.selectedAppId === payload) {
return { draft.selectedAppId = null;
...state, }
selectedAppId: });
state.selectedAppId === payload ? null : state.selectedAppId,
clients: newClients,
};
} }
case 'START_CLIENT_SETUP': { case 'START_CLIENT_SETUP': {
@@ -522,31 +515,25 @@ export const appPluginListChanged = (): Action => ({
type: 'APP_PLUGIN_LIST_CHANGED', type: 'APP_PLUGIN_LIST_CHANGED',
}); });
export function getAvailableClients( export function getClientsByDevice(
device: null | undefined | BaseDevice, device: null | undefined | BaseDevice,
clients: Client[], clients: Map<string, Client>,
): Client[] { ): Client[] {
if (!device) { if (!device) {
return []; return [];
} }
return clients return Array.from(clients.values())
.filter( .filter((client: Client) => client.query.device_id === device.serial)
(client: Client) =>
(device &&
device.supportsOS(client.query.os) &&
client.query.device_id === device.serial) ||
// Old android sdk versions don't know their device_id
// Display their plugins under all selected devices until they die out
client.query.device_id === 'unknown',
)
.sort((a, b) => (a.query.app || '').localeCompare(b.query.app)); .sort((a, b) => (a.query.app || '').localeCompare(b.query.app));
} }
export function getClientByAppName( export function getClientsByAppName(
clients: Client[], clients: Map<string, Client>,
appName: string | null | undefined, appName: string | null | undefined,
): Client | undefined { ): Client[] {
return clients.find((client) => client.query.app === appName); return Array.from(clients.values()).filter(
(client) => client.query.app === appName,
);
} }
export function getClientById( export function getClientById(
@@ -586,3 +573,7 @@ export function isPluginEnabled(
const enabledAppPlugins = enabledPlugins[appInfo.app]; const enabledAppPlugins = enabledPlugins[appInfo.app];
return enabledAppPlugins && enabledAppPlugins.indexOf(pluginId) > -1; return enabledAppPlugins && enabledAppPlugins.indexOf(pluginId) > -1;
} }
export function getAllClients(state: State): readonly Client[] {
return Array.from(state.clients.values());
}

View File

@@ -116,13 +116,13 @@ export class Group {
store.dispatch( store.dispatch(
setStaticView(require('../fb-stubs/SupportRequestFormV2').default), setStaticView(require('../fb-stubs/SupportRequestFormV2').default),
); );
const selectedApp = store.getState().connections.selectedAppId; const selectedAppId = store.getState().connections.selectedAppId;
const selectedClient = store.getState().connections.clients.find((o) => { const selectedClient = store
return o.id === store.getState().connections.selectedAppId; .getState()
}); .connections.clients.get(selectedAppId!);
let errorMessage: string | undefined = undefined; let errorMessage: string | undefined = undefined;
if (selectedApp) { if (selectedAppId) {
const {app} = deconstructClientId(selectedApp); const {app} = deconstructClientId(selectedAppId);
const enabledPlugins: Array<string> | null = const enabledPlugins: Array<string> | null =
store.getState().connections.enabledPlugins[app]; store.getState().connections.enabledPlugins[app];
const unsupportedPlugins = []; const unsupportedPlugins = [];

View File

@@ -22,7 +22,7 @@ import {batch} from 'react-redux';
import {useDispatch, useStore} from '../../utils/useStore'; import {useDispatch, useStore} from '../../utils/useStore';
import { import {
canBeDefaultDevice, canBeDefaultDevice,
getAvailableClients, getClientsByDevice,
selectClient, selectClient,
selectDevice, selectDevice,
} from '../../reducers/connections'; } from '../../reducers/connections';
@@ -85,7 +85,7 @@ export function AppSelector() {
onSelectDevice, onSelectDevice,
onSelectApp, onSelectApp,
); );
const client = clients.find((client) => client.id === selectedAppId); const client = clients.get(selectedAppId!);
return ( return (
<> <>
@@ -194,7 +194,7 @@ const AppIconContainer = styled.div({
function computeEntries( function computeEntries(
devices: BaseDevice[], devices: BaseDevice[],
clients: Client[], clients: Map<string, Client>,
uninitializedClients: State['connections']['uninitializedClients'], uninitializedClients: State['connections']['uninitializedClients'],
onSelectDevice: (device: BaseDevice) => void, onSelectDevice: (device: BaseDevice) => void,
onSelectApp: (device: BaseDevice, client: Client) => void, onSelectApp: (device: BaseDevice, client: Client) => void,
@@ -205,7 +205,7 @@ function computeEntries(
// hide non default devices, unless they have a connected client or plugins // hide non default devices, unless they have a connected client or plugins
canBeDefaultDevice(device) || canBeDefaultDevice(device) ||
device.hasDevicePlugins || device.hasDevicePlugins ||
clients.some((c) => c.device === device), getClientsByDevice(device, clients).length > 0,
) )
.map((device) => { .map((device) => {
const deviceEntry = ( const deviceEntry = (
@@ -219,7 +219,7 @@ function computeEntries(
<DeviceTitle device={device} /> <DeviceTitle device={device} />
</Menu.Item> </Menu.Item>
); );
const clientEntries = getAvailableClients(device, clients).map( const clientEntries = getClientsByDevice(device, clients).map(
(client) => ( (client) => (
<Menu.Item <Menu.Item
key={client.id} key={client.id}

View File

@@ -142,7 +142,7 @@ const NAVIGATION_PLUGIN_ID = 'Navigation';
function navPluginStateSelector(state: State) { function navPluginStateSelector(state: State) {
const {selectedAppId, clients} = state.connections; const {selectedAppId, clients} = state.connections;
if (!selectedAppId) return undefined; if (!selectedAppId) return undefined;
const client = clients.find((client) => client.id === selectedAppId); const client = clients.get(selectedAppId);
if (!client) return undefined; if (!client) return undefined;
return client.sandyPluginStates.get(NAVIGATION_PLUGIN_ID)?.instanceApi as return client.sandyPluginStates.get(NAVIGATION_PLUGIN_ID)?.instanceApi as
| undefined | undefined

View File

@@ -330,7 +330,7 @@ export function openNotification(store: Store, noti: PluginNotificationOrig) {
} }
function getClientById(store: Store, identifier: string | null) { function getClientById(store: Store, identifier: string | null) {
return store.getState().connections.clients.find((c) => c.id === identifier); return store.getState().connections.clients.get(identifier!);
} }
function getDeviceById(store: Store, identifier: string | null) { function getDeviceById(store: Store, identifier: string | null) {

View File

@@ -16,19 +16,13 @@ import {
import createSelector from './createSelector'; import createSelector from './createSelector';
const getSelectedPluginId = (state: State) => state.connections.selectedPlugin; const getSelectedPluginId = (state: State) => state.connections.selectedPlugin;
const getSelectedAppId = (state: State) => state.connections.selectedAppId;
const getSelectedDevice = (state: State) => state.connections.selectedDevice; const getSelectedDevice = (state: State) => state.connections.selectedDevice;
const getClients = (state: State) => state.connections.clients;
const getDevices = (state: State) => state.connections.devices; const getDevices = (state: State) => state.connections.devices;
const getPluginDownloads = (state: State) => state.pluginDownloads; const getPluginDownloads = (state: State) => state.pluginDownloads;
export const getActiveClient = createSelector( // N.B. no useSelector, It can't memoise on maps :-/
getSelectedAppId, export const getActiveClient = (state: State) =>
getClients, state.connections.clients.get(state.connections.selectedAppId!) ?? null;
(selectedApp, clients) => {
return clients.find((c) => c.id === selectedApp) || null;
},
);
export const getMetroDevice = createSelector(getDevices, (devices) => { export const getMetroDevice = createSelector(getDevices, (devices) => {
return ( return (

View File

@@ -33,7 +33,7 @@ import {
sleep, sleep,
Device, Device,
} from 'flipper-plugin'; } from 'flipper-plugin';
import {selectPlugin} from '../../reducers/connections'; import {selectPlugin, getAllClients} from '../../reducers/connections';
import {TestIdler} from '../Idler'; import {TestIdler} from '../Idler';
import {TestDevice} from '../..'; import {TestDevice} from '../..';
@@ -1298,7 +1298,7 @@ test('Sandy plugins are imported properly', async () => {
await importDataToStore('unittest.json', JSON.stringify(data), store); await importDataToStore('unittest.json', JSON.stringify(data), store);
const client2 = store.getState().connections.clients[1]; const client2 = getAllClients(store.getState().connections)[1];
expect(client2).not.toBeFalsy(); expect(client2).not.toBeFalsy();
expect(client2).not.toBe(client); expect(client2).not.toBe(client);
expect(Array.from(client2.plugins)).toEqual([TestPlugin.id]); expect(Array.from(client2.plugins)).toEqual([TestPlugin.id]);
@@ -1511,15 +1511,13 @@ test('Sandy plugin with custom import', async () => {
await importDataToStore('unittest.json', JSON.stringify(data), store); await importDataToStore('unittest.json', JSON.stringify(data), store);
expect( expect(
store getAllClients(store.getState().connections)[0]
.getState() .sandyPluginStates.get(plugin.id)
.connections.clients[0].sandyPluginStates.get(plugin.id)
?.instanceApi.counter.get(), ?.instanceApi.counter.get(),
).toBe(0); ).toBe(0);
expect( expect(
store getAllClients(store.getState().connections)[1]
.getState() .sandyPluginStates.get(plugin.id)
.connections.clients[1].sandyPluginStates.get(plugin.id)
?.instanceApi.counter.get(), ?.instanceApi.counter.get(),
).toBe(4); ).toBe(4);
}); });
@@ -1635,9 +1633,9 @@ test('Sandy plugins with complex data are imported / exported correctly', async
]); ]);
await importDataToStore('unittest.json', data.serializedString, store); await importDataToStore('unittest.json', data.serializedString, store);
const api = store const api = getAllClients(
.getState() store.getState().connections,
.connections.clients[1].sandyPluginStates.get(plugin.id)?.instanceApi; )[1].sandyPluginStates.get(plugin.id)?.instanceApi;
expect(api.m.get()).toMatchInlineSnapshot(` expect(api.m.get()).toMatchInlineSnapshot(`
Map { Map {
"a" => 1, "a" => 1,

View File

@@ -194,7 +194,7 @@ export function createSandyPluginWrapper<S, A extends BaseAction, P>(
(instance.device as BaseDevice) (instance.device as BaseDevice)
: // eslint-disable-next-line : // eslint-disable-next-line
useStore((state) => useStore((state) =>
state.connections.clients.find((c) => c.id === instance.appId), state.connections.clients.get(instance.appId!),
); );
if (!target) { if (!target) {
throw new Error('Illegal state: missing target'); throw new Error('Illegal state: missing target');

View File

@@ -420,7 +420,7 @@ async function getStoreExport(
let state = store.getState(); let state = store.getState();
const {clients, selectedAppId, selectedDevice} = state.connections; const {clients, selectedAppId, selectedDevice} = state.connections;
const pluginsToProcess = determinePluginsToProcess( const pluginsToProcess = determinePluginsToProcess(
clients, Array.from(clients.values()),
selectedDevice, selectedDevice,
state.plugins, state.plugins,
); );
@@ -433,7 +433,7 @@ async function getStoreExport(
const fetchMetaDataMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:fetch-meta-data`; const fetchMetaDataMarker = `${EXPORT_FLIPPER_TRACE_EVENT}:fetch-meta-data`;
performance.mark(fetchMetaDataMarker); performance.mark(fetchMetaDataMarker);
const client = clients.find((client) => client.id === selectedAppId); const client = clients.get(selectedAppId!);
const pluginStates2 = pluginsToProcess const pluginStates2 = pluginsToProcess
? await exportSandyPluginStates(pluginsToProcess, idler, statusUpdate) ? await exportSandyPluginStates(pluginsToProcess, idler, statusUpdate)