Home Opinions Focus Off Spectacles Off

Building a Simple User Presence

Bobby posted in Programming | Jun 04, 2023 | 1,678 views

Computer Science 💻 Programming

Guess who's back? Back again?... for the rest, go listen to the fucking song! I am not here to sing songs for you. Anyroad, what's up? Actually, no one cares... so, let's get started! Remember back in the day, when you visited your favourite forum and it would say something like "$N$ users online: $(n)$ members, $(x)$ guests" (of course $ {n, x} \in \mathbb{N} $)? This is called user presence. It's a simple way to show your users that they are not alone on your website. It's also a great way to show off your mad skills to your friends. So, let's get started!

Post Image for Building a Simple User Presence

Before we start, I would like to discuss your options to track user presence on your website. There are multiple ways to do this:

WebSockets: This is the most modern and probably the most efficient way to do this. WebSockets will provide you with a persistent, bidirectional communication channel between your server and the client. Talking in a broad perspective, you would set up a WebSocket server that listens for incoming connections and handles WebSocket events, and when a client establishes a WebSocket connection, register their presence by storing relevant data (e.g., user ID, session ID) on the server. Then you can implement mechanisms to track user activity, such as sending heartbeats or receiving client-initiated events. 

However, this shit is complicated as fuck. Do yourself a favour and don't waste your time setting up and listening for WebSocket events, if you don't need to (like, if you are not building a chat app or something). Also, generally, browsers will use bfcache (back-forward cache) to store a snapshot of the page in memory for when the page is restored from the navigation history. This significantly speeds up return navigations to the page, however, some browser APIs (e.g. unload listeners) can cause the bfcache to fail and the page will be loaded normally. And guess who falls in the category of "some browser APIs"? That's right, our very own WebSockets!

Sessions: This is the most common way to track user presence. You can use sessions to store user data on the server and then use that data to track user presence. The way you will usually implement this is by generating a unique session ID upon user visit and storing it on the server or in a database. Associate the session ID with user-specific data. Then, when the user visits your website again, check if the session ID is present in the database. If it is, then the user is online, otherwise, they are offline.

Also, you need to manage session expiration and cleanup to avoid resource wastage. Another big issue is "Session Hijacking". Session hijacking is the exploitation of a valid computer session—sometimes also called a session key—to gain unauthorized access to information or services in a computer system. In particular, it is used to refer to the theft of a magic cookie used to authenticate a user to a remote server, and you would need to implement robust session handling mechanisms to prevent session hijacking or fixation.

Cookies: This is the most simple way to track user presence. You can use cookies to store user data on the client and then use that data to track user presence. This is what we are going to build today. Go drink some water and we will get started.

Fun Trivia: This is the same method this website uses to track user presence. If you open the developer tools and go to the "Application" tab, you will see a cookie named `user_uuid`. This is the cookie that is used to track your presence on this website. If you delete this cookie, the user presence will reset (but they will come back once you refresh the page). Go ahead and try it out!

There are numerous other ways, but this isn't the article where we discuss ways to track user presence. We are here to learn to build one, so let's do that!

How do we do this?

You started reading this article, and reached this point and wondered, "Wait, what is all this text bullshit"? "How do we start"? If you are pondering over it... fuck it! I am not gonna write the same thing thrice! (What are you talking about?)

So, in order to track user presence, we will need a few things. First, we need to identify each user uniquely. You can do this in any manner you please, but for the sake of keeping this short, I am going to use UUIDs, specifically v4. UUIDs stand for Universally Unique Identifiers and they are generated using random or pseudo-random numbers and have a specific format consisting of 32 hexadecimal digits grouped into five sections, separated by hyphens (e.g., 123e4567-e89b-12d3-a456-426655440000). The random nature of Version 4 UUID generation, combined with a large number of possible values, makes collisions highly improbable but not impossible. It is, however, fine for our use case.

Another thing we would need is a place to store our presence data. You can store this in a database, but I am going to use Redis, because not only it is fast, but it also supports auto-expiration of keys, which is very useful for our use case, as we won't have to hold our data longer than we need to.

This Code Looks Funky!

Now, this is not the tutorial where I hold your hand and build a complete project from scratch. I am hereby assuming that either you have a web project already set up and ready or you have experience building one of those. I am going to use very generic pseudo code like syntax (in Python) to explain the concepts. Please refer to the documentation of your web framework of choice to understand how to implement these concepts in your project.

