socket.io switch between localhost & server - socket.io

I am running socket.io on node.js on a local Mac machine.
I have setup port forwarding
I am using the following code:
socket = io.connect('XXX.XXX.137.143:1337');
socket.on('connect_failed', function(){
$.jGrowl('Connection Failed');
});
socket.on('disconnect', function(){
$.jGrowl('disconnected');
});
socket.on('connect', function () {
$.jGrowl('We are connected ' );
});
The IP address is my router IP address. This all works as expected.
However if I run the app on the same network as the router it fails to connect. It needs :
socket = io.connect('localhost'); // or the ip of the machine.
How do I determine I am on the same network as the machine and switch the IP setting ?
I tried:
if (location.hostname === "localhost" || location.hostname === "192.168.1.1")
{$.jGrowl("It's a local client!");
var IPaddress = 'http://192.168.1.6:1337';}
else {var IPaddress = '92.12.137.143:1337';
$.jGrowl("It's a remote client!")
}
This works locally but fails on another network that has IP = 192.168.1.1.
Cheers
Steve Warby

You could first attempt a connection to the public IP address and, if that fails, then attempt to connect locally. Here's one way to do that:
let socket = io.connect('http://92.12.137.143:1337');
socket.on('connect_error', handleNoConnect);
socket.on('connect_timeout', handleNoConnect);
socket.on('connect', onConnect);
function handleNoConnect() {
console.log("No connection to http://92.12.137.143:1337");
socket = io.connect('http://192.168.1.6:1337');
socket.on('connect_error', handleNoConnect2);
socket.on('connect_timeout', handleNoConnect2);
socket.on('connect', onConnect);
}
function handleNoConnect2() {
console.log("No connection to http://192.168.1.6:1337");
// decide what to do when you can't connect to either
}
function onConnect() {
console.log("connected");
// set other event handlers on a connected socket
socket.on('disconnect', function() {
console.log("disconnected");
});
}
Or, here's a more general purpose implementation that lets you pass in an array of URLs to try connecting to and it returns a promise that will resolve with the socket that made the connection or reject with an appropriate error.
function connect(list) {
let index = 0;
return new Promise((resolve, reject) => {
function next() {
if (index < list.length) {
let url = list[index++];
let doneThis = false;
let socket = io.connect(url);
socket.on('connect_error', handleNoConnect);
socket.on('connect_timeout', handleNoConnect);
socket.on('connect', () => {
// set property on socket object that tells you
// which URL it connected to
socket._url = url;
resolve(socket);
});
function handleNoConnect() {
if (!doneThis) {
doneThis = true;
console.log("No connection to " + url);
// try next item in the list
next();
}
}
} else {
reject(new Error("No connections succeeded"));
}
}
if (!list || !list.length) {
reject(new Error("Must pass array of URLs to connect()"));
return;
}
next();
});
}
connect(['http://92.12.137.143:1337', 'http://192.168.1.6:1337']).then(socket => {
// connected here
console.log("connected to " + socket._url);
// configure event handlers on the connected socket object here
socket.on('xxx', ...);
}).catch(err => {
// all connections failed here
console.log(err);
})

Related

Socket.io - Refresh tokens before reconnect

