PlayMesh/Docs
HomeGitHubnpm

Presence

Live join/leave events, member snapshots, and cross-node session lookup.

Overview

Joins and leaves are broadcast to instance members in real time, and join snapshots include the current member list - every client always knows who is in each of its instances, without any application code.

On the client

client.presenceOf('world/city');   // { count, users }
client.onPresence(event => {
  // { instance, type: 'join' | 'leave', userId, sessionId, count }
});

On the server

await city.memberCount();          // total members across all nodes
await city.members();              // { [sessionId]: userId } across all nodes
await mesh.sessionsOf('alice');    // all session ids of a user across nodes
ℹ
instance.sessions is local-node only. Use instance.memberCount() for the cluster total.

Cleaning up after members

instance.onLeave(session => ...) receives a live session, so it cannot run for a player whose node crashed - which is why games end up writing manual crash cleanup. onMemberLeft runs for every departure and is the one place to remove per-player records:
arena.onMemberLeft(async ({ sessionId, userId }, reason) => {
  // reason: 'leave' | 'disconnect' | 'ghost'
  await arena.publicState.delete(`player:${userId}`);
});
'ghost'means the session's node crashed and the Redis presence sweeper reaped it. The hook runs exactly once cluster-wide, on the node that claims the ghost. Sessions count as ghosts once their heartbeat is 60 seconds old, and a restarted node sweeps on startup, so a crash-and-restart cleans up its own leftovers.

Multi-node self-healing

In multi-node (Redis) deployments, nodes heartbeat their sessions and periodically sweep ghost sessions left behind by crashed nodes, so presence counts self-heal without manual intervention.
When the sweeper evicts a ghost session, it broadcasts one leave presence event per removed membership with the cluster-wide member count after removal - clients see crashed players leave just like normal disconnects. Concurrent sweepers claim each ghost atomically, so every leave is broadcast exactly once and a normal disconnect racing the sweep is never double-reported. The sweep also releases the ghost's uniqueUser claim and arms auto-destroy when a ghost was the final member.
ℹ
Auto-destroy after a ghost eviction requires some surviving node to hold a local copy of the instance (the usual case when topology is created at bootstrap on every node). Instances created dynamically on only the crashed node are not reaped.