SignalR multi-room session
My application is as follows
The user sees the rooms on the login screen. When he chooses one of the rooms and types his username, he logs into that room.
Rooms are formed as follows eg:
www.domain.com/room/1
www.domain.com/room/2
Then he texts with other users in the room.
My question is as follows:
When a user connected to a room opens the room link in a different tab, a new "Context.ConnectionId" value is assigned, so the room is created from the beginning. I want the connection to continue as it connects to the same room even if it's on a different tab.
In addition, a user should be able to be in both room1 and room2 at the same time, if he / she wishes.
How can I do that?
You want to look into using "Groups" in SignalR. Users can, depending on your design, open a new tab, log in and join a different room. They will get messages through both in their respective room.
public async Task JoinRoom(string roomName)
{
await Groups.Add(Context.ConnectionId, roomName);
Clients.Group(roomName).addChatMessage(Context.User.Identity.Name + " joined.");
}
Reference Documentation - https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/working-with-groups
Related
I have an application that I need to pass the room the current user has just disconnected from to the server. The user can be in multiple rooms via multiple tabs.
I can detect that the user has left this way but it does not seem to be able to have data passed with it:
#socketio.on('disconnect')
def on_disconnect():
print(session['id'])
print("user left " )
In my client end I have tried this:
socket.on('disconnect', function () {
socket.emit('user_disconnect', {"channel": "{{symbol}}"});
});
This emit never goes through to the server though. I am guessing the window closes or changes before this can get called? How can I pass data to the server on the disconnect event?
I believe I have figured out a solution.
Utilizing request.sid, I can store the room name in a list along with this sid on the server-side join event.
#socketio.on('join')
def on_join(data):
active_rooms.append(json.dumps({'room':data['channel'],'socket_id':str(request.sid)}))
When the disconnect event is triggered I can view this same sid and use it as a lookup in the list. The sid appears to be unique for each socket connection so multiple tabs will result in a new sid.
Initially we were using Domain events to handle communications with external systems. For instance, every time a user was updating his phone number OR his name we raise a PhoneNumberUpdated AND a NameUpdated event. These are then caught by handlers processed and sent to other systems.
public void SetName(Name name)
{
if (Name == name) return;
(...)
RaiseEvent(new NameUpdated(Id, name));
}
public void SetPhoneNumber(PhoneNumber number, PhoneNumberType type)
{
RaiseEvent(new PhoneNumberUpdated());
}
It works great as long as we do not need to "aggregate" events. For example, we got a new requirement asking us to to send one single email whenever a user updates his name and/or his phone number. With the current structure, our handlers would be notified multiples times (one time for each event raised) and this would result in multiple emails sent.
Making our events more generic don't seem to be a good solution. But then how would we aggregate several events raised within one transaction?
Thx
Seb
I believe your new requirement is a separate concern from your actual domain. Your domain generates events that describe what has happened. User notification, on the other hand, is a projection of that stream of events into email form. Just like you would keep your read model requirements separate from your domain, you should keep this separate as well.
A simple solution would be to capture the events you care about into a table and then once a day, on a schedule, and send one email per aggregate.
I am trying to structure a firebase database for chat system. What I wanted to achieve is after user successfully logged in, they will see a list of message they have sent to different users. Each of the message preview will show the last message. Then, user can select on the message to view the full chat details. It should work like the Facebook Messenger.
My design structure as such:
chatMessage
sender *(Assume this one is user)*
threads
threadID1
messageID1
datetime, content, receiver, status
messageID2
datetime, content, receiver, status
threadID2
messageID1
datetime, content, receiver, status
messageID2
datetime, content, receiver, status
sender *(Assume this one is admin)*
threads
threadID1
messageID1
datetime, content, receiver, status
messageID2
datetime, content, receiver, status
The design above allows me to know let's say userID1 logged in, I can retrieve all the messages he sent. However, I am not able to know if there is any reply prior to the message and therefore I am not able to retrieve the last message.
How can I actually restructure it so that I can achieve what I mentioned above? Any suggestions?
Thanks!
It sounds like you want to:
Have chat "rooms" between users
Show a list of each user's chat rooms, with the latest message for that room
If those are your requirements, I'd model precisely those in your database.
So for each chat room (a chat between a certain set of users), model the messages for that room:
chats: {
$roomId: {
$messageId: {
senderId: "..."
message: "..."
}
}
}
Now for each user, model a separate list of their chats and the latest message:
userRooms: {
$uid: {
$roomId: {
"message: "..."
}
}
}
Now whenever a user posts a message to a room, you will need to push that message to /chats/$roomId and for each user in that chat room write the message to /userRooms/$uid/$roomId (overwriting the exiting message there).
This type of data duplication is known as fanning out data, because you're spreading a single snippet of information over multiple places in the database. It is quite common in NoSQL databases, and is part of the reason they scale so well: they trade write complexity for read performance.
I'm trying to create a simple game where rooms are generated for game sessions and destroyed after the game ends. I would like to have users join this room to play the game by just entering a nickname.
Would I need to use the meteor user accounts or is there a simpler way to do this as I won't need any authentication or password of sorts.
I am thinking of creating just a player collection and inserting the nickname when they click to join the room, but I don't know how I can keep track of who 'I am' or keep them tracked if they loose connection and have to rejoin.
But if I were to use user accounts, I'm not sure how to customise it so that a casual/loose user can be easily created and probably destroyed after game sessions without any password or email, etc.
Thanks in advance!
You could just use a collection, but as you say, you would still need to track the user's id, so that would take some effort.
Personally, I would use the Meteor accounts system, using the accounts-token package:
https://atmospherejs.com/andrei/accounts-token
You can generate a token on the server, and then log the user in. They don't need to know that they are logged in.
Using Meteor's accounts system has many advantages in tracking the user, and if at a later stage you do want people to login using accounts-password, accounts-facebook etc you won't need to restructure your app.
You don't need to user Meteor's account system.
Just create a Rooms collection and each document inside that collection will be each "game room," or however you want to call it. Then inside, add a "players" key and store all the players or whatever you need ($pull player who leaves the game, $push player who joins). When the game ends, you can completely remove that MongoDB document via its _id field.
Something like:
db.rooms = {
_id: xzu90udfmd,
players: [
{
tempUsername: "username1",
tempWhatever: "whatever",
token: 3zz97hrnw
}, {
tempUsername: "username2",
tempWhatever: "whatever",
token: 3zz97hrnw
}, {
tempUsername: "username3",
tempWhatever: "whatever",
token: 3zz97hrnw
}
]
};
Then, you can add all the customization in either the individual user objects, or outside that could apply to the entire game room. Something like this would be super easy with React.
Since Firebase security rules cannot be used to filter children, what's the best way to structure data for efficient queries in a basic multi-user application? I've read through several guides, but they seem to break down when scaled past the examples given.
Say you have a basic messaging application like WhatsApp. Users can open chats with other groups of users to send private messages between themselves. Here's my initial idea of how this could be organized in Firebase (a bit similar to this example from the docs):
{
users: {
$uid: {
name: string,
chats: {
$chat_uid : true,
$chat2_uid: true
}
}
},
chats: {
$uid: {
messages: {
message1: 'first message',
message2: 'another message'
}
}
}
}
Firebase permissions could be set up to only let users read chats that are marked true in their user object (and restrict adding arbitrarily to the chats object, etc).
However this layout requires N+1 selects for several common scenarios. For example: to build the home screen, the app has to first retrieve the user's chats object, then make a get request for each thread to get its info. Same thing if a user wants to search their conversations for a specific string: the app has to run a separate request for every chat they have access to in order to see if it matches.
I'm tempted to set up a node.js server to run root-authenticated queries against the chats tree and skip the client-side firebase code altogether. But that's defeating the purpose of Firebase in the first place.
Is there a way to organize data like this using Firebase permissions and avoid the N+1 select problem?
It appears that n+1 queries do not necessarily need to be avoided and that Firebase is engineered specifically to offer good performance when doing n+1 selects, despite being counter-intuitive for developers coming from a relational database background.
An example of n+1 in the Firebase 2.4.2 documentation is followed by a reassuring message:
// List the names of all Mary's groups
var ref = new Firebase("https://docs-examples.firebaseio.com/web/org");
// fetch a list of Mary's groups
ref.child("users/mchen/groups").on('child_added', function(snapshot) {
// for each group, fetch the name and print it
String groupKey = snapshot.key();
ref.child("groups/" + groupKey + "/name").once('value', function(snapshot) {
System.out.println("Mary is a member of this group: " + snapshot.val());
});
});
Is it really okay to look up each record individually? Yes. The Firebase protocol uses web sockets, and the client libraries do a great deal of internal optimization of incoming and outgoing requests. Until we get into tens of thousands of records, this approach is perfectly reasonable. In fact, the time required to download the data (i.e. the byte count) eclipses any other concerns regarding connection overhead.