Summary: This prefixes APIs of `flipper-plugin`, that are used by Flipper, but should not be used by plugins directly, with `_`. Also added tests to make sure we are always intentional when extending the exposed APIs Reviewed By: passy Differential Revision: D24991700 fbshipit-source-id: ed3700efa188fca7f5a14d5c68250598cf011e42
245 lines
7.7 KiB
TypeScript
245 lines
7.7 KiB
TypeScript
/**
|
|
* 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 {
|
|
FlipperDevicePlugin,
|
|
FlipperBasePlugin,
|
|
PluginDefinition,
|
|
DevicePluginDefinition,
|
|
isSandyPlugin,
|
|
} from '../plugin';
|
|
import {State as PluginStatesState} from '../reducers/pluginStates';
|
|
import {State as PluginsState} from '../reducers/plugins';
|
|
import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue';
|
|
import {deconstructPluginKey, deconstructClientId} from './clientUtils';
|
|
import {_SandyPluginDefinition} from 'flipper-plugin';
|
|
|
|
type Client = import('../Client').default;
|
|
|
|
export const defaultEnabledBackgroundPlugins = ['Navigation']; // The navigation plugin is enabled always, to make sure the navigation features works
|
|
|
|
export function pluginsClassMap(
|
|
plugins: PluginsState,
|
|
): Map<string, PluginDefinition> {
|
|
return new Map<string, PluginDefinition>([
|
|
...plugins.clientPlugins.entries(),
|
|
...plugins.devicePlugins.entries(),
|
|
]);
|
|
}
|
|
|
|
export function getPluginKey(
|
|
selectedAppId: string | null,
|
|
baseDevice: {serial: string} | null,
|
|
pluginID: string,
|
|
): string {
|
|
if (selectedAppId) {
|
|
return `${selectedAppId}#${pluginID}`;
|
|
}
|
|
if (baseDevice) {
|
|
// If selected App is not defined, then the plugin is a device plugin
|
|
return `${baseDevice.serial}#${pluginID}`;
|
|
}
|
|
return `unknown#${pluginID}`;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param starredPlugin starredPlugin is the dictionary of client and its enabled plugin
|
|
* @param client Optional paramater indicating the selected client.
|
|
* @param plugins Plugins from the state which has the mapping to Plugin's Class.
|
|
|
|
* Returns plugins which are enabled or which has exportPersistedState function defined for the passed client.
|
|
* Note all device plugins are enabled.
|
|
*/
|
|
|
|
export function getEnabledOrExportPersistedStatePlugins(
|
|
starredPlugin: {
|
|
[client: string]: string[];
|
|
},
|
|
client: Client,
|
|
plugins: PluginsState,
|
|
): Array<{id: string; label: string}> {
|
|
const appName = deconstructClientId(client.id).app;
|
|
const pluginsMap: Map<string, PluginDefinition> = pluginsClassMap(plugins);
|
|
// Enabled Plugins with no exportPersistedState function defined
|
|
const enabledPlugins = starredPlugin[appName]
|
|
? starredPlugin[appName]
|
|
.map((pluginName) => pluginsMap.get(pluginName)!)
|
|
.filter(Boolean)
|
|
.filter((plugin) => {
|
|
return !plugin.exportPersistedState;
|
|
})
|
|
.sort(sortPluginsByName)
|
|
.map((plugin) => {
|
|
return {id: plugin.id, label: getPluginTitle(plugin)};
|
|
})
|
|
: [];
|
|
// Device Plugins
|
|
const devicePlugins = Array.from(plugins.devicePlugins.keys())
|
|
.filter((plugin) => {
|
|
return client.plugins.includes(plugin);
|
|
})
|
|
.map((plugin) => {
|
|
return {
|
|
id: plugin,
|
|
label: getPluginTitle(plugins.devicePlugins.get(plugin)!),
|
|
};
|
|
})
|
|
.filter(Boolean);
|
|
// Plugins which have defined exportPersistedState.
|
|
const exportPersistedStatePlugins = client.plugins
|
|
.filter((name) => {
|
|
return pluginsMap.get(name)?.exportPersistedState != null;
|
|
})
|
|
.map((name) => {
|
|
const plugin = pluginsMap.get(name)!;
|
|
return {id: plugin.id, label: getPluginTitle(plugin)};
|
|
});
|
|
return [
|
|
...devicePlugins,
|
|
...enabledPlugins,
|
|
...exportPersistedStatePlugins,
|
|
{id: 'DeviceLogs', label: 'Logs'},
|
|
];
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param pluginsState PluginsState of the Redux Store.
|
|
* @param plugins Plugins from the state which has the mapping to Plugin's Class.
|
|
* @param selectedClient Optional paramater indicating the selected client.
|
|
* Returns active persistent plugin, which means plugins which has the data in redux store or has the `exportPersistedState` function defined which can return the plugin's data when called.
|
|
* If the selectedClient is defined then the active persistent plugins only for the selectedClient will be returned, otherwise it will return all active persistent plugins.
|
|
*/
|
|
export function getActivePersistentPlugins(
|
|
pluginsState: PluginStatesState,
|
|
pluginsMessageQueue: PluginMessageQueueState,
|
|
plugins: PluginsState,
|
|
selectedClient?: Client,
|
|
): {id: string; label: string}[] {
|
|
const pluginsMap = pluginsClassMap(plugins);
|
|
return getPersistentPlugins(plugins)
|
|
.map((pluginName) => pluginsMap.get(pluginName)!)
|
|
.sort(sortPluginsByName)
|
|
.filter((plugin) => {
|
|
if (plugin.id == 'DeviceLogs') {
|
|
return true;
|
|
}
|
|
if (selectedClient) {
|
|
const pluginKey = getPluginKey(
|
|
selectedClient.id,
|
|
{serial: selectedClient.query.device_id},
|
|
plugin.id,
|
|
);
|
|
// If there is a selected client, active persistent plugins are those that (can) have persisted state
|
|
return (
|
|
selectedClient.isEnabledPlugin(plugin.id) &&
|
|
// this plugin can fetch and export state
|
|
(plugin.exportPersistedState ||
|
|
// this plugin has some persisted state already
|
|
pluginsState[pluginKey] ||
|
|
pluginsMessageQueue[pluginKey] ||
|
|
// this plugin has some persistable sandy state
|
|
selectedClient.sandyPluginStates.get(plugin.id)?.isPersistable())
|
|
);
|
|
}
|
|
{
|
|
// If there is no selected client, active persistent plugin is the plugin which is just persistent.
|
|
const pluginsWithReduxData = [
|
|
...new Set([
|
|
...Object.keys(pluginsState),
|
|
...Object.keys(pluginsMessageQueue),
|
|
]),
|
|
].map((key) => deconstructPluginKey(key).pluginName);
|
|
return (
|
|
(plugin && plugin.exportPersistedState != undefined) ||
|
|
isSandyPlugin(plugin) ||
|
|
pluginsWithReduxData.includes(plugin.id)
|
|
);
|
|
}
|
|
})
|
|
.map((plugin) => ({
|
|
id: plugin.id,
|
|
label: getPluginTitle(plugin),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Returns all enabled plugins that are potentially exportable
|
|
* @param plugins
|
|
*/
|
|
export function getPersistentPlugins(plugins: PluginsState): Array<string> {
|
|
const pluginsMap = pluginsClassMap(plugins);
|
|
|
|
[...plugins.disabledPlugins, ...plugins.gatekeepedPlugins].forEach(
|
|
(plugin) => {
|
|
pluginsMap.delete(plugin.name);
|
|
},
|
|
);
|
|
plugins.failedPlugins.forEach(([details]) => {
|
|
pluginsMap.delete(details.id);
|
|
});
|
|
|
|
return Array.from(pluginsMap.keys()).filter((plugin) => {
|
|
const pluginClass = pluginsMap.get(plugin);
|
|
return (
|
|
plugin == 'DeviceLogs' ||
|
|
isSandyPlugin(pluginClass) ||
|
|
pluginClass?.defaultPersistedState ||
|
|
pluginClass?.exportPersistedState
|
|
);
|
|
});
|
|
}
|
|
|
|
export function getPluginTitle(pluginClass: {
|
|
title?: string | null;
|
|
id: string;
|
|
}) {
|
|
return pluginClass.title || pluginClass.id;
|
|
}
|
|
|
|
export function sortPluginsByName(
|
|
a: PluginDefinition,
|
|
b: PluginDefinition,
|
|
): number {
|
|
// make sure Device plugins are sorted before normal plugins
|
|
if (isDevicePluginDefinition(a) && !isDevicePluginDefinition(b)) {
|
|
return -1;
|
|
}
|
|
if (isDevicePluginDefinition(b) && !isDevicePluginDefinition(a)) {
|
|
return 1;
|
|
}
|
|
return getPluginTitle(a) > getPluginTitle(b) ? 1 : -1;
|
|
}
|
|
|
|
export function isDevicePluginDefinition(
|
|
definition: PluginDefinition,
|
|
): definition is DevicePluginDefinition {
|
|
return (
|
|
(definition as any).prototype instanceof FlipperDevicePlugin ||
|
|
(definition instanceof _SandyPluginDefinition && definition.isDevicePlugin)
|
|
);
|
|
}
|