feat: NV conference provider (Old PoC) - #41735
Conversation
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
🦋 Changeset detectedLatest commit: f9365fb The changes in this PR will be included in the next version bump. This PR includes changesets to release 8 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #41735 +/- ##
============================================
- Coverage 69.33% 0 -69.34%
============================================
Files 4255 0 -4255
Lines 168644 0 -168644
Branches 30052 0 -30052
============================================
- Hits 116929 0 -116929
+ Misses 46534 0 -46534
+ Partials 5181 0 -5181
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
9e8f454 to
e36e37e
Compare
95764dc to
d57634e
Compare
| label | ||
| .replace(/\s*\([0-9a-f]{4}:[0-9a-f]{4}\)\s*$/i, '') |
20377ad to
3ab5aea
Compare
3dc75f8 to
4dc27ae
Compare
3ab5aea to
9d8f48f
Compare
0ef8550 to
0061d3c
Compare
9d8f48f to
a1b8a88
Compare
73a6f9d to
cb10cdf
Compare
… bar Move the conference chrome from a vertical side rail to a control bar along the bottom of the call — the position third-party providers put their own toolbar in, so an embedded provider and the future native conference read the same. `SideRail`/`SideRailPanel` are renamed and split into `CallBar` (+ `CallBarActions`, `CallBarAction`) and `CallPanel`, so both names describe their position and can be reused by the native video conference. The page is now a column — a row holding the call and the panel, then the bar — so `CallPanel` is a sibling of the call area rather than a child of the bar. Toggling the chat animates its own width without ever reflowing the bar, by construction instead of by careful sizing. The panel moves to the inline end to match the chat action, which puts its close button at the far end of its header. `CallBarActions placement='end'` is taken out of flow and anchored to the inline end, so adding or removing end actions never pulls the centred controls off-centre — the shape the native conference needs once it owns mic/camera/ screen-share/hang-up. Add `useProviderCallBridge`, letting a provider that renders its own toolbar hide ours and drive the chat panel (`set-call-bar-visible`, `set-chat-visible`, `toggle-chat`) so the user never sees two competing sets of controls. The iframe is cross-origin, so `event.origin` cannot be allow-listed against our own origin; instead every message must come from the exact window we embedded, which no other frame or tab can forge. Unknown commands and malformed payloads are rejected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A call belongs in its own window rather than a tab in the user's strip — it mirrors what the desktop app already does with its dedicated video window, and keeps the call visible while the user works in the main app. `useVideoConfOpenCall` now tries a popout (1280x800, capped to the available screen and centred) and falls back to an ordinary tab when it is refused; some browsers and extensions block popup-shaped windows while still allowing a plain one. Only when both are blocked does `VideoConfBlockModal` ask the user to allow it. A window that comes back already closed counts as blocked too. `noopener` is deliberately absent from the features string: the conference page posts navigation requests back to its opener, and `noopener` would both sever that link and make `window.open` return null — indistinguishable from a blocked popup, so the popout would silently degrade to a tab forever. There is a test pinning that. Reusing the shared conference window is unchanged, and now covered: already showing this conference focuses it without features, so a window the user has arranged is not resized or recentred; a different conference navigates it; a closed one is reopened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…loading `UsernameCheck` shows `HomeSkeleton` — a sidebar list, room and composer skeleton — while it resolves the user. It sits in the shared authentication chain, so the conference, which renders standalone, was flashing a whole fake app shell it never goes on to show. This is the same shape of problem as the navigation chrome itself: the chrome moved to `MainLayout`, but its loading counterpart stayed behind in the auth chain. `AuthenticationCheck` and `UsernameCheck` now take an optional `loading` node. It still defaults to `HomeSkeleton`, so no existing route changes, and the conference route passes `PageLoading` instead — which also matches what the conference shows while joining, making startup one continuous state rather than a skeleton followed by a spinner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The chat panel is 400px, but it renders the full room UI, which is spaced for the main app. The default message start padding plus the avatar gutter's left margin spent more of that width on empty space than the panel can spare, and the composer sat flush against the edges — opting into the embedded layout zeroes its inline padding, which is sized for the tiny `?layout=embedded` iframe where every pixel counts. `narrowRoomStyle` restores the composer's padding and trims the message start padding and gutter margin, scoped to this subtree so the room's normal full-width appearance and every external embed are untouched. Only the start padding is trimmed; the message toolbar and timestamp column sit against the end padding and need the room. The panel header is condensed to match. The call iframe is named with `aria-label` rather than `title`: a `title` on a full-viewport iframe also renders as a hover tooltip, floating a label over the call for as long as the pointer is inside it. Also documents the popout ladder and the standalone loading placeholder from the two preceding commits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Documents the verified add-participants behaviour per room type, and the agreed design for separating "being in the call" from "being able to read the chat". Adding someone to a conference currently puts them in a room — the conference's room, or a fresh discussion — and join authorization is then derived from room membership. That conflates two different things. The planned model makes the existing `users[]` the membership list with a per-entry `joined` flag, authorizes on room access *or* conference membership, rings the added users, and surfaces members who can't see the chat with a remedy instead of forcing that choice up-front. The section is fenced as planned-not-implemented and doubles as the progress tracker. It records the decisions behind the design, and the traps each phase has to clear — notably that `addUserById` uses `$addToSet` with a whole document (so marking a member joined later would append a duplicate rather than update), and that every `users[]` entry already stored predates the flag and represents someone who joined, so a reader filtering on it would show historical conferences as empty. Also documents the verified per-room-type matrix for the current flow, including the divergence where a repeated "don't keep history" add rebuilds from the original room and drops earlier invitees. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adding someone to a conference used to put them in a room — either the conference's room, keeping its history, or a fresh discussion — because joining was authorized purely from room membership. That conflated being in the call with being able to read its chat. `users[]` becomes the conference's membership list, with a per-entry `joined` flag and `joinedAt`; `ts` keeps meaning "became a member". `join`/`info` now authorize on conference membership *or* access to the conference's room or discussion, so someone added from outside the room can join the call without being handed the room's history. `add-participants` registers members, touches no room, drops `keepHistory`, and returns the ids it added. Two traps this had to clear. `addUserById` used `$addToSet` with a whole document, which compares entire objects — once an entry can be mutated, a second write would append a duplicate instead of updating. It is replaced by `addMemberById` (a `$push` guarded on `users._id` in the query, atomic and idempotent, which also removes a racy in-memory dedup) and `setUserJoinedById` (`$set` through `arrayFilters`). And every `users[]` entry already stored predates the flag while representing someone who joined, so `hasJoinedVideoConference` reads an absent flag as joined and is the only place that decides — no reader tests the field, and no backfill or deploy ordering is required. Every reader that means "who is in the call" now filters through that helper: the message block's counts, name list, avatar stacks and empty state, the call history list's avatars, and the direct-call ring-stop check. The `video-conference` stream's `allowRead` deliberately keeps using full membership, so someone added can follow `discussionUpdated` before joining. `assignDiscussionToConference` now subscribes the union of the conference's members and the original room's members, rather than only those who had joined. Ringing is decided per call event against the list being rung, via `shouldRingVideoConference`. Starting a call rings the room, so a large room still rings nobody; an add rings only the people added and is capped at the same limit, so an add always rings. This also makes server-originated ringing work at all. The server has long set `ringing: true` on group conferences and broadcast a `ring` action, but no client ever handled it — the only match for `'ring'` in the client was a word in the E2EE wordlist. Real DM ringing is driven client-side by the caller repeating `call` on an interval. `VideoConfManager` now handles `ring`, which means group conferences that were silently not ringing will start to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Declining a conference is now stored on the member's `users[]` entry as `declined` / `declinedAt`, through `POST /v1/video-conference.decline`. The endpoint takes no target user and writes only against the caller's own membership, so declining can never be a way to end someone else's conference. The client-published `rejected` message stays, because the 1:1 flow depends on it — the caller's client is waiting on it to stop dialling — but it is a claim one client makes about another user's call, so it is no longer what gets stored. Someone rung as a member of the conference's room has no entry yet, so declining creates one; without it there is nowhere to record the decline. Declining is not exclusive with joining — a member can decline and then join later. Adds a regression test pinning that a rejection for a call we are not placing never reaches `video-conference.cancel`. That currently holds only because `onDirectCallRejected` bails when the callId doesn't match `currentCallData`, and the teardown sits behind that same guard, so widening it would silently let one person's decline end everybody's call. Also extracts `mapVideoConfUserFromApi`: membership now carries three optional timestamps, each arriving as a string over REST, and deserializing them one at a time meant every new field surfaced as a type error at a distant consumer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
These assert the shape of the update document rather than its effect, which is the level the bug they exist to prevent lives at: `$addToSet` on a whole document stops de-duplicating the moment an entry can be mutated, so the guard has to be in the query. A regression would reintroduce duplicate member entries, which is invisible until someone counts participants. Also pins that declining does not clear `joined` — a member can dismiss the ring and join later — and that `joinedAt` is omitted rather than stored as undefined. `BaseRaw` is stubbed because it participates in a circular import that leaves it uninitialized when this module is loaded directly by jest; only its constructor matters here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The call-history page could only ever record VoIP calls: `CallHistoryItem` was a union of two `type: 'media-call'` variants, both 1:1-contact-shaped. A conference is room-and-many-participants shaped, so it joins the union as a sibling variant, `IVideoConferenceHistoryItem`, carrying the room, the conference title, and how many members actually joined. Ending a group conference writes one item per member, since history is per-user. `buildConferenceCallHistoryItems` is kept pure and DB-free so the per-member semantics are unit-testable: `direction` is outbound for the creator and inbound for everyone else, and `state` is `ended` for a member who joined, `not-answered` for one who did not. Only members get an item and a member either joined or was rung and didn't, so not joining is not answering — whether they declined or just ignored it. Reporting an ignored ring as a normal ended call would hide a missed conference. `setStatus(ENDED)` is public and reachable more than once for the same call, so the write is guarded on the call not already having ended; without that, every member collects a duplicate entry. A history failure is logged and swallowed rather than failing the end of the call. Every item type shares one collection, so the existing direction/state filters needed no change; only the free-text search gained a branch matching the conference title. The table dispatches conference items to a new row showing the room and joined count instead of a contact. Two type holes that widening the union opened are closed here too: `MediaCallHistoryExternal` typed its payload as `Exclude<CallHistoryItem, IInternalMediaCallHistoryItem>`, which silently began including conference items and would have broken `item.duration`, and its `isExternalCallHistoryItem` guard tested `type !== 'media-call'` — previously dead, now wrong. Both are narrowed to the external media-call variant they mean. Deep-linking to a conference item's detail route still falls through to the generic "call info could not be loaded" panel; a conference detail view is out of scope and noted in the docs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Conference membership deliberately grants no room access, so a member can be in the call while unable to read its chat. Rather than forcing that choice on whoever adds a participant, it is surfaced once it actually matters. `video-conference.info` reports `membersWithoutChatAccess`, and `POST /v1/video-conference.share-chat` fixes it. Access is asked per member with `canAccessRoomIdAsync` rather than derived from subscriptions, because reading a room doesn't always require one — a public channel is readable by anyone unless it belongs to a private team. Conferences are small enough that getting that case wrong matters more than the extra reads. The remedy asks the room whether it can take new members — `allowMemberAction(room, RoomMemberActions.INVITE)` — instead of testing for a DM. The room type owns that rule, and it covers cases a `t === 'd'` check misses, such as a federated DM that can grow. A room that can take members is joined; one that can't has its chat moved to a discussion carrying everyone. `ChatAccessNotice` renders in the chat panel, where the remedy is in context, and moves up to the conference page while the panel is closed — so it can't be missed, and is never shown twice. This also closes the divergence documented earlier: `createConferenceDiscussionWithParticipants` now builds from `discussionRid || rid` rather than always the original room, so a second discussion no longer silently drops everyone added since the first. It is reached as a remedy now, which is why the fix matters again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Captures what isn't visible from the code: that group ringing was dead code before this (the server broadcast `ring` and no client handled it, so enabling it makes conferences that were silently not ringing start to), that declining creates a membership entry because there is nowhere else to record it, where the test coverage lives and why those runners, and the gaps left open — no members panel, no conference detail view in call history, one access check per member on info, and the 10-user cap per add that guarantees an add always rings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ringing on add is the first case where a call rings someone with no access to the room it belongs to, and the incoming popup was built entirely around that room: it read it from the client store and returned null when it wasn't there, while still calling focusManager.focusFirst() — which then looked for the parent of a node the focus scope never got. Incoming popups now render without a room, describing the call from the conference record the popup already fetches. The popups that act on a room, starting or placing a call, still require one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1:1 accept is a handshake: the callee publishes `accepted` and waits for the caller's client to reply `confirmed`, giving up after 5s. A server-originated ring has no caller waiting, so running that handshake left a user who had just been added staring at "No response from remote user". Incoming calls now carry a `handshake` flag. Without it, accepting joins the conference outright — membership is what authorizes joining — and declining records the decline without publishing `rejected` to whoever added them, which their client would read as their own call being turned down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… chat The notice for members who can't read the chat applied the only remedy the room allowed, behind a "Give access" button, on a hand-rolled banner with unreadable contrast. Both ways out give something away, in different directions: inviting exposes the room's whole history to an outsider, while moving the chat to a discussion leaves the earlier history behind for everyone already there. So the notice now opens a modal that names the members and spells out each consequence next to its own button. Which action leads is a privacy judgement — private rooms and DMs lead with the discussion, public rooms with the invite — and `share-chat` takes a `mode`, re-deriving `canInvite` server-side rather than trusting it. The banner is now the one rooms use for announcements, so it inherits readable contrast. `AnnouncementBanner`'s className was too narrow to accept css output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9377e17 to
93f4ac4
Compare
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
The rebuilt branch had every native file and nothing connecting them, so a call opened its window onto an empty iframe. This is the wiring. Taken from #41735 where it is the newer work: the stage rewrite of `MediaCallRoomSection` and `MediaCallRoomActivity`, the device pickers, the members panel with its raised hands and per-participant mute, the preflight with its device selectors and camera preview, and the page that puts them together. Kept from here where *this* is the newer work: #41944's `ActionButton`, `ToggleButton`, `ActionToggleChat` and `usePlayMediaStream` — that PR was merged after the native branch was cut, so taking its ui-voip wholesale would have undone the blink fix and the badge's accessible name. Two things the native page drops that this stack had, restored: the top bar's elapsed time and call name, which only an iframe provider needs because a native one supplies its own header; and the bottom control bar is now rendered only for a provider that has controls to put in it, rather than as an empty strip under an iframe. The two LiveKit endpoints are declared in `rest-typings` rather than left untyped as they are on that branch — this stack's API layer infers request and response from the endpoint map, and an undeclared path silently widens it.
Proposed changes
Adds LiveKit as a native video conference provider, so a call can run inside Rocket.Chat instead of handing the user off to someone else's page.
Every provider until now has been a URL. The workspace knows a call is open and nothing else: not who is in it, not what was said, and certainly not how to record it. Running the call in-process changes what the server is able to know, and most of this diff is the consequence of that rather than the SFU itself — conferences now carry the participants actually connected, so presence stops being inferred from who clicked join.
On top of that: multi-party calls with a grid and spotlight, screen sharing, hand-raise and reactions, egress-based recording that lands as an ordinary upload posted in a thread under the call's message, and opt-in live captions from a worker that joins each room as a hidden participant — with note-taking on, the transcript is kept and summarised when the call ends.
The provider is enterprise and off unless configured. A workspace with no LiveKit deployment behaves exactly as before.
Deployment (CloudFormation + walkthrough):
docs/features/native-video-conference/deploying-livekit.md.Where this came from
Ported from #40726 (
feat/vc-poc), minus that PR's persistent-chat work, which this stack already carries from #41657. That PR's history splits cleanly: commits up to7e617cc8a8care the LiveKit work, everything after is its own conference-window integration. Only the first range was taken, so none ofclient/views/conference/,client/views/root/,appLayout,desktop-apiorstartup/routes.tsxcame across.Reconciling against a newer
developneeded six things:app/was restructured — the settings, notifications and file-upload imports across eight EE files point at their new homes (server/settings,server/lib/notifications/core/lib/Notifications,server/lib/media/file-upload,server/meteor-methods/messages/sendFileMessage).useGoToRoomleftui-contextsfor the app.w/h/bgshorthands.MediaCallRoomActivitygainedMediaCallViewProviderand container-height sizing on develop while the PR replaced that sizing withuseTileGridLayout; the PR's layout wins, since it is what the rewritten section expects.IVideoConferencetook both sides — this stack's membership predicates and the provider's participant / recording / transcript records.@livekit/agentsand@livekit/rtc-nodewere pinned atlatest, which a committed lockfile cannot honour. Pinned to what resolved (^1.6.2,^0.13.33).Then it was wired to the conference window
Testing against a live LiveKit found the seam between the two features, and it was worse than "not wired": with both enabled, the room's call button led to a preflight that never went anywhere. An embedded provider answers the join with an empty
url— there is no page to send anyone to — and the conference page read a missing url as "not joined yet", so it re-rendered the preflight forever.Start_recordingand friends. The hang-up button read "End call with Call" on group calls.Steps to test or reproduce
Requires a LiveKit deployment; see the deploy doc. Then, as admin:
Verified
Against a live LiveKit (
wss://livekit.dev.rocket.chat):video-conference.capabilitiesembedded: truevideo-conference.startvideo-conference.joinurl: "", records the participant…livekit.transport.configmin-width: 0, no horizontal overflowyarn typecheckclean · jest 309 suites / 2214 passing · mocha 2258 · definition 152 · eslint clean over the ported tree.Not verified — please exercise before this goes anywhere near merge
MediaCallRoomSectionnow rendersCallStagefor direct calls too, replacing develop'sMediaCallCardList. That is the ported PR's design, but it changes existing VoIP call rendering and deserves a look on a running workspace.Dockerfile.alpinegained a musl build of the LiveKit FFI binding; the image was never built here.Further comments
MEDIA-CALLS-FEATURE.mdanddeploy/livekit/deploy.mdmoved underdocs/features/native-video-conference/, where this repo keeps feature docs. The CloudFormation templates stay indeploy/livekit/.🤖 Generated with Claude Code