Documentation

Presence and availability

Track who is online, set agent status, and subscribe to real-time presence changes.

Presence answers two questions: "is this user online right now?" and "what should I tell the routing engine about them?". ChatPresenceService keeps both answers in Redis so any pod can read consistent state without leader election.

Storage model

Three Redis key families back the service:

  • chat-socket:{socketId} (TTL 120s) — per-connection metadata. One row per live websocket.
  • chat-identity:{orgId}:{role}:{email} — set of socketIds for this identity. SCARD > 0 means online.
  • chat-presence:{orgId}:{role}:{email} — aggregated metadata: name, status, skills, activeChats, location.

A user with three open tabs has one identity row and three socket rows. Closing one tab decrements the identity set; closing the last one removes the presence record.

Agent status

Agents have an explicit status independent of "online". The set is available | busy | away.

Set your own status

socket.emit('set-status', { status: 'busy' });

Or via REST for admin override:

POST/chat/agents/{email}/statusJWT
{ "status": "available" }

The server broadcasts a presence-change event to every connected socket so dashboards update instantly.

Read agent presence

GET/chat/agents/onlineJWT

Returns currently online agents. Optional query: status (filter by available, busy, away) and skill (matches presence.skills[]).

curl "https://appengine.appmint.io/chat/agents/online?status=available&skill=billing" \
  -H "orgid: my-org" -H "Authorization: Bearer <jwt>"
GET/chat/agents/{email}/presenceJWT

The full presence record for one agent.

Customer presence

Customers don't set status — they're either connected or not. Online customer presence is tracked the same way agents are; useful for the "live visitors" panel in the admin console.

GET/chat/customers/onlineJWT
GET/chat/customers/{email}/journeyJWT

The customer journey endpoint joins live presence with the most recent web_visit rows so admins can see both "where they are now" and "where they've been". Optional ?limit=50 controls the depth.

Stats

GET/chat/presence/statsJWT

Aggregated counts: agents broken down by status, total online customers. Drives the header counters in the admin UI.

Active sessions

GET/chat/sessionsJWT

Every live socket in the org. Returns the same shape as the per-socket Redis row (email, role, ipAddress, userAgent, currentUrl, etc.) for live audit views.

Subscribing to changes

Every status mutation emits a presence-change to the whole /chat namespace:

socket.on('presence-change', (evt) => {
  // { email, name?, role, status: 'online' | 'offline' | 'available' | 'busy' | 'away',
  //   timestamp }
});

If you only care about a specific user, key off evt.email. If you only care about agents, key off evt.role === 'agent'.

A user can be online (socket connected) and busy (status flag) at the same time. The status field is meaningful only for agents — customer presence reports online or offline.

TTL safety net

If a pod crashes mid-conversation the per-socket row self-expires in <=120s and the identity set is cleaned by the next read. There is also an hour-long SAFETY_TTL_SECONDS on identity-level keys so absolute orphans get garbage-collected.