2015-04-07 17:02:15 +02:00
|
|
|
/**
|
2017-01-11 16:24:33 +01:00
|
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
2015-04-07 17:02:15 +02:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
**/
|
|
|
|
|
|
|
|
//var UglifyJS = require("uglify-js");
|
2021-03-15 22:06:10 +01:00
|
|
|
const path = require("path");
|
|
|
|
const fs = require("fs");
|
2017-02-15 23:54:32 +01:00
|
|
|
|
2018-04-26 13:32:05 +02:00
|
|
|
var library = require("./library");
|
2020-12-02 10:25:10 +01:00
|
|
|
const {events} = require("@node-red/util")
|
2020-08-28 17:36:11 +02:00
|
|
|
var subflows = require("./subflow");
|
2021-02-12 19:14:13 +01:00
|
|
|
var externalModules = require("./externalModules")
|
2015-04-07 17:02:15 +02:00
|
|
|
var settings;
|
2015-04-26 00:29:53 +02:00
|
|
|
var loader;
|
|
|
|
|
2018-09-28 17:58:06 +02:00
|
|
|
var nodeConfigCache = {};
|
2015-04-07 17:02:15 +02:00
|
|
|
var moduleConfigs = {};
|
|
|
|
var nodeList = [];
|
|
|
|
var nodeConstructors = {};
|
2021-02-12 19:14:13 +01:00
|
|
|
var nodeOptions = {};
|
2020-08-28 17:36:11 +02:00
|
|
|
var subflowModules = {};
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
var nodeTypeToId = {};
|
|
|
|
var moduleNodes = {};
|
|
|
|
|
2020-12-02 10:25:10 +01:00
|
|
|
function init(_settings,_loader) {
|
2015-04-07 17:02:15 +02:00
|
|
|
settings = _settings;
|
2015-04-26 00:29:53 +02:00
|
|
|
loader = _loader;
|
2021-02-12 19:14:13 +01:00
|
|
|
clear();
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
2016-07-15 01:11:28 +02:00
|
|
|
function load() {
|
|
|
|
if (settings.available()) {
|
|
|
|
moduleConfigs = loadNodeConfigs();
|
|
|
|
} else {
|
|
|
|
moduleConfigs = {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
function filterNodeInfo(n) {
|
|
|
|
var r = {
|
2015-05-27 15:11:11 +02:00
|
|
|
id: n.id||n.module+"/"+n.name,
|
2015-04-07 17:02:15 +02:00
|
|
|
name: n.name,
|
|
|
|
types: n.types,
|
2016-08-04 17:49:36 +02:00
|
|
|
enabled: n.enabled,
|
2020-11-25 20:07:30 +01:00
|
|
|
local: n.local||false,
|
|
|
|
user: n.user || false
|
2015-04-07 17:02:15 +02:00
|
|
|
};
|
|
|
|
if (n.hasOwnProperty("module")) {
|
|
|
|
r.module = n.module;
|
|
|
|
}
|
|
|
|
if (n.hasOwnProperty("err")) {
|
2018-01-16 00:20:20 +01:00
|
|
|
r.err = n.err;
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2020-12-10 17:01:55 +01:00
|
|
|
if (n.hasOwnProperty("plugins")) {
|
|
|
|
r.plugins = n.plugins;
|
|
|
|
}
|
|
|
|
if (n.type === "plugin") {
|
|
|
|
r.editor = !!n.template;
|
|
|
|
r.runtime = !!n.file;
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-12-10 17:01:55 +01:00
|
|
|
function getModuleFromSetId(id) {
|
2016-05-17 22:56:03 +02:00
|
|
|
var parts = id.split("/");
|
|
|
|
return parts.slice(0,parts.length-1).join("/");
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
2020-12-10 17:01:55 +01:00
|
|
|
function getNodeFromSetId(id) {
|
2016-05-17 22:56:03 +02:00
|
|
|
var parts = id.split("/");
|
|
|
|
return parts[parts.length-1];
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function saveNodeList() {
|
|
|
|
var moduleList = {};
|
2017-01-22 00:46:44 +01:00
|
|
|
var hadPending = false;
|
|
|
|
var hasPending = false;
|
2015-04-07 17:02:15 +02:00
|
|
|
for (var module in moduleConfigs) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (moduleConfigs.hasOwnProperty(module)) {
|
|
|
|
if (Object.keys(moduleConfigs[module].nodes).length > 0) {
|
|
|
|
if (!moduleList[module]) {
|
|
|
|
moduleList[module] = {
|
|
|
|
name: module,
|
|
|
|
version: moduleConfigs[module].version,
|
2016-08-04 17:49:36 +02:00
|
|
|
local: moduleConfigs[module].local||false,
|
2020-11-25 20:07:30 +01:00
|
|
|
user: moduleConfigs[module].user||false,
|
2015-04-07 17:02:15 +02:00
|
|
|
nodes: {}
|
|
|
|
};
|
2017-01-22 00:46:44 +01:00
|
|
|
if (moduleConfigs[module].hasOwnProperty('pending_version')) {
|
|
|
|
hadPending = true;
|
|
|
|
if (moduleConfigs[module].pending_version !== moduleConfigs[module].version) {
|
|
|
|
moduleList[module].pending_version = moduleConfigs[module].pending_version;
|
|
|
|
hasPending = true;
|
|
|
|
} else {
|
|
|
|
delete moduleConfigs[module].pending_version;
|
|
|
|
}
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
var nodes = moduleConfigs[module].nodes;
|
|
|
|
for(var node in nodes) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (nodes.hasOwnProperty(node)) {
|
|
|
|
var config = nodes[node];
|
|
|
|
var n = filterNodeInfo(config);
|
|
|
|
delete n.err;
|
|
|
|
delete n.file;
|
|
|
|
delete n.id;
|
|
|
|
n.file = config.file;
|
|
|
|
moduleList[module].nodes[node] = n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-22 00:46:44 +01:00
|
|
|
if (hadPending && !hasPending) {
|
2017-07-08 18:27:45 +02:00
|
|
|
events.emit("runtime-event",{id:"restart-required",retain: true});
|
2017-01-22 00:46:44 +01:00
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
if (settings.available()) {
|
|
|
|
return settings.set("nodes",moduleList);
|
|
|
|
} else {
|
2018-04-25 00:49:57 +02:00
|
|
|
return Promise.reject("Settings unavailable");
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadNodeConfigs() {
|
|
|
|
var configs = settings.get("nodes");
|
|
|
|
|
|
|
|
if (!configs) {
|
|
|
|
return {};
|
|
|
|
} else if (configs['node-red']) {
|
|
|
|
return configs;
|
|
|
|
} else {
|
|
|
|
// Migrate from the 0.9.1 format of settings
|
|
|
|
var newConfigs = {};
|
|
|
|
for (var id in configs) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (configs.hasOwnProperty(id)) {
|
|
|
|
var nodeConfig = configs[id];
|
|
|
|
var moduleName;
|
|
|
|
var nodeSetName;
|
|
|
|
|
|
|
|
if (nodeConfig.module) {
|
|
|
|
moduleName = nodeConfig.module;
|
|
|
|
nodeSetName = nodeConfig.name.split(":")[1];
|
|
|
|
} else {
|
|
|
|
moduleName = "node-red";
|
|
|
|
nodeSetName = nodeConfig.name.replace(/^\d+-/,"").replace(/\.js$/,"");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!newConfigs[moduleName]) {
|
|
|
|
newConfigs[moduleName] = {
|
|
|
|
name: moduleName,
|
|
|
|
nodes:{}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
newConfigs[moduleName].nodes[nodeSetName] = {
|
|
|
|
name: nodeSetName,
|
|
|
|
types: nodeConfig.types,
|
|
|
|
enabled: nodeConfig.enabled,
|
|
|
|
module: moduleName
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
settings.set("nodes",newConfigs);
|
|
|
|
return newConfigs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-26 13:32:05 +02:00
|
|
|
function addModule(module) {
|
|
|
|
moduleNodes[module.name] = [];
|
|
|
|
moduleConfigs[module.name] = module;
|
2022-09-29 22:28:13 +02:00
|
|
|
for (const setName in module.nodes) {
|
2018-04-26 13:32:05 +02:00
|
|
|
if (module.nodes.hasOwnProperty(setName)) {
|
2022-09-29 22:28:13 +02:00
|
|
|
const set = module.nodes[setName];
|
2022-09-03 22:23:38 +02:00
|
|
|
if (!set.types) {
|
2022-09-29 22:28:13 +02:00
|
|
|
const err = new Error("Set has no types")
|
|
|
|
err.code = "set_has_no_types"
|
2022-09-03 22:23:38 +02:00
|
|
|
err.details = {
|
2022-09-29 22:28:13 +02:00
|
|
|
...set
|
2022-09-03 22:23:38 +02:00
|
|
|
}
|
|
|
|
set.err = err
|
|
|
|
}
|
2018-04-26 13:32:05 +02:00
|
|
|
moduleNodes[module.name].push(set.name);
|
|
|
|
nodeList.push(set.id);
|
|
|
|
if (!set.err) {
|
|
|
|
set.types.forEach(function(t) {
|
|
|
|
if (nodeTypeToId.hasOwnProperty(t)) {
|
2021-07-12 17:09:25 +02:00
|
|
|
set.err = new Error("Type already registered");
|
2018-04-26 13:32:05 +02:00
|
|
|
set.err.code = "type_already_registered";
|
|
|
|
set.err.details = {
|
|
|
|
type: t,
|
|
|
|
moduleA: getNodeInfo(t).module,
|
|
|
|
moduleB: set.module
|
|
|
|
}
|
2018-01-16 00:20:20 +01:00
|
|
|
|
2018-04-26 13:32:05 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
if (!set.err) {
|
|
|
|
set.types.forEach(function(t) {
|
|
|
|
nodeTypeToId[t] = set.id;
|
|
|
|
});
|
|
|
|
}
|
2018-01-16 00:20:20 +01:00
|
|
|
}
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2018-04-26 13:32:05 +02:00
|
|
|
if (module.icons) {
|
2018-08-04 23:23:06 +02:00
|
|
|
icon_paths[module.name] = icon_paths[module.name] || [];
|
2018-04-26 13:32:05 +02:00
|
|
|
module.icons.forEach(icon=>icon_paths[module.name].push(path.resolve(icon.path)) )
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2018-04-26 13:32:05 +02:00
|
|
|
if (module.examples) {
|
|
|
|
library.addExamplesDir(module.name,module.examples.path);
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2018-09-28 17:58:06 +02:00
|
|
|
nodeConfigCache = {};
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
2018-04-26 13:32:05 +02:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
function removeNode(id) {
|
2020-12-10 17:01:55 +01:00
|
|
|
var config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
if (!config) {
|
|
|
|
throw new Error("Unrecognised id: "+id);
|
|
|
|
}
|
2020-12-10 17:01:55 +01:00
|
|
|
delete moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
var i = nodeList.indexOf(id);
|
|
|
|
if (i > -1) {
|
|
|
|
nodeList.splice(i,1);
|
|
|
|
}
|
|
|
|
config.types.forEach(function(t) {
|
2015-06-08 17:32:50 +02:00
|
|
|
var typeId = nodeTypeToId[t];
|
|
|
|
if (typeId === id) {
|
2020-08-28 17:36:11 +02:00
|
|
|
delete subflowModules[t];
|
2015-06-08 17:32:50 +02:00
|
|
|
delete nodeConstructors[t];
|
2021-02-12 19:14:13 +01:00
|
|
|
delete nodeOptions[t];
|
2015-06-08 17:32:50 +02:00
|
|
|
delete nodeTypeToId[t];
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
});
|
|
|
|
config.enabled = false;
|
|
|
|
config.loaded = false;
|
2018-09-28 17:58:06 +02:00
|
|
|
nodeConfigCache = {};
|
2015-04-07 17:02:15 +02:00
|
|
|
return filterNodeInfo(config);
|
|
|
|
}
|
|
|
|
|
2020-11-25 20:07:30 +01:00
|
|
|
function removeModule(name,skipSave) {
|
2015-04-07 17:02:15 +02:00
|
|
|
if (!settings.available()) {
|
|
|
|
throw new Error("Settings unavailable");
|
|
|
|
}
|
2020-11-25 20:07:30 +01:00
|
|
|
var infoList = [];
|
|
|
|
var module = moduleConfigs[name];
|
|
|
|
var nodes = moduleNodes[name];
|
2015-04-07 17:02:15 +02:00
|
|
|
if (!nodes) {
|
2020-11-25 20:07:30 +01:00
|
|
|
throw new Error("Unrecognised module: "+name);
|
|
|
|
}
|
|
|
|
if (module.usedBy && module.usedBy > 0) {
|
|
|
|
// We are removing a module that is used by other modules... so whilst
|
|
|
|
// this module should be removed from the editor palette, it needs to
|
|
|
|
// stay in the runtime... for now.
|
|
|
|
module.user = false;
|
|
|
|
for (var i=0;i<nodes.length;i++) {
|
|
|
|
infoList.push(filterNodeInfo(nodes[i]));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (module.dependencies) {
|
|
|
|
module.dependencies.forEach(function(dep) {
|
|
|
|
// Check each dependency of this module to see if it is a non-user-installed
|
|
|
|
// module that we can expect to disappear once npm uninstall is run
|
|
|
|
if (!moduleConfigs[dep].user) {
|
|
|
|
moduleConfigs[dep].usedBy = moduleConfigs[dep].usedBy.filter(m => m !== name);
|
|
|
|
if (moduleConfigs[dep].usedBy.length === 0) {
|
|
|
|
// Remove the dependency
|
|
|
|
removeModule(dep,true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
for (var i=0;i<nodes.length;i++) {
|
|
|
|
infoList.push(removeNode(name+"/"+nodes[i]));
|
|
|
|
}
|
|
|
|
delete moduleNodes[name];
|
|
|
|
delete moduleConfigs[name];
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2020-11-25 20:07:30 +01:00
|
|
|
if (!skipSave) {
|
|
|
|
saveNodeList();
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
return infoList;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getNodeInfo(typeOrId) {
|
|
|
|
var id = typeOrId;
|
2016-04-28 15:17:48 +02:00
|
|
|
if (nodeTypeToId.hasOwnProperty(typeOrId)) {
|
2015-04-07 17:02:15 +02:00
|
|
|
id = nodeTypeToId[typeOrId];
|
|
|
|
}
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (id) {
|
2020-12-10 17:01:55 +01:00
|
|
|
var module = moduleConfigs[getModuleFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
if (module) {
|
2020-12-10 17:01:55 +01:00
|
|
|
var config = module.nodes[getNodeFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
if (config) {
|
|
|
|
var info = filterNodeInfo(config);
|
|
|
|
if (config.hasOwnProperty("loaded")) {
|
|
|
|
info.loaded = config.loaded;
|
|
|
|
}
|
2019-01-07 15:54:35 +01:00
|
|
|
if (module.pending_version) {
|
|
|
|
info.pending_version = module.pending_version;
|
|
|
|
}
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
info.version = module.version;
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-05-27 15:11:11 +02:00
|
|
|
function getFullNodeInfo(typeOrId) {
|
|
|
|
// Used by index.enableNodeSet so that .file can be retrieved to pass
|
|
|
|
// to loader.loadNodeSet
|
|
|
|
var id = typeOrId;
|
2016-04-28 15:17:48 +02:00
|
|
|
if (nodeTypeToId.hasOwnProperty(typeOrId)) {
|
2015-05-27 15:11:11 +02:00
|
|
|
id = nodeTypeToId[typeOrId];
|
|
|
|
}
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (id) {
|
2020-12-10 17:01:55 +01:00
|
|
|
var module = moduleConfigs[getModuleFromSetId(id)];
|
2015-05-27 15:11:11 +02:00
|
|
|
if (module) {
|
2020-12-10 17:01:55 +01:00
|
|
|
return module.nodes[getNodeFromSetId(id)];
|
2015-11-11 23:11:02 +01:00
|
|
|
}
|
2015-05-27 15:11:11 +02:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
function getNodeList(filter) {
|
|
|
|
var list = [];
|
|
|
|
for (var module in moduleConfigs) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (moduleConfigs.hasOwnProperty(module)) {
|
2020-11-25 20:07:30 +01:00
|
|
|
if (!moduleConfigs[module].user && (moduleConfigs[module].usedBy && moduleConfigs[module].usedBy.length > 0)) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
var nodes = moduleConfigs[module].nodes;
|
|
|
|
for (var node in nodes) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (nodes.hasOwnProperty(node)) {
|
|
|
|
var nodeInfo = filterNodeInfo(nodes[node]);
|
|
|
|
nodeInfo.version = moduleConfigs[module].version;
|
2017-01-22 00:46:44 +01:00
|
|
|
if (moduleConfigs[module].pending_version) {
|
|
|
|
nodeInfo.pending_version = moduleConfigs[module].pending_version;
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
if (!filter || filter(nodes[node])) {
|
|
|
|
list.push(nodeInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getModuleList() {
|
2015-04-08 21:17:24 +02:00
|
|
|
return moduleConfigs;
|
2020-12-10 17:01:55 +01:00
|
|
|
}
|
|
|
|
function getModule(id) {
|
|
|
|
return moduleConfigs[id];
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getModuleInfo(module) {
|
|
|
|
if (moduleNodes[module]) {
|
|
|
|
var nodes = moduleNodes[module];
|
|
|
|
var m = {
|
|
|
|
name: module,
|
|
|
|
version: moduleConfigs[module].version,
|
2016-08-04 17:49:36 +02:00
|
|
|
local: moduleConfigs[module].local,
|
2020-11-25 20:07:30 +01:00
|
|
|
user: moduleConfigs[module].user,
|
2018-05-21 23:08:04 +02:00
|
|
|
path: moduleConfigs[module].path,
|
2015-04-07 17:02:15 +02:00
|
|
|
nodes: []
|
|
|
|
};
|
2020-11-25 20:07:30 +01:00
|
|
|
if (moduleConfigs[module].dependencies) {
|
|
|
|
m.dependencies = moduleConfigs[module].dependencies;
|
|
|
|
}
|
2019-01-07 15:54:35 +01:00
|
|
|
if (moduleConfigs[module] && moduleConfigs[module].pending_version) {
|
|
|
|
m.pending_version = moduleConfigs[module].pending_version;
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
for (var i = 0; i < nodes.length; ++i) {
|
|
|
|
var nodeInfo = filterNodeInfo(moduleConfigs[module].nodes[nodes[i]]);
|
|
|
|
nodeInfo.version = m.version;
|
|
|
|
m.nodes.push(nodeInfo);
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-24 23:38:42 +01:00
|
|
|
function getCaller(){
|
|
|
|
var orig = Error.prepareStackTrace;
|
|
|
|
Error.prepareStackTrace = function(_, stack){ return stack; };
|
2015-11-24 23:43:17 +01:00
|
|
|
var err = new Error();
|
2015-11-24 23:38:42 +01:00
|
|
|
Error.captureStackTrace(err, arguments.callee);
|
|
|
|
var stack = err.stack;
|
|
|
|
Error.prepareStackTrace = orig;
|
|
|
|
stack.shift();
|
|
|
|
stack.shift();
|
|
|
|
return stack[0].getFileName();
|
|
|
|
}
|
|
|
|
|
2021-02-12 19:14:13 +01:00
|
|
|
function registerNodeConstructor(nodeSet,type,constructor,options) {
|
2016-04-28 15:17:48 +02:00
|
|
|
if (nodeConstructors.hasOwnProperty(type)) {
|
2015-04-07 17:02:15 +02:00
|
|
|
throw new Error(type+" already registered");
|
|
|
|
}
|
|
|
|
//TODO: Ensure type is known - but doing so will break some tests
|
|
|
|
// that don't have a way to register a node template ahead
|
|
|
|
// of registering the constructor
|
2016-04-07 23:18:28 +02:00
|
|
|
|
2016-04-28 12:23:42 +02:00
|
|
|
var nodeSetInfo = getFullNodeInfo(nodeSet);
|
|
|
|
if (nodeSetInfo) {
|
|
|
|
if (nodeSetInfo.types.indexOf(type) === -1) {
|
|
|
|
// A type is being registered for a known set, but for some reason
|
|
|
|
// we didn't spot it when parsing the HTML file.
|
|
|
|
// Registered a type is the definitive action - not the presence
|
|
|
|
// of an edit template. Ensure it is on the list of known types.
|
|
|
|
nodeSetInfo.types.push(type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
nodeConstructors[type] = constructor;
|
2021-02-12 19:14:13 +01:00
|
|
|
nodeOptions[type] = options;
|
|
|
|
if (options) {
|
|
|
|
if (options.dynamicModuleList) {
|
|
|
|
externalModules.register(type,options.dynamicModuleList);
|
|
|
|
}
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
events.emit("type-registered",type);
|
|
|
|
}
|
|
|
|
|
2020-08-28 17:36:11 +02:00
|
|
|
function registerSubflow(nodeSet, subflow) {
|
|
|
|
var nodeSetInfo = getFullNodeInfo(nodeSet);
|
|
|
|
|
|
|
|
const result = subflows.register(nodeSet,subflow);
|
|
|
|
|
|
|
|
if (subflowModules.hasOwnProperty(result.type)) {
|
|
|
|
throw new Error(result.type+" already registered");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nodeSetInfo) {
|
|
|
|
if (nodeSetInfo.types.indexOf(result.type) === -1) {
|
|
|
|
nodeSetInfo.types.push(result.type);
|
|
|
|
nodeTypeToId[result.type] = nodeSetInfo.id;
|
|
|
|
}
|
|
|
|
nodeSetInfo.config = result.config;
|
|
|
|
}
|
|
|
|
subflowModules[result.type] = result;
|
2021-02-14 01:02:08 +01:00
|
|
|
externalModules.registerSubflow(result.type,subflow);
|
|
|
|
|
|
|
|
|
2020-08-28 17:36:11 +02:00
|
|
|
events.emit("type-registered",result.type);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-04-26 00:29:53 +02:00
|
|
|
function getAllNodeConfigs(lang) {
|
2018-09-28 17:58:06 +02:00
|
|
|
if (!nodeConfigCache[lang]) {
|
2015-04-07 17:02:15 +02:00
|
|
|
var result = "";
|
|
|
|
var script = "";
|
|
|
|
for (var i=0;i<nodeList.length;i++) {
|
|
|
|
var id = nodeList[i];
|
2020-12-10 17:01:55 +01:00
|
|
|
var module = moduleConfigs[getModuleFromSetId(id)]
|
2020-11-25 20:07:30 +01:00
|
|
|
if (!module.user && (module.usedBy && module.usedBy.length > 0)) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-12-10 17:01:55 +01:00
|
|
|
var config = module.nodes[getNodeFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
if (config.enabled && !config.err) {
|
2018-05-11 23:30:57 +02:00
|
|
|
result += "\n<!-- --- [red-module:"+id+"] --- -->\n";
|
2015-04-07 17:02:15 +02:00
|
|
|
result += config.config;
|
2015-04-27 20:59:09 +02:00
|
|
|
result += loader.getNodeHelp(config,lang||"en-US")||"";
|
2015-04-07 17:02:15 +02:00
|
|
|
//script += config.script;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//if (script.length > 0) {
|
|
|
|
// result += '<script type="text/javascript">';
|
|
|
|
// result += UglifyJS.minify(script, {fromString: true}).code;
|
|
|
|
// result += '</script>';
|
|
|
|
//}
|
2018-09-28 17:58:06 +02:00
|
|
|
nodeConfigCache[lang] = result;
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2018-09-28 17:58:06 +02:00
|
|
|
return nodeConfigCache[lang];
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
2015-04-26 00:29:53 +02:00
|
|
|
function getNodeConfig(id,lang) {
|
2020-12-10 17:01:55 +01:00
|
|
|
var config = moduleConfigs[getModuleFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
if (!config) {
|
|
|
|
return null;
|
|
|
|
}
|
2020-12-10 17:01:55 +01:00
|
|
|
config = config.nodes[getNodeFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
if (config) {
|
2018-05-11 23:30:57 +02:00
|
|
|
var result = "<!-- --- [red-module:"+id+"] --- -->\n"+config.config;
|
2015-04-26 00:29:53 +02:00
|
|
|
result += loader.getNodeHelp(config,lang||"en-US")
|
2015-11-11 23:11:02 +01:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
//if (config.script) {
|
|
|
|
// result += '<script type="text/javascript">'+config.script+'</script>';
|
|
|
|
//}
|
|
|
|
return result;
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getNodeConstructor(type) {
|
|
|
|
var id = nodeTypeToId[type];
|
|
|
|
|
|
|
|
var config;
|
|
|
|
if (typeof id === "undefined") {
|
|
|
|
config = undefined;
|
|
|
|
} else {
|
2020-12-10 17:01:55 +01:00
|
|
|
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!config || (config.enabled && !config.err)) {
|
2020-08-28 17:36:11 +02:00
|
|
|
return nodeConstructors[type] || subflowModules[type];
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function clear() {
|
2018-09-28 17:58:06 +02:00
|
|
|
nodeConfigCache = {};
|
2015-04-07 17:02:15 +02:00
|
|
|
moduleConfigs = {};
|
|
|
|
nodeList = [];
|
|
|
|
nodeConstructors = {};
|
2021-02-12 19:14:13 +01:00
|
|
|
nodeOptions = {};
|
2020-08-28 17:36:11 +02:00
|
|
|
subflowModules = {};
|
2015-04-07 17:02:15 +02:00
|
|
|
nodeTypeToId = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getTypeId(type) {
|
2016-04-28 15:17:48 +02:00
|
|
|
if (nodeTypeToId.hasOwnProperty(type)) {
|
|
|
|
return nodeTypeToId[type];
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function enableNodeSet(typeOrId) {
|
|
|
|
if (!settings.available()) {
|
|
|
|
throw new Error("Settings unavailable");
|
|
|
|
}
|
|
|
|
|
|
|
|
var id = typeOrId;
|
2016-04-28 15:17:48 +02:00
|
|
|
if (nodeTypeToId.hasOwnProperty(typeOrId)) {
|
2015-04-07 17:02:15 +02:00
|
|
|
id = nodeTypeToId[typeOrId];
|
|
|
|
}
|
|
|
|
var config;
|
|
|
|
try {
|
2020-12-10 17:01:55 +01:00
|
|
|
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
delete config.err;
|
|
|
|
config.enabled = true;
|
2018-09-28 17:58:06 +02:00
|
|
|
nodeConfigCache = {};
|
2017-03-08 15:38:33 +01:00
|
|
|
settings.enableNodeSettings(config.types);
|
2015-05-27 15:11:11 +02:00
|
|
|
return saveNodeList().then(function() {
|
|
|
|
return filterNodeInfo(config);
|
|
|
|
});
|
2015-04-07 17:02:15 +02:00
|
|
|
} catch (err) {
|
|
|
|
throw new Error("Unrecognised id: "+typeOrId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function disableNodeSet(typeOrId) {
|
|
|
|
if (!settings.available()) {
|
|
|
|
throw new Error("Settings unavailable");
|
|
|
|
}
|
|
|
|
var id = typeOrId;
|
2016-04-28 15:17:48 +02:00
|
|
|
if (nodeTypeToId.hasOwnProperty(typeOrId)) {
|
2015-04-07 17:02:15 +02:00
|
|
|
id = nodeTypeToId[typeOrId];
|
|
|
|
}
|
|
|
|
var config;
|
|
|
|
try {
|
2020-12-10 17:01:55 +01:00
|
|
|
config = moduleConfigs[getModuleFromSetId(id)].nodes[getNodeFromSetId(id)];
|
2015-04-07 17:02:15 +02:00
|
|
|
// TODO: persist setting
|
|
|
|
config.enabled = false;
|
2018-09-28 17:58:06 +02:00
|
|
|
nodeConfigCache = {};
|
2017-03-08 15:38:33 +01:00
|
|
|
settings.disableNodeSettings(config.types);
|
2015-05-27 15:11:11 +02:00
|
|
|
return saveNodeList().then(function() {
|
|
|
|
return filterNodeInfo(config);
|
|
|
|
});
|
2015-04-07 17:02:15 +02:00
|
|
|
} catch (err) {
|
|
|
|
throw new Error("Unrecognised id: "+id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanModuleList() {
|
|
|
|
var removed = false;
|
|
|
|
for (var mod in moduleConfigs) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (moduleConfigs.hasOwnProperty(mod)) {
|
|
|
|
var nodes = moduleConfigs[mod].nodes;
|
|
|
|
var node;
|
|
|
|
if (mod == "node-red") {
|
|
|
|
// For core nodes, look for nodes that are enabled, !loaded and !errored
|
|
|
|
for (node in nodes) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (nodes.hasOwnProperty(node)) {
|
|
|
|
var n = nodes[node];
|
|
|
|
if (n.enabled && !n.err && !n.loaded) {
|
|
|
|
removeNode(mod+"/"+node);
|
|
|
|
removed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-04-08 21:17:24 +02:00
|
|
|
} else {
|
|
|
|
if (moduleConfigs[mod] && !moduleNodes[mod]) {
|
|
|
|
// For node modules, look for missing ones
|
|
|
|
for (node in nodes) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (nodes.hasOwnProperty(node)) {
|
|
|
|
removeNode(mod+"/"+node);
|
|
|
|
removed = true;
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2015-04-08 21:17:24 +02:00
|
|
|
delete moduleConfigs[mod];
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (removed) {
|
|
|
|
saveNodeList();
|
|
|
|
}
|
|
|
|
}
|
2017-01-22 00:46:44 +01:00
|
|
|
function setModulePendingUpdated(module,version) {
|
|
|
|
moduleConfigs[module].pending_version = version;
|
|
|
|
return saveNodeList().then(function() {
|
|
|
|
return getModuleInfo(module);
|
|
|
|
});
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
|
2020-11-25 20:07:30 +01:00
|
|
|
function setUserInstalled(module,userInstalled) {
|
|
|
|
moduleConfigs[module].user = userInstalled;
|
|
|
|
return saveNodeList().then(function() {
|
|
|
|
return getModuleInfo(module);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
function addModuleDependency(module,usedBy) {
|
|
|
|
moduleConfigs[module].usedBy = moduleConfigs[module].usedBy || [];
|
|
|
|
moduleConfigs[module].usedBy.push(usedBy);
|
|
|
|
}
|
|
|
|
|
2018-08-16 00:12:51 +02:00
|
|
|
var icon_paths = { };
|
2017-02-15 23:54:32 +01:00
|
|
|
var iconCache = {};
|
|
|
|
|
|
|
|
function getNodeIconPath(module,icon) {
|
2018-04-20 21:50:20 +02:00
|
|
|
if (/\.\./.test(icon)) {
|
|
|
|
throw new Error();
|
|
|
|
}
|
2017-02-15 23:54:32 +01:00
|
|
|
var iconName = module+"/"+icon;
|
|
|
|
if (iconCache[iconName]) {
|
|
|
|
return iconCache[iconName];
|
|
|
|
} else {
|
|
|
|
var paths = icon_paths[module];
|
|
|
|
if (paths) {
|
|
|
|
for (var p=0;p<paths.length;p++) {
|
|
|
|
var iconPath = path.join(paths[p],icon);
|
|
|
|
try {
|
|
|
|
fs.statSync(iconPath);
|
|
|
|
iconCache[iconName] = iconPath;
|
|
|
|
return iconPath;
|
|
|
|
} catch(err) {
|
|
|
|
// iconPath doesn't exist
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-04 10:04:27 +02:00
|
|
|
if (module !== "node-red") {
|
|
|
|
return getNodeIconPath("node-red", icon);
|
|
|
|
}
|
2018-08-16 00:12:51 +02:00
|
|
|
return null;
|
2017-02-15 23:54:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-30 14:13:35 +01:00
|
|
|
function getNodeIcons() {
|
|
|
|
var iconList = {};
|
|
|
|
for (var module in moduleConfigs) {
|
|
|
|
if (moduleConfigs.hasOwnProperty(module)) {
|
|
|
|
if (moduleConfigs[module].icons) {
|
2018-04-26 13:32:05 +02:00
|
|
|
iconList[module] = [];
|
|
|
|
moduleConfigs[module].icons.forEach(icon=>{ iconList[module] = iconList[module].concat(icon.icons)})
|
2017-11-30 14:13:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return iconList;
|
|
|
|
}
|
|
|
|
|
2021-03-15 22:06:10 +01:00
|
|
|
function getModuleResource(module, resourcePath) {
|
|
|
|
let mod = moduleConfigs[module];
|
|
|
|
if (mod && mod.resources) {
|
|
|
|
let basePath = mod.resources.path;
|
|
|
|
let fullPath = path.join(basePath,resourcePath);
|
|
|
|
if (/^\.\./.test(path.relative(basePath,fullPath))) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (fs.existsSync(fullPath)) {
|
|
|
|
return fullPath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
var registry = module.exports = {
|
|
|
|
init: init,
|
2016-07-15 01:11:28 +02:00
|
|
|
load: load,
|
2015-04-07 17:02:15 +02:00
|
|
|
clear: clear,
|
|
|
|
|
|
|
|
registerNodeConstructor: registerNodeConstructor,
|
|
|
|
getNodeConstructor: getNodeConstructor,
|
|
|
|
|
2020-08-28 17:36:11 +02:00
|
|
|
registerSubflow: registerSubflow,
|
2018-04-26 13:32:05 +02:00
|
|
|
|
|
|
|
addModule: addModule,
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
enableNodeSet: enableNodeSet,
|
|
|
|
disableNodeSet: disableNodeSet,
|
2015-11-11 23:11:02 +01:00
|
|
|
|
2017-01-22 00:46:44 +01:00
|
|
|
setModulePendingUpdated: setModulePendingUpdated,
|
2020-11-25 20:07:30 +01:00
|
|
|
setUserInstalled: setUserInstalled,
|
|
|
|
addModuleDependency:addModuleDependency,
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
removeModule: removeModule,
|
2015-11-11 23:11:02 +01:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
getNodeInfo: getNodeInfo,
|
2015-05-27 15:11:11 +02:00
|
|
|
getFullNodeInfo: getFullNodeInfo,
|
2015-04-07 17:02:15 +02:00
|
|
|
getNodeList: getNodeList,
|
|
|
|
getModuleList: getModuleList,
|
2020-12-10 17:01:55 +01:00
|
|
|
getModule: getModule,
|
2015-04-07 17:02:15 +02:00
|
|
|
getModuleInfo: getModuleInfo,
|
|
|
|
|
2017-02-15 23:54:32 +01:00
|
|
|
getNodeIconPath: getNodeIconPath,
|
2017-11-30 14:13:35 +01:00
|
|
|
getNodeIcons: getNodeIcons,
|
2021-03-15 22:06:10 +01:00
|
|
|
getModuleResource: getModuleResource,
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
/**
|
|
|
|
* Gets all of the node template configs
|
|
|
|
* @return all of the node templates in a single string
|
|
|
|
*/
|
|
|
|
getAllNodeConfigs: getAllNodeConfigs,
|
|
|
|
getNodeConfig: getNodeConfig,
|
|
|
|
|
|
|
|
getTypeId: getTypeId,
|
|
|
|
|
|
|
|
saveNodeList: saveNodeList,
|
|
|
|
|
2020-12-10 17:01:55 +01:00
|
|
|
cleanModuleList: cleanModuleList,
|
|
|
|
getModuleFromSetId: getModuleFromSetId,
|
|
|
|
getNodeFromSetId: getNodeFromSetId,
|
|
|
|
filterNodeInfo: filterNodeInfo
|
2015-04-07 17:02:15 +02:00
|
|
|
};
|