// @flow import mkdirp from 'mkdirp'; import * as packageJson from '../../package.json'; import * as models from '../models'; import fs from 'fs'; import path from 'path'; import { PLUGIN_PATH } from '../common/constants'; import { resolveHomePath } from '../common/misc'; import { showError } from '../ui/components/modals/index'; import type { PluginTemplateTag } from '../templating/extensions/index'; import type { PluginTheme } from './misc'; import type { RequestGroup } from '../models/request-group'; import type { Request } from '../models/request'; export type Plugin = { name: string, description: string, version: string, directory: string, module: *, }; export type TemplateTag = { plugin: Plugin, templateTag: PluginTemplateTag, }; export type RequestGroupAction = { plugin: Plugin, action: ( context: Object, models: { requestGroup: RequestGroup, requests: Array, }, ) => void | Promise, label: string, icon?: string, }; export type RequestHook = { plugin: Plugin, hook: Function, }; export type ResponseHook = { plugin: Plugin, hook: Function, }; export type Theme = { plugin: Plugin, theme: PluginTheme, }; let plugins: ?Array = null; export async function init(): Promise { // Force plugins to load. await getPlugins(true); } async function _traversePluginPath(pluginMap: Object, allPaths: Array) { for (const p of allPaths) { if (!fs.existsSync(p)) { continue; } for (const filename of fs.readdirSync(p)) { try { const modulePath = path.join(p, filename); const packageJSONPath = path.join(modulePath, 'package.json'); // Only read directories if (!fs.statSync(modulePath).isDirectory()) { continue; } // Is it a scoped directory? if (filename.startsWith('@')) { await _traversePluginPath(pluginMap, [modulePath]); } // Is it a Node module? if (!fs.readdirSync(modulePath).includes('package.json')) { continue; } // Delete `require` cache if plugin has been required before for (const p of Object.keys(global.require.cache)) { if (p.indexOf(modulePath) === 0) { delete global.require.cache[p]; } } // Use global.require() instead of require() because Webpack wraps require() const pluginJson = global.require(packageJSONPath); // Not an Insomnia plugin because it doesn't have the package.json['insomnia'] if (!pluginJson.hasOwnProperty('insomnia')) { continue; } // Delete require cache entry and re-require const module = global.require(modulePath); pluginMap[pluginJson.name] = _initPlugin(pluginJson || {}, module, modulePath); console.log(`[plugin] Loaded ${modulePath}`); } catch (err) { showError({ title: 'Plugin Error', message: 'Failed to load plugin ' + filename, error: err, }); } } } } export async function getPlugins(force: boolean = false): Promise> { if (force) { plugins = null; } if (!plugins) { const settings = await models.settings.getOrCreate(); const extraPaths = settings.pluginPath .split(':') .filter(p => p) .map(resolveHomePath); // Make sure the default directories exist mkdirp.sync(PLUGIN_PATH); // Also look in node_modules folder in each directory const basePaths = [PLUGIN_PATH, ...extraPaths]; const extendedPaths = basePaths.map(p => path.join(p, 'node_modules')); const allPaths = [...basePaths, ...extendedPaths]; // Store plugins in a map so that plugins with the same // name only get added once // TODO: Make this more complex and have the latest version always win const pluginMap: { [string]: Plugin } = { // "name": "module" }; for (const p of packageJson.app.plugins) { const pluginJson = global.require(`${p}/package.json`); const pluginModule = global.require(p); pluginMap[pluginJson.name] = _initPlugin(pluginJson, pluginModule); } await _traversePluginPath(pluginMap, allPaths); plugins = Object.keys(pluginMap).map(name => pluginMap[name]); } return plugins; } export async function getRequestGroupActions(): Promise> { let extensions = []; for (const plugin of await getPlugins()) { const actions = plugin.module.requestGroupActions || []; extensions = [...extensions, ...actions.map(p => ({ plugin, ...p }))]; } return extensions; } export async function getTemplateTags(): Promise> { let extensions = []; for (const plugin of await getPlugins()) { const templateTags = plugin.module.templateTags || []; extensions = [...extensions, ...templateTags.map(tt => ({ plugin, templateTag: tt }))]; } return extensions; } export async function getRequestHooks(): Promise> { let functions = []; for (const plugin of await getPlugins()) { const moreFunctions = plugin.module.requestHooks || []; functions = [...functions, ...moreFunctions.map(hook => ({ plugin, hook }))]; } return functions; } export async function getResponseHooks(): Promise> { let functions = []; for (const plugin of await getPlugins()) { const moreFunctions = plugin.module.responseHooks || []; functions = [...functions, ...moreFunctions.map(hook => ({ plugin, hook }))]; } return functions; } export async function getThemes(): Promise> { let extensions = []; for (const plugin of await getPlugins()) { const themes = plugin.module.themes || []; extensions = [...extensions, ...themes.map(theme => ({ plugin, theme }))]; } return extensions; } function _initPlugin(packageJSON: Object, module: any, path: ?string): Plugin { const meta = packageJSON.insomnia || {}; return { name: packageJSON.name || meta.name, description: packageJSON.description || meta.description || '', version: packageJSON.version || 'unknown', directory: path || '', module: module, }; }