I am creating an Electron app with Socket.io. When the user's computer goes into sleep mode the server disconnects from the client throwing an error "transport close". When the user tries to reconnect I check if the tokens are still valid, if they are not, I refresh them and try to send them to the socketIo server.
The problem I have is that on "reconnect_attempt" socket.io doesn't wait until I refresh the tokens to try reconnecting, it tries reconnecting right away with the old tokens, which get rejected by the server, which also seems to terminate the connection with the user impeding future reconnect attempts.
This is part of my code to connect to the server
module.exports.connect = async (JWT) => {
return new Promise( async resolve => {
console.log("connecting to the server")
const connectionOptions = {
secure: true,
query: {token: JWT},
reconnectionDelay: 4000
}
let socket = await socketIo.connect(`${process.env.SERVER_URL}:${process.env.SERVER_PORT}`, connectionOptions);
resolve(socket)
})
}
This is my code for reconnect_attempt
socket.on('reconnect_attempt', async () => {
const getCurrentJWT = require("../../main").getCurrentJWT;
let JWT = await getCurrentJWT(); //By the time this line returns, socket.io has already tried to reconnect
if(JWT.success) { //if refreshed successfully
console.log("Trying to submit new token......", JWT);
socket.query.token = JWT.JWT;
} else {
console.log("Token not refreshed.")
}
});
And this is part of what I have on the server
io.use(async (socket, next) => {
let token = socket.handshake.query.token;
//and the instruction from here https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
let tokenIsValid = await checkTokenValidity(token);
if( tokenIsValid ) {
next();
} else {
next(new Error('invalidToken'));
console.log("Not valid token")
}
})
In short, you can use auth for this.
While connecting
auth: {
token: token
}
In the time of reconnection
socket.auth.token = "NEW_TOKEN";
socket.connect();
I can share socket io implementation for this and you can modify it as your need.
For the client-side,
let unauthorized = false;
let socket = io.connect('ws://localhost:8080', {
transports: ["websocket"],
auth: {
token: GET_YOUR_TOKEN()
}
});
socket.on("connect", () => {
unauthorized = false;
});
socket.on('UNAUTHORIZED', () => {
unauthorized = true;
});
socket.on("disconnect", (reason) => {
if (reason === "io server disconnect") {
if(unauthorized) {
socket.auth.token = token;
}
socket.connect();
}
});
socket.on('PING', ()=>{
socket.emit('PONG', token);
});
For the server-side
io.on("connection", (socket) => {
socket.on('PONG', function (token) {
if (isValidToken(token) == false) {
socket.emit("UNAUTHORIZED");
socket.disconnect();
}
});
setInterval(() => {
socket.emit('PING');
}, <YOUR-TIME>);
});
Having the following in your server.
io.use( async function(socket, next) {
let address = socket.handshake.address;
run++; // 0 -> 1
// Validate Token
const token = socket.handshake.auth.token;
if(token !== undefined){
try{
await tokenVerify(token).then((payload) => {
const serverTimestamp = Math.floor(Date.now() / 1000);
const clientTimestamp = payload.exp;
if(clientTimestamp > serverTimestamp){
console.log("Connection from: " + address + " was accepted");
console.log("Token [" + token + "] from: " + address + " was accepted");
next();
}else{
console.log("Connection from: " + address + " was rejected");
console.log("Token [" + token + "] from: " + address + " was rejected");
next(new Error("unauthorized"));
}
});
}catch (e) {
console.log(e);
}
}
})
With the code above, the server will respond "unauthorized" if the token isn't valid.
So, on the client-side, we can catch that message as shown below.
socket_io.on("connect_error", (err) => {
if(err?.message === 'unauthorized'){
var timeout = (socket_reconnection_attempts === 0 ? 5000 : 60000)
console.log("Trying to reconnect in the next " + (timeout / 1000) + ' seconds')
setTimeout(function (){
console.log('Trying to reconnect manually')
socket_reconnection_attempts++;
loadAuthToken().then(function (token) {
socket_io.auth.token = token;
socket_io.connect();
})
}, timeout)
}
});
With the code above, the client-side will try to reconnect and refresh the token only if the error message is "unauthorized."
The variable "socket_reconnection_attempts" is to avoid sending a massive number of reconnection attempts in a short period of time.

Laravel IO Socket and redis over SSL https connection

