UNPKG

node-red-contrib-snap4city-user

Version:

Nodes for Snap4city project, targeted to standard user (no developer)

1,247 lines (1,034 loc) 48.6 kB
/* NODE-RED-CONTRIB-SNAP4CITY-USER Copyright (C) 2018 DISIT Lab http://www.disit.org - University of Florence This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ var s4cUtility = require("./snap4city-utility.js"); var s4cOrionUtility = require("./snap4city-orion-utility.js"); var bodyParser = require("body-parser"); var getBody = require('raw-body'); var typer = require('media-typer'); var isUtf8 = require('is-utf8'); var http = require("follow-redirects").http; var https = require("follow-redirects").https; var urllib = require("url"); var https2 = require('https'); var when = require('when'); var fs = require('fs'); var path = require('path') var os = require('os') module.exports = function (RED) { "use strict"; var subscriptionIDs = new s4cOrionUtility.SubscriptionStore(RED); var nodeStatus = new s4cOrionUtility.NodeStatus(); var orionHttpRequestOptions = new s4cOrionUtility.OrionHttpRequestOptions(); var jsonParser = bodyParser.json(); var urlencParser = bodyParser.urlencoded({ extended: true }); var token = ""; var LIMIT = 30; /* *This node is not shown by NodeRed but is used by any other node *to make the various http requests (query, subscribe, etc.). *Configuration nodes doc: https://nodered.org/docs/creating-nodes/config-nodes */ RED.nodes.registerType("orion-service-api-v2", OrionServiceV2, { credentials: { user: { type: "text" }, password: { type: "password" } } }); function OrionServiceV2(config) { RED.nodes.createNode(this, config); var serviceNode = this; this.authentication = config.authentication; this.url = config.url; this.port = config.port; var orionUrl = getOrionUrl(config); var credentials = { user: '', password: '' }; if (/\/$/.test(this.url)) { this.url = this.url.substring(0, this.url.length - 1); } if (!this.port) { throw "Missing port"; } this.init = function (node) { nodeStatus.initializing(node); node.selectedContextbroker = config.selectedContextbroker; return when.promise(function (resolve) { // get token from context broker getToken(node, serviceNode.url, credentials).then(function () { resolve(); }); }); }; this.queryContext = function (node, config, queryParams) { const logger = s4cUtility.getLogger(RED, node); const uid = s4cUtility.retrieveAppID(RED); var orionBrokerService = RED.nodes.getNode(config.service); var accessToken = s4cUtility.retrieveAccessToken(RED, node, orionBrokerService.authentication, uid); nodeStatus.querying(node) logger.debug(`Querying entity: ${config.enid}`); return when.promise(function (resolve, reject) { s4cOrionUtility.getContextBrokerListForRegisterActivity(RED, node, orionBrokerService.url, orionBrokerService.port, retrieveDeviceName(node, config.enid, config.tenant, config.servicepath), uid, accessToken); var [hostname, prefixPath] = s4cOrionUtility.splitUrlInHostnameAndPrefixPath(orionBrokerService.url); var options = orionHttpRequestOptions.generateForOrionAPIV2Query(hostname, orionBrokerService.port, prefixPath, config, queryParams, accessToken) options = orionHttpRequestOptions.setHeaderAuthTenantAndTls(options, config, RED) var msg = {}; var req = https2.request(options, function (res) { (node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8'); msg.statusCode = res.statusCode; msg.headers = res.headers; msg.payload = ""; res.on('data', function (chunk) { msg.payload += chunk; }); res.on('end', function () { if (res.statusCode === 200) { resolve(msg); } else { reject(msg); } }); }); req.on('error', function (err) { reject(err); }); req.write(JSON.stringify(queryParams)); req.end(); }); }; this.updateContext = function (node, config, payload, auth) { const logger = s4cUtility.getLogger(RED, node); const uid = s4cUtility.retrieveAppID(RED); var orionBrokerService = RED.nodes.getNode(config.service); var accessToken = s4cUtility.retrieveAccessToken(RED, node, orionBrokerService.authentication, uid); nodeStatus.sending(node) logger.debug(`Updating entity: ${config.enid} with payload: ${JSON.stringify(payload)}`); return when.promise(function (resolve, reject) { s4cOrionUtility.getContextBrokerListForRegisterActivity(RED, node, orionBrokerService.url, orionBrokerService.port, retrieveDeviceName(node, config.enid, config.tenant, config.servicepath), uid, accessToken); var [hostname, prefixPath] = s4cOrionUtility.splitUrlInHostnameAndPrefixPath(orionBrokerService.url); var options = orionHttpRequestOptions.generateForOrionAPIV2Update(hostname, orionBrokerService.port, prefixPath, config, auth, Buffer.byteLength(JSON.stringify(payload)), accessToken) options = orionHttpRequestOptions.setHeaderAuthTenantAndTls(options, config, RED, auth) var msg = {}; var req = https2.request(options, function (res) { msg.statusCode = res.statusCode; msg.headers = res.headers; msg.payload = ""; res.on('data', function (chunk) { msg.payload += chunk; }); res.on('end', function () { if (res.statusCode === 204) { resolve(msg); } else { reject(msg); } }); }); req.on('error', function (e) { reject(e); }); req.write(JSON.stringify(payload)); req.end(); }); }; this.subscribe = function (node, config, payload) { const logger = s4cUtility.getLogger(RED, node); const uid = s4cUtility.retrieveAppID(RED); var orionBrokerService = RED.nodes.getNode(config.service); var accessToken = s4cUtility.retrieveAccessToken(RED, node, orionBrokerService.authentication, uid, false); var reference = payload.notification.http.url; nodeStatus.subscribing(node) logger.debug(`Subscribing entity: ${config.enid} with payload: ${JSON.stringify(payload)}`); var correctPath =os.homedir() + "/.snap4cityConfig/";//consider edge scenario if (RED.settings.APPID != null) {//check edge scenario or cloud scenario correctPath = "/data/.snap4cityConfig/";//consider cloud scenario } var jsonFilePath=correctPath+node.id+".json"; if(!node.enid&&fs.existsSync(jsonFilePath)){ //if(fs.existsSync(jsonFilePath)){ //if(!node.enid){ var subscriptionJson = JSON.parse(fs.readFileSync(jsonFilePath)); config.enid=subscriptionJson[subscriptionJson.length-1]['id']; } s4cOrionUtility.getContextBrokerListForRegisterActivity(RED, node, orionBrokerService.url, orionBrokerService.port, retrieveDeviceName(node, config.enid, config.tenant, config.servicepath), uid, accessToken); var [hostname, prefixPath] = s4cOrionUtility.splitUrlInHostnameAndPrefixPath(orionBrokerService.url); var options = orionHttpRequestOptions.generateForOrionAPIV2Subscribe(hostname, orionBrokerService.port, prefixPath, config, JSON.stringify(payload).length, accessToken) options = orionHttpRequestOptions.setHeaderAuthTenantAndTls(options, config, RED) logger.debug("subscribeContext options:" + JSON.stringify(options)); try { var msg = {}; var req = https2.request(options, function (res) { if (node.ret === "bin") res.setEncoding('binary'); else res.setEncoding('utf8'); msg.statusCode = res.statusCode; msg.headers = res.headers; msg.payload = ""; res.on('data', function (chunk) { msg.payload += chunk; }); res.on('end', function () { logger.info("subscribeContext result:" + msg.statusCode + " " + msg.payload); if (res.statusCode === 201) { if (msg.headers.location != null) {//TODO verify this var subscriptionID = msg.headers.location.replace('/v2/subscriptions/', ''); var nodeID = (node.id + "").replace('.', ''); //listen subscription just if the return code is 200ok listenOnUrl(nodeID, function (req, res) { if (req.body.subscriptionId != subscriptionIDs.getSubscriptionOfNode(nodeID)) { logger.error("Recognized invalid subscription: " + req.body.subscriptionId + " currentId: " + subscriptionIDs.getSubscriptionOfNode(nodeID)); unsubscribeFromOrion(node, req.body.subscriptionId, orionUrl, config); } else { //var payload = formatOutput(node, config, req.body.data);//TODO *1 verify formatOutput node.send({ payload: req.body.data,//TODO *1 statusCode: 201 }); } res.sendStatus(200); }); logger.info("subscribeContext elementId: " + config.enid + " nodeId: " + nodeID + " oldSubId: " + subscriptionIDs.getSubscriptionOfNode(nodeID) + " newSubId: " + subscriptionID); var idToUnsubscribe = subscriptionIDs.getSubscriptionOfNode(nodeID);//save previous sub for unsub subscriptionIDs.setSubscriptionOnNode(subscriptionID, nodeID);//update new subs if (idToUnsubscribe != undefined) {//if there was a previous sub logger.info("unsubscription:" + idToUnsubscribe); setTimeout(function () { unsubscribeFromOrion(node, idToUnsubscribe, orionUrl, config);//unsub previous sub }, 2000); } } else if (parsedResponse.result == false) { logger.error("subscribeContext error:" + JSON.stringify(msg)); nodeStatus.getError(node, msg) } } else { logger.error("subscribeContext error:" + JSON.stringify(msg)); var deviceType = (msg.payload.deviceType ? msg.payload.deviceType : config.entype); var deviceId = (msg.payload.deviceId ? msg.payload.deviceId : config.enid); var correctPath =os.homedir() + "/.snap4cityConfig/";//consider edge scenario if (RED.settings.APPID != null) {//check edge scenario or cloud scenario correctPath = "/data/.snap4cityConfig/";//consider cloud scenario } var nodeID = (node.id + "").replace('.', ''); var jsonFilePath=correctPath+nodeID+".json"; if(fs.existsSync(jsonFilePath)){ var subscriptionJson = JSON.parse(fs.readFileSync(jsonFilePath)); var ind=-1 for(var i = 0; i < subscriptionJson.length; i++){ if(subscriptionJson[i].id === deviceId && subscriptionJson[i].type === deviceType){ ind=i; break; } } if(ind!=-1){ subscriptionJson.splice(ind, 1); } fs.writeFileSync(jsonFilePath, JSON.stringify(subscriptionJson)) } nodeStatus.getError(node, msg) } }); }); req.on('error', function (err) { logger.error("subscribeContext error:" + err); nodeStatus.getError(node, err) }); nodeStatus.listening(node, reference) if (payload) { logger.debug("subscribeContext payload:" + JSON.stringify(payload)); req.write(JSON.stringify(payload)); } req.end(); } catch (err) { logger.error("subscribeContext error:" + err); nodeStatus.getError(node, err) } }; } function unsubscribeFromOrion(node, subscriptionId, url, config) { const logger = s4cUtility.getLogger(RED, node); const uid = s4cUtility.retrieveAppID(RED); var orionBrokerService = RED.nodes.getNode(config.service); var accessToken = s4cUtility.retrieveAccessToken(RED, node, orionBrokerService.authentication, uid); return when.promise(function (resolve, reject) { var [hostname, prefixPath] = s4cOrionUtility.splitUrlInHostnameAndPrefixPath(orionBrokerService.url); var options = orionHttpRequestOptions.generateForOrionAPIV2Unsubscribe(hostname, orionBrokerService.port, prefixPath, config, subscriptionId, accessToken) options = orionHttpRequestOptions.setHeaderAuthTenantAndTls(options, config, RED) var req = https2.request(options, function (res) { (node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8'); res.on('end', function () { resolve(res); }); }); req.on('error', function (err) { logger.error("Error in unsubscribe: "+err); console.log(err) reject(err); }); req.write(JSON.stringify( { "statusCode": { "code": "200", "reasonPhrase": "OK" }, "subscriptionId": subscriptionId } )); req.end(); }); } // retrieve token from context broker function getToken(node, orionUrl, credentials) { const logger = s4cUtility.getLogger(RED, node); var tokenUrl = orionUrl + "/token"; if (tokenUrl.indexOf("http://") >= 0) { tokenUrl = "https://" + tokenUrl.substring(7); } else if (orionUrl.indexOf("https://") < 0) { tokenUrl = "https://" + tokenUrl; } var opts = urllib.parse(tokenUrl); opts.method = "POST"; opts.headers = {}; opts.headers['content-type'] = "application/json"; opts.headers["Accept"] = "application/json"; var payload = { "username": credentials.user, "password": credentials.password }; payload = JSON.stringify(payload); opts.headers['content-length'] = Buffer.byteLength(payload); token = ""; return when.promise(function (resolve, reject) { if (!credentials.user) { resolve(); } else { logger.debug("--requesting token using payload: " + payload); var req = (https).request(opts, function (res) { (node.ret === "bin") ? res.setEncoding('binary') : res.setEncoding('utf8'); res.on('data', function (chunk) { token += chunk; }); res.on('end', function () { logger.debug("--resolved token: " + token); resolve(token); }); }); req.on('error', function (err) { reject(err); node.status({ fill: "red", shape: "ring", text: err.code }); node.send({ payload: err.toString() + " : " + tokenUrl, statusCode: err.code }); }); req.write(payload); req.end(); } }); } function formatOutput(node, n, msg) { var contextResponses = msg.contextResponses; var payload = []; if (typeof contextResponses != "undefined") { contextResponses.forEach(function (entry) { var contextElement = entry.contextElement; delete contextElement.isPattern; if (!n.includeattr) { // removing attribute metadata contextElement.attributes.forEach(function (entry) { delete entry.metadatas; }); } payload.push(contextElement); }); } return payload; } function rawBodyParser(req, res, next) { if (req._body) { return next(); } req.body = ""; req._body = true; var isText = true; var checkUTF = false; if (req.headers['content-type']) { var parsedType = typer.parse(req.headers['content-type']) if (parsedType.type === "text") { isText = true; } else if (parsedType.subtype === "xml" || parsedType.suffix === "xml") { isText = true; } else if (parsedType.type !== "application") { isText = false; } else if (parsedType.subtype !== "octet-stream") { checkUTF = true; } } getBody(req, { length: req.headers['content-length'], encoding: isText ? "utf8" : null }, function (err, buf) { if (err) { return next(err); } if (!isText && checkUTF && isUtf8(buf)) { buf = buf.toString() } req.body = buf; next(); }); } function validateInput(node, n) { var err = null; n.url = n.url; n.port = n.port; n.enid = n.enid; n.entype = n.entype; n.ispattern = n.ispattern; n.userk1 = n.userk1; n.passk2 = n.passk2; n.tenant = n.tenant; n.servicepath = n.servicepath; n.apikey = n.apikey; n.basicAuth = n.basicAuth; n.attributes = n.attributes; n.condvalues = n.condvalues; n.includeattr = n.includeattr; n.port = n.port * 1; /*if (!n.enid || !n.entype) { err = "Missing subscription parameters"; }*/ if (err) { throw err; } n.attributes = n.attributes || []; if (n.attributes.constructor !== Array) { n.attributes = (n.attributes || "").split(","); for (var i = 0; i < n.attributes.length; i++) { n.attributes[i] = n.attributes[i].trim(); } } n.condvalues = n.condvalues || []; if (n.condvalues.constructor !== Array) { n.condvalues = (n.condvalues || "").split(","); for (var i = 0; i < n.condvalues.length; i++) { n.condvalues[i] = n.condvalues[i].trim(); } } } function generateSubscribePayload(node, n) { // prepare payload for context subscription // contains node uid and url besides data supplied in node fields var nodeID = node.id + ""; nodeID = nodeID.replace('.', ''); return when.promise( function (resolve, reject) { getMyUri(n).then(function (myUri) { var correctPath =os.homedir() + "/.snap4cityConfig/";//consider edge scenario if (RED.settings.APPID != null) {//check edge scenario or cloud scenario correctPath = "/data/.snap4cityConfig/";//consider cloud scenario } var jsonFilePath=correctPath+n.id+".json"; if (fs.existsSync(jsonFilePath)) { var subscriptionJson = JSON.parse(fs.readFileSync(jsonFilePath)); var entities=[] for (var i = 0; i < subscriptionJson.length; i++) { entities.push({"id": subscriptionJson[i]['id'],"isPattern": n.ispattern,"type": subscriptionJson[i]['type']}) } if(!n.enid){ var devid=subscriptionJson[subscriptionJson.length-1]['id']; }else{ var devid=n.enid; } }else{ var entities=[] entities.push({"id": n.enid,"isPattern": n.ispattern,"type": n.entype}) fs.writeFileSync(jsonFilePath, JSON.stringify(entities)); } var subject= {"condition": { "attrs": n.condvals ? [n.condvals] : [] } }; /////////////////// /////////////////// /////////////////// var orionUrl = node.brokerConn.url; var prot='http'; if (orionUrl.indexOf("https://") >= 0) { prot='https'; } /////////////////// /////////////////// ////////////////// subject["entities"]=entities; var sub={ "description": `A subscription to get info about ${devid}`, "notification": { "http": { "url": `${prot}://${myUri}/${nodeID}` }, "attrs": n.attributes } }; sub["subject"]=subject; var expires = new Date(); expires.setDate(expires.getDate()+Number(n.duration)); sub["expires"]= expires.toISOString(); if (n.throttle!=0) sub["throttling"]=Number(n.throttle); resolve(sub); }); } ); } function getOrionUrl(n) { var orionUrl = n.url; if (!/^((http|https):\/\/)/.test(orionUrl)) { orionUrl = "https://" + orionUrl + ":" + n.port; } return orionUrl; } //OrionSubscribeV2 node constructor RED.nodes.registerType("orion-subscribe-api-v2", OrionSubscribeV2); function OrionSubscribeV2(n) { RED.nodes.createNode(this, n); var node = this; this.service = n.service; this.brokerConn = RED.nodes.getNode(this.service); this.noderedhost = n.noderedhost; this.userk1 = n.userk1; this.passk2 = n.passk2; this.apikey = n.apikey; this.basicAuth = n.basicAuth; this.on("close", function () { var nodeID = node.id + ""; nodeID = nodeID.replace('.', ''); RED.httpNode._router.stack.forEach(function (route, i, routes) { if (route.route && route.route.path == "/" + nodeID) { routes.splice(i, 1); } }); //cannot invoke unsubscribe from here since the config is empty (and the contextbroker uri is unknown) // unsubscribeFromOrion(node, subscriptionIDs[nodeID], null, n); }); // validate mandatory fields validateInput(this, n); this.on('input', function (msg) { var action = msg.payload.action; var deviceType = (msg.payload.deviceType ? msg.payload.deviceType : n.entype); var deviceId = (msg.payload.deviceId ? msg.payload.deviceId : n.enid); var isPattern = (!(typeof msg.payload.isPattern === "undefined") ? msg.payload.isPattern :n.ispattern); n.entype=deviceType; n.enid=deviceId; n.ispattern=isPattern; var correctPath =os.homedir() + "/.snap4cityConfig/";//consider edge scenario if (RED.settings.APPID != null) {//check edge scenario or cloud scenario correctPath = "/data/.snap4cityConfig/";//consider cloud scenario } var jsonFilePath=correctPath+n.id+".json"; if (!fs.existsSync(correctPath)) { fs.mkdirSync(correctPath);//not exist folder, create folder + empty file fs.writeFileSync(jsonFilePath, JSON.stringify(msg.payload)); } else if (!fs.existsSync(jsonFilePath)){ //this.subscriptionJson = JSON.parse(fs.readFileSync(jsonFilePath)); fs.writeFileSync(jsonFilePath, '[]',{ flag: 'w' });//not exist file, create empy file } else //folder and file exist, read from it this.subscriptionJson = JSON.parse(fs.readFileSync(jsonFilePath)); var subscriptionJson = JSON.parse(fs.readFileSync(jsonFilePath)); if(action==="add"){ var ind=true; for(var i = 0; i < subscriptionJson.length; i++){ if(subscriptionJson[i].id === deviceId && subscriptionJson[i].type === deviceType){ ind=false; break; } } if(ind){ subscriptionJson.push({"id": deviceId,"isPattern": n.ispattern,"type": deviceType}); fs.writeFileSync(jsonFilePath, JSON.stringify(subscriptionJson)); } }else if(action==="remove"){ var ind=-1 for(var i = 0; i < subscriptionJson.length; i++){ if(subscriptionJson[i].id === deviceId && subscriptionJson[i].type === deviceType){ ind=i; break; } } if(ind!=-1){ subscriptionJson.splice(ind, 1); } fs.writeFileSync(jsonFilePath, JSON.stringify(subscriptionJson)) }else{ msg.payload ="possible actions are add or remove" } generateSubscribePayload(node, n).then(function (payload) { node.brokerConn.subscribe(node, n, payload); }); msg.info=msg.payload; node.send(msg); }); node.brokerConn.init(node, n).then(function () { var correctPath =os.homedir() + "/.snap4cityConfig/";//consider edge scenario if (RED.settings.APPID != null) {//check edge scenario or cloud scenario correctPath = "/data/.snap4cityConfig/";//consider cloud scenario } var jsonFilePath=correctPath+n.id+".json"; if (fs.existsSync(jsonFilePath)){ generateSubscribePayload(node, n).then(function (payload) { node.brokerConn.subscribe(node, n, payload); }); }else{ generateSubscribePayload(node, n).then(function (payload) { node.brokerConn.subscribe(node, n, payload); }); } }); } /*Cancellazione sottoscrizione*/ RED.nodes.registerType("orion-unsubscribe-api-v2", OrionUnsubscribeV2); function OrionUnsubscribeV2(n) { RED.nodes.createNode(this, n); this.on('input', function (msg) { var node = this; // Riferimento al nodo // URL di Orion Broker (può essere parametrizzato) // Recupera l'ID di iscrizione dal payload del messaggio o dal parametro del nodo var subscriptionId = (msg.payload.subscriptionId ? msg.payload.subscriptionId : n.subscriptionId); var deviceId=(msg.payload.id ? msg.payload.id : n.enid); n.enid=deviceId; // Chiamata alla funzione di disiscrizione (unsubscribeFromOrion) if (subscriptionId !== undefined && subscriptionId !== "" && subscriptionId !== null && deviceId !== null && deviceId !== "" && deviceId !== undefined) { unsubscribeFromOrion(node, subscriptionId, null, n); msg.payload = "The subscription has been canceled."; node.send(msg); } else { let errorMessage = "Missing required parameters"; node.error(errorMessage); } }); } //OrionSubscribeV2 node constructor RED.nodes.registerType("orion-broker-subscribe-list-entity-api-v2", OrionBrokerSubscribeListEntityV2); function OrionBrokerSubscribeListEntityV2(n) { RED.nodes.createNode(this, n); var node = this; this.service = n.service; this.brokerConn = RED.nodes.getNode(this.service); this.noderedhost = n.noderedhost; this.userk1 = n.userk1; this.passk2 = n.passk2; this.apikey = n.apikey; this.basicAuth = n.basicAuth; this.on("close", function () { var nodeID = node.id + ""; nodeID = nodeID.replace('.', ''); RED.httpNode._router.stack.forEach(function (route, i, routes) { if (route.route && route.route.path == "/" + nodeID) { routes.splice(i, 1); } }); //cannot invoke unsubscribe from here since the config is empty (and the contextbroker uri is unknown) // unsubscribeFromOrion(node, subscriptionIDs[nodeID], null, n); }); var nodeID = (node.id + "").replace('.', ''); listenOnUrl(nodeID, function (req, res) { if (req.body.subscriptionId != subscriptionIDs.getSubscriptionOfNode(nodeID)) { logger.error("Recognized invalid subscription: " + req.body.subscriptionId + " currentId: " + subscriptionIDs.getSubscriptionOfNode(nodeID)); unsubscribeFromOrion(node, req.body.subscriptionId, orionUrl, config); } else { //var payload = formatOutput(node, config, req.body.data);//TODO *1 verify formatOutput node.send({ payload: req.body.data,//TODO *1 statusCode: 201 }); } res.sendStatus(200); var orionUrl = node.brokerConn.url; var prot='http'; if (orionUrl.indexOf("https://") >= 0) { prot='https'; } getMyUri(n).then(function(myUri) { var url=`${prot}://${myUri}/${nodeID}` node.status({ fill: "blue", shape: "dot", text: "Listening on: "+url }) }).catch(function(error) { console.error("Errore:", error); }); }); // validate mandatory fields validateInput(this, n); this.on('input', function (msg) { n.ispattern="isPattern"; n.enid=msg.payload var correctPath =os.homedir() + "/.snap4cityConfig/";//consider edge scenario var created=false if (RED.settings.APPID != null) {//check edge scenario or cloud scenario correctPath = "/data/.snap4cityConfig/";//consider cloud scenario } var jsonFilePath=correctPath+n.id+".json"; var jsonFileSubscription=correctPath+"subscriptions.json"; if (!fs.existsSync(correctPath)) { fs.mkdirSync(correctPath);//not exist folder, create folder + empty file fs.writeFileSync(jsonFilePath, JSON.stringify(msg.payload)); } else if (!fs.existsSync(jsonFilePath)){ //this.subscriptionJson = JSON.parse(fs.readFileSync(jsonFilePath)); fs.writeFileSync(jsonFilePath, '[]',{ flag: 'w' });//not exist file, create empy file } else{ //folder and file exist, read from it var subscriptionJsonRead = JSON.parse(fs.readFileSync(jsonFilePath)); if(subscriptionJsonRead.length!=0){ var subscriptionFlow = JSON.parse(fs.readFileSync(jsonFileSubscription)); var msg2={} msg2.payload={ message:"The subscription has already been created.", subscription_id:subscriptionFlow[n.id], devices_entities:n["enid"] } created=true node.send([,msg2]); } } if(!created){ var subscriptionJson = msg.payload; fs.writeFileSync(jsonFilePath, JSON.stringify(subscriptionJson)); generateSubscribePayload(node, n).then(function (payload) { node.brokerConn.subscribe(node, n, payload); }); node.brokerConn.init(node, n).then(function () { var correctPath =os.homedir() + "/.snap4cityConfig/";//consider edge scenario if (RED.settings.APPID != null) {//check edge scenario or cloud scenario correctPath = "/data/.snap4cityConfig/";//consider cloud scenario } var jsonFilePath=correctPath+n.id+".json"; if (fs.existsSync(jsonFilePath)){ generateSubscribePayload(node, n).then(function (payload) { node.brokerConn.subscribe(node, n, payload); }); }else{ generateSubscribePayload(node, n).then(function (payload) { node.brokerConn.subscribe(node, n, payload); }); } }); setTimeout(() => { var subscriptionFlow = JSON.parse(fs.readFileSync(jsonFileSubscription)); var msg2={} msg2.payload={ message:"Subscription created successfully.", subscription_id:subscriptionFlow[n.id], devices_entities:n["enid"] } node.send([,msg2]); }, 2000); } }); } function listenOnUrl(url, callback) { var errorHandler = function (err, req, res, next) { res.sendStatus(500); }; var next = function (req, res, next) { next(); }; // will listen on 'localhost/url' for notifications from context broker and call callback function RED.httpNode.post("/" + url, next, next, next, jsonParser, urlencParser, rawBodyParser, callback, errorHandler); } function getMyUri(node) { return when.promise( function (resolve, reject) { const logger = s4cUtility.getLogger(RED, node); // first try to get user specified uri, TODO: many input validations... var myUri = node.noderedhost; if (myUri) { resolve(myUri + RED.settings.httpRoot /*+ ":" + RED.settings.uiPort*/); //PB removed port } else { myUri = RED.settings.externalHost; //PB fix added if (myUri) //TODO check if this one is needed //resolve(myUri + RED.settings.httpRoot.substring(0, RED.settings.httpRoot.lenght - 1) /*+ ":" + RED.settings.uiPort*/ ); //PB remove port resolve(myUri + RED.settings.httpRoot /*+ ":" + RED.settings.uiPort*/); //PB remove port else { // attempt to get from bluemix try { var app = JSON.parse(process.env.VCAP_APPLICATION); myUri = app['application_uris'][0]; } catch (e) { logger.error("Probably not running in bluemix..."); } } } if (myUri) { resolve(myUri); } else { var net = require('net'); var client = net.connect({ port: 80, host: "google.com" }, function () { if (!client.localAddress) { reject("Failed to get local address"); } else { resolve(client.localAddress + ":" + RED.settings.uiPort); } } ); } } ); } //OrionQueryV2 node constructor RED.nodes.registerType("orion-query-api-v2", OrionQueryV2); function OrionQueryV2(config) { RED.nodes.createNode(this, config); const logger = s4cUtility.getLogger(RED, this); this.on("input", function (msg) { this.service = config.service; this.serviceNode = RED.nodes.getNode(this.service); var node = this; // process input from UI and input pipe s4cOrionUtility.processInput(config, msg); var queryParams = { "entities": [{ "type": config.entype, "id": config.enid }], "attributes": config.attributes }; try { node.serviceNode.init(node, config).then(function () { node.serviceNode.queryContext(node, config, queryParams).then( function (msg) { logger.debug("queryContext result:" + JSON.stringify(msg)); nodeStatus.send(node, [JSON.parse(msg.payload)], 200) nodeStatus.success(node) }, function (reason) { logger.error("queryContext error:" + JSON.stringify(reason)); nodeStatus.getError(node, reason, "Failed to query! Reason: " + JSON.stringify(reason)) } ); }); } catch (err) { nodeStatus.getError(node, err.code, msg) node.send({ payload: err.toString(), statusCode: err.code }); } }); } //OrionUpdate node constructor RED.nodes.registerType("orion-update-api-v2", OrionUpdateV2); function OrionUpdateV2(config) { RED.nodes.createNode(this, config); const logger = s4cUtility.getLogger(RED, this); this.on("input", function (msg) { this.service = config.service; this.serviceNode = RED.nodes.getNode(this.service); var node = this; if (typeof msg.auth == "string") { msg.auth = JSON.parse(msg.auth); } var payload = generateUpdatePayload(config); try { node.serviceNode.init(node, config).then(function () { node.serviceNode.updateContext(node, config, payload, msg.auth).then( function () { //In API v2 there is no body in response logger.debug("Entity updated"); nodeStatus.send(node, "Success", 200) nodeStatus.success(node) }, function (reason) { logger.error("updateContext error:" + JSON.stringify(reason)); nodeStatus.getError(node, reason, "failed to update, reason: " + JSON.stringify(reason)); } ); }); } catch (err) { nodeStatus.getError(node, err.code, msg) } }); } function generateUpdatePayload(nodeConfig) { var attr var attributesName = []; var attributesValue = []; var payload = {} if (nodeConfig.attrkey && nodeConfig.attrvalue) { attributesName = nodeConfig.attrkey.replace(' ', '').split(',') attributesValue = nodeConfig.attrvalue.replace(' ', '').split(',') //Remove last element if attributes list ends with , (comma) if (attributesName[attributesName.length - 1] === '') attributesName.pop() if (attributesValue[attributesValue.length - 1] === '') attributesName.pop() if (attributesName.length !== attributesValue.length) throw "Missing attributes name or value " } attributesValue.forEach((attrValue, index) => { attr = { "value": attrValue, "type": "string" } if (attrValue.indexOf('.') !== -1 && attrValue === Number.parseFloat(attrValue).toString()) attr = { "value": Number.parseFloat(attrValue), "type": "float" } if (attrValue === Number.parseInt(attrValue).toString()) attr = { "value": Number.parseInt(attrValue), "type": "int" } payload[attributesName[index]] = attr }) return payload; } //OrionOut node constructor RED.nodes.registerType("orion-out-api-v2", OrionOutV2); function OrionOutV2(config) { const logger = s4cUtility.getLogger(RED, this); RED.nodes.createNode(this, config); this.on("input", function (msg) { this.service = config.service; this.serviceNode = RED.nodes.getNode(this.service); var node = this; try { if (typeof msg.auth == "string") { msg.auth = JSON.parse(msg.auth); } if (typeof msg.payload == "string") { msg.payload = JSON.parse(msg.payload); } //it converts to array a single attribute if (typeof msg.payload.length == "undefined" && typeof msg.payload.id == "undefined" && typeof msg.payload.type == "undefined") { msg.payload = [msg.payload]; } var msgToSend; if (Array.isArray(msg.payload)) { if ((config.entype == "") || (config.enid == "")) throw ("entype or enid missing"); msgToSend=msg.payload[0]; } else { if (((msg.payload.type == undefined) && (config.entype == "")) || ((msg.payload.id == undefined) && (config.enid == ""))) throw ("entype or enid missing"); config.enid=msg.payload.id; config.entype=msg.payload.type; delete msg.payload['type']; delete msg.payload['id']; msgToSend=msg.payload; } try { node.serviceNode.init(node, config).then(() => { node.serviceNode.updateContext(node, config, msgToSend, msg.auth) .then( () => { nodeStatus.success(node) msg.payload={"data":{"id":config.enid,"type":config.entype,msgToSend},"status":{"statusCode":200,"headers":{},"payload":"Success"}} node.send(msg) //console.log(node.currentContextBroker) /*try { //s4cUtility.eventLog(RED, {"id":config.enid,"type":config.entype,msgToSend}, msg.payload, config, "Node-Red", "Orion", node.currentContextBroker.accesslink, "TX"); console.log("FATTO") }catch (e) { console.log("ENTRATO NEL CATCH") }*/ }, (reason) => { logger.error("Update id:"+config.enid+",type:"+config.entype+JSON.stringify({"data":msgToSend,"status":reason})); nodeStatus.getError(node, reason,"Update id:"+config.enid+",type:"+config.entype+JSON.stringify({"data":msgToSend,"status":reason}) ); msg.payload={"data":{"id":config.enid,"type":config.entype,msgToSend},"status":reason} node.send(msg) s4cUtility.eventLog(RED, {"id":config.enid,"type":config.entype,msgToSend}, msg.payload, config, "Node-Red", "Orion", node.currentContextBroker.accesslink, "TX"); } ); }); } catch (err) { nodeStatus.getError(node, err.code, msg); nodeStatus.send(node, err.toString(), err.code) } } catch (e) { node.status({ fill: "red", shape: "ring", text: "Problem with input msg" }); node.error(e); } }); } function retrieveDeviceName(node, id, tenant, servicepath){ const logger = s4cUtility.getLogger(RED, node); var deviceName=id; //logger.info("Device name is:"+deviceName); //logger.info("tenant is:"+tenant); //logger.info("servicepath is:"+servicepath); if ((tenant != null && tenant != "")||(servicepath != null && servicepath != "")) { if (servicepath != null && servicepath != "") { if (!servicepath.startsWith("/")) deviceName="/"+servicepath+"."+deviceName; else deviceName=servicepath+"."+deviceName; } else deviceName="/."+deviceName; if (tenant != null && tenant != "") deviceName=tenant+"."+deviceName; else deviceName="."+deviceName; } logger.info("Device name is:"+deviceName); return deviceName; } RED.httpAdmin.get('/myContextbrokerDataList', RED.auth.needsPermission('orion-api-v2.read'), function (req, res) { var s4cUtility = require("./snap4city-utility.js"); var s4cOrionUtility = require("./snap4city-orion-utility.js"); var accessToken = s4cUtility.retrieveAccessToken(RED, null, req.body.authenticationNodeId, null); var lista=s4cOrionUtility.getContextBrokerList(RED, accessToken, req.body.authenticationNodeId ); if (lista==null) lista=[]; res.send({ "contextbrokerDataList": lista }); }); RED.httpAdmin.get('/getDeviceSubscription', function (req, res) { const os = require('os'); const fs = require('fs'); const path = require('path'); // Determina il percorso corretto del file basato sullo scenario let correctPath = path.join(os.homedir(), "/.snap4cityConfig/"); if (RED.settings.APPID != null) { correctPath = "/data/.snap4cityConfig/"; // Cloud scenario } const nodeId = req.query.nodeId || req.body.nodeId; // Percorso del file JSON, si assume che ci sia un file predefinito var jsonFilePath=correctPath+nodeId+".json"; // Verifica l'esistenza del file if (fs.existsSync(jsonFilePath)) { // Legge e restituisce il contenuto del file JSON try { var fileContent = JSON.parse(fs.readFileSync(jsonFilePath)); res.send({ deviceSubscription: fileContent }); } catch (error) { res.status(500).send({ error: "Error reading or parsing the JSON file.", details: error.message }); } } else { // File non trovato, restituisce niente res.send({ deviceSubscription: null }); } }); }