1
0
mirror of https://github.com/node-red/node-red-nodes.git synced 2023-10-10 13:36:58 +02:00
node-red-nodes/social/xmpp/92-xmpp.js

859 lines
38 KiB
JavaScript
Raw Permalink Normal View History

2013-10-30 21:38:23 +01:00
module.exports = function(RED) {
"use strict";
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.nickname = n.nickname;
this.jid = n.user;
2021-06-09 23:28:34 +02:00
if (this.jid.match(/\@/)) {
this.username = n.user.split('@')[0];
}
else {
this.username = n.user;
this.jid = n.user+'@'+n.server;
}
// 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
2020-10-24 16:02:45 +02:00
if ("undefined" === typeof n.server || n.server === "") {
this.server = n.user.split('@')[1];
}
else {
this.server = n.server;
}
2020-10-24 16:02:45 +02:00
if ("undefined" === typeof n.port || n.port === "") {
this.port = 5222;
}
else {
2020-10-24 16:02:45 +02:00
this.port = parseInt(n.port);
}
// The password is obfuscated and stored in a separate location
var credentials = this.credentials;
if (credentials) {
this.password = credentials.password;
}
// The basic xmpp client object, this will be referred to as "xmpp" in the nodes.
// note we're not actually connecting here.
2020-10-24 16:02:45 +02:00
var proto = "xmpp";
if (this.port === 5223) {
proto = "xmpps";
}
if (RED.settings.verbose || LOGITALL) {
this.log("Setting up connection xmpp: {service: "+proto+"://"+this.server+":"+this.port+", username: "+this.username+", password: "+this.password+"}");
}
this.client = client({
2020-10-24 16:02:45 +02:00
service: proto+'://' + this.server + ':' + this.port,
username: this.username,
2021-05-06 15:48:43 +02:00
password: this.password,
timeout: 60000
});
2021-05-06 15:48:43 +02:00
this.client.timeout = 60000;
// 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 = {};
// store the chatrooms (MUC) that we've joined (sent "presence" XML to) already
this.MUCs = {};
// helper variable, because "this" changes definition inside a callback
var that = this;
// function for a node to tell us it has us as config
this.register = function(xmppThat) {
2020-10-24 16:02:45 +02:00
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 nodes that takes care of that.
// 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) {
2020-10-24 16:02:45 +02:00
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 {
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
2020-10-24 16:02:45 +02:00
this.used = function(xmppThat) {
if (RED.settings.verbose || LOGITALL) {that.log(xmppThat.id+" sent a message to the xmpp server"); }
that.lastUsed = xmppThat;
}
// 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) => {
2021-02-28 18:32:49 +01:00
//console.log("STANZA",stanza.toString())
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');
2020-10-24 16:02:45 +02:00
if (err) {
var textObj = err.getChild('text');
var text = "error";
if (typeof textObj !== "undefined") {
text = textObj.getText();
}
else {
textObj = err.getAttr('code');
if (typeof textObj !== "undefined") {
text = textObj;
2020-10-24 16:02:45 +02:00
}
}
if (RED.settings.verbose || LOGITALL) {that.log("Culprit: "+that.lastUsed.id); }
if (typeof that.lastUsed !== "undefined") {
that.lastUsed.status({fill:"yellow",shape:"dot",text:"warning. "+text});
that.lastUsed.warn("Warning. "+text);
if (that.lastUsed.join) {
// it was trying to MUC things up
clearMUC(that);
}
}
2020-10-24 16:02:45 +02:00
if (RED.settings.verbose || LOGITALL) {
that.log("We did wrong: Error "+text);
2020-10-24 16:02:45 +02:00
that.log(stanza);
}
// maybe throw the message or summit
//that.error(text);
}
}
}
2020-10-24 16:02:45 +02:00
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',
2020-10-24 16:02:45 +02:00
{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");
}
}
2021-02-28 18:32:49 +01:00
if (stanza.attrs.to && stanza.attrs.to.indexOf(that.jid) !== -1) {
var _x = stanza.getChild("x")
if (_x !== undefined) {
var _stat = _x.getChildren("status");
for (var i = 0; i<_stat.length; i++) {
if (_stat[i].attrs.code == 201) {
if (RED.settings.verbose || LOGITALL) {that.log("created new room"); }
var stanza = xml('iq',
{type:'set', id:that.id, from:that.jid, to:stanza.attrs.from.split('/')[0]},
xml('query', 'http://jabber.org/protocol/muc#owner',
xml('x', {xmlns:'jabber:x:data', type:'submit'})
)
);
that.client.send(stanza);
}
}
}
}
}
2020-10-24 16:02:45 +02:00
else if (stanza.is('iq')) {
if (RED.settings.verbose || LOGITALL) {that.log("got an iq query"); }
if (stanza.attrs.type === 'error') {
if (RED.settings.verbose || LOGITALL) {that.log("oh noes, it's an error"); }
2022-01-17 10:54:38 +01:00
if (that && that.hasOwnProperty("lastUsed") && that.lastUsed.hasOwnProperty("id") && stanza.attrs.id === that.lastUsed.id) {
2020-10-24 16:02:45 +02:00
that.lastUsed.status({fill:"red", shape:"ring", text:stanza.getChild('error')});
that.lastUsed.warn(stanza.getChild('error'));
}
}
}
});
2020-10-24 16:02:45 +02:00
// 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;
await that.client.send(xml('presence'));
// await that.client.send(xml('presence', {type: 'available'},xml('status', {}, 'available')));
2020-10-24 16:02:45 +02:00
if (RED.settings.verbose || LOGITALL) {that.log('connected as '+that.username+' to ' +that.server+':'+that.port); }
});
// if the connection has gone away, not sure why!
this.client.on('offline', () => {
that.connected = false;
2020-10-24 16:02:45 +02:00
if (RED.settings.verbose || LOGITALL) {that.log('connection closed'); }
});
// gets called when the node is destroyed, e.g. if N-R is being stopped.
this.on("close", async done => {
const rooms = Object.keys(this.MUCs)
for (const room of rooms) {
await that.client.send(xml('presence', {to:room, type:'unavailable'}));
}
2021-02-26 17:05:03 +01:00
if (that.connected) {
await that.client.send(xml('presence', {type:'unavailable'}));
try{
if (RED.settings.verbose || LOGITALL) {
that.log("Calling stop() after close, status is "+that.client.status);
}
await that.client.stop().then(that.log("XMPP client stopped")).catch(error=>{that.warn("Got an error whilst closing xmpp session: "+error)});
}
catch(e) {
that.warn(e);
2020-10-24 16:02:45 +02:00
}
}
done();
});
}
RED.nodes.registerType("xmpp-server",XMPPServerNode,{
credentials: {
password: {type: "password"}
}
});
function getItems(thing,id,xmpp) {
// Now try to get a list of all items/conference rooms available on this server
var stanza = xml('iq',
{type:'get', id:id, to:thing},
xml('query', 'http://jabber.org/protocol/disco#items')
);
xmpp.send(stanza);
}
function joinMUC(node, xmpp, name) {
// 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.
// Yes, there's a race condition, but it's not a huge problem to send two messages
// so we don't care.
2021-02-28 18:32:49 +01:00
var mu = name.split("/")[0];
if (mu in node.serverConfig.MUCs) {
if (RED.settings.verbose || LOGITALL) { node.log("already joined MUC "+name); }
}
else {
var stanza = xml('presence',
{"to":name},
xml("x",'http://jabber.org/protocol/muc',
xml("history", {maxstanzas:0, seconds:1}) // We don't want any history
)
);
if (node.hasOwnProperty("credentials") && node.credentials.hasOwnProperty("password")) {
stanza = xml('presence',
{"to":name},
xml("x",'http://jabber.org/protocol/muc',
xml("history", {maxstanzas:0, seconds:1}), // We don't want any history
xml("password", {}, node.credentials.password) // Add the password
)
);
}
node.serverConfig.used(node);
2021-02-28 18:32:49 +01:00
node.serverConfig.MUCs[mu] = "joined";
if (RED.settings.verbose || LOGITALL) { node.log("JOINED "+mu); }
xmpp.send(stanza);
}
}
function clearMUC(config) {
//something has happened, so clear out our presence indicators
if (RED.settings.verbose || LOGITALL) {
config.log("cleared all MUC membership");
}
config.MUCs = {};
}
// separated out since we want the same functionality from both in and out nodes
function errorHandler(node, err){
if (!node.quiet) {
node.quiet = true;
// if the error has a "stanza" then we've probably done something wrong and the
// server is unhappy with us
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"});
}
// The error might be a string
else if (err == "TimeoutError") {
// OK, this happens with OpenFire, suppress it, but invalidate MUC membership as it will need to be re-established.
clearMUC(node.serverConfig);
node.status({fill:"grey",shape:"dot",text:"TimeoutError"});
node.log("Timed out! ",err);
// node.status({fill:"red",shape:"ring",text:"XMPP timeout"});
}
else if (err === "XMPP authentication failure") {
node.error(err,err);
node.status({fill:"red",shape:"ring",text:"XMPP authentication failure"});
}
// or it might have a name that tells us what's wrong
else if (err.name === "SASLError") {
node.error("Authorization error! "+err.condition,err);
node.status({fill:"red",shape:"ring",text:"XMPP authorization failure"});
}
// or it might have the errno set.
else if (err.errno === "ETIMEDOUT") {
node.error("Timeout connecting to server",err);
node.status({fill:"red",shape:"ring",text:"timeout"});
}
else if (err.errno === "ENOTFOUND") {
node.error("Server doesn't exist "+node.serverConfig.server,err);
node.status({fill:"red",shape:"ring",text:"bad address"});
}
// nothing we've seen before!
else {
node.error("Unknown error: "+err,err);
node.status({fill:"red",shape:"ring",text:"error"});
}
}
}
function XmppInNode(n) {
RED.nodes.createNode(this,n);
this.server = n.server;
this.serverConfig = RED.nodes.getNode(this.server);
this.nick = this.serverConfig.nickname || this.serverConfig.username.split("@")[0];
this.join = n.join || false;
this.sendAll = n.sendObject;
2020-10-24 16:02:45 +02:00
// Yes, it's called "from", don't ask me why; I don't know why
2021-02-23 13:45:55 +01:00
// (because it's where you are asking to get messages from...)
this.from = ((n.to || "").split(':')).map(s => s.trim());
this.quiet = false;
2021-02-28 18:32:49 +01:00
this.subject = {};
// MUC == Multi-User-Chat == chatroom
//this.muc = this.join && (this.from !== "")
var node = this;
var joinrooms = function() {
if (node.from[0] === "") {
// try to get list of all rooms and join them all.
getItems(node.serverConfig.server, node.serverConfig.id, xmpp);
}
else {
// if we want to use a chatroom, we need to tell the server we want to join it
for (var i=0; i<node.from.length; i++) {
joinMUC(node, xmpp, node.from[i]+'/'+node.nick);
}
}
}
var xmpp = this.serverConfig.client;
2020-10-24 16:02:45 +02:00
/* 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
*/
// if we're already connected, then do the actions now, otherwise register a callback
// if (xmpp.status === "online") {
// node.status({fill:"green",shape:"dot",text:"connected"});
// if (node.muc) {
// joinMUC(node, xmpp, node.from+'/'+node.nick);
// }
// }
// sod it, register it anyway, that way things will work better on a reconnect:
xmpp.on('online', async address => {
node.quiet = false;
node.status({fill:"green",shape:"dot",text:"connected"});
if (node.join) {
node.jointick = setInterval(function() { joinrooms(); }, 60000);
joinrooms();
}
});
xmpp.on('connecting', async address => {
if (!node.quiet) {
node.status({fill:"grey",shape:"dot",text:"connecting"});
}
});
xmpp.on('connect', async address => {
node.status({fill:"grey",shape:"dot",text:"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:"ring",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
2020-10-24 16:02:45 +02:00
// Should we listen on other's status (chatstate) or a chatroom state (groupbuddy)?
xmpp.on('error', err => {
2020-10-24 16:02:45 +02:00
if (RED.settings.verbose || LOGITALL) { node.log("XMPP Error: "+err); }
errorHandler(node, err);
});
// Meat of it, a stanza object contains chat messages (and other things)
xmpp.on('stanza', async (stanza) => {
2021-02-23 23:15:57 +01:00
if (RED.settings.verbose || LOGITALL) { node.log(stanza); }
if (stanza.is('message')) {
2021-02-28 18:32:49 +01:00
var subj = stanza.getChild("subject");
if (subj) {
subj = subj.getText();
if (subj.trim() !== "") { node.subject[stanza.attrs.from.split('/')[0]] = subj; }
}
if (stanza.attrs.type == 'chat') {
var body = stanza.getChild('body');
if (body) {
2021-02-28 18:32:49 +01:00
var msg = { payload:body.getText(), subject:node.subject[stanza.attrs.from.split('/')[0]] };
var ids = stanza.attrs.from.split('/');
2022-04-01 23:51:40 +02:00
if (ids.length > 1 && ids[1].length !== 36) {
msg.topic = stanza.attrs.from
}
else { msg.topic = ids[0]; }
2021-02-23 23:15:57 +01:00
// if (RED.settings.verbose || LOGITALL) { node.log("Received a message from "+stanza.attrs.from); }
if (!node.join && ((node.from[0] === "") || (node.from.includes(stanza.attrs.from.split('/')[0])) || (node.from.includes(stanza.attrs.from.split('/')[1])) )) {
node.send([msg,null]);
}
}
}
2020-10-24 16:02:45 +02:00
else if (stanza.attrs.type == 'groupchat') {
const parts = stanza.attrs.from.split("/");
var conference = parts[0];
var from = parts[1];
2021-02-28 18:32:49 +01:00
var msg = { topic:from, room:conference, subject:node.subject[stanza.attrs.from.split('/')[0]] };
var body = stanza.getChild('body');
if (typeof body !== "undefined") {
2021-02-28 18:32:49 +01:00
msg.payload = body.getText();
//if (from && stanza.attrs.from != node.nick && from != node.nick) {
if (from && node.join && (node.from[0] === "" || node.from.includes(conference))) {
node.send([msg,null]);
}
}
//}
}
}
2020-10-24 16:02:45 +02:00
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.
}
else {
if (stanza.attrs.type === 'error') {
var error = stanza.getChild('error');
if (error.attrs.code) {
2021-03-12 10:36:20 +01:00
var reas = "";
try {
2021-03-12 10:36:20 +01:00
reas = error.toString().split('><')[1].split(" xml")[0].trim();
if (reas == "registration-required") { reas = "membership-required"; }
}
2021-06-09 23:28:34 +02:00
catch(e) { }
if (error.attrs.code !== '404' && (error.attrs.code !== '400' && error.attrs.type !== 'wait')) {
var msg = {
topic:stanza.attrs.from,
payload: {
code:error.attrs.code,
status:"error",
reason:reas,
name:node.serverConfig.MUCs[stanza.attrs.from.split('/')[0]]
}
};
node.send([null,msg]);
node.status({fill:"red",shape:"ring",text:"error : "+error.attrs.code+", "+error.attrs.type+", "+reas});
node.error(error.attrs.type+" error. "+error.attrs.code+" "+reas,msg);
}
else {
// ignore 404 error
}
}
2021-06-10 00:28:51 +02:00
return;
}
var state = stanza.getChild('show');
if (state) { state = state.getText(); }
else { state = "available"; }
var statusText="";
2020-10-24 16:02:45 +02:00
if (stanza.attrs.type === 'unavailable') {
// the user might not exist, but the server doesn't tell us that!
statusText = "offline";
state = "offline";
}
2021-06-10 00:28:51 +02:00
else {
statusText = "online";
state = "online";
}
var status = stanza.getChild('status');
2021-06-10 00:28:51 +02:00
if (status !== undefined) {
statusText = status.getText() || "online";
}
2021-06-10 00:28:51 +02:00
if (statusText !== "" && (stanza.attrs.from !== stanza.attrs.to)) {
var from = stanza.attrs.from;
2021-02-28 18:32:49 +01:00
var msg = {
topic:from,
payload: {
presence:state,
status:statusText,
name:stanza.attrs.from.split('/')[1]
2021-02-28 18:32:49 +01:00
}
};
node.send([null,msg]);
}
else {
if (RED.settings.verbose || LOGITALL) {
node.log("not propagating blank status");
node.log(stanza);
}
}
}
}
else if (stanza.attrs.type === 'result') {
// AM To-Do check for 'bind' result with our current jid
var query = stanza.getChild('query');
2021-02-23 23:15:57 +01:00
if (RED.settings.verbose || LOGITALL) { this.log("result!"); }
if (RED.settings.verbose || LOGITALL) { this.log(query); }
// handle query for list of rooms available
if (query && query.attrs.hasOwnProperty("xmlns") && query.attrs["xmlns"] === "http://jabber.org/protocol/disco#items") {
var _items = stanza.getChild('query').getChildren('item');
for (var i = 0; i<_items.length; i++) {
if ( _items[i].attrs.jid.indexOf('@') === -1 ) {
// if no @ in jid then it's probably the root or the room server so ask again
getItems(_items[i].attrs.jid,this.serverConfig.jid,xmpp);
}
else {
var name = _items[i].attrs.jid+'/'+node.serverConfig.username;
if (!(name in node.serverConfig.MUCs)) {
if (RED.settings.verbose || LOGITALL) { node.log("Need to Join room:"+name); }
joinMUC(node, xmpp, name);
2021-02-28 18:32:49 +01:00
node.serverConfig.MUCs[name.split('/')[0]] = _items[i].attrs.name.split('/')[0];
}
else {
if (RED.settings.verbose || LOGITALL) { node.log("Already joined:"+name); }
}
}
}
}
2021-02-23 23:15:57 +01:00
if (query && query.attrs.hasOwnProperty("xmlns") && query.attrs["xmlns"] === "http://jabber.org/protocol/disco#info") {
var fe = [];
var _items = stanza.getChild('query').getChildren('feature');
for (var i = 0; i<_items.length; i++) {
fe.push(_items[i].attrs);
}
var id = []
var _idents = stanza.getChild('query').getChildren('identity');
for (var i = 0; i<_idents.length; i++) {
id.push(_idents[i].attrs);
}
var from = stanza.attrs.from;
var msg = {topic:from, payload: { identity:id, features:fe} };
node.send([null,msg]);
}
}
});
// xmpp.on('subscribe', from => {
// xmpp.acceptSubscription(from);
// });
//register with config
this.serverConfig.register(this);
// Now actually make the connection
try {
2020-10-24 16:02:45 +02:00
if (xmpp.status === "online") {
node.status({fill:"green",shape:"dot",text:"connected"});
}
else {
node.status({fill:"grey",shape:"dot",text:"connecting"});
2020-10-24 16:02:45 +02:00
if (xmpp.status === "offline") {
if (RED.settings.verbose || LOGITALL) {
node.log("starting xmpp client");
}
xmpp.start().catch(error => {
node.warn("Got error on start: "+error);
node.warn("XMPP Status is now: "+xmpp.status)
});
}
}
2017-01-29 18:45:44 +01:00
}
catch(e) {
node.error("Bad xmpp configuration; service: "+xmpp.options.service+" jid: "+node.serverConfig.jid);
node.warn(e.stack);
node.status({fill:"red",shape:"ring",text:"disconnected"});
}
node.on("close", function(removed, done) {
if (node.jointick) { clearInterval(node.jointick); }
node.status({fill:"grey",shape:"ring",text:"disconnected"});
node.serverConfig.deregister(node, done);
});
}
RED.nodes.registerType("xmpp in",XmppInNode,{
credentials: {
password: {type: "password"}
}
});
function XmppOutNode(n) {
RED.nodes.createNode(this,n);
this.server = n.server;
this.serverConfig = RED.nodes.getNode(this.server);
this.nick = this.serverConfig.nickname || this.serverConfig.username.split("@")[0];
this.join = n.join || false;
this.sendAll = n.sendObject;
this.to = n.to || "";
this.quiet = false;
// MUC == Multi-User-Chat == chatroom
this.muc = this.join && (this.to !== "")
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
*/
// if we're already connected, then do the actions now, otherwise register a callback
// if (xmpp.status === "online") {
// node.status({fill:"green",shape:"dot",text:"connected"});
// if (node.muc){
// // if we want to use a chatroom, we need to tell the server we want to join it
// joinMUC(node, xmpp, node.from+'/'+node.nick);
// }
// }
// sod it, register it anyway, that way things will work better on a reconnect:
xmpp.on('online', function(data) {
node.quiet = false;
node.status({fill:"green",shape:"dot",text:"connected"});
if (node.muc) {
// if we want to use a chatroom, we need to tell the server we want to join it
joinMUC(node, xmpp, node.from+'/'+node.nick);
}
});
xmpp.on('connecting', async address => {
if (!node.quiet) {
node.status({fill:"grey",shape:"dot",text:"connecting"});
}
});
xmpp.on('connect', async address => {
node.status({fill:"grey",shape:"dot",text:"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 || LOGITALL) { node.log(err); }
errorHandler(node, err)
});
xmpp.on('stanza', async (stanza) => {
if (stanza.attrs.type === 'error') {
var error = stanza.getChild('error');
if (error.attrs.code) {
2021-03-12 10:36:20 +01:00
var reas = "";
try {
2021-03-12 10:36:20 +01:00
reas = error.toString().split('><')[1].split(" xml")[0].trim();
if (reas == "registration-required") { reas = "membership-required"; }
}
2021-03-12 10:36:20 +01:00
catch(e) {}
2021-06-09 23:28:34 +02:00
if (error.attrs.code !== '404' && (error.attrs.code !== '400' && error.attrs.type !== 'wait')) {
var msg = {
topic:stanza.attrs.from,
payload: {
code:error.attrs.code,
status:"error",
reason:reas,
name:node.serverConfig.MUCs[stanza.attrs.from.split('/')[0]]
}
};
node.status({fill:"red",shape:"ring",text:"error : "+error.attrs.code+", "+error.attrs.type+", "+reas});
node.error(error.attrs.type+" error. "+error.attrs.code+" "+reas,msg);
}
else {
// ignore 404 error
}
}
}
});
//register with config
this.serverConfig.register(this);
// Now actually make the connection
2020-10-24 16:02:45 +02:00
if (xmpp.status === "online") {
node.status({fill:"green",shape:"dot",text:"online"});
2017-01-29 18:45:44 +01:00
}
else {
node.status({fill:"grey",shape:"dot",text:"connecting"});
2020-10-24 16:02:45 +02:00
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:"error"});
});
}
}
// Let's get down to business and actually send a message
node.on("input", function(msg) {
2021-02-28 18:32:49 +01:00
var to = node.to || msg.topic || "";
if (msg.presence) {
if (['away', 'dnd', 'xa', 'chat'].indexOf(msg.presence) > -1 ) {
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); }
}
2020-10-24 16:02:45 +02:00
else if (msg.command) {
if (msg.command === "subscribe") {
var stanza = xml('presence', {type:'subscribe', to:msg.payload});
node.serverConfig.used(node);
xmpp.send(stanza);
}
2020-10-24 16:02:45 +02:00
else if (msg.command === "get") {
var stanza = xml('iq',
{type:'get', id:node.id, to:to},
2020-10-24 16:02:45 +02:00
xml('query', 'http://jabber.org/protocol/muc#admin',
xml('item', {affiliation:msg.payload})
)
);
2020-10-24 16:02:45 +02:00
node.serverConfig.used(node);
2021-02-23 23:15:57 +01:00
if (RED.settings.verbose || LOGITALL) { node.log("sending stanza "+stanza.toString()); }
xmpp.send(stanza);
}
else if (msg.command === "info") {
var stanza = xml('iq',
{type:'get', id:node.id, to:to},
xml('query', 'http://jabber.org/protocol/disco#info')
);
node.serverConfig.used(node);
if (RED.settings.verbose || LOGITALL) { node.log("sending stanza "+stanza.toString()); }
2020-10-24 16:02:45 +02:00
xmpp.send(stanza);
}
}
else {
if (to !== "") {
var message;
var type = "chat";
if (node.join) {
// we want to connect to groupchat / chatroom / MUC
type = "groupchat";
// joinMUC will do nothing if we're already joined
joinMUC(node, xmpp, to+'/'+node.nick);
}
if (msg.subject) {
var stanza = xml(
"message",
{ type:type, to:to, from:node.serverConfig.jid },
xml("subject", {}, msg.subject.toString())
);
node.serverConfig.used(node);
xmpp.send(stanza);
}
if (node.sendAll) {
message = xml(
"message",
{ type: type, to: to },
xml("body", {}, JSON.stringify(msg))
);
2017-01-29 18:45:44 +01:00
}
else if (msg.payload) {
if (typeof(msg.payload) === "object") {
message = xml(
"message",
{ type: type, to: to },
xml("body", {}, JSON.stringify(msg.payload))
);
}
else {
message = xml(
"message",
{ type: type, to: to },
xml("body", {}, msg.payload.toString())
);
}
}
if (message) {
node.serverConfig.used(node);
xmpp.send(message);
}
}
}
});
node.on("close", function(removed, done) {
2021-02-23 23:15:57 +01:00
if (RED.settings.verbose || LOGITALL) { node.log("Closing"); }
node.status({fill:"grey",shape:"ring",text:"disconnected"});
node.serverConfig.deregister(node, done);
});
}
RED.nodes.registerType("xmpp out",XmppOutNode,{
credentials: {
password: {type: "password"}
}
});
}