Generate custom socket id for socket.io - node.js

According to socket.io documentation for generating custom id this should work:
const express = require('express');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
require('dotenv').config();
io.engine.generateId = (req) => {
// generate a new custom id here
var randomCode = '';
for (let r = 0; r < 6; r++) {
randomCode += Math.floor(Math.random() * 10)+'';
}
return randomCode
}
io.on('connection', (socket) => {
console.log("a user connected :D", socket.id);
socket.on('storeClientInfo', (data) => {
console.log(socket.id)
});
socket.on("private message", (anotherSocketId, msg) => {
console.log(anotherSocketId, msg)
socket.to(anotherSocketId).emit("private message", socket.id, msg);
});
});
server.listen(4000);
app.listen(process.env.PORT, () => console.log('Server is running'));
but it doesn't work for me the console outputs:
a user connected :D wJ8d-SC2su9YYCdWAAAC
it should be 6 random numbers, the random number is just for testing I want to assign the id to the user id in the database later.

use can assign custom params to socket
var users = []
io.use((socket, next) => {
socket.customId = 123456789;
users[socket.customId] = socket;
next()
});
and you can use custom params in
io.on("connection", (socket) => {
socket.on("sendPrivateMessage", params => {
users[params.toUserId].emit("message", "you have a private message")
})
});

Related

Socket.io - server receives message but doesn't emit it to clients using nodejs and mongodb?

when i run the code, the server server side receive the message but the client side doesnt get anything until they send message. however in html doesnt show anything wrong
i have this server code:
var http = require('http').Server(app);
var io = require('socket.io')(http);
var cors = require('cors')
const { socket } = require('socket.io');
io.on('connection', () =>{
console.log('a user is connected')
})
i have this route:
var http = require('http').Server(router);
var io = require('socket.io')(http);
// Render Message
router.get('/messages/:id', async function (req, res, next) {
user = await User.findOne({_id: req.session.userId}, {username: 1})
return res.render("user/messages.ejs", {user: user, booking: req.params.id});
});
// Display Message from DB
router.get('/messageslist/:booking', (req, res) => {
Message.find({booking: req.params.booking})
.populate({
path: "pro",
model: Pro,
}).populate({
path: "user",
model: User,
}).exec().then((data) => {
res.json(data)
})
})
router.post('/messages', async (req, res) => {
const {booking, user, message} = req.body;
try {
var msg = new Message({
booking: booking,
message: message,
user: user
});
var savedMessage = await msg.save()
console.log('saved');
io.emit('message', req.body);
res.sendStatus(200);
} catch (error) {
res.sendStatus(500);
return console.log('error', error);
} finally {
console.log('Message Posted')
}
})
this is my html:
var socket = io();
$(() => {
$("#send").click(() => {
sendMessage({
booking: $("input[name=booking]").val(),
user: $("input[name=user]").val(),
message: $("#message").val()
});
})
getMessages()
})
socket.on('message', addMessages)
function addMessages(message) {
if (message.user && message.user != '') {
if ($("input[name=user]").val() == message.user._id) {
html = '<div class="msg right-msg"><div class="msg-img" style="background-image: url(' + message.user.image +
')"></div>'
html += '<div class="msg-bubble"><div class="msg-info"><div class="msg-info-name">' + message.user.username +
'</div><div class="msg-info-time">' + message.createdAt + '</div></div>'
}
} else {
html = '<div class="msg left-msg"><div class="msg-img" style="background-image: url(' + message.pro.image +
')"></div>'
html += '<div class="msg-bubble"><div class="msg-info"><div class="msg-info-name">' + message.pro.username +
'</div><div class="msg-info-time">' + message.createdAt + '</div></div>'
}
html += '<div class="msg-text">' + message.message + '</div></div></div>'
window.scrollTo(0, document.body.scrollHeight);
$("#msger-chat")+$(".msger-chat").append(html)
}
function getMessages() {
$.get('http://127.0.0.1:3000/messageslist/<%=booking%>', (data) => {
data.forEach(addMessages);
})
}
function sendMessage(message) {
$.post('http://127.0.0.1:3000/messages', message)
}
When i run it, my server outputs: User connected and message saved;. But my client doesn't get a response
I think the problem is you are creating two io instances. One in server code
var http = require('http').Server(app);
var io = require('socket.io')(http);
var cors = require('cors')
const { socket } = require('socket.io');
io.on('connection', () =>{
console.log('a user is connected')
})
another one is in route handlers
var http = require('http').Server(router);
var io = require('socket.io')(http);
You should have a singleton io object which is responsible to handle all the activities. This is an approach how to create such design. you will have mainSocketServer
createSocketServer = (server) => {
// this is how you implemented
var io = require('socket.io')(http);
// this is the part to create singleton object
serverStore.setSocketServerInstance(io);
io.on("connection", (socket) => {
// this socket obj will include
about connected user. this socket is actually connected user client
console.log('a user is connected')
socket.on("direct-message", (data) => {
// you create socket events handler in different file
directMessageHandler(socket, data);
}); });};
module.exports = {
createSocketServer,
};
you need another file where you store all the connected users. usually in socket server, users are stored in a Map
const connectedUsers = new Map();
let activeRooms = [];
let io = null;
// we call this in mainSocketServer file
const setSocketServerInstance = (ioInstance) => {
io = ioInstance;
};
// you export this and use it anywhere on your server
const getSocketServerInstance = () => {
return io;
};
On the client side where you listen for sockets, you do not pass reference to the function. 2nd argument is callback function that has received data as parameter and you should call addMessage from inside. Here is the example code:
socket.on('message', (data) => {
addMessages(data)
})
Just update this in front end code and it should work

