Only Update Specific Users Socket IO and Node JS - javascript

I am trying to learn node JS and am currently attempting to extend this article.
http://www.gianlucaguarini.com/blog/push-notification-server-streaming-on-a-mysql-database/
I am having major issue because I am getting multiple updates in the SQL query.
I only want to send one socket update.
This issue is in the transactions loop I get multiple socket updates.
I have been struggling with this for over a month and can't seem to figure it out on my own (or with google searches)
Can someone please tell me how I can make this work so I only get one socket update per client.
What I would like to happen is when there is a change in one of the transactions that the two parties (buyer and seller) are the only ones that get the socket update.
How can I make this work? It is so close to what I want it do but I can't get over this last challenge.
Please help.
Thank you in advance.
<html>
<head>
<title>GAT UPDATER</title>
<script src="/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<script src = "http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script type="text/javascript" src="http://board.gameassettrading.com/js/jquery.cookie.js"></script>
</head>
<body>
<script>
var nodeuserid;
function getUserId() {
var url = window.location.href;
var user_id = url.replace('http://heartbeat.gameassettrading.com:4000/id/', '');
return user_id;
}
user_id = getUserId();
$.cookie('useridcookie', user_id, { expires: 1 });
var useridcookie = $.cookie("useridcookie");
// Get Base Url for development between local and dev enviroment
function getBaseURL() {
var url = location.href; // entire url including querystring - also: window.location.href;
var baseURL = url.substring(0, url.indexOf('/', 14));
if (baseURL.indexOf('http://localhost') != -1) {
// Base Url for localhost
var url = location.href; // window.location.href;
var pathname = location.pathname; // window.location.pathname;
var index1 = url.indexOf(pathname);
var index2 = url.indexOf("/", index1 + 1);
var baseLocalUrl = url.substr(0, index2);
return baseLocalUrl + "/";
}
else {
// Root Url for domain name
return baseURL + "/";
}
}
// set the base_url variable
base_url = getBaseURL();
document.domain = "transactionsserver.com"
// create a new websocket
var socket = io.connect('http://heartbeat.transactionsserver.com:4000');
socket.on('connect',function() {
var data = {
url: window.location.href,
};
socket.emit('client-data', data);
});
// this is always open you have to filter out the data you want
socket.on('notification', function (data) {
if(data.hasOwnProperty("data")) {
if(data.data[0]['seller_id'] != ''){
$('#StatusUpdate', window.parent.document).text( data.data[0]['seller_id']+ ':' + data.data[0]['seller_status'] +':'+ data.data[0]['buyer_id']+':'+ data.data[0]['buyer_status']).click();
}
}
window.parent.checkData(data,user_id);
if(data.hasOwnProperty("changed_user_id")) {
$('#StatusUpdate', window.parent.document).text( data.changed_user_id+ ':' + data.changed_user_status +':'+ data.changed_user_id).click();
}
});
</script>
</body>
</html>
Server . js
var app = require("express")();
var path = require('path');
var mysql = require("mysql");
var http = require('http').Server(app);
var io = require("socket.io")(http);
var sockets = {};
var mysql = require('mysql'),
connectionsArray = [],
connection = mysql.createConnection({
multipleStatements: true,
host: 'localhost',
user: '*****',
password: '******',
database: 'transactionsdb',
port: 3306
}),
POLLING_INTERVAL = 1000,
pollingTimer;
// Add Redis For Comparing SQL Results againts Cache
var redis = require('redis');
var client = redis.createClient();
var express = require('express');
/* Creating POOL MySQL connection.*/
var pool = mysql.createPool({
connectionLimit: 100,
host: 'localhost',
user: '*****',
password: '*****',
database: 'transactionsdb',
debug: false
});
var count = 0;
var clients = [];
function processAllTransactions(sellsreply) {
pool.query('SELECT t.id,t.status,t.original_status, t.active, t.buyer_id, t.seller_id, t.seller_acked, t.seller_complete, t.buyer_complete, b.user_status as buyer_status,t.chat_ended, s.user_status as seller_status FROM transaction t LEFT JOIN sf_guard_user_profile s ON s.user_id = t.seller_id LEFT JOIN sf_guard_user_profile b ON b.user_id = t.buyer_id WHERE active = 1 LIMIT 1', [sellsreply], function (err, sells) {
if (sells != '') {
// attempt to stop the updates if it's not the the active transaction
client.get('active transaction id:'+sellsreply, function (err, active_transaction_id) {
passed_active_transaction_id = active_transaction_id;
});
// is there a trasnaction with status defined and transation id does not equal the active transaction id
if(sells[0].status !== undefined && sells[0].id !== passed_active_transaction_id )
{
client.get('active transaction:'+sellsreply, function (err, data1) {
if(JSON.stringify(sells) != data1){
client.set('active transaction id:'+sellsreply,sells[0]["id"]);
client.set('active transaction:'+sellsreply,JSON.stringify(sells));
console.log(JSON.stringify(sells));
updateSockets({
data: sells // pass the database result
});
}
});
}
}
});
}
// Method
function getUserInfo(user_id, callback) {
var query = connection.query('SELECT user_status from sf_guard_user_profile WHERE user_id = ' + connection.escape(user_id));
query.on('result', function (row) {
callback(null, row.user_status);
});
}
var updateSockets = function (data) {
// adding the time of the last update
data.time = new Date();
console.log('Pushing new data to the clients connected ( connections amount = %s ) - %s', connectionsArray.length , data.time);
// sending new data to all the sockets connected
connectionsArray.forEach(function (tmpSocket) {
console.log(tmpSocket);
tmpSocket.volatile.emit('notification', data);
});
};
var pollingLoop = function () {
var socket;
for (var id in sockets) {
socket = sockets[id];
client.get("uuid:" + socket.id, function (err, useridreply) {
processAllTransactions(useridreply);
});
}
connection.query('SELECT * FROM sf_guard_user_profile; select * FROM transaction', function (err, result) {
// error check
if (err) {
console.log(err);
updateSockets(err);
throw err;
} else {
// loop through the queries
var element =
// compare the cache results againt the database query for users
result[0].forEach(function (element, index, array) {
client.get('logged_in:' + element.user_id, function (err, reply) {
if (reply === null) {
// console.log( element.user_id + ' is disconnected');
}
else {
// console.log(reply);
}
});
client.get("user:" + element.user_id, function (err, userreply) {
if (element.user_status != userreply) {
client.set('user:' + element.user_id, +element.user_status);
changed_users.push(element);
console.log(element.user_id + " is now set to: " + element.user_status);
updateSockets({
changed_user_id: element.user_id,
changed_user_status: element.user_status
});
}
});
});
}
// loop on itself only if there are sockets still connected
if (connectionsArray.length) {
pollingTimer = setTimeout(pollingLoop, POLLING_INTERVAL);
// reset changed users and changed transactions arrays
changed_users = [];
changed_transactions = [];
} else {
console.log('The server timer was stopped because there are no more socket connections on the app');
}
});
};
// count the connections array
Array.prototype.contains = function (k, callback) {
var self = this;
return (function check(i) {
if (i >= self.length) {
return callback(false);
}
if (self[i] === k) {
return callback(true);
}
return process.nextTick(check.bind(null, i + 1));
}(0));
};
io.sockets.on('connection', function (socket) {
// runs for every connection
sockets[socket.id] = socket;
socket.on('client-data', function (data) {
// get the user id from the url that is passed onload
var user_id = data.url.replace('http://servernameremoved.com:4000/id/', '');
console.log('user id ' + user_id + ' is connected with session id ' + socket.id);
client.set('uuid:' + socket.id, +user_id);
});
console.log('Number of connections:' + (connectionsArray.length));
// starting the loop only if at least there is one user connected
if (!connectionsArray.length) {
pollingLoop();
}
socket.on('disconnect', function (socketIndex) {
delete sockets[socket.id];
client.get("uuid:" + socket.id, function (err, userreply) {
console.log('user id ' + userreply + ' got redis disconnected');
});
socketIndex = connectionsArray.indexOf(socket);
console.log('socketID = %s got disconnected', socketIndex);
if (~socketIndex) {
connectionsArray.splice(socketIndex, 1);
}
});
connectionsArray.push(socket);
});
// express js route
app.get('/id/:id', function (req, res) {
clients.contains(req.params.id, function (found) {
if (found) {
console.log("Found");
} else {
client.set('logged_in:' + req.params.id, +req.params.id + 'is logged in');
}
});
res.sendFile(__dirname + '/client.html');
});
// build the server
http.listen(4000, function () {
console.log("Server Started");
});
Here is the console log results
Pushing new data to the clients connected ( connections amount = 2 ) - Sat May 30 2015 21:16:23 GMT-0700 (PDT)
30 2015 21:15:15 GMT-0700 (PDT)
user id 3 is connected with session id CRTtkRIl7ihQ2yaEAAAA
user id 2 is connected with session id wNG7XDcEDjhYKBEIAAAB
***********************************************
[{"id":1,"status":20,"original_status":15,"active":1,"buyer_id":2,"seller_id":1,"seller_acked":1,"seller_complete":0,"buyer_complete":1,"buyer_status":4,"chat_ended":"2015-05-31T03:58:40.000Z","seller_status":4}]
***********************************************
Pushing new data to the clients connected ( connections amount = 2 ) - Sat May 30 2015 21:16:23 GMT-0700 (PDT)
***********************************************
[{"id":1,"status":20,"original_status":15,"active":1,"buyer_id":2,"seller_id":1,"seller_acked":1,"seller_complete":0,"buyer_complete":1,"buyer_status":4,"chat_ended":"2015-05-31T03:58:40.000Z","seller_status":4}]
***********************************************
Pushing new data to the clients connected ( connections amount = 2 ) - Sat May 30 2015 21:16:23 GMT-0700 (PDT)

