Creating a private room through a link using node.js - javascript

I've read through this:
https://socket.io/docs/rooms-and-namespaces/#
private chat with socket.io
What I'm trying to do is have a public chat on:
"/"
And a private chat on /xyz, where everyone that's using this URL can talk in.
I'll get to generating random links and figuring them out later, but first I need to figure out how to connect public users and private users to different sockets? Especially since they're doing the same thing I have no idea how to do this efficiently at all.
So firstly I have to catch a server/private URL using:
app.get("/private",function(req,res){
res.render("page");
console.log("Rendered private page"); });
The solution I've thought of first is using a custom namespace.
var namespace = io.of('/private');
namespace.on('connection',function(socket){
console.log('someone connected to private');
socket.emit('pmessage',{message:'Connected to a private chat!'});
});
But this becomes an issue with my frontend(which I know not how to manipulate since I'm very new to this). I'd basically be using duplicate code to handle the same thing, just with different subset of users.
So this:
var socket = io.connect('127.0.0.1:8090');
I need to add a new socket, right:
var private = io.connect('127.0.0.1:8090/private');
Then do I just duplicate everything? I know this is probably not the right solution. But I don't know where to turn to. Basically making everything for private instead of socket.
socket.on('message',function(data){
//Type out the message in the htmlTextField into the htmlChatContent if message was received
//Keep the other chats
if(data.message){
//From w3c:The push() method adds new items to the end of an array, and returns the new length.
//Example: ["hi","hello"] ---push("wazzzaaap")--->["hi","hello","wazzzaaap"]
messages.push(data);
//put messages into the HTML code
var html = '';
console.log("Currently in messages" + data.message);
console.log(data.username);
for(var i = 0;i<messages.length ;i++){
//Put it into a string and add a HTML defined symbol sequence for a line break
//Add username in front of it in bold
//FIXME: Currently only able to get messages[i] which is just the content
if(messages[i].username==null){
html+=messages[i].message + '<br />';
}
else{
html+='<b>'+ messages[i].username + ': </b>' + messages[i].message + '<br />';
}
}
//Add the message formatted into HTML into the chat content box
htmlChatContent.innerHTML = html;
//When sending clear the input field also
htmlTextField.value = "";
}
else{
//This means there was an error
//Put error text inside the users text box
console.log("Error");
htmlTextField.innerHTML = "There was an sending error!";
}
});
I'd appreciate guidance on how to handle randomly generated links, what I've thought of is:
Database of created links, that removes entries the second the last person leaves. However how do I program dynamic links? I can't hardcode 500 different options, right?
Do I need to add more code for the question to be better?

I'll get to generating random links and figuring them out later, but first I need to figure out how to connect public users and private users to different sockets?
No, you need one socket between the client and your server. You can then send data to just some of the aockets from your server. Socket.io got rooms for that, which basically just means that you can manage the sockets in groups and you send data to the sockets in that group easily.
I can't hardcode 500 different [sockets / links], right?
No, that would be overkill. Just let the client / server generate random urls. To make them unique you could just take the timestamp and add a random number:
const id = "" + Math.floor(Math.random() * 1000) + Date.now();
Now if you want to manage/verify clients on the http server, you could just work with dynamic urls, like:
yourserver/private/1272271737
With express thats quite easy to catch them all:
app.get("/private/:id", (req, res) => {
const { id } = req params;
// Verify the id and return the clientside code
});
But actually only the socket server needs to know the rooms id, so you could use so called "hashbang urls", they look like:
yourserver/private#127272
On the serverside it looks like if the client visits /private so you can just return the application:
app.get("/private", (req, res) => /*...*/);
But on the clientside you can get the id as:
const id = location.hash;
Now the client can join the related room:
socket.join(id);
Now when sending a message just send the room id with it:
socket.emit("msg", id, "Hi!");
On the server, you just broadcast it to that room:
io.on('connection', (socket) => {
socket.on("msg", (id, msg) => {
io.to(id).emit("msg", msg);
});
});

Related

SignalR multi user live chat desynchronisation

I have a live chat in which multiple people would be connected simultaneously. All public messaging works fine, but sometimes private messaging to a specific id doesn't work. I believe i narrowed it down to when people disconnected and reconnected that they connected to a different instance (perhaps IIS had recycled and started a new hub).
I thought I had fixed it, but I haven't and now I'm here because I'm stuck. What I thought would fix it was changing the connection variable within the startChat() function to refresh it with the correct information.
This is a cut down version of the code, as I didnt thing the rest would be necesary.
Issue is that when connected to signalR recipients of a message directly to them doean't come through, even though the chat Id it's being sent to it correct. Possible hub/socket mismatch?
var chat = $.connection.chatHub;
$(document).ready(function () {
// Start the chat connection.
startChat();
//restart chat if disconnected
$.connection.hub.disconnected(function () {
setTimeout(startChat(), 5000);
});
$.connection.hub.error(function (error) {
$('#messagebar').html('Chat ' + error + '. If this message doesn\'t go away, refresh your page.');
});
chat.client.addToChat = function (response) {
$('#chat-' + response.Type).prepend(response.Message);
};
});
function startChat() {
chat = $.connection.chatHub;
$.connection.hub.start().done(function () {
//get recent chat from db and insert to page.
//also saves user's chat id to their user for lookup when private messaging
$.ajax({
method: 'POST',
url: '/api/Chat/SetupChat/'
});
$('#messagebar').html('Connected to chat.');
});
}
Any help appreciated, Thanks.
Not sure exactly how your message is going missing, but you should send messages to a User instead of by connection id. This way you be able to identify on the server that a User has at least one connection, and send messages to all connections for that User. if a given connection id is no longer valid, but another one is (because the client has refreshed the page for example) the message wont be lost.
From the docs https://learn.microsoft.com/en-us/aspnet/core/signalr/groups:
The user identifier for a connection can be accessed by the
Context.UserIdentifier property in the hub.
public Task SendPrivateMessage(string user, string message)
{
return Clients.User(user).SendAsync("ReceiveMessage", message);
}
Not sure how this relates exactly to your server code, but you could add it if you're unclear how to progress.

Is there some way of importing a variable(specifically map) from the server side onto the client side(Socket.io)?

I am trying to assign unique colors to each different client( by using socket.id ). In my map() I have paired (socket.id,randomcolor()), but this variable is on the server side. I've found out that the require() statement doesn't work on client side,
why is that and what is a solution to it? I want to be able to pass map() variable to the client side so that it uses the color assigned to that socket.id and displays the color accordingly.
Or is there some way to know the socket.id on the client side(I don't think it is but not sure), specifically a users computer has to know who sent the message i.e. what socket.id was used to send the message, Is it possible to know that?
Here's my server side:
var express = require('express');
var app = express();
app.use(express.static('public'))
var http = require('http').createServer(app);
var io = require('socket.io')(http);
const map = new Map();
io.on('connection', function(socket) {
console.log('connected by ' + socket.id);
map.set(socket.id, RandomColor())
socket.on('chat', function(data) {
//emitting to all sockets connected
io.emit('chat', data);
console.log(map.entries());
});
socket.on('typing', function(data) {
socket.broadcast.emit('typing', data);
})
});
http.listen(3000, function() {
console.log('listening on port 3000');
});
Here's client side :
// import '../index';
var socket = io.connect('http://localhost:3000')
var message = document.getElementById('Message');
var handle = document.getElementById('Handle');
var btn = document.getElementById('Send');
var output = document.getElementById('Output');
var feedback = document.getElementById('Feedback');
var ids = []
console.log(server);
//emit event
btn.addEventListener('click', function() {
socket.emit('chat', {
message: message.value,
handle: handle.value,
})
})
message.addEventListener('keypress', function() {
socket.emit('typing', handle.value)
})
messageArray = []
//listening for any message received
socket.on('chat', function(data) {
// console.log(data);
feedback.innerHTML = ""
var item = document.createElement('li')
item.innerHTML = "<span style=\"font-family:\"cursive\";\" ;><strong>" + data.handle + ": " + data.message + "</strong></span>";
document.getElementById('Output').appendChild(item)
})
//listening for any typing event listener
socket.on('typing', function(data) {
feedback.innerHTML = "<p><strong>" + data + " is typing a message </strong></p>";
})
PS: Also, I'm new to JS and Socket.io so please suggest some good practices for anything in the code.
First of all, JS has no built-in include/reference property.
So you can't just join another file into another file. But some libraries achieve this with their own written methods etc.
A JS executed on the client-side is not able to access local files. Although you may access an online file load into the document or to an object. So similar functionality can be achieved via 3rd party scripts.
Node.JS follows the CommonJS module system and uses the power of being able to access the local file system.
About the index: So you don't need a Map and Map is pretty similar to a standard object, main difference is might be the order of contents.
But since all you need is a dictionary object. Just create a simple object. Then you can emit the color index whenever you want.
const colorIndex = {}
colorIndex[socketID] = color
Each can set their color on client-side and send it to the server, on each update server has to update every other client about the color.
A client cannot know other clients otherwise wouldn't be secure and it doesn't work like that. It works more like you are calling someone and the server is a middle man that connecting you two.
So, create an object, store socket ids, nicknames, any other info you need. Keep it on serverside, on each message send all of them together with the message.
const users = {}
io.on('connection', function(socket) {
users[socket.id] = {//Add new user
color:RandomColor()
}
socket.on('chat', function(message) {
let u = users[socket.id];//Get user from index
let data = {//Create a message package
user:(u.username)?u.username:"Guest", //name of the user if set
color:u.color,//color of user
message
}
io.emit('chat', data );//Send
});
socket.on('setColor', function(color) {//User can update color
users[socket.id].color = color
});
socket.on('setname', function(name) {//User can update username
users[socket.id].username = name
});
});
So you probably get the idea. There are bunch of ways to achieve.
I don't think you could send that map as an argument, but you can't try creating an array of arrays and emit it to an event like io.emit(colors, array) and once you have it on the client side you can transform back to a map using something like map or reduce
RequireJS is responsible to handle dependencies and ensure that you have everything you need. It is a Javascript library which can work anywhere you use Javascript at, including your server and client-side. The reason it does not work on your client-side (which manifests in the error you see) is that it's not configured on your client-side.
You can read about configurating RequireJS as well.
However, if you set it up properly on your client-side, then there might still be issues, particularly if you try to use on your client-side something which is available on the server. Client-side is a browser, potentially very far from the server. Luckily there is a client API for Socket.IO.
EDIT
Server and client-side can share values in several ways:
WebSockets (a duplex protocol which should be chosen if available in most cases)
Push notifications
AJAX
Page load
Forever frame (that's a hack which should be avoided)

Socket IO and Dynamic Rooms

If anyone is experienced with Websockets / Socket IO hopefully you can point me in the right direction.
I made a Discord Clone and I'm trying to optimize it to scale better. Right now when a user sends a message I query the DB for all users part of that server, and emit a message to their specific socket. This is obviously not going to scale well as every message requires a expensive query and lookup in the client list
// Emit messages to only users part of specific server
// Will only return list of users part of server and active in last 10 minutes
sqlQuery = `SELECT userservers.user_id FROM userservers
JOIN users ON users.user_id = userservers.user_id AND users.user_last_active > (NOW() - INTERVAL 10 minute)
WHERE server_id = ${sql.escape(serverId)}`;
const users = await sql.query(sqlQuery);
action = { type: "message", payload: msg };
// Iterate over users, and find them in clients list
// Emit over socket only to that user
users.forEach((user) => {
clients.forEach((client) => {
if (client.userId === user.user_id) {
io.to(client.id).emit(user.user_id, action);
}
})
});
However using Rooms for each Sever would eliminate my need to query the DB. I understand I can do this when the socket server first starts
// Get server list from Mysql DB
servers.forEach((server) => {
socket.join(server.name);
}
However my issue becomes, when a user create a new server once the application is already running It will not update the list.
I am probably missing some concept on creating dynamic rooms.
EDIT : I am thinking the solution could be that every time a "server" is created, I send a message to the socket server so it can join that "room"
Right now when a user sends a message I query the DB for all users part of that server
I think you can submit broad cast message to all online users, so instead of forEach client => io.to(clientId) you can submit broad cast message to all connected users io.emit('some event', { for: 'everyone' });
also I'm wondering why you are creating many servers? you can divide your server into namespaces by using const namespace = io.of('/thisIsASeparateNamespace'); and also you can submit broadcast messages to all users inside this name space by namespace.emit('some event', { for: 'everyone in name space' });
So your chat structure can be like this
Server
Namespaces // for separate chat app / or like slack work spaces
Rooms // for group chatting
ClientID // for one to one

socket.io assigning custom socket.id

I keep track the list of every users connected in the array.
So if there is a new connection, it will check whether the user is already on the list or not, if he was already on the list, then assign their socket.id with the corresponding socket.id on the list, otherwise just add them to the list.
It's for preventing same user counted as 2 user while he attempt to do multi-login.
Object.keys(client).forEach(function (key) {
if (client[key].id == data.id){
is_connected = true;
socket.id = key;
}
});
I have no problem handling the messages/chat that was sent/received by the user who attempt multi-login.
socket.on('chat', function(msg){
var data = {"name": client[socket.id].name, "message": msg};
io.emit('chat', data);
});
The io.emit for the chat message was succesfully sent to the user who attempting multi-login.
The problem I got was whenever the user decide to logout/disconnect from the server.
io.emit('user_leave', client[socket.id].id);
[Multi-Login Case] -> Multi-User and Dual-User are same user attempting Multi-Login
Whenever the Main-User disconnected from the server, the Dual-User received 'user_leave' sent by the server, because io.emit supposed to send it to all sockets.
But not otherwise, while the Sub-User disconnected from the server, the Main-user do not receive 'user_leave' emitted by the server.
*Note: Main-User is login first, then the Dual-User. So the Main-User information was saved directly in the array, while the Sub-User socket.id was assigned with the Main-User socket.id
[Update]
B2 socket.id was assigned with B1 socket.id, the io.emit for chat work perfectly while io.emit for disconnect only emitted to All except Dual-User(B2)
socket.id is used internally by socket.io for its own socket list. You cannot overwrite that or you break some of its ability to maintain its own data structures.
You have two choices:
You can use the existing socket.id value as is (without overwriting it) so you don't break existing behavior. It is already guaranteed to be unique on the server.
You can use a different property name for your own id such as socket.userId and then you won't conflict.
If you need to, you can maintain a map between your own custom id and the socket.io socket.id so you could get to one from the other.
Similar question here: Socket.io custom client ID
generateId prop of io.engine object can be used for to set the custom id.
Using this way, the all socket ids can be created on the server side without any issue.
Actually I wrote an answer for a similar question today.
An example:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
io.engine.generateId = function (req) {
// generate a new custom id here
return 1
}
io.on('connection', function (socket) {
// listing the default namespace rooms
console.log("rooms: ", io.nsps["/"].adapter.rooms);
})
The console output would be as the following:
rooms: { '/#1': Room { sockets: { '/#1': true }, length: 1 } }
It seems to be it has been handled.
It must be in mind that socket id must be unpredictable and unique value with considering security and the app operations!
Extra: If socket.id is returned as undefined because of your intense processes on your generateId method, async/await combination can be used to overcome this issue on node.js version 7.6.0 and later. handshake method of node_modules/engine.io/lib/server.js file should be changed as following:
former:
Server.prototype.handshake = function (transportName, req) {
var id = this.generateId(req);
...
}
new:
Server.prototype.handshake = async function (transportName, req) {
var id = await this.generateId(req);
...
}

how can you tell which socket connection clicked a button using socket.io?

if you have a a button on the page and you want to make sure the button cannot be clicked again before another socket clicks their button.
if socket a clicked i should disable the button until another socket sends a message that they clicked their button and back and fourth.
$(".buttonTurns").on("click", function(){
socket.emit("turnbutton click")
})
and they dont input any names when they connect
Every socket.io session is automatically assigned a unique string as an id. On the server you can get it from:
socket.id
If you are inside a session (that is, the connection that returns the socket object) you can send messages back to the client by simply doing:
socket.emit('event name',data);
But if you want to send a message to a different session then you need to do:
io.sockets.socket(socket_id).emit('event name',data);
If you want to send a message to all connected sessions then just do:
io.emit('event name', data); // broadcast
If your application have state that needs to be managed, then one technique is to store them in an object using the socket id as the key:
var buttonState = {}
io.on('connection',function('socket'){
// default button to off when socket connects:
bottonState[socket.id] = 'off';
socket.on('on button',function(){
bottonState[socket.id] = 'on';
})
socket.on('off button',function(){
bottonState[socket.id] = 'off';
})
})
Now that you can manage the individual state for each individual client on the server you can use that to communicate them to other clients.
You can set a UUID for each client (either client side javascript or server side javascript).
For a server side solution:
As #slebetman mentioned in the comments, socket.io has a built-in unique identifier for each socket.
as #slebetman suggested in his answer, to get the socket's unique ID () see his answer for more details):
// v0.6.x
var sid = socket.sessionId;
// v0.7.x
var sid = socket.id;
For a client side solution:
If you set the unique ID on the client's side, than you probably need to create your own unique ID.
I took the following code from 6 revs and Briguy37's answer to a question here on this site:
function generateUUID(){
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
};
The randomness should be strong enough for all your clients to have a unique identifier using this ID generator. This should work with the code you wanted to use regarding your question here.

Categories