Currently have IO sockets with laravel broadcasting with redis working perfectly. Until I then set up an SSL cert on the domain.
I have redis-server running on port 3001.
Then there is a socket.js set to listen to 3000.
My JS on the page I listen via io('//{{ $listen }}:3000').
Any guidance would be great on how to get this working over https. Would I just use 443 as the port?
Thanks.
My socket.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.subscribe('notifications', function(err, count) {
});
redis.on('message', function(channel, message) {
console.log('Message Recieved: ' + message);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
http.listen(3000, function(){
console.log('Listening on Port 3000');
});
First, setup your serverOptions object:
var serverOptions = {
port: 3000,
host: 127.0.0.1, //address to your site
key: '/etc/nginx/ssl/your_site/server.key', //Or whatever the path to your SSL is
cert: '/etc/nginx/ssl/your_site/server.crt',
NPNProtocols: ['http/2.0', 'spdy', 'http/1.1', 'http/1.0']
}
For the NPNProtocols, you may not care for all of them, but they're provided for reference.
Now just create the server:
var app = require('https').createServer(serverOptions),
io = require('socket.io')(app);
This should be pretty plug and play into your source at his point.\
As a side note your stuff is completely wide open and anyone can listen on your web socket, so nothing should be sent that is private through here. If you need to make your data private, then you're going to need 1 of two things;
Something like JWT-Auth Token
Something custom that interface with the Redis queue:
Here's an example of the latter:
var SECRET_KEY = '<YOUR_LARAVEL_SECRET_KEY>';
var laravel_session_parser = {
ord: function (string) {
return string.charCodeAt(0);
},
decryptSession: function (cookie, secret) {
if (cookie) {
var session_cookie = JSON.parse(new Buffer(cookie, 'base64'));
var iv = new Buffer(session_cookie.iv, 'base64');
var value = new Buffer(session_cookie.value, 'base64');
var rijCbc = new mcrypt.MCrypt('rijndael-128', 'cbc');
rijCbc.open(secret, iv);
var decrypted = rijCbc.decrypt(value).toString();
var len = decrypted.length - 1;
var pad = laravel_session_parser.ord(decrypted.charAt(len));
return phpunserialize.unserialize(decrypted.substr(0, decrypted.length - pad));
}
return null;
},
getUidFromObj: function (obj, pattern) {
var regexp = /login_web_([a-zA-Z0-9]+)/gi;
if (pattern) {
regexp = pattern;
}
var u_id = null;
for (var key in obj) {
var matches_array = key.match(regexp);
if (matches_array && matches_array.length > 0) {
u_id = obj[matches_array[0]];
return u_id;
}
}
return u_id;
},
getRedisSession: function (s_id, cb) {
var _sessionId = 'laravel:' + s_id;
client.get(_sessionId, function (err, session) {
if (err) {
cb && cb(err);
return;
}
cb && cb(null, session);
});
},
getSessionId: function (session, _callback) {
var u_id = null,
err = null;
try {
var laravelSession = phpunserialize.unserialize(phpunserialize.unserialize(session));
u_id = laravel_session_parser.getUidFromObj(laravelSession);
} catch (err) {
_callback(err, null);
}
_callback(err, u_id);
},
ready: function (socket, _callback) {
if (typeof socket.handshake.headers.cookie === 'string') {
var cookies = cookie.parse(socket.handshake.headers.cookie);
var laravel_session = cookies.laravel_session;
var session_id = laravel_session_parser.decryptSession(laravel_session, SECRET_KEY);
laravel_session_parser.getRedisSession(session_id, function (err, session) {
if (!err && session) {
laravel_session_parser.getSessionId(session, function (err, user_id) {
if (user_id) {
_callback(null, session_id, user_id, laravel_session)
} else {
_callback(new Error('Authentication error'), null);
}
});
} else {
_callback(new Error('Authentication error'), null);
}
});
}
}
};
Now you can just have IO get an instance of the individuals session when they establish a connection to socket.io
io.on('connection', function (socket) {
laravel_session_parser.ready(socket, function(err, session_id, user_id, laravel_session) {
//log out the variables above to see what they provide
});
});
Note, I prefer to use dotenv in NodeJS to share environment variables between Laravel and Node.
Then you can do process.env.APP_KEY and you don't need to worry about sharing variables.
Also of note, that script above is not complete and is not production ready, it's just meant to be used as an example.

socket.io Websocket connection inside a HTML5 SharedWorker

I hope you all are doing well. I'm trying to establish connection to socket.io server from inside of the worker.js file using importScripts which loads the socket.io-client js file which is in the same directory with worker.js. After loading socket.io-client
by using var socket = io.connect('http://38.98.xxx.xxx:6000'); I am trying to establish connection to socket.io server on different host, but it ain't working. Please point me in the right direction.I appreciate any help.
<script>
var worker = new SharedWorker("http://baseUrl.com/js/push/worker/worker.js");
worker.port.addEventListener("message", function(e) {
console.log("Got message: " + e.data);
}, false);
worker.port.start();
worker.port.postMessage("start");
</script>
worker.js
importScripts('socket.io.js');
var socket = io.connect('http://38.98.154.167:6000');
var connections = 0;
self.addEventListener("connect", function(e) {
var port = e.ports[0];
connections ++;
port.addEventListener("message", function(e) {
if (e.data === "start") {
port.postMessage('hello');
}
}, false);
port.start();
}, false);
socket.on('connect', function () {
port.postMessage('connect');
});
socket.on('disconnect', function () {
port.postMessage('disconnect');
});
I figured it out. Just had to move
socket.on('connect', function () {
port.postMessage('connect');
});
socket.on('disconnect', function () {
port.postMessage('disconnect');
});
into the self.addEventListener("connect", function(e) {});in the worker.js and change from var socket=io.connect('http://38.98.xxx.xxx:6000');
to
var socket = io('http://38.98.xxx.xxx:6000');
Here is the working example is case if anybody needs.
worker.js
importScripts('socket.io.js');
var socket = io('http://38.98.xxx.xxx:6000');
var connections = 0;
self.addEventListener("connect", function(e) {
var port = e.ports[0];
connections ++;
port.addEventListener("message", function(e) {
if (e.data === "start") {
port.postMessage('hello');
}
}, false);
port.start();
socket.on('push', function(pushed){
port.postMessage(pushed);
});
socket.on('connect', function () {
port.postMessage('connect');
});
socket.on('disconnect', function () {
port.postMessage('disconnect');
});
}, false);
There is a drop in replacement for const io = require('socket.io-client');
which runs the connection for the returned socket in a dedicated webworker. It is
const io = require('sockerworker.io');
const socket = io([url][, options]);
Instead of writing your own boilerplate for the webworker, you could use this. It is available here via npm. (disclosure: I am its author.)

handle browser refresh socket.io

I have a requirement using node js that handles disconnecting a user from a chat application.
I am not sure how to handle telling the difference between a browser closing and a user refreshing the browser.
client.on('disconnect', function () {
console.log( 'Disconnected' );
// run mysql code to remove user from logged in table
});
I have googled for a couple hours and cannot find a solution.
This seems like something pretty simple and I think it is the keywords that I am using.
Can someone point me in the right direction on how to handle this?
Thanks in advance.
One way would be to generate a random UID and save it to local storage. Right after the client connects, send this UID to the server and check to see if that UID exists as a connected user. On the server side, set a timeout in the disconnect that gives the user 15 seconds or so before their unique UID is deleted from the "users online" data.
Client:
// When the client starts, create the uid.
localstorage.setItem('uUID', Math.random().toString(24) + new Date());
// Emit the UID right after connection
socket.emit('userLogin', localstorage.getItem('uUID');
Server:
var currentUIDS = [];
var userIsConnected = true;
io.sockets.on('connection', function (socket) {
var currentUID = null;
socket.on('userLogin', function (data) {
if (data !== null) {
if (currentUIDS.includes(data)) {
userIsConnected = true;
currentUID = data;
}
}
});
socket.on('disconnect', function () {
userIsConnected = false;
setTimeout(function () {
if (!userIsConnected) currentUIDS.pop(currentUID);
}, 15000);
});
});
I have a better solution for that to handle multiple users:
var users = [],
users_connected = [];
io.on('connection', function(socket) {
var uid = null;
// register the new user
socket.on('register', function (user_uid) {
if ( users_connected.indexOf(user_uid) < 0 ) {
users_connected.push(user_uid);
}
if ( users.indexOf(user_uid) < 0 ) {
console.log('New user connected: ' + user_uid);
users.push(user_uid);
// notify other clients that a new user has joined
socket.broadcast.emit('user:join', {
name: user_uid,
users: users_connected.length
});
}
uid = user_uid;
});
// clean up when a user leaves, and broadcast it to other users
socket.on('disconnect', function () {
users_connected.splice( users_connected.indexOf(uid), 1);
setTimeout(function () {
if ( users_connected.indexOf(uid) < 0 ) {
socket.broadcast.emit('user:left', {
name: uid
});
var index = users.indexOf(uid);
users.splice(index, 1);
}
}, 3000);
});
});

Node.js proxy server with https support

I'm trying to write proxy server that will proxy (almost) all http/s requests. Almost all because I need catch requests for some specific https url's and as response send the file from hdd instead of real response from the web.
Whole solution should works as proxy in the browser and have to work on windows 7. I started with my own proxy based on express.js. It works great ... but unfortunately not via https. Then I was trying to use several existing node.js proxy servers from github (https://github.com/horaci/node-mitm-proxy, https://github.com/Hypermediaisobar/hyperProxy and few other) but any of them worked in windows environment on https (or I don't know how to congiure them).
Finally I found somewhere in internet code (don't have the link to source) which works via https (see code below). The problems with this code is, that I can't find right way to check the incoming request url and depending on the request url handle them in different ways.
I will be grateful if someone could help me with that.
var http = require('http');
var net = require('net');
var debugging = 0;
var regex_hostport = /^([^:]+)(:([0-9]+))?$/;
function getHostPortFromString(hostString, defaultPort) {
var host = hostString;
var port = defaultPort;
var result = regex_hostport.exec(hostString);
if (result != null) {
host = result[1];
if (result[2] != null) {
port = result[3];
}
}
return( [ host, port ] );
}
// handle a HTTP proxy request
function httpUserRequest(userRequest, userResponse) {
var httpVersion = userRequest['httpVersion'];
var hostport = getHostPortFromString(userRequest.headers['host'], 80);
// have to extract the path from the requested URL
var path = userRequest.url;
result = /^[a-zA-Z]+:\/\/[^\/]+(\/.*)?$/.exec(userRequest.url);
if (result) {
if (result[1].length > 0) {
path = result[1];
} else {
path = "/";
}
}
var options = {
'host': hostport[0],
'port': hostport[1],
'method': userRequest.method,
'path': path,
'agent': userRequest.agent,
'auth': userRequest.auth,
'headers': userRequest.headers
};
var proxyRequest = http.request(
options,
function (proxyResponse) {
userResponse.writeHead(proxyResponse.statusCode, proxyResponse.headers);
proxyResponse.on('data', function (chunk) {
userResponse.write(chunk);
}
);
proxyResponse.on('end',
function () {
userResponse.end();
}
);
}
);
proxyRequest.on('error', function (error) {
userResponse.writeHead(500);
userResponse.write(
"<h1>500 Error</h1>\r\n<p>Error was <pre>" + error + "</pre></p>\r\n</body></html>\r\n";
);
userResponse.end();
}
);
userRequest.addListener('data', function (chunk) {
proxyRequest.write(chunk);
}
);
userRequest.addListener('end', function () {
proxyRequest.end();
}
);
}
function main() {
var port = 5555; // default port if none on command line
// check for any command line arguments
for (var argn = 2; argn < process.argv.length; argn++) {
if (process.argv[argn] === '-p') {
port = parseInt(process.argv[argn + 1]);
argn++;
continue;
}
if (process.argv[argn] === '-d') {
debugging = 1;
continue;
}
}
if (debugging) {
console.log('server listening on port ' + port);
}
// start HTTP server with custom request handler callback function
var server = http.createServer(httpUserRequest).listen(port);
server.addListener('checkContinue', function (request, response){
console.log(request);
response.writeContinue();
});
// add handler for HTTPS (which issues a CONNECT to the proxy)
server.addListener(
'connect',
function (request, socketRequest, bodyhead) {
var url = request['url'];
var httpVersion = request['httpVersion'];
var hostport = getHostPortFromString(url, 443);
// set up TCP connection
var proxySocket = new net.Socket();
proxySocket.connect(
parseInt(hostport[1]), hostport[0],
function () {
console.log("ProxySocket: " + hostport[1] + " | " + hostport[0]);
proxySocket.write(bodyhead);
// tell the caller the connection was successfully established
socketRequest.write("HTTP/" + httpVersion + " 200 Connection established\r\n\r\n");
}
);
proxySocket.on('data', function (chunk) {
socketRequest.write(chunk);
}
);
proxySocket.on('end', function () {
socketRequest.end();
}
);
socketRequest.on('data', function (chunk) {
proxySocket.write(chunk);
}
);
socketRequest.on('end', function () {
proxySocket.end();
}
);
proxySocket.on('error', function (err) {
socketRequest.write("HTTP/" + httpVersion + " 500 Connection error\r\n\r\n");
socketRequest.end();
}
);
socketRequest.on('error', function (err) {
proxySocket.end();
}
);
}
); // HTTPS connect listener
}
main();
are you asking for
http://expressjs.com/4x/api.html#req.secure
req.secure -> https
http://expressjs.com/4x/api.html#req.protocol
req.protocol -> http
http://expressjs.com/4x/api.html#req.host
req.host
req.url
this should all be on your userRequest
I probably did not understand your question correctly.
Just add this line:
var https = require('https');
And when you are making regular http requests, use http.request, and for the ssl requests, https.request.

Resources