Related

Socket.io / Node.js Message send to bad users

i am trying to do an message system (only conversation one user to one user) with mongoDB, node.js and socket.io all is working fine but when i send a message all the users receive it ... I will try to explain you better :
When 4 users are connected (2 in one chat and 2 other in an other chat)
User 1 : Chat a
User 2 : Chat a
User 3 : Chat b
User 4 : Chat b
When user 1 send a message : User 1, 2, 3, 4 see the message but only user 1 and 2 have the message save in database (so when i refresh all user : only user 1 and 2 have the message)
And here is the problem my messages are well saved but when someone send a message the socket send the message to all the users connected
Here is my code : server.js
var http = require('http');
var MongoClient = require('mongodb').MongoClient;
// Connection URL
const url = 'mongodb://localhost:27017';
// Database Name
const dbName = 'Database';
MongoClient.connect(url, function(err, client) {
if (err)
throw err;
console.log('MongoDB connected ...');
httpServer = http.createServer(function(req, res) {
console.log('This is a test');
res.end('Hello World');
});
httpServer.listen(1337);
var io = require('socket.io').listen(httpServer);
var users = {};
var messages = [];
io.sockets.on('connection', function (socket) {
const collection = client.db(dbName).collection('MessageUser');
var me = false;
var friend = false;
var conv = false;
for(var k in users) {
socket.emit('new_user', users[k]);
}
/**
* Login
*/
socket.on('login_chat', function (user) {
me = user;
friend = user.friend;
conv = user.conv;
console.log('New user : ', me.username, ' - id : ', me.id);
users[me.id] = me;
io.sockets.emit('new_user', me);
});
/**
* Disconnect
*/
socket.on('disconnect', function() {
if (!me) {
return false;
}
delete users[me.id];
io.sockets.emit('disc_user', me);
});
/**
* Message receive
*/
socket.on('new_msg', function(message){
if (message.message !== '') {
message.user = me;
date = new Date();
message.h = date.getHours();
message.m = date.getMinutes();
message.y = date.getFullYear();
message.m = date.getMonth();
message.d = date.getDate();
console.log(message);
messages.push(message);
msg = {};
msg.content = message.message;
msg.sendAt = new Date();
msg.idTransmitter = me.id;
msg.idReceiver = friend;
msg.idConversation = conv;
collection.insertOne(msg);
console.log(msg.idReceiver, msg.idTransmitter, me);
io.sockets.emit('new_msg', message, me, msg.idReceiver, msg.idTransmitter);
}
});
});
});
And client.js :
(function($){
var socket = io.connect('http://localhost:1337');
var lastmsg = false;
startTchat = function(user_id, username, friend_id, conv_id) {
socket.emit('login_chat', {
id : user_id,
username : username,
friend : friend_id,
conv : conv_id,
})
};
/**
* Error
*/
socket.on('error', function(err){
alert(err);
});
/**
* Messages
*/
$('#chat_form').submit(function(event){
event.preventDefault();
socket.emit('new_msg', {message: $('#message').val() });
$('#message').val('');
$('#message').focus();
});
socket.on('new_msg', function(message, me, id_receiver, id_transmiter){
if (me.id === id_receiver || me.id === id_transmiter) {
if (lastmsg != message.user.id) {
*(here i add a div with the msg)*
lastmsg = message.user.id;
} else {
*(here too)*
}
}
});
/**
* Login
*/
socket.on('new_user', function(user){
*(here i add a div with new user)*
});
/**
* Disconnect
*/
socket.on('disc_user', function(user){
$('#' + user.id).remove();
})
})(jQuery);
My idea was this line : if (me.id === id_receiver || me.id === id_transmiter) (client.js line 33) but that seems doing nothing ...
So should i add something or maybe my condition is not good ...
ps : That's one of my first time using node, js and socket so i hope i explain well and ask me if you need more info or screenshots maybe :)

