--- id: "0152" title: "matrix-client-pc mini-webapps embebidas: Matrix Widget API v2" status: pending priority: high created: 2026-05-24 related_flows: ["0010"] related_issues: ["0151", "0153"] dependencies: ["0151"] tags: [matrix, widgets, webapps, iframe, sandbox, agents, postmessage] --- ## Objetivo Implementar host de widgets segun Matrix Widget API v2 (MSC2762, MSC2871, MSC2974). Cada room puede tener widgets activos publicados como state events `m.widget`. Los widgets son URLs cargadas en iframes sandboxed con bridge postMessage que da capabilities controladas (leer eventos del room, enviar eventos, mostrar UI overlay, etc.). Agentes de `agents_and_robots` pueden publicar widgets en sus rooms (ej. dashboard telemetria, formulario, kanban inline, panel de control del agente). ## Tareas 1. Backend Go: - `MatrixService.ListWidgets(roomID) -> []Widget` — lee state events `m.widget` del room. - `MatrixService.AddWidget(roomID, widget Widget)` — publica state event. - `MatrixService.RemoveWidget(roomID, widgetID)`. - `MatrixService.GenerateWidgetURL(widget Widget, userID) -> string` — substituye `$matrix_user_id`, `$matrix_room_id`, `$matrix_display_name`, `$matrix_avatar_url`, `$matrix_widget_id`, `$theme` en la URL del widget. - Slash command `/widget ` handler en composer (issue 0149) que crea state event con widget temporal. - `MatrixService.MintWidgetScopedToken(widgetID, userID) -> string` — token efimero con scope reducido (solo el room donde esta el widget). 2. Frontend React: - Hook `useWidgets(roomID)` — lista widgets activos. - Componente `WidgetPanel`: - Tabs por widget activo + boton "+" para anadir. - Cada widget en iframe con `sandbox="allow-scripts allow-same-origin allow-forms allow-popups-to-escape-sandbox"`. - `iframe.referrerpolicy="no-referrer"`. - CSP: `frame-src https: data: blob:`. - `WidgetBridge` — clase JS que escucha `postMessage` del iframe e implementa Widget API v2: - `capabilities` handshake: el widget declara que necesita, el host pide consentimiento usuario (dialog Mantine). - `read_events`, `send_event`, `send_to_device`, `get_openid`, `m.always_on_screen`, etc. - Whitelist estricta de capabilities concedidas. Audit log de mensajes en `store.db`. - Layout: widgets se abren en panel lateral derecho (toggleable) o en modal fullscreen. 3. Widgets internos primer batch (proof of concept): - `widget-jitsi-fallback` — si LiveKit falla, fallback a Jitsi via widget (URL config). - `widget-agent-panel` — panel de control de agente: estado, ultima ejecucion, restart, view logs. Servido por `agents_and_robots` HTTP API (issue 0113 ya creando agent runner API). - `widget-kanban` — kanban inline embebido para tasks del room. Reusa `apps/kanban` (Go) servido en LAN. - `widget-issue-tracker` — widget que abre issue API (`0109m`). 4. Tests: - `e2e/test_widget_capabilities.sh` — widget pide capability, dialog aparece, deniega/acepta funciona. - `e2e/test_widget_send_event.sh` — widget con capability `send_event` envia msg al room. - `e2e/test_widget_sandbox.sh` — widget malicioso (intenta `top.location =`) es bloqueado por sandbox. ## Funciones del registry a crear - `matrix_widget_state_go_infra` — CRUD state events `m.widget`. - `widget_url_template_go_core` — substituye placeholders en URL. - `widget_token_mint_go_infra` — token scoped a un widget+room+user. - `WidgetBridge_ts_ui` — clase postMessage bridge Widget API v2 completa. - `WidgetPanel_ts_ui` — UI tabs + iframes + permisos. - `CapabilityConsentDialog_ts_ui` — dialog Mantine para consentimiento. ## Acceptance - [ ] `/widget https://my.app` crea state event y abre iframe. - [ ] Widget declara capability `m.send_event` -> dialog Mantine pide consentimiento. - [ ] Widget concedido envia msg al room que aparece en timeline. - [ ] Widget malicioso `` bloqueado por sandbox. - [ ] `agents_and_robots` publica widget panel y se ve embebido en el room del agente. - [ ] Widget kanban inline funciona: drag&drop card persiste en DB del kanban. ## Notas **Anti-criterios:** - NO permitir `javascript:` ni `data:text/html` URLs (XSS). - NO conceder capabilities sin consentimiento explicito del usuario (auditable). - NO compartir el access_token Matrix del usuario al widget — usar siempre tokens scoped efimeros. **Decisiones:** - Widget API v2 (no v1) — soporta capabilities + tokens scoped. - iframe sandbox sin `allow-top-navigation` (previene escape). - CSP `frame-src https:` + permitir `data:`/`blob:` solo para widgets internos firmados. **Roadmap post-DoD:** - Widget marketplace interno: `widget-catalog` en `agents_and_robots` con widgets internos descubribles. - Widget templates: un agente publica un widget HTML estatico subido al room (`mxc://`) y el cliente lo renderiza desde la URL `mxc -> http`. - Cross-room widgets: widget que persiste entre rooms (TBD, requiere MSC propio).