3049265230
A merge left the v0.2.0 and v0.1.0 growth-log entries duplicated. Keep one entry per version in descending order and add the v0.5.0 line covering this release: readable handles in messages, sidebar with real last message/time, and the documented `pnpm dev` setup. Frontmatter version 0.4.0 -> 0.5.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
146 lines
8.2 KiB
Markdown
146 lines
8.2 KiB
Markdown
---
|
|
name: uniweb
|
|
lang: ts
|
|
domain: infra
|
|
version: 0.5.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.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.
|