Allow only one connection at a time in websocket - javascript

When someone connect to my websocket , I want the last opened connection to be active and close all other old connections.Every users has unique token.Following is the code I created
wss.on('connection', function connection(ws,req) {
const myURL = new URL("https://example.com"+req.url);
var token = myURL.searchParams.get('token');
ws.send("success");
exists = users.hasOwnProperty(token);
if(exists)
{
//console.log("Token exists already");
// ws.send("fail");
// ws.close();
users[token]["ws"].send("fail");
users[token]["ws"].close();
users[token] = [];
users[token]["ws"] = ws;
}
else
{
users[token] = [];
users[token]["ws"] = ws;
//console.log('connected: ' + token + ' in ' + Object.getOwnPropertyNames(users));
}
ws.on('close', function () {
delete users[token]
//console.log('deleted: ' + token);
})
});
But above code works only first time , If I open third time both 2nd and 3rd connection is live.I want to close the 2nd and keep the 3rd alive.Any help is appreciated Thank you.

You probably meant to use an object instead of array
so
users[token] = {};
instead of
users[token] = [];
I would close all other connections when a new connection comes so new connection handler is something like this
wss.on('connection', function connection(ws, req) {
const myURL = new URL("https://example.com" + req.url);
var token = myURL.searchParams.get('token');
ws.send("success");
exists = users.hasOwnProperty(token);
for(const token in users){ // close all existing connections
users[token]["ws"].send("fail");
users[token]["ws"].close();
}
if (exists) {
users[token]["ws"] = ws; // update websocket
}
else {
users[token] = {ws: ws}; // add new websocket to users
// same thing as
// users[token] = {}
// users[token]["ws"] = ws
}
}

Related

How can I open multiple WebSocket streams

I am trying to stream data from the Binance WebSocket API, I have it working for one symbol at a time.
if ("WebSocket" in window) {
//open websocket
var symbols = getSymbol();
//console.log(symbols);
symbols.forEach(function(entry) {
console.log(entry);
})
var ws = new WebSocket("wss://stream.binance.com:9443/ws/btcusdt#miniTicker")
ws.onopen = function() {
console.log("Binance connected...");
};
ws.onmessage = function(evt) {
var r_msg = evt.data;
var jr_msg = JSON.parse(r_msg);
}
ws.onclose = function() {
console.log("Binance disconnected");
}
} else {
alert("WebSocket is NOT supported");
}
the line var symbols = getSymbol(); creates an array of 431 symbols, my logic (and what I am trying to achieve) is to add the new websocket() to the forEach and stream price data from all of the currency pairs.
I'm not sure if this is possible at all or what a better solution would be but I wish to stream and display live data from the api.
Your idea about putting the new WebSocket() inside the for-each should work. However,
I'm not sure if you are allowed to opening hundreds of web sockets from the same tab, and there could also be some performance issues related to it.
According to the API documentation, it is possible to open just one web socket which will send you data from a list of streams, or even just all streams. Just construct the URLs like this:
Specific streams: wss://stream.binance.com:9443/ws/stream1/stream2/stream3
All streams: wss://stream.binance.com:9443/ws/!miniTicker#arr
Here is a code sample that takes these things into consideration. By default this code uses the URL for all streams, but it also has the code (commented out) that uses specific streams.
let streams = [
"ethbtc#miniTicker","bnbbtc#miniTicker","wavesbtc#miniTicker","bchabcbtc#miniTicker",
"bchsvbtc#miniTicker","xrpbtc#miniTicker","tusdbtc#miniTicker","eosbtc#miniTicker",
"trxbtc#miniTicker","ltcbtc#miniTicker","xlmbtc#miniTicker","bcptbtc#miniTicker",
"adabtc#miniTicker","zilbtc#miniTicker","xmrbtc#miniTicker","stratbtc#miniTicker",
"zecbtc#miniTicker","qkcbtc#miniTicker","neobtc#miniTicker","dashbtc#miniTicker","zrxbtc#miniTicker"
];
let trackedStreams = [];
//let ws = new WebSocket("wss://stream.binance.com:9443/ws/" + streams.join('/'));
let ws = new WebSocket("wss://stream.binance.com:9443/ws/!miniTicker#arr");
ws.onopen = function() {
console.log("Binance connected...");
};
ws.onmessage = function(evt) {
try {
let msgs = JSON.parse(evt.data);
if (Array.isArray(msgs)) {
for (let msg of msgs) {
handleMessage(msg);
}
} else {
handleMessage(msgs)
}
} catch (e) {
console.log('Unknown message: ' + evt.data, e);
}
}
ws.onclose = function() {
console.log("Binance disconnected");
}
function handleMessage(msg) {
const stream = msg.s;
if (trackedStreams.indexOf(stream) === -1) {
document.getElementById('streams').innerHTML += '<br/>' + stream + ': <span id="stream_' + stream + '"></span>';
trackedStreams.push(stream);
document.getElementById('totalstreams').innerText = trackedStreams.length;
}
document.getElementById('stream_' + stream).innerText = msg.v;
}
<span id="totalstreams"></span> streams tracked<br/>
Total traded base asset volume:<br/>
<div id="streams"></div>

Running node-rdkafka code in server

I'm running the below node-rdkafka code in Eclipse as Node.js application. This is the sample code from https://blizzard.github.io/node-rdkafka/current/tutorial-producer_.html
I want to run this in a test server and call from iOS Mobile application.
I knew about running node.js app in AWS.
Question I: Is there any other options to run in a free test server environment like Tomcat?
Question II: Even If I am able to run this node.js app in a server, how do i call from a mobile application? Do I need to call producer.on('ready', function(arg) (or) What function i need to call from Mobile app?
var Kafka = require('node-rdkafka');
//console.log(Kafka.features);
//console.log(Kafka.librdkafkaVersion);
var producer = new Kafka.Producer({
'metadata.broker.list': 'localhost:9092',
'dr_cb': true
});
var topicName = 'MyTest';
//logging debug messages, if debug is enabled
producer.on('event.log', function(log) {
console.log(log);
});
//logging all errors
producer.on('event.error', function(err) {
console.error('Error from producer');
console.error(err);
});
//counter to stop this sample after maxMessages are sent
var counter = 0;
var maxMessages = 10;
producer.on('delivery-report', function(err, report) {
console.log('delivery-report: ' + JSON.stringify(report));
counter++;
});
//Wait for the ready event before producing
producer.on('ready', function(arg) {
console.log('producer ready.' + JSON.stringify(arg));
for (var i = 0; i < maxMessages; i++) {
var value = new Buffer('MyProducerTest - value-' +i);
var key = "key-"+i;
// if partition is set to -1, librdkafka will use the default partitioner
var partition = -1;
producer.produce(topicName, partition, value, key);
}
//need to keep polling for a while to ensure the delivery reports are received
var pollLoop = setInterval(function() {
producer.poll();
if (counter === maxMessages) {
clearInterval(pollLoop);
producer.disconnect();
}
}, 1000);
});
/*
producer.on('disconnected', function(arg) {
console.log('producer disconnected. ' + JSON.stringify(arg));
});*/
//starting the producer
producer.connect();
First of all, you need an HTTP server. ExpressJS can be used. Then, just tack on the Express code basically at the end, but move the producer loop into the request route.
So, start with what you had
var Kafka = require('node-rdkafka');
//console.log(Kafka.features);
//console.log(Kafka.librdkafkaVersion);
var producer = new Kafka.Producer({
'metadata.broker.list': 'localhost:9092',
'dr_cb': true
});
var topicName = 'MyTest';
//logging debug messages, if debug is enabled
producer.on('event.log', function(log) {
console.log(log);
});
//logging all errors
producer.on('event.error', function(err) {
console.error('Error from producer');
console.error(err);
});
producer.on('delivery-report', function(err, report) {
console.log('delivery-report: ' + JSON.stringify(report));
counter++;
});
//Wait for the ready event before producing
producer.on('ready', function(arg) {
console.log('producer ready.' + JSON.stringify(arg));
});
producer.on('disconnected', function(arg) {
console.log('producer disconnected. ' + JSON.stringify(arg));
});
//starting the producer
producer.connect();
Then, you can add this in the same file.
var express = require('express')
var app = express()
app.get('/', (req, res) => res.send('Ready to send messages!'))
app.post('/:maxMessages', function (req, res) {
if (req.params.maxMessages) {
var maxMessages = parseInt(req.params.maxMessages);
for (var i = 0; i < maxMessages; i++) {
var value = new Buffer('MyProducerTest - value-' +i);
var key = "key-"+i;
// if partition is set to -1, librdkafka will use the default partitioner
var partition = -1;
producer.produce(topicName, partition, value, key);
} // end for
} // end if
}); // end app.post()
app.listen(3000, () => console.log('Example app listening on port 3000!'))
I don't think the poll loop is necessary since you don't care about the counter anymore.
Now, connect your mobile app to http://<your server IP>:3000/ and send test messages with a POST request to http://<your server IP>:3000/10, for example, and adjust to change the number of messages to send
I might be late on this but this is how I did using promises and found it better than have a time out etc.
const postMessageToPublisher = (req, res) => {
return new Promise((resolve, reject) => {
producer.connect();
producer.setPollInterval(globalConfigs.producerPollingTime);
const actualBody = requestBody.data;
const requestBody = req.body;
const topicName = req.body.topicName;
const key = requestBody.key || uuid();
const partition = requestBody.partition || undefined;
const data = Buffer.from(JSON.stringify(udpatedBody));
/**
* Actual messages are sent here when the producer is ready
*/
producer.on(kafkaEvents.READY, () => {
try {
producer.produce(
topic,
partition,
message,
key // setting key user provided or UUID
);
} catch (error) {
reject(error);
}
});
// Register listener for debug information; only invoked if debug option set in driver_options
producer.on(kafkaEvents.LOG, log => {
logger.info('Producer event log notification for debugging:', log);
});
// Register error listener
producer.on(kafkaEvents.ERROR, err => {
logger.error('Error from producer:' + JSON.stringify(err));
reject(err);
});
// Register delivery report listener
producer.on(kafkaEvents.PUBLISH_ACKNOWLEDGMENT, (err, ackMessage) => {
if (err) {
logger.error(
'Delivery report: Failed sending message ' + ackMessage.value
);
logger.error('and the error is :', err);
reject({ value: ackMessage.value, error: err });
} else {
resolve({
teamName: globalConfigs.TeamNameService,
topicName: ackMessage.topic,
key: ackMessage.key.toString()
});
}
});
});
};
Please note that kafkaEvents contains my constants for the events we listen to and it is just a reference such as kafkaEvents.LOG is same as event.log
and also the calling function is expecting this to a promise and accordingly we user .then(data => 'send your response to user from here') and .catch(error => 'send error response to user
this is how I achieved it using promises

React Native Websocket outside access

Im trying to send an answer to my websocket-server from a component which does not contain the websocket. My Websocket server looks like this:
componentDidMount() {
var ws = new WebSocket('ws:// URL');
ws.onmessage = this.handleMessage.bind(this);
...
}
How can I pass the "var ws" to another class or component. Or is it possible to make the websocket globally accessable?
Thank you very much for any help!
I found a solution with help from this question in stackoverflow:
visit:
React native: Always running component
I created a new class WebsocketController like this:
let instance = null;
class WebsocketController{
constructor() {
if(!instance){
instance = this;
}
this.ws = new WebSocket('ws://URL');
return instance;
}
}
export default WebsocketController
And then in my other class where I need my websocket I just called it like this:
let controller = new WebsocketController();
var ws = controller.ws;
 
websocket connection
keep this code in some file, name it with .js extenstion. ex: websocket.js
var WebSocketServer = require("ws").Server;
var wss = new WebSocketServer({port:8100});
wss.broadcast = function broadcast(msg) {
console.log(msg);
wss.clients.forEach(function each(client) {
client.send(msg);
});
};
wss.on('connection', function connection(ws) {
// Store the remote systems IP address as "remoteIp".
var remoteIp = ws.upgradeReq.connection.remoteAddress;
// Print a log with the IP of the client that connected.
console.log('Connection received: ', remoteIp);
ws.send('You successfully connected to the websocket.');
ws.on('message',wss.broadcast);
});
In your app/website side. create .js file. Ex: client.js
var SERVER_URL = 'ws://127.0.0.1:8100';
var ws;
function connect() {
//alert('connect');
ws = new WebSocket(SERVER_URL, []);
// Set the function to be called when a message is received.
ws.onmessage = handleMessageReceived;
// Set the function to be called when we have connected to the server.
ws.onopen = handleConnected;
// Set the function to be called when an error occurs.
ws.onerror = handleError;
}
function handleMessageReceived(data) {
// Simply call logMessage(), passing the received data.
logMessage(data.data);
}
function handleConnected(data) {
// Create a log message which explains what has happened and includes
// the url we have connected too.
var logMsg = 'Connected to server: ' + data.target.url;
// Add the message to the log.
logMessage(logMsg)
ws.send("hi am raj");
}
function handleError(err) {
// Print the error to the console so we can debug it.
console.log("Error: ", err);
}
function logMessage(msg) {
// $apply() ensures that the elements on the page are updated
// with the new message.
$scope.$apply(function() {
//Append out new message to our message log. The \n means new line.
$scope.messageLog = $scope.messageLog + msg + "\n";
});
}
Please let me know if you face any issue with this code

Socket not saving data inside handshake object?

I am trying to save session data inside a handshake object from io.set('authorization')
var io = sio.listen(server);
io.set('authorization', function (handshake, callback) {
if(handshake.headers.cookie) {
var cookies = cookie.parse(handshake.headers.cookie);
var sid = cookieParser.signedCookie(cookies['xygaming'], secrets.sessionSecret);
sessionStore.load(sid, function(err, session) {
if(err || !session) {
return callback('Error retrieving session!', false);
}
// this is not storing the data into the handshake object
handshake.balloons = {
user: session.passport.user,
room: /\/(?:([^\/]+?))\/?$/g.exec(handshake.headers.referer)[1]
};
return callback(null, true);
});
} else {
return callback('No cookie transmitted.', false);
}
});
I have an adapter for pub sub
io.adapter(redisIo({
host: 'localhost',
port: 6379,
pubClient: pub, // just redis.createClient()
subClient: sub // just redis.createClient()
}));
Then I want to access the handshake data inside the io.sockets.on('connection') but its not there? Any idea why its not passing? In the original repo it works on express 3x, but since I upgraded to 4x and made some modifications of my own it does not pass through?
io.sockets.on('connection', function (socket) {
console.log(socket.handshake);
// I want to pass the handshake data here, but its undefined??
var hs = socket.handshake
, nickname = hs.balloons.user.username
, provider = hs.balloons.user.provider
, userKey = provider + ":" + nickname
, room_id = hs.balloons.room
, now = new Date()
// Chat Log handler
, chatlogFileName = './chats/' + room_id + (now.getFullYear()) + (now.getMonth() + 1) + (now.getDate()) + ".txt"
// , chatlogWriteStream = fs.createWriteStream(chatlogFileName, {'flags': 'a'});
socket.join(room_id);
});

how to prevent new connection on every page refresh in sockjs

So, every time I refresh the page, it seems like sockjs is creating a new connection.
I am saving every message to my mongodb on every channel.onmessage, so if I refresh my page 7 times and send a message, I would save 7 messages of the same content into my mongodb.
This is very problematic because when I retrieve those messages when I go into the chat room, to see the log, I would see bunch of duplicate messages.
I want to keep track of all connections that are 'active', and if a user tries to make another connection, I want to terminate the old one so there is only one connection listening to each message at a time.
How do I do this ?
var connections = {};
//creating the sockjs server
var chat = sockjs.createServer();
//installing handlers for sockjs server instance, with the same url as client
chat.installHandlers(server, {prefix:'/chat/private'});
var multiplexer = new multiplexServer.MultiplexServer(chat);
var configChannel = function (channelId, userId, userName){
var channel = multiplexer.registerChannel(channelId);
channel.on('connection', function (conn) {
// console.log('connection');
console.log(connections);
connections[channelId] = connections[channelId] || {};
if (connections[channelId][userId]) {
//want to close the extra connection
} else {
connections[channelId][userId] = conn;
}
// }
// if (channels[channelId][userId]) {
// conn = channels[channelId][userId];
// } else {
// channels[channelId][userId] = conn;
// }
// console.log('accessing channel! ', channels[channelId]);
conn.on('new user', function (data, message) {
console.log('new user! ', data, message);
});
// var number = connections.length;
conn.on('data', function(message) {
var messageObj = JSON.parse(message);
handler.saveMessage(messageObj.channelId, messageObj.user, messageObj.message);
console.log('received the message, ', messageObj.message);
conn.write(JSON.stringify({channelId: messageObj.channelId, user: messageObj.user, message: messageObj.message }));
});
conn.on('close', function() {
conn.write(userName + ' has disconnected');
});
});
return channel;
};
The way I resolve a problem like yours was with a Closure and Promises, I don't know if that could help you. I let you the code that help me, this is with EventBus from Vertx:
window.Events = (function NewEvents() {
var eventBusUrl = $('#eventBusUrl').val();
var eventBus = null;
return new RSVP.Promise(function(resolve, reject) {
if(!eventBus) {
eventBus = new vertx.EventBus(eventBusUrl);
eventBus.onopen = function eventBusOpened() {
console.log('Event bus online');
resolve(eventBus);
}
eventBus.onclose = function() {
eventBus = null;
};
}
});
}());
And then in other script I call it in this way:
Events.then(function(eventBus) {
console.log("registering handlers for comments");
eventBus.registerHandler(address, function(incomingMessage) {
console.log(incomingMessage);
});
});
I hope this can help you.
Regards.

Categories