Laravel Presence Channel: Store Joining and Leaving Users in database - laravel

I have this working:
Echo.join(`chat.${roomId}`)
.here((users) => {
//
})
.joining((user) => {
this.users.push(user);
this.saveStatus(user, 'online');
})
.leaving((user) => {
this.users = this.users.filter(u => u.id != user.id);
this.saveStatus(user, 'offline');
});
That shows me an accurate list of the Users that are present.
But what I want, is to store also their status in the database with a timestamp.
So I thought about this function:
saveStatus(user, status) {
axios.post("/logbooks", { user_id: user.id, status: "offline"})
}
And call that function in the joining and leaving state.
But that's not accurate: it doesn't store values when just one user is online. And when there are 20 users offline, and one user goes offline > it'll store 19 times this value.
Any tips how to get a more accurate online-offline-log to the database?

If you are using Pusher Channels you can configure presence webhooks for your Channels app. You can activate webhooks in your account dashboard on a per app basis.
The presence webhook will be triggered for the following events; member_added and member_removed (user joins a presence channel or user leaves a presence channel (goes offline)):
You can listen for these events on your server and then update the user state depending on what events you are receiving.
If the user can join multiple rooms (channels) at once (e.g. room1, room2, room3) you may want to track whether the user is online or offline by having a user specific presence channel.
For example if the user was called 'Joe Bloggs' you would join a channel called joebloggs when first connecting to your Pusher app. (if the user joins this channel they are online, if the user leaves this channel they are offline)

You could resolve this issue by using Laravel Echo Server (instead of Pusher) and Redis. Here is the documentation:
https://github.com/tlaverdure/laravel-echo-server#presence-channels
Redis handles a publish/subscribe mechanism. Laravel Echo Server publishes each time someone joins/leaves a presence channel, and with an Artisan command, you can subscribe to Redis and react to the join/leave by notifying your database.

Related

Best practice for subscribe and publish architecture for chat-like app

