feat: MatrixRTC support: any-event notification, arbitrary state and MSC4354 sticky events

Draft
RO
15d ago
+1919-23

Proposed changes

What Rocket.Chat needs from the SDK to run LiveKit video calls in Matrix-federated rooms over MatrixRTC, the way
Element Call does. Rocket.Chat is the homeserver: it announces call memberships in the room on behalf of its users
and reads the announcements of other servers. Consumer: RocketChat/Rocket.Chat#42724 (draft, proof of concept).

  • fix: sendEventToAllServersInRoom derived a destination from any state_key. Call memberships are keyed
    _@alice:rc1_DEVICE_m.call, which produced the bogus destination rc1_DEVICE_m.call (and a key without :
    threw). Only a state key that is a user id adds a destination now.
  • feat: homeserver.matrix.event is emitted for every PDU accepted from federation, of any type, after the
    typed emits. Until now unknown types were stored but never reached the consumer.
  • feat: sendStateEvent(roomId, type, stateKey, content, sender) sends state of any type and state key
    ({} content included, which is how a call membership leaves), throwing EventRejectedError when the room's
    rules refuse it.
  • feat: rooms the SDK creates give org.matrix.msc3401.call.member power 0, as Element does, so any member
    can join a call.
  • feat: isUserJoined(roomId, userId) and getJoinedServers(roomId), to authorize token requests from other
    servers.
  • feat: MSC4354 sticky events, under the unstable msc4354_sticky field:
    • inbound sticky PDUs keep the field and get a sticky window, min(received_ts, origin_server_ts) + min(duration_ms, 1h), indexed for querying; rejected, state and late events are never sticky;
    • sendStickyEvent(roomId, type, content, sender, durationMs); the field is covered by hashes.sha256 and not by
      the event id or signatures, which are computed over the redacted form;
    • getActiveStickyEvents(roomId, { type?, now? });
    • homeserver.matrix.event carries sticky.expiresAt;
    • this server's own unexpired sticky events are pushed to a server that newly joins a room (MSC4354 obligation 2),
      after a short delay so the joiner has stored the room.
  • refactor: locally sent events share one build-and-persist path (enables sendStickyEvent).

Each commit builds and passes the tests on its own.

Not done

  • Soft-failure re-evaluation of sticky events (MSC4354 obligation 4).
  • Catch-up after a destination was unreachable for long: the outgoing queue is in memory.
  • Redactions received over federation are still not applied (pre-existing), so a redacted sticky event stays listed
    until it expires.
  • Spam checks and rate limits on sticky events.

Steps to test or reproduce

RUN_MONGO_TESTS=1 MONGO_URI='mongodb://127.0.0.1:<port>/?directConnection=true' bun test from the repo root.
Locally: 594 pass, 3 skip, 4 fail. The 4 failures are the PerDestinationQueue timer tests, which need
jest.advanceTimersByTime from bun 1.3 (the local bun was 1.2.20); they fail identically on 0.8.1.

The wire formats this follows are documented in docs/features/video-conference-federation/protocol.md on the
Rocket.Chat PR.

šŸ¤– Generated with Claude Code