---
name: matrix_sync_service
kind: function
lang: go
domain: infra
version: "0.1.0"
purity: impure
signature: "func MatrixSyncService(ctx context.Context, cfg MatrixSyncServiceConfig) (*MatrixSyncServiceHandle, error)"
description: "Arranca el sync loop de mautrix contra Synapse en background con backoff exponencial, emite eventos Matrix normalizados via canal Go y expone funcion de stop idempotente."
tags: [matrix, mautrix, sync, longpoll, reconnect, goroutine, channels, infra, matrix-mas]
params:
  - name: ctx
    desc: "Context padre. Si se cancela, la goroutine sale limpiamente y cierra los channels."
  - name: cfg.Client
    desc: "*mautrix.Client ya inicializado con credenciales (HomeserverURL, AccessToken, UserID). Usar matrix_client_init para crearlo. Obligatorio."
  - name: cfg.InitialBackoffMS
    desc: "Milisegundos de espera inicial entre reintentos tras error de sync. Default: 1000 (1s)."
  - name: cfg.MaxBackoffMS
    desc: "Techo del backoff exponencial en ms. Default: 60000 (60s)."
  - name: cfg.ChannelBuffer
    desc: "Capacidad del buffer del canal Events. Si el consumer va lento y el buffer se llena, el sync se bloquea hasta que el consumer drene. Default: 256."
output: "*MatrixSyncServiceHandle con Events <-chan MatrixSyncEvent (canal de eventos normalizados), Errors <-chan error (errores transitorios no fatales), Stop func() (cancela y cierra todo, idempotente)."
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports:
  - "maunium.net/go/mautrix"
  - "maunium.net/go/mautrix/event"
  - "maunium.net/go/mautrix/id"
tested: true
tests:
  - "RecibeMensajeYStop"
  - "BackoffRecovery"
  - "Error401NoExit"
  - "StopIdempotente"
  - "CtxCancelCierraChannels"
test_file_path: "functions/infra/matrix/matrix_sync_service_test.go"
file_path: "functions/infra/matrix/matrix_sync_service.go"
---

## Ejemplo

```go
ctx := context.Background()
h, err := MatrixSyncService(ctx, MatrixSyncServiceConfig{
    Client: client, // *mautrix.Client de matrix_client_init
})
if err != nil {
    panic(err)
}
defer h.Stop()

// Consumir errores transitorios en goroutine separada
go func() {
    for e := range h.Errors {
        log.Println("matrix sync error:", e)
    }
}()

// Loop de eventos (bloquea hasta que h.Stop() se llame o ctx sea cancelado)
for ev := range h.Events {
    fmt.Printf("[%s] %s: %s\n", ev.Type, ev.Sender, ev.Body)
}
```

## Cuando usarla

Usar despues de `MatrixClientInit` (y opcionalmente `MatrixCryptoInit`) para recibir el stream de eventos de Matrix en tiempo real. Es el servicio long-running central de cualquier cliente Matrix: matrix_client_pc, admin_panel, bots, monitores. Un solo `MatrixSyncService` por client, durante toda la vida de la aplicacion.

## Gotchas

- **Solo UN Sync por client**: dos goroutines llamando `SyncWithContext` simultaneamente sobre el mismo client rompe el `since` token y produce duplicados o perdidas. Esta funcion garantiza una sola goroutine de sync si es llamada una sola vez. NO llamar `MatrixSyncService` dos veces sobre el mismo `*mautrix.Client`.
- **Crypto antes del Sync**: mensajes `m.room.encrypted` que llegan antes de inicializar `MatrixCryptoInit` quedan sin descifrar (emitidos con `Type:"encrypted"`, `Body:""`, `Raw:*event.Event`). Inicializar crypto siempre ANTES de llamar a esta funcion.
- **Buffer de channel**: si el consumer no drena `Events` con suficiente rapidez, el sync se bloquea en el punto de emision. Synapse puede acumular deltas. Mantener el consumer rapido o aumentar `ChannelBuffer`.
- **Errores fatales (401/M_UNKNOWN_TOKEN)**: no cierran el servicio automaticamente — se emiten a `Errors` y el servicio espera con backoff maximo. El caller decide llamar `Stop()` y re-autenticar.
- **Stop idempotente**: llamar `Stop()` multiples veces es seguro; no causa panic.
- **Build tag**: el paquete `infra` requiere `-tags goolm` para compilar tests sin libolm (dependencia C de la crypto de mautrix). Los tests usan `//go:build goolm`.