I'd like to know what best practices exist around subscribing to changes and publishing them to the user. This is a pretty broad and vaguely worded question. Therefore, allow me to elaborate on this using an example.
Imagine the following (simplified) chat-like application:
The user opens the app and sees the home screen.
On this home screen a list of chat-groups is fetched and displayed.
Each chat-group has a list of users (members).
The user can view this list of members.
Each user/member has at least a first name available.
The user can change its name in the settings.
And now the important part: When this name is changed, every user that is viewing the list of members, should see the name change in real-time.
My question concerns the very last point.
Let's create some very naive pseudo-code to simulate such a thing.
The client should at least subscribe to something. So we could write something like this:
subscribeToEvent("userChanged")
The backend should on its part, publish to this event with the right data. So something like this:
publishDataForEvent("userChanged", { userId: "9", name: "newname" } )
Of course there is a problem with this code. The subscribed user now gets all events for every user. Instead it should only receive events for users it is interested in (namely the list of members it is currently viewing).
Now that is the issue I want to know more about. I could think of a few solutions:
Method 1
The client subscribes to the event, and sends with it, the id of the group he is currently viewing. Like so for example:
subscribeToEvent("userChanged", { groupId: "abc" })
Consequently, on the backend, when a user changes its name, the following should happen:
Fetch all group ids of the user
Send out the event using those group ids
Something like this:
publishDataForEvent("userChanged", { userId: "9", name: "newname" }, { groupIds: ["abc", "def" })
Since the user is subscribed to a group with id "abc" and the backend publishes to several groups, including "abc", the user will receive the event.
A drawback of this method is that the backend should always fetch all group ids of the user that is being changed.
Method 2
Same as method 1. But instead of using groupIds, we will use userIds.
subscribeToEvent("userChanged", { myUserId: "1" })
Consequently, on the backend, when a user changes its name, the following should happen:
Fetch all the user ids that relate to the user (so e.g. friendIds based on the users he shares a group with)
Send out the event using those friendIds
Something like this:
publishDataForEvent("userChanged", { userId: "xyz", name: "newname" }, { friendIds: ["1", "2" })
An advantage of this is that the subscription can be somewhat more easily reused. Ergo the user does not need to start a separate subscription for each group he opens, since he is using his own userId instead of the groupId.
Drawback of this method is that it (like with method 1 but probably even worse) potentially requires a lot of ids to publish the event to.
Method 3
This one is just a little different.
In this method the client subscribes on multiple ids.
An example:
On the client side the application gathers all users that are relevant to the current user. For example, that can be done by gathering all the user ids of the currently viewed group.
subscribeToEvent("userChanged", { friendIds: ["9", "10"] })
At the backend the publish method can be fairly simple like so:
publishDataForEvent("userChanged", { userId: "9", name: "newname" }, { userId: "9" } )
Since the client is subscribed to user with userId "9", amongst several users, the client will receive this event.
Advantage of this method is that the backend publish method can be held fairly simple.
Drawback of this is that the client needs quite some logic to subscribe to the right users.
I hope that the examples made the question more clear. I have the feeling I am missing something here. Like, major chat-app companies, can't be doing it one of these ways right? I'd love to hear your opinion about this.
On a side note, I am using graphql as a backend. But I think this question is general enough to not let that play a role.
The user can change its name in the settings.
And now the important part: When this name is changed, every user that is viewing the list of members, should see the name change in real-time.
I assume the user can change his name via a FORM. The contents of that form will be send with a HTTP-Reuqest to a backand script that will do the change in a DB like
update <table> set field=? where userid=?
Preferred
This would be the point where that backend script would connect to your web socket server and send a message like.
{ opcode:'broadcast', task:'namechange', newname='otto' ,userid='47111' }
The server will the broadcast to all connected clients
{task:'namechange', newname='otto' ,userid='4711' }
All clients that have a relationship to userid='4711' can now take action.
Alternative 1
If you cant connect your backend script to the web socket server the client might send { opcode:'broadcast', task:'namechange', newname='otto' ,userid='47111' }
right before the FORM is trasnmitted to the backend script.
This is shaky because if anything goes wrong in the backend, the false message is already delivered, or the client might die before the message goes out, then no one will notice the change.

How to handle new_chat_members when a user joined to telegram group with telegram bot?

I'm trying to send a message to user, who joined to a group, with my telegram bot. I add bot to the group and bot is admin in group. and I set the Group Privacy off. I get user with new_chat_member when user joined to group, but the message send to user infinitely. And the update which is for when a member join to group is pending.
if(isset($arrayMessage['message'])) {
if (isset($arrayMessage['message']['new_chat_member'])) {
$text= "hello!";
$url= "https://api.telegram.org/bot".$token."/sendMessage?chat_id=".$chat_id."&text=".$text;
file_get_contents($url);
}
}
I'll assume you are using long polling for this answer. When using the getUpdates method, it will keep retrieving the existing unconfirmed updates until you confirm them by using the offset parameter.
See enter link description here
If you do not confirm the updates, you will keep receiving the same update, thus the infinite messages.

socket.io rejoin rooms on reconnect

I'm writing a new SPA application that will subscribes to several rooms for several types of information updates.
In my production setup I'll use two servers behind a load balancer for reliability.
In the event of disconnect - Does the client have to resend the request for rooms subscriptions on the reconnect event callback, or is there a way to have the server reconnect the client automatically (even when the client reconnects to a different server due to server failure) ?
Socket.io will unsubscribe your users from all rooms on a disconnect. It will unsubscribe you from the server side. I played around with this a little. The server can store your user's rooms in redis or a database under the user ID and, upon connecting, check to see if that user should be in any of these rooms. At which time your user can join them from the server side without ever having to do anything from the client.
The problem is that this list of rooms must be constantly stored and updated. It's just another thing that has to work seamlessly on the backend. It's a lot of tests to consider all the possibilities that could mess up your organization. Like, what if they log in on another device, you have to clear the rooms and put in new ones, but if the user opens his laptop again and it reconnects, now he has to get back in those rooms from his laptop. ...It's totally doable/solvable, but I only did this on the front end:
// rejoin if there's a disconnect
mySocket.on('reconnect', () => {
mySocket.emit('subscribe', 'theRoom')
})
...and no further hassle. If you added some more details about why it's necessary to do it from the server..?
From my experience, I found this to be the easiest and useful solution:
Client side:
// the next 3 functions will be fired automatically on a disconnect.
// the disconnect (the first function) is not required, but you know,
// you can use it make some other good stuff.
socket.on("disconnect", function() {
console.log("Disconnected");
});
socket.on("reconnect", function() {
// do not rejoin from here, since the socket.id token and/or rooms are still
// not available.
console.log("Reconnecting");
});
socket.on("connect", function() {
// thats the key line, now register to the room you want.
// info about the required rooms (if its not as simple as my
// example) could easily be reached via a DB connection. It worth it.
socket.emit("registerToRoom", $scope.user.phone);
});
Server side:
io.on('connection', function(socket){
socket.on("registerToRoom", function(userPhone) {
socket.join(userPhone);
});
});
And thats it. Very simple and straight forward.
You also can add in the connected socket (the last function) some more updates to the user display, such as refreshing its index or something else.
Socket.io does have a reconnect event - Docs here
Something like the below should work
socket.on('reconnect', () => attemptReconnection())
The attempt reconnection callback would look something like:
const attemptReconnection = () => socket.emit('joinRoom', roomId)

Parse Background Job can send a message to all users currently logged in?

I am new to Parse and I want to know if there is a way to schedule a Background job that starts every 3 minutes and sends a message (an integer or something) to all users that at that moment are logged in. I could not find any help here reading the guide. I hope someone can help me here.
I was in need to push information for all logged in users in several apps which were built with Parse.com.
None of the solutions introduced earlier by Emilio, because we were in need to trigger some live event for logged users only.
So we decided to work with PubNub within CloudCode in Parse : http://www.pubnub.com/blog/realtime-collaboration-sync-parse-api-pubnub/
Our strategy is to open a "channel" available for all users, and if a user is active (logged in), we are pushing to this dedicated "channel" some information which are triggered by the app, and create some new events or call to action.
This is a sample code to send information to a dedicated channel :
Parse.Cloud.define("sendPubNubMessage", function(request, response) {
var message = JSON.stringify(request.params.message);
var PubNubUrl;
var PubNubKeys;
Parse.Config.get().then(function(config) {
PubNubKeys = config.get('PubNubkeys');
}).then(function() {
PubNubUrl = 'https://pubsub.pubnub.com/publish/';
PubNubUrl+= PubNubKeys.Publish_Key + '/';
PubNubUrl+= PubNubKeys.Subscribe_Key + '/0/';
PubNubUrl+= request.params.channel +'/0/';
PubNubUrl+= message;
return Parse.Cloud.httpRequest({
url: PubNubUrl,
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
}).then(function(httpResponse) {
return httpResponse;
});
}).then(function(httpResponse) {
response.success(httpResponse.text);
}, function(error) {
response.error(error);
});
});
This is an another sample code used to send a message to a dedicated channel once something was changed on a specific class :
Parse.Cloud.afterSave("your_class", function(request, response) {
if (!request.object.existed()) {
Parse.Cloud.run('sendPubNubMessage', {
'message': JSON.stringify({
'collection': 'sample',
'objectId': request.object.id
}),
'channel' : 'all' // could be request.object.get('user').id
});
}
});
#Toucouleur is right in suggesting PubNub for your Parse project. PubNub acts essentially like an open socket between client and server so that the sever can send messages to clients and vice versa. There are 70+ SDKs supported, including one here for Win Phone.
One approach for your problem would be to Subscribe all users to a Channel when they log in, and Unsubscribe from that Channel when they exit the app or timeout.
When you want to send a message you can publish to a Channel and all users Subscribed will receive that message in < 1/4 second. PubNub makes sending those messages as Push Notifications really simple as well.
Another feature you may find useful is "Presence" which can give you realtime information about who is currently Subscribed to your "Channel".
If you think a code sample would help let me know!
Here's a few ideas I came up with.
Send a push notification to all users, but don't add an alert text. No alert will show for users who have the app closed and you can handle the alert in the App Delegate. Disadvantage: Uses a lot of push notifications, and not all of them are going to be used.
When the app comes to foreground, add a flag to the PFInstallation object that specifies the user is online, when it goes to the background, set the flag to false. Send a push notification to the installations that have the flag set to true. Disadvantages: If the app crashes, you would be sending notifications to users that are not online. Updating the user twice per session can increase your Parse request count.
Add a new property to the PFInstallation object where you store the last time a user did something, you can also set it on a timer of 30s/1m while the app is open. Send a push notification to users that have been active in the last 30s/1m. Disadvantage: Updating the PFInstallation every 30 seconds might cause an increase on your Parse request count. More accuracy (smaller interval) means more requests. The longer the session length of your users, the more requests you will use.

Pubnub chat application with storage

I'm looking to develop a chat application with Pubnub where I want to make sure all the chat messages that are send is been stored in the database and also want to send messages in chat.
I found out that I can use the Parse with pubnub to provide storage options, But I'm not sure how to setup those two in a way where the messages and images send in the chat are been stored in the database.
Anyone have done this before with pubnub and parse? Are there any other easy options available to use with pubnub instead of using parse?
Sutha,
What you are seeking is not a trivial solution unless you are talking about a limited number of end users. So I wouldn't say there are no "easy" solutions, but there are solutions.
The reason is your server would need to listen (subscribe) to every chat channel that is active and store the messages being sent into your database. Imagine your app scaling to 1 million users (doesn't even need to get that big, but that number should help you realize how this can get tricky to scale where several server instances are listening to channels in a non-overlapping manner or with overlap but using a server queue implementation and de-duping messages).
That said, yes, there are PubNub customers that have implemented such a solution - Parse not being the key to making this happen, by the way.
You have three basic options for implementing this:
Implement a solution that will allow many instances of your server to subscribe to all of the channels as they become active and store the messages as they come in. There are a lot of details to making this happen so if you are not up to this then this is not likely where you want to go.
There is a way to monitor all channels that become active or inactive with PubNub Presence webhooks (enable Presence on your keys). You would use this to keep a list of all channels that your server would use to pull history (enable Storage & Playback on your keys) from in an on-demand (not completely realtime) fashion.
For every channel that goes active or inactive, your server will receive these events via the REST call (and endpoint that you implement on your server - your Parse server in this case):
channel active: record "start chat" timetoken in your Parse db
channel inactive: record "end chat" timetoken in your Parse db
the inactive event is the kickoff for a process that uses start/end timetokens that you recorded for that channel to get history from for channel from PubNub: pubnub.history({channel: channelName, start:startTT, end:endTT})
you will need to iterate on this history call until you receive < 100 messages (100 is the max number of messages you can retrieve at a time)
as you retrieve these messages you will save them to your Parse db
New Presence Webhooks have been added:
We now have webhooks for all presence events: join, leave, timeout, state-change.
Finally, you could just save each message to Parse db on success of every pubnub.publish call. I am not a Parse expert and barely know all of its capabilities but I believe they have some sort or store local then sync to cloud db option (like StackMob when that was a product), but even if not, you will save msg to Parse cloud db directly.
The code would look something like this (not complete, likely errors, figure it out or ask PubNub support for details) in your JavaScript client (on the browser).
var pubnub = PUBNUB({
publish_key : your_pub_key,
subscribe_key : your_sub_key
});
var msg = ... // get the message form your UI text box or whatever
pubnub.publish({
// this is some variable you set up when you enter a chat room
channel: chat_channel,
message: msg
callback: function(event){
// DISCLAIMER: code pulled from [Parse example][4]
// but there are some object creation details
// left out here and msg object is not
// fully fleshed out in this sample code
var ChatMessage = Parse.Object.extend("ChatMessage");
var chatMsg = new ChatMessage();
chatMsg.set("message", msg);
chatMsg.set("user", uuid);
chatMsg.set("channel", chat_channel);
chatMsg.set("timetoken", event[2]);
// this ChatMessage object can be
// whatever you want it to be
chatMsg.save();
}
error: function (error) {
// Handle error here, like retry until success, for example
console.log(JSON.stringify(error));
}
});
You might even just store the entire set of publishes (on both ends of the conversation) based on time interval, number of publishes or size of total data but be careful because either user could exit the chat and the browser without notice and you will fail to save. So the per publish save is probably best practice if a bit noisy.
I hope you find one of these techniques as a means to get started in the right direction. There are details left out so I expect you will have follow up questions.
Just some other links that might be helpful:
http://blog.parse.com/learn/building-a-killer-webrtc-video-chat-app-using-pubnub-parse/
http://www.pubnub.com/blog/realtime-collaboration-sync-parse-api-pubnub/
https://www.pubnub.com/knowledge-base/discussion/293/how-do-i-publish-a-message-from-parse
And we have a PubNub Parse SDK, too. :)

Resources