Skip to content

Commit edeef85

Browse files
Frank Karlitschekclaude
andcommitted
feat(matrix): Matrix rooms as Talk conversations
Talk can now act as a Matrix client: users link a Matrix account on an admin-configured homeserver and their Matrix rooms appear as ordinary Talk conversations - in the web UI as well as in the unchanged mobile and desktop apps. Protocol layer: new standalone, framework-free library nextcloud/matrix-client-php under matrix-client/ (PSR-18/17, no OCP imports, own test suite) including a pure-PHP implementation of the Matrix end-to-end encryption stack (Olm, Megolm, SAS verification, key backup) that is verified byte-for-byte against Element's vodozemac in an interop harness. Talk integration under lib/Matrix/: background /sync with per-account locks and opportunistic foreground sync, ingestion of messages, state, membership and power levels into Talk rooms/attendees (new actor type 'matrix', object type 'matrix'), decrypt-on-ingest with placeholder replacement, key sharing/requests and device verification (emoji), room key backup restore, media proxy with thumbnails and Save-to-Nextcloud, uploads as (encrypted) Matrix attachments, reactions/edits/deletions/ threads/read receipts in both directions, room creation/DM/join-by-address and the public room directory, invitations through the existing federation invitation API, per-room capability detection exposed as matrixCapabilities, admin settings (homeservers, policies, sync health), occ commands (talk:matrix:*) and documentation (docs/matrix.md, full specification in docs/matrix/SPEC.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent a8d8850 commit edeef85

175 files changed

Lines changed: 19079 additions & 21 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

appinfo/info.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
<screenshot>https://raw.githubusercontent.com/nextcloud/spreed/main/docs/video-verfication.png</screenshot>
6262

6363
<dependencies>
64-
<nextcloud min-version="36" max-version="36" />
64+
<nextcloud min-version="34" max-version="36" />
6565
</dependencies>
6666

6767
<background-jobs>
@@ -79,6 +79,8 @@
7979
<job>OCA\Talk\BackgroundJob\ResetAssignedSignalingServer</job>
8080
<job>OCA\Talk\BackgroundJob\RetryNotificationsJob</job>
8181
<job>OCA\Talk\BackgroundJob\SendScheduledMessages</job>
82+
<job>OCA\Talk\Matrix\BackgroundJob\MatrixSync</job>
83+
<job>OCA\Talk\Matrix\BackgroundJob\MatrixCleanup</job>
8284
</background-jobs>
8385

8486
<repair-steps>
@@ -106,6 +108,11 @@
106108
<command>OCA\Talk\Command\Developer\GenerateChats</command>
107109
<command>OCA\Talk\Command\Developer\UpdateDocs</command>
108110

