- 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>
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:
- 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; usamatrix-js-sdkque 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 <access_token>salvo login. - Todas las respuestas son JSON UTF-8.
Indice
- Flujo de login OIDC (MAS)
- Bootstrap de sesion
- Sync loop
- Listado de rooms + state
- Timeline (messages history)
- Envio de mensajes
- E2EE (Olm/Megolm + cross-signing)
- Media (mxc://)
- Profile + presence
- Mapeo Go (bound) ↔ TS (frontend)
- 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.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/<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_infra → MatrixService.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:
- Toma el
contentplano. - Lo cifra con la outbound Megolm session del room.
- Sustituye el body por:
{
"algorithm": "m.megolm.v1.aes-sha2",
"ciphertext": "AwgBE...",
"session_id": "abc...",
"sender_key": "curve25519...",
"device_id": "QWERTYUIOP"
}
- PUT a
/send/m.room.encrypted/<txnId>(tipo cambia am.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:
- Documentar endpoint(s) reales que mautrix golpea (capturar con
MAUTRIX_LOG_LEVEL=tracesi dudas). - Anadir request/response shape al apartado correspondiente.
- Anadir mapeo Go ↔ TS si es nuevo tipo bound.
- Mover la fila del Apendice B al Apendice A.
Ultima revision: 2026-05-25.