MQTT, ESP8266 acting a random manner

I have been developing a home automation system
Scenario:
ESP8266 sending keep alive to Rasp pi MQTT server
webpage on Chrome using IBM utility javascript Paho client
ESP sends alive every 10 secs and shows when it receives its own message including received time, this proves the server is sending the messages back on time.
Webpage(also has timer) displays the time messages are received with the message
Messages are received on the webpage in a random manner, by this I mean that no messages arrive for a long time then a glut arrives all at the same time
from the esp I can see that the messages are being received when the are published. there seems to be a problem in the webpage code
I am just wondering if anyone has seen this before and can point me in the right direction, my code is below:
thanks in advance
Dave
/*
Original Copyright (c) 2015 IBM Corp.
Eclipse Paho MQTT-JS Utility
This utility can be used to test MQTT brokers
its been bent by me to fit my needs
*/
// global variables
var MyLib = {}; // space for my bits
MyLib.theMessage = ""; // received message
MyLib.theTopic = ""; // received topic
MyLib.clientId = 'webRunpage2' + (Math.random() * 10);
MyLib.connected = false;
MyLib.ticker = setInterval(function(){MyLib.tickTime ++;}, 1000); // 5secs
MyLib.tickTime = 0;
MyLib.aliveTimer = setInterval(function(){keepMeAlive()}, 90000); // 1.5 mins
//=============================================================
function keepMeAlive() {
var topic='webpage', message='KeepAlive';
publish(topic, 1, message, false); // keeps the conection open
console.info("Alive Time: ", MyLib.tickTime);
}
//=============================================================
function onMessageArrived(message) {
MyLib.theTopic = message.destinationName; MyLib.theMessage = message.payloadString;
var topic = MyLib.theTopic;
var messg = MyLib.theMessage;
var id = messg.substring(0,6);
console.log('Message Recieved: Topic: ', message.destinationName, "Time ", MyLib.tickTime);
}
//=============================================================
function onFail(context) {
console.log("Failed to connect");
MyLib.connected = false;
}
//=============================================================
function connectionToggle(){
if(MyLib.connected){ disconnect();
} else { connect(); }
}
//=============================================================
function onConnect(context) {
// Once a connection has been made
console.log("Client Connected");
document.getElementById("status").value = "Connected";
MyLib.connected = true;
client.subscribe('LIGHTS/#'); // subscribe to all 'LIGHTS' members
}
//=================================================
// called when the client loses its connection
function onConnectionLost() {
console.log("Connection Lost: ");
document.getElementById("status").value = "Connection Lost";
MyLib.connected = false;
connectionToggle();
}
//=============================================================
function connect(){
var hostname = "192.168.1.19";
var port = 1884;
var clientId = MyLib.clientId;
var path = "/ws";
var user = "dave";
var pass = "vineyard01";
var keepAlive = 120; // 2 mins stay alive
var timeout = 3;
var ssl = false;
var cleanSession = true; //*******************
var lastWillTopic = "lastwill";
var lastWillQos = 0;
var lastWillRetain = false;
var lastWillMessage = "its Broken";
if(path.length > 0){
client = new Paho.MQTT.Client(hostname, Number(port), path, clientId);
} else {
client = new Paho.MQTT.Client(hostname, Number(port), clientId);
}
// console.info('Connecting to Server: Hostname: ', hostname, '. Port: ', port, '. Path: ', client.path, '. Client ID: ', clientId);
// set callback handlers
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
var options = {
invocationContext: {host : hostname, port: port, path: client.path, clientId: clientId},
timeout: timeout,
keepAliveInterval:keepAlive,
cleanSession: cleanSession,
useSSL: ssl,
onSuccess: onConnect,
onFailure: onFail
};
if(user.length > 0){ options.userName = user; }
if(pass.length > 0){ options.password = pass; }
if(lastWillTopic.length > 0){
var lastWillMessage = new Paho.MQTT.Message(lastWillMessage);
lastWillMessage.destinationName = lastWillTopic;
lastWillMessage.qos = lastWillQos;
lastWillMessage.retained = lastWillRetain;
options.willMessage = lastWillMessage;
}
// connect the client
client.connect(options);
}
//=============================================================
function disconnect(){
// console.info('Disconnecting from Server');
client.disconnect();
MyLib.connected = false;
}
//=============================================================
function publish(topic, qos, message, retain){
// console.info('Publishing Message: Topic: ', topic, '. QoS: ' + qos + '. Message: ', message, '. Retain: ', retain);
message = new Paho.MQTT.Message(message);
message.destinationName = topic;
message.qos = Number(qos);
message.retained = retain;
client.send(message);
}
//=============================================================
function subscribe(topic){
var qos = 1;
client.subscribe(topic, {qos: Number(qos)});
}
//=============================================================
// left next 4 functions but not used
function unsubscribe(topic){
client.unsubscribe(topic, {
onSuccess: unsubscribeSuccess,
onFailure: unsubscribeFailure,
invocationContext: {topic : topic}
});
}
//=============================================================
function unsubscribeSuccess(context){
// console.info('Successfully unsubscribed from ', context.invocationContext.topic);
}
//=============================================================
function unsubscribeFailure(context){
// console.info('Failed to unsubscribe from ', context.invocationContext.topic);
}
//=============================================================
// Just in case someone sends html
function safe_tags_regex(str) {
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
//=============================================================

node.js + socket.io: auction website development

I am currently working on an auction script using node.js and socket.io.But site was developed by using PHP & MySQL. Here I'm using node.js + socket.io for auction bidding process only. The site will have 500-1000 logged in users viewing a single page during the auction. Only one item will be on auction and it will be sold at one day once.
I will be broadcasting(emitting) a countdown timer to all of the users from the server to the client. On the server side I will be using setInterval(),recursive setTimeout() of 1 second to countdown to the auction end time. Apart from this the only other message being sent across will be the current bid being passed from a single client to the server then broadcast to all. This way to do be a reliable? And will it be able to handle the usage on the server?. Here I've tested with 500 users means in browsers getting hanging the timer.
Server.js
var cluster = require('cluster');
var app = require('express')();
//var http = require('http');
var https = require('https');
var socket = require('socket.io');
var redis = require('redis');
var redisAdapter = require('socket.io-redis');
var request = require('request');
var fs = require('fs');
var options = {
key: fs.readFileSync('keys/e1317_0f2c9_71565598d419e37e376ccef5c2827113.key'),
cert: fs.readFileSync('certs/e1317_0f2c9_1468152279_2dc46c1f2cc135a.crt'),
ca: fs.readFileSync('cabundles/90490a5c829d2aca24f22b5820864c6e_1935558000.cabundle')
};
//var server = http.createServer( app );
var server = https.createServer(options, app);
var io = socket.listen(server);
var port = process.env.PORT || 8080;
var workers = process.env.WORKERS || require('os').cpus().length;
var redisUrl = process.env.REDISTOGO_URL || 'redis://127.0.0.1:6379';
var redisOptions = require('parse-redis-url')(redis).parse(redisUrl);
var pub = redis.createClient(redisOptions.port, redisOptions.host, {
detect_buffers: true,
return_buffers: true,
auth_pass: redisOptions.password
});
var sub = redis.createClient(redisOptions.port, redisOptions.host, {
detect_buffers: true,
return_buffers: true,
auth_pass: redisOptions.password
});
io.adapter(redisAdapter({
pubClient: pub,
subClient: sub
}));
console.log('Redis adapter started with url: ' + redisUrl);
io.sockets.on('connection', function(client) {
//console.log('first');
client.on('nauction', function(data) {
io.sockets.emit('nauction', data);
});
});
io.on('connection', function(socket) {
//console.log('in');
console.log('connected client count:' + io.sockets.sockets.length);
var recursive = function() {
//console.log("It has been one second!");
if (io.sockets.sockets.length > 0) {
request('https://www.example.com/file.php', function(error, response, body) {
if (!error && response.statusCode == 200) {
data = JSON.parse(body);
socket.volatile.emit('auction_data', {
'auction_data': data
});
//console.log(data);
} else {
//console.log('else');
console.log(error);
}
});
} //else{
//console.log('No clients connected now');
//}
setTimeout(recursive, 1000);
}
recursive();
socket.on("disconnect", function() {
console.log('clear interval')
//clearInterval(interval);
clearTimeout(recursive);
});
});
if (cluster.isMaster) {
console.log('start cluster with %s workers', workers - 1);
workers--;
for (var i = 0; i < workers; ++i) {
var worker = cluster.fork();
console.log('worker %s started.', worker.process.pid);
}
cluster.on('death', function(worker) {
console.log('worker %s died. restart...', worker.process.pid);
});
} else {
start();
}
function start() {
server.listen(port, function() {
console.log('listening on *:' + port);
});
}
Client.js
socket.on('auction_data', function(auction_details) {
//console.log(auction_details);
$.each(auction_details, function(keys, values) {
//countdwon formation
var tm, days, hrs, mins, secs;
days = value.auction_data.time.days;
if (value.auction_data.time.hours < 10) {
hrs = ("0" + value.auction_data.time.hours);
} else {
hrs = value.auction_data.time.hours;
}
if (value.auction_data.time.mins < 10) {
mins = ("0" + value.auction_data.time.mins);
} else {
mins = value.auction_data.time.mins;
}
if (value.auction_data.time.secs < 10) {
secs = ("0" + value.auction_data.time.secs);
} else {
secs = value.auction_data.time.secs;
}
if (days == 0) {
tm = '' + hrs + '' + '' + mins + '' + '' + secs + '';
} else {
tm = '' + days + '' + '' + hrs + '' + '' + mins + '' + '' + secs + '';
}
$('#auction_' + value.auction_data.product_id + " .countdown").html(tm);
});
});
I'm waiting for your answers to fix the browser hanging problem.
First Question: Is This way to do be a reliable?
Sending the time every Second to EVERY client is not necessary. Simply send them the time at their first visit and use a local timer (at their local page) to reduce the time every second.
You also need to check for server-time on every bid (more secure).
If this is not "secure" enough for you, send the time with the changing bid. You only have to send the actual Bid, when it changed (using Broadcast) or when the user joins the site (just send it to him).
Second Question: And will it be able to handle the usage on the server?
Yes and No.
If your Sever is good enough (every 5$ server with endless traffic would fit),
you should not get in trouble. Only, if your script is very very bad and seeded with Memory Leaks.
Now a few tips:
Never trust the user input - parse it before you use it!
Recalculate everything you get from the client on the Server.
Send the Client only what he needs. He does not need information about stuff that he does not use.
If this was the answer you hoped for, please select the green arrow on the left.
If not, write a comment here and I will give more tips.

How to access/save the session data on the authorization event in Socket.io/express-sessions?

I setup a websocket using Socket.io and express 4 framework on node.js server.
I am trying to implement authorization step for my users when using my websocket.
When a user connects, a token is passed as a query value to the server. At the server level, I query a database for a session that match the passed token. If a session is found, I do few other check to ensure that the token is not hijacked.
Problem
The session data seems to be cleared on every page reload. Or the server is failing to link the sessionId to the user who created it so everytime it generates a new session.
I am puzzled on how to access the session variables "if they are set."
My Code's Problem
When a user reload his/her page/client, the session data will become undefined on the new request. The session is good until the page is refreshed which is my problem. I need to be able to keep the session active even after the user refresh their page.
Questions
How can ensure that the session data are not cleared on every page refresh?
Here is my authorization code
io.set('authorization', function (handshakeData, accept) {
var session = handshakeData.session || {};
//This is always undefined!
console.log('Session Data:' + session.icwsSessionId);
//var cookies = handshakeData.headers.cookie;
var token = handshakeData._query.tokenId || '';
//console.log('Token: ' + token);
if(!token){
console.log('Log: token was not found');
return accept('Token was found.', false);
}
//allow any user that is authorized
if(session && session.autherized && token == session.token){
console.log('Log: you are good to go');
return accept('You are good to go', true);
}
//if the client changed their token "client logged out"
//terminate the open session before opening a new one
if (session.autherized && token != session.token){
var icwsConnection = new icwsConn(icwsRequest);
icwsRequest.setConnection(session.icwsServer, session.icwsPort);
icwsRequest.setIcwsHeaders(session.icwsSessionId, session.icwsToken);
icwsConnection.logout();
session.autherized = false;
session.token = null;
session.icwsServer = null;
session.icwsPort = null;
session.icwsSessionId = null;
session.icwsToken = null;
icwsConnection = null;
}
Here is my entire code if needed
var env = require('./modules/config'),
app = require('express')(),
https = require('https'),
fs = require('fs'),
session = require('express-session'),
redisStore = require("connect-redis")(session),
sharedsession = require("express-socket.io-session"),
base64url = require('base64url');
const server = https.createServer(
{
key: fs.readFileSync('certs/key.pem'),
cert: fs.readFileSync('certs/cert.pem')
}, function (req, res){
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
}
).listen(env.socket.port, env.socket.host, function () {
console.log('\033[2J');
console.log('Websocket is running at https://%s:%s', server.address().address, server.address().port);
});
var io = require('socket.io')(server);
const sessionMiddleware = session({
store: new redisStore({
host: env.redis.host,
port: env.redis.port
}),
secret: env.session.secret,
name: env.session.name,
rolling: false,
resave: true,
saveUninitialized: true
});
app.use(sessionMiddleware);
// Use shared session middleware for socket.io
// setting autoSave:true
io.use(sharedsession(sessionMiddleware, {
autoSave: true
}));
var icwsReq = require('./modules/icws/request.js'),
icwsConn = require('./modules/icws/connection.js'),
icwsInter = require('./modules/icws/interactions.js'),
sessionValidator = require('./modules/validator.js');
var clients = {};
var icwsRequest = new icwsReq();
var sessionChecker = new sessionValidator();
app.get('/', function (req, res) {
res.send('welcome');
});
io.set('authorization', function (handshakeData, accept) {
var session = handshakeData.session || {};
//This is always undefined!
console.log('Session Data:' + session.icwsSessionId);
//var cookies = handshakeData.headers.cookie;
var token = handshakeData._query.tokenId || '';
//console.log('Token: ' + token);
if(!token){
console.log('Log: token was not found');
return accept('Token was found.', false);
}
//allow any user that is authorized
if(session && session.autherized && token == session.token){
console.log('Log: you are good to go');
return accept('You are good to go', true);
}
/*
if (!originIsAllowed(origin)) {
// Make sure we only accept requests from an allowed origin
socket.destroy();
console.log((new Date()) + ' Connection from origin ' + origin + ' rejected.');
return false;
}
*/
//if the client changed their token "client logged out"
//terminate the open session before opening a new one
if (session.autherized && token != session.token){
var icwsConnection = new icwsConn(icwsRequest);
icwsRequest.setConnection(session.icwsServer, session.icwsPort);
icwsRequest.setIcwsHeaders(session.icwsSessionId, session.icwsToken);
icwsConnection.logout();
session.autherized = false;
session.token = null;
session.icwsServer = null;
session.icwsPort = null;
session.icwsSessionId = null;
session.icwsToken = null;
icwsConnection = null;
}
var myIP = '10.0.4.195';
var decodedToken = base64url.decode(token);
sessionChecker.validateData(decodedToken, myIP, env.session.duration, function(isValid, icws){
if(isValid){
session.authorized = true;
session.icwsServer = icws.host;
session.icwsPort = icws.port;
session.token = token;
session.icwsSessionId = null;
session.icwsToken = null;
icwsRequest.setConnection(icws.host, icws.port);
var icwsConnection = new icwsConn(icwsRequest);
icwsConnection.login(icws.username, icws.password, function(isLogged, icwsSession, headers){
if(isLogged && icwsSession.sessionId && icwsSession.csrfToken){
//icwsConnection.setWorkstation(icws.workstaton);
session.icwsSessionId = icwsSession.sessionId;
session.icwsToken = icwsSession.csrfToken;
icwsRequest.setIcwsHeaders(session.icwsSessionId, session.icwsToken);
console.log('Log: new connection to ICWS! ' + session.icwsSessionId );
}
});
console.log('Log: new connection to websocket!')
return accept('New connection to websocket!', true);
} else {
console.log('Log: token could not be validated!');
return accept('Token could not be validated!', false);
}
});
});
io.on('connection', function (socket) {
console.log('Authorized Session! Websocket id ready for action!');
//var origin = socket.request.headers.origin || '';
//var myIP = socket.request.socket.remoteAddress || '';
if(!socket.request.sessionID){
console.log('Missing Session ID');
return false;
}
var socketId = socket.id;
var sessionID = socket.request.sessionID;
//Add this socket to the user's connection
if(userCons.indexOf(socketId) == -1){
userCons.push(socketId);
}
clients[sessionID] = userCons;
console.log(clients); //display all connected clients
socket.on('placeCall', function(msg){
icwsInter.call(method, uri, params, header, true);
});
socket.on('chat', function(msg){
console.log('Chat Message: ' + msg);
socket.emit('chat', { message: msg });
});
socket.on('disconnect', function(msg){
console.log('Closing sessionID: ' + sessionID);
var userCons = clients[sessionID] || [];
var index = userCons.indexOf(socketId);
if(index > -1){
userCons.splice(index, 1);
console.log('Removed Disconnect Message: ' + msg);
} else {
console.log('Disconnect Message: ' + msg);
}
});
socket.on('error', function(msg){
console.log('Error Message: ' + msg);
});
});
function originIsAllowed(origin) {
// put logic here to detect whether the specified origin is allowed.
var allowed = env.session.allowedOrigins || []
if(allowed.indexOf(origin) >= 0){
return true;
}
return false;
}
Edited
The io cookie changes on every request. When a io cookie is created it will have a last accessed values of 12/31/1969 4:00:00 PM
Also, this cookie changes on every page reload.
After #Osk suggestion below Here is my new code which is still isn't saving my session data on page reload.
var env = require('./modules/config'),
app = require('express')(),
https = require('https'),
fs = require('fs'),
session = require('express-session'),
redisStore = require("connect-redis")(session),
sharedsession = require("express-socket.io-session"),
base64url = require('base64url'),
cookieParser = require("cookie-parser");
const server = https.createServer(
{
key: fs.readFileSync('certs/key.pem'),
cert: fs.readFileSync('certs/cert.pem')
}, function (req, res){
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
}
).listen(env.socket.port, env.socket.host, function () {
console.log('\033[2J');
console.log('Websocket is running at https://%s:%s', server.address().address, server.address().port);
});
var io = require('socket.io')(server);
var sessionStore = new redisStore({
host: env.redis.host,
port: env.redis.port
});
const sessionMiddleware = session({
store: sessionStore,
secret: env.session.secret,
name: env.session.name,
rolling: true,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 60 * 60 * 1000
}
});
app.use(sessionMiddleware);
// Use shared session middleware for socket.io
// setting autoSave:true
io.use(sharedsession(sessionMiddleware, {
autoSave: false
}));
var icwsReq = require('./modules/icws/request.js'),
icwsConn = require('./modules/icws/connection.js'),
icwsInter = require('./modules/icws/interactions.js'),
sessionValidator = require('./modules/validator.js');
var clients = {};
var icwsRequest = new icwsReq();
var sessionChecker = new sessionValidator();
app.get('/', function (req, res) {
res.send('welcome');
});
//Middleware for authorizing a user before establishing a connection
io.use(function(socket, next) {
var origin = socket.request.headers.origin || '';
if (!originIsAllowed(origin)) {
// Make sure we only accept requests from an allowed origin
socket.destroy();
console.log((new Date()) + ' Connection from origin ' + origin + ' rejected.');
return false;
}
var myIP = socket.request.socket.remoteAddress || '';
var token = socket.handshake.query.tokenId || '';
var session = socket.handshake.session || {};
//This should be defined on a reload
console.log('IP Address: ' + myIP + ' SessionID: ' + socket.handshake.sessionID);
if(!token){
console.log('Log: token was not found');
return next(new Error('Token not found'));
}
//allow any user that is authorized
if(session && session.autherized && token == session.token){
console.log('Log: you are good to go');
return next(new Error('You are good to go'));
}
//if the client changed their token "client logged out"
//terminate the open session before opening a new one
if (session.autherized && token != session.token){
var icwsConnection = new icwsConn(icwsRequest);
icwsRequest.setConnection(session.icwsServer, session.icwsPort);
icwsRequest.setIcwsHeaders(session.icwsSessionId, session.icwsToken);
icwsConnection.logout();
session.autherized = false;
session.token = null;
session.icwsServer = null;
session.icwsPort = null;
session.icwsSessionId = null;
session.icwsToken = null;
icwsConnection = null;
session.save();
}
var decodedToken = base64url.decode(token);
sessionChecker.validateData(decodedToken, myIP, env.session.duration, function(isValid, icws){
if(isValid){
session.authorized = true;
session.icwsServer = icws.host;
session.icwsPort = icws.port;
session.token = token;
session.icwsSessionId = null;
session.icwsToken = null;
icwsRequest.setConnection(icws.host, icws.port);
var icwsConnection = new icwsConn(icwsRequest);
/*
icwsConnection.login(icws.username, icws.password, function(isLogged, icwsSession, headers){
if(isLogged && icwsSession.sessionId && icwsSession.csrfToken){
//icwsConnection.setWorkstation(icws.workstaton);
session.icwsSessionId = icwsSession.sessionId;
session.icwsToken = icwsSession.csrfToken;
icwsRequest.setIcwsHeaders(session.icwsSessionId, session.icwsToken);
console.log('Log: new connection to ICWS! ' + session.icwsSessionId );
}
});
*/
session.save(function(){
console.log('Log: new connection to websocket!');
});
return next();
} else {
console.log('Log: token could not be validated!');
return next(new Error('Token could not be validated!'));
}
});
});
io.on('connection', function (socket) {
console.log('Connection is validated and ready for action!');
var socketId = socket.id;
if(!socket.handshake.sessionID){
console.log('sessionId was not found');
return false;
}
var sessionID = socket.handshake.sessionID;
var userCons = clients[sessionID] || [];
//Add this socket to the user's connection
if(userCons.indexOf(socketId) == -1){
userCons.push(socketId);
}
clients[sessionID] = userCons;
//console.log(clients);
socket.on('placeCall', function(msg){
icws.call(method, uri, params, header, true);
});
socket.on('chat', function(msg){
console.log('Chat Message: ' + msg);
socket.emit('chat', { message: msg });
});
socket.on('disconnect', function(msg){
console.log('Closing sessionID: ' + sessionID);
var userCons = clients[sessionID] || [];
var index = userCons.indexOf(socketId);
if(index > -1){
userCons.splice(index, 1);
console.log('Removed Disconnect Message: ' + msg);
} else {
console.log('Disconnect Message: ' + msg);
}
});
socket.on('error', function(msg){
console.log('Error Message: ' + msg);
});
});
function originIsAllowed(origin) {
// put logic here to detect whether the specified origin is allowed.
var allowed = env.session.allowedOrigins || []
if(allowed.indexOf(origin) >= 0){
return true;
}
return false;
}
Which version of socket.io are you using?
express-socket.io-session works with socket.io 1.x
I see you're calling io.set() which is deprecated on socket.io 1.x
For more on this, take a look at http://socket.io/docs/migrating-from-0-9/ under the title Authentication differences.
There, it's stated that
The old io.set() and io.get() methods are deprecated and only
supported for backwards compatibility."
Could this be related to your issue ?
When you install the express-socket.io-session package, there's an example directory inside the package. It may come in handy to test against a working example for this module.
Here is a working example of how you can share sessions between express and socket.io, even when they are not on the same domain.
(You can find a slightly different git repository with a running example here https://github.com/dievardump/express-socket-auth )
I simply use express-session, I don't see why using another middleware, since it works perfectly with socket.io
Since I do not have redis accessible, I used require('session-file-store') for the shared sessions.
Problem
Problem comes from the cross-domain policy which won't let you share the connect.sid Cookie value.
A workaround is :
serve non-httpOnly session cookies from the host (here for me server.dev). [express.js line 16]
read via JavaScript and send the connect.sid value as a sessionId parameter when connection to socket.io [client.js line 26:30]
when handshaking adding the value of connect.sid=socket.handshake.query.sessionId to the socket.handshake.headers.cookie before reading the handshake with the session middleware [socket.js line 32:37]
Architecture
Here followed :
server.js which require
express.js : Create express server accessed on my computer via http://server.dev:3000
serve HTML
create Sessions when loading page
socket.js : Create Socket.io server accessed on my computer via http://socket.dev:8000
client.js
served on http://server.dev:3000
connect to socket server on http://socket.dev:8000
Tests
Steps to test I used here :
Client open the page
If the cookie key connect.sid is not set
Client tries to connect to Socket.io : Connection error : [Not authenticated]
Client calls /authenticate
session is generated
Client tries to connect to Socket.io with value of connect.sid as sessionId parameter : Connection sucessfull
If cookie connect.sid is set
Client tries to connect to Socket.io with value of connect.sid as sessionId parameter : Connection sucessfull
Files
server.js
require('./express');
require('./socket');
express.js
var express = require('express');
var app = express();
var http = require('http');
var io = require('socket.io');
var bodyParser = require('body-parser');
var sessionExpress = require('express-session');
var FileStore = require('session-file-store')(sessionExpress);
var secret = 'keyboard cat';
var session = sessionExpress({
secret: secret,
store: new FileStore(),
resave: true,
saveUninitialized: true,
cookie: {
httpOnly: false, // important to allow client to read session cookie with JavaScript
maxAge: 60 * 60 * 1000
}
});
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(express.static(__dirname));
app.use('/authenticate', session);
app.get('/authenticate', function(req, res) {
var session = req.session;
session.userdata = session.userdata || {};
session.userdata.connected = true;
session.save(function(err) {
if (err) {
connectionError(res, session);
} else {
res.status(200);
res.send();
}
});
});
// routes
app.get('/', function(req, res) {
res.send('welcome');
});
// setup servers
var server = http.createServer(app);
server.listen(3000);
socket.js
var express = require('express');
var app = express();
var http = require('http');
var io = require('socket.io');
var sessionExpress = require('express-session');
var FileStore = require('session-file-store')(sessionExpress);
var secret = 'keyboard cat';
var sessionIdKey = 'connect.sid';
var session = sessionExpress({
secret: secret,
store: new FileStore(),
resave: true,
saveUninitialized: true,
cookie: {
maxAge: 60 * 60 * 1000
}
});
// setup servers
var server = http.createServer(app, function (req, res){
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
});
server.listen(8000);
var sio = io(server);
sio.use(function(socket, accept) {
// writing sessionId, sent as parameter, on the socket.handshake cookies
if (socket.handshake.query.sessionId) {
var cookies = (socket.handshake.headers.cookie || '').split(';');
cookies.push(sessionIdKey + '=' + socket.handshake.query.sessionId);
socket.handshake.headers.cookie = cookies.join(';');
}
session(socket.handshake, {}, function(err) {
if (err) return accept(err);
console.log('User trying to connect to Socket.io');
var session = socket.handshake.session,
userData = session.userdata || {};
// is connected and good
if (!userData || !userData.connected) {
console.log('----- User has no active session, error');
accept(new Error('Not authenticated'));
} else {
console.log('----- Socket.io connection attempt successful');
accept(null, session.userid !== null);
}
});
});
sio.on('connection', function (socket) {
console.log('Connection');
});
client.js
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return decodeURIComponent(parts.pop().split(";").shift());
}
function fetch(url, data, callback) {
try {
var x = new XMLHttpRequest();
x.open(data ? 'POST' : 'GET', url, 1);
x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
x.onreadystatechange = function () {
x.readyState > 3 && callback && callback(x.responseText, x);
};
x.send(data || null);
} catch (e) {
window.console && console.log(e);
}
};
function connectServer(cb) {
var sessionId = getCookie('connect.sid');
var data = { forceNew: true, query : { } };
if (sessionId) {
data.query.sessionId = sessionId
}
console.log('Trying to connect to Socket.io server');
var server = io('http://socket.dev:8000', data);
server.on('error', function (err) {
console.log('----- Connection error : [%s]', err);
setTimeout(function () {
cb();
}, 200);
});
server.on('connect', function (data) {
console.log('----- Connection successful with sessionId [%s]', sessionId);
setTimeout(function () {
cb();
}, 200);
});
}
if (getCookie('connect.sid')) {
console.log('Session cookie Detected');
connectServer(function () { });
} else {
connectServer(function () {
console.log('Call ./authenticate to create session server side');
fetch('./authenticate', null, function () {
console.log('Session created')
connectServer(function () {});
});
});
}
Execution
First Page loading Results
Client :
Trying to connect to Socket.io server
----- Connection error : [Not authenticated]
Call ./authenticate to create session server side
Session created
Trying to connect to Socket.io server
----- Connection successful with sessionId [s:Ir9dVPi8wzplPCoeNXAsDlOkhL8AW0gx.wwzUQ2jftntWEc6lRdYqGxRBoszjPtjT4dBW/KjFIXQ]
Server :
User trying to connect to Socket.io
----- User has no active session, error
User trying to connect to Socket.io
----- Socket.io connection attempt successful
Connection
Reload page
Client :
Session cookie Detected
Trying to connect to Socket.io server
----- Connection successful with sessionId [s:Ir9dVPi8wzplPCoeNXAsDlOkhL8AW0gx.wwzUQ2jftntWEc6lRdYqGxRBoszjPtjT4dBW/KjFIXQ]
Server :
User trying to connect to Socket.io
----- Socket.io connection attempt successful
Connection

How do I code a node.js proxy to use NTLMv2 authentication

I've tried to search through stackoverflow for a similar question but most people are asking about the client-side of the NTLMv2 protocol.
I'm implementing a proxy that is performing the server-side of the protocol to authenticate users connecting to the proxy.
I've coded a lot of the protocol but I'm now stuck because the documentation that should take me further is difficult to understand.
This is the best documentation I've found so far: http://www.innovation.ch/personal/ronald/ntlm.html, but how to deal with the LM and NT responses is oblivious to me.
The proxy is located on an application server. The domain server is a different machine.
Example code for the node proxy:
var http = require('http')
, request = require('request')
, ProxyAuth = require('./proxyAuth');
function handlerProxy(req, res) {
ProxyAuth.authorize(req, res);
var options = {
url: req.url,
method: req.method,
headers: req.headers
}
req.pipe(request(options)).pipe(res)
}
var server = http.createServer(handlerProxy);
server.listen(3000, function(){
console.log('Express server listening on port ' + 3000);
});
ProxyAuth.js code:
ProxyAuth = {
parseType3Msg: function(buf) {
var lmlen = buf.readUInt16LE(12);
var lmoff = buf.readUInt16LE(16);
var ntlen = buf.readUInt16LE(20);
var ntoff = buf.readUInt16LE(24);
var dlen = buf.readUInt16LE(28);
var doff = buf.readUInt16LE(32);
var ulen = buf.readUInt16LE(36);
var uoff = buf.readUInt16LE(40);
var hlen = buf.readUInt16LE(44);
var hoff = buf.readUInt16LE(48);
var domain = buf.slice(doff, doff+dlen).toString('utf8');
var user = buf.slice(uoff, uoff+ulen).toString('utf8');
var host = buf.slice(hoff, hoff+hlen).toString('utf8');
var lmresp = buf.slice(lmoff, lmoff+lmlen).toString('utf8');
var ntresp = buf.slice(ntoff, ntoff+ntlen).toString('utf8');
console.log(user, lmresp, ntresp);
/* NOW WHAT DO I DO? */
},
authorize: function(req, res) {
var auth = req.headers['authorization'];
if (!auth) {
res.writeHead(401, {
'WWW-Authenticate': 'NTLM',
});
res.end('<html><body>Proxy Authentication Required</body></html>');
}
else if(auth) {
var header = auth.split(' ');
var buf = new Buffer(header[1], 'base64');
var msg = buf.toString('utf8');
console.log("Decoded", msg);
if (header[0] == "NTLM") {
if (msg.substring(0,8) != "NTLMSSP\x00") {
res.writeHead(401, {
'WWW-Authenticate': 'NTLM',
});
res.end('<html><body>Header not recognized</body></html>');
}
// Type 1 message
if (msg[8] == "\x01") {
console.log(buf.toString('hex'));
var challenge = require('crypto').randomBytes(8);
var type2msg = "NTLMSSP\x00"+
"\x02\x00\x00\x00"+ // 8 message type
"\x00\x00\x00\x00"+ // 12 target name len/alloc
"\x00\x00\x00\x00"+ // 16 target name offset
"\x01\x82\x00\x00"+ // 20 flags
challenge.toString('utf8')+ // 24 challenge
"\x00\x00\x00\x00\x00\x00\x00\x00"+ // 32 context
"\x00\x00\x00\x00\x00\x00\x00\x00"; // 40 target info len/alloc/offset
type2msg = new Buffer(type2msg).toString('base64');
res.writeHead(401, {
'WWW-Authenticate': 'NTLM '+type2msg.trim(),
});
res.end();
}
else if (msg[8] == "\x03") {
console.log(buf.toString('hex'));
ProxyAuth.parseType3Msg(buf);
/* NOW WHAT DO I DO? */
}
}
else if (header[0] == "Basic") {
}
}
}
};
module.exports = ProxyAuth;
The /* NOW WHAT DO I DO? */ comment specifies where I am stuck.
I hope I put enough information there, but let me know if anything else is needed.

Categories