From 2d0c8f0fb015c97669068ac9cd6633c4377a3bce Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 16 Jul 2020 09:15:01 +0100 Subject: [PATCH 01/37] fix esversion for lint --- .jshintrc | 2 +- package.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.jshintrc b/.jshintrc index 4084293c..d7edf4bc 100644 --- a/.jshintrc +++ b/.jshintrc @@ -11,5 +11,5 @@ "shadow": true, "sub": true, "proto": true, - "esversion": 6 + "esversion": 8 } diff --git a/package.json b/package.json index 21926918..5cdd05a8 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,12 @@ "grunt-simple-mocha": "^0.4.1", "imap": "^0.8.19", "mailparser": "^2.7.7", - "markdown-it": "^10.0.0", + "markdown-it": "^11.0.0", "mocha": "~6.2.3", "msgpack-lite": "^0.1.26", "multilang-sentiment": "^1.2.0", "ngeohash": "^0.6.3", - "node-red": "^1.0.6", + "node-red": "^1.1.2", "node-red-node-test-helper": "~0.2.5", "nodemailer": "^6.4.6", "poplib": "^0.1.7", @@ -59,6 +59,6 @@ "when": "^3.7.8" }, "engines": { - "node": ">=8.6.0" + "node": ">=8.17.0" } } From e334040dd7f9dccd1c2452885b69358eb439791a Mon Sep 17 00:00:00 2001 From: Andreas Martens Date: Thu, 16 Jul 2020 10:00:27 +0100 Subject: [PATCH 02/37] replace xmpp-simple with @xmpp/client (#664) * replace xmpp-simple with @xmpp/client * indent at 4 * remove trailing commas --- social/xmpp/92-xmpp.html | 4 +- social/xmpp/92-xmpp.js | 502 ++++++++++++++++++++++++++++++--------- social/xmpp/package.json | 25 +- 3 files changed, 403 insertions(+), 128 deletions(-) diff --git a/social/xmpp/92-xmpp.html b/social/xmpp/92-xmpp.html index 92b0f3f3..3de2bb25 100644 --- a/social/xmpp/92-xmpp.html +++ b/social/xmpp/92-xmpp.html @@ -133,8 +133,8 @@ RED.nodes.registerType('xmpp-server',{ category: 'config', defaults: { - // server: {required:true}, - // port: {value:5222,required:true,validate:RED.validators.number()}, + server: {required:false}, + port: {value:5222,required:false,validate:RED.validators.number()}, user: {type:"text"}, nickname: {value:""} }, diff --git a/social/xmpp/92-xmpp.js b/social/xmpp/92-xmpp.js index 4e302aca..501e37e2 100644 --- a/social/xmpp/92-xmpp.js +++ b/social/xmpp/92-xmpp.js @@ -1,52 +1,163 @@ module.exports = function(RED) { "use strict"; - var XMPP = require('simple-xmpp'); + const {client, xml, jid} = require('@xmpp/client') process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + const LOGITALL=false; function XMPPServerNode(n) { RED.nodes.createNode(this,n); - this.server = n.server; - this.port = n.port; this.nickname = n.nickname; - this.username = n.user; + this.jid = n.user; + this.username = n.user.split('@')[0]; + // The user may elect to just specify the jid in the settings, + // in which case extract the server from the jid and default the port + if("undefined" === typeof n.server || n.server === ""){ + this.server = n.user.split('@')[1]; + } + else{ + this.server = n.server; + } + if("undefined" === typeof n.port || n.port === ""){ + this.port = 5222; + } + else{ + this.port = n.port; + } + + // The password is obfuscated and stored in a separate location var credentials = this.credentials; if (credentials) { this.password = credentials.password; } - this.client = new XMPP.SimpleXMPP(); + // The basic xmpp client object, this will be referred to as "xmpp" in the nodes. + // note we're not actually connecting here. + this.client = client({ + service: 'xmpp://' + this.server + ':' + this.port, + username: this.username, + password: this.password + }); + + // helper variable for checking against later, maybe we should be using the client + // object directly... this.connected = false; + // store the nodes that have us as config so we know when to tear it all down. + this.users = {}; + // helper variable, because "this" changes definition inside a callback var that = this; - this.client.con = function() { - if (that.connected === false ) { - that.connected = true; - that.client.connect({ - jid : that.username, - password : that.password, - host : that.server, - port : that.port, - //skipPresence : true, - reconnect : true, - preferred : "PLAIN" - }); + // function for a node to tell us it has us as config + this.register = function(xmppThat) { + if (RED.settings.verbose || LOGITALL) {that.log("registering "+xmppThat.id);} + that.users[xmppThat.id] = xmppThat; + // So we could start the connection here, but we already have the logic in the thats. + // if (Object.keys(that.users).length === 1) { + // this.client.start(); + // } + }; + + // function for a node to tell us it's not using us anymore + this.deregister = function(xmppThat,done) { + if (RED.settings.verbose || LOGITALL) {that.log("deregistering "+xmppThat.id);} + delete that.users[xmppThat.id]; + if (that.closing) { + return done(); } + if (Object.keys(that.users).length === 0) { + if (that.client && that.client.connected) { + return that.client.stop(done); + } else { + that.client.stop(); + return done(); + } + } + done(); + }; + + // store the last node to use us, in case we get an error back + this.lastUsed = undefined; + // function for a node to tell us it has just sent a message to our server + // so we know which node to blame if it all goes Pete Tong + this.used = function(xmppThat){ + if (RED.settings.verbose || LOGITALL) {that.log(xmppThat.id+" sent a message to the xmpp server");} + that.lastUsed = xmppThat; } - that.client.on('online', function(data) { + + // Some errors come back as a message :-( + // this means we need to figure out which node might have sent it + // we also deal with subscriptions (i.e. presence information) here + this.client.on('stanza', async (stanza) =>{ + if (stanza.is('message')) { + if (stanza.attrs.type == 'error') { + if (RED.settings.verbose || LOGITALL) { + that.log("Received error"); + that.log(stanza); + } + var err = stanza.getChild('error'); + if(err){ + var textObj = err.getChild('text'); + var text = "node-red:common.status.error"; + if("undefined" !== typeof textObj){ + text = textObj.getText(); + } + if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed);} + if("undefined" !== typeof that.lastUsed){ + that.lastUsed.status({fill:"red",shape:"ring",text:text}); + that.lastUsed.warn(text); + } + // maybe throw the message or summit + //that.error(text); + } + } + } + else if(stanza.is('presence')){ + if(['subscribe','subscribed','unsubscribe','unsubscribed'].indexOf(stanza.attrs.type) > -1){ + if (RED.settings.verbose || LOGITALL) {that.log("got a subscription based message");} + switch(stanza.attrs.type){ + case 'subscribe': + // they're asking for permission let's just say yes + var response = xml('presence', + {type:'subscribed', to:stanza.attrs.from}); + // if an error comes back we can't really blame anyone else + that.used(that); + that.client.send(response); + break; + default: + that.log("Was told we've "+stanza.attrs.type+" from "+stanza.attrs.from+" but we don't really care"); + } + } + } + }); + // We shouldn't have any errors here that the input/output nodes can't handle + // if you need to see everything though; uncomment this block + // this.client.on('error', err => { + // that.warn(err); + // that.warn(err.stack); + // }); + + // this gets called when we've completed the connection + this.client.on('online', async address => { + // provide some presence so people can see we're online that.connected = true; - that.client.setPresence('online', data.jid.user+' is online'); - that.log('connected as '+data.jid.user+' to '+data.jid._domain+":5222"); + await that.client.send(xml('presence')); + // await that.client.send(xml('presence', {type: 'available'},xml('status', {}, 'available'))); + if (RED.settings.verbose || LOGITALL) {that.log('connected as '+that.username+' to ' +that.server+':'+that.port);} }); - that.client.on('close', function() { + + // if the connection has gone away, not sure why! + this.client.on('offline', () => { that.connected = false; - that.log('connection closed'); + if (RED.settings.verbose || LOGITALL) {that.log('connection closed');} }); - this.on("close", function(done) { - that.client.setPresence('offline'); - that.client.disconnect(); - if (that.client.conn) { that.client.conn.end(); } - that.client = null; + + // gets called when the node is destroyed, e.g. if N-R is being stopped. + this.on("close", async done => { + if(that.connected){ + await that.client.send(xml('presence', {type: 'unavailable'})); + } + that.client.stop().catch(that.error); + done(); }); } @@ -68,31 +179,95 @@ module.exports = function(RED) { var node = this; var xmpp = this.serverConfig.client; + + /* connection states + online: We are connected + offline: disconnected and will not autoretry + connecting: Socket is connecting + connect: Socket is connected + opening: Stream is opening + open: Stream is open + closing: Stream is closing + close: Stream is closed + disconnecting: Socket is disconnecting + disconnect: Socket is disconnected + */ - xmpp.on('online', function(data) { - node.status({fill:"green",shape:"dot",text:"connected"}); + xmpp.on('online', async address => { + node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); if ((node.join) && (node.from !== "")) { - // disable chat history var to = node.from+'/'+node.nick; - var stanza = new xmpp.Element('presence', {"to": to}). - c('x', { xmlns: 'http://jabber.org/protocol/muc' }). - c('history', { maxstanzas:0, seconds:1 }); - xmpp.conn.send(stanza); - xmpp.join(to); + // the presence with the muc x element signifies we want to join the muc + // if we want to support passwords, we need to add that as a child of the x element + // (third argument to the x/muc/children ) + // We also turn off chat history (maxstanzas 0) because that's not what this node is about. + var stanza = xml('presence', + {"to": to}, + xml("x",'http://jabber.org/protocol/muc'), + { maxstanzas:0, seconds:1 } + ); + node.serverConfig.used(node); + xmpp.send(stanza); } }); - // xmpp.on('chat', function(from, message) { - // var msg = { topic:from, payload:message }; - // if (!node.join && ((node.from === "") || (node.from === from))) { - // node.send([msg,null]); - // } - // }); + xmpp.on('connecting', async address => { + node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + }); + xmpp.on('connect', async address => { + node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connected"}); + }); + xmpp.on('opening', async address => { + node.status({fill:"grey",shape:"dot",text:"opening"}); + }); + xmpp.on('open', async address => { + node.status({fill:"grey",shape:"dot",text:"open"}); + }); + xmpp.on('closing', async address => { + node.status({fill:"grey",shape:"dot",text:"closing"}); + }); + xmpp.on('close', async address => { + node.status({fill:"grey",shape:"dot",text:"closed"}); + }); + xmpp.on('disconnecting', async address => { + node.status({fill:"grey",shape:"dot",text:"disconnecting"}); + }); + // we'll not add a offline catcher, as the error catcher should populate the status for us + + // Should we listen on other's status (chatstate) or a chatroom state (groupbuddy)? + xmpp.on('error', err => { + if (RED.settings.verbose || LOGITALL) { node.log(err); } + if (err.hasOwnProperty("stanza")) { + if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } + else { node.error(err.stanza.name,err); } + node.status({fill:"red",shape:"ring",text:"bad login"}); + } + else { + if (err.errno === "ETIMEDOUT") { + node.error("Timeout connecting to server",err); + node.status({fill:"red",shape:"ring",text:"timeout"}); + } + if (err.errno === "ENOTFOUND") { + node.error("Server doesn't exist "+xmpp.options.service,err); + node.status({fill:"red",shape:"ring",text:"bad address"}); + } + else if (err === "XMPP authentication failure") { + node.error(err,err); + node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); + } + else { + node.error(err.errno,err); + node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); + } + } + }); - xmpp.on('stanza', function(stanza) { + // Meat of it, a stanza object contains chat messages (and other things) + xmpp.on('stanza', async (stanza) =>{ + // node.log("Received stanza"); + if (RED.settings.verbose || LOGITALL) {node.log(stanza);} if (stanza.is('message')) { if (stanza.attrs.type == 'chat') { - //console.log(stanza); var body = stanza.getChild('body'); if (body) { var msg = { payload:body.getText() }; @@ -106,74 +281,85 @@ module.exports = function(RED) { } } } + else if(stanza.attrs.type == 'groupchat'){ + const parts = stanza.attrs.from.split("/"); + var conference = parts[0]; + var from = parts[1]; + var body = stanza.getChild('body'); + var payload = ""; + if("undefined" !== typeof body){ + payload = body.getText(); + } + var msg = { topic:from, payload:payload, room:conference }; + if (stanza.attrs.from != node.nick) { + if ((node.join) && (node.from === conference)) { + node.send([msg,null]); + } + } + } } - }); + else if(stanza.is('presence')){ + if(['subscribe','subscribed','unsubscribe','unsubscribed'].indexOf(stanza.attrs.type) > -1){ + // this isn't for us, let the config node deal with it. - xmpp.on('groupchat', function(conference, from, message, stamp) { - var msg = { topic:from, payload:message, room:conference }; - if (from != node.nick) { - if ((node.join) && (node.from === conference)) { - node.send([msg,null]); + } + else{ + var statusText=""; + if(stanza.attrs.type === 'unavailable'){ + // the user might not exist, but the server doesn't tell us that! + statusText = "offline"; + } + var status = stanza.getChild('status'); + if("undefined" !== typeof status){ + statusText = status.getText(); + } + // right, do we care if there's no status? + if(statusText !== ""){ + var from = stanza.attrs.from; + var state = stanza.attrs.show; + var msg = {topic:from, payload: {presence:state, status:statusText} }; + node.send([null,msg]); + } + else{ + if (RED.settings.verbose || LOGITALL) { + node.log("not propagating blank status"); + node.log(stanza); + } + } } } }); - //xmpp.on('chatstate', function(from, state) { - //console.log('%s is currently %s', from, state); - //var msg = { topic:from, payload: {presence:state} }; - //node.send([null,msg]); - //}); - xmpp.on('buddy', function(jid, state, statusText) { - node.log(jid+" is "+state+" : "+statusText); - var msg = { topic:jid, payload: { presence:state, status:statusText} }; - node.send([null,msg]); - }); - // xmpp.on('groupbuddy', function(conference, from, state, statusText) { - // //console.log('%s: %s is in %s state - %s',conference, from, state, statusText); - // var msg = { topic:from, payload: { presence:state, status:statusText}, room:conference }; + // xmpp.on('subscribe', from => { + // xmpp.acceptSubscription(from); // }); - xmpp.on('error', function(err) { - if (RED.settings.verbose) { node.log(err); } - if (err.hasOwnProperty("stanza")) { - if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } - else { node.error(err.stanza.name,err); } - node.status({fill:"red",shape:"ring",text:"bad login"}); - } - else { - if (err.errno === "ETIMEDOUT") { - node.error("Timeout connecting to server",err); - node.status({fill:"red",shape:"ring",text:"timeout"}); - } - else if (err === "XMPP authentication failure") { - node.error(err,err); - node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); - } - else { - node.error(err.errno,err); - node.status({fill:"red",shape:"ring",text:"error"}); - } - } - }); - - xmpp.on('subscribe', function(from) { - xmpp.acceptSubscription(from); - }); - + //register with config + this.serverConfig.register(this); // Now actually make the connection try { - node.status({fill:"grey",shape:"dot",text:"connecting"}); - xmpp.con(); + if(xmpp.status === "online"){ + node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); + } + else{ + node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + if(xmpp.status === "offline"){ + xmpp.start(); + } + } } catch(e) { - node.error("Bad xmpp configuration"); - node.status({fill:"red",shape:"ring",text:"not connected"}); + node.error("Bad xmpp configuration; service: "+xmpp.options.service+" jid: "+node.serverConfig.jid); + node.warn(e); + node.warn(e.stack); + node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); } - node.on("close", function() { - node.status({}); + node.on("close", function(removed, done) { + node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); + node.serverConfig.deregister(node, done); }); } RED.nodes.registerType("xmpp in",XmppInNode); @@ -191,21 +377,63 @@ module.exports = function(RED) { var xmpp = this.serverConfig.client; + /* connection states + online: We are connected + offline: disconnected and will not autoretry + connecting: Socket is connecting + connect: Socket is connected + opening: Stream is opening + open: Stream is open + closing: Stream is closing + close: Stream is closed + disconnecting: Socket is disconnecting + disconnect: Socket is disconnected + */ + xmpp.on('online', function(data) { - node.status({fill:"green",shape:"dot",text:"connected"}); + node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"}); if ((node.join) && (node.from !== "")) { // disable chat history var to = node.to+'/'+node.nick; - var stanza = new xmpp.Element('presence', {"to": to}). - c('x', { xmlns: 'http://jabber.org/protocol/muc' }). - c('history', { maxstanzas:0, seconds:1 }); - xmpp.conn.send(stanza); - xmpp.join(to); + // the presence with the muc x element signifies we want to join the muc + // if we want to support passwords, we need to add that as a child of the x element + // (third argument to the x/muc/children ) + // We also turn off chat history (maxstanzas 0) because that's not what this node is about. + var stanza = xml('presence', + {"to": to}, + xml("x",'http://jabber.org/protocol/muc'), + { maxstanzas:0, seconds:1 } + ); + node.serverConfig.used(node); + xmpp.send(stanza); } }); + xmpp.on('connecting', async address => { + node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + }); + xmpp.on('connect', async address => { + node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connected"}); + }); + xmpp.on('opening', async address => { + node.status({fill:"grey",shape:"dot",text:"opening"}); + }); + xmpp.on('open', async address => { + node.status({fill:"grey",shape:"dot",text:"open"}); + }); + xmpp.on('closing', async address => { + node.status({fill:"grey",shape:"dot",text:"closing"}); + }); + xmpp.on('close', async address => { + node.status({fill:"grey",shape:"dot",text:"closed"}); + }); + xmpp.on('disconnecting', async address => { + node.status({fill:"grey",shape:"dot",text:"disconnecting"}); + }); + // we'll not add a offline catcher, as the error catcher should populate the status for us + xmpp.on('error', function(err) { - if (RED.settings.verbose) { node.log(err); } + if (RED.settings.verbose || LOGITALL) { node.log(err); } if (err.hasOwnProperty("stanza")) { if (err.stanza.name === 'stream:error') { node.error("stream:error - bad login id/pwd ?",err); } else { node.error(err.stanza.name,err); } @@ -216,54 +444,98 @@ module.exports = function(RED) { node.error("Timeout connecting to server",err); node.status({fill:"red",shape:"ring",text:"timeout"}); } + if (err.errno === "ENOTFOUND") { + node.error("Server doesn't exist "+xmpp.options.service,err); + node.status({fill:"red",shape:"ring",text:"bad address"}); + } else if (err === "XMPP authentication failure") { node.error(err,err); node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"}); } else { node.error(err.errno,err); - node.status({fill:"red",shape:"ring",text:"error"}); + node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); } } }); + //register with config + this.serverConfig.register(this); // Now actually make the connection - try { - node.status({fill:"grey",shape:"dot",text:"connecting"}); - xmpp.con(); + if(xmpp.status === "online"){ + node.status({fill:"green",shape:"dot",text:"online"}); } - catch(e) { - node.error("Bad xmpp configuration"); - node.status({fill:"red",shape:"ring",text:"not connected"}); + else{ + node.status({fill:"grey",shape:"dot",text:"node-red:common.status.connecting"}); + if(xmpp.status === "offline"){ + xmpp.start().catch(error => { + node.error("Bad xmpp configuration; service: "+xmpp.options.service+" jid: "+node.serverConfig.jid); + node.warn(error); + node.warn(error.stack); + node.status({fill:"red",shape:"ring",text:"node-red:common.status.error"}); + }); + } } + // Let's get down to business and actually send a message node.on("input", function(msg) { if (msg.presence) { if (['away', 'dnd', 'xa', 'chat'].indexOf(msg.presence) > -1 ) { - xmpp.setPresence(msg.presence, msg.payload); + var stanza = xml('presence', + {"show":msg.presence}, + xml('status',{},msg.payload)); + node.serverConfig.used(node); + xmpp.send(stanza); } else { node.warn("Can't set presence - invalid value: "+msg.presence); } } + else if(msg.command){ + if(msg.command === "subscribe"){ + var stanza = xml('presence', + {type:'subscribe', to: msg.payload}); + node.serverConfig.used(node); + xmpp.send(stanza); + } + } else { var to = node.to || msg.topic || ""; if (to !== "") { + var message; + var type = node.join? "groupchat":"chat"; if (node.sendAll) { - xmpp.send(to, JSON.stringify(msg), node.join); + message = xml( + "message", + { type: type, to: to }, + xml("body", {}, JSON.stringify(msg)) + ); + } else if (msg.payload) { if (typeof(msg.payload) === "object") { - xmpp.send(to, JSON.stringify(msg.payload), node.join); + message = xml( + "message", + { type: type, to: to }, + xml("body", {}, JSON.stringify(msg.payload)) + ); } else { - xmpp.send(to, msg.payload.toString(), node.join); + message = xml( + "message", + { type: type, to: to }, + xml("body", {}, msg.payload.toString()) + ); } } + node.serverConfig.used(node); + xmpp.send(message); } } }); - node.on("close", function() { - node.status({}); + node.on("close", function(removed, done) { + if (RED.settings.verbose || LOGITALL) {node.log("Closing");} + node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); + node.serverConfig.deregister(node, done); }); } RED.nodes.registerType("xmpp out",XmppOutNode); diff --git a/social/xmpp/package.json b/social/xmpp/package.json index a37f3b5d..2225f826 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,18 +1,21 @@ { - "name" : "node-red-node-xmpp", - "version" : "0.2.4", - "description" : "A Node-RED node to talk to an XMPP server", - "dependencies" : { - "simple-xmpp" : "^1.3.1" + "name": "node-red-node-xmpp", + "version": "0.2.4", + "description": "A Node-RED node to talk to an XMPP server", + "dependencies": { + "@xmpp/client": "^0.11.1" }, - "repository" : { - "type":"git", - "url":"https://github.com/node-red/node-red-nodes/tree/master/social/xmpp" + "repository": { + "type": "git", + "url": "https://github.com/node-red/node-red-nodes/tree/master/social/xmpp" }, "license": "Apache-2.0", - "keywords": [ "node-red", "xmpp" ], - "node-red" : { - "nodes" : { + "keywords": [ + "node-red", + "xmpp" + ], + "node-red": { + "nodes": { "xmpp": "92-xmpp.js" } }, From 4cabe5ea304b5b3a35ed6cc5e16f8cefc97b95a6 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 16 Jul 2020 10:15:49 +0100 Subject: [PATCH 03/37] bump xmpp node package version --- .jshintrc | 2 +- social/xmpp/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.jshintrc b/.jshintrc index d7edf4bc..b16056c8 100644 --- a/.jshintrc +++ b/.jshintrc @@ -11,5 +11,5 @@ "shadow": true, "sub": true, "proto": true, - "esversion": 8 + "esversion": 8 } diff --git a/social/xmpp/package.json b/social/xmpp/package.json index 2225f826..2670f699 100644 --- a/social/xmpp/package.json +++ b/social/xmpp/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-xmpp", - "version": "0.2.4", + "version": "0.3.0", "description": "A Node-RED node to talk to an XMPP server", "dependencies": { "@xmpp/client": "^0.11.1" From 81039eacbca8cbccb9034d25a3e0251cb11df525 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 16 Jul 2020 10:16:03 +0100 Subject: [PATCH 04/37] bump markdown node package version --- parsers/markdown/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parsers/markdown/package.json b/parsers/markdown/package.json index 669ae525..f578e0fa 100644 --- a/parsers/markdown/package.json +++ b/parsers/markdown/package.json @@ -1,9 +1,9 @@ { "name": "node-red-node-markdown", - "version": "0.1.4", + "version": "0.2.0", "description": "A Node-RED node to convert a markdown string to html.", "dependencies": { - "markdown-it": "^10.0.0" + "markdown-it": "^11.0.0" }, "repository": { "type": "git", From 4ed0cb84d9fa5cf3f01412895b074930c7484c40 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 6 Aug 2020 18:10:09 +0100 Subject: [PATCH 05/37] add some words to snmp info/readme to close #667 --- io/snmp/README.md | 4 ++-- io/snmp/package.json | 2 +- io/snmp/snmp.html | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/io/snmp/README.md b/io/snmp/README.md index e71c82f8..5067b11b 100644 --- a/io/snmp/README.md +++ b/io/snmp/README.md @@ -107,7 +107,7 @@ Values depends on the oids being requested. ### snmp-subtree -Simple SNMP oid subtree fetcher. Triggered by any input. +Simple SNMP oid subtree fetcher. Triggered by any input. Reads from OID specified and any below it. `msg.host` may contain the host. @@ -127,7 +127,7 @@ Values depends on the oids being requested. ### snmp-walker -Simple SNMP oid walker fetcher. Triggered by any input. +Simple SNMP oid walker fetcher. Triggered by any input. Reads from OID specified to the end of the table. `msg.host` may contain the host. diff --git a/io/snmp/package.json b/io/snmp/package.json index 89a7a457..c2c118f7 100644 --- a/io/snmp/package.json +++ b/io/snmp/package.json @@ -1,6 +1,6 @@ { "name" : "node-red-node-snmp", - "version" : "0.0.24", + "version" : "0.0.25", "description" : "A Node-RED node that looks for SNMP oids.", "dependencies" : { "net-snmp" : "1.2.4" diff --git a/io/snmp/snmp.html b/io/snmp/snmp.html index 4676dfb1..4cd13ef8 100644 --- a/io/snmp/snmp.html +++ b/io/snmp/snmp.html @@ -224,7 +224,7 @@ @@ -111,6 +112,7 @@
  • msg.count if set this will override the configured number of characters as long as it is less than the number configured.
  • msg.waitfor single character, escape code, or hex code. If set, the node will wait until it matches that character in the stream and then start the output.
  • +
  • Optionally the baudrate can be changed using msg.baudrate
  • Outputs

      diff --git a/io/serialport/25-serial.js b/io/serialport/25-serial.js index 172ffbd7..81f4fc0a 100644 --- a/io/serialport/25-serial.js +++ b/io/serialport/25-serial.js @@ -41,6 +41,19 @@ module.exports = function(RED) { node.port = serialPool.get(this.serialConfig); node.on("input",function(msg) { + if (msg.hasOwnProperty("baudrate")) { + var baud = parseInt(msg.baudrate); + if (isNaN(baud)) { + node.error(RED._("serial.errors.badbaudrate"),msg); + } else { + node.port.update({baudRate: baud},function(err,res) { + if (err) { + var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path); + node.error(errmsg,msg); + } + }); + } + } if (!msg.hasOwnProperty("payload")) { return; } // do nothing unless we have a payload var payload = node.port.encodePayload(msg.payload); node.port.write(payload,function(err,res) { @@ -121,6 +134,19 @@ module.exports = function(RED) { node.port = serialPool.get(this.serialConfig); // Serial Out node.on("input",function(msg) { + if (msg.hasOwnProperty("baudrate")) { + var baud = parseInt(msg.baudrate); + if (isNaN(baud)) { + node.error(RED._("serial.errors.badbaudrate"),msg); + } else { + node.port.update({baudRate: baud},function(err,res) { + if (err) { + var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path); + node.error(errmsg,msg); + } + }); + } + } if (!msg.hasOwnProperty("payload")) { return; } // do nothing unless we have a payload if (msg.hasOwnProperty("count") && (typeof msg.count === "number") && (node.serialConfig.out === "count")) { node.serialConfig.newline = msg.count; @@ -249,6 +275,7 @@ module.exports = function(RED) { return payload; }, write: function(m,cb) { this.serial.write(m,cb); }, + update: function(m,cb) { this.serial.update(m,cb); }, enqueue: function(msg,sender,cb) { var payload = this.encodePayload(msg.payload); var qobj = { diff --git a/io/serialport/locales/en-US/25-serial.json b/io/serialport/locales/en-US/25-serial.json index fd52c996..f816b611 100644 --- a/io/serialport/locales/en-US/25-serial.json +++ b/io/serialport/locales/en-US/25-serial.json @@ -66,7 +66,8 @@ "unexpected-close": "serial port __port__ closed unexpectedly", "disconnected": "serial port __port__ disconnected", "closed": "serial port __port__ closed", - "list": "Failed to list ports. Please enter manually." + "list": "Failed to list ports. Please enter manually.", + "badbaudrate": "Baudrate is not valid" } } } diff --git a/io/serialport/locales/ja/25-serial.json b/io/serialport/locales/ja/25-serial.json index f2189c85..36bf27d1 100644 --- a/io/serialport/locales/ja/25-serial.json +++ b/io/serialport/locales/ja/25-serial.json @@ -51,7 +51,8 @@ "unexpected-close": "serial port __port__ closed unexpectedly", "disconnected": "serial port __port__ disconnected", "closed": "serial port __port__ closed", - "list": "ポートのリスト化に失敗しました。手動で入力してください。" + "list": "ポートのリスト化に失敗しました。手動で入力してください。", + "badbaudrate": "Baudrate is not valid" } } } From 53ad00f1653970f223efa8b4634f95a1063cad90 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 27 Aug 2020 13:00:10 +0100 Subject: [PATCH 09/37] bump serialport dependency, add ja translation for baudrate message, fix linting, bump package --- io/serialport/25-serial.js | 50 +++++++++++----------- io/serialport/locales/en-US/25-serial.json | 2 +- io/serialport/locales/ja/25-serial.json | 2 +- io/serialport/package.json | 4 +- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/io/serialport/25-serial.js b/io/serialport/25-serial.js index 81f4fc0a..2d9b4f2c 100644 --- a/io/serialport/25-serial.js +++ b/io/serialport/25-serial.js @@ -41,19 +41,19 @@ module.exports = function(RED) { node.port = serialPool.get(this.serialConfig); node.on("input",function(msg) { - if (msg.hasOwnProperty("baudrate")) { - var baud = parseInt(msg.baudrate); - if (isNaN(baud)) { + if (msg.hasOwnProperty("baudrate")) { + var baud = parseInt(msg.baudrate); + if (isNaN(baud)) { node.error(RED._("serial.errors.badbaudrate"),msg); - } else { - node.port.update({baudRate: baud},function(err,res) { - if (err) { - var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path); - node.error(errmsg,msg); - } - }); - } - } + } else { + node.port.update({baudRate: baud},function(err,res) { + if (err) { + var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path); + node.error(errmsg,msg); + } + }); + } + } if (!msg.hasOwnProperty("payload")) { return; } // do nothing unless we have a payload var payload = node.port.encodePayload(msg.payload); node.port.write(payload,function(err,res) { @@ -135,18 +135,18 @@ module.exports = function(RED) { // Serial Out node.on("input",function(msg) { if (msg.hasOwnProperty("baudrate")) { - var baud = parseInt(msg.baudrate); - if (isNaN(baud)) { - node.error(RED._("serial.errors.badbaudrate"),msg); - } else { - node.port.update({baudRate: baud},function(err,res) { - if (err) { - var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path); - node.error(errmsg,msg); - } - }); - } - } + var baud = parseInt(msg.baudrate); + if (isNaN(baud)) { + node.error(RED._("serial.errors.badbaudrate"),msg); + } else { + node.port.update({baudRate: baud},function(err,res) { + if (err) { + var errmsg = err.toString().replace("Serialport","Serialport "+node.port.serial.path); + node.error(errmsg,msg); + } + }); + } + } if (!msg.hasOwnProperty("payload")) { return; } // do nothing unless we have a payload if (msg.hasOwnProperty("count") && (typeof msg.count === "number") && (node.serialConfig.out === "count")) { node.serialConfig.newline = msg.count; @@ -275,7 +275,7 @@ module.exports = function(RED) { return payload; }, write: function(m,cb) { this.serial.write(m,cb); }, - update: function(m,cb) { this.serial.update(m,cb); }, + update: function(m,cb) { this.serial.update(m,cb); }, enqueue: function(msg,sender,cb) { var payload = this.encodePayload(msg.payload); var qobj = { diff --git a/io/serialport/locales/en-US/25-serial.json b/io/serialport/locales/en-US/25-serial.json index f816b611..03ef70e6 100644 --- a/io/serialport/locales/en-US/25-serial.json +++ b/io/serialport/locales/en-US/25-serial.json @@ -67,7 +67,7 @@ "disconnected": "serial port __port__ disconnected", "closed": "serial port __port__ closed", "list": "Failed to list ports. Please enter manually.", - "badbaudrate": "Baudrate is not valid" + "badbaudrate": "Baudrate is invalid" } } } diff --git a/io/serialport/locales/ja/25-serial.json b/io/serialport/locales/ja/25-serial.json index 36bf27d1..647ed53c 100644 --- a/io/serialport/locales/ja/25-serial.json +++ b/io/serialport/locales/ja/25-serial.json @@ -52,7 +52,7 @@ "disconnected": "serial port __port__ disconnected", "closed": "serial port __port__ closed", "list": "ポートのリスト化に失敗しました。手動で入力してください。", - "badbaudrate": "Baudrate is not valid" + "badbaudrate": "ボーレートが不正です" } } } diff --git a/io/serialport/package.json b/io/serialport/package.json index d553ad75..d8f237ed 100644 --- a/io/serialport/package.json +++ b/io/serialport/package.json @@ -1,9 +1,9 @@ { "name" : "node-red-node-serialport", - "version" : "0.10.3", + "version" : "0.11.0", "description" : "Node-RED nodes to talk to serial ports", "dependencies" : { - "serialport" : "^8.0.8" + "serialport" : "^9.0.1" }, "repository" : { "type":"git", From 46f3dfc3827f2a4e01df7a9617f6464f41b88d39 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 28 Aug 2020 09:36:06 +0100 Subject: [PATCH 10/37] email don't retry connection while still busy. --- social/email/61-email.js | 8 +++++--- social/email/package.json | 6 +++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/social/email/61-email.js b/social/email/61-email.js index 2dbfad24..c171d2ba 100644 --- a/social/email/61-email.js +++ b/social/email/61-email.js @@ -348,10 +348,10 @@ module.exports = function(RED) { //console.log("> Inbox err : %j", err); //console.log("> Inbox open: %j", box); if (err) { - s = false; node.status({fill:"red", shape:"ring", text:"email.status.foldererror"}); node.error(RED._("email.errors.fetchfail", {folder:node.box}),err); imap.end(); + s = false; setInputRepeatTimeout(); return; } @@ -412,6 +412,7 @@ module.exports = function(RED) { var cleanup = function() { imap.end(); s = false; + setInputRepeatTimeout(); }; if (this.disposition === "Delete") { imap.addFlags(results, "\Deleted", cleanup); @@ -420,11 +421,12 @@ module.exports = function(RED) { } else { cleanup(); } - setInputRepeatTimeout(); }); fetch.once('error', function(err) { console.log('Fetch error: ' + err); + imap.end(); + s = false; setInputRepeatTimeout(); }); } @@ -442,7 +444,7 @@ module.exports = function(RED) { if (node.protocol === "POP3") { checkPOP3(msg); } else if (node.protocol === "IMAP") { - if (s === false) { checkIMAP(msg); } + if (s === false && ss == false) { checkIMAP(msg); } } } // End of checkEmail diff --git a/social/email/package.json b/social/email/package.json index 56375b20..90da80a8 100644 --- a/social/email/package.json +++ b/social/email/package.json @@ -1,11 +1,11 @@ { "name": "node-red-node-email", - "version": "1.7.8", + "version": "1.7.9", "description": "Node-RED nodes to send and receive simple emails", "dependencies": { "imap": "^0.8.19", - "mailparser": "^2.7.7", - "nodemailer": "^6.4.6", + "mailparser": "^2.8.1", + "nodemailer": "^6.4.11", "poplib": "^0.1.7" }, "repository": { From 0b4c1ff977c021e0c3293e2dcb34de34cd0d395f Mon Sep 17 00:00:00 2001 From: Pablo Acosta-Serafini <63065092+pmacostapdi@users.noreply.github.com> Date: Mon, 31 Aug 2020 05:45:41 -0400 Subject: [PATCH 11/37] Enable the use of environment variables to define GPIO pin number (#676) * Enable the use of environment variables to define GPIO pin number * Pin field moved below table. Field width made the same as table. Restored fa-icon and "Pin" label. Closed validation escapes. --- hardware/PiGpio/36-rpi-gpio.html | 79 +++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/hardware/PiGpio/36-rpi-gpio.html b/hardware/PiGpio/36-rpi-gpio.html index 47f8e4c7..a4a66deb 100644 --- a/hardware/PiGpio/36-rpi-gpio.html +++ b/hardware/PiGpio/36-rpi-gpio.html @@ -70,8 +70,7 @@ @@ -90,20 +115,20 @@

      Save will update an existing object or insert a new object if one does not already exist.

      Insert will insert a new object.

      Save and insert either store msg or msg.payload.

      -

      Update will modify an existing object or objects. The query to select objects to update uses msg.query, +

      Update will modify an existing object or objects. The query to select objects to update uses msg.query and the update to the element uses msg.payload. If msg.query._id is a valid mongo ObjectId string it will be converted to an ObjectId type.

      -

      Update can add an object if it does not exist or update multiple objects.

      +

      Update can add a object if it does not exist or update multiple objects.

      Remove will remove objects that match the query passed in on msg.payload. A blank query will delete all of the objects in the collection.

      You can either set the collection method in the node config or on msg.collection. Setting it in the node will override msg.collection.

      -

      By default, MongoDB creates an _id property as the primary key, so repeated injections of the +

      By default MongoDB creates an _id property as the primary key - so repeated injections of the same msg will result in many database entries.

      -

      If this is NOT the desired behaviour, i.e., you want repeated entries to overwrite, then you must set +

      If this is NOT the desired behaviour - ie. you want repeated entries to overwrite, then you must set the msg._id property to be a constant by the use of a previous function node.

      This could be a unique constant or you could create one based on some other msg property.

      -

      Currently we do not limit or cap the collection size, however this may well change.

      +

      Currently we do not limit or cap the collection size at all... this may well change.

      + - - - - - + + + + + diff --git a/social/email/61-email.js b/social/email/61-email.js index 4efaf2a3..f046a635 100644 --- a/social/email/61-email.js +++ b/social/email/61-email.js @@ -1,3 +1,4 @@ +/* eslint-disable indent */ /** * POP3 protocol - RFC1939 - https://www.ietf.org/rfc/rfc1939.txt @@ -11,11 +12,13 @@ module.exports = function(RED) { "use strict"; - var nodemailer = require("nodemailer"); + var util = require("util"); var Imap = require('imap'); var POP3Client = require("poplib"); - var SimpleParser = require("mailparser").simpleParser; - var util = require("util"); + var nodemailer = require("nodemailer"); + var simpleParser = require("mailparser").simpleParser; + var SMTPServer = require("smtp-server").SMTPServer; + //var microMTA = require("micromta").microMTA; if (parseInt(process.version.split("v")[1].split(".")[0]) < 8) { throw "Error : Requires nodejs version >= 8."; @@ -291,8 +294,8 @@ module.exports = function(RED) { // We have now received a new email message. Create an instance of a mail parser // and pass in the email message. The parser will signal when it has parsed the message. - SimpleParser(data, {}, function(err, parsed) { - //node.log(util.format("SimpleParser: on(end): %j", mailObject)); + simpleParser(data, {}, function(err, parsed) { + //node.log(util.format("simpleParser: on(end): %j", mailObject)); if (err) { node.status({fill:"red", shape:"ring", text:"email.status.parseerror"}); node.error(RED._("email.errors.parsefail", {folder:node.box}), err); @@ -406,7 +409,7 @@ module.exports = function(RED) { //console.log("> Fetch message - msg=%j, seqno=%d", imapMessage, seqno); imapMessage.on('body', function(stream, info) { //console.log("> message - body - stream=?, info=%j", info); - SimpleParser(stream, {}, function(err, parsed) { + simpleParser(stream, {}, function(err, parsed) { if (err) { node.status({fill:"red", shape:"ring", text:"email.status.parseerror"}); node.error(RED._("email.errors.parsefail", {folder:node.box}),err); @@ -513,4 +516,63 @@ module.exports = function(RED) { global: { type:"boolean" } } }); + + + function emailMtaNode(n) { + RED.nodes.createNode(this,n); + this.port = n.port; + var node = this; + + node.mta = new SMTPServer({ + secure: false, + logger: false, + disabledCommands: ['AUTH', 'STARTTLS'], + + onData: function (stream, session, callback) { + simpleParser(stream, { + skipTextToHtml: true, + skipTextLinks: true + }) + .then(parsed => { + node.status({fill:"green", shape:"dot", text:""}); + var msg = {} + msg.payload = parsed.text; + msg.topic = parsed.subject; + msg.date = parsed.date; + msg.header = {}; + parsed.headers.forEach((v, k) => {msg.header[k] = v;}); + if (parsed.html) { msg.html = parsed.html; } + if (parsed.to) { + if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; } + else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; } + } + if (parsed.cc) { + if (typeof(parsed.cc) === "string" && parsed.cc.length > 0) { msg.cc = parsed.cc; } + else if (parsed.cc.hasOwnProperty("text") && parsed.cc.text.length > 0) { msg.cc = parsed.cc.text; } + } + if (parsed.cc && parsed.cc.length > 0) { msg.cc = parsed.cc; } + if (parsed.bcc && parsed.bcc.length > 0) { msg.bcc = parsed.bcc; } + if (parsed.from && parsed.from.value && parsed.from.value.length > 0) { msg.from = parsed.from.value[0].address; } + if (parsed.attachments) { msg.attachments = parsed.attachments; } + else { msg.attachments = []; } + node.send(msg); // Propagate the message down the flow + setTimeout(function() { node.status({})}, 500); + }) + .catch(err => { node.error(RED._("email.errors.parsefail"),err); }) + .finally(callback); + } + }); + + node.mta.listen(node.port); + + node.mta.on("error", err => { + node.error("Error: " + err.message, err); + }); + + node.on("close", function() { + node.mta.close(); + }); + } + RED.nodes.registerType("e-mail mta",emailMtaNode); + }; diff --git a/social/email/locales/en-US/61-email.html b/social/email/locales/en-US/61-email.html index 3f54dba8..282ff3b2 100644 --- a/social/email/locales/en-US/61-email.html +++ b/social/email/locales/en-US/61-email.html @@ -26,7 +26,7 @@

      Additionally msg.header contains the complete header object including to, cc and other potentially useful properties.

      It can optionally mark the message as Read (default), Delete it, or leave unmarked (None).

      -

      Uses the node-imap module - see that page for +

      Uses the node-imap module - see that page for information on the msg.criteria format if needed.

      Note: uses IMAP with SSL to port 993.

      Any attachments supplied in the incoming email can be found in the msg.attachments property. This will be an array of objects where @@ -51,3 +51,13 @@ + \ No newline at end of file diff --git a/social/email/locales/en-US/61-email.json b/social/email/locales/en-US/61-email.json index 9bc3a8fc..cca6fa1d 100644 --- a/social/email/locales/en-US/61-email.json +++ b/social/email/locales/en-US/61-email.json @@ -34,7 +34,8 @@ "default-message": "__description__\n\nFile from Node-RED is attached: __filename__", "tip": { "cred": "Note: Copied credentials from global emailkeys.js file.", - "recent": "Tip: Only retrieves the single most recent email." + "recent": "Tip: Only retrieves the single most recent email.", + "mta": "Note: To use ports below 1024 you may need elevated (root) privileges. See help sidebar." }, "status": { "messagesent": "Message sent: __response__", @@ -47,6 +48,7 @@ "inboxzero": "you have achieved Inbox Zero", "sending": "sending", "sendfail": "send failed", + "parseerror": "Failed to parse message", "connecterror": "connect error" }, "errors": { @@ -56,6 +58,7 @@ "nosmtptransport": "No SMTP transport. See info panel.", "nopayload": "No payload to send", "fetchfail": "Failed to fetch folder: __folder__", + "parsefail": "Failed to parse message", "messageerror": "Fetch message error: __error__", "refreshtoolarge": "Refresh interval too large. Limiting to 2147483 seconds" } diff --git a/social/email/locales/ja/61-email.html b/social/email/locales/ja/61-email.html index 560dcb8b..d8dbfac9 100644 --- a/social/email/locales/ja/61-email.html +++ b/social/email/locales/ja/61-email.html @@ -38,3 +38,13 @@

      POP3プロトコルでのデフォルトポート番号は、素のTCPでは110番、SSLでは995番です。IMAPプロトコルでは、素のTCPでは143番、SSLでは993番です。

      + + \ No newline at end of file diff --git a/social/email/locales/ja/61-email.json b/social/email/locales/ja/61-email.json index 08d04197..0938a236 100644 --- a/social/email/locales/ja/61-email.json +++ b/social/email/locales/ja/61-email.json @@ -21,7 +21,8 @@ "default-message": "__description__\n\nNode-REDからファイルが添付されました: __filename__", "tip": { "cred": "注釈: emailkeys.jsファイルから認証情報をコピーしました。", - "recent": "注釈: 最新のメールを1件のみ取得します。" + "recent": "注釈: 最新のメールを1件のみ取得します。", + "mta": "注釈: 1024未満のポートを使用するには、昇格された(root)特権が必要です。ヘルプサイドバーを参照してください。" }, "status": { "messagesent": "メッセージを送信しました: __response__", @@ -34,6 +35,7 @@ "inboxzero": "受信トレイにメールがありません", "sending": "送信中", "sendfail": "送信が失敗しました", + "parseerror": "メッセージのパースに失敗", "connecterror": "接続エラー" }, "errors": { @@ -43,6 +45,7 @@ "nosmtptransport": "SMTP転送が設定されていません。「情報」タブを参照してください", "nopayload": "送信するペイロードがありません", "fetchfail": "フォルダの受信に失敗しました: __folder__", + "parsefail": "メッセージのパースに失敗", "messageerror": "メッセージ受信エラー: __error__" } } diff --git a/social/email/package.json b/social/email/package.json index 45d3bcbd..ccfc19d5 100644 --- a/social/email/package.json +++ b/social/email/package.json @@ -1,12 +1,13 @@ { "name": "node-red-node-email", - "version": "1.7.11", - "description": "Node-RED nodes to send and receive simple emails", + "version": "1.8.0", + "description": "Node-RED nodes to send and receive simple emails.", "dependencies": { "imap": "^0.8.19", - "mailparser": "^2.8.1", - "nodemailer": "^6.4.11", - "poplib": "^0.1.7" + "poplib": "^0.1.7", + "mailparser": "^3.0.0", + "nodemailer": "~6.4.10", + "smtp-server": "^3.7.0" }, "repository": { "type": "git", @@ -18,7 +19,8 @@ "email", "gmail", "imap", - "pop" + "pop", + "mta" ], "node-red": { "nodes": { From ef1ebe7b449388ee4dd004fd1acde0ac2c3a7c31 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Oct 2020 14:32:09 +0100 Subject: [PATCH 31/37] Fix email tests and add one for MTA node --- social/email/61-email.js | 4 +-- test/social/email/61-email_spec.js | 56 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/social/email/61-email.js b/social/email/61-email.js index f046a635..15371c12 100644 --- a/social/email/61-email.js +++ b/social/email/61-email.js @@ -518,7 +518,7 @@ module.exports = function(RED) { }); - function emailMtaNode(n) { + function EmailMtaNode(n) { RED.nodes.createNode(this,n); this.port = n.port; var node = this; @@ -573,6 +573,6 @@ module.exports = function(RED) { node.mta.close(); }); } - RED.nodes.registerType("e-mail mta",emailMtaNode); + RED.nodes.registerType("e-mail mta",EmailMtaNode); }; diff --git a/test/social/email/61-email_spec.js b/test/social/email/61-email_spec.js index 03caa0a6..5d4f9a56 100644 --- a/test/social/email/61-email_spec.js +++ b/test/social/email/61-email_spec.js @@ -43,6 +43,7 @@ describe('email Node', function () { id: "n1", type: "e-mail", name: "emailout", + port: 1025, wires: [ [] ] @@ -188,4 +189,59 @@ describe('email Node', function () { }) }); + + describe('email mta', function () { + + it('should catch an email send to localhost 1025', function (done) { + var flow = [{ + id: "n1", + type: "e-mail mta", + name: "emailmta", + port: 1025, + wires: [ + ["n2"] + ] + }, + { + id:"n2", + type:"helper" + }, + { + id: "n3", + type: "e-mail", + dname: "testout", + server: "localhost", + secure: false, + port: 1025, + wires: [ + [] + ] + }]; + helper.load(emailNode, flow, function () { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + n1.should.have.property('port', 1025); + + n2.on("input", function(msg) { + //console.log("GOT",msg); + msg.should.have.a.property("payload",'Hello World\n'); + msg.should.have.a.property("topic","Test"); + msg.should.have.a.property("from",'foo@example.com'); + msg.should.have.a.property("to",'bar@example.com'); + msg.should.have.a.property("attachments"); + msg.should.have.a.property("header"); + done(); + }); + + n3.emit("input", { + payload: "Hello World", + topic: "Test", + from: "foo@example.com", + to: "bar@example.com" + }); + //done(); + }); + }); + }); }); From ffa5761756518b2787069216bca4f52730011f4a Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Oct 2020 14:44:09 +0100 Subject: [PATCH 32/37] add smtp-package for tests --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d4ae2196..08c1e6af 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "grunt-lint-inline": "^1.0.0", "grunt-simple-mocha": "^0.4.1", "imap": "^0.8.19", - "mailparser": "^2.8.1", + "mailparser": "^3.0.0", "markdown-it": "^11.0.0", "mocha": "~6.2.3", "msgpack-lite": "^0.1.26", @@ -48,13 +48,14 @@ "ngeohash": "^0.6.3", "node-red": "^1.1.3", "node-red-node-test-helper": "~0.2.5", - "nodemailer": "^6.4.11", + "nodemailer": "^6.4.10", "poplib": "^0.1.7", "proxyquire": "^2.1.3", "pushbullet": "^2.4.0", "sentiment": "^2.1.0", "should": "^13.2.3", "sinon": "~7.5.0", + "smtp-server": "^3.7.0", "supertest": "^4.0.2", "when": "^3.7.8" }, From 6f351943cbd442a80add12f3892cd17f516eb614 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Oct 2020 21:33:32 +0100 Subject: [PATCH 33/37] email node - remove promises for node8 and fixup tests --- social/email/61-email.js | 58 +++++++++++++++--------------- social/email/package.json | 2 +- test/social/email/61-email_spec.js | 19 ++++++---- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/social/email/61-email.js b/social/email/61-email.js index 15371c12..272c7390 100644 --- a/social/email/61-email.js +++ b/social/email/61-email.js @@ -529,37 +529,35 @@ module.exports = function(RED) { disabledCommands: ['AUTH', 'STARTTLS'], onData: function (stream, session, callback) { - simpleParser(stream, { - skipTextToHtml: true, - skipTextLinks: true - }) - .then(parsed => { - node.status({fill:"green", shape:"dot", text:""}); - var msg = {} - msg.payload = parsed.text; - msg.topic = parsed.subject; - msg.date = parsed.date; - msg.header = {}; - parsed.headers.forEach((v, k) => {msg.header[k] = v;}); - if (parsed.html) { msg.html = parsed.html; } - if (parsed.to) { - if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; } - else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; } + simpleParser(stream, { skipTextToHtml:true, skipTextLinks:true }, (err, parsed) => { + if (err) { node.error(RED._("email.errors.parsefail"),err); } + else { + node.status({fill:"green", shape:"dot", text:""}); + var msg = {} + msg.payload = parsed.text; + msg.topic = parsed.subject; + msg.date = parsed.date; + msg.header = {}; + parsed.headers.forEach((v, k) => {msg.header[k] = v;}); + if (parsed.html) { msg.html = parsed.html; } + if (parsed.to) { + if (typeof(parsed.to) === "string" && parsed.to.length > 0) { msg.to = parsed.to; } + else if (parsed.to.hasOwnProperty("text") && parsed.to.text.length > 0) { msg.to = parsed.to.text; } + } + if (parsed.cc) { + if (typeof(parsed.cc) === "string" && parsed.cc.length > 0) { msg.cc = parsed.cc; } + else if (parsed.cc.hasOwnProperty("text") && parsed.cc.text.length > 0) { msg.cc = parsed.cc.text; } + } + if (parsed.cc && parsed.cc.length > 0) { msg.cc = parsed.cc; } + if (parsed.bcc && parsed.bcc.length > 0) { msg.bcc = parsed.bcc; } + if (parsed.from && parsed.from.value && parsed.from.value.length > 0) { msg.from = parsed.from.value[0].address; } + if (parsed.attachments) { msg.attachments = parsed.attachments; } + else { msg.attachments = []; } + node.send(msg); // Propagate the message down the flow + setTimeout(function() { node.status({})}, 500); } - if (parsed.cc) { - if (typeof(parsed.cc) === "string" && parsed.cc.length > 0) { msg.cc = parsed.cc; } - else if (parsed.cc.hasOwnProperty("text") && parsed.cc.text.length > 0) { msg.cc = parsed.cc.text; } - } - if (parsed.cc && parsed.cc.length > 0) { msg.cc = parsed.cc; } - if (parsed.bcc && parsed.bcc.length > 0) { msg.bcc = parsed.bcc; } - if (parsed.from && parsed.from.value && parsed.from.value.length > 0) { msg.from = parsed.from.value[0].address; } - if (parsed.attachments) { msg.attachments = parsed.attachments; } - else { msg.attachments = []; } - node.send(msg); // Propagate the message down the flow - setTimeout(function() { node.status({})}, 500); - }) - .catch(err => { node.error(RED._("email.errors.parsefail"),err); }) - .finally(callback); + callback(); + }); } }); diff --git a/social/email/package.json b/social/email/package.json index ccfc19d5..d9f4f93c 100644 --- a/social/email/package.json +++ b/social/email/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-email", - "version": "1.8.0", + "version": "1.8.1", "description": "Node-RED nodes to send and receive simple emails.", "dependencies": { "imap": "^0.8.19", diff --git a/test/social/email/61-email_spec.js b/test/social/email/61-email_spec.js index 5d4f9a56..dd2316d9 100644 --- a/test/social/email/61-email_spec.js +++ b/test/social/email/61-email_spec.js @@ -225,13 +225,18 @@ describe('email Node', function () { n2.on("input", function(msg) { //console.log("GOT",msg); - msg.should.have.a.property("payload",'Hello World\n'); - msg.should.have.a.property("topic","Test"); - msg.should.have.a.property("from",'foo@example.com'); - msg.should.have.a.property("to",'bar@example.com'); - msg.should.have.a.property("attachments"); - msg.should.have.a.property("header"); - done(); + try { + msg.should.have.a.property("payload",'Hello World\n'); + msg.should.have.a.property("topic","Test"); + msg.should.have.a.property("from",'foo@example.com'); + msg.should.have.a.property("to",'bar@example.com'); + msg.should.have.a.property("attachments"); + msg.should.have.a.property("header"); + done(); + } + catch(e) { + done(e) + } }); n3.emit("input", { From 43f7326c12fcd57b3e557a93e466343045e5fc32 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Oct 2020 21:33:50 +0100 Subject: [PATCH 34/37] try nyc instead of instanbul --- .travis.yml | 6 ++++-- coverall | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9e9ae945..2350eb13 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,9 @@ before_install: before_script: # Remove the './node_modules/.bin:' entry, see https://github.com/travis-ci/travis-ci/issues/8813 - export PATH=`echo ${PATH} | sed -re 's,(^|:)(./)?node_modules/.bin($|:),\1,'` - - npm install -g istanbul grunt-cli coveralls + - npm install -g nyc grunt-cli coveralls - npm install node-red script: - - istanbul cover grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage + # - istanbul cover grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage + # - nyc --check-coverage --reporter=text --reporter=lcovonly --reporter=text grunt && rm -rf coverage + - nyc grunt && rm -rf coverage .nyc_output \ No newline at end of file diff --git a/coverall b/coverall index c4a004f8..2d3f6e21 100755 --- a/coverall +++ b/coverall @@ -1,3 +1,3 @@ # check coverage of tests... and browse report -istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report html +nyc --check-coverage --reporter=html grunt /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome coverage/index.html From 789abedd96bd005351c7e80a675da47b22404958 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Oct 2020 22:30:36 +0100 Subject: [PATCH 35/37] Update .travis.yml --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2350eb13..627f909f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,5 +27,6 @@ before_script: - npm install node-red script: # - istanbul cover grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage - # - nyc --check-coverage --reporter=text --reporter=lcovonly --reporter=text grunt && rm -rf coverage - - nyc grunt && rm -rf coverage .nyc_output \ No newline at end of file + # - nyc --check-coverage --reporter=text --reporter=lcovonly --reporter=html grunt + # - nyc grunt && rm -rf coverage .nyc_output + - nyc grunt && nyc report --reporter=text-lcov | coveralls && rm -rf coverage .nyc_output \ No newline at end of file From ae75e7f7a8b4a3a2d39222df6178a2d83ccf612f Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Oct 2020 22:38:31 +0100 Subject: [PATCH 36/37] Update .travis.yml --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 627f909f..10bd5ea7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,10 +8,10 @@ matrix: - node_js: 12 - node_js: 10 - node_js: 8 - - python: 2.7 - language: python - before_script: pip install flake8 - script: flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics + # - python: 2.7 + # language: python + # before_script: pip install flake8 + # script: flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics - python: 3.7 language: python dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) @@ -29,4 +29,4 @@ script: # - istanbul cover grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage # - nyc --check-coverage --reporter=text --reporter=lcovonly --reporter=html grunt # - nyc grunt && rm -rf coverage .nyc_output - - nyc grunt && nyc report --reporter=text-lcov | coveralls && rm -rf coverage .nyc_output \ No newline at end of file + - nyc grunt && nyc report --reporter=text-lcov | coverallss From eddeef9630fee3dcc1d3eddd02b654b43696d3ce Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 19 Oct 2020 22:42:25 +0100 Subject: [PATCH 37/37] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 10bd5ea7..e7d7f89c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,4 +29,4 @@ script: # - istanbul cover grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage # - nyc --check-coverage --reporter=text --reporter=lcovonly --reporter=html grunt # - nyc grunt && rm -rf coverage .nyc_output - - nyc grunt && nyc report --reporter=text-lcov | coverallss + - nyc grunt && nyc report --reporter=text-lcov | coveralls