Files
uniweb/app.md
T
egutierrez 6c4baf1397 chore(uniweb): make pnpm dev usable after the same-origin switch
Same-origin (Caddy) means the SPA reaches /api and /nats through its own
origin in production, but those relative paths do not exist on the bare Vite
dev server, so `pnpm dev` no longer connects. busService already reads
VITE_BUS_HTTP / VITE_BUS_WS as overrides of the same-origin defaults — this
documents that path (Option A, no proxy code) and moves the dev server off the
port reserved by an unrelated local app.

- vite.config: dev server port 5173 -> 5174 (5173 is in use by another local
  app). strictPort left off so Vite falls back to the next free port. Comment
  explains the same-origin/dev split and the env-var override.
- app.md: Ejemplo and the CORS gotcha document the exact dev command
  (VITE_BUS_HTTP/WS pointing at a cluster node) on :5174 and the same-origin
  production model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:32:12 +02:00

150 lines
8.4 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
# 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.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.