Build a chat app in Flask and Vue with sentiment analysis - Part 4: Adding an online presence feature
You will need Node 8.9+ and Python 3.6+ installed on your machine.
In this part, we’ll see how to add a “who’s online” feature. This feature will make our chat more lively. This way we know if the other participants are online so we know if we’ll be getting a reply soon. The code is available on GitHub.
You can find the previous parts of the series here:
Part 1
Part 2
Part 3
If you don’t have the setup from previous parts, follow the below instructions to get it otherwise skip this.
# Clone the repo
$ git clone https://github.com/dongido001/pusher-chat-sentiment-analysis.git -b part-3
# Go to the project root folder
$ cd pusher-chat-sentiment-analysis
# Install dependencies
$ npm install
Update the .env
file in the project’s root folder with your correct Pusher App key:
VUE_APP_PUSHER_KEY=<PUSHER_APP_KEY>
VUE_APP_PUSHER_CLUSTER=<PUSHER_APP_CLUSTER>
Then run the app:
# Start vue app
$ npm run serve
Then from a new terminal execute the following:
$ cd api
$ python -m venv env
$ pip install -r requirements.txt
$ source env/bin/activate
Update the api/.env
file with your correct Pusher API key:
PUSHER_APP_ID=app_id
PUSHER_KEY=key
PUSHER_SECRET=secret
PUSHER_CLUSTER=cluster
$ flask run
Now you should have both apps running. Both apps should be running on a different port. Note the URL of the Vue.js app in your terminal because this is what you will use to access the chat application.
Presence channels
Presence channels are similar to the private chat channel. It exposes an additional feature of an awareness of who is subscribed to a channel. This will enable us to easily implement “who’s online” functionality into our chat app. When naming a presence channel, it needs to have a prefix of “presence-”.
The flow is much related to the private channel. First, we will subscribe all users logging in to the app to a presence channel we’ll name “presence-chitchat”. Before they are subscribed, Pusher will make a request to our auth endpoint (which we have already) to authenticate the channel.
An important thing to note is that when setting up the authentication for presence channel, you must add a custom data to the authenticate function. The custom data is the information of the user you want to authenticate. The custom data will be passed back to your client’s app so we can use them.
Authenticating channel
First, let’s update the endpoint so that it accommodates authentication for presence channels.
When Pusher makes a request to this endpoint to authenticate the channel, it passes along the channel name and the connected user’s socket ID.
Remember that our routes are protected with JWT. We are using the @jwt_required
decorator to protect the route. When Channels makes the request to the endpoint, it includes a JWT token in its request header. If the token is not valid, it results to a 403 HTTP error. Also, remember that we set the JWT token to be included in the request header while we are initializing Pusher JavaScript client.
Update the pusher_authentication
function in api/app.py
to include custom data:
# ./api/app.py
[...]
@app.route("/api/pusher/auth", methods=['POST'])
@jwt_required
def pusher_authentication():
channel_name = request.form.get('channel_name')
socket_id = request.form.get('socket_id')
username = get_jwt_identity()
user_data = User.query.filter_by(username=username).first()
auth = pusher.authenticate(
channel=channel_name,
socket_id=socket_id,
custom_data={
"user_id": user_data.id,
"user_info": {
"username": user_data.username
}
}
)
return jsonify(auth)
[...]
In the code above,
- We first fetch the channel name and socket ID from the request.
- Then, we fetch the username from the JWT token.
- Next, we query the database with the username to get more information about the user.
- Finally, we call
pusher.authenticate
to authenticate the channel. We also added some custom data to the authenticate function. Theuser_id
in thecustom_data
is the ID of the user while the user_info property is for additional information for that user.
Now, with this, we can authenticate both our private and presence channel.
⚠️ If you don’t include a custom data, the channel won’t be authenticated. This means you won’t be able to subscribe to the channel. The
user_id
in the custom data is also required.
Subscribe the user
Now, let’s subscribe our users to a common channel from the Vue app. We’ll name this channel presence-chatchit
.
Add the following code to the setAuthenticated
method in src/App.vue
:
var presenceChannel = pusher.subscribe("presence-chitchat");
Once the user logs in, we’ll subscribe the user to the presence-chitchat
channel.
Next, update the user is_online
status to true as they subscribe. Add the below code to the setAuthenticated
method in src/App.vue
:
[...]
presenceChannel.bind("pusher:member_added", data => {
// Get the index of user that just scubscribed
const index = this.users.findIndex(user => user.id == data.id);
// Set the is_online status of the user to true
this.$set(this.users, index, { ...this.users[index], is_online: true });
});
[...]
Here,
- We bind the channel to the pre-defined event called
pusher:member_added
. This event is available by default for the presence channels. In the event, we can access the custom data we added while authenticating the channel. this.users
is a state we used for storing all users available on the app.- the
data
is the custom data we passed while authenticating the channel. - Then finally, we set the
is_online
status for the user to be true.
Next, update the user is_online
status to false as they leave the channel. Add the below code to the setAuthenticated
method in src/App.vue
:
[...]
presenceChannel.bind("pusher:member_removed", data => {
// Get the index of user that just subscribed
const index = this.users.findIndex(user => user.id == data.id);
// Set the is_online status of the user to false
this.$set(this.users, index, {
...this.users[index],
is_online: false
});
});
[...]
Here, we bind the channel to the pre-defined event called pusher:member_removed
. This event is available by default for the presence channels. Then finally, we set the is_online
status for the user to be false.
Next, get all users already on the channel before the user joined and set their is_online
status to be true. Add the below code to the setAuthenticated
method in src/App.vue
:
[...]
presenceChannel.bind("pusher:subscription_succeeded", data => {
// Fetch members already on this channel, then set them to be online
for (let member_id of Object.keys(data.members)) {
const index = this.users.findIndex(user => user.id == member_id);
this.$set(this.users, index, {
...this.users[index],
is_online: true
});
}
});
[...]
The pusher:subscription_succeeded
is triggered as soon as a user subscribes to the channel. As the user subscribes to the channel, it’s possible to have users already subscribed to that channel.
In the code above, when we get the event, we’ll fetch all the users already subscribed to the channel and then update their is_online property to true.
Adding an online indicator
We need an indicator that shows if a user is online or offline. We’ll use a small circle beside a user to show when the user is online, and remove it when the user is offline.
In the template section of src/components/Users.vue
, add the below markup:
<template>
<div style="margin-top: 0px;">
<div v-for="(user, id) in users" v-bind:key="id">
<div
v-bind:class="[activeUser == user.id ? 'user active' : 'user']"
@click="chat(user.id)"
>
{{user.userName}}
<span v-if="user.has_new_message" class="has_new_message">New message</span>
<span v-if="user.is_online" class="online"></span>
</div>
</div>
</div>
</template>
Here, we check if the user is online so that we can show the HTML indicator.
Next, add some style for the indicator in the <style>
section of the src/components/Users.vue
file:
[...]
.online {
height: 15px;
width: 15px;
background-color: #17a2b8;
border-radius: 50%;
display: inline-block;
margin-bottom: -4px;
border: 1px solid white;
}
[...]
Testing the app
Congrats! Now we can see those online and those that are offline.
Now test the app. Open the app in different tabs on your browser then log in. You will get a similar experience as below!
Conclusion
In this tutorial, we explored how to add a “who’s online” feature to chat apps using Channel’s presence channel.
The source code for the tutorial is available in GitHub.
4 September 2018
by Gideon Onwuka