chore(video-conf): conference data model, server service & API (1/4)
Stacked PR — 1 of 4. Targets develop. Review order: this one → #41956 (conference window, unmounted) → #41657 (call flow, behind a new setting) → #42000 (LiveKit provider). Each PR's diff only reads correctly in that order.
Proposed changes (including videos or screenshots)
The server half of the conference work: data model, service and API. No client UI.
Nothing here changes what a workspace does today. Every addition is either unreached by any shipped client or guarded by a provider capability (embedded) that no provider in this PR declares. The existing Jitsi/BBB/Meet flow is untouched.
Membership becomes a lifecycle rather than a list
IVideoConferenceUser gains joined / joinedAt / declined / declinedAt / leftAt / leftReason / lastSeenAt / ringingAt, read through predicates that keep the rules in one place — hasJoinedVideoConference, isInVideoConference, isRingingVideoConferenceMember.
users[] is the only record of who is in a call. Not an accident of scope: a second per-participant list maintained by whichever provider runs the media is how a call ends up counted as occupied by one half of the code and empty by the other.
Model layer follows: addUserById → addMemberById (adding someone is not the same as their joining), plus setUserJoinedById, setUserDeclinedById, setUserLeftById, setUsersRingingById, renewUserPresenceById, findActiveWithMembers, setTitleById.
Presence leases
Leaving is reported when it can be and inferred when it can't. A client in a call renews a lease every 30s (PRESENCE_HEARTBEAT_MS); a lease unrenewed for 180s (PRESENCE_LEASE_MS) is swept, and the departure is stamped with the last evidence we had rather than the moment of the sweep. This is what survives a crashed tab, a closed laptop, or the workspace being down while the call carried on in the provider — cases where no leave can arrive by definition.
Two guards that matter more than they look:
isPresenceSweepDue— right after a restart every lease looks expired, so the sweep waits out one full lease before believing itself.- The sweep only touches calls whose media runs in a window of ours (the provider's
embeddedcapability, deliberately not a setting). A call handed off to the provider's own page never heartbeats, so sweeping it would end a live Jitsi call after three minutes. Those keep the pre-existing 24-hour TTL cron.
API
| Endpoint | Purpose |
|---|---|
POST video-conference.leave | Report a departure. The call ends as a consequence of nobody being left, never because one member asked |
POST video-conference.heartbeat | Renew a presence lease |
POST video-conference.ring | Ring one member, by user id |
POST video-conference.decline | Record this member's own refusal; never touches the call's status |
POST video-conference.add-participants | Add by username, ringing the batch |
POST video-conference.rename | |
POST video-conference.share-chat | Give the call's chat to members who can't read it. mode is required — the server never guesses which remedy was meant |
GET video-conference.joinable | Calls this user may join, with a capped face list |
join and info return enriched payloads. video-conference.cancel is pre-existing and unchanged; its params type is now the shared VideoConfCallIdProps (replacing VideoConfCancelProps, deleted).
RING_RECIPIENTS_LIMIT (10) bounds the recipients of a single ringing action — a property of the broadcast, not of the conference — and caps add-participants at the same number, so an add always rings rather than silently ringing part of itself.
Also here
- Chat access resolution — which members can read the call's chat, and which remedy is available (invite to the room, or move the chat to a discussion).
- Busy status — members are marked busy for the duration, and the claim is released when they leave, including by lease expiry: a status left on busy by a crashed tab is exactly what nobody thinks to fix by hand.
- Shared isomorphic lib (
apps/meteor/lib/videoConference/) —chatAccess,conferenceName,memberStatus,presence,constants, so client and server can't disagree about the rules. - DDP stream
video-conference/${id}/updated.
48 files, +5,252 / −156. Tests: 9 service specs plus a shared harness, 4 shared-lib specs, videoConfAccess, the share-chat schema, and the VideoConference model.
Issue(s)
https://rocketchat.atlassian.net/browse/NV-64
Steps to test or reproduce
The claim to test is that nothing changed:
- Start, join and end a 1:1 and a group call on Jitsi (or Meet). Identical to
develop. - Confirm the presence cron does not expire members of a non-embedded call: leave a Jitsi call open for >3 minutes without closing the tab, and the call must stay open.
- Confirm no client issues
video-conference.joinable,heartbeat,leave,ringordecline— nothing calls them yet.
The new surface is reachable only by driving the endpoints directly, which the unit suites do.
Further comments
- Departures are stamped with last evidence, never with sweep time. A member whose laptop closed at 10:00 and is swept at 10:03 left at 10:00. Anything else writes a call history that says people stayed until the cron happened to run.
- A renewal undoes an inferred departure, and only an inferred one (
INFERRED_LEAVE_REASONS). A lease given up on while the window was in fact alive was simply wrong, and the window still talking to us is the correction; a member who reported leaving is never revived this way.
Summary by CodeRabbit
-
New Features
- Expanded video conference controls, including joining, leaving, declining, ringing participants, renaming calls, and sharing chat.
- Added a list of joinable calls with participant previews and improved conference naming.
- Added persistent chat options for using the main room or a thread.
- Added embedded-call participant tracking and presence updates.
- Added real-time conference update notifications.
-
Bug Fixes
- Improved access checks, ringing limits, presence recovery, and handling of expired or abandoned calls.
- Conference timestamps are now displayed correctly as dates.