First, let's add some middleware to generate and set a UUID cookie on the client:

import uuid

class handleUUIDMiddleware:
  # This middleware will generate a UUID and set it as a cookie on the client
  def __init__(self):
    self.cookie_name = "user_presence_uuid"

  def process_request(self, req, resp):
    # Check if the cookie is already set
    if self.cookie_name in req.cookies:
      # Whoa! There is a cookie already set. Let's return it
      return req.cookies[self.cookie_name]
    else:
      # Oh no! There is no cookie set. Let's generate a new one
      new_uuid = uuid.uuid4()
      # Set the cookie on the client
      req.set_cookie(self.cookie_name, new_uuid)
      # Return the cookie
      return new_uuid

Now, that we have a UUID for each user who visits our site, we would need to track it and send it to our view. I am going to write a simple context-processor-like function for this. Again this is just pseudo code, you will have to implement this in your own way.

import redis # Import the Redis library
import json

# Connect to Redis
redis_client = redis.Redis(host='localhost', port=6379, db=0)

def user_presence_context_processor(req, resp):
  # Prefix for our Redis keys
  prefix = "user_presence_"

  # Get the UUID from the request
  uuid = req.cookies["user_presence_uuid"]
   
  # store any additional data if you want to
  user_data = {
    'type': 'guest', # or 'member', just an example
    ...
  }

  # Set the UUID as a key in Redis and set the value to the user data
  # We will set the key to expire after 5 minutes, this means our
  # presence data updates every 5 minutes. Adjust this value to your liking
  redis_client.set(f'{prefix}{uuid}', json.dumps(user_data), ex=300)

  # Now we need to tell our view how many users are online, so we will
  # get the number of keys in Redis and pass it to the view
  total_online = redis_client.keys(f'{prefix}*')

  # Separate into types if you want to, optional
  guests = [user for user in total_online if json.loads(redis_client.get(user))['type'] == 'guest']
  members = [user for user in total_online if json.loads(redis_client.get(user))['type'] == 'member']

  # Pass the data to the view
  return {
    'total_online': len(total_online),
    'guests': len(guests),
    'members': len(members)
  }

That's it! Now you can use the data in your view to display the number of users online. You can potentially improve from here and add whatever extra logic you want to. For example, you can add a "last seen" feature, where you can store the last time the user was online and display it on the website. You can also add a "currently viewing" feature, where you can store the page the user is currently viewing and display it on the website.

Cons? Can'ts? CANNES? Keanus?

Obviously, this method is not perfect. There are a few drawbacks to this method. First, it is not real-time. The data updates every 5 minutes, so if a user leaves the website, it will take 5 minutes for the data to update. Second, it is not very accurate. If a user leaves the website and comes back within 5 minutes, the data will not update. Third, it is not very scalable. If you have a lot of users, you will have a lot of keys in Redis and that will slow down your Redis server. You can potentially solve this by using a Redis cluster, but that is out of the scope of this tutorial.

Also, You will need to ensure proper handling of secure and HttpOnly flags for sensitive data and be mindful of cookie size limitations. And if you're feeling fancy, you can add a privacy policy page and a message saying "Hey, this site uses cookies, whether you like it or not!" to comply with GDPR.

Alright, Ciao!

Liked this post? Wanna stay updated?

Subscribe to my RSS feed to get the latest updates from this weblog. Copy and paste the following link into your favorite RSS reader:

https://shi.foo/rss/

Read Next

Comments Back to Top

Profile Picture Registered User
doctors0choice on Jun 21, 2023
This was a very informative article, thank you for writing it! I was planning on implementing something similar but did not know where to start, so this is really helpful. Also, in the line "If you open the developer tools and go to the "Application" tab, you will see a cookie named `user_uuid`.", I noticed that the cookies were not in the Applications tab but the Storage tab. This may just be a difference in browsers (I am using Firefox, perhaps you are using chrome), but I thought I would let you know.
Profile Picture Registered User
bobby on Jun 22, 2023
Yes. That's correct. I did, in fact, reference Google Chrome's developer tools in the article. In Chrome, you go to the "Applications" tab > Storage section to see the cookies. And yes, this location may vary per browser, but the general idea remains the same, i.e. you are looking for a cookie called `user_uuid`.

Leave a Comment

You must be logged in to leave a comment. Or, you can leave an anonymous comment.