docs: update system specs for federated DM calls v2

This commit is contained in:
Jannis Braun
2026-04-08 03:28:54 +02:00
parent ba7b975bc8
commit f40ea03cfb
3 changed files with 39 additions and 10 deletions
+1 -1
View File
@@ -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`)
+30 -1
View File
@@ -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).
+8 -8
View File
@@ -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 |