# Matrix Client-Server Protocol — referencia para matrix_client_pc Documentacion del trafico real entre `matrix_client_pc` y el homeserver (Synapse + MAS). Foco: que endpoint se llama, con que payload, que vuelve, y como mapea al tipo Go bound a Wails (frontend Mantine). **Fuentes:** - Matrix Client-Server API v1.11 — https://spec.matrix.org/v1.11/client-server-api/ - MSC3861 (OAuth2 + MAS) — https://github.com/matrix-org/matrix-spec-proposals/pull/3861 - mautrix-go v0.23 — capa que consumimos (no llamamos endpoints crudos en Go salvo casos puntuales) - element-web (`sources/element-web/`) — referencia de comportamiento UI; usa `matrix-js-sdk` que ataca los mismos endpoints **Convencion:** - Base homeserver: `https://matrix.organic-machine.com` - Base MAS: `https://mas.organic-machine.com` - Todas las requests autenticadas llevan `Authorization: Bearer ` salvo login. - Todas las respuestas son JSON UTF-8. --- ## Indice 1. [Flujo de login OIDC (MAS)](#1-flujo-de-login-oidc-mas) 2. [Bootstrap de sesion](#2-bootstrap-de-sesion) 3. [Sync loop](#3-sync-loop) 4. [Listado de rooms + state](#4-listado-de-rooms--state) 5. [Timeline (messages history)](#5-timeline-messages-history) 6. [Envio de mensajes](#6-envio-de-mensajes) 7. [E2EE (Olm/Megolm + cross-signing)](#7-e2ee-olmmegolm--cross-signing) 8. [Media (mxc://)](#8-media-mxc) 9. [Profile + presence](#9-profile--presence) 10. [Mapeo Go (bound) ↔ TS (frontend)](#10-mapeo-go-bound--ts-frontend) 11. [Errores: matriz canonica](#11-errores-matriz-canonica) --- ## 1. Flujo de login OIDC (MAS) Cubierto por `mas_oidc_loopback_go_infra` (registry). Flow OAuth2 Authorization Code + PKCE. ### 1.1 Discovery del issuer ``` GET https://matrix.organic-machine.com/.well-known/matrix/client ``` Respuesta: ```json { "m.homeserver": { "base_url": "https://matrix.organic-machine.com" }, "org.matrix.msc2965.authentication": { "issuer": "https://mas.organic-machine.com/", "account": "https://mas.organic-machine.com/account" }, "org.matrix.msc4143.rtc_foci": [ { "type": "livekit", "livekit_service_url": "..." } ] } ``` `org.matrix.msc2965.authentication.issuer` indica que el HS delega auth a MAS. ### 1.2 OIDC discovery ``` GET https://mas.organic-machine.com/.well-known/openid-configuration ``` Respuesta (claves consumidas): ```json { "issuer": "https://mas.organic-machine.com/", "authorization_endpoint": "https://mas.organic-machine.com/authorize", "token_endpoint": "https://mas.organic-machine.com/oauth2/token", "registration_endpoint": "https://mas.organic-machine.com/oauth2/registration", "scopes_supported": ["openid", "urn:matrix:org.matrix.msc2967.client:api:*", "urn:matrix:org.matrix.msc2967.client:device:*"], "code_challenge_methods_supported": ["S256"] } ``` ### 1.3 Dynamic client registration (RFC 7591) ``` POST https://mas.organic-machine.com/oauth2/registration Content-Type: application/json { "client_name": "matrix_client_pc", "application_type": "native", "redirect_uris": ["http://127.0.0.1:/callback"], "token_endpoint_auth_method": "none", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:" } ``` Respuesta: ```json { "client_id": "01HEXXXX...", "client_id_issued_at": 1716565000 } ``` `device_id` lo genera el cliente (16 chars hex). Se reutiliza en cada login para preservar las Olm sessions. ### 1.4 Authorization request (browser) mautrix-go abre browser SO con: ``` GET https://mas.organic-machine.com/authorize? response_type=code& client_id=& redirect_uri=http://127.0.0.1:/callback& scope=openid+urn:matrix:org.matrix.msc2967.client:api:*+urn:matrix:org.matrix.msc2967.client:device:& state=& code_challenge=& code_challenge_method=S256 ``` Usuario aprueba en MAS UI. MAS redirige a: ``` http://127.0.0.1:/callback?code=&state= ``` ### 1.5 Token exchange ``` POST https://mas.organic-machine.com/oauth2/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=& redirect_uri=http://127.0.0.1:/callback& client_id=& code_verifier= ``` Respuesta: ```json { "access_token": "mat_xxx...", "token_type": "Bearer", "expires_in": 300, "refresh_token": "mar_xxx...", "scope": "openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:" } ``` Persistido por `keyring_token_store_go_infra`. ### 1.6 Refresh `expires_in` corto (5 min). mautrix refresca automaticamente con: ``` POST https://mas.organic-machine.com/oauth2/token Content-Type: application/x-www-form-urlencoded grant_type=refresh_token& refresh_token=& client_id= ``` Misma respuesta. Si MAS rota refresh_token (`rotate_refresh_tokens: true`), el viejo deja de servir tras un grace period. ### 1.7 Logout ``` POST https://mas.organic-machine.com/oauth2/revoke Content-Type: application/x-www-form-urlencoded token=& token_type_hint=refresh_token& client_id= ``` Mas Logout local: borrar token de keyring + `client.Logout()` de mautrix (envia `POST /_matrix/client/v3/logout`). --- ## 2. Bootstrap de sesion Tras obtener token, el cliente arranca con `matrix_client_init_go_infra`. ### 2.1 /whoami — validar token + descubrir DeviceID ``` GET /_matrix/client/v3/account/whoami Authorization: Bearer ``` Respuesta: ```json { "user_id": "@lucas:organic-machine.com", "device_id": "QWERTYUIOP", "is_guest": false } ``` Usado para rellenar `SessionView` (`UserID`, `DeviceID`). ### 2.2 /versions — capacidades del HS (opcional) ``` GET /_matrix/client/versions ``` Respuesta: ```json { "versions": ["v1.1", "v1.2", ..., "v1.11"], "unstable_features": { "org.matrix.msc3861": true, "org.matrix.msc4108": true } } ``` Util si la app condiciona features por capability. --- ## 3. Sync loop Cubierto por `matrix_sync_service_go_infra`. Long-poll continuo. ### 3.1 Initial sync ``` GET /_matrix/client/v3/sync? filter=& timeout=0 Authorization: Bearer ``` Respuesta (esquema simplificado): ```json { "next_batch": "s12345_67_8_9_10_11_12_13", "rooms": { "join": { "!roomId:server": { "summary": { "m.heroes": ["@bob:server"], "m.joined_member_count": 2, "m.invited_member_count": 0 }, "state": { "events": [ ] }, "timeline": { "events": [ ], "limited": true, "prev_batch": "t12-34_5_6" }, "ephemeral": { "events": [ ] }, "account_data": { "events": [ ] }, "unread_notifications": { "highlight_count": 0, "notification_count": 1 } } }, "invite": { "!roomId:server": { "invite_state": { "events": [...] } } }, "leave": { ... } }, "presence": { "events": [...] }, "account_data": { "events": [ ] }, "to_device": { "events": [ ] }, "device_lists": { "changed": ["@bob:server"], "left": [] }, "device_one_time_keys_count": { "signed_curve25519": 50 } } ``` ### 3.2 Incremental sync (long-poll) ``` GET /_matrix/client/v3/sync?since=&timeout=30000 ``` mautrix re-llama con el `next_batch` previo. timeout 30s. Si nada cambia → respuesta vacia tras 30s. Si hay evento nuevo → respuesta inmediata. ### 3.3 Eventos clave que matrix_client_pc procesa | Event type | Donde aparece | Que hacer | |---|---|---| | `m.room.message` | `rooms.join.X.timeline.events` | Render en timeline | | `m.room.encrypted` | `rooms.join.X.timeline.events` | Descifrar via Crypto.Decrypt → re-render como `m.room.message` | | `m.room.member` | state + timeline | Update memberlist | | `m.room.name` / `m.room.topic` / `m.room.avatar` | state | Update room header | | `m.room.encryption` | state | Marcar room como E2EE | | `m.room.redaction` | timeline | Tachar evento referido | | `m.reaction` (m.annotation) | timeline | Agregar al evento target | | `m.typing` (ephemeral) | `rooms.join.X.ephemeral` | Indicador "X is typing" | | `m.receipt` (ephemeral) | `rooms.join.X.ephemeral` | Read markers de otros users | | `m.direct` (account_data global) | `account_data.events` | Marcar rooms como DM | | `m.tag` (account_data por room) | `rooms.join.X.account_data` | Favoritos / low priority | | `m.room_key` (to_device) | `to_device.events` | Inbox group session — guardar para descifrar mensajes futuros | ### 3.4 Filters Para acotar el sync (perf). Pre-creado con: ``` POST /_matrix/client/v3/user//filter Authorization: Bearer { "room": { "timeline": { "limit": 50 }, "state": { "lazy_load_members": true } }, "presence": { "not_types": ["m.presence"] } } ``` Respuesta: `{"filter_id": "1"}`. Se pasa como `?filter=1` en /sync. mautrix lo gestiona internamente. --- ## 4. Listado de rooms + state Cubierto por `matrix_room_list_go_infra`. Lee del store del SyncStore (no requiere request extra si el sync ya corrio). Si se quiere fetch directo: ### 4.1 Joined rooms ``` GET /_matrix/client/v3/joined_rooms ``` Respuesta: ```json { "joined_rooms": ["!a:server", "!b:server", ...] } ``` ### 4.2 Room state (todo el estado actual) ``` GET /_matrix/client/v3/rooms//state ``` Respuesta: array de state events: ```json [ { "type": "m.room.name", "state_key": "", "content": { "name": "Equipo" }, "sender": "@admin:server", ... }, { "type": "m.room.topic", "state_key": "", "content": { "topic": "..." }, ... }, { "type": "m.room.encryption", "state_key": "", "content": { "algorithm": "m.megolm.v1.aes-sha2" }, ... }, { "type": "m.room.member", "state_key": "@lucas:server", "content": { "membership": "join", "displayname": "Lucas" }, ... } ] ``` ### 4.3 State event puntual ``` GET /_matrix/client/v3/rooms//state/[/] ``` Ejemplo nombre: ``` GET /_matrix/client/v3/rooms/!a:server/state/m.room.name ``` Respuesta: `content` del evento directo: ```json { "name": "Equipo" } ``` ### 4.4 Mapeo a `infra.RoomSummary` | Campo Go | Origen | |---|---| | `RoomID` | key del map `rooms.join` | | `Name` | `m.room.name` state event → `content.name`. Fallback: `summary.m.heroes` + display names | | `CanonicalAlias` | `m.room.canonical_alias` state event → `content.alias` | | `AvatarMxc` | `m.room.avatar` → `content.url` | | `Topic` | `m.room.topic` → `content.topic` | | `IsDirect` | `account_data` global `m.direct` lista al roomID | | `IsSpace` | `m.room.create` → `content.type == "m.space"` | | `IsEncrypted` | existencia de state event `m.room.encryption` | | `Tags` | `rooms.join.X.account_data` event `m.tag` → keys del map | | `LastEventTs` | `timeline.events[-1].origin_server_ts` | --- ## 5. Timeline (messages history) Cubierto por `MatrixService.LoadTimeline` → mautrix `client.Messages(...)`. ### 5.1 /messages — paginacion historica ``` GET /_matrix/client/v3/rooms//messages? from=& # prev_batch del sync o de respuesta anterior dir=b& # b=backward (mas antiguo), f=forward limit=50& filter= Authorization: Bearer ``` Respuesta: ```json { "start": "", "end": "", "chunk": [ { "type": "m.room.message", "event_id": "$abc", "sender": "@x:s", "origin_server_ts": 1716..., "content": { "msgtype": "m.text", "body": "hola" } }, ... ], "state": [ ] } ``` ### 5.2 /context — un evento + vecinos Para ir a un permalink: ``` GET /_matrix/client/v3/rooms//context/?limit=10 ``` Respuesta: ```json { "event": { ... }, "events_before": [ ... ], "events_after": [ ... ], "start": "", "end": "", "state": [ ... ] } ``` ### 5.3 Mapeo a `MatrixEvent` | Campo Go | Origen | |---|---| | `EventID` | `event_id` | | `RoomID` | path param (no esta en el body de /messages) | | `Sender` | `sender` | | `Type` | `type` | | `Ts` | `origin_server_ts` | | `Body` | `content.body` (si `type=m.room.message`) | | `EncryptedRaw` | `true` si `type=m.room.encrypted` y no se pudo descifrar | --- ## 6. Envio de mensajes Cubierto por `matrix_message_send_go_infra` → `MatrixService.SendText` / `SendMarkdown`. ### 6.1 PUT /send/{type}/{txnId} ``` PUT /_matrix/client/v3/rooms//send// Authorization: Bearer Content-Type: application/json ``` `txnId` = string unico por request (mautrix usa nanosecond timestamp). Idempotente: re-PUT del mismo txnId NO duplica. Respuesta: ```json { "event_id": "$xyz..." } ``` ### 6.2 Variantes de `content` **Texto plano:** ```json { "msgtype": "m.text", "body": "hola mundo" } ``` **Markdown con HTML formateado:** ```json { "msgtype": "m.text", "body": "hola **mundo**", "format": "org.matrix.custom.html", "formatted_body": "hola mundo" } ``` **Reply (MSC2781 fallback + m.in_reply_to):** ```json { "msgtype": "m.text", "body": "> <@alice:s> hola\n\nrespuesta", "format": "org.matrix.custom.html", "formatted_body": "
In reply to @alice:s
hola
respuesta", "m.relates_to": { "m.in_reply_to": { "event_id": "$abc" } } } ``` **Edit (m.replace):** ```json { "msgtype": "m.text", "body": "* nuevo body", "m.new_content": { "msgtype": "m.text", "body": "nuevo body" }, "m.relates_to": { "rel_type": "m.replace", "event_id": "$abc" } } ``` **Reaction (m.annotation):** type=`m.reaction`, no `m.room.message`: ```json { "m.relates_to": { "rel_type": "m.annotation", "event_id": "$abc", "key": "👍" } } ``` PUT a `/send/m.reaction/`. ### 6.3 En rooms E2EE Si `client.Crypto != nil` (configurado via `matrix_crypto_init_go_infra`), mautrix: 1. Toma el `content` plano. 2. Lo cifra con la outbound Megolm session del room. 3. Sustituye el body por: ```json { "algorithm": "m.megolm.v1.aes-sha2", "ciphertext": "AwgBE...", "session_id": "abc...", "sender_key": "curve25519...", "device_id": "QWERTYUIOP" } ``` 4. PUT a `/send/m.room.encrypted/` (tipo cambia a `m.room.encrypted`). El frontend NO ve esta diferencia — `SendText` retorna el `event_id` igual. ### 6.4 Redact (delete event) ``` PUT /_matrix/client/v3/rooms//redact// { "reason": "spam" } ``` Respuesta: `{ "event_id": "$redact..." }`. --- ## 7. E2EE (Olm/Megolm + cross-signing) Cubierto por `matrix_crypto_init_go_infra` + mautrix cryptohelper. ### 7.1 Upload device keys + one-time keys ``` POST /_matrix/client/v3/keys/upload { "device_keys": { "user_id": "@lucas:s", "device_id": "QWERTYUIOP", "algorithms": ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"], "keys": { "curve25519:QWERTYUIOP": "", "ed25519:QWERTYUIOP": "" }, "signatures": { "@lucas:s": { "ed25519:QWERTYUIOP": "" } } }, "one_time_keys": { "signed_curve25519:AAAAAA": { "key": "", "signatures": { ... } }, ... } } ``` Respuesta: ```json { "one_time_key_counts": { "signed_curve25519": 50 } } ``` mautrix mantiene siempre ≥50 OTKs disponibles. Cuando /sync devuelve `device_one_time_keys_count.signed_curve25519 < 50`, sube mas. ### 7.2 Query device keys (de otros) ``` POST /_matrix/client/v3/keys/query { "device_keys": { "@bob:s": [] } } # array vacio = todos los devices ``` Respuesta: ```json { "device_keys": { "@bob:s": { "DEVICE1": { "user_id": "@bob:s", "device_id": "DEVICE1", "algorithms": [...], "keys": {...}, "signatures": {...} } } }, "master_keys": { "@bob:s": { ... cross-signing master key ... } }, "self_signing_keys": { ... }, "user_signing_keys": { ... } } ``` ### 7.3 Claim OTK (para iniciar Olm session) ``` POST /_matrix/client/v3/keys/claim { "one_time_keys": { "@bob:s": { "DEVICE1": "signed_curve25519" } } } ``` Respuesta: ```json { "one_time_keys": { "@bob:s": { "DEVICE1": { "signed_curve25519:AAAA": { "key": "...", "signatures": {...} } } } } } ``` ### 7.4 Send-to-device (compartir Megolm room key) ``` PUT /_matrix/client/v3/sendToDevice/m.room.encrypted/ { "messages": { "@bob:s": { "DEVICE1": { "algorithm": "m.olm.v1.curve25519-aes-sha2", "ciphertext": { "": { "type": 0|1, "body": "..." } }, "sender_key": "" } } } } ``` Una vez Bob descifra ese to-device con Olm, obtiene el `m.room_key` con la inbound Megolm session que le permite descifrar todos los mensajes posteriores del room. ### 7.5 Cross-signing keys (4S, secret storage) ``` POST /_matrix/client/v3/keys/device_signing/upload POST /_matrix/client/v3/keys/signatures/upload GET /_matrix/client/v3/user//account_data/m.cross_signing.{master,self_signing,user_signing} GET /_matrix/client/v3/user//account_data/m.secret_storage.default_key GET /_matrix/client/v3/user//account_data/m.secret_storage.key. ``` mautrix cryptohelper gestiona todo. matrix_client_pc solo expone API "verify device" / "recover from passphrase" — ambos traducen a estas requests. --- ## 8. Media (mxc://) ### 8.1 Download ``` GET /_matrix/client/v1/media/download// Authorization: Bearer # v1 require auth (v3 sin auth deprecado) ``` Respuesta: bytes binarios. `Content-Type` indica el mime. ### 8.2 Thumbnail ``` GET /_matrix/client/v1/media/thumbnail//? width=96&height=96&method=scale&animated=false ``` ### 8.3 Upload ``` POST /_matrix/media/v3/upload Content-Type: image/png Authorization: Bearer ``` Respuesta: ```json { "content_uri": "mxc://organic-machine.com/abc123def" } ``` ### 8.4 mxc:// → URL http Helper canonico (no es endpoint, es transformacion): ``` mxc://organic-machine.com/abc123def → /_matrix/client/v1/media/download/organic-machine.com/abc123def ``` Para avatares en `AvatarMxc` del `RoomSummary`, el frontend resuelve con un wrapper que appende el Bearer token. NO se puede meter en `` directo (necesita auth header). --- ## 9. Profile + presence ### 9.1 Displayname + avatar ``` GET /_matrix/client/v3/profile/ ``` Respuesta: ```json { "displayname": "Lucas", "avatar_url": "mxc://..." } ``` ### 9.2 Set displayname ``` PUT /_matrix/client/v3/profile//displayname { "displayname": "Nuevo nombre" } ``` ### 9.3 Presence ``` PUT /_matrix/client/v3/presence//status { "presence": "online", "status_msg": "trabajando" } ``` Synapse desactiva presence por defecto. matrix_client_pc no debe asumir que esta disponible. --- ## 10. Mapeo Go (bound) ↔ TS (frontend) Wails serializa structs Go → objetos JS via JSON tags. Mantener sincronia con tipos TS. ### 10.1 SessionView ```go type SessionView struct { UserID string `json:"user_id"` DeviceID string `json:"device_id"` HomeserverURL string `json:"homeserver_url"` HasToken bool `json:"has_token"` ExpiresAt string `json:"expires_at,omitempty"` } ``` ```ts export interface SessionView { user_id: string; device_id: string; homeserver_url: string; has_token: boolean; expires_at?: string; } ``` ### 10.2 RoomSummary ```go type RoomSummary struct { RoomID string `json:"room_id"` Name string `json:"name,omitempty"` CanonicalAlias string `json:"canonical_alias,omitempty"` AvatarMxc string `json:"avatar_mxc,omitempty"` Topic string `json:"topic,omitempty"` IsDirect bool `json:"is_direct"` IsSpace bool `json:"is_space"` IsEncrypted bool `json:"is_encrypted"` Tags []string `json:"tags,omitempty"` LastEventTs int64 `json:"last_event_ts,omitempty"` } ``` ```ts export interface RoomSummary { room_id: string; name?: string; canonical_alias?: string; avatar_mxc?: string; topic?: string; is_direct: boolean; is_space: boolean; is_encrypted: boolean; tags?: string[]; last_event_ts?: number; } ``` ### 10.3 MatrixEvent (historico timeline) ```go type MatrixEvent struct { EventID string `json:"event_id"` RoomID string `json:"room_id"` Sender string `json:"sender"` Type string `json:"type"` Ts int64 `json:"ts"` Body string `json:"body,omitempty"` EncryptedRaw bool `json:"encrypted_raw"` } ``` ```ts export interface MatrixEvent { event_id: string; room_id: string; sender: string; type: string; // "m.room.message" | "m.room.encrypted" | "m.reaction" | ... ts: number; // ms desde epoch body?: string; // plain text (puede ser fallback del m.room.encrypted descifrado) encrypted_raw: boolean; // true = no se pudo descifrar, mostrar placeholder } ``` ### 10.4 SyncEventView (push en vivo via Wails event) ```go type SyncEventView struct { Type string `json:"type"` RoomID string `json:"room_id"` EventID string `json:"event_id"` Sender string `json:"sender"` Ts int64 `json:"ts"` Body string `json:"body,omitempty"` } ``` Emit Wails: `runtime.EventsEmit(ctx, "matrix:event", view)`. Listener TS: ```ts EventsOn("matrix:event", (ev: SyncEventView) => { ... }); ``` ### 10.5 Diagnostics ```go type Diagnostics struct { SyncRunning bool `json:"sync_running"` LastSyncAt string `json:"last_sync_at,omitempty"` CryptoEnabled bool `json:"crypto_enabled"` HomeserverURL string `json:"homeserver_url"` NextBatchToken string `json:"next_batch_token,omitempty"` PendingEvents int `json:"pending_events"` } ``` --- ## 11. Errores: matriz canonica Synapse devuelve `application/json` con shape estandar Matrix: ```json { "errcode": "M_FORBIDDEN", "error": "You do not have permission to send a message in this room" } ``` HTTP status varia. Tabla minima: | HTTP | errcode | Significado | Accion en matrix_client_pc | |---|---|---|---| | 401 | `M_UNKNOWN_TOKEN` | Token revocado / expirado | Trigger refresh OIDC; si falla → forzar re-login | | 401 | `M_MISSING_TOKEN` | Sin Authorization | Bug — siempre debe ir Bearer | | 403 | `M_FORBIDDEN` | Permission denied | Mostrar toast, no retry | | 403 | `M_USER_DEACTIVATED` | Cuenta deshabilitada | Logout + mensaje claro | | 404 | `M_NOT_FOUND` | Room/event no existe | Refrescar lista | | 429 | `M_LIMIT_EXCEEDED` | Rate limit | Backoff exponencial (mautrix lo hace solo) | | 400 | `M_INVALID_PARAM` | Body malformado | Bug del cliente | | 400 | `M_UNKNOWN` | Generico | Log + reportar | | 502/503/504 | (no JSON) | HS caido | Reintentar con backoff | **Errores E2EE (descifrado):** | Sintoma | Causa | Mitigacion | |---|---|---| | `m.room.encrypted` recibido pero no se descifra | Falta `m.room_key` inbound (llego antes de unirnos) | Mostrar placeholder; key backup recovery (si configurado) | | `Olm: OUTBOUND_GROUP_SESSION_NOT_FOUND` al enviar | Crypto store corrupto | Wipe + re-bootstrap (ver memoria `agents-e2ee-unblock-pattern`) | | `M_FORBIDDEN` al PUT /send/m.room.encrypted | El user no esta en room o no tiene power level | Bug de UI; chequear membership antes | --- ## Apendice A — Endpoints que matrix_client_pc consume HOY (auditoria) | MatrixService method | mautrix call | Endpoint HTTP | |---|---|---| | `Login()` | `mas_oidc_loopback.Run()` | OIDC flow (§1) | | `Start(userID)` | `client.SyncWithContext()` | GET /sync (§3) | | `StartNoCrypto(userID)` | igual sin crypto store | GET /sync (§3) | | `ListRooms()` | lee del SyncStore en memoria | (ninguno extra) | | `LoadTimeline(roomID, limit)` | `client.Messages(roomID, "", "b", filter, limit)` | GET /rooms/{id}/messages (§5.1) | | `SendText(roomID, body)` | `client.SendMessageEvent(roomID, "m.room.message", content)` | PUT /rooms/{id}/send/m.room.message/{txn} (§6.1) | | `SendMarkdown(roomID, md)` | parse markdown → mismo SendMessageEvent | PUT /rooms/{id}/send/m.room.message/{txn} (§6.1) | | `Logout(userID)` | `client.Logout()` + revoke MAS | POST /_matrix/client/v3/logout + POST /oauth2/revoke | ## Apendice B — Endpoints pendientes (gap del cliente) Estos NO estan envueltos todavia y son el roadmap natural: | Capability | Endpoint | Funcion registry candidata | |---|---|---| | Read receipts | `POST /rooms/{id}/receipt/m.read/{eventId}` | `matrix_receipt_send_go_infra` | | Typing notifications | `PUT /rooms/{id}/typing/{userId}` | `matrix_typing_set_go_infra` | | Redact (delete) | `PUT /rooms/{id}/redact/{eventId}/{txn}` | `matrix_redact_event_go_infra` | | Reply / edit / reaction | ya cubierto por `matrix_message_send` (variantes) | — (anadir wrappers en MatrixService) | | Upload media | `POST /_matrix/media/v3/upload` | `matrix_media_upload_go_infra` | | Download mxc | `GET /_matrix/client/v1/media/download/...` | `matrix_media_download_go_infra` | | Resolve room alias | `GET /_matrix/client/v3/directory/room/{alias}` | `matrix_resolve_alias_go_infra` | | Join room | `POST /_matrix/client/v3/join/{idOrAlias}` | `matrix_room_join_go_infra` | | Leave room | `POST /_matrix/client/v3/rooms/{id}/leave` | `matrix_room_leave_go_infra` | | Invite | `POST /_matrix/client/v3/rooms/{id}/invite` | `matrix_room_invite_go_infra` | | Create room | `POST /_matrix/client/v3/createRoom` | `matrix_room_create_go_infra` | | Set displayname | `PUT /profile/{userId}/displayname` | `matrix_profile_set_go_infra` | | Verify device (SAS) | flujo to-device `m.key.verification.*` | `matrix_verify_sas_go_infra` | | Key backup (recovery) | `GET/PUT /room_keys/...` + 4S | `matrix_key_backup_go_infra` | Cada gap → issue + spawn `fn-constructor`. --- ## Apendice C — Referencias element-web (solo lectura, AGPL — NO copiar codigo) | Tema | Fichero element-web | Concepto a portar | |---|---|---| | Reply fallback parse | `apps/web/src/utils/Reply.ts` | Stripping `` antes de render | | Composer markdown | `apps/web/src/editor/serialize.ts` | Pipeline markdown → html sanitizado | | Permalink parsing | `apps/web/src/utils/permalinks/` | `matrix.to/#/!r:s/$e` → roomId + eventId | | Reaction aggregation | `apps/web/src/utils/Reactions.ts` | Agrupar por key + contar | | Room list sort | `apps/web/src/stores/room-list/algorithms/` | Recency / favorites / DM tiers | | Verification UX state | `apps/web/src/verification.ts` | SAS state machine: started → key_received → confirmed | | Notifier (push rules) | `apps/web/src/Notifier.ts` | Eval push rules → tray/sound/badge | Patron: leer fichero element-web como referencia algoritmica, reescribir limpio en Go o TS, citar en `source_file` del frontmatter. --- ## Mantenimiento de este documento Cada vez que se anade una capability nueva a `MatrixService`: 1. Documentar endpoint(s) reales que mautrix golpea (capturar con `MAUTRIX_LOG_LEVEL=trace` si dudas). 2. Anadir request/response shape al apartado correspondiente. 3. Anadir mapeo Go ↔ TS si es nuevo tipo bound. 4. Mover la fila del Apendice B al Apendice A. Ultima revision: 2026-05-25.