HTTPS over WebSockets tunnel

I'm trying to build a system in which the backend can send request to the client that will behave as a proxy. I'm constrained in using WebSockets due to the current state of the remote server. I've build a basic version of my idea that works with HTTP request sent from a browser, but as soon as I activate HTTPS it apparently fails on the handshake. I'm adding a request ID to organise and imitate the request/response model. I want to achieve an E2E encryption with this proxy chain.
This is my code for the proxy that catches the requests and sends them over the WebSocket:
const net = require('net');
const WebSocket = require('ws');
const crypto = require("crypto");
const wss = new WebSocket('ws://localhost:8001');
const server = net.createServer();
server.on('connection', (socket) => {
// settings per connection opened
let requests = [];
// Receives data
socket.on('data', data => {
const requestID = crypto.randomBytes(16).toString("hex");
const additionalInfo = JSON.stringify({
"Request": requestID,
});
const header = Buffer.from(`${additionalInfo.length} ${additionalInfo}\n\n`);
wss.send(Buffer.concat([header, data]));
requests.push(requestID);
});
socket.on('error', error => {
console.log(error);
});
socket.on('end', () => {
requests = [];
});
// server response to the request
wss.on('message', (data) => {
// extract basic data
const jsonDataSize = data.toString().split(' ')[0];
const jsonDataOffset = jsonDataSize.length + 1;
const jsonDataText = data.slice(jsonDataOffset, Number(jsonDataSize) + jsonDataOffset).toString();
const jsonData = JSON.parse(jsonDataText);
const message = data.slice(Number(jsonDataSize) + jsonDataOffset + 2);
// check if received response matches the request
if (requests.includes(jsonData.Request)) {
// console.log(data.toString());
socket.write(message);
}
});
wss.on('error', error => {
console.log(error);
});
});
server.on('close', () => {
console.log("Server closed");
})
server.listen({host: "localhost", port: 8000}, () => {
console.log("Server listening on localhost:8000");
});
This is my code for the WebSocket receiver:
const net = require('net');
const WebSocket = require('ws');
const wss = new WebSocket.Server({port: 8001}, () => {
console.log('WebSocket server listening on localhost:8001');
});
wss.on('connection', socket => {
let server;
// trigger for a new message from the same connection
socket.on('message', data => {
// console.log("---------->");
// console.log(data.toString());
const jsonDataSize = data.toString().split(' ')[0];
const jsonDataOffset = jsonDataSize.length + 1;
const jsonDataText = data.slice(jsonDataOffset, Number(jsonDataSize) + jsonDataOffset).toString();
// check if it is a new tls connection request
let isTLSConnection = data.toString().indexOf("CONNECT") !== -1;
// set the new connection's settings
if (isTLSConnection && data.toString().includes('Host: ')) {
const serverPort = 443;
const serverAddress = data
.toString()
.split("CONNECT")[1]
.split(" ")[1]
.split(":")[0];
server = net.createConnection({host: serverAddress, port: serverPort});
// Send back 200 OK to the browser
const header = Buffer.from(`${jsonDataSize} ${jsonDataText}\n\n`);
socket.send(Buffer.concat([header, Buffer.from("HTTP/1.1 200 OK\r\n\r\n")]));
return;
} else if (data.toString().includes('Host: ')) {
const serverPort = 80;
const serverAddress = data.toString().split('Host: ')[1].split("\r\n")[0];
server = net.createConnection({host: serverAddress, port: serverPort});
}
// extract payload
const message = data.slice(Number(jsonDataSize) + jsonDataOffset + 2);
// send payload to server
server.write(message);
server.on('data', (data) => {
// console.log("<----------");
// console.log(data.toString());
const header = Buffer.from(`${jsonDataSize} ${jsonDataText}\n\n`);
socket.send(Buffer.concat([header, data]));
});
server.on('error', error => {
console.log(error);
});
});
socket.on('error', (error) => {
console.log(error);
});
});
I took inspiration from Build your own proxy server from scratch
I tried implementing a request/response model on top of WebSockets since I believe the error relies there, but it didn't work
You should use wss://localhost:8001 when using https

