Manage multiple Websocket client connection with node.js - javascript

I'm implementing a websocket client in node and my webhook's trying to handle multiple connections from a chatbot service. For example: a new user income, a websocket connection is established on an external chatbot service. The websocket URL is obtained through XMLHttpRequest in my code. And then I use this url to connect to the chatbot service using the ws object (new WebSocket('wssUrlObtainedThroughAjaxRequest','default-protocol')). So each user have a WebSocket. The question is that my code runs sequentially. So if two people sends message to my webhook node, things don't works properly because parallelism. Well, I'll post example code here to make it better to understand.
const express = require('express');
const PORT = process.env.PORT || 8002;
let WebSocket = require('ws');
let CONNECTIONS = new Map();
...
...
...
const app = express()
.use(bodyparser.urlencoded({extended: false}))
.use(bodyparser.json());
app.post('/', (req, res) => {
...
...
...
} else if (req.body.type === 'MESSAGE') {
let DM = req.body.space.name;
let msg = req.body.message.text;
ws = (CONNECTIONS.get(DM)!=null) ? CONNECTIONS.get(DM) : null;
if(ws==null || ws.readyState==3){
controlws.gerarURLWS();
ws = new WebSocket(controlws.urlws, 'talk-protocol');
CONNECTIONS.set(DM,ws);
}
// Executes on websocket openning
ws.onopen = function (event) {
console.log('Canal aberto;');
keepAliveWS();
ws.send(JSON.stringify(msgKoreAi(msg)));
}
if(ws.readyState==1)
ws.send(JSON.stringify(msgKoreAi(msg)));
ws.onmessage = async function (event) {
let resp = JSON.parse(event.data);
if (resp.type == "bot_response") {
text = resp.message[0].component.payload.text;
if(text==null){ // tem quick reply
//mount card hangouts response
let qreplies = resp.message[0].component.payload.payload.quick_replies;
card = '{"sections": [{"widgets": [{"buttons": [';
for(let i=0; i<qreplies.length; i++){
if(i!=qreplies.length-1)
card+='{"textButton": {"text": "'+qreplies[i].payload+'","onClick": {"action": {"actionMethodName": "'+qreplies[i].payload+'"}}}},';
else
card+='{"textButton": {"text": "'+qreplies[i].payload+'","onClick": {"action": {"actionMethodName": "'+qreplies[i].payload+'"}}}}';
}
card+=']}]}],"name": "respostas"}';
card = JSON.parse(card);
text = resp.message[0].component.payload.payload.text;
{
await assyncMessage(DM, text);
await assyncMessage(DM, card);
}
return;
}
//Send assync messages if synchronous was already sent
if(res.headersSent){
{
return await assyncMessage(DM, text);
}
}
else
return res.json({text});
}
}
return;
}
...
...
...
app.listen(PORT, () => {
console.log(`Server is running in port - ${PORT}`);
});

Related

Room not found/ accessible in io object

Currently trying to learn how socket.io works and to create a room based game, but having trouble to get clients to the same room after trying to move my code in a seperated file.
If I use the same code from game.js in my app.js file within io.on("connection")... i´m able to access the room and put players in the same room.
app.js
const express = require("express");
const cors = require("cors");
var http = require("http");
const game = require("./core/game/game");
const app = express();
const port = process.env.PORT || 4001;
const index = require("./routes/index");
const server = http.createServer(app);
const io = require("socket.io")(server, {
cors: {
origin: "*",
},
});
/
app.use(cors({ origin: "*" }));
app.use("/", index);
// Reduce the logging output of Socket.IO
/* io.set('log level',1); */
io.on("connection", (socket) => {
console.log("New client connected");
game.initGame(io, socket);
});
server.listen(port, () => {
console.log(`listening on *:${port}`);
});
game.js
var io;
var gameSocket;
/**
* This function is called by index.js to initialize a new game instance.
*
* #param sio The Socket.IO library
* #param socket The socket object for the connected client.
*/
exports.initGame = function (sio, socket) {
io = sio;
gameSocket = socket;
gameSocket.emit("connected", { message: "You are connected!" });
// Host Events
gameSocket.on("hostCreateNewGame", createRoom);
/* gameSocket.on("hostRoomFull", hostPrepareGame);
gameSocket.on("hostCountdownFinished", hostStartGame);
gameSocket.on("hostNextRound", hostNextRound); */
// Player Events
gameSocket.on("playerJoinGame", addPlayer);
/* gameSocket.on("playerAnswer", playerAnswer);
gameSocket.on("playerRestart", playerRestart); */
};
async function createRoom(data) {
console.log("Create Session");
console.log(data);
console.log(data.username);
var gameId = (Math.random() * 100000) | 0;
console.log(data.username);
console.log(gameId);
//gameSocket.username = username;
gameSocket.join(gameId);
}
async function addPlayer(data) {
console.log("JOIN Session");
console.log(data.gameId);
//console.log(socket.username);
const clients = await io.in(data.gameId).allSockets();
console.log(clients);
if (!clients) {
console.error("[INTERNAL ERROR] Room creation failed!");
}
if (clients.size === 0) {
console.log("room does not exist");
return;
}
console.log(await io.in(data.gameId).allSockets());
gameSocket.username = data.username;
gameSocket.join(gameId);
console.log(await io.in(data.gameId).allSockets());
io.to(data.gameId).emit("joinSuccess", { message: "JUHU" });
If I try to use this code, the clients are always undefined which means I cannot the room from my current io object
const clients = await gameSocket.in(data.gameId).allSockets(); //undefined
Can someone show me what I would need to change in order to access the right io object and find the rooms. Maybe I´m trying to follow a bad approach here when trying to seperate the code from my app.js file.
Any help would be great.
Got this working finally.
The issue was that this returned a real number
var gameId = (Math.random() * 100000) | 0;
Whereas the value send to addPlayers function was a String...

UDP pinger timeout in javascript dgram node

So, for a course i'm taking, we're coding a UDP pinger in Javascript, using Node.js and Dgram. We've been given the following assignment:
Create the client code for an application. Your client should send 10 ping messages to the target UDP server. For each message, your client should calculate the round trip time from when the package is sent to when the response is received. Should a package be dropped along the way, the client is to handle this as well. This should be done by having the client wait 1 second for a response after sending each package. If no reply is received, the client should log accordingly (package lost, no response, timeout, etc.) and send a new package to try again. However, the total amount of packages sent should still only be 10. The client should also calculate a percentage of packages lost/no response received, and log this before connection is closed.
THis if course seems rather straight forward, and I thought so. I've been coding it for a while, and I'm almost finished, but I'm having issues with the aspect of making the client send a package, await response, and then act accordingly.
So far, what my code does is basically to send a ping, and when a pong is received, it sends another ping. What I can't figure out is how to make it log that a response wasn't received before sending the next package. In other words, I know how to make it react to a received response, I just don't know how to make it respond if no response is given within a set timeframe. I've tried playing around with if-statements and loops, as well as async functions, but I haven't made it work yet, so now I'm asking for help.
Code is here:
const dgram = require("dgram");
const ms = require("ms");
var client = dgram.createSocket("udp4");
const PORT = 8000;
const HOST = "localhost";
let today = "";
let t0 = "";
let t1 = "";
let RTT = "";
let sentPackages = "";
let receivedPackages = "";
const messageOutbound = Buffer.from("You Up?");
sendPackage();
const x = setInterval(sendPackage, 1000);
client.on("message", (message, remote) => {
receivedPackages++
today = new Date();
t1 = today.getTime();
console.log(
`Message from: ${remote.address}:${remote.port} saying: ${message}`
);
RTT = ms(t1 - t0, { long: true });
console.log(RTT);
const x = setInterval(sendPackage, 1000);
});
client.on('error', (err) => {
console.log(`server error:\n${err.stack}`);
server.close();
});
async function sendPackage() {
if (sentPackages < 10) {
client.send(messageOutbound, 0, messageOutbound.length, PORT, HOST, () => {
sentPackages++
let today = new Date();
t0 = today.getTime();
console.log(
`message has been sent to ${HOST}:${PORT}. Message sent at: ${t0}`
);
});
} else {
calculateLoss();
client.close();
}
};
function calculateLoss() {
let amountLost = sentPackages - receivedPackages;
let percentageLoss = amountLost / sentPackages * 100
console.log(amountLost);
console.log(percentageLoss +"% of packages lost");
};
I would use async / await to simply wait 1000ms / 1s between messages, then keep track of all messages in an array.
We identify messages with a uuid, so we can ensure that messages we receive can be matched to those we send.
We can then log all the required statistics afterwards:
const dgram = require("dgram");
const uuid = require('uuid');
const PORT = 8000;
const HOST = "localhost";
const client = dgram.createSocket("udp4");
// Array that keeps track of the messages we send
let messages = [];
// When we get a message, decode it and update our message list accordingly...
client.on("message", (messageBuffer, remote) => {
let receivedMessage = bufferToMessage(messageBuffer);
// Find the message we sent and set the response time accordingly.
let message = messages.find(message => message.uuid === (receivedMessage ||{}).uuid);
if (message) {
message.responseTimestamp = new Date().getTime();
}
});
client.on('error', (err) => {
console.log(`server error:\n${err.stack}`);
server.close();
});
function createMessage() {
return { uuid: uuid.v4() };
}
function messageToBuffer(message) {
return Buffer.from(JSON.stringify(message), "utf-8");
}
function bufferToMessage(buffer) {
try {
return JSON.parse(buffer.toString("utf-8"));
} catch (error) {
return null;
}
}
// Wait for timeout milliseconds
function wait(timeout) {
return new Promise(resolve => setTimeout(resolve, timeout));
}
function sendMessage(message, port, host) {
// Save the messages to our list...
messages.push(message);
console.log(`Sending message #${messages.length}...`);
// Set the time we send out message...
message.sentTimestamp = new Date().getTime();
let messageBuffer = messageToBuffer(message);
return new Promise((resolve, reject) => {
client.send(messageBuffer, 0, messageBuffer.length, port, host, (error, bytes) => {
if (error) {
reject(error);
} else {
resolve(bytes);
}
})
});
}
async function sendMessages(messageCount, port, host, timeout) {
for(let messageIndex = 0; messageIndex < messageCount; messageIndex++) {
let message = createMessage();
await sendMessage(message, port, host);
await wait(timeout);
if (message.responseTimestamp) {
console.log(`Response received after ${message.responseTimestamp - message.sentTimestamp} ms...`);
} else {
console.log(`No response received after ${timeout} ms...`);
}
}
logStatistics(messages);
}
function logStatistics(messages) {
let messagesSent = messages.length;
let messagesReceived = messages.filter(m => m.responseTimestamp).length;
let messagesLost = messagesSent - messagesReceived;
console.log(`Total messages sent: ${messagesSent}`);
console.log(`Total messages received: ${messagesReceived}`);
console.log(`Total messages lost: ${messagesLost} / ${(100*messagesLost / (messages.length || 1) ).toFixed(2)}%`);
if (messagesReceived > 0) {
console.log(`Average response interval:`, messages.filter(m => m.responseTimestamp).reduce((averageTime, message) => {
averageTime += (message.responseTimestamp - message.sentTimestamp) / messagesReceived;
return averageTime;
}, 0) + " ms");
}
}
sendMessages(10, PORT, HOST, 1000);

Parse live query not working?

I tried setting up parse live query, but for some reason it returns no results.
I can pull data from it, so I know the server and db are running fine.
The 'open' connection works when I run on localhost, but even then, the subscription events are never called.
Client
var Parse = require('parse/node');
Parse.initialize("key", "", "pass");
Parse.serverURL = parseServer;
Parse.liveQueryServerURL = 'ws://localhost:1337/';
let query = new Parse.Query('groups');
query.equalTo('name', 'name');
let subscription = query.subscribe();
subscription.on('update', (people) => {
console.log("YEAY");
// console.log(people.get('score')); // This should output 100
});
subscription.on('open', () => {
console.log('subscription opened');
});
Server
var app = new ParseServer({
startLiveQueryServer: true,
liveQuery: {
classNames: ["groups", "comments"] // List of classes to support for query subscriptions
},
..}
var port = process.env.PORT || 1337;
var httpServer = require('http').createServer(app);
httpServer.listen(port, function() {
console.log('parse-server-example running on port ' + port + '.');
});
// This will enable the Live Query real-time server
var parseLiveQueryServer = ParseServer.createLiveQueryServer(httpServer);
You can try changing this
let subscription = query.subscribe();
to this
let subscription = await query.subscribe();
Hope it helps.

Why does my websocket close after a few minutes?

I'm using ws with node.js on the server side and the regular WebSocket API on the client side. Opening the connection and messaging a few times back and forth works fine. But the socket always closes after a minute or two. Aren't they supposed to persist? Am I doing something wrong?
My server is node.js hosted on heroku. I just tested locally again using foreman start (the heroku tool to run the server locally) and the socket doesn't close unexpectedly at all, so perhaps it's a misconfiguration on heroku. Anyway, here's a relevant code sample with a few functions omitted for brevity.
I'm testing the application in Chrome on OSX Yosemite but have seen the same behavior in Chrome on Windows 7 when running against production environment.
server:
// Client <-> Host Protocol functions. Move to a different file so that they can be shared.
var C2H_SIGNAL_TYPE_REGISTER = "register";
var H2C_SIGNAL_WELCOME = "welcome";
var H2C_SIGNAL_TYPE_ERROR = "error";
var H2C_SIGNAL_TYPE_PEER_ADDED = "peer_joined";
var H2C_SIGNAL_TYPE_PEER_LEFT = "peer_left";
// Update channel endpoint names.
var UPDATE_ENDPOINT_PEERS = "/peers";
// Create a signal message with all asociated default properties.
// Signal senders should create this object and update it accordingly when
// building a signal message to send to a peer.
function createHostMsg(type)
{
var msg = { signalType: type };
if ( type == H2C_SIGNAL_WELCOME ) {
// Since we're sending a welcome message, we need to provide a list
// of currently connected clients.
msg.peers = {};
for ( var addr in clients ) {
console.log("addr " + addr);
var c = clients[addr].description;
if ( c && c.id ) {
msg.peers[c.id] = c;
}
}
}
return msg;
}
// require modules.
var express = require('express');
var http = require('http');
var bodyParser = require('body-parser');
var multer = require('multer');
// Tracks connected peers.
var clients = { };
// 1. Configure the application context settings.
var app = express();
app.enable('trust proxy');
app.use(express.static(__dirname + '/public'));
app.use(bodyParser.json()); // parse json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
app.use(multer()); // for parsing multipart/form-data
// a. configure http routers. these will handle requests coming from app.
app.set('port', (process.env.PORT || 5000));
app.get('/app', peerApp);
app.get('/script/:name', publicScriptRouter);
// 2. Create the http server itself, passing app to be the request handler.
// app will handle routing and multiplexing of incoming requests to different
// route middleware handlers.
var http = require('http');
var WebSocketServer = require("ws").Server
var httpServer = http.createServer(app);
httpServer.listen( app.get('port') );
// 3. Create one of these for all socket endpoints.
var wss = new WebSocketServer( { server: httpServer, path: UPDATE_ENDPOINT_PEERS } );
wss.on("connection", function(webSocket) {
// 1. Associate the socket with the remote address it came from.
var remoteAddress = webSocket._socket.remoteAddress;
var remotePort = webSocket._socket.remotePort;
var clientConnID = remoteAddress + ":" + remotePort;
var exists = clients[clientConnID] != null;
if ( exists ) {
console.log("socket server connection: associating new connection from %s with registered peer.", clientConnID);
clients[clientConnID].socket = webSocket;
} else {
console.log("socket server connection: associating new connection from %s with unregistered peer.", clientConnID);
clients[clientConnID] = { description: null, socket: webSocket };
}
// 2. Hook up handlers for communication over this particular socket.
webSocket.on("message", function(data, flags) {
processMessage(webSocket, data, flags);
});
webSocket.on("close", function() {
// Praise satin for closures!!
removePeer(clientConnID);
});
});
// Transduce the message and handle it accordingly.
function processMessage(socket, data, flags)
{
var msg = JSON.parse(data);
if ( !msg.signalType ) {
var msg = createHostMsg( H2C_SIGNAL_TYPE_ERROR );
msg.errStr = "message_malformed";
socket.send( JSON.stringify( msg ) );
} else if ( msg.signalType == C2H_SIGNAL_TYPE_REGISTER ) {
handleRegistration(socket, msg);
}
}
client:
function initSignalChannel()
{
rtcPeer.channel = new WebSocket( location.origin.replace(/^http/, 'ws') + "/peers" );
rtcPeer.channel.onmessage = updateChannelMessage;
rtcPeer.channel.onopen = function(event) {
console.log("remote socket opened");
}
rtcPeer.channel.onclose = function(event) {
console.log("host closed remote socket.");
}
}
function updateChannelMessage(event) {
var msgObj = JSON.parse(event.data);
if ( !msgObj || !msgObj.signalType ) {
console.log("updateChannelMessage: malformed response!! %o", msgObj );
} else if ( msgObj.signalType == "welcome" ) {
console.log("updateChannelMessage: received welcome from host.");
handleWelcome(msgObj);
} else if ( msgObj.signalType == "peer_joined" ) {
console.log("updateChannelMessage: received peer_joined from host.");
if ( msgObj.peer.id == rtcPeer.description.id ) {
console.log("updateChannelMessage: peer_joined: received notification that I've been added to the room. " + msgObj.peer.id);
console.log(msgObj);
} else {
console.log("updateChannelMessage: peer_joined: peer %s is now online.", msgObj.peer.id);
console.log(msgObj);
addRemotePeer( msgObj.peer );
}
}
}
function addRemotePeer(peerObj)
{
remotePeers[peerObj.id] = peerObj;
var ui = createPeerUIObj(peerObj);
$("#connectedPeerList").append( ui );
}
function createPeerUIObj(peerObj)
{
var ui = null;
if ( peerObj ) {
ui = $("<li></li>");
var a = $("<a></a>");
a.append("peer " + peerObj.id);
ui.append(a);
ui.click(function(event) { console.log("clicked");});
}
return ui;
}
function handleWelcome(msgObj)
{
if ( msgObj.id ) {
console.log("updateChannelMessage: welcome: received id from host. " + msgObj.id);
console.log(msgObj);
rtcPeer.description.id = msgObj.id;
for ( var p in msgObj.peers ) {
addRemotePeer(msgObj.peers[p]);
}
} else {
console.log("updateChannelMessage: malformed response. no id.");
}
}
Thanks for the comments everyone. It turns out that jfriend00 had the right answer, I just didn't realize that the hosting service I was using wouldn't allow for the connection to be kept open.
From the below forum posting, the solution is
you'll need to make your clients ping the server periodically to keep the socket alive.
Not the most ideal situation, but indeed doable. Thanks for pointing me in the right direction.

Disconnect event fired after cilent reconnect - socket.io

I'm using socket.io for realtime communication. When inetrnet is not available for few seconds, then it will connect to server when the connection is establish and then it will immediately trigger event which will log in my clinet. After establishing connection, (i think) my socket.io on server side fire disconnect event after ~one minute (in callback function I log off my client), and that is my problem.
This is my server side. (a have server.js and he run other.js files, in object 'files' a have that files).
server.js :
io = require('socket.io').listen(server);//server listening on 8080 port
io.configure(function () {
io.set('transports', ['websocket', 'xhr-polling']);
io.set('log level', 1);
io.disable('browser client');
});
var files = {
control: null,
terminal: null,
messages: null
};
for (var game in games) {
games[game] = require('./game_modules/' + game + '.js').listen(io, create_waiter(game));
}
require('http').createServer(function (req, res) {
var resultCode = 500;
var resultText = '';
var parts = require('url').parse(req.url, true);
var matches = parts.pathname.match('^/([a-z_-]+)/([a-z_-]+)/$');
if (matches) {
var game = matches[1];
var command = matches[2];
resultText = games[game][command](parts.query);
resultCode = 200;
res.writeHead(resultCode, {'Content-Type': 'text/plain'});
res.end(resultText);
}).listen(8081, "127.0.0.1");
control.js :
var control = module.exports = function() {
};
control.listen = function(io) {
this.server = io.of('/control');
this.onTerminate = onTerminate;
//********************************************************************************
this.server.on('connection', function(socket) {
socket.on('disconnect', function(data) {
console.log(data); //after my client reconnect, disconnect event is triggered, and
//console.log write 'close timeout'
After client reconnect, I do not want trigger disconnect event, or if disconnect event was triggered I want check in callback function: is my client connected and prevent "log off" for my client.
Thanks :)

Categories