Developer Docs

The uChat Online Presence system provides real-time "active" status indicators across the Mallnline platform. It leverages a combination of WebSocket heartbeats and Redis TTL keys to track user connectivity globally, allowing users to see when their conversation partners are currently online.

Core Architecture

The presence system is built around a lightweight heartbeat protocol managed by the uChat service. It is designed to be highly performant and strictly privacy-aware.

Global WebSocket Connection

The presence heartbeat relies on the Global WebSocket. Unlike the per-conversation WebSocket which only connects when viewing a specific chat, the global WebSocket initializes as soon as an authenticated user accesses any page on the platform.

  1. Initialization: The MessagesBadge component in the global navigation triggers uchatStore.connectGlobalWebSocket().
  2. Heartbeat Loop: The frontend sends a ping text message every 20 seconds.
  3. Redis Persistence: The uChat backend intercepts the ping and sets a Redis key uchat:presence:{userId} with a 30-second Time-to-Live (TTL).
  4. Disconnection: If the user closes the tab or loses connection, the heartbeat stops, and the Redis key expires automatically 10 seconds later, marking them offline.

Presence Resolution

To avoid broadcasting presence state to every user (which is unscalable), presence is resolved via targeted polling:

  • When viewing the conversation list, the frontend extracts the userId of all active participants.
  • The uchatStore executes the ONLINE_USERS GraphQL query every 30 seconds.
  • The backend performs an MGET against Redis for the requested IDs, returning the list of users who currently have an active uchat:presence:{userId} key.
query OnlineUsers($userIds: [String!]!) {
  onlineUsers(userIds: $userIds)
}

Privacy & Opt-Out

The system operates on a strict "opt-out" privacy model. If a user disables their online presence via their Privacy & Security settings, the system respects this invisibly.

When showOnlinePresence is false:

  1. The frontend appends &invisible=true to the WebSocket connection URL.
  2. The ws_handler reads this flag and skips the Redis write upon connection and heartbeat.
  3. The frontend completely stops polling the ONLINE_USERS query, saving network requests since the user has chosen not to participate in presence features.

Frontend State Management

The presence data is stored in a Svelte 5 $state Set within the uchatStore.

let onlineUsers = $state(new Set<string>());

export const uchatStore = {
  isUserOnline: (userId: string) => onlineUsers.has(userId)
};

This ensures that the isOnline reactivity is granular and O(1). UI components like the conv-avatar and header-presence-dot simply call uchatStore.isUserOnline(userId), which Svelte tracks. Whenever the polling service replaces the onlineUsers set, Svelte automatically patches the DOM to show or hide the green pulsing indicators.

NOTE

For Users: See Understanding Online Presence for user-facing instructions on privacy toggles.