Node js Socket.iO accessing socket outside for multiple client

How can we access socket object outside for multiple socket connection. I created a object globally and tried to do this. But it always works for last connected socket.
'use strict';
const path = require('path')
const express = require('express');
const http = require('http');
const chalk = require('chalk');
const socketio = require('socket.io');
var connectionString = '';
const eventHubConsumerGroup = ""
const app = express()
const server = http.createServer(app ,() => {
console.log(chalk.green('Server created'))
})
const io = socketio(server)
const port = process.env.port || 3000
const publicDirectoryPath = path.join(__dirname , '../public')
var server_token = "1234567890";
app.use(express.static(publicDirectoryPath))
var localSocket;
io.on('connection',function(socket){
localSocket = socket;
console.log(socket.handshake.query.deviceID)
console.log('on user connected '+socket.id);
//report = new Report(socket);
socket.auth = false;
socket.on('authenticate',function(token){
console.log('token recieved is '+token);
if(server_token == token){
socket.auth = true;
console.log('connection is authenticated '+socket.id);
socket.emit("authenticate",true);
} else {
console.log("Connection not established")
socket.emit("authenticate",false);
}
})
socket.on('sendSocketEvent' , message => {
console.log(chalk.yellowBright(`Message recieved from ${socket.id} + ${message}`));
io.to(socket.id).emit('recieveSocketEvent', `Hello test`);
})
socket.on('disconnect',function(){
console.log('one user disconnected '+socket.id);
})
setTimeout(function(){
if(!socket.auth){
console.log('disconnecting the socket '+socket.id);
socket.emit("timeOut");
socket.disconnect();
}
},1000);
})
server.listen(port,() => {
console.log(chalk.redBright(`Server is up on port ${port}`))
})
var printMessage = function (message) {
console.log(JSON.stringify(message));
console.log(message.DeviceId);
if (localSocket != null){
if (message.DeviceId == localSocket.handshake.query.deviceID) {
localSocket.emit('recieveSocketEvent', message);
}
}
};
class EventHubReader {
constructor(connectionString, consumerGroup) {
this.connectionString = connectionString;
this.consumerGroup = consumerGroup;
this.eventHubClient = undefined;
this.receiveHandlers = undefined;
}
async startReadMessage(startReadMessageCallback) {
try {
console.log(this.connectionString)
const client = await EventHubClient.createFromIotHubConnectionString(this.connectionString);
console.log('Successfully created the EventHub Client from IoT Hub connection string.');
this.eventHubClient = client;
const partitionIds = await this.eventHubClient.getPartitionIds();
console.log('The partition ids are: ', partitionIds);
const onError = (err) => {
console.error(err.message || err);
};
const onMessage = (message) => {
const deviceId = message.annotations['iothub-connection-device-id'];
return startReadMessageCallback(message.body, message.enqueuedTimeUtc, deviceId);
};
this.receiveHandlers = partitionIds.map(id => this.eventHubClient.receive(id, onMessage, onError, {
eventPosition: EventPosition.fromEnqueuedTime(Date.now()),
consumerGroup: this.consumerGroup,
}));
} catch (ex) {
console.error(ex.message || ex);
}
}
// Close connection to Event Hub.
async stopReadMessage() {
const disposeHandlers = [];
this.receiveHandlers.forEach((receiveHandler) => {
disposeHandlers.push(receiveHandler.stop());
});
await Promise.all(disposeHandlers);
this.eventHubClient.close();
}
}
var { EventHubClient, EventPosition } = require('#azure/event-hubs');
const eventHubReader = new EventHubReader(connectionString, eventHubConsumerGroup);
(async () => {
console.log("Step1")
await eventHubReader.startReadMessage((message, date, deviceId) => {
console.log("Here getting called");
try {
const payload = {
IotData: message,
MessageDate: date || Date.now().toISOString(),
DeviceId: deviceId,
};
printMessage(payload);
} catch (err) {
console.error('Error broadcasting: [%s] from [%s].', err, message);
}
});
})().catch();
the problem is in condition "printMessage" . here I am trying to restrict the emit based on socket deviceID, but it's only working for last connected socket.
Can You please help me in this.
var localSocket;
io.on('connection',function(socket){
localSocket = socket;
})
You're overwriting the same variable, on each new connection, which means it will always point to the last socket connected.
What exactly do you want to do? To send this message to all connected sockets?

