UNPKG

node-red-nodegen

Version:

Node generator for Node-RED

369 lines (340 loc) 13.6 kB
/** * Copyright OpenJS Foundation and other contributors * * 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. **/ const Ajv = require('ajv'); const addFormats = require('ajv-formats'); const addFormatsDraft2019 = require('ajv-formats-draft2019'); const url = require('url'); const fs = require('fs'); const path = require('path'); function validateTd(td) { const TDSchema = JSON.parse(fs.readFileSync(path.join(__dirname, './td-json-schema-validation.json'), 'utf-8')); const ajv = new Ajv({allErrors: true, strict: false, strictSchema: false}); addFormats(ajv); addFormatsDraft2019(ajv); const valid = ajv.validate(TDSchema, td); return { result: valid, errorText: valid?"":ajv.errorsText()}; } function setdefault(obj, propname, dflt) { return obj.hasOwnProperty(propname) ? obj[propname] : dflt; } function isOpInForms(op, forms) { return forms && forms .map(e=>(e.op && typeof e.op === 'string') ? [e.op] : e.op) .reduce((a,v)=>a.concat(v),[]) .some(e=>e === op); } function textDirection(langid) { // based on CLDR and other sources. const rtlLangs = [ "ar-AE","ar-BH","ar-DJ","ar-DZ","ar-EG","ar-EH","ar-ER","ar-IL","ar-IQ", "ar-JO","ar-KM","ar-KW","ar-LB","ar-LY","ar-MA","ar-MR","ar-OM","ar-PS", "ar-QA","ar-SA","ar-SD","ar-SO","ar-SS","ar-SY","ar-TD","ar-TN","ar-YE","ar", "az-Arab", "ckb-IR","ckb", "fa-AF","fa", "he", "ks", "lrc-IQ","lrc", "mzn", "pa-Arab", "ps-PK","ps", "sd", "ug", "ur-IN","ur", "uz-Arab", "yi", ]; if (langid === undefined || langid === "") { return 'auto'; } else { return (rtlLangs.includes(langid) ? 'rtl' : 'ltr'); } } function normalizeTd(td) { const baseUrl = td.base || ""; function formconv(intr, f, affordance) { if (f.hasOwnProperty("href")) { if (td.base) { f.href = new URL(f.href, td.base).toString() } else { f.href = new URL(f.href).toString() } f.href = f.href.replace(/%7B/gi,'{').replace(/%7D/gi,'}'); } if (f.hasOwnProperty("security") && typeof f.security === 'string') { f.security = [f.security]; } if (!f.hasOwnProperty("security")) { if (intr.hasOwnProperty("security")) { f.security = intr.security; } else if (td.hasOwnProperty("security")) { f.security = td.security; } } f.contentType = setdefault(f, "contentType", "application/json"); switch (affordance) { case "PropertyAffordance": f.op = setdefault(f, "op", ["readproperty", "writeproperty"]); break; case "ActionAffordance": f.op = setdefault(f, "op", "invokeaction"); break; case "EventAffordance": f.op = setdefault(f, "op", "subscribeevent"); break; } return f; } // normalize 'security' as Array of String. if (td.hasOwnProperty("security") && typeof td.security === "string") { td.security = [td.security]; } // Set default values in security definition for (const sd in td.securityDefinitions) { const sdef = td.securityDefinitions[sd]; switch (sdef.scheme) { case "basic": sdef.in = setdefault(sdef, "in", "header"); break; case "digest": sdef.in = setdefault(sdef, "in", "header"); sdef.qop = setdefault(sdef, "qop", "auth"); break; case "bearer": sdef.in = setdefault(sdef, "in", "header"); sdef.alg = setdefault(sdef, "alg", "ES256"); sdef.format = setdefault(sdef, "format", "jwt"); break; case "pop": sdef.in = setdefault(sdef, "in", "header"); sdef.alg = setdefault(sdef, "alg", "ES256"); sdef.format = setdefault(sdef, "format", "jwt"); break; case "oauth2": sdef.flow = setdefault(sdef, "flow", "implicit"); break; case "apikey": sdef.in = setdefault(sdef, "in", "query"); break; default: break; } } // Set default values in properties for (const p in td.properties) { const pdef = td.properties[p]; if (pdef.hasOwnProperty("security") && typeof pdef.security === 'string') { pdef.security = [pdef.security]; } if (pdef.forms) { pdef.forms = pdef.forms.map((f) => formconv(pdef, f, "PropertyAffordance")); // no filtering based on protocol } // if there is forms which have readproperty in op, writeOnly is false, otherwise true pdef.writeOnly = setdefault(pdef, "writeOnly", !isOpInForms("readproperty", pdef.forms)); // if there is forms which have writeproperty in op, readOnly is false, otherwise true pdef.readOnly = setdefault(pdef, "readOnly", !isOpInForms("writeproperty", pdef.forms)); // if there is forms which have observeproperty in op, observable is true, otherwise false pdef.observable = setdefault(pdef, "observable", isOpInForms("observeproperty", pdef.forms)); // in any cases, if it explicitly stated by writeOnly/readOnly/observable, use it. } // Set default values in actions for (const a in td.actions) { const adef = td.actions[a]; adef.safe = setdefault(adef, "safe", false); adef.idempotent = setdefault(adef, "idempotent", false); if (adef.hasOwnProperty("security") && typeof adef.security === 'string') { adef.security = [adef.security]; } if (adef.forms) { adef.forms = adef.forms.map((f) => formconv(adef, f, "ActionAffordance")); // no filtering based on protocol } } // Set default values in events for (const e in td.events) { const edef = td.events[e]; if (edef.hasOwnProperty("security") && typeof edef.security === 'string') { edef.security = [edef.security]; } if (edef.forms) { edef.forms = edef.forms.map((f) => formconv(edef, f, "EventAffordance")); // no filtering based on protocol } } // Set default values in toplevel forms --- TODO: make this work if (td.forms) { const pdef = td; td.forms = td.forms.map((f) => formconv(pdef, f, "TopPropertyAffordance")); } // Set default value in toplevel context td["@context"] = setdefault(td, "@context", "https://www.w3.org/2019/wot/td/v1"); // Convert top level forms ({read/write}allproperties) to "ALLPROPERTIES" property. if (td.forms) { const convforms = td.forms .map(f => { if (f.op && typeof f.op === 'string') { f.op = [f.op]; } f.op = f.op.map(o => { let res = o; switch (o) { case 'readallproperties': res = 'readproperty'; break; case 'writeallproperties': res = 'writeproperty'; } return res; }); return f; }); td.properties['__ALLPROPERTIES'] = { title: "All Properties", description: "all properties of this Thing", forms: convforms, type: "object", writeOnly: !isOpInForms("readproperty", convforms), readOnly: !isOpInForms("writeproperty", convforms), observable: false }; } return td; } function filterFormTd(td) { for (const p in td.properties) { let forms = td.properties[p].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.properties[p].forms = forms; } for (const a in td.actions) { let forms = td.actions[a].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.actions[a].forms = forms; } for (const e in td.events) { let forms = td.events[e].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.events[e].forms = forms; } return td; } function makeformsel(td) { const formsel = { property: {}, action: {}, event: {} }; for (const p in td.properties) { const forms = td.properties[p].forms; const readforms = []; const writeforms = []; const observeforms = []; for (let i = 0; i < forms.length; i++) { const secscheme = td.securityDefinitions[forms[i].security[0]].scheme; if (!forms[i].hasOwnProperty('op') || forms[i].op.includes("readproperty")) { readforms.push({index:i,secscheme:secscheme,title:forms[i].href}); } if (!forms[i].hasOwnProperty('op') || forms[i].op.includes("writeproperty")) { writeforms.push({index:i,secscheme:secscheme,title:forms[i].href}); } if (forms[i].hasOwnProperty('op') && forms[i].op.includes("observeproperty")) { observeforms.push({index:i,secscheme:secscheme,title:forms[i].href}); } } formsel.property[p] = {read: readforms, write: writeforms, observe: observeforms}; } for (const a in td.actions) { const forms = td.actions[a].forms; formsel.action[a] = []; for (let i = 0; i < forms.length; i++) { const secscheme = td.securityDefinitions[forms[i].security[0]].scheme; if (!forms[i].hasOwnProperty('op') || forms[i].op.includes("invokeaction")) { formsel.action[a].push({index:i,secscheme:secscheme,title:forms[i].href}); } } } for (const e in td.events) { const forms = td.events[e].forms; formsel.event[e] = []; for (let i = 0; i < forms.length; i++) { const secscheme = td.securityDefinitions[forms[i].security[0]].scheme; if (!forms[i].hasOwnProperty('op') || forms[i].op.includes("subscribeevent")) { formsel.event[e].push({index:i,secscheme:secscheme,title:forms[i].href}); } } } return formsel; } function woticon(td) { const iotschemaToIcon = { "Sensor": "font-awesome/fa-microchip", "BinarySwitch": "font-awesome/fa-toggle-on", "SwitchStatus": "font-awesome/fa-toggle-on", "Toggle": "font-awesome/fa-toggle-on", "Light": "font-awesome/fa-lightbulb-o",//"light.png", "Actuator": "font-awesome/fa-bolt", "CurrentColour": "font-awesome/fa-paint-brush", "ColourData": "font-awesome/fa-paint-brush", "LightControl": "font-awesome/fa-cogs", "Illuminance": "font-awesome/fa-sun-o", "IlluminanceSensing": "font-awesome/fa-sun-o", "MotionControl": "font-awesome/fa-arrows-alt", "Temperature": "font-awesome/fa-thermometer-half", "TemperatureSensing": "font-awesome/fa-thermometer-half", "TemperatureData": "font-awesome/fa-thermometer-half", "Thermostat": "font-awesome/fa-thermometer-half", "Pump": "font-awesome/fa-tint", "AirConditioner": "font-awesome/fa-snowflake-o", "UltrasonicSensing": "font-awesome/fa-rss", "HumiditySensing": "font-awesome/fa-umbrella", "SoundPressure": "font-awesome/fa-volume-up", "Valve": "font-awesome/fa-wrench", "ProximitySensing": "font-awesome/fa-crosshairs" }; const iotschemaPrefix = 'iot'; if (Array.isArray(td['@type'])) { const candidates = td['@type'].map((e) => { for (f in iotschemaToIcon) { if (`${iotschemaPrefix}:${f}` === e) { return iotschemaToIcon[f]; }; }; }).filter((e) => e); if (candidates.length > 0) { return candidates[0]; } } return "white-globe.png"; } module.exports = { validateTd: validateTd, normalizeTd: normalizeTd, filterFormTd: filterFormTd, makeformsel: makeformsel, woticon: woticon, textDirection: textDirection }