node-red-contrib-snap4city-user
Version:
Nodes for Snap4city project, targeted to standard user (no developer)
1,520 lines (1,304 loc) • 46.8 kB
JavaScript
/**
* Copyright 2016 IBM Corp.
*
* 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.
**/
/* 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/>. */
module.exports = function (RED) {
"use strict";
var bodyParser = require("body-parser");
var getBody = require('raw-body');
var jsonParser = bodyParser.json();
var urlencParser = bodyParser.urlencoded({
extended: true
});
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 testSubscriptionIDs = {}; //PB fix
var subscriptionIDs = {}; //PB fix
var when = require('when');
var token = "";
var LIMIT = 30;
function OrionService(config) {
RED.nodes.createNode(this, config);
var serviceNode = this;
this.url = config.url;
this.port = config.port;
var orionUrl = getOrionUrl(config);
var credentials = {};
credentials.user = "";
credentials.password = "";
if (/\/$/.test(this.url)) {
this.url = this.url.substring(0,this.url.length - 1);//remove the final / if exist
}
if (!this.port) {
throw "Missing port";
}
this.init = function (node) {
node.status({
fill: "blue",
shape: "dot",
text: "Initializing"
});
return when.promise(function (resolve) {
// get token from context broker
getToken(node, serviceNode.url, credentials).then(function () {
resolve();
});
});
};
this.queryContext = function (node, config, payload) {
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, node);
const uid = s4cUtility.retrieveAppID(RED);
var accessToken = s4cUtility.retrieveAccessToken(RED, node, config.authentication, uid);
node.status({
fill: "blue",
shape: "dot",
text: "Querying data"
});
logger.info("queryContext, entityID: " + config.enid);
logger.debug("queryContext, payload: " + JSON.stringify(payload));
var orionBrokerService = RED.nodes.getNode(config.service);
return when.promise(function (resolve, reject) {
getContextBrokerListForRegisterActivity(node, orionBrokerService.url, orionBrokerService.port, retrieveDeviceName(node, config.enid, config.tenant, config.servicepath), uid, accessToken);
var urlWithoutHttp = orionBrokerService.url;
if (urlWithoutHttp.indexOf("http") != -1)
urlWithoutHttp = urlWithoutHttp.replace("https://", "").replace("http://", "");
var prefixPath = "";
var hostname = urlWithoutHttp;
var indexSlash = urlWithoutHttp.indexOf("/");
var indexDot = urlWithoutHttp.indexOf(":");
if (indexSlash!=-1 || indexDot!=-1){
if (indexDot!=-1)
//here the port information is ignored (use orionBrokerService.port instead)
hostname = urlWithoutHttp.substring(0, indexDot);
else
hostname = urlWithoutHttp.substring(0, indexSlash);
if (indexSlash!=-1)
prefixPath = urlWithoutHttp.substring(indexSlash);
}
var options = {
hostname: hostname,
port: orionBrokerService.port,
path: prefixPath + "/v1/queryContext/?limit=" + config.limit + "&elementid=" + config.enid + (config.userk1 ? "&k1=" + config.userk1.trim() : "") + (config.passk2 ? "&k2=" + config.passk2.trim() : ""),
method: 'POST',
rejectUnauthorized: false,
headers: {
'Authorization': 'Bearer ' + accessToken, //by default, we insert the Snap4City SSo AccessToken, that can be overrided by the config.basicAuth
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(JSON.stringify(payload))
}
};
if (config.apikey != null && config.apikey != "") {
options.headers.apikey = config.apikey;
}
if (config.basicAuth != null && config.basicAuth != "") {
options.headers.Authorization = config.basicAuth;
}
if (config.tenant != null && config.tenant != "") {
options.headers["Fiware-Service"]=config.tenant;
}
if (config.servicepath != null && config.servicepath != "") {
if (!config.servicepath.startsWith("/"))
config.servicepath="/"+config.servicepath;
options.headers["Fiware-ServicePath"]=config.servicepath;
}
var tlsNode = RED.nodes.getNode(config.tls);
if (tlsNode != null) {
if (tlsNode.credentials != null) {
options.key = tlsNode.credentials.keydata;
options.cert = tlsNode.credentials.certdata;
options.ca = tlsNode.credentials.cadata;
options.rejectUnauthorized = tlsNode.verifyservercert;
}
}
logger.debug("queryContext options:" + JSON.stringify(options));
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(payload));
req.end();
});
}
this.updateContext = function (node, config, payload, auth) {
var k1 = (auth && auth.k1) ? auth.k1.trim() : ((config.userk1) ? config.userk1.trim() : undefined);
var k2 = (auth && auth.k2) ? auth.k2.trim() : ((config.passk2) ? config.passk2.trim() : undefined);
var apikey = (auth && auth.apikey) ? auth.apikey : ((config.apikey) ? config.apikey : undefined);
var basicAuth = (auth && auth.basicAuth) ? auth.basicAuth : ((config.basicAuth) ? config.basicAuth : undefined);
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, node);
const uid = s4cUtility.retrieveAppID(RED);
var accessToken = s4cUtility.retrieveAccessToken(RED, node, config.authentication, uid);
node.status({
fill: "blue",
shape: "dot",
text: "Sending data"
});
logger.info("updateContext, entityID: " + payload.contextElements[0].id);
logger.debug("updateContext, payload: " + JSON.stringify(payload));
var orionBrokerService = RED.nodes.getNode(config.service);
return when.promise(function (resolve, reject) {
getContextBrokerListForRegisterActivity(node, orionBrokerService.url, orionBrokerService.port, retrieveDeviceName(node, payload.contextElements[0].id, config.tenant, config.servicepath), uid, accessToken);
var urlWithoutHttp = orionBrokerService.url;
if (urlWithoutHttp.indexOf("http") != -1)
urlWithoutHttp = urlWithoutHttp.replace("https://", "").replace("http://", "");
var prefixPath = "";
var hostname = urlWithoutHttp;
var indexSlash = urlWithoutHttp.indexOf("/");
var indexDot = urlWithoutHttp.indexOf(":");
if (indexSlash!=-1 || indexDot!=-1){
if (indexDot!=-1)
//here the port information is ignored (use orionBrokerService.port instead)
hostname = urlWithoutHttp.substring(0, indexDot);
else
hostname = urlWithoutHttp.substring(0, indexSlash);
if (indexSlash!=-1)
prefixPath = urlWithoutHttp.substring(indexSlash);
}
var options = {
hostname: hostname,
port: orionBrokerService.port,
path: prefixPath + "/v1/updateContext/?elementid=" + payload.contextElements[0].id + (k1 ? "&k1=" + k1.trim() : "") + (k2 ? "&k2=" + k2.trim() : ""),
method: 'POST',
rejectUnauthorized: false,
headers: {
'Authorization': 'Bearer ' + accessToken, //by default, we insert the Snap4City SSo AccessToken, that can be overrided by the config.basicAuth
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(JSON.stringify(payload))
}
};
if (apikey) {
options.headers.apikey = apikey;
}
if (basicAuth) {
options.headers.Authorization = basicAuth;
}
if (config.tenant != null && config.tenant != "") {
options.headers["Fiware-Service"]=config.tenant;
}
if (config.servicepath != null && config.servicepath != "") {
if (!config.servicepath.startsWith("/"))
config.servicepath="/"+config.servicepath;
options.headers["Fiware-ServicePath"]=config.servicepath;
}
var tlsNode = RED.nodes.getNode(config.tls);
if (tlsNode != null) {
if (tlsNode.credentials != null) {
options.key = tlsNode.credentials.keydata;
options.cert = tlsNode.credentials.certdata;
options.ca = tlsNode.credentials.cadata;
options.rejectUnauthorized = tlsNode.verifyservercert;
}
}
logger.debug("updateContext options:" + JSON.stringify(options));
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 === 200) {
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) {
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, node);
const uid = s4cUtility.retrieveAppID(RED);
var accessToken = s4cUtility.retrieveAccessToken(RED, node, config.authentication, uid, false);
node.status({
fill: "blue",
shape: "dot",
text: "Subscribing"
});
logger.info("subscribeContext, entityID: " + config.enid);
logger.debug("subscribeContext, payload: " + JSON.stringify(payload));
var reference = payload.reference;
var orionBrokerService = RED.nodes.getNode(config.service);
getContextBrokerListForRegisterActivity(node, orionBrokerService.url, orionBrokerService.port, retrieveDeviceName(node, config.enid, config.tenant, config.servicepath), uid, accessToken);
var urlWithoutHttp = orionBrokerService.url;
if (urlWithoutHttp.indexOf("http") != -1)
urlWithoutHttp = urlWithoutHttp.replace("https://", "").replace("http://", "");
var prefixPath = "";
var hostname = urlWithoutHttp;
var indexSlash = urlWithoutHttp.indexOf("/");
var indexDot = urlWithoutHttp.indexOf(":");
if (indexSlash!=-1 || indexDot!=-1){
if (indexDot!=-1)
//here the port information is ignored (use orionBrokerService.port instead)
hostname = urlWithoutHttp.substring(0, indexDot);
else
hostname = urlWithoutHttp.substring(0, indexSlash);
if (indexSlash!=-1)
prefixPath = urlWithoutHttp.substring(indexSlash);
}
var options = {
hostname: hostname,
port: orionBrokerService.port,
path: prefixPath + "/v1/subscribeContext/?elementid=" + config.enid + (config.userk1 ? "&k1=" + config.userk1.trim() : "") + (config.passk2 ? "&k2=" + config.passk2.trim() : ""),
method: 'POST',
rejectUnauthorized: false,
headers: {
'Authorization': 'Bearer ' + accessToken, //by default, we insert the Snap4City SSo AccessToken, that can be overrided by the config.basicAuth
'Content-Type': 'application/json',
'Accept': 'application/json',
'Content-Length': Buffer.byteLength(JSON.stringify(payload))
}
};
if (config.apikey != null && config.apikey != "") {
options.headers.apikey = config.apikey;
}
if (config.basicAuth != null && config.basicAuth != "") {
options.headers.Authorization = config.basicAuth;
}
if (config.tenant != null && config.tenant != "") {
options.headers["Fiware-Service"]=config.tenant;
}
if (config.servicepath != null && config.servicepath != "") {
if (!config.servicepath.startsWith("/"))
config.servicepath="/"+config.servicepath;
options.headers["Fiware-ServicePath"]=config.servicepath;
}
var tlsNode = RED.nodes.getNode(config.tls);
if (tlsNode != null) {
if (tlsNode.credentials != null) {
options.key = tlsNode.credentials.keydata;
options.cert = tlsNode.credentials.certdata;
options.ca = tlsNode.credentials.cadata;
options.rejectUnauthorized = tlsNode.verifyservercert;
}
}
logger.debug("subscribeContext options:" + JSON.stringify(options));
try {
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 () {
logger.info("subscribeContext result:" + msg.statusCode + " " + msg.payload);
if (res.statusCode === 200) {
var parsedResponse = JSON.parse(msg.payload);
if (parsedResponse.subscribeResponse != null) {
var subscriptionID = parsedResponse.subscribeResponse.subscriptionId;
logger.info("subs debug:"+subscriptionID);
var nodeID = (node.id + "").replace('.', '');
//listen subscription just if the return code is 200ok
listenOnUrl(nodeID, function (req, res) {
if (req.body.subscriptionId != subscriptionIDs[nodeID]) {
logger.error("Recognized invalid subscription: " + req.body.subscriptionId + " currentId: " + subscriptionIDs[nodeID]);
unsubscribeFromOrion(node, req.body.subscriptionId, orionUrl, config);
} else {
var payload = formatOutput(node, config, req.body);
node.send({
payload: payload,
statusCode: 200
});
}
res.sendStatus(200);
});
logger.info("subscribeContext elementId: " + config.enid + " nodeId: " + nodeID + " oldSubId: " + subscriptionIDs[nodeID] + " newSubId: " + subscriptionID);
var idToUnsubscribe = subscriptionIDs[nodeID];//save previous sub for unsub
subscriptionIDs[nodeID] = subscriptionID;//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));
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(msg)
});
node.error(msg);
}
} else {
logger.error("subscribeContext error:" + JSON.stringify(msg));
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(msg)
});
node.error(msg);
}
});
});
req.on('error', function (err) {
logger.error("subscribeContext error:" + err);
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(err)
});
node.error(err);
});
node.status({
fill: "blue",
shape: "dot",
text: "listening on " + reference
});
if (payload) {
logger.debug("subscribeContext payload:" + JSON.stringify(payload));
req.write(JSON.stringify(payload));
}
req.end();
} catch (err) {
logger.error("subscribeContext error:" + err);
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(err)
});
node.error(err);
}
};
}
RED.nodes.registerType("orion-service", OrionService, {
credentials: {
user: {
type: "text"
},
password: {
type: "password"
}
}
});
function unsubscribeFromOrion(node, subscriptionId, url, config) {
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, node);
const uid = s4cUtility.retrieveAppID(RED);
var accessToken = s4cUtility.retrieveAccessToken(RED, node, config.authentication, uid);
logger.debug("unsubscribeFromOrion with: " + JSON.stringify(subscriptionId));
var payload = {
"subscriptionId": subscriptionId
};
var orionBrokerService = RED.nodes.getNode(config.service);
return when.promise(function (resolve, reject) {
var urlWithoutHttp = orionBrokerService.url;
if (urlWithoutHttp.indexOf("http") != -1)
urlWithoutHttp = urlWithoutHttp.replace("https://", "").replace("http://", "");
var prefixPath = "";
var hostname = urlWithoutHttp;
var indexSlash = urlWithoutHttp.indexOf("/");
var indexDot = urlWithoutHttp.indexOf(":");
if (indexSlash!=-1 || indexDot!=-1){
if (indexDot!=-1)
//here the port information is ignored (use orionBrokerService.port instead)
hostname = urlWithoutHttp.substring(0, indexDot);
else
hostname = urlWithoutHttp.substring(0, indexSlash);
if (indexSlash!=-1)
prefixPath = urlWithoutHttp.substring(indexSlash);
}
var options = {
hostname: hostname,
port: orionBrokerService.port,
path: prefixPath + "/v1/unsubscribeContext/?elementid=" + config.enid + (config.userk1 ? "&k1=" + config.userk1.trim() : "") + (config.passk2 ? "&k2=" + config.passk2.trim() : ""),
method: 'POST',
rejectUnauthorized: false,
headers: {
'Authorization': 'Bearer ' + accessToken, //by default, we insert the Snap4City SSo AccessToken, that can be overrided by the config.basicAuth
'Content-Type': 'application/json',
'Accept': 'application/json',
'Content-Length': Buffer.byteLength(JSON.stringify(payload))
}
};
if (config.apikey != null && config.apikey != "") {
options.headers.apikey = config.apikey;
}
if (config.basicAuth != null && config.basicAuth != "") {
options.headers.Authorization = config.basicAuth;
}
if (config.tenant != null && config.tenant != "") {
options.headers["Fiware-Service"]=config.tenant;
}
if (config.servicepath != null && config.servicepath != "") {
if (!config.servicepath.startsWith("/"))
config.servicepath="/"+config.servicepath;
options.headers["Fiware-ServicePath"]=config.servicepath;
}
var tlsNode = RED.nodes.getNode(config.tls);
if (tlsNode != null) {
if (tlsNode.credentials != null) {
options.key = tlsNode.credentials.keydata;
options.cert = tlsNode.credentials.certdata;
options.ca = tlsNode.credentials.cadata;
options.rejectUnauthorized = tlsNode.verifyservercert;
}
}
logger.debug("unsubscribeContext options:" + JSON.stringify(options));
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) {
reject(err);
});
req.write(JSON.stringify(payload));
req.end();
});
}
// retrieve token from context broker
function getToken(node, orionUrl, credentials) {
var s4cUtility = require("./snap4city-utility.js");
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 getSubscribePayload(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) {
resolve({
"entities": [{
"type": n.entype,
"isPattern": n.ispattern,
"id": n.enid
}],
"attributes": n.attributes,
"reference": "http://" + myUri + "/" + nodeID,
"duration": n.duration,
"notifyConditions": [{
"type": "ONCHANGE",
"condValues": n.condvals
}],
"throttling": n.throttle
});
})
}
);
}
function getOrionUrl(n) {
var orionUrl = n.url;
if (!/^((http|https):\/\/)/.test(orionUrl)) {
orionUrl = "https://" + orionUrl + ":" + n.port;
}
return orionUrl;
}
function request(node, method, payload, url) {
var opts = urllib.parse(url);
opts.method = method;
opts.headers = {};
opts.headers['Content-Type'] = "application/json";
opts.headers["Accept"] = "application/json";
payload = JSON.stringify(payload);
opts.headers['content-length'] = Buffer.byteLength(payload);
if (token) {
opts.headers["X-Auth-Token"] = token;
}
return when.promise(function (resolve, reject) {
var req = ((/^https/.test(url)) ? https : http).request(opts, function (res) {
(node.ret === "bin") ? res.setEncoding('binary'): res.setEncoding('utf8');
var result = "";
res.on('data', function (chunk) {
result += chunk;
});
res.on('end', function () {
resolve(JSON.parse(result));
});
});
req.on('error', function (err) {
reject(err);
node.status({
fill: "red",
shape: "ring",
text: err.code
});
node.send({
payload: err.toString(),
statusCode: err.code
});
});
req.write(payload);
req.end();
});
}
function retrieveDeviceName(node, id, tenant, servicepath){
var s4cUtility = require("./snap4city-utility.js");
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;
}
function getContextBrokerListForRegisterActivity(node, contextBrokerUrl, contextBrokerPort, deviceName, iotappid, accessToken) {
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, node);
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xmlHttp = new XMLHttpRequest();
if (typeof node.currentContextBroker != "undefined" && node.currentContextBroker.accesslink.indexOf(contextBrokerUrl) == -1) {
delete node.contextBrokerList;
}
if (typeof node.contextBrokerList == "undefined") {
var uri = (RED.settings.iotDirectoryUrl ? RED.settings.iotDirectoryUrl : "https://iotdirectory.snap4city.org/");
if (accessToken != "" && typeof accessToken != "undefined") {
xmlHttp.open("GET", encodeURI(uri + "/api/contextbroker.php?action=get_all_contextbroker&nodered=yes"), true);
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.setRequestHeader("Authorization", "Bearer " + accessToken);
xmlHttp.onload = function (e) {
if (xmlHttp.readyState === 4) {
if (xmlHttp.status === 200) {
if (xmlHttp.responseText != "") {
try {
node.contextBrokerList = JSON.parse(xmlHttp.responseText).data;
} catch (e) {
node.contextBrokerList = xmlHttp.responseText.data;
}
createDeviceLongIdAndRegisterActivity(node, contextBrokerUrl, contextBrokerPort, deviceName, iotappid, accessToken);
}
} else {
logger.error(xmlHttp.statusText);
}
}
};
xmlHttp.onerror = function (e) {
logger.error(xmlHttp.statusText);
};
xmlHttp.send(null);
}
} else {
createDeviceLongIdAndRegisterActivity(node, contextBrokerUrl, contextBrokerPort, deviceName, iotappid, accessToken);
}
}
function createDeviceLongIdAndRegisterActivity(node, contextBrokerUrl, contextBrokerPort, deviceName, iotappid, accessToken) {
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, node);
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xmlHttp = new XMLHttpRequest();
if (typeof node.deviceLongId == "undefined" || node.deviceLongId.indexOf(deviceName) == -1) {
if (typeof node.contextBrokerList != "undefined") {
for (var i = 0; i < node.contextBrokerList.length; i++) {
if (node.contextBrokerList[i].accesslink.indexOf(contextBrokerUrl) != -1 && node.contextBrokerList[i].accessport == contextBrokerPort) {
node.deviceLongId = node.contextBrokerList[i].organization + ":" + node.contextBrokerList[i].name + ":" + deviceName;
node.currentContextBroker = node.contextBrokerList[i];
}
}
}
}
if (typeof node.deviceLongId != "undefined") {
node.s4cAuth = RED.nodes.getNode(node.service.authentication);
var uri = s4cUtility.settingUrl(RED,node, "myPersonalDataUrl", "https://www.snap4city.org", "/datamanager/api/v1/") + "";
if (accessToken != "" && typeof accessToken != "undefined") {
xmlHttp.open("POST", encodeURI(uri + "/lightactivities/?elementType=IOTID&sourceRequest=iotapp&sourceId=" + iotappid + "&elementId=" + node.deviceLongId), true);
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.setRequestHeader("Authorization", "Bearer " + accessToken);
xmlHttp.onload = function (e) {
if (xmlHttp.readyState === 4) {
if (xmlHttp.status !== 200) {
logger.error("Error retrieved from lightActivities");
logger.error(xmlHttp.statusText);
}
} else {
logger.error(xmlHttp.statusText);
}
};
xmlHttp.onerror = function (e) {
logger.error(xmlHttp.statusText);
};
xmlHttp.send(null);
}
}
}
// To validate two ways connectivity following flow implemented
// init:
// 1. (v) create context element of type Test with id: node uid
// 2. temporary subscribe to changes to that element
// action:
// 3. change test element
// 4. validate update message received
// #cleanup
// 5. delete element
// 6. delete subscription
function validateOrionConnectivityTwoWays(node, orionUrl, createElementPayload) {
var nodeID = node.id + "";
nodeID = nodeID.replace('.', '');
return when.promise(function (resolve, reject) {
listenOnUrl(nodeID, function (req, res) {
var result = req.body;
var testSubscriptionID = testSubscriptionIDs[nodeID]; //PB fix
/*PB commentato forza la validazione
if(result.subscriptionId != testSubscriptionID){
console.log(nodeID+" result.subscriptionId: " + result.subscriptionId + "!=" + testSubscriptionID);
node.error("Communication with context broker failed: received wrong subId");
unsubscribeFromOrion(node, result.subscriptionId, orionUrl).then(function(res){
console.log("Unsubscribed invalid subscription: " + result.subscriptionId + " res: " + JSON.stringify(res));
});
unsubscribeFromOrion(node, testSubscriptionID, orionUrl).then(function(res){
console.log("Unsubscribed test subscription: " + testSubscriptionID + " res: " + JSON.stringify(res));
});
reject("Communication with context broker failed");
}else{*/
//console.log(nodeID + " result.subscriptionId: " + result.subscriptionId + "==" + testSubscriptionID);
resolve();
//}
});
request(node, "POST", createElementPayload, orionUrl + "/v1/updateContext").then(function (result) {
getSubscribeTestPayload(node).then(function (subscribePayload) {
request(node, "POST", subscribePayload, orionUrl + "/v1/subscribeContext").then(function (subscription) {
try {
var testSubscriptionID = subscription.subscribeResponse.subscriptionId
//console.log(node.id + " " + nodeID + " pre test:" + testSubscriptionIDs[nodeID]);
testSubscriptionIDs[nodeID] = testSubscriptionID;
if (testSubscriptionID) {
var data = {
"_id": "test_" + node.id,
"subscriptionId": testSubscriptionID,
"brokerUrl": orionUrl
}
}
} catch (e) {
node.error(RED._("httpin.errors.json-error"));
reject(e);
}
});
});
});
});
}
function validateOrionConnectivityOneWay(node, orionUrl, createElementPayload) {
console.log("in validateOrionConnectivityOneWay with createElementPayload: " + JSON.stringify(createElementPayload));
return when.promise(function (resolve, reject) {
request(node, "POST", createElementPayload, orionUrl + "/v1/updateContext").then(function (result) {
rcValidate(200, result, reject);
resolve();
});
});
}
function rcValidate(expected, result, reject) {
var rc = result.contextResponses[0].statusCode.code;
if (rc != expected) {
reject("Return Code: " + rc + " is not " + expected)
}
}
//OrionSubscribe node constructor
RED.nodes.registerType("fiware orion in", OrionSubscribe);
function OrionSubscribe(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);
node.brokerConn.init(node, n).then(function () {
getSubscribePayload(node, n).then(function (payload) {
node.brokerConn.subscribe(node, n, payload);
});
});
}
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 getSubscribeTestPayload(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(node).then(function (myUri) {
logger.debug("myUri: " + myUri);
resolve({
"entities": [{
"type": "Test",
"isPattern": "false",
"id": nodeID
}],
"attributes": "test",
"reference": "http://" + myUri + "/" + nodeID,
"duration": "PT30S",
"notifyConditions": [{
"type": "ONCHANGE",
"condValues": ["test"]
}],
"throttling": "PT5S"
});
});
});
}
function getMyUri(node) {
return when.promise(
function (resolve, reject) {
var s4cUtility = require("./snap4city-utility.js");
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);
}
}
);
}
}
);
}
//OrionQuery node constructor
RED.nodes.registerType("fiware orion", OrionQuery);
function OrionQuery(n) {
RED.nodes.createNode(this, n);
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, this);
this.on("input", function (msg) {
this.service = n.service;
this.brokerConn = RED.nodes.getNode(this.service);
var node = this;
// process input from UI and input pipe
processInput(this, n, msg);
// create json payload for context request
var payload = {
"entities": [{
"type": n.entype,
"isPattern": true,
"id": n.enid
}],
"attributes": n.attributes
};
if (n.rtype && n.rvalue) {
payload.restriction = {
"scopes": [{
"type": n.rtype,
"value": n.rvalue
}]
};
}
try {
node.brokerConn.init(node, n).then(function () {
node.brokerConn.queryContext(node, n, payload).then(
function (msg) {
logger.debug("queryContext result:" + JSON.stringify(msg));
msg = formatOutput(node, n, JSON.parse(msg.payload));
node.send({
payload: msg,
statusCode: 200
});
node.status({
fill: "green",
shape: "dot",
text: "success"
});
},
function (reason) {
logger.error("queryContext error:" + JSON.stringify(reason));
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(reason)
});
node.error("failed to query, reason: " + JSON.stringify(reason));
}
);
});
} catch (err) {
node.error(err, msg);
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(err.code)
});
node.send({
payload: err.toString(),
statusCode: err.code
});
}
});
}
function processInput(node, n, msg) {
n.url = n.url || msg.url;
n.port = n.port || msg.port;
n.enid = n.enid || msg.enid || ".*"; //TODO don't allow .* for queryContext (but also for subscribeContext)
n.entype = n.entype || msg.entype;
n.limit = n.limit || msg.limit || LIMIT;
n.userk1 = n.userk1 || msg.userk1;
n.passk2 = n.passk2 || msg.passk2;
n.tenant = n.tenant || msg.tenant;
n.servicepath = n.servicepath || msg.servicepath;
n.apikey = n.apikey || msg.apikey;
n.basicAuth = n.basicAuth || msg.basicAuth;
n.attributes = n.attributes || msg.attributes;
n.ispattern = n.ispattern || msg.ispattern || false;
n.includeattr = n.includeattr || msg.includeattr;
n.rtype = n.rtype || msg.rtype;
n.rvalue = n.rvalue || msg.rvalue;
if (n.rtype && !n.rvalue) {
n.rvalue = "entity::type";
}
// n.attributes = n.attributes || '.*';
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();
}
}
}
//OrionUpdate node constructor
RED.nodes.registerType("orion-test", OrionUpdate);
function OrionUpdate(n) {
RED.nodes.createNode(this, n);
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, this);
this.on("input", function (msg) {
this.service = n.service;
this.brokerConn = RED.nodes.getNode(this.service);
var node = this;
if (typeof msg.auth == "string") {
msg.auth = JSON.parse(msg.auth);
}
// create json payload for context update
var payload = generateCreateElementPayload(node, n, msg);
try {
node.brokerConn.init(node, n).then(function () {
node.brokerConn.updateContext(node, n, payload, msg.auth).then(
function (msg) {
logger.debug("updateContext result:" + JSON.stringify(msg));
msg = formatOutput(node, n, JSON.parse(msg.payload));
node.send({
payload: msg,
statusCode: 200
});
node.status({
fill: "green",
shape: "dot",
text: "success"
});
},
function (reason) {
logger.error("updateContext error:" + JSON.stringify(reason));
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(reason)
});
node.error("failed to update, reason: " + JSON.stringify(reason));
}
);
});
} catch (err) {
node.error(err, msg);
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(err.code)
});
node.send({
payload: err.toString(),
statusCode: getErrorMessage(err.code)
});
}
});
}
//reason is a json
function getErrorMessage(reason) {
try {
if (JSON.stringify(reason).indexOf("TIMEDOUT") != -1)
return "problem, Timeout";
else if (JSON.stringify(reason).indexOf("ENOTFOUND") != -1)
return "problem, Not reachable";
else if (JSON.stringify(reason).indexOf("ECONNREFUSED") != -1)
return "problem, Connection refused";
else if (JSON.stringify(reason).indexOf("SELF_SIGNED_CERT_IN_CHAIN") != -1)
return "problem, CA certificate not valid";
else if (JSON.stringify(reason).indexOf("is not in the cert's list") != -1)
return "problem, Broker URL mismatch";
else if ((JSON.stringify(reason).indexOf("certificate unknown") != -1) || (JSON.stringify(reason).indexOf("EPROTO") != -1))
return "problem, Certificate credentials not valid";
else if (JSON.stringify(reason).indexOf("key values mismatch") != -1)
return "problem, Key values mismatch";
else
return "problem, " + JSON.parse(reason.payload).message;
} catch (err) {
return "unknown problem";
}
}
//priority to the n.attrkey and n.value. If not found, retrieve from msg
//in any case, use n.id and n.type
function generateCreateElementPayload(node, n, msg) {
var attributes = [];
if (n.attrkey && n.attrvalue) {
var name = n.attrkey.trim();
var value = n.attrvalue.trim();
attributes.push({
name,
value
});
} else if (typeof msg.attributes != "undefined") {
attributes = msg.attributes;
} else if (typeof msg.payload.attributes != "undefined") {
attributes = msg.payload.attributes;
}
if (!attributes) {
throw "Missing 'attributes' property";
}
var payload = {
"contextElements": [{
"type": n.entype,
"isPattern": "false",
"id": n.enid,
"attributes": attributes
}],
"updateAction": "APPEND"
};
return payload;
}
//OrionOut node constructor
RED.nodes.registerType("fiware-orion-out", OrionOut);
function OrionOut(n) {
RED.nodes.createNode(this, n);
var s4cUtility = require("./snap4city-utility.js");
const logger = s4cUtility.getLogger(RED, this);
this.on("input", function (msg) {
this.service = n.service;
this.brokerConn = 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.name != "undefined" && typeof msg.payload.value != "undefined") {
msg.payload = [msg.payload];
}
// create json payload for context update
//var payload = generateCreateElementPayload(node, n, msg);
var payload = ""
if (Array.isArray(msg.payload)) {
if ((n.entype == "") || (n.enid == ""))
throw ("entype or enid missing");
payload = {
"contextElements": [{
"type": n.entype,
"isPattern": "false",
"id": n.enid,
"attributes": msg.payload
}],
"updateAction": "APPEND"
};
} else {
if (((msg.payload.type == undefined) && (n.entype == "")) ||
((msg.payload.id == undefined) && (n.enid == "")))
throw ("entype or enid missing");
payload = {
"contextElements": [{
"type": (msg.payload.type ? msg.payload.type : n.entype),
"isPattern": "false",
"id": (msg.payload.id ? msg.payload.id : n.enid),
"attributes": msg.payload.attributes
}],
"updateAction": "APPEND"
};
}
try {
node.brokerConn.init(node, n).then(function () {
node.brokerConn.updateContext(node, n, payload, msg.auth).then(
function (msg) {
logger.info("updateContext result:" + JSON.stringify(msg));
msg = formatOutput(node, n, JSON.parse(msg.payload));
node.send({
payload: msg,
statusCode: 200
});
node.status({
fill: "green",
shape: "dot",
text: "success"
});
},
function (reason) {
logger.error("updateContext error:" + JSON.stringify(reason));
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(reason)
});
node.error("failed to update, reason: " + JSON.stringify(reason));
}
);
});
} catch (err) {
node.error(err, msg);
node.status({
fill: "red",
shape: "ring",
text: getErrorMessage(err.code)
});
node.send({
payload: err.toString(),
statusCode: getErrorMessage(err.code)
});
}
} catch (e) {
node.status({
fill: "red",
shape: "ring",
text: "Problem with input msg"
});
node.error(e);
}
});
}
}