From f40ea03cfb3f15370951638ee8b55ff6d8ff1242 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:28:54 +0200 Subject: [PATCH] docs: update system specs for federated DM calls v2 --- docs/systems/dm-system.md | 2 +- docs/systems/voice.md | 31 ++++++++++++++++++++++++++++++- docs/systems/websocket.md | 16 ++++++++-------- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/docs/systems/dm-system.md b/docs/systems/dm-system.md index 2f8c57f4..1ad3b5e9 100644 --- a/docs/systems/dm-system.md +++ b/docs/systems/dm-system.md @@ -284,7 +284,7 @@ If a `member_add` federation event arrives for a soft-deleted channel (non-null **Request:** `{ content?: string, attachments?: string[], replyToId?: string }` -**Cross-instance access:** Federated users (those with `homeInstance` set) can send messages on any DM channel where they are a member, regardless of which instance serves the request. The `requireLocalUser` gate that previously blocked federated users from DM write endpoints has been removed. DM calls (`dm_call_*` WS events) remain gated — only local (non-replicated) users can initiate or receive calls. +**Cross-instance access:** Federated users (those with `homeInstance` set) can send messages on any DM channel where they are a member, regardless of which instance serves the request. The `requireLocalUser` gate that previously blocked federated users from DM write endpoints has been removed. DM calls work across federated instances. The caller's instance hosts the LiveKit room; remote clients connect directly. Call signaling is relayed to all active federation peers via synchronous HTTP POST (not the outbox worker). See `docs/systems/voice.md` for the full federated call architecture. **Validation:** - Caller must be a member (`isDmMember`) diff --git a/docs/systems/voice.md b/docs/systems/voice.md index 67a36195..fa7cbe83 100644 --- a/docs/systems/voice.md +++ b/docs/systems/voice.md @@ -52,7 +52,36 @@ States: `ringing` → `active` → destroyed ## Federated DM Calls -DM calls work across federated instances. The caller's instance hosts the LiveKit room; remote clients connect to it directly. +DM calls work across federated instances. The caller's instance hosts the LiveKit room; remote clients connect to it directly. Call signaling is relayed to ALL active federation peers via synchronous HTTP POST (not the outbox worker). This ensures calls ring on every instance where a participant is connected, even if the DM is local-only on the caller's instance. + +### Universal Relay + +All `dm_call_*` signaling events (`start`, `accept`, `reject`, `end`) are relayed to every active federation peer in parallel via `Promise.all`. Each `sendCallRelay` call has a 10-second timeout. This is a synchronous HTTP POST to the peer's federation endpoint — it bypasses the outbox worker entirely because call signaling is latency-sensitive and must not be queued. + +### Dual-Path Processing + +When a peer instance receives a call relay, it uses one of two delivery paths: + +| Path | Condition | Delivery | +|------|-----------|----------| +| **A** | DM exists on the receiving instance | Look up `dm_members` for the local `dmChannelId` and deliver to connected members | +| **B** | DM does not exist on the receiving instance | Match participants by `homeUserId + homeInstance` identity against connected WebSocket users | + +Path B enables calls to ring for federated users even when no local DM channel has been created yet (e.g., first contact via a federated call). + +### FederatedCallEntry + +The in-memory call state (`FederatedCallEntry`) is keyed by `federatedId` (not `dmChannelId`): + +- `dmChannelId` is **nullable** — null for Path B scenarios where no local DM channel exists +- `ringedUserIds` tracks all users who were notified of the incoming call, used for end-call cleanup +- `callerId`, `callerHomeUserId`, `callerHomeInstance` identify the caller across instances + +### Late-Bind dmChannelId + +When `findOrCreateDmChannel` creates a local DM channel during an active federated call (e.g., the first message arrives while a call is ringing), it binds the `dmChannelId` on the existing `FederatedCallEntry`. This transitions the call from Path B to Path A delivery without interrupting the call. + +### Token Generation & Room Identity **Token generation:** `generateFederatedCallToken(federatedId, homeUserId, displayName)` in `routes/livekit.ts` issues 5-minute tokens scoped to the `federatedId` room (not the local `dmChannelId`). Grants full DM permissions (mic, camera, screen share, subscribe, data channel). diff --git a/docs/systems/websocket.md b/docs/systems/websocket.md index 26b3dc49..1d603377 100644 --- a/docs/systems/websocket.md +++ b/docs/systems/websocket.md @@ -71,10 +71,10 @@ Source: `packages/server/src/ws/handler.ts`, `packages/server/src/ws/events.ts` ### DM Calls | type | fields | notes | |------|--------|-------| -| `dm_call_start` | dmChannelId | 60s auto-timeout if not accepted | -| `dm_call_accept` | dmChannelId | ringing→active | -| `dm_call_reject` | dmChannelId | | -| `dm_call_end` | dmChannelId | | +| `dm_call_start` | dmChannelId?, federatedCallId? | `dmChannelId` can be null when `federatedCallId` is provided. 60s auto-timeout if not accepted | +| `dm_call_accept` | dmChannelId?, federatedCallId? | ringing→active | +| `dm_call_reject` | dmChannelId?, federatedCallId? | | +| `dm_call_end` | dmChannelId?, federatedCallId? | | ### System | type | fields | @@ -166,10 +166,10 @@ reason: `'displaced'` (new tab) | `'session_closed'` ### DM Calls | type | fields | scope | |------|--------|-------| -| `dm_call_incoming` | dmChannelId, callerId, callerName, livekitUrl?, livekitToken? | DM members (excludes caller). Federation fields present for cross-instance calls. | -| `dm_call_accepted` | dmChannelId | DM members | -| `dm_call_rejected` | dmChannelId | DM members | -| `dm_call_ended` | dmChannelId | DM members | +| `dm_call_incoming` | dmChannelId?, federatedCallId, callerId, callerName, callOrigin?, livekitUrl?, livekitToken? | DM members (excludes caller). `dmChannelId` can be null for Path B federated calls (no local DM channel). `callOrigin` identifies the hosting instance for cross-instance calls. | +| `dm_call_accepted` | dmChannelId?, federatedCallId? | DM members | +| `dm_call_rejected` | dmChannelId?, federatedCallId? | DM members | +| `dm_call_ended` | dmChannelId?, federatedCallId? | DM members | ### Social | type | fields | scope |