Pusher one to one chat structure - javascript

I'm a bit confused on the presence-channels in Pusher's platform, as I'm building a chat application from scratch. Now, I know some of you guys have seen tons of "realtime chat app" topics around, but, I'm looking for a peer-to-peer chat and not the site-wide global thingy. More like a facebook chat, where you can go one-to-one.
Now, I've seen an example in PubNub's demos (named Babel) but, that thing is far from what I'm looking for because I've checked the requests in the console and even if it's not shown, the sent messages between other users are shown in my network request logs too because it's being filtered in JS and not server-side and thats not something I want for sure.
So, coming back to the subject,
I'm aware of the channel / private-channel / presence channel functionality, and I decided to do this:
When opening the app, every user subcribes to his private-user_id channel ( creates, if it doesn't exist already ).
At the same time ( while opening the app ) user1 subscribes to a presence-global channel where others keep track if friends are online.
When others want to send him a message, e.g. user2 to user1, he subscribes to private-1 thereafter javascript will process the events.
Now, I know something's wrong with this because.. if user3 would send a message to user1 he'd subscribe to private-user1 so I guess he'll see the events that user2 is triggering when sending messages to user1 too, right ? Or did I get this wrong ?
I've read in their docs that presence channel is actually a private channel extension, so I'm thinking now.. why using private channels anymore, and then, how can I notify all my friends I'm online.
But then, something else comes up in their docs , telling me that channels provide two important things (among others), from which, first is a way of filtering data and second is a way of controlling access.
How am I supposed to "filter data" since there's no link in their docs, or better, what do you have in mind for a one-to-one chat. I'm sorry if I got all their docs wrong, I had a look on their sample applications but none of them are using the one-to-one technique which I'm looking for.
I am new to Pusher and socket connections etc, but I've learned how to authenticate, how to create , detect and process the events in the channel, and I can create a simple global chat with online members, but, when it comes to private-channels I'm quite confused on how to create separate channels for two users.
Thanks in advance !

The purpose of private channels is to restrict who can subscribe to that channel. So, you can either:
Use it to ensure only a users friends can subscribe to updates
Use it for notifications only for that user
In one-to-one chat I'd suggest you choose the latter (No.2).
With this in mind I'd set out achieving one-to-one chat as follows:
The Forum
When users join the chat application they all subscribe to two channels:
private-notifications-<user_id> where user_id is their unique user ID e.g. leggetter in my case. This channel is utilised for user-specific notifications.
presence-forum for all users in that forum. The question called this presence-global.
This is achieved as follows:
var notifications = pusher.subscribe( 'private-notifications-user_one' );
var forum = pusher.subscribe( 'presence-forum' );
Upon subscription to each channel the channel authentication process will take place.
Within the forum you could have a general public chat on the presence-forum/presence-global presence channel by sending and receiving messages.
Starting one-to-one chat
When one user (user_one) wants to have a private chat with another user (user_two) you obviously need something in the UI to trigger this. Say user_one clicks on something next to user_two that indicates they want a one-to-one chat. When this happens a request should be made to the server (the authority) to indicate that user_one wants to initiate the private chat with user_two†.
Note: † if you chose a channel naming convention for one-to-one chat the private channel authentication could actually be used as the private one-to-one chat initiation
When the server receives this request it can generate a unique private channel name for this one-to-one chat. A really simple way of doing this is by concatenating the user IDs e.g. private-chat-<initiating_user>-<receiving_user> (there are other considerations e.g. maybe you want to ensure the channel name is always the same between the two users). In our simple scenario the channel name would be private-chat-user_one-user_two.
The server can then trigger a one-to-one-chat-request event on the private notification channel for each user delivering the one-to-one private chat channel name in the payload.
// Trigger event on both user channels with one call
var channels = [ 'private-notifications-user_one', 'private-notifications-user_two' ];
// Additional event data could also be sent
// e.g. more info on the initiating user
var eventData = {
'channel_name': 'private-chat-user_one-user_two',
'initiated_by': 'user_one'
'chat_with' : 'user_two'
};
pusher.trigger( channels, 'one-to-one-chat-request', eventData );
When user_one receives the one-to-one-chat-request they will subscribe to the eventData.channel_name channel and the auth process will take place for that channel.
// A lookup of private chats
// where the key is the user ID of the current user is chatting with
var privateChats = {};
notifications.bind( 'one-to-one-chat-request', function( data ) {
// MY_USER_ID would need to be stored somewhere
// and in this case the value would be 'user_one'.
// expectingChatWith should make sure user_one is waiting for
// a private chat response with the given user
if( data.initiated_by === MY_USER_ID &&
expectingChatWith( data.chat_with ) ) {
startPrivateChat( data.chat_with, data.channel_name );
}
} );
function startPrivateChat( withUserId, channelName ) {
privateChats[ withUserId ] = pusher.subscribe( channelName );
}
When user_two receives the one-to-one-chat-request the user will need to be notified about the request and either accept or decline it. If the user accepts then the client-side code simply subscribes to the channel. If the user declines then a request should be sent to the server and an event triggered on private-notifications-user_one telling them their one-to-one chat request was declined. This will allow user_one to unsubscribe from the private chat channel.
var privateChats = {};
notifications.bind( 'one-to-one-chat-request', function( data ) {
if( ... ) { ... }
// has somebody request to chat with this user?
else if( data.chatWith === MY_USER_ID ) {
// Prompt the user
// Note: more user info required
displayChatPrompt( data );
}
} );
// callback when the user accepts the chat request
function accepted( chatUserId, channelName ) {
startPrivateChat( chatUserId, channelName );
}
// the user doesn't want to chat
function declined( chatUserId ) {
// send info to the server indicating declined request
}
Private one-to-one chat success
With both user_one and user_two subscribed to private-chat-user_one-user_two they can trigger events on the channel and participate in their private one-to-one chat.

Related

Ideal way to handle multiple socket rooms?

So I am creating a chat application and I want to handle multiple chat rooms. Now I watched some tutorials and came up with a way.
const io = require("socket.io")(http);
io.on("connection", (socket) => {
socket.on("joinRoom", (roomid) => {
//Joining the room
socket.join(roomid)
//Broadcasting all previous messages
io.to(roomid).emit("messages",allPreviousMessages)
})
socket.on("chatMessage", (data) => {
//Saving msg to dB then broadcasting
io.to(roomid).emit("message",receivedMessage)
})
socket.on("disconnect",(data) => {
//updating user's lastSeen info in dB
})
})
So on my frontend when user clicks on a chatroom we call the "joinRoom" event and connect to the room and on clicking another chatroom make the same process of joining room.
Is this an ideal way for handling multiple chatrooms? If not so please let me know a better solution.
I think the best way to implement private rooms or channels or chats is this way. I have implemented an example for these three sections. Link
User token and api must be authenticated before connecting to socket.io. If this part is ok it will connect to socket.io otherwise it can't cause you to see the event that is there. Something happens by calling each of them. For example, by calling this onNotificationForVoiceCall event, the received data is first checked, then it is checked whether this user is present in the list of online users or not, and the state of the next step is checked. Whether or not this room has already been created in the database, the response of all these operations is returned to the user by socket.emit,
And I fixed some bug in project.

Notification system using socket.io

I am trying to create a notification system for my Events Manager Website.
Whenever a user is logged in and does something (for example if he creates an event), the notification that he has created an event should be sent to the other users .The notifications should be available in the '/notification/:username' page which is authenticated (where the username is unique for each user).
Also if the user creates a private event ,the notification must be sent to the concerned users.
I am using Nodejs(Express),socket.io,Vanilla javascript,mysql.For this if I am correct, I need to store the clientid(in my case,it is username) and socketid in database(mysql) as key value pairs .But I am so confused on how to proceed ? I don't know how to get the socketid for different users and store in database. I have no clue how to proceed. It would be really great if someone explain me what should I do ,what are the things I need to tackle the problem,etc. Thank you!
I'm gonna try to explain some various ways to you can proceed.
So from my understanding you already have a users system attached to your mysql and such so you know the users info right?
So I'm an auth user on ur system and i can create an event, when it's public it need's to be sended everyone right?
what you can use for this is following:
io.emit('event-created', data);`
// so this part from server is gonna send everyone
// your frontend part should recieve this event.
socket.on("event-created", function(data) {
// this is gonna be a public event anyway so everyone can see it
// so here you have your newly created event's data so use it as needed =)
});
So the second part is a bit more complicated, it needs to be private right?
Here what you can do, you told us that u'r username's are uniq right? so everyone can join its own room named as their username so in your connection you might do something like this:
io.on("connection", (socket) => {
// I'm assuming that you have a username here
socket.join('uniq-username');
});
What we have here that lets say for me, if you want to send an event only named halilcakar this is my username and u already added me to my room.
What you can do is
io.in('halilcakar').emit('some-event', { ...withSomeData });
When u use this in somewhere in your code, and if i'm online this event is gonna be only seeable by me.
So What i'm trying to tell again assuming that while creating private event we need to select which user is included with this am i right?
So lets right in this code.
// this part is already in your connection
// create a private event
socket.on('private-event-created', data => {
// here the data coming from frontend
// while sending this down just make sure data
// has a property called users as usernames and array maybe?
// since you created this event what you can do is send an emit to
// each user like following:
data.users.forEach(username => io.in(username).emit('private-event', data));
// So basicly we are sending this event to
// every user selected by the creator of this private event
// again the part giving this ability is | .in(username) | this part
});
After everything, you need to listen for this private-event event from socket.io
// so on frontend
socket.on('private-event', function(data) {
// here do the private events objectives.
// and the good part that even me(single user)
// I'll know who has this private-event by
// data.users
});
I'm hoping this will gave you more ideas :)) Please feel free to ask 😊😊

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

pusher client events triggering for user

I am working on a realtime application at the moment, that allows a user to add other others to a "team", I am wanting to notify a user they have been added in realtime. However I can not for the life of me work how to implement this, I think it is the documentation that is confusing me more than anything,
The user must be subscribed to the channel that the event is being triggered on
How can this be possible? Surely by the fact that I am trying to add some one to a group in a realtime, and that group is a unique channel they cannot me subscribed too it?
Here is my attempt, and explanation of the process.
1) A user logins into an app and is subscribed to a unique to them channel "presence-user_{id}" where id is there user id from the database.
2) A user elsewhere wants to add a user to their group, a select that user which runs the following code,
App['presence-' + 'user_' + newUser.get('id')] = App.pusher.subscribe('presence-user_' + newUser.get('id'));
App['presence-' + 'user_' + newUser.get('id')].trigger('client-user-added', { project : self.model.get('id') });
newUser is a backbone model detailing the user details of the selected user. What I am trying to do here is subscribe the user who is trying to a user to a group to that users channel.
This should then fire this method listenter,
App['presence-' + 'user_' + App.Session.get('user_id')].bind('client-user-added', this.RT_addProject, this);
This in turn should fire the following the method on the added clients application,
RT_addProject: function(data) {
console.log(data);
},
however it does not. What is the correct method of triggering client events?
TLDR; I want notify a user in realtime when they are added to a group using pusher.
There key points to presence channels are:
The presence channel name should be shared between the users that you wish to join the group - the act of subscribing to the channel adds them to the channel.
Each user on a channel needs a unique ID that you supply via your server (probably the user session ID) via an auth callback. This uniquely identifies them on the channel.
If the authentication is successful they are subscribed. Upon subscription the client get an initial list of existing users on the channel via the pusher:subscription_succeeded event.
Existing subscribers to the channel are notified of the presence of new users via pusher:member_added events.
As users go offline (close the browser, navigate between pages or unsubscribe via code) all other users on the channel are informed that the user has left the channel via pusher:member_removed events.
For example. On the client:
var pusher = new Pusher(APP_KEY);
var chatChannel = pusher.subscribe('presence-chat');
chatChannel.bind('pusher:subscription_succeeded', function(members) {
members.each(memberAdded);
});
chatChannel.bind('pusher:member_added', memberAdded);
chatChannel.bind('pusher:member_removed', memberRemoved);
function memberAdded(member) {
// add to UI etc.
}
function memberRemoved(member) {
// remove from UI etc.
}
The act of subscribing results in the Pusher JS library making an auth POST request to a server endpoint.
Server pseudo code:
var channelName = authRequest.postParam['channel-name'];
var socketId = authRequest.postParam['socket-id'];
var pusher = new Pusher(YOUR_CREDENTIALS);
if( currentSessionUserAllowedToSubscribeToChannel(channelName) ) {
var uniqueUserId = getCurrentSessionUserUniqueId();
var customUserInfo = getCustomUserInfo();
var authSignature = pusher.presence_auth(channelName,
uniqueUserId,
customUserInfo);
send200AuthJSONToClient(authSignature);
}
else {
send401ToClient();
}
What is the correct method of triggering client events?
Client events can only be triggered on private or presence channels once the subscription has succeeded:
var pusher = new Pusher(APP_KEY);
var chatChannel = pusher.subscribe('presence-chat');
chatChannel.bind('pusher:subscription_succeeded', function() {
var wasTriggered = pusher.trigger('client-event', {some: 'data'});
console.log(wasTriggered);
});
TLDR; I want notify a user in realtime when they are added to a group using pusher.
If userA wants userB to join channelX then you need to signal userB about the existence of channelX so they can subscribe to it.
userB can only receive events on channels they are subscribed to. So, userA somehow needs to send an event on a channel userB knows about. A common solution to this is to have a per user channel e.g. userA-notifications and userA needs to trigger an event on this channel via the server. Ultimately you need an authority to stop unwanted events from being triggered.
This answer is relevant to the process described above: https://stackoverflow.com/a/28165893/39904

Checking whether user subscribed to certain channel in socket.io

Is it possible to check whether a user has subscribed to a certain channel in socket.io?
Let´s say I have a channel called news. User subscribed to that channel on client-side. But the site´s data is dynamic, therefore the news-tab of the site might not be open at any time. I do not want to create the content for the news-tab if the news tab is not open on the client-side. I know that the news-tab is not open, when the user has not subscribed to the news channel.
Is there a way to check that?
You can use io.sockets.clients('news') and this will return the socket id's of all clients. If you know the client socket.id, you could also call io.sockets.manager.roomClients[socket.id] (room name will have '/' leading character)
This is a sample I use for my admin clients to call to get client counts per room:
socket.on('countClientsInRoom', function (room, callback) {
var count = io.sockets.clients(room).length;
callback(count);
});

Categories