plugin redux
Summary: Plugins were loaded in `/plugins/index.js` which was loaded once at launch of the app. This moves the list of available plugins to redux. This way, plugins can be dynamically added. The redux store keeps to Maps of plugins (devicePlugins and clientPlugins) with their ID as key: ``` devicePlugins: Map<string, Class<FlipperDevicePlugin<>>>, clientPlugins: Map<string, Class<FlipperPlugin<>>>, ``` On launch of the app, all plugins bundled with the app and the one found in `pluginsPath` are dynamically added. This changes now allows to add new plugins at any time. All components that need to know which plugins are available (e.g. the sidebar) are connected to the redux store. This way, they will automatically update, whenever a new plugin is added. - add `plugins` to the redux store to keep the list of available plugins - add a plugins dispatcher, responsible for loading the plugins on launch - connecting all React components that imported `plugins/index.js` before to the redux store to get the plugins from there. - moved the updating of the MenuBar to the plugins dispatcher as it needs to update whenever a new plugin is added. Reviewed By: jknoxville, passy Differential Revision: D12449236 fbshipit-source-id: 6ef3e243e2c80443614b901ccbfde485fcb4301c
This commit is contained in:
committed by
Facebook Github Bot
parent
e02420ac78
commit
7747a0714d
122
src/dispatcher/plugins.js
Normal file
122
src/dispatcher/plugins.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright 2018-present Facebook.
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {Store} from '../reducers/index.js';
|
||||
import type Logger from '../fb-stubs/Logger.js';
|
||||
import type {FlipperPlugin, FlipperDevicePlugin} from '../plugin.js';
|
||||
import type {State} from '../reducers/plugins';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import * as Flipper from 'flipper';
|
||||
import {registerPlugins} from '../reducers/plugins';
|
||||
import {remote} from 'electron';
|
||||
import {GK} from 'flipper';
|
||||
import {FlipperBasePlugin} from '../plugin.js';
|
||||
import {setupMenuBar} from '../MenuBar.js';
|
||||
|
||||
type PluginDefinition = {
|
||||
name: string,
|
||||
out: string,
|
||||
gatekeeper?: string,
|
||||
};
|
||||
|
||||
export default (store: Store, logger: Logger) => {
|
||||
// expose Flipper and exact globally for dynamically loaded plugins
|
||||
window.React = React;
|
||||
window.ReactDOM = ReactDOM;
|
||||
window.Flipper = Flipper;
|
||||
|
||||
const disabled = checkDisabled();
|
||||
const initialPlugins: Array<
|
||||
Class<FlipperPlugin<> | FlipperDevicePlugin<>>,
|
||||
> = [...getBundledPlugins(), ...getDynamicPlugins()]
|
||||
.filter(disabled)
|
||||
.filter(checkGK)
|
||||
.map(requirePlugin)
|
||||
.filter(Boolean);
|
||||
|
||||
store.dispatch(registerPlugins(initialPlugins));
|
||||
|
||||
let state: ?State = null;
|
||||
store.subscribe(() => {
|
||||
const newState = store.getState().plugins;
|
||||
if (state !== newState) {
|
||||
setupMenuBar([
|
||||
...newState.devicePlugins.values(),
|
||||
...newState.clientPlugins.values(),
|
||||
]);
|
||||
}
|
||||
state = newState;
|
||||
});
|
||||
};
|
||||
|
||||
function getBundledPlugins(): Array<PluginDefinition> {
|
||||
// DefaultPlugins that are included in the bundle.
|
||||
// List of defaultPlugins is written at build time
|
||||
let bundledPlugins: Array<PluginDefinition> = [];
|
||||
try {
|
||||
bundledPlugins = window.electronRequire('./defaultPlugins/index.json');
|
||||
} catch (e) {}
|
||||
|
||||
return bundledPlugins.map(plugin => ({
|
||||
...plugin,
|
||||
out: './' + plugin.out,
|
||||
}));
|
||||
}
|
||||
|
||||
function getDynamicPlugins() {
|
||||
let dynamicPlugins: Array<PluginDefinition> = [];
|
||||
try {
|
||||
// $FlowFixMe process.env not defined in electron API spec
|
||||
dynamicPlugins = JSON.parse(remote?.process.env.PLUGINS || '[]');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return dynamicPlugins;
|
||||
}
|
||||
|
||||
function checkGK(plugin: PluginDefinition): boolean {
|
||||
const result = plugin.gatekeeper && !GK.get(plugin.gatekeeper);
|
||||
if (!result) {
|
||||
console.warn(
|
||||
'Plugin %s will be ignored as user is not part of the gatekeeper "%s".',
|
||||
plugin.name,
|
||||
plugin.gatekeeper,
|
||||
);
|
||||
}
|
||||
return !result;
|
||||
}
|
||||
|
||||
function checkDisabled(): (plugin: PluginDefinition) => boolean {
|
||||
let disabledPlugins: Set<string> = new Set();
|
||||
try {
|
||||
disabledPlugins = new Set(
|
||||
// $FlowFixMe process.env not defined in electron API spec
|
||||
JSON.parse(remote?.process.env.CONFIG || '{}').disabledPlugins || [],
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return (plugin: PluginDefinition) => !disabledPlugins.has(plugin.name);
|
||||
}
|
||||
|
||||
function requirePlugin(
|
||||
pluginDefinition: PluginDefinition,
|
||||
): ?Class<FlipperPlugin<> | FlipperDevicePlugin<>> {
|
||||
try {
|
||||
const plugin = window.electronRequire(pluginDefinition.out);
|
||||
if (!plugin.prototype instanceof FlipperBasePlugin) {
|
||||
throw new Error(`Plugin ${plugin.name} is not a FlipperBasePlugin`);
|
||||
}
|
||||
return plugin;
|
||||
} catch (e) {
|
||||
console.error(pluginDefinition, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user