Files
uniweb/app.md
T
2026-06-14 19:40:12 +02:00

9.3 KiB

name, lang, domain, version, description, tags, uses_functions, uses_types, framework, entry_point, dir_path, repo_url, icon, e2e_checks
name lang domain version description tags uses_functions uses_types framework entry_point dir_path repo_url icon e2e_checks
uniweb ts infra 0.6.0 Cliente web browser-nativo del bus unibus: SPA de chat (React+Mantine) con wallet por usuario (BIP39) que habla DIRECTO al bus (nats.ws + control-plane HTTPS firmado), sin gateway. La clave privada nunca sale del navegador.
messaging
web
frontend
e2e
react web/src/main.tsx projects/message_bus/apps/uniweb https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/dataforge/uniweb
phosphor accent
chats-circle #6366f1
id cmd timeout_s
typecheck cd web && pnpm install --frozen-lockfile && pnpm exec tsc --noEmit -p tsconfig.app.json 180
id cmd timeout_s
unit cd web && pnpm test 120
id cmd timeout_s
web_build cd web && pnpm build 180

Qué es

uniweb es el cliente web browser-nativo del bus unibus: la interfaz que un humano usa desde el navegador para hablar por el bus. Es solo frontend (web/) — una SPA, sin backend Go, sin gateway. Habla directamente con el bus, igual que unibus_android lo hace en Kotlin:

  • Control plane — HTTPS firmado al membershipd (rooms, claves, miembros). Cada request lleva la firma Ed25519 del usuario (cabeceras X-Unibus-*).
  • Data plane — NATS sobre WebSocket (nats.ws), autenticado con el nkey derivado de la identidad del usuario.

Stack: React 18 + Vite + Mantine v9. La identidad criptográfica de cada usuario se deriva de forma determinista de una frase BIP39 de 12 palabras y se cifra at-rest en el dispositivo (AES-256-GCM). La clave privada nunca sale del navegador: firma, sella y descifra en el cliente. No hay servidor al que enviarla.

El SDK del bus (web/src/bus/)

El protocolo y el cifrado E2E del bus están portados a TypeScript, validados byte a byte contra la implementación Go de referencia (vectores de unibus cmd/busvectors):

  • crypto.ts — Ed25519, ChaCha20-Poly1305, sealed box (nonce BLAKE2b, igual que Go).
  • frame.ts — wire format = encoding/json de Go byte a byte.
  • room.ts — Policy (ModeNATS / ModeMatrix).
  • busauth.ts — nkey NATS (base32 + crc16) + firma de requests del control-plane.
  • client.ts — envelope de room + BusClient + ControlPlane HTTP firmado.
  • wstransport.ts — transporte nats.ws.

busService.ts es la capa de datos de la SPA sobre el SDK (reemplazó al viejo módulo api que hablaba con el gateway). Ya no depende de unibus como módulo Go: el desacople es total.

Ejemplo

# Producción: SPA same-origin detrás de Caddy, que sirve la SPA + /api + /nats.
# Build estático y despliegue de web/dist:
cd web && pnpm install
pnpm build             # genera web/dist (se despliega a magnus:/opt/uniweb/dist)

# Dev (`pnpm dev`): el dev server NO tiene el proxy de Caddy, así que /api y /nats no
# existen en localhost. Apunta la SPA a un nodo real del cluster con las env vars
# (overrides del default same-origin). El dev server corre en el puerto 5174:
VITE_BUS_HTTP=https://<nodo>:8470 VITE_BUS_WS=wss://<nodo>:8480 pnpm dev
# Navegador: http://localhost:5174
# (Añade http://localhost:5174 a la --cors-origins del nodo, o el control-plane
#  rechazará la petición por CORS.)

Cuándo usarla

Cuando quieras que un humano hable por el bus desde un navegador, o cuando trabajes en la UI de chat / el onboarding wallet. Para la lógica del bus en sí (membresía, claves, peers programáticos) ve a unibus; uniweb es el cliente web encima.

Gotchas

  • wss:// con CA self-signed: el cluster sirve el WebSocket con el cert del bus (CA propia). Un navegador rechaza wss:// self-signed salvo que se importe la CA o se ponga un reverse proxy con cert válido (Let's Encrypt). En dev se puede aceptar el cert a mano.
  • Onboarding admin-side: el bus no tiene endpoint de auto-registro (el viejo gateway lo mockeaba). En enforce, una identidad nueva debe ser autorizada por un admin (membershipd user add) antes de poder abrir sesión; el flujo de Join muestra la clave pública del usuario para que un admin la autorice.
  • CORS / same-origin: en producción la SPA es same-origin detrás de Caddy (/api y /nats proxyados), así que no hay CORS. En dev (pnpm dev, puerto 5174) esos paths relativos no existen: hay que apuntar a un nodo con VITE_BUS_HTTP/VITE_BUS_WS y añadir http://localhost:5174 a la --cors-origins del nodo. El puerto 5173 está reservado a otra app local; si 5174 está ocupado, Vite usa el siguiente libre.
  • La passphrase del wallet nunca se guarda ni se envía; perderla en un dispositivo sin la mnemónica BIP39 = identidad irrecuperable en ese dispositivo (recuperable en otro con las 12 palabras).