nodejs express server stops unexpectedly

I have a server built with express and socket IO. The problem is that every time the server just stops responding 2 minutes after first client connection. Things I have already done/checked:
Express logs show no errors.
socket.IO logs show no errors on server side.
socket.IO logs show ping timeout on client side (after a few successful pings).
Listening to the uncaughtException/UnhandeledRejection doesn't help.
So, when I start the server, and GET /login through the browser, without doing anything else, the server will stop responding on its own after ~2 minutes, and the only way to close it is closing the cmd (CTRL+C doesn't work even after listening to SIGINT).
I'll post the relevant parts of the code here:
server.js
const http = require('http');
const express = require('express');
const socketIO = require('socket.io');
let app = express();
let server = http.createServer(app);
var io = socketIO(server);
app.get(`/login`, (req, res) => {
console.log(`got GET /login`);
res.sendFile(publicPath + '/login.html');
});
io.on(`connection`, (socket) => {
socket.on(`login`, async (details, cb) => {
login(details.username, details.password, async (res) => {
if (res === true) {
let user = new User(details.username, details.password);
let token = await user.generateAuthToken();
cb(`/set?token=${token}`);
} else if (res === false) {
socket.emit(`loginFailed`, 'Password incorrect. Please try again!')
} else if (res === `Username doesn't exist`) {
socket.emit(`loginFailed`, res)
}
});
});
socket.on('getAllLeads', async (callback) => {
leads = await getAllLeads();
callback(leads);
});
socket.on('getSomeLeads', async (options, callback) => {
leads = await getSomeLeads(options);
callback(leads);
});
socket.on(`newFile`, ({text, words, options, fileName}) => {
console.log('Starting...');
words.forEach((wordSet, index) => {
wordSet.forEach(word => {
regexp = `\\s+${word}+\\s`;
re = new RegExp(regexp);
text = text.replace(re, `${options[index]}`);
});
});
fs.writeFileSync(`${fileName}.txt`, text, 'utf8');
console.log(`Finished writing to ${fileName}.txt successfully!`);
});
});
server.listen(3000, () => {
console.log(chalk.yellow(`Server is up on port ${port}`));
});
login.js
const socket = io();
socket.on(`reconnect_error`, (err) => {
console.log(err);
});
socket.on(`connect`, () => {
console.log('connected to login page');
if (getQueryVariable('failed')) {
$('.error, .error p').css('opacity', 1);
$('.error p').text('Unauthorized! PLEASE log in.');
}
socket.on(`loginFailed`, (reason) => {
$('.error, .error p').css('opacity', 1);
$('.error p').text(reason);
console.log(`got login failed`);
});
$('#form').submit((e) => {
e.preventDefault();
let username = $('#username').val();
let password = $('#pass').val();
socket.emit(`login`, {username, password}, (path) => {
window.location.href = path;
});
});
});
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) return pair[1];
}
return (false);
}

SocketIO Identify what user disconnected

