Handle XMPP presence with Node - node.js

I'm using the node-xmpp module to connect to a XMPP server and join a group chat. Connecting to the server, setting the presence, joining the room and reading out messages works so far. But I want to receive the userlist of the room too.
The XMPP protocol requires to send a presence stanza when the client enters the room (http://xmpp.org/extensions/xep-0045.html#enter-pres). But how can I now parse it in node?
My code currently looks like this:
var xmpp = require('node-xmpp');
// Create the XMPP Client
var cl = new xmpp.Client({
jid: jid,
password: password,
reconnect: true
});
// Do things when online
cl.on('online', function() {
util.log("We're online!");
// Set client's presence
cl.send(new xmpp.Element('presence', { type: 'available' }).c('show').t('chat'));
cl.send(new xmpp.Element('presence', { to: room_jid+'/'+room_nick }).c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', {seconds: 1}));
// Send keepalive
setInterval(function() {
cl.send(' ');
}, 30000);
cl.on('stanza', function(stanza) {
// always log error stanzas
if (stanza.attrs.type == 'error') {
util.log('[error] ' + stanza);
return;
}
// ignore everything that isn't a room message
if (!stanza.is('message') || !stanza.attrs.type == 'chat') {
return;
}
var body = stanza.getChild('body');
// message without body is probably a topic change
if (!body) {
return;
}
// Extract username
var from, room, _ref;
_ref = stanza.attrs.from.split('/'), room = _ref[0], from = _ref[1];
var message = body.getText();
// Log topics and messages to the console
if(!from) {
util.log('Topic: ' + message);
} else {
util.log('[' + from + ']: ' + message);
}
});
});
I already tried triggering presence by using
if(stanza.is('presence')) {}
within the cl.on('stanza') part but it doesn't work.

UPDATE: I'm describing a new method now which doesn't require the client to send requests.
Background: When the client joins a group chat, the server returns presence stanzas which contain information about the connected users to the group chat.
cl.on('stanza', function(stanza) {
// always log error stanzas
if (stanza.attrs.type == 'error') {
util.log('[error] ' + stanza);
return;
}
if(stanza.is('presence')){
// We are only interested in stanzas with <x> in the payload or it will throw some errors
if(stanza.getChild('x') !== undefined) {
// Deciding what to do based on the xmlns attribute
var _presXmlns = stanza.getChild('x').attrs.xmlns;
switch(_presXmlns) {
// If someone is joining or leaving
case 'http://jabber.org/protocol/muc#user':
// Get the role of joiner/leaver
_presRole = stanza.getChild('x').getChild('item').attrs.role;
// Get the JID of joiner/leaver
_presJID = stanza.getChild('x').getChild('item').attrs.jid;
// Get the nick of joiner/leaver
_presNick = stanza.attrs.from.split('/')[1];
// If it's not none, this user must be joining or changing his nick
if(_presRole !== 'none') {
// We are now handling the data of joinging / nick changing users. I recommend to use an in-memory store like 'dirty' [https://github.com/felixge/node-dirty] to store information of the users currentliy in the group chat.
} else {
// We are now handling the data of leaving users
}
break;
}
return;
}
return;
}
OLD METHOD
I previously described a method how to query the server for current users in the group chat. By maintaining a store where all user traffic (joining, leaving, nick changing) is stored, this is no longer required. However you could still use it to make sure the data is consistent by issues like a presence stanza was not delivered to the client correctly. That's the reason it's still described below:
To request a list with users connected to the room, you need to perform the following actions:
First send a request to the server and ask for the user list:
cl.send(new xmpp.Element('iq', {from: jid, to: room_jid, type: 'get' }).c('query', { xmlns: 'http://jabber.org/protocol/disco#items' }));
then listen for iq-stanzas, parse them and populate an array with the data:
// Catching the requested user list
if(stanza.is('iq')){
// Fetching usernames from return data (data structure: http://xmpp.org/extensions/xep-0045.html#example-12)
var _items = stanza.getChild('query').getChildren('item');
var users = new Array();
for(var i = 0; i<_items.length; i++) {
// We are building an object here to add more data later
users[i] = new Object();
users[i]['name'] = _items[i].attrs.name;
}
console.log(util.inspect(users, {depth: null, colors: true}));
return;
}
This will provide you with a user list. To request unique JIDs you have to probe every user. To keep the list up to date, you should remove users when they leave and add + probe when they join.

Related

Recording Bot using Skype.bot.media

We are creating a bot that can join the team meeting and it can start the recording as it joins the team meeting. But we are getting this error(Expected not null
Parameter name: client). I am attaching the code below:
when debugger goes to CreateLocalMediaSession() session method then at that method it gives the error.(Expected not null
Parameter name: client)
public async Task<ICall> JoinCallAsync()
{
// A tracking id for logging purposes. Helps identify this call in logs.
var scenarioId = Guid.NewGuid();
var (chatInfo, meetingInfo) = JoinInfo.ParseJoinURL("https://teams.microsoft.com/l/meetup-join/19:meeting_YTI5NDQ2ODQtMmNlNy00YTBhLTg2NTMtYmZmOGIyMzdhMTgw#thread.v2/0?context=%7B%22Tid%22:%22204d6395-ea6c-4e64-abea-e04cd30845e2%22,%22Oid%22:%225a95f69b-70e2-40d3-8b9a-5810ffcc6ec9%22%7D");
var tenantId = (meetingInfo as OrganizerMeetingInfo).Organizer.GetPrimaryIdentity().GetTenantId();
var mediaSession = this.CreateLocalMediaSession(scenarioId);
var joinParams = new JoinMeetingParameters(chatInfo, meetingInfo, mediaSession)
{
TenantId = tenantId,
};
if (!string.IsNullOrWhiteSpace("bot"))
{
// Teams client does not allow changing of one's display name.
// If the display name is specified, we join as an anonymous (guest) user
// with the specified display name. This will put the bot in lobby
// unless lobby bypass is disabled.
joinParams.GuestIdentity = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = "bot",
};
}
var statefulCall = await this.Client.Calls().AddAsync(joinParams, scenarioId).ConfigureAwait(false);
statefulCall.GraphLogger.Info($"Call creation complete: {statefulCall.Id}");
return statefulCall;
}
Code for creating local media session:
private ILocalMediaSession CreateLocalMediaSession(Guid mediaSessionId = default)
{
try
{
// create media session object, this is needed to establish call connections
return this.Client.CreateMediaSession(
new AudioSocketSettings
{
StreamDirections = StreamDirection.Recvonly,
// Note! Currently, the only audio format supported when receiving unmixed audio is Pcm16K
SupportedAudioFormat = AudioFormat.Pcm16K,
ReceiveUnmixedMeetingAudio = true //get the extra buffers for the speakers
},
new VideoSocketSettings
{
StreamDirections = StreamDirection.Inactive
},
mediaSessionId: mediaSessionId);
}
catch (Exception e)
{
_logger.Log(System.Diagnostics.TraceLevel.Error, e.Message);
throw;
}
}
We are creating a bot that can join the team meeting and it can start the recording as it joins the team meeting. But we are getting this error(Expected not null
Parameter name: client). I am attaching the code below:
error facing:
enter image description here

Using conditional operators with QnAMaker - operators aren't routing correctly

I'm having difficulty figuring out what most likely is a simple issue, which relates to a 'if then else' problem in my code (NodeJS, Bot Framework v4).
I can't quite figure out why the relevant card isn't being shown depending on the number of semi-colons it finds in the response string from QnAMaker.
When testing with the Bot Framework emulator, it only returns one response type, whether that's plain text or one Rich Card no matter how many semi-colons are in the response.
I've tried to see if it's the length of the string it's having problems with by parsing the number value in the length statement. Didn't make a difference sadly. Notably if I use any other conditional operator such as '===' for example, it breaks the response completely.
const { ActivityTypes, CardFactory } = require('botbuilder');
const { WelcomeCard } = require('./dialogs/welcome');
// const { HeroCard } = require('./dialogs/welcome');
// const { VideoCard } = require('./dialogs/welcome');
class MyBot {
/**
*
* #param {TurnContext} on turn context object.
*/
constructor(qnaServices) {
this.qnaServices = qnaServices;
}
async onTurn(turnContext) {
if (turnContext.activity.type === ActivityTypes.Message) {
for (let i = 0; i < this.qnaServices.length; i++) {
// Perform a call to the QnA Maker service to retrieve matching Question and Answer pairs.
const qnaResults = await this.qnaServices[i].getAnswers(turnContext);
const qnaCard = qnaResults.includes(';');
// If an answer was received from QnA Maker, send the answer back to the user and exit.
if (qnaCard.toString().split(';').length < 3) {
await turnContext.sendActivity(qnaResults[0].answer);
await turnContext.sendActivity({
text: 'Hero Card',
attachments: [CardFactory.heroCard(HeroCard)]
});
} else if (qnaCard.toString().split(';').length > 3) {
await turnContext.sendActivity(qnaResults[0].answer);
await turnContext.sendActivity({
text: 'Video Card',
attachments: [CardFactory.videoCard(VideoCard)]
});
} else if (qnaCard.toString().split(';').length === 0) {
await turnContext.sendActivity(qnaResults[0].answer);
return;
}
}
// If no answers were returned from QnA Maker, reply with help.
await turnContext.sendActivity('No QnA Maker answers were found.');
} else {
await turnContext.sendActivity(`[${ turnContext.activity.type } event detected]`);
} if (turnContext.activity.type === ActivityTypes.ConversationUpdate) {
// Handle ConversationUpdate activity type, which is used to indicates new members add to
// the conversation.
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types
// Do we have any new members added to the conversation?
if (turnContext.activity.membersAdded.length !== 0) {
// Iterate over all new members added to the conversation
for (var idx in turnContext.activity.membersAdded) {
// Greet anyone that was not the target (recipient) of this message
// the 'bot' is the recipient for events from the channel,
// context.activity.membersAdded == context.activity.recipient.Id indicates the
// bot was added to the conversation.
if (turnContext.activity.membersAdded[idx].id !== turnContext.activity.recipient.id) {
// Welcome user.
// When activity type is "conversationUpdate" and the member joining the conversation is the bot
// we will send our Welcome Adaptive Card. This will only be sent once, when the Bot joins conversation
// To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
const welcomeCard = CardFactory.adaptiveCard(WelcomeCard);
await turnContext.sendActivity({ attachments: [welcomeCard] });
}
}
}
}
}
}
module.exports.MyBot = MyBot;
Ideally, what I'm hoping to see is if I ask a question which has 3 semi-colons in the response, it outputs a Hero Card. If it has more than 3, then a Video Card and if it doesn't have either, a text response.
I'm not a js specialist, but I'm quite confused by the following:
const qnaCard = qnaResults.includes(';');
In Javascript, includes is the following (source):
The includes() method determines whether an array includes a certain
value among its entries, returning true or false as appropriate.
So here your qnaCard is true or false. But it looks like you are trying to use it as if it was containing the text:
if (qnaCard.toString().split(';').length < 3) {
...
You have to work on the object containing the answer: qnaResults[0].answer.

How to get the client id?

I am using the ws lib and I want to make a private chat just like this: client A sends a message to client B.
There I have a ws.clients.forEach() method to broadcast to every client, but how can I get the client id of an individual one?
When you set up your chat system and userA wants to chat with userB, you have to have an identifier that userA users to tell the server they want to chat with userB. If the ws library doesn't provide an easy to use identifier for each connected user, then you need to assign one when the user connects to your server and then keep track of it for each connection in a way you can find the socket that has a given id. One simple way would be to just create a Map object where key is the ID and value is the socket. You can then lookup any socket by id.
For example, you could create a unique ID for each incoming socket connection and store it like this:
const uuid = require("uuid/v4");
const wss = new WebSocket.Server({ port: 8080 });
const idMap = new Map();
wss.on('connection', function connection(ws) {
// create uuid and add to the idMap
ws.uuid = uuid();
idMap.add(ws.uuid, ws);
ws.on('close', function() {
// remove from the map
idMap.delete(ws.uuid);
});
ws.on('message', function(info) {
try {
info = JSON.parse(info);
if (info.action = "send") {
let dest = idMap.get(info.targetId);
if (dest) {
dest.send(JSON.stringify({action: "message", sender: ws.uuid, data: info.message}));
}
}
} catch(e) {
console.log("Error processing incoming message", e);
}
});
});
Then, you could use that uuid value internally to identify which user someone wanted to connect to or send to.
In most real applications, you will also create some sort of login and username. A unique username could also be used as the id.

NodeJs Socket programming how to handle and manage Sockets? (Without Using socket.io) What is the efficient way?

After connection to the Server, every time Data coming from this connection (Socket.on('data',...)), Server fetches UserID from Data and check the ClientList (array of Socket objects), to see if Socket with this UserID exists in ClientList, if not : adds UserID as a property of Socket and then adds Socket object to Client list.
So when user with ID=1 want to send a message to user with ID = 2,
Server search for Socket with UserID = 2 in ClientList to find the right Socket and send user 1's message to the found Socket (user 2's socket).
I'm trying to accomplish this without using socket.io! That's what my employer made me to do! :))
Now my question is: am I doing this right? Is this efficient to check ClientList array (every time a connection send Data) to see if this UserID exists in ClientList? if not, then what is the right and efficient way? there is no problem with my code and it works. but what if there are thousands of connections?
Any Sample code , example or link would be appreciated. Thank you.
here is a pseudo code :
var net = require('net');
var Server = net.createServer();
var myAuth = require('./myAuth');
var ClientList = [];
Server.on('connection', function(Socket){
Socket.UserData = {}; // I want to add user data as a property to Socket
Socket.on('data', function (Data) {
var userID = myAuth.Authenticate_and_getUserID(Data);
if(userID != undefined){
var found = false;
ClientList.filter(function(item){
// check if Socket is in ClientList
if(item.UserData.ID == userID){
// client was connected before
found = true;
}
});
if(!found){
// this is a new connection, Add it to ClientList
Socket.UserData.ID = userID;
ClientList.push(Socket);
}
}
Socket.once('close', function(has_error){
var index = ClientList.indexOf(Socket);
if (index != -1){
ClientList.splice(index, 1);
console.log('Client closed (port=' + Socket.remotePort + ').');
}
});
});
UPDATE for clarification:
is this efficient to look into ClientList every time Data is coming to Server, to check for receiverID (presence of receiver) and to Update ClientList with current connection UserID if not exists?
how should I manage new connections(users) and store them in server for later use when number of users are thousands or millions! NOT 10 or 100. How socket.io is doing this?
later usages could be:
check to see if one specific user is online (have an object in ClientList)
send realtime message to a user if he/she is online
etc . . .
Actually I am doing this wrong!
Arrays in JavaScript are passed by reference! So there is no need to update ClientList every time a Socket send data.
Therefor the Code changes like Following:
var net = require('net');
var Server = net.createServer();
var myAuth = require('./myAuth');
var ClientList = [];
Server.on('connection', function(Socket){
ClientList.push(Socket);
Socket._isAuthorized = false;
// when socket send data for the first time
// it gets authenticated and next time it send data
// server does not authenticate it
Socket.on('data', function (Data) {
var userID = getUserID(Data);
if(Socket._isAuthorized != true){
if(authenticate(Socket)){
Socket._isAuthorized = true;
Socket._userID = userID;
return;
}
}
// do something with data...
}
Socket.once('close', function(has_error){
var index = ClientList.indexOf(Socket);
if (index != -1){
ClientList.splice(index, 1);
console.log('Client closed (port=' + Socket.remotePort + ').');
}
});
});
And its efficient!

node-gcm server receive error: MismatchSenderId

I have some problems with my project, i hope you will help me!
Description:
I use library from https://github.com/ToothlessGear/node-gcm to build server push notification for some devices.
I have configured API key from Google Cloud Message to SenderId.
My code:
var message2 = new gcm.Message();
**// ... or some given values
var message2 = new gcm.Message({
collapseKey: 'demo',
delayWhileIdle: true,
timeToLive: 3,
data: {
key1: 'message1',
key2: 'message2'
}
});
// Change the message data
// ... as key-value
message2.addData('key1','message1');
message2.addData('key2','message2');
// ... or as a data object (overwrites previous data object)
message2.addData({
key1: 'message1',
key2: 'message2'
});
// Change the message variables
message2.collapseKey = 'demo';
message2.delayWhileIdle = true;
message2.timeToLive = 3;
message2.dryRun = true;
// Set up the sender with you API key
var sender = new gcm.Sender('AIzaSyBzb1PsEBEiAjagslkEANqyxxxxxxxxx');
// Add the registration IDs of the devices you want to send to
var registrationIds = [];
registrationIds.push('APA91bHAbFJYpHsSN-uz1Hkh8XjBw4xU87VIz5hZYOtobVv6I2pUZM67pWUGbb5zcP2HxXrooYiIOhzhIV7TuRDbVOlRDak-.xxxxxxxxxx');
sender.send(message2, registrationIds, 4, function(err, result) {
console.log(result);
});**
Then, I ran and recevied message as picture!
According to the documentation, MismatchSenderId means that the sender (API key) does not have access to send notifications to the registration ID:
A registration ID is tied to a certain group of senders. When a client app registers for GCM, it must specify which senders are allowed to send messages. You should use one of those sender IDs when sending messages to the client app. If you switch to a different sender, the existing registration IDs won't work.
You should make sure that your registration ID allows notification from the sender.

Resources