Capability growth log

  • v0.6.0 (2026-06-14) — carga el histórico de cada room (GET /api/rooms/{id}/history) al abrirla, con dedup vs live; recargar ya no pierde los mensajes. ControlPlane.fetchHistory pega al control-plane (firmado, mismas cabeceras X-Unibus-*) y decodifica cada frame de base64-std; BusClient.history lo descifra/verifica con el MISMO camino de envelope que subscribe (refactor: helper privado openFrame compartido por ambos). En busService, bus.subscribeRoom (que usa ChatPanel) ahora siembra la room con su historia y sigue en vivo: dedup por frame.id con un Set por room y los mensajes live se bufferean hasta que la historia (oldest->newest) se entrega, garantizando el orden; si el endpoint falta (404/500) cae a live-only como antes. El ts de cada mensaje se deriva del ULID msgID (ulidTime, inverso de newULID) para que historia y live compartan reloj y ordenen bien; ChatPanel ordena por ts. El sidebar siembra su preview con history(id, 1) (sin traer todo), manteniendo el fallback "—" para rooms vacías. tsc + 23 unit (incluye ulid.test.ts)
    • pnpm build verdes.
  • v0.5.0 (2026-06-14) — nombres legibles en mensajes + sidebar con último mensaje/hora reales + pnpm dev documentado. (1) Los mensajes muestran el handle del remitente en vez del endpoint id: ControlPlane.fetchDirectory() pega al control-plane GET /api/directory (firmado) y busService mantiene un mapa endpoint -> handle (cargado al abrir sesión, refrescado tras createRoom); el resolver bus.displayName(endpoint) devuelve el handle o un id corto de fallback (nunca el endpoint largo), usado en la cabecera y el avatar de ChatPanel (el endpoint queda en un title para depurar). Resiliente: si el endpoint aún no existe en el cluster (404) el mapa queda vacío y el chat funciona igual que antes. (2) El sidebar muestra el último mensaje y la hora reales: busService posee un store de rooms con una suscripción de metadatos por room (último mensaje/hora + unread de rooms no activas); Sidebar ya no pinta el "01:00" de epoch-0. (3) pnpm dev queda usable tras el cambio a same-origin: apunta a un nodo con VITE_BUS_HTTP/VITE_BUS_WS y el dev server corre en el puerto 5174 (documentado en app.md + vite.config.ts). tsc + 19/19 unit + pnpm build verdes.
  • v0.3.0 (2026-06-14) — uniweb se vuelve cliente browser-nativo puro (issue 0001, Fase 2): la SPA se cablea al SDK del bus (busService.ts reemplaza el módulo api) y se elimina el gateway Go (cmd/webgw, go.mod, go.sum). uniweb queda como solo web/, sin nada de Go, sin dependencia de unibus como módulo. La clave privada se usa solo en el navegador (saveAndOpen/unlockAndOpen abren la sesión localmente; ya NO se hace POST /api/session con la privada — se cierra el agujero E2E del modelo gateway). Validado end-to-end contra el cluster descentralizado real (Fase 3): identidad registrada conecta por nats.ws y hace round-trip de un mensaje cifrado (crear room → publicar → recibir descifrado + firma verificada). El onboarding por token queda admin-side (el bus no tiene auto-registro). tsc + pnpm build + 19/19 unit verdes.
  • v0.2.0 (2026-06-13) — SDK del bus en TypeScript (web/src/bus/), issue 0001 Fase 1: el protocolo y el cifrado E2E del bus portados al navegador para que uniweb deje de depender del gateway Go. Módulos: crypto.ts (Ed25519, ChaCha20-Poly1305, sealed box con nonce BLAKE2b igual que Go), frame.ts (wire format = encoding/json de Go byte a byte), room.ts (Policy), busauth.ts (nkey NATS + firma de requests del control-plane), client.ts (envelope de room puro + BusClient sobre una interfaz de transporte + cliente HTTP firmado) y wstransport.ts (adaptador nats.ws). Paridad cross-language verificada contra vectores Go (cmd/busvectors): 19/19 tests verdes — endpoint id, firma Ed25519, AEAD, sealed box, frame marshal/sign, nkey y canonical request. La clave privada del usuario nunca se serializa hacia la red. La conexión nats.ws + control-plane reales se validan en la Fase 3 (E2E) por requerir un unibus vivo con WebSocket.
  • v0.1.0 (2026-06-13) — scaffold inicial: extracción de la SPA (web/) y el gateway (cmd/webgw) desde unibus v0.13.0 a su propia app/sub-repo. Sin cambios de capacidad respecto a lo que ya vivía en unibus 0.12.0 (wallet BIP39 + sesiones por usuario); solo cambia la ubicación y el módulo Go. go build/vet/test + pnpm build verdes en la nueva ubicación con los replace cross-repo.