I am making a simple Node.js game that uses Express, Socket.io, and an Http server. All of the users are stored in a multidimensional object on the server. This is how the server-side code works:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.use(express.static(__dirname + '/'));
var playerList = {};
createPlayer = function(array,width,height,spdx,spdy,x,y,color,name,id) {
var player = {
width:width,
height:height,
spdx:spdx,
spdy:spdy,
x:x,
y:y,
wKeyDown:false,
aKeyDown:false,
sKeyDown:false,
dKeyDown:false,
color:color,
name:name,
id:id
}
array[id] = player;
}
io.on('connection', function(socket) {
socket.on('new player', function(id, name) {
id = parseInt(id);
if (!playerList[id]) {
createPlayer(playerList,25,25,4,4,Math.round(Math.random() * 800),Math.round(Math.random() * 600),randomColor(),name,id);
}
socket.on('pressW', function(id, keyDown) {
playerList[id].wKeyDown = keyDown;
});
socket.on('pressA', function(id, keyDown) {
playerList[id].aKeyDown = keyDown;
});
socket.on('pressS', function(id, keyDown) {
playerList[id].sKeyDown = keyDown;
});
socket.on('pressD', function(id, keyDown) {
playerList[id].dKeyDown = keyDown;
});
});
socket.on('disconnect', function() {
});
};
sendPlayerList = function() {
//newPlayerList is used to prevent client from seeing other users IDs
var newPlayerList = {};
var count = 0;
for (var q in playerList) {
player = {
x:playerList[q].x,
y:playerList[q].y,
width:playerList[q].width,
height:playerList[q].height,
color:playerList[q].color,
name:playerList[q].name,
}
newPlayerList[count] = player;
count++;
}
io.emit('edit playerlist', newPlayerList);
}
SPLInterval = setInterval(sendPlayerList, 1000);
Here is the client-side code for connection:
var id;
$('#playbutton').click(function() {
var name = document.getElementById('name').value;
id = Math.floor(Date.now() * Math.random());
socket.emit('new player', id, name);
});
On the client-side, in the update loop, when the game wants to tell the server your input, it emits your input like so:
update = function() {
ctx.clearRect(0,0,canvas.width,canvas.height);
if (document.hasFocus()) {
socket.emit('pressD', id, dKeyDown);
socket.emit('pressS', id, sKeyDown);
socket.emit('pressA', id, aKeyDown);
socket.emit('pressW', id, wKeyDown);
}else{
socket.emit('pressD', id, false);
socket.emit('pressS', id, false);
socket.emit('pressA', id, false);
socket.emit('pressW', id, false);
}
clientUpdatePlayer();
updatePlayers();
}
}
var updateInterval = setInterval(update, 31.25);
The function to update players just draws players based on the player list sent from the server.
My problem is that when a user disconnects, they stay in the player list.
I don't understand how I should go about fixing this. I identify users by getting the ID they send from the client, but I can't get the user's id when they disconnect.
There is a lot more code, but I tried to only include the code that I thought was necessary. I am willing to include more code if that is needed.
You could just store the id value in the parent scope, which the disconnect event handler would have access to:
io.on('connection', function(socket) {
var userId;
socket.on('new player', function(id, name) {
userId = id = parseInt(id);
// ...
});
socket.on('disconnect', function() {
delete playerList[userId];
});
};
Maybe I'm late to the party but I was stuck with something similar and found it the hard way and this may help someone.
The best way to detect if the user is disconnected is would be to first set the username in socket session.
Send the name from the client on emit
socket.emit("newUser", username);
and on server
socket.on('newUser',function (username) {
// we store the username in the socket session for this client
socket.username = username;
});
and when the user disconnects find that on the disconnect event
socket.on('disconnect', function () {
var connectionMessage = socket.username + " Disconnected from Socket " + socket.id;
console.log(connectionMessage);
});
and you can take it from there.
This worked for me:
On every new connection or user who comes online generate a socket Id, add it to the user object, and add it to the array of all the users online.
const users = [];
io.on('connection', (socket) => {
const socketId = socket.id;
socket.on('user online', (data) => {
users.push({ ...data, socketId });
io.emit('view user online', user);
});
Then in the disconnect, use forEach to loop through each object in the array, then use for to loop through and delete each key in the object:
socket.on('disconnect', () => {
users.forEach((user) => {
if (user.socketId === socket.id) {
for (const key in user) {
delete user[key];
}
}
});
logger(`A user has disconnected`);
});
});
});
Tweak to the way you want.
var users = [];
socket.on('newUser', (username) => {
users.push({
id: socket.id,
username: username
});
});
socket.on('disconnect', () => {
const presentUser = users.find(user => user.id == socket.id);
users = users.filter(user => user != presentUser);
});
We can use socket id for storing data as a refrence in playerList. whenever user will disconnect you can delete element from object according to socket id
var playerList = {};
io.on("connection", socket => {
if (!Object.values(playerList).includes(playername) && playername != null) {
var U_data = {
[socket.id]: playername
};
playerList = { ...playerList, ...U_data };
}
socket.on("disconnect", function(e, id) {
console.log(socket.id);
delete playerList[socket.id];
io.emit("broadcast", Object.values(playerList));
});
}

Resources