111+
<command>OCA\Talk\Command\Matrix\Homeserver</command>
112+
<command>OCA\Talk\Command\Matrix\Account</command>
113+
<command>OCA\Talk\Command\Matrix\Status</command>
114+
<command>OCA\Talk\Command\Matrix\Rekey</command>
115+
109116
<command>OCA\Talk\Command\Monitor\Calls</command>
110117
<command>OCA\Talk\Command\Monitor\HasActiveCalls</command>
111118
<command>OCA\Talk\Command\Monitor\Room</command>

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"autoload": {
33
"psr-4": {
4-
"OCA\\Talk\\": "lib/"
4+
"OCA\\Talk\\": "lib/",
5+
"Nextcloud\\Matrix\\": "matrix-client/src/"
56
}
67
},
78
"config": {

docs/capabilities.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,6 @@
237237
* `config => call => external-call-service` (local) - The target URL for an external call service if one is configured
238238
* `bot-features-api` (local) - Whether bots can fetch their own enabled features using their shared secret
239239
* `promote-demote-owner` - Whether owners can promote other participants to owner and demote other owners again, by sending the `participantType` parameter when promoting or demoting a participant
240+
241+
## 25
242+
* `matrix-rooms` (local) - Matrix rooms can be shown as conversations; `config => matrix => enabled|can-link|linked|homeservers|e2ee-enabled|upload-enabled|typing-enabled`; conversations of Matrix rooms carry `matrixRoomId` and `matrixCapabilities`

docs/chat.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,3 +569,13 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob
569569
* `avatar_removed` - {actor} removed the conversation avatar
570570
* `federated_user_added` - {actor} invited {federated_user} / {federated_user} accepted the invitation
571571
* `federated_user_removed` - {actor} removed {federated_user} / {federated_user} declined the invitation
572+
573+
### Matrix system messages
574+
575+
* `matrix_user_added` - {user} joined the Matrix room (Matrix-only member)
576+
* `matrix_user_removed` - {user} left the Matrix room
577+
* `matrix_room_upgraded` - The Matrix room was upgraded, the conversation is read-only
578+
* `matrix_call_unsupported` - A call was started in Matrix (calls are not supported in Talk for Matrix rooms)
579+
* `matrix_encryption_enabled` - Encryption was enabled for the Matrix room
580+
581+
Rich object `matrix-media` (`{object}`): `id` (Matrix event id), `name`, `mimetype`, `size`, `msgtype`, `mxc`, `encrypted`, `width`, `height`, `link` (download through Talk).

docs/constants.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
| `sample` | No | Room is a sample conversation | User ID the sample |
2222
| `event` | Yes | Event conversation created via the calendar | Start and end unix timestamp of the event concatenated by pound sign: `start#end` |
2323
| `extended_conversation` | Yes | Room is created from another conversation (e.g. adding a participant to 1-1) | Token of previous conversation |
24+
| `matrix` | No | Conversation mirrors a Matrix room (`matrix-rooms` capability) | Matrix room id |
2425
| `external_call` | Yes | Room whose calls are handled by an external video service (see [External Call Service](external-call-service.md)) | Meeting/room identifier on the external service side |
2526
| `classified` | No | Classified conversation queued for automatic deletion after a call (retention `retention_classified_rooms`) | Unix timestamp of the call that queued the deletion |
2627
| `classified_persist` | No | Classified conversation a moderator kept (unbound), so it is no longer auto-deleted | Unix timestamp of when it was kept |
@@ -122,6 +123,7 @@ Required capability: `conversation-presets`
122123
### Attendee types
123124
* `users` - Logged-in users
124125
* `federated_users` - Federated users invited by their CloudID
126+
* `matrix` - Matrix users without a Nextcloud account, the actor id is the Matrix user id (`matrix-rooms` capability)
125127
* `groups` - Groups
126128
* `circles` - [Circle from the Circles app](https://github.com/nextcloud/circles)
127129
* `guests` - Guest without a login
@@ -151,6 +153,7 @@ Required capability: `conversation-presets`
151153
* `bridged` - Users whose messages are bridged in by the [Matterbridge integration](matterbridge.md)
152154
* `deleted_users` - Former logged-in users that got deleted (actor id is hardcoded to `deleted_users` and the display name is empty)
153155
* `federated_users` - Federated users
156+
* `matrix` - Matrix users without a Nextcloud account (`matrix-rooms` capability)
154157

155158
### Session states
156159
* `0` - Inactive (Notifications should still be sent, even though the user has this session in the room)

docs/conversation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
| `lastCommonReadMessage` | int | v3 | | ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability) |
9696
| `lastMessage` | message | v1 | | Last message in a conversation if available, otherwise empty. **Note:** Even when given the message will not contain the `parent` or `reactionsSelf` attribute due to performance reasons |
9797
| `objectType` | string | v1 | | The type of object that the conversation is associated with (See [Object types](constants.md#object-types)) |
98+
| `matrixRoomId` | string | v4 | `matrix-rooms` | Matrix room id, only for conversations with `objectType` = `matrix` |
99+
| `matrixCapabilities` | object | v4 | `matrix-rooms` | What the Matrix room and the current user in it support (`canSend`, `canInvite`, `canRename`, `isModerator`, `isDirect`, `encrypted`, `calls`, …), only for `objectType` = `matrix` |
98100
| `objectId` | string | v1 | | See [Object types](constants.md#object-types) documentation for explanation |
99101
| `breakoutRoomMode` | string | v4 | | Breakout room configuration mode (see [constants list](constants.md#breakout-room-modes)) (only available with `breakout-rooms-v1` capability) |
100102
| `breakoutRoomStatus` | string | v4 | | Breakout room status (see [constants list](constants.md#breakout-room-status)) (only available with `breakout-rooms-v1` capability) |

docs/matrix.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Matrix rooms in Talk
2+
3+
Talk can show and use rooms of a [Matrix](https://matrix.org) account as conversations. Talk's
4+
server acts as a Matrix *client* (one device per linked Nextcloud user): it syncs the rooms of the
5+
linked account into ordinary Talk conversations, so the web interface as well as the unchanged
6+
mobile and desktop apps work with them.
7+
8+
Status: **phase 4** – everything below plus: create Matrix rooms and direct chats from Talk,
9+
join rooms by address or from the public directory (*Matrix room …* in the conversation-list
10+
menu), restore encrypted history from the key backup (verified device or recovery key),
11+
mentions of Matrix-only members, `occ talk:matrix:rekey`. Phase 3 – everything from phases 1 and 2 plus reactions, edits, deletions and
12+
threads in both directions, read receipts to Matrix, inline media with thumbnails, "Save to
13+
Nextcloud" and uploads of shared files (also into encrypted rooms). Phase 2 – everything from phase 1 (account linking, rooms read/write, replies,
14+
invitations, membership, power levels, room name/topic) plus **end-to-end encrypted rooms**:
15+
Talk is a real Matrix E2EE device (Olm/Megolm implemented in PHP, interoperable with
16+
Element/vodozemac), decrypts incoming messages when they arrive, encrypts outgoing ones and
17+
shares room keys with the members' devices, decrypts encrypted attachments, and can be
18+
verified from another client via emoji (SAS). Media upload, reactions/edits/threads and room
19+
creation follow in later phases. See `docs/matrix/SPEC.md` for the full design.
20+
21+
## Administration
22+
23+
1. In *Administration settings → Talk → Matrix rooms* enable the feature.
24+
2. Add the homeserver(s) people may link accounts on. Talk resolves the server name via
25+
`.well-known/matrix/client` and validates `/_matrix/client/versions`; a client API URL can be set
26+
manually. Only these servers are ever contacted.
27+
3. Optionally restrict linking to groups and tune the synchronisation settings.
28+
29+
Synchronisation runs in the background job `MatrixSync` every time cron runs (interval 10 s, budget
30+
25 s per run, per-account locks). Real latency is bounded by how often cron runs – **run cron every
31+
minute**. When a person looks at a Matrix conversation in a client, Talk additionally syncs inline if
32+
the account's data is older than *foreground sync age* (default 5 s), so the web UI feels near-real-time.
33+
34+
`occ` commands:
35+
36+
```
37+
occ talk:matrix:homeserver add example.org [--name=Label] [--base-url=https://matrix.example.org]
38+
occ talk:matrix:homeserver list|remove|test <server-name>
39+
occ talk:matrix:account list
40+
occ talk:matrix:account sync --user=<uid> [--budget=20]
41+
occ talk:matrix:account unlink --user=<uid>
42+
occ talk:matrix:status [--output=json] # exit code 2 when accounts need a re-login
43+
occ talk:matrix:account request-keys --user=<uid> # ask the other devices for missing room keys again
44+
occ talk:matrix:rekey --old-secret=<previous config.php secret> [--dry-run] # after rotating "secret"
45+
```
46+
47+
App config keys (`occ config:app:set spreed <key>`): `matrix_enabled`, `matrix_allowed_groups`
48+
(JSON list), `matrix_sync_interval` (10–300 s, default 30), `matrix_idle_sync_interval` (default
49+
120), `matrix_max_parallel_syncs` (4), `matrix_foreground_sync_age` (5), `matrix_history_events`
50+
(200), `matrix_history_days` (30), `matrix_max_upload` (bytes), `matrix_typing_in`, `matrix_typing_out`,
51+
`matrix_e2ee_shared_lookup`, `matrix_e2ee_verified_only`.
52+
53+
## Linking an account
54+
55+
In Talk → Settings → *Matrix account* a person picks a homeserver and enters their Matrix username
56+
and password. Talk performs a `m.login.password` login once, stores only the access token (encrypted
57+
with the instance secret) and device id, and registers as a device named `Nextcloud Talk (<instance>)`.
58+
Rooms are imported immediately (bounded) and then kept in sync. If the homeserver invalidates the
59+
token, the person gets a notification and can log in again; the device id is reused.
60+
61+
Unlinking logs the device out on the homeserver, deletes the stored token and removes the person from
62+
their Matrix conversations. Conversations without any remaining linked Nextcloud user are deleted.
63+
64+
## End-to-end encryption
65+
66+
* On linking, Talk creates an Olm account for the new device and uploads its device keys plus
67+
one-time keys. Key material and tokens are stored encrypted with the instance secret in the
68+
`talk_matrix_*` crypto tables; **decrypted messages are stored like any other Talk message**
69+
(this is what makes search, previews and all clients work — the Nextcloud server is inside
70+
the trust boundary, see the spec).
71+
* Incoming encrypted events are decrypted during sync. When the key is missing the message is
72+
shown as "🔒 Encrypted message – waiting for the key", a key request is sent to the person's
73+
other devices and to the sender, and the placeholder is replaced in place once the key arrives.
74+
* Sending creates/rotates a Megolm session per room (rotation by age/message count/membership
75+
change) and shares it via Olm with every device of every member (unless the device is blocked,
76+
or the admin enabled *only trusted devices*).
77+
* **Verify the Talk device** in Talk → Settings → Matrix account → *Verify this device*: the
78+
request appears on the other Matrix clients (Element etc.), both sides compare seven emoji,
79+
then the other client cross-signs the Talk device and shares keys for rooms it knows. Until
80+
a device is verified, other clients may refuse to share keys with it.
81+
* Key requests from the person's *own* devices are answered only when that device is
82+
cross-signed or was verified. Forwarded keys are accepted only from such devices.
83+
* Admin policy per homeserver: *Allow encrypted rooms* off → Talk neither decrypts nor sends
84+
into encrypted rooms (placeholders only). `matrix_e2ee_shared_lookup` (default on) lets a
85+
message be decrypted with another linked Nextcloud user's copy of the key when they are in the
86+
same room; `matrix_e2ee_verified_only` restricts key sharing to cross-signed/verified devices.
87+
* **Encrypted history from before the link**: Talk → Settings → Matrix account → *Restore
88+
encrypted history*. After a verification the other client usually hands over the backup key
89+
automatically; otherwise paste the recovery key (`EsTc …`). Talk downloads the server-side key
90+
backup, imports the sessions and replaces the placeholders.
91+
* Not yet: verifying *other* users, creating cross-signing keys.
92+
93+
## How rooms map to conversations
94+
95+
| Matrix | Talk |
96+
|---|---|
97+
| joined room | group conversation with `objectType = "matrix"`, `objectId = <room id>` |
98+
| room name / canonical alias / heroes | conversation name (Matrix room-name algorithm) |
99+
| topic | description |
100+
| `m.room.avatar` (or the peer's avatar in a direct chat) | conversation avatar |
101+
| linked Nextcloud users | `users` attendees |
102+
| other members | attendees with actor type `matrix` (actor id = Matrix user id, display name and avatar from Matrix; avatar served by `/apps/spreed/matrix/avatar/{token}/{size}/{mxid}`) |
103+
| power level ≥ 50 / creator | moderator / owner |
104+
| `m.room.message` text/notice/emote | chat message (HTML → Markdown, pills → mentions) |
105+
| `m.image`/`m.file`/… | `matrix-media` rich object (link served by `/apps/spreed/matrix/media/{eventId}`, `?thumbnail=1` for previews, `POST …/save` copies it into the user's files) |
106+
| `m.in_reply_to` | reply |
107+
| `m.thread` | Talk thread (created on first use) |
108+
| `m.reaction` / redaction of one | reaction added / removed |
109+
| `m.replace` (edit by the original sender) | message edited |
110+
| `m.room.redaction` | message deleted (Talk moderator rules do not apply – Matrix power levels do) |
111+
| Nextcloud file shared into the conversation | uploaded to the homeserver and posted as `m.file`/`m.image`/… (encrypted attachment in encrypted rooms) |
112+
| membership changes | system messages (`matrix_user_added`, `matrix_user_removed` for Matrix-only members) |
113+
| `m.room.tombstone` | conversation becomes read-only, `matrix_room_upgraded` |
114+
| calls | `matrix_call_unsupported` system message |
115+
| invite | pending invitation in the federation invitation API (`remoteServerUrl` = `matrix:<server>`) |
116+
117+
Calls, lobby, SIP, listable, password, read-only toggle, message expiration, recording consent and
118+
permission changes are rejected with HTTP 405 `matrix-unsupported`. Renaming, topic, invites, kicks,
119+
promotions, leaving, reactions, edits, deletions and thread replies are executed on the Matrix side
120+
first (the homeserver enforces power levels); the Talk read marker is mirrored as an `m.read` receipt.
121+
122+
## API additions
123+
124+
* Capabilities: feature `matrix-rooms` (local), `config.matrix` with `enabled`, `can-link`, `linked`,
125+
`homeservers`, `e2ee-enabled`, `upload-enabled`, `typing-enabled`.
126+
* Conversation objects of Matrix rooms carry `matrixRoomId` and `matrixCapabilities` (room-level
127+
capabilities plus the current user's power-level dependent ones, e.g. `canSend`, `canInvite`,
128+
`canRename`, `isModerator`, `isDirect`, `encrypted`).
129+
* `GET|POST|PUT|DELETE /ocs/v2.php/apps/spreed/api/v1/matrix/account`, `POST …/matrix/account/sync`
130+
* Verification: `POST|GET|PUT|DELETE …/matrix/account/verification` (start, poll, confirm/reject, cancel)
131+
* Key backup: `POST …/matrix/account/backup` (`recoveryKey` optional)
132+
* Rooms: `POST …/matrix/room` (create/DM), `POST …/matrix/room/join` (`reference`), `GET …/matrix/room/directory`
133+
* Administration: `…/matrix/admin/homeserver[/{id}[/test]]`, `PUT …/matrix/admin/settings`, `GET …/matrix/admin/status`
134+
* Adding a participant to a Matrix conversation accepts `source=users` (linked Nextcloud user) or
135+
`source=matrix` with a Matrix user id.
136+
137+
## The Matrix client library
138+
139+
The protocol implementation lives in `matrix-client/` as the standalone, framework-free package
140+
`nextcloud/matrix-client-php` (namespace `Nextcloud\Matrix`). It has no Nextcloud dependency and
141+
its own test-suite; `examples/echo-bot.php` runs against a plain Synapse. Talk-specific code lives
142+
in `lib/Matrix/` (`OCA\Talk\Matrix`).

docs/matrix.md img/material-icons/matrix.svg lib/Controller/MatrixAccountController.php lib/Controller/MatrixAdminController.php lib/Controller/MatrixMediaController.php lib/Controller/MatrixRoomController.php lib/Migration/Version25000Date20260829120000.php lib/Migration/Version25000Date20260830120000.php src/components/AdminSettings/MatrixSettings.vue src/components/LeftSidebar/MatrixRoomDialog.vue src/components/MessagesList/MessagesGroup/Message/MessagePart/MatrixMedia.vue src/components/SettingsDialog/MatrixAccountSettings.vue src/services/matrixService.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)