/** * 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 {Store} from '../reducers/index'; import {Logger} from '../fb-interfaces/Logger'; import {FlipperPlugin, FlipperDevicePlugin} from '../plugin'; import React from 'react'; import ReactDOM from 'react-dom'; import adbkit from 'adbkit'; import * as Flipper from '../index'; import { registerPlugins, addGatekeepedPlugins, addDisabledPlugins, addFailedPlugins, } from '../reducers/plugins'; import {ipcRenderer} from 'electron'; import GK from '../fb-stubs/GK'; import {FlipperBasePlugin} from '../plugin'; import {setupMenuBar} from '../MenuBar'; import path from 'path'; import {default as config} from '../utils/processConfig'; import isProduction from '../utils/isProduction'; import {notNull} from '../utils/typeUtils'; import {sideEffect} from '../utils/sideEffect'; // eslint-disable-next-line import/no-unresolved import {default as defaultPluginsIndex} from '../defaultPlugins/index'; export type PluginDefinition = { id?: string; name: string; out?: string; gatekeeper?: string; entry?: string; }; export default (store: Store, _logger: Logger) => { // expose Flipper and exact globally for dynamically loaded plugins const globalObject: any = typeof window === 'undefined' ? global : window; globalObject.React = React; globalObject.ReactDOM = ReactDOM; globalObject.Flipper = Flipper; globalObject.adbkit = adbkit; const gatekeepedPlugins: Array = []; const disabledPlugins: Array = []; const failedPlugins: Array<[PluginDefinition, string]> = []; const initialPlugins: Array< typeof FlipperPlugin | typeof FlipperDevicePlugin > = [...getBundledPlugins(), ...getDynamicPlugins()] .filter(checkDisabled(disabledPlugins)) .filter(checkGK(gatekeepedPlugins)) .map(requirePlugin(failedPlugins)) .filter(notNull); store.dispatch(addGatekeepedPlugins(gatekeepedPlugins)); store.dispatch(addDisabledPlugins(disabledPlugins)); store.dispatch(addFailedPlugins(failedPlugins)); store.dispatch(registerPlugins(initialPlugins)); sideEffect( store, {name: 'setupMenuBar', throttleMs: 100}, (state) => state.plugins, (plugins, store) => { setupMenuBar( [...plugins.devicePlugins.values(), ...plugins.clientPlugins.values()], store, ); }, ); }; function getBundledPlugins(): Array { // DefaultPlugins that are included in the bundle. // List of defaultPlugins is written at build time const pluginPath = process.env.BUNDLED_PLUGIN_PATH || (isProduction() ? path.join(__dirname, 'defaultPlugins') : './defaultPlugins/index.json'); let bundledPlugins: Array = []; try { bundledPlugins = global.electronRequire(pluginPath); } catch (e) { console.error(e); } return bundledPlugins .filter((plugin) => notNull(plugin.out)) .map( (plugin) => ({ ...plugin, out: path.join(pluginPath, plugin.out!), } as PluginDefinition), ) .concat(bundledPlugins.filter((plugin) => !plugin.out)); } export function getDynamicPlugins() { let dynamicPlugins: Array = []; try { dynamicPlugins = ipcRenderer.sendSync('get-dynamic-plugins'); } catch (e) { console.error(e); } return dynamicPlugins; } export const checkGK = (gatekeepedPlugins: Array) => ( plugin: PluginDefinition, ): boolean => { if (!plugin.gatekeeper) { return true; } const result = GK.get(plugin.gatekeeper); if (!result) { gatekeepedPlugins.push(plugin); } return result; }; export const checkDisabled = (disabledPlugins: Array) => ( plugin: PluginDefinition, ): boolean => { let disabledList: Set = new Set(); try { disabledList = config().disabledPlugins; } catch (e) { console.error(e); } if (disabledList.has(plugin.name)) { disabledPlugins.push(plugin); } return !disabledList.has(plugin.name); }; export const requirePlugin = ( failedPlugins: Array<[PluginDefinition, string]>, reqFn: Function = global.electronRequire, ) => { return ( pluginDefinition: PluginDefinition, ): typeof FlipperPlugin | typeof FlipperDevicePlugin | null => { try { let plugin = pluginDefinition.out ? reqFn(pluginDefinition.out) : defaultPluginsIndex[pluginDefinition.name]; if (plugin.default) { plugin = plugin.default; } if (!(plugin.prototype instanceof FlipperBasePlugin)) { throw new Error(`Plugin ${plugin.name} is not a FlipperBasePlugin`); } // set values from package.json as static variables on class Object.keys(pluginDefinition).forEach((key) => { if (key === 'name') { plugin.id = plugin.id || pluginDefinition.name; } else if (key === 'id') { throw new Error( 'Field "id" not allowed in package.json. The plugin\'s name will be used as ID"', ); } else { plugin[key] = plugin[key] || pluginDefinition[key as keyof PluginDefinition]; } }); return plugin; } catch (e) { failedPlugins.push([pluginDefinition, e.message]); console.error(pluginDefinition, e); return null; } }; };