diff --git a/Gruntfile.js b/Gruntfile.js
index 2bdc2e3aa..23481f793 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -142,6 +142,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/text/bidi.js",
"packages/node_modules/@node-red/editor-client/src/js/text/format.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/state.js",
+ "packages/node_modules/@node-red/editor-client/src/js/plugins.js",
"packages/node_modules/@node-red/editor-client/src/js/nodes.js",
"packages/node_modules/@node-red/editor-client/src/js/font-awesome.js",
"packages/node_modules/@node-red/editor-client/src/js/history.js",
diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js
index 34c47b2cb..cf3505439 100644
--- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js
+++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js
@@ -22,6 +22,7 @@ var flow = require("./flow");
var context = require("./context");
var auth = require("../auth");
var info = require("./settings");
+var plugins = require("./plugins");
var apiUtil = require("../util");
@@ -32,6 +33,7 @@ module.exports = {
nodes.init(runtimeAPI);
context.init(runtimeAPI);
info.init(settings,runtimeAPI);
+ plugins.init(runtimeAPI);
var needsPermission = auth.needsPermission;
@@ -80,6 +82,10 @@ module.exports = {
adminApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler);
+ // Plugins
+ adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler);
+ adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler);
+
return adminApp;
}
}
diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js
new file mode 100644
index 000000000..15428f86f
--- /dev/null
+++ b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js
@@ -0,0 +1,38 @@
+var apiUtils = require("../util");
+
+var runtimeAPI;
+
+module.exports = {
+ init: function(_runtimeAPI) {
+ runtimeAPI = _runtimeAPI;
+ },
+ getAll: function(req,res) {
+ var opts = {
+ user: req.user,
+ req: apiUtils.getRequestLogObject(req)
+ }
+ if (req.get("accept") == "application/json") {
+ runtimeAPI.plugins.getPluginList(opts).then(function(list) {
+ res.json(list);
+ })
+ } else {
+ opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages());
+ runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) {
+ res.send(configs);
+ })
+ }
+ },
+ getCatalogs: function(req,res) {
+ var opts = {
+ user: req.user,
+ lang: req.query.lng,
+ req: apiUtils.getRequestLogObject(req)
+ }
+ runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) {
+ res.json(result);
+ }).catch(function(err) {
+ console.log(err.stack);
+ apiUtils.rejectHandler(req,res,err);
+ })
+ }
+};
diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
index 7894f1ed2..7cdfde99d 100755
--- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
+++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json
@@ -38,6 +38,7 @@
}
},
"event": {
+ "loadPlugins": "Loading Plugins",
"loadPalette": "Loading Palette",
"loadNodeCatalogs": "Loading Node catalogs",
"loadNodes": "Loading Nodes __count__",
diff --git a/packages/node_modules/@node-red/editor-client/src/js/i18n.js b/packages/node_modules/@node-red/editor-client/src/js/i18n.js
index f1d0edfaf..ac439ecbc 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/i18n.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/i18n.js
@@ -108,6 +108,31 @@ RED.i18n = (function() {
}
});
})
+ },
+
+ loadPluginCatalogs: function(done) {
+ var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage());
+ var toLoad = languageList.length;
+
+ languageList.forEach(function(lang) {
+ $.ajax({
+ headers: {
+ "Accept":"application/json"
+ },
+ cache: false,
+ url: apiRootUrl+'plugins/messages?lng='+lang,
+ success: function(data) {
+ var namespaces = Object.keys(data);
+ namespaces.forEach(function(ns) {
+ i18n.addResourceBundle(lang,ns,data[ns]);
+ });
+ toLoad--;
+ if (toLoad === 0) {
+ done();
+ }
+ }
+ });
+ })
}
}
})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/plugins.js b/packages/node_modules/@node-red/editor-client/src/js/plugins.js
new file mode 100644
index 000000000..e6f41517d
--- /dev/null
+++ b/packages/node_modules/@node-red/editor-client/src/js/plugins.js
@@ -0,0 +1,46 @@
+RED.plugins = (function() {
+ var plugins = {};
+ var pluginsByType = {};
+
+ function registerPlugin(id,definition) {
+ plugins[id] = definition;
+ if (definition.type) {
+ pluginsByType[definition.type] = pluginsByType[definition.type] || [];
+ pluginsByType[definition.type].push(definition);
+ }
+ if (RED._loadingModule) {
+ definition.module = RED._loadingModule;
+ definition["_"] = function() {
+ var args = Array.prototype.slice.call(arguments);
+ var originalKey = args[0];
+ if (!/:/.test(args[0])) {
+ args[0] = definition.module+":"+args[0];
+ }
+ var result = RED._.apply(null,args);
+ if (result === args[0]) {
+ return originalKey;
+ }
+ return result;
+ }
+ } else {
+ definition["_"] = RED["_"]
+ }
+ if (definition.onadd && typeof definition.onadd === 'function') {
+ definition.onadd();
+ }
+ RED.events.emit("registry:plugin-added",id);
+ }
+
+ function getPlugin(id) {
+ return plugins[id]
+ }
+
+ function getPluginsByType(type) {
+ return pluginsByType[type] || [];
+ }
+ return {
+ registerPlugin: registerPlugin,
+ getPlugin: getPlugin,
+ getPluginsByType: getPluginsByType
+ }
+})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js
index 3a4f4781f..a2bcb0e7d 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/red.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/red.js
@@ -15,19 +15,65 @@
**/
var RED = (function() {
- function appendNodeConfig(nodeConfig,done) {
+
+ function loadPluginList() {
+ loader.reportProgress(RED._("event.loadPlugins"), 10)
+ $.ajax({
+ headers: {
+ "Accept":"application/json"
+ },
+ cache: false,
+ url: 'plugins',
+ success: function(data) {
+ loader.reportProgress(RED._("event.loadPlugins"), 13)
+ RED.i18n.loadPluginCatalogs(function() {
+ loadPlugins(function() {
+ loadNodeList();
+ });
+ });
+ }
+ });
+ }
+ function loadPlugins(done) {
+ loader.reportProgress(RED._("event.loadPlugins",{count:""}), 17)
+ var lang = localStorage.getItem("editor-language")||i18n.detectLanguage();
+
+ $.ajax({
+ headers: {
+ "Accept":"text/html",
+ "Accept-Language": lang
+ },
+ cache: false,
+ url: 'plugins',
+ success: function(data) {
+ var configs = data.trim().split(/(?=)/);
+ var totalCount = configs.length;
+ var stepConfig = function() {
+ // loader.reportProgress(RED._("event.loadNodes",{count:(totalCount-configs.length)+"/"+totalCount}), 30 + ((totalCount-configs.length)/totalCount)*40 )
+ if (configs.length === 0) {
+ done();
+ } else {
+ var config = configs.shift();
+ appendPluginConfig(config,stepConfig);
+ }
+ }
+ stepConfig();
+ }
+ });
+ }
+
+ function appendConfig(config, moduleIdMatch, targetContainer, done) {
done = done || function(){};
- var m = //.exec(nodeConfig.trim());
var moduleId;
- if (m) {
- moduleId = m[1];
+ if (moduleIdMatch) {
+ moduleId = moduleIdMatch[1];
+ RED._loadingModule = moduleId;
} else {
moduleId = "unknown";
}
try {
var hasDeferred = false;
-
- var nodeConfigEls = $("
"+nodeConfig+"
");
+ var nodeConfigEls = $(""+config+"
");
var scripts = nodeConfigEls.find("script");
var scriptCount = scripts.length;
scripts.each(function(i,el) {
@@ -38,14 +84,15 @@ var RED = (function() {
newScript.onload = function() {
scriptCount--;
if (scriptCount === 0) {
- $("#red-ui-editor-node-configs").append(nodeConfigEls);
+ $(targetContainer).append(nodeConfigEls);
+ delete RED._loadingModule;
done()
}
}
if ($(el).attr('type') === "module") {
newScript.type = "module";
}
- $("#red-ui-editor-node-configs").append(newScript);
+ $(targetContainer).append(newScript);
newScript.src = RED.settings.apiRootUrl+srcUrl;
hasDeferred = true;
} else {
@@ -61,7 +108,8 @@ var RED = (function() {
}
})
if (!hasDeferred) {
- $("#red-ui-editor-node-configs").append(nodeConfigEls);
+ $(targetContainer).append(nodeConfigEls);
+ delete RED._loadingModule;
done();
}
} catch(err) {
@@ -70,9 +118,27 @@ var RED = (function() {
timeout: 10000
});
console.log("["+moduleId+"] "+err.toString());
+ delete RED._loadingModule;
done();
}
}
+ function appendPluginConfig(pluginConfig,done) {
+ appendConfig(
+ pluginConfig,
+ //.exec(pluginConfig.trim()),
+ "#red-ui-editor-plugin-configs",
+ done
+ );
+ }
+
+ function appendNodeConfig(nodeConfig,done) {
+ appendConfig(
+ nodeConfig,
+ //.exec(nodeConfig.trim()),
+ "#red-ui-editor-node-configs",
+ done
+ );
+ }
function loadNodeList() {
loader.reportProgress(RED._("event.loadPalette"), 20)
@@ -580,7 +646,7 @@ var RED = (function() {
RED.actions.add("core:show-about", showAbout);
- loadNodeList();
+ loadPluginList();
}
@@ -596,6 +662,7 @@ var RED = (function() {
''+
''+
'').appendTo(options.target);
+ $('').appendTo(options.target);
$('').appendTo(options.target);
$('').appendTo(options.target);
diff --git a/packages/node_modules/@node-red/registry/lib/index.js b/packages/node_modules/@node-red/registry/lib/index.js
index 03f979424..251b04971 100644
--- a/packages/node_modules/@node-red/registry/lib/index.js
+++ b/packages/node_modules/@node-red/registry/lib/index.js
@@ -28,6 +28,7 @@ var registry = require("./registry");
var loader = require("./loader");
var installer = require("./installer");
var library = require("./library");
+var plugins = require("./plugins");
/**
* Initialise the registry with a reference to a runtime object
@@ -40,6 +41,7 @@ function init(runtime) {
// the util module it. The Util module is responsible for constructing the
// RED object passed to node modules when they are loaded.
loader.init(runtime);
+ plugins.init(runtime.settings);
registry.init(runtime.settings,loader);
library.init();
}
@@ -297,6 +299,12 @@ module.exports = {
*/
getNodeExampleFlowPath: library.getExampleFlowPath,
+ registerPlugin: plugins.registerPlugin,
+ getPlugin: plugins.getPlugin,
+ getPluginsByType: plugins.getPluginsByType,
+ getPluginList: plugins.getPluginList,
+ getPluginConfigs: plugins.getPluginConfigs,
+
deprecated: require("./deprecated")
};
diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js
index f35987732..bd5a7aae4 100644
--- a/packages/node_modules/@node-red/registry/lib/loader.js
+++ b/packages/node_modules/@node-red/registry/lib/loader.js
@@ -36,78 +36,140 @@ function load(disableNodePathScan) {
// To skip node scan, the following line will use the stored node list.
// We should expose that as an option at some point, although the
// performance gains are minimal.
- //return loadNodeFiles(registry.getModuleList());
+ //return loadModuleFiles(registry.getModuleList());
log.info(log._("server.loading"));
- var nodeFiles = localfilesystem.getNodeFiles(disableNodePathScan);
- return loadNodeFiles(nodeFiles);
+ var modules = localfilesystem.getNodeFiles(disableNodePathScan);
+ return loadModuleFiles(modules);
}
-function loadNodeFiles(nodeFiles) {
+
+function loadModuleTypeFiles(module, type) {
+ const things = module[type];
+ var first = true;
var promises = [];
- var nodes = [];
- for (var module in nodeFiles) {
+ for (var thingName in things) {
/* istanbul ignore else */
- if (nodeFiles.hasOwnProperty(module)) {
- if (nodeFiles[module].redVersion &&
- !semver.satisfies((settings.version||"0.0.0").replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), nodeFiles[module].redVersion)) {
+ if (things.hasOwnProperty(thingName)) {
+ if (module.name != "node-red" && first) {
+ // Check the module directory exists
+ first = false;
+ var fn = things[thingName].file;
+ var parts = fn.split("/");
+ var i = parts.length-1;
+ for (;i>=0;i--) {
+ if (parts[i] == "node_modules") {
+ break;
+ }
+ }
+ var moduleFn = parts.slice(0,i+2).join("/");
+
+ try {
+ var stat = fs.statSync(moduleFn);
+ } catch(err) {
+ // Module not found, don't attempt to load its nodes
+ break;
+ }
+ }
+
+ try {
+ var promise;
+ if (type === "nodes") {
+ promise = loadNodeConfig(things[thingName]);
+ } else if (type === "plugins") {
+ promise = loadPluginConfig(things[thingName]);
+ }
+ promises.push(
+ promise.then(
+ (function() {
+ var m = module.name;
+ var n = thingName;
+ return function(nodeSet) {
+ things[n] = nodeSet;
+ return nodeSet;
+ }
+ })()
+ ).catch(err => {console.log(err)})
+ );
+ } catch(err) {
+ console.log(err)
+ //
+ }
+ }
+ }
+ return promises;
+}
+
+function loadModuleFiles(modules) {
+ var pluginPromises = [];
+ var nodePromises = [];
+ for (var module in modules) {
+ /* istanbul ignore else */
+ if (modules.hasOwnProperty(module)) {
+ if (modules[module].redVersion &&
+ !semver.satisfies((settings.version||"0.0.0").replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), modules[module].redVersion)) {
//TODO: log it
- log.warn("["+module+"] "+log._("server.node-version-mismatch",{version:nodeFiles[module].redVersion}));
- nodeFiles[module].err = "version_mismatch";
+ log.warn("["+module+"] "+log._("server.node-version-mismatch",{version:modules[module].redVersion}));
+ modules[module].err = "version_mismatch";
continue;
}
if (module == "node-red" || !registry.getModuleInfo(module)) {
- var first = true;
- for (var node in nodeFiles[module].nodes) {
- /* istanbul ignore else */
- if (nodeFiles[module].nodes.hasOwnProperty(node)) {
- if (module != "node-red" && first) {
- // Check the module directory exists
- first = false;
- var fn = nodeFiles[module].nodes[node].file;
- var parts = fn.split("/");
- var i = parts.length-1;
- for (;i>=0;i--) {
- if (parts[i] == "node_modules") {
- break;
- }
- }
- var moduleFn = parts.slice(0,i+2).join("/");
-
- try {
- var stat = fs.statSync(moduleFn);
- } catch(err) {
- // Module not found, don't attempt to load its nodes
- break;
- }
- }
-
- try {
- promises.push(loadNodeConfig(nodeFiles[module].nodes[node]).then((function() {
- var m = module;
- var n = node;
- return function(nodeSet) {
- nodeFiles[m].nodes[n] = nodeSet;
- nodes.push(nodeSet);
- }
- })()).catch(err => {}));
- } catch(err) {
- //
- }
- }
+ if (modules[module].nodes) {
+ nodePromises = nodePromises.concat(loadModuleTypeFiles(modules[module], "nodes"));
+ }
+ if (modules[module].plugins) {
+ pluginPromises = pluginPromises.concat(loadModuleTypeFiles(modules[module], "plugins"));
}
}
}
}
- return Promise.all(promises).then(function(results) {
- for (var module in nodeFiles) {
- if (nodeFiles.hasOwnProperty(module)) {
- if (!nodeFiles[module].err) {
- registry.addModule(nodeFiles[module]);
+ var pluginList;
+ var nodeList;
+
+ return Promise.all(pluginPromises).then(function(results) {
+ pluginList = results.filter(r => !!r);
+ // Initial plugin load has happened. Ensure modules that provide
+ // plugins are in the registry now.
+ for (var module in modules) {
+ if (modules.hasOwnProperty(module)) {
+ if (modules[module].plugins && Object.keys(modules[module].plugins).length > 0) {
+ // Add the modules for plugins
+ if (!modules[module].err) {
+ registry.addModule(modules[module]);
+ }
}
}
}
- return loadNodeSetList(nodes);
+ return loadNodeSetList(pluginList);
+ }).then(function() {
+ return Promise.all(nodePromises);
+ }).then(function(results) {
+ nodeList = results.filter(r => !!r);
+ // Initial node load has happened. Ensure remaining modules are in the registry
+ for (var module in modules) {
+ if (modules.hasOwnProperty(module)) {
+ if (!modules[module].plugins || Object.keys(modules[module].plugins).length === 0) {
+ if (!modules[module].err) {
+ registry.addModule(modules[module]);
+ }
+ }
+ }
+ }
+ return loadNodeSetList(nodeList);
+ });
+}
+
+async function loadPluginTemplate(plugin) {
+ return fs.readFile(plugin.template,'utf8').then(content => {
+ plugin.config = content;
+ return plugin;
+ }).catch(err => {
+ if (err.code === 'ENOENT') {
+ plugin.err = "Error: "+plugin.template+" does not exist";
+ } else {
+ plugin.err = err.toString();
+ }
+ return plugin;
});
}
@@ -175,11 +237,12 @@ async function loadNodeLocales(node) {
node.namespace = node.module;
return node
}
- return fs.stat(path.join(path.dirname(node.file),"locales")).then(stat => {
+ const baseFile = node.file||node.template;
+ return fs.stat(path.join(path.dirname(baseFile),"locales")).then(stat => {
node.namespace = node.id;
return i18n.registerMessageCatalog(node.id,
- path.join(path.dirname(node.file),"locales"),
- path.basename(node.file,".js")+".json")
+ path.join(path.dirname(baseFile),"locales"),
+ path.basename(baseFile).replace(/\.[^.]+$/,".json"))
.then(() => node);
}).catch(err => {
node.namespace = node.module;
@@ -204,6 +267,7 @@ async function loadNodeConfig(fileInfo) {
}
var node = {
+ type: "node",
id: id,
module: module,
name: name,
@@ -227,6 +291,58 @@ async function loadNodeConfig(fileInfo) {
return node;
}
+async function loadPluginConfig(fileInfo) {
+ var file = fileInfo.file;
+ var module = fileInfo.module;
+ var name = fileInfo.name;
+ var version = fileInfo.version;
+
+ var id = module + "/" + name;
+ var isEnabled = true;
+
+ // TODO: registry.getPluginInfo
+
+ // var info = registry.getPluginInfo(id);
+ // if (info) {
+ // if (info.hasOwnProperty("loaded")) {
+ // throw new Error(file+" already loaded");
+ // }
+ // isEnabled = info.enabled;
+ // }
+
+
+ if (!fs.existsSync(jsFile)) {
+ }
+
+ var plugin = {
+ type: "plugin",
+ id: id,
+ module: module,
+ name: name,
+ enabled: isEnabled,
+ loaded:false,
+ version: version,
+ local: fileInfo.local,
+ plugins: [],
+ config: "",
+ help: {}
+ };
+ var jsFile = file.replace(/\.[^.]+$/,".js");
+ var htmlFile = file.replace(/\.[^.]+$/,".html");
+ if (fs.existsSync(jsFile)) {
+ plugin.file = jsFile;
+ }
+ if (fs.existsSync(htmlFile)) {
+ plugin.template = htmlFile;
+ }
+ await loadNodeLocales(plugin)
+
+ if (plugin.template && !settings.disableEditor) {
+ return loadPluginTemplate(plugin);
+ }
+ return plugin
+}
+
/**
* Loads the specified node into the runtime
* @param node a node info object - see loadNodeConfig
@@ -236,8 +352,6 @@ async function loadNodeConfig(fileInfo) {
*
*/
function loadNodeSet(node) {
- var nodeDir = path.dirname(node.file);
- var nodeFn = path.basename(node.file);
if (!node.enabled) {
return Promise.resolve(node);
} else {
@@ -284,11 +398,59 @@ function loadNodeSet(node) {
}
}
+async function loadPlugin(plugin) {
+ if (!plugin.file) {
+ // No runtime component - nothing to load
+ return plugin;
+ }
+ try {
+ var r = require(plugin.file);
+ if (typeof r === "function") {
+
+ var red = registryUtil.createNodeApi(plugin);
+ var promise = r(red);
+ if (promise != null && typeof promise.then === "function") {
+ return promise.then(function() {
+ plugin.enabled = true;
+ plugin.loaded = true;
+ return plugin;
+ }).catch(function(err) {
+ plugin.err = err;
+ return plugin;
+ });
+ }
+ }
+ plugin.enabled = true;
+ plugin.loaded = true;
+ return plugin;
+ } catch(err) {
+ console.log(err);
+ plugin.err = err;
+ var stack = err.stack;
+ var message;
+ if (stack) {
+ var i = stack.indexOf(plugin.file);
+ if (i > -1) {
+ var excerpt = stack.substring(i+node.file.length+1,i+plugin.file.length+20);
+ var m = /^(\d+):(\d+)/.exec(excerpt);
+ if (m) {
+ plugin.err = err+" (line:"+m[1]+")";
+ }
+ }
+ }
+ return plugin;
+ }
+}
+
function loadNodeSetList(nodes) {
var promises = [];
nodes.forEach(function(node) {
if (!node.err) {
- promises.push(loadNodeSet(node).catch(err => {}));
+ if (node.type === "plugin") {
+ promises.push(loadPlugin(node).catch(err => {}));
+ } else {
+ promises.push(loadNodeSet(node).catch(err => {}));
+ }
} else {
promises.push(node);
}
@@ -338,7 +500,7 @@ function addModule(module) {
}
}
}
- return loadNodeFiles(moduleFiles).then(() => module)
+ return loadModuleFiles(moduleFiles).then(() => module)
} catch(err) {
return Promise.reject(err);
}
diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js
index ed4b81d8e..92e03fd62 100644
--- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js
+++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js
@@ -220,33 +220,41 @@ function getModuleNodeFiles(module) {
var moduleDir = module.dir;
var pkg = module.package;
- var nodes = pkg['node-red'].nodes||{};
- var results = [];
var iconDirs = [];
var iconList = [];
- for (var n in nodes) {
- /* istanbul ignore else */
- if (nodes.hasOwnProperty(n)) {
- var file = path.join(moduleDir,nodes[n]);
- results.push({
- file: file,
- module: pkg.name,
- name: n,
- version: pkg.version
- });
- var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons");
- if (iconDirs.indexOf(iconDir) == -1) {
- try {
- fs.statSync(iconDir);
- var icons = scanIconDir(iconDir);
- iconList.push({path:iconDir,icons:icons});
- iconDirs.push(iconDir);
- } catch(err) {
+
+ function scanTypes(types) {
+ const files = [];
+ for (var n in types) {
+ /* istanbul ignore else */
+ if (types.hasOwnProperty(n)) {
+ var file = path.join(moduleDir,types[n]);
+ files.push({
+ file: file,
+ module: pkg.name,
+ name: n,
+ version: pkg.version
+ });
+ var iconDir = path.join(moduleDir,path.dirname(types[n]),"icons");
+ if (iconDirs.indexOf(iconDir) == -1) {
+ try {
+ fs.statSync(iconDir);
+ var icons = scanIconDir(iconDir);
+ iconList.push({path:iconDir,icons:icons});
+ iconDirs.push(iconDir);
+ } catch(err) {
+ }
}
}
}
+ return files;
}
- var result = {files:results,icons:iconList};
+
+ var result = {
+ nodeFiles:scanTypes(pkg['node-red'].nodes||{}),
+ pluginFiles:scanTypes(pkg['node-red'].plugins||{}),
+ icons:iconList
+ };
var examplesDir = path.join(moduleDir,"examples");
try {
@@ -396,6 +404,7 @@ function convertModuleFileListToObject(moduleFiles) {
local: moduleFile.local||false,
user: moduleFile.user||false,
nodes: {},
+ plugins: {},
icons: nodeModuleFiles.icons,
examples: nodeModuleFiles.examples
};
@@ -408,11 +417,14 @@ function convertModuleFileListToObject(moduleFiles) {
if (moduleFile.usedBy) {
nodeList[moduleFile.package.name].usedBy = moduleFile.usedBy;
}
- nodeModuleFiles.files.forEach(function(node) {
- node.local = moduleFile.local||false;
+ nodeModuleFiles.nodeFiles.forEach(function(node) {
nodeList[moduleFile.package.name].nodes[node.name] = node;
nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false;
});
+ nodeModuleFiles.pluginFiles.forEach(function(plugin) {
+ nodeList[moduleFile.package.name].plugins[plugin.name] = plugin;
+ nodeList[moduleFile.package.name].plugins[plugin.name].local = moduleFile.local || false;
+ });
});
return nodeList;
}
diff --git a/packages/node_modules/@node-red/registry/lib/plugins.js b/packages/node_modules/@node-red/registry/lib/plugins.js
new file mode 100644
index 000000000..c2bb2298d
--- /dev/null
+++ b/packages/node_modules/@node-red/registry/lib/plugins.js
@@ -0,0 +1,100 @@
+const registry = require("./registry");
+const {events} = require("@node-red/util")
+
+var pluginConfigCache = {};
+var pluginToId = {};
+var plugins = {};
+var pluginsByType = {};
+var settings;
+
+function init(_settings) {
+ settings = _settings;
+ plugins = {};
+ pluginConfigCache = {};
+ pluginToId = {};
+ pluginsByType = {};
+}
+
+function registerPlugin(nodeSetId,id,definition) {
+ var moduleId = registry.getModuleFromSetId(nodeSetId);
+ var pluginId = registry.getNodeFromSetId(nodeSetId);
+
+ definition.id = id;
+ definition.module = moduleId;
+ pluginToId[id] = nodeSetId;
+ plugins[id] = definition;
+ var module = registry.getModule(moduleId);
+ module.plugins[pluginId].plugins.push(definition);
+ if (definition.type) {
+ pluginsByType[definition.type] = pluginsByType[definition.type] || [];
+ pluginsByType[definition.type].push(definition);
+ }
+ if (definition.onadd && typeof definition.onadd === 'function') {
+ definition.onadd();
+ }
+ events.emit("registry:plugin-added",id);
+}
+
+function getPlugin(id) {
+ return plugins[id]
+}
+
+function getPluginsByType(type) {
+ return pluginsByType[type] || [];
+}
+
+function getPluginConfigs(lang) {
+ if (!pluginConfigCache[lang]) {
+ var result = "";
+ var script = "";
+ var moduleConfigs = registry.getModuleList();
+ for (var module in moduleConfigs) {
+ /* istanbul ignore else */
+ if (moduleConfigs.hasOwnProperty(module)) {
+ var plugins = moduleConfigs[module].plugins;
+ for (var plugin in plugins) {
+ if (plugins.hasOwnProperty(plugin)) {
+ var config = plugins[plugin];
+ if (config.enabled && !config.err && config.config) {
+ result += "\n\n";
+ result += config.config;
+ }
+ }
+ }
+ }
+ }
+ pluginConfigCache[lang] = result;
+ }
+ return pluginConfigCache[lang];
+}
+function getPluginList() {
+ var list = [];
+ var moduleConfigs = registry.getModuleList();
+ for (var module in moduleConfigs) {
+ /* istanbul ignore else */
+ if (moduleConfigs.hasOwnProperty(module)) {
+ var plugins = moduleConfigs[module].plugins;
+ for (var plugin in plugins) {
+ /* istanbul ignore else */
+ if (plugins.hasOwnProperty(plugin)) {
+ var pluginInfo = registry.filterNodeInfo(plugins[plugin]);
+ pluginInfo.version = moduleConfigs[module].version;
+ // if (moduleConfigs[module].pending_version) {
+ // nodeInfo.pending_version = moduleConfigs[module].pending_version;
+ // }
+ list.push(pluginInfo);
+ }
+ }
+ }
+ }
+ return list;
+}
+
+module.exports = {
+ init,
+ registerPlugin,
+ getPlugin,
+ getPluginsByType,
+ getPluginConfigs,
+ getPluginList
+}
\ No newline at end of file
diff --git a/packages/node_modules/@node-red/registry/lib/registry.js b/packages/node_modules/@node-red/registry/lib/registry.js
index 730280d72..96594fbef 100644
--- a/packages/node_modules/@node-red/registry/lib/registry.js
+++ b/packages/node_modules/@node-red/registry/lib/registry.js
@@ -67,17 +67,24 @@ function filterNodeInfo(n) {
if (n.hasOwnProperty("err")) {
r.err = n.err;
}
+ if (n.hasOwnProperty("plugins")) {
+ r.plugins = n.plugins;
+ }
+ if (n.type === "plugin") {
+ r.editor = !!n.template;
+ r.runtime = !!n.file;
+ }
return r;
}
-function getModule(id) {
+function getModuleFromSetId(id) {
var parts = id.split("/");
return parts.slice(0,parts.length-1).join("/");
}
-function getNode(id) {
+function getNodeFromSetId(id) {
var parts = id.split("/");
return parts[parts.length-1];
}
@@ -220,11 +227,11 @@ function addModule(module) {
function removeNode(id) {
- var config = moduleConfigs[getModule(id)].nodes[getNode(id)];
+ var config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
if (!config) {
throw new Error("Unrecognised id: "+id);
}
- delete moduleConfigs[getModule(id)].nodes[getNode(id)];
+ delete moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
var i = nodeList.indexOf(id);
if (i > -1) {
nodeList.splice(i,1);
@@ -294,9 +301,9 @@ function getNodeInfo(typeOrId) {
}
/* istanbul ignore else */
if (id) {
- var module = moduleConfigs[getModule(id)];
+ var module = moduleConfigs[getModuleFromSetId(id)];
if (module) {
- var config = module.nodes[getNode(id)];
+ var config = module.nodes[getNodeFromSetId(id)];
if (config) {
var info = filterNodeInfo(config);
if (config.hasOwnProperty("loaded")) {
@@ -323,9 +330,9 @@ function getFullNodeInfo(typeOrId) {
}
/* istanbul ignore else */
if (id) {
- var module = moduleConfigs[getModule(id)];
+ var module = moduleConfigs[getModuleFromSetId(id)];
if (module) {
- return module.nodes[getNode(id)];
+ return module.nodes[getNodeFromSetId(id)];
}
}
return null;
@@ -359,16 +366,10 @@ function getNodeList(filter) {
}
function getModuleList() {
- //var list = [];
- //for (var module in moduleNodes) {
- // /* istanbul ignore else */
- // if (moduleNodes.hasOwnProperty(module)) {
- // list.push(registry.getModuleInfo(module));
- // }
- //}
- //return list;
return moduleConfigs;
-
+}
+function getModule(id) {
+ return moduleConfigs[id];
}
function getModuleInfo(module) {
@@ -461,13 +462,11 @@ function getAllNodeConfigs(lang) {
var script = "";
for (var i=0;i 0)) {
continue;
}
-
- var config = module.nodes[getNode(id)];
+ var config = module.nodes[getNodeFromSetId(id)];
if (config.enabled && !config.err) {
result += "\n\n";
result += config.config;
@@ -486,11 +485,11 @@ function getAllNodeConfigs(lang) {
}
function getNodeConfig(id,lang) {
- var config = moduleConfigs[getModule(id)];
+ var config = moduleConfigs[getModuleFromSetId(id)];
if (!config) {
return null;
}
- config = config.nodes[getNode(id)];
+ config = config.nodes[getNodeFromSetId(id)];
if (config) {
var result = "\n"+config.config;
result += loader.getNodeHelp(config,lang||"en-US")
@@ -511,7 +510,7 @@ function getNodeConstructor(type) {
if (typeof id === "undefined") {
config = undefined;
} else {
- config = moduleConfigs[getModule(id)].nodes[getNode(id)];
+ config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
}
if (!config || (config.enabled && !config.err)) {
@@ -548,7 +547,7 @@ function enableNodeSet(typeOrId) {
}
var config;
try {
- config = moduleConfigs[getModule(id)].nodes[getNode(id)];
+ config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
delete config.err;
config.enabled = true;
nodeConfigCache = {};
@@ -571,7 +570,7 @@ function disableNodeSet(typeOrId) {
}
var config;
try {
- config = moduleConfigs[getModule(id)].nodes[getNode(id)];
+ config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
// TODO: persist setting
config.enabled = false;
nodeConfigCache = {};
@@ -710,6 +709,7 @@ var registry = module.exports = {
getFullNodeInfo: getFullNodeInfo,
getNodeList: getNodeList,
getModuleList: getModuleList,
+ getModule: getModule,
getModuleInfo: getModuleInfo,
getNodeIconPath: getNodeIconPath,
@@ -725,5 +725,8 @@ var registry = module.exports = {
saveNodeList: saveNodeList,
- cleanModuleList: cleanModuleList
+ cleanModuleList: cleanModuleList,
+ getModuleFromSetId: getModuleFromSetId,
+ getNodeFromSetId: getNodeFromSetId,
+ filterNodeInfo: filterNodeInfo
};
diff --git a/packages/node_modules/@node-red/registry/lib/util.js b/packages/node_modules/@node-red/registry/lib/util.js
index 94690d6cb..e6caa5d67 100644
--- a/packages/node_modules/@node-red/registry/lib/util.js
+++ b/packages/node_modules/@node-red/registry/lib/util.js
@@ -70,6 +70,17 @@ function createNodeApi(node) {
})
}
},
+ plugins: {
+ registerPlugin: function(id,definition) {
+ return runtime.plugins.registerPlugin(node.id,id,definition);
+ },
+ get: function(id) {
+ return runtime.plugins.getPlugin(id);
+ },
+ getByType: function(type) {
+ return runtime.plugins.getPluginsByType(type);
+ }
+ },
library: {
register: function(type) {
return runtime.library.register(node.id,type);
diff --git a/packages/node_modules/@node-red/runtime/lib/api/index.js b/packages/node_modules/@node-red/runtime/lib/api/index.js
index b131470b0..46d15d1e7 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/index.js
@@ -28,6 +28,7 @@ var api = module.exports = {
api.library.init(runtime);
api.projects.init(runtime);
api.context.init(runtime);
+ api.plugins.init(runtime);
},
comms: require("./comms"),
@@ -37,6 +38,7 @@ var api = module.exports = {
settings: require("./settings"),
projects: require("./projects"),
context: require("./context"),
+ plugins: require("./plugins"),
isStarted: async function(opts) {
return runtime.isStarted();
diff --git a/packages/node_modules/@node-red/runtime/lib/api/plugins.js b/packages/node_modules/@node-red/runtime/lib/api/plugins.js
new file mode 100644
index 000000000..ee35d445a
--- /dev/null
+++ b/packages/node_modules/@node-red/runtime/lib/api/plugins.js
@@ -0,0 +1,57 @@
+/**
+ * @mixin @node-red/runtime_plugins
+ */
+
+var runtime;
+
+var api = module.exports = {
+ init: function(_runtime) {
+ runtime = _runtime;
+ },
+
+
+ /**
+ * Gets the editor content for an individual plugin
+ * @param {Object} opts
+ * @param {User} opts.user - the user calling the api
+ * @param {Object} opts.req - the request to log (optional)
+ * @return {Promise} - the node information
+ * @memberof @node-red/runtime_nodes
+ */
+ getPluginList: async function(opts) {
+ runtime.log.audit({event: "plugins.list.get"}, opts.req);
+ return runtime.plugins.getPluginList();
+ },
+
+ getPluginConfigs: async function(opts) {
+ runtime.log.audit({event: "plugins.configs.get"}, opts.req);
+ return runtime.plugins.getPluginConfigs(opts.lang);
+ },
+ /**
+ * Gets all registered module message catalogs
+ * @param {Object} opts
+ * @param {User} opts.user - the user calling the api
+ * @param {User} opts.lang - the i18n language to return. If not set, uses runtime default (en-US)
+ * @param {Object} opts.req - the request to log (optional)
+ * @return {Promise