ADR-0009: Collaboration presence via backend SocketIO + short ticket
- Status: Accepted (2026-08-02)
- Decision makers: Project maintainers
Context
P3b "collaboration cursor/presence" needs real-time rooms; frontend once connected to standalone Node at localhost:3000, disconnected from backend netty-socketio:9092. Putting full JWT authorities in handshake query causes URL too long → 400/disconnect.
Decision
- Presence single channel: backend SocketIO, namespace
/project/erd, eventsmartin:event:joinRoom/leaveRoom - Handshake auth:
POST /auth/socket-ticket(login required) issues Redis short ticket (TTL 2min, payloaduserId+username); query carriesticket+projectId - Forbid oversized JWT in Socket handshake; length >512 rejected with prompt to use short ticket
- Project members: handshake and
JOIN_ROOMboth verify current user ∈project_user(R-AUTH-05); non-members getconnect_error; cursor/sync broadcast only after successful room join - Online roster + collaboration cursor (
martin:event:cursor) + model delta (martin:event:sync, jsondiffpatch delta, scopeprojectJSON) - Client protocol:
[email protected](aligned with netty-socketio 1.7 / Engine.IO 3) - Sync: local debounced broadcast; remote patch deduped by timestamp and echo suppressed; not via
:3000
Consequences
- Positive: same room sees roster/cursor/model delta; auth deployable; non-members cannot listen/inject room events
- Disconnect: namespace
DisconnectListenerand explicitleaveRoomsame path remove name; multi-connection same user only removes from roster when last connection leaves - Known limits: no OT/CRDT, last-write-wins on conflict; large delta not chunked
- Conflict hint: toast on remote sync success (warning if local dirty); error on patch failure
- Follow-up: cursor idle timeout