Files
uniweb/app.md
T
egutierrez 103a7f2f05 feat: persistent session (no re-unlock on reload) + reconnect ACL after createRoom
Session persistence (web/src/session.ts): the unlocked wallet identity is kept
across reloads so an F5 no longer forces a password re-unlock. By default it lives
in sessionStorage (survives F5, cleared with the tab); with 'keep me signed in' it
lives in localStorage (survives closing the browser) bounded by a 30-day absolute
TTL and a 12-hour inactivity auto-lock. logout clears it; activity (send/createRoom)
refreshes the idle timer. No cookie is ever used — the private key never travels to
any server. WalletLogin gains the 'keep me signed in' checkbox; Recover/Join keep
the session by default (recovering/creating on a device implies it is yours).
App.tsx restores the session on mount before falling back to the unlock screen.

ACL reconnect: a room created while connected was not in the NATS per-subject ACL
grant (subjects are frozen at connect time), so its first messages silently did not
deliver until a re-login. WsNatsTransport gains reconnect(); BusClient.refresh()
calls it; busService.createRoom reconnects after creating so the new room is usable
immediately. Bumps uniweb to 0.4.0.
2026-06-14 13:58:06 +02:00

143 lines
7.8 KiB
Markdown

---
name: uniweb
lang: ts
domain: infra
version: 0.4.0
description: "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."
tags: [messaging, web, frontend, e2e]
uses_functions: []
uses_types: []
framework: "react"
entry_point: "web/src/main.tsx"
dir_path: "projects/message_bus/apps/uniweb"
repo_url: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/dataforge/uniweb"
icon:
phosphor: "chats-circle"
accent: "#6366f1"
e2e_checks:
- id: typecheck
cmd: "cd web && pnpm install --frozen-lockfile && pnpm exec tsc --noEmit -p tsconfig.app.json"
timeout_s: 180
- id: unit
cmd: "cd web && pnpm test"
timeout_s: 120
- id: web_build
cmd: "cd web && pnpm build"
timeout_s: 180
---
## Qué es
`uniweb` es el **cliente web browser-nativo** del bus [unibus](../unibus/app.md): 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
```bash
# El bus ya corre (cluster unibus con WebSocket habilitado, --ws-port). Apunta la SPA a un
# nodo y arráncala en dev (puerto 5173, que coincide con la CORS allowlist del cluster):
cd web && pnpm install
VITE_BUS_HTTP=https://<nodo>:8470 VITE_BUS_WS=wss://<nodo>:8480 pnpm dev
# Navegador: http://localhost:5173
# Producción: build estático y sirve web/dist con cualquier static server.
cd web && pnpm build # genera web/dist
```
## 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**: el dev server corre en `http://localhost:5173` para coincidir con la
`--cors-origins` del cluster. Otro origen necesita añadirse a esa allowlist.
- 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.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.
- 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.