I want to share a user presence system design I’ve used in one of my Bubble apps in order to discern when users are online/offline. This design makes use of Bubble nocode tools only, i.e. no third party services integration, Javascript or plugins.
Disclaimer: this design is no longer a viable solution since it is WU consumption intensive, and probably not viable economic-wise in most cases. So, consider it just as an exercise of what can be done with Bubble pure nocode elements only, and not as a practical strategy.
Introduction
User presence, also known as user online status or user availability, represents a feature that is very much needed in any app that offers users information about the availability or online status (or presence) of other users. Think of any messaging app where you want to know when the other person is online at a given time.
As I developed a real-time Bubble app myself, I faced the need of adding such a feature. So, I investigated the problem and came up with different strategies to do so. However, today I will just share one of those. In particular, I’ll disclose one strategy that is solely based on Bubble “native” nocode elements, i.e. no external plugins, no Javascript, no third party service integrations, just workflows and Bubble DB.
Architecture
The system design I’m describing here is composed of two elements: a server-side initiated heartbeat and a server-side presence monitor. More on this below.
Heartbeat
A heartbeat is a signal that a component sends to indicate that it is alive. In our case, it’s the client-side, i.e. the app running in the users’ browser, the one signalling.
In this design, the heartbeat is requested by the server (ping-pong schema).
Server asks the client (ping) for a heartbeat (pong). It’s a synchronous process since all clients receive the ping at the same time.
The ping must be sent periodically by the server, so a process must be always running on the server-side. The ping request scales O(n) as it needs to execute one action (DB writing) per user.
Presence monitoring
Presence monitoring evaluates client heartbeats and decides on the user presence status. This is, with the information provided by the Heartbeat architecture element, presence monitoring evaluates whether a certain user is present or not.
In this design, we will rely on the server-side for presence monitoring of all users. A User field presenceStatus is needed to keep track of the user availability, and it is the source of truth for any agent that needs to know whether the user is present or not.
Server does a periodic sweep, that takes into account certain tolerance to avoid false Offline-positives. There will be a single process running in the server for all users, but actions will still take place for each user, so it still scales O(n) with the number of users.
Implementation
Summary
- The heartbeat is registered in User’s field lastHeartbeat.
- The the user presence status (Online/Offline) is stored in User’s field presenceStatus
- The User’s field flagHeartbeatRequestedByServer serves the server to signal the user when it should send a new heartbeat.
A diagram of the proposed architecture can be found in here and in the figure below.
Client-side
Keeping webapp active
If we want to consider our users as present even if the webapp is not focused, i.e. the browser is minimized, the tab where the app is running is not focused etc., client-side must ensure the tab is kept active. Otherwise, client-side will stop responding to server and user will be considered as not present. For that purpose, this design relies on the Audio / Sound Player Howler.js Lite to reproduce a light sound in the background, so that the browser keeps the tab active.
Disclaimer: this is the only plugin we use, although not directly related to the heartbeat/presence monitoring mechanism.
Workflow “active user procedure”
We update the value of lastHeartbeat with the active user procedure, that performs the following actions
- Updates the User’s lastHeartbeat (fig. 3)
- Sets User’s presenceStatus to Online (fig. 4)
The active user procedure workflow is executed under two circumstances
- When page is loaded, so that the User is set as Online as soon as it opens the app (fig. 5)
- When the User’s flagHeartbeatRequestedByServer is set to yes (fig. 6), i.e. when the server-side asks the user to send a heartbeat
Server side
API workflow “initiate inactivity detection process”
The superadmin user schedules (fig. 7) the initiate inactivity detection process API workflow (fig. x), used for starting the inactivity detection process (10-20-25), that does various things
- Signals users (ping) to send a heartbeat (pong)
- Checks users’ lastHeartbeat to mark them as Offline when it’s older than a threshold
- Keeps itself running without user intervention by re-scheduling itself
API workflow “inactivity detection process (10-20-25)”
The initiate inactivity detection process API workflow calls the inactivity detection process (10-20-25) API workflow (fig. 9).
The code 10-20-25 stands for
- (x) 10 seconds is the periodicity of server signals (ping), asking for users’ heartbeats
- (2x) 20 seconds is the threshold for process health check. It’s set to two times the ping periodicity so as to give some tolerance for new pings to be scheduled
- (2.5x) 25 seconds is the threshold for inactive user detection. It’s set to 2.5 times the ping periodicity so as to give some tolerance for clients responding heartbeats (pong)
The thresholds and periodicity can be changed but it will directly affect the WU consumption and inactivity detection margin.
This API workflow does various things:
- Triggers the Custom event request heartbeat from users (fig. 10)
- Schedules itself 10 seconds in the future. The ID of this scheduled API Workflow will be used later. This is done to maintain the process running ad infinitum (fig. 11)
- Schedules the API workflow check process health 20 seconds in the future, providing the ID of the inactivity detection process (10-20-25) API workflow just scheduled 10 seconds in the future, as an argument called lastInactivityDetectionProcessId (fig. 12)
- Stores the ID of the scheduled inactivity detection process (10-20-25) API workflow in the superadmin User’s nextInactivityDetectionProcessId field (fig. 13). The superadmin user is the one that started the whole process.
- Schedules the API workflow check users inactivity (+25 sec) 25 seconds in the future (fig. 14).
Custom event “request heartbeat from users”
The Custom event request heartbeat from users sets the flagHeartbeatRequestedByServer value of every User to yes (fig. 15).
API workflow “check process health”
The API workflow check process health (fig. 16) reinitiates the process by calling the initiate inactivity detection process API workflow in case the process is dead (there’s no new inactivity detection process (10-20-25) scheduled in the future) (fig. 17)
API workflow “check users inactivity (+25 sec)”
-
API workflow check users inactivity (+25 sec) applies the API workflow apply inactive user protocol to a user to a list of Users (fig. 18)
-
The API workflow apply inactive user protocol to a user (fig. 19) sets the User’s presenceStatus to Offline (fig. 19)
-
The list of Users contains the subset of Users constrained by the following conditions
a. lastHeartbeat is older than 20 seconds
b. presenceStatus is Online
Execution timeline
-
(T0) server-side. Admin executes API workflow initiate inactivity detection process (Fig. 7). This action must be done only once, via a superadmin dashboard.
-
(T0) server-side. An API workflow inactivity detection process (10-20-25) is scheduled 0 sec in the future (Fig. 9)
-
(T0) server-side. An API workflow inactivity detection process (10-20-25) executes.
-
(T0) server-side. Custom event request heartbeat from users is executed.
-
(T0) server-side. All users are set flagHeartbeatRequestedByServer = yes (Fig. 15)
-
(T0) client-side. All present users get Do when Current User’s flagHeartbeatRequestedByServer is yes workflow triggered (Fig. 6).
-
(T0) client-side. All present users trigger the custom event active user procedure
-
(T0) client-side. All present users update their lastHeartbeat to current time and set their flagHeartbeatRequestedByServer = no (Fig. 3)
-
(T0) client-side. Present users who had their status set to Offline, reset it to Online (Fig. 4).
-
(T0) server-side. An API workflow inactivity detection process (10-20-25) is scheduled 10 sec. in the future (Fig. 11).
-
(T0) server-side. An API workflow check process health is scheduled 20 sec. in the future, providing the ID of the scheduled API workflow of the previous step, in the lastInactivityDetectionProcessId argument. (Fig. 12)
-
(T0) server-side. The same ID is saved in the superadmin User’s field
nextInactivityDetectionProcessId (Fig. 13).
-
(T0) server-side. An API workflow check users inactivity (+25 sec) is scheduled 25 sec. in the future. (Fig. 14)
-
(T+10) server-side. API workflow inactivity detection process (10-20-25) executes. Steps 3 to 13 repeat.
-
(T+20) server-side. API workflow inactivity detection process (10-20-25) executes. Steps 3 to 13 repeat.
-
(T+20) server-side. If the superadmin user’s
nextInactivityDetectionProcessId equals lastInactivityDetectionProcessId, the process has problems and must be reinitiated. We do this by invoking API workflow initiate inactivity detection process API workflow (return to Step 1) (fig. x).
-
(T+25) server-side. API workflow check users inactivity (+25 sec) runs, executing the API workflow apply inactive user protocol to a user on a list of Users. The list contains users whose lastHeartbeat is older than 20 sec. and whose presenceStatus is not Offline (fig. 18).
-
(starts T+25) server-side. Each selected user is set its status to “Offline” (selected in step 17).
Performance analysis
2 users continuously present consume ~2920 WU/h
5 users consume ~4886 WU/h
We can divide this consumption between
- overhead operations, e.g. (scheduling backend process, changes to superadmin user…)
- operations per present user.
We can perform a quick estimation of how many WUs are consumed per user by doing the difference between both calculations and dividing by the difference of users (3 users):
- 4886 WU/h - 2920 WU/h = 1966 WU/h
- 1966 WU/h / 3 users = 655.33 WU/h/user
The overhead WU consumption is approximately:
- 655.33 WU/h/user * 5 users = 3276.7 WU/h
- 4889 WU/h - 3267.7 WU/h = 1609.33 WU/h