From 85436135630cc8622fdd5da8112441fa4ff3bd00 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 15 Mar 2021 21:06:10 +0000 Subject: [PATCH] Allow module to provide resources and automatically expose them --- .../@node-red/editor-api/lib/editor/index.js | 2 ++ .../@node-red/editor-api/lib/editor/ui.js | 22 ++++++++++++++++ .../@node-red/registry/lib/index.js | 14 ++++++++++- .../@node-red/registry/lib/localfilesystem.js | 9 +++++++ .../@node-red/registry/lib/registry.js | 22 +++++++++++++--- .../@node-red/runtime/lib/api/nodes.js | 25 +++++++++++++++++++ .../@node-red/runtime/lib/nodes/index.js | 1 + test/resources/plugin/test-plugin/test.html | 4 ++- 8 files changed, 94 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/index.js b/packages/node_modules/@node-red/editor-api/lib/editor/index.js index b2fdc1f6b..6943027c2 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/index.js @@ -75,6 +75,8 @@ module.exports = { editorApp.get("/icons/:module/:icon",ui.icon); editorApp.get("/icons/:scope/:module/:icon",ui.icon); + editorApp.get(/^\/resources\/((?:@[^\/]+\/)?[^\/]+)\/(.+)$/,ui.moduleResource); + var theme = require("./theme"); theme.init(settings, runtimeAPI); editorApp.use("/theme",theme.app()); diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/ui.js b/packages/node_modules/@node-red/editor-api/lib/editor/ui.js index a4812a668..5fff30725 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/ui.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/ui.js @@ -68,6 +68,28 @@ module.exports = { apiUtils.rejectHandler(req,res,err); }) }, + + moduleResource: function(req, res) { + let resourcePath = req.params[1]; + let opts = { + user: req.user, + module: req.params[0], + path: resourcePath + } + runtimeAPI.nodes.getModuleResource(opts).then(function(data) { + if (data) { + var contentType = mime.getType(resourcePath); + res.set("Content-Type", contentType); + res.send(data); + } else { + res.status(404).end() + } + }).catch(function(err) { + console.log(err.stack); + apiUtils.rejectHandler(req,res,err); + }) + }, + editor: async function(req,res) { res.send(Mustache.render(editorTemplate,await theme.context())); }, diff --git a/packages/node_modules/@node-red/registry/lib/index.js b/packages/node_modules/@node-red/registry/lib/index.js index a620d9ad2..17bb92dce 100644 --- a/packages/node_modules/@node-red/registry/lib/index.js +++ b/packages/node_modules/@node-red/registry/lib/index.js @@ -301,6 +301,17 @@ module.exports = { */ getNodeExampleFlowPath: library.getExampleFlowPath, + /** + * Gets the full path to a module's resource file + * @param {String} module - the name of the module providing the resource file + * @param {String} path - the relative path of the resource file + * @return {String} the full path to the resource file + * + * @function + * @memberof @node-red/registry + */ + getModuleResource: registry.getModuleResource, + checkFlowDependencies: externalModules.checkFlowDependencies, registerPlugin: plugins.registerPlugin, @@ -309,7 +320,8 @@ module.exports = { getPluginList: plugins.getPluginList, getPluginConfigs: plugins.getPluginConfigs, exportPluginSettings: plugins.exportPluginSettings, - + + deprecated: require("./deprecated") }; diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index d07d104ec..9e08c4c53 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -262,6 +262,14 @@ function getModuleNodeFiles(module) { result.examples = {path:examplesDir}; } catch(err) { } + + var resourcesDir = path.join(moduleDir,"resources"); + try { + fs.statSync(resourcesDir) + result.resources = {path:resourcesDir}; + } catch(err) { + } + return result; } @@ -406,6 +414,7 @@ function convertModuleFileListToObject(moduleFiles,seedObject) { user: moduleFile.user||false, nodes: {}, plugins: {}, + resources: nodeModuleFiles.resources, icons: nodeModuleFiles.icons, examples: nodeModuleFiles.examples }; diff --git a/packages/node_modules/@node-red/registry/lib/registry.js b/packages/node_modules/@node-red/registry/lib/registry.js index 82de4e442..ec55330f1 100644 --- a/packages/node_modules/@node-red/registry/lib/registry.js +++ b/packages/node_modules/@node-red/registry/lib/registry.js @@ -15,8 +15,8 @@ **/ //var UglifyJS = require("uglify-js"); -var path = require("path"); -var fs = require("fs"); +const path = require("path"); +const fs = require("fs"); var library = require("./library"); const {events} = require("@node-red/util") @@ -680,7 +680,6 @@ function getNodeIconPath(module,icon) { function getNodeIcons() { var iconList = {}; - for (var module in moduleConfigs) { if (moduleConfigs.hasOwnProperty(module)) { if (moduleConfigs[module].icons) { @@ -692,6 +691,21 @@ function getNodeIcons() { return iconList; } +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; +} + var registry = module.exports = { init: init, load: load, @@ -722,6 +736,8 @@ var registry = module.exports = { getNodeIconPath: getNodeIconPath, getNodeIcons: getNodeIcons, + getModuleResource: getModuleResource, + /** * Gets all of the node template configs * @return all of the node templates in a single string diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js index 20a4c2a7b..1dbe575c7 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js +++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js @@ -463,5 +463,30 @@ var api = module.exports = { } else { return null } + }, + + /** + * Gets a resource from a module + * @param {Object} opts + * @param {User} opts.user - the user calling the api + * @param {String} opts.module - the id of the module requesting the resource + * @param {String} opts.path - the path of the resource + * @param {Object} opts.req - the request to log (optional) + * @return {Promise} - the resource file as a Buffer or null if not found + * @memberof @node-red/runtime_nodes + */ + getModuleResource: async function(opts) { + var resourcePath = runtime.nodes.getModuleResource(opts.module, opts.path); + if (resourcePath) { + return fs.readFile(resourcePath).catch(err => { + if (err.code === 'EISDIR') { + return null; + } + err.status = 400; + throw err; + }); + } else { + return null + } } } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js index 3d8acb34c..0ee041665 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js @@ -230,6 +230,7 @@ module.exports = { getNodeIcons: registry.getNodeIcons, getNodeExampleFlows: registry.getNodeExampleFlows, getNodeExampleFlowPath: registry.getNodeExampleFlowPath, + getModuleResource: registry.getModuleResource, clearRegistry: registry.clear, cleanModuleList: registry.cleanModuleList, diff --git a/test/resources/plugin/test-plugin/test.html b/test/resources/plugin/test-plugin/test.html index 72d2979df..7a43a0528 100644 --- a/test/resources/plugin/test-plugin/test.html +++ b/test/resources/plugin/test-plugin/test.html @@ -1,4 +1,6 @@ \ No newline at end of file + + +