UNPKG

node-red-contrib-subflow2node

Version:

Node-RED node for converting subflow to node

233 lines (215 loc) 7.45 kB
/** * Copyright JS Foundation and other contributors, http://js.foundation * * 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. **/ module.exports = function(RED) { "use strict"; const fs = require("fs"); const os = require("os"); const path = require("path"); const crypt = require("crypto-js"); const childProcess = require("child_process"); function createPackage(dir, json) { try { const [sf, newFlow] = getSubflowDef(json); const meta = sf.meta; if (!meta) { throw new Error("no metadata"); } const module = meta.module; const type = meta.type; if (!module || !type) { throw new Error("no module info"); } const version = (meta.version || ""); const author = (meta.author || ""); const desc = (meta.desc || ""); const keywords = (meta.keywords || "").split(/ *, */).filter(x => (x !== "")); const license = meta.license; const nodes = {}; nodes[type] = "subflow.js"; const pkg = { name: module, version: version, description: desc, keywords: keywords, license: license, "node-red": { "nodes": nodes, dependencies: [ ] }, dependencies: { "crypto-js": "^4.0.0" } }; fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify(pkg, null, "\t")); return pkg; } catch (e) { console.log(e, e.stack); throw new Error("unexpected subflow JSON format"); } } function createReadme(dir, readme, pkg) { const module = pkg.name; const desc = pkg.desc; let text = readme; if (!readme || (readme === "")) { const sep = "-".repeat(module.length); text = `${module} ${sep} ${desc} `; } fs.writeFileSync(path.join(dir, "README.md"), text); } function createLicense(dir, license, pkg) { const text = ((license && (license.text !== "")) ? license : pkg.license); if (text && (text !== "")) { fs.writeFileSync(path.join(dir, "LICENSE"), text); } } function npmPack(dir) { childProcess.execSync("npm pack", { cwd: dir }); } function readTgz(dir, pkg, base64) { const tgzPath = path.join(dir, pkg.name +"-" +pkg.version +".tgz"); const data = fs.readFileSync(tgzPath, { encoding: (base64 ? "base64": null) }); return data; } function getEncoder(encoding) { var settings = RED.settings; if (settings && settings.encodeSubflow && settings.encodeSubflow.methods) { const methods = settings.encodeSubflow.methods; const method = methods.find((x) => (x.name === encoding)); if (method) { return method.encode; } } if (encoding === "AES") { // default: AES return function (flow, key) { var data = JSON.stringify(flow); var enc = crypt.AES.encrypt(data, key); return enc.toString(); } } throw new Error("encoding not defined:" +encoding); } function getSubflowDef(flow) { const newFlow = []; let sf = null; flow.forEach((item) => { if (item.hasOwnProperty("meta") && item.meta.hasOwnProperty("module")) { if (sf !== null) { throw new Error("unexpected subflow definition"); } sf = item; } else { newFlow.push(item); } }); return [sf, newFlow]; } function createJSON(dstPath, flow, encoding, key) { const [sf, newFlow] = getSubflowDef(flow); if (encoding && (encoding !== "none")) { const encode = getEncoder(encoding); const encStr = encode(newFlow, key); sf.flow = { encoding: encoding, flow: encStr }; } else { sf.flow = newFlow; } const data = JSON.stringify(sf, null, "\t"); fs.writeFileSync(dstPath, data); } function Subflow2Node(n) { RED.nodes.createNode(this, n); const node = this; const name = n.name; const base64 = n.base64; const encoding = n.encoding; this.on("input", function(msg) { const payload = msg.payload; const tmpDir = os.tmpdir(); const dir = fs.mkdtempSync(path.join(tmpDir, "subflow_")); const jsSrc = path.join(__dirname, "template", "subflow.js"); const jsDst = path.join(dir, "subflow.js"); const jsonDst = path.join(dir, "subflow.json"); const enc = msg.encoding || n.encoding; const key = enc ? (msg.encodeKey || (node.credentials && node.credentials.encodeKey) || null): null; if ((enc !== "none") && (!key || (key === ""))) { node.error("no encoding key", msg); return; } try { const pkg = createPackage(dir, payload); createReadme(dir, msg.readme, pkg); createLicense(dir, msg.license, pkg); createJSON(jsonDst, payload, encoding, key); fs.copyFileSync(jsSrc, jsDst); npmPack(dir); msg.payload = readTgz(dir, pkg, base64); node.send(msg); } catch (e) { console.log(e, e.stack); node.error(e, msg); } finally { try { fs.rmdirSync(dir, { recursive: true }); } catch (e) { console.log(e, e.stack); node.error(e, msg); } } }); this.on("close", function() { }); } RED.nodes.registerType("sf to node", Subflow2Node, { credentials: { encodeKey: { type: "password" } } }); RED.httpNode.get("/subflow2node/encodings", (req, res) => { var encs = []; var settings = RED.settings; if (settings && settings.encodeSubflow && settings.encodeSubflow.methods) { var methods = settings.encodeSubflow.methods; encs = methods.map((x) => x.name); } res.send(encs); }); };