Files
matrix_client_pc/docs/matrix_protocol.md
T
egutierrez 41bafa57cc chore: auto-commit (17 archivos)
- app.md
- applog.go
- frontend/package.json
- frontend/package.json.md5
- frontend/vite.config.ts
- go.mod
- main.go
- matrix_service.go
- sqlite_driver.go
- .wails_dev.log
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:38:16 +02:00

27 KiB

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:

Convencion:

  • Base homeserver: https://matrix.organic-machine.com
  • Base MAS: https://mas.organic-machine.com
  • Todas las requests autenticadas llevan Authorization: Bearer <access_token> salvo login.
  • Todas las respuestas son JSON UTF-8.

Indice

  1. Flujo de login OIDC (MAS)
  2. Bootstrap de sesion
  3. Sync loop
  4. Listado de rooms + state
  5. Timeline (messages history)
  6. Envio de mensajes
  7. E2EE (Olm/Megolm + cross-signing)
  8. Media (mxc://)
  9. Profile + presence
  10. Mapeo Go (bound) ↔ TS (frontend)
  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:

{
  "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):

{
  "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:<loopback_port>/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:<device_id>"
}

Respuesta:

{ "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=<client_id>&
    redirect_uri=http://127.0.0.1:<port>/callback&
    scope=openid+urn:matrix:org.matrix.msc2967.client:api:*+urn:matrix:org.matrix.msc2967.client:device:<device_id>&
    state=<random>&
    code_challenge=<base64url(sha256(verifier))>&
    code_challenge_method=S256

Usuario aprueba en MAS UI. MAS redirige a:

http://127.0.0.1:<port>/callback?code=<auth_code>&state=<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=<auth_code>&
redirect_uri=http://127.0.0.1:<port>/callback&
client_id=<client_id>&
code_verifier=<verifier>

Respuesta:

{
  "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:<device_id>"
}

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=<rt>&
client_id=<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=<refresh_token>&
token_type_hint=refresh_token&
client_id=<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 <access_token>

Respuesta:

{
  "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:

{
  "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=<filter_id_o_json>&
    timeout=0
Authorization: Bearer <access_token>

Respuesta (esquema simplificado):

{
  "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": [ <state events> ] },
        "timeline": {
          "events": [ <message events> ],
          "limited": true,
          "prev_batch": "t12-34_5_6"
        },
        "ephemeral": { "events": [ <typing, receipts> ] },
        "account_data": { "events": [ <m.tag, etc> ] },
        "unread_notifications": { "highlight_count": 0, "notification_count": 1 }
      }
    },
    "invite": { "!roomId:server": { "invite_state": { "events": [...] } } },
    "leave": { ... }
  },
  "presence": { "events": [...] },
  "account_data": { "events": [ <m.direct, m.push_rules, etc> ] },
  "to_device": { "events": [ <m.room.encrypted, m.room_key, etc> ] },
  "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=<next_batch>&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/<user_id>/filter
Authorization: Bearer <token>

{
  "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:

{ "joined_rooms": ["!a:server", "!b:server", ...] }

4.2 Room state (todo el estado actual)

GET /_matrix/client/v3/rooms/<roomId>/state

Respuesta: array de state events:

[
  { "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/<roomId>/state/<eventType>[/<stateKey>]

Ejemplo nombre:

GET /_matrix/client/v3/rooms/!a:server/state/m.room.name

Respuesta: content del evento directo:

{ "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.avatarcontent.url
Topic m.room.topiccontent.topic
IsDirect account_data global m.direct lista al roomID
IsSpace m.room.createcontent.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/<roomId>/messages?
    from=<token>&            # prev_batch del sync o de respuesta anterior
    dir=b&                   # b=backward (mas antiguo), f=forward
    limit=50&
    filter=<json_opcional>
Authorization: Bearer <token>

Respuesta:

{
  "start": "<token_from>",
  "end": "<token_para_siguiente_pagina>",
  "chunk": [
    { "type": "m.room.message", "event_id": "$abc", "sender": "@x:s", "origin_server_ts": 1716..., "content": { "msgtype": "m.text", "body": "hola" } },
    ...
  ],
  "state": [ <state events relevantes en este rango> ]
}

5.2 /context — un evento + vecinos

Para ir a un permalink:

GET /_matrix/client/v3/rooms/<roomId>/context/<eventId>?limit=10

Respuesta:

{
  "event": { ... },
  "events_before": [ ... ],
  "events_after": [ ... ],
  "start": "<token>",
  "end": "<token>",
  "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_infraMatrixService.SendText / SendMarkdown.

6.1 PUT /send/{type}/{txnId}

PUT /_matrix/client/v3/rooms/<roomId>/send/<eventType>/<txnId>
Authorization: Bearer <token>
Content-Type: application/json

<content>

txnId = string unico por request (mautrix usa nanosecond timestamp). Idempotente: re-PUT del mismo txnId NO duplica.

Respuesta:

{ "event_id": "$xyz..." }

6.2 Variantes de content

Texto plano:

{ "msgtype": "m.text", "body": "hola mundo" }

Markdown con HTML formateado:

{
  "msgtype": "m.text",
  "body": "hola **mundo**",
  "format": "org.matrix.custom.html",
  "formatted_body": "hola <strong>mundo</strong>"
}

Reply (MSC2781 fallback + m.in_reply_to):

{
  "msgtype": "m.text",
  "body": "> <@alice:s> hola\n\nrespuesta",
  "format": "org.matrix.custom.html",
  "formatted_body": "<mx-reply><blockquote><a href=\"https://matrix.to/#/!r:s/$abc\">In reply to</a> <a href=\"https://matrix.to/#/@alice:s\">@alice:s</a><br>hola</blockquote></mx-reply>respuesta",
  "m.relates_to": { "m.in_reply_to": { "event_id": "$abc" } }
}

Edit (m.replace):

{
  "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:

{
  "m.relates_to": { "rel_type": "m.annotation", "event_id": "$abc", "key": "👍" }
}

PUT a /send/m.reaction/<txnId>.

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:
{
  "algorithm": "m.megolm.v1.aes-sha2",
  "ciphertext": "AwgBE...",
  "session_id": "abc...",
  "sender_key": "curve25519...",
  "device_id": "QWERTYUIOP"
}
  1. PUT a /send/m.room.encrypted/<txnId> (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/<roomId>/redact/<eventId>/<txnId>
{ "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": "<base64>",
      "ed25519:QWERTYUIOP": "<base64>"
    },
    "signatures": { "@lucas:s": { "ed25519:QWERTYUIOP": "<sig>" } }
  },
  "one_time_keys": {
    "signed_curve25519:AAAAAA": { "key": "<base64>", "signatures": { ... } },
    ...
  }
}

Respuesta:

{ "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:

{
  "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:

{
  "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/<txnId>
{
  "messages": {
    "@bob:s": {
      "DEVICE1": {
        "algorithm": "m.olm.v1.curve25519-aes-sha2",
        "ciphertext": { "<bob_curve25519>": { "type": 0|1, "body": "..." } },
        "sender_key": "<lucas_curve25519>"
      }
    }
  }
}

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/<user_id>/account_data/m.cross_signing.{master,self_signing,user_signing}
GET  /_matrix/client/v3/user/<user_id>/account_data/m.secret_storage.default_key
GET  /_matrix/client/v3/user/<user_id>/account_data/m.secret_storage.key.<keyId>

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/<serverName>/<mediaId>
Authorization: Bearer <token>            # v1 require auth (v3 sin auth deprecado)

Respuesta: bytes binarios. Content-Type indica el mime.

8.2 Thumbnail

GET /_matrix/client/v1/media/thumbnail/<serverName>/<mediaId>?
    width=96&height=96&method=scale&animated=false

8.3 Upload

POST /_matrix/media/v3/upload
Content-Type: image/png
Authorization: Bearer <token>

<binary bytes>

Respuesta:

{ "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 <img src> directo (necesita auth header).


9. Profile + presence

9.1 Displayname + avatar

GET /_matrix/client/v3/profile/<userId>

Respuesta:

{ "displayname": "Lucas", "avatar_url": "mxc://..." }

9.2 Set displayname

PUT /_matrix/client/v3/profile/<userId>/displayname
{ "displayname": "Nuevo nombre" }

9.3 Presence

PUT /_matrix/client/v3/presence/<userId>/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

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"`
}
export interface SessionView {
  user_id: string;
  device_id: string;
  homeserver_url: string;
  has_token: boolean;
  expires_at?: string;
}

10.2 RoomSummary

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"`
}
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)

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"`
}
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)

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:

EventsOn("matrix:event", (ev: SyncEventView) => { ... });

10.5 Diagnostics

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:

{
  "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 <mx-reply> 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.