# Consola web y Web Push

> HTML: https://remote.ivjn.us/docs/es/web-console/

La consola del navegador está en `https://remote.ivjn.us/console/`. Admite registro con verificación de correo, inicio de sesión con cuenta y contraseña o token de operador, dispositivos por espacio de trabajo, comandos con salida en directo, una PTY xterm, capturas/pulsaciones/navegación/entrada de texto de Android, métricas, registros de auditoría, creación y eliminación de dispositivos y notificaciones voluntarias. Las cuentas de solo lectura no pueden ejecutar comandos ni ver pantallas Android. Los dispositivos desconectados siguen visibles.

Los dispositivos que anuncian `ubus` muestran una pestaña **ubus** con objeto, método y parámetros de objeto JSON opcionales (vacío envía `null`). Las llamadas usan sesiones cifradas y requieren un dispositivo habilitado, en línea y permisos de operación. Cancelar termina la espera, pero no revierte métodos ya ejecutados. Cambiar de dispositivo o perder permisos borra las llamadas pendientes y los resultados.

El registro crea un espacio de trabajo independiente. Antes de iniciar sesión se debe verificar el correo con su código de ocho dígitos. La consola permite reenviar códigos y volver a la verificación tras recargar. Exchange necesita SMTP configurado: `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM` y los ajustes de autenticación y seguridad de `.env.example`. Sin un servicio de correo disponible, el registro muestra un error en lugar de omitir la verificación.

Las credenciales permanecen en la memoria de la página. Al recargar hay que iniciar sesión de nuevo. Cerrar sesión cierra las sesiones activas, intenta eliminar la suscripción push del navegador y revoca las sesiones de acceso por contraseña. Los tokens principales de operador conservan su duración habitual. Al pulsar una notificación, se enfoca una consola abierta sin recargar sus credenciales.

## Compilar y servir

```sh
cd website/console
npm ci
npm test
npm run build
```

Copia `dist/` al directorio `console/` del sitio. Mantén `website/assets/` disponible en `/assets/` para las fuentes y la marca compartidas. Sirve `index.html`, `sw.js` y el manifiesto con revalidación/no-cache. Los recursos con hash pueden almacenarse con caché inmutable. No almacenes en caché `/api/*` ni `/ws/*`.

La consola utiliza `/api/*` y `/ws/browser` del mismo origen. Configura el proxy inverso hacia Exchange conservando Host y permitiendo la actualización a WebSocket. No elimines los prefijos `/api` ni `/ws`. El servidor de desarrollo Vite redirige API/WS a `127.0.0.1:18082`. Rust Exchange permite por defecto acceso del navegador desde el mismo origen. Configure orígenes exactos con `REMOTE_CORS_ORIGINS`; solo un `*` explícito permite todos. Worker utiliza `CORS_ORIGINS` y sus propios valores predeterminados. La autenticación Bearer y los tickets WebSocket de un solo uso siguen siendo obligatorios; no se habilitan cookies entre orígenes.

La autenticación del navegador usa `POST /api/ws-ticket` con autorización Bearer, seguido de `GET /ws/browser` y una primera trama JSON `{"ticket":"..."}`. Los tickets caducan en 30 segundos, son de un solo uso y, al consumirse, vuelven a comprobar que el operador esté habilitado y sus credenciales sean válidas. Las credenciales duraderas nunca se incluyen en las URL de WebSocket. Los clientes nativos siguen usando `/ws/operator`.

Todas las operaciones de dispositivo utilizan sesiones WebSocket selladas nativas. HTTP ofrece autenticación, metadatos de dispositivos, directorios de claves y autorizaciones y administración de sesiones del servicio de retransmisión; consulte [API HTTP para operaciones del agente](/docs/es/http-api.md).

## Activar Web Push

Define ambas variables en el entorno de Exchange; omitir las dos desactiva push:

```text
WEB_PUSH_VAPID_PRIVATE_KEY=<base64url-unpadded-32-byte-P256-private-key>
WEB_PUSH_VAPID_SUBJECT=https://remote.ivjn.us
```

Genera una clave privada con `python scripts/generate-vapid-key.py`. Guárdala fuera de Git en un archivo de entorno con acceso restringido, mantenla estable entre reinicios y realiza una copia de seguridad segura. La clave pública se deriva automáticamente. Si cambias la clave, los navegadores deben suscribirse de nuevo.

`WEB_PUSH_ALLOWED_ORIGINS` contiene de forma predeterminada los orígenes de confianza exactos `https://fcm.googleapis.com,https://updates.push.services.mozilla.com,https://web.push.apple.com`. Añade otro proveedor solo después de verificar su origen push HTTPS. Los endpoints proporcionados por clientes no pueden ampliar esta lista. Nunca se siguen redirecciones.

Rutas autenticadas:

| Método y ruta | Finalidad |
| --- | --- |
| GET `/api/webpush/config` | Estado de activación y clave pública del servidor de aplicaciones |
| GET/POST `/api/webpush/subscriptions` | Listar los metadatos propios o registrar/actualizar una suscripción |
| DELETE `/api/webpush/subscriptions/{id}` | Eliminar una suscripción propia |
| POST `/api/webpush/test` | Encolar una prueba visible para las suscripciones del solicitante |
| POST `/api/webpush/messages` | Enviar a su cuenta o, como administrador, a otra cuenta del mismo espacio |
| POST `/api/webpush/devices/{id}/messages` | Notificar a administradores suscritos que gestionan el dispositivo autenticado |

Las suscripciones utilizan `PushSubscription.toJSON()` del navegador, `application_server_key` y `triggers`. `triggers` admite como máximo un objeto `online` y otro `offline`. Cada objeto contiene `kind`, `tick_count`, con valor predeterminado 3 y rango 1–100, y `device_ids` opcional. `offline` notifica tras `tick_count` fallos consecutivos; `online`, tras `tick_count` éxitos consecutivos. `device_ids` puede ser un ID de dispositivo como cadena, una lista de IDs, o estar omitido/ser `null` para abarcar todo el espacio de trabajo. Un array `triggers` vacío suscribe el navegador sin avisos automáticos de conexión/desconexión. No se aceptan `missed_count` ni `success_count`. Los mensajes usan `operator_id` opcional y `notification` con `title`, `body`, y los campos opcionales `url`, una ruta relativa del mismo origen, y `tag`. `/webpush/` es un cliente de referencia mínimo; la consola de producción utiliza su propio worker limitado a `/console/`.

La migración `0005_web_push.sql` añade suscripciones y una bandeja de salida persistente. `0008_online_offline_triggers.sql` traslada las notificaciones de dispositivos a `triggers` y elimina `missed_count` / `success_count`. `202` significa encolado, sin garantizar entrega ni visualización. Los trabajos caducan a los 15 minutos. Los errores de red y las respuestas 408, 429 y 5xx se reintentan hasta cinco veces; 404/410 eliminan la revisión correspondiente de la suscripción. Un arrendamiento de 90 segundos permite recuperar el trabajo pendiente tras un fallo del worker. La entrega es al menos una vez y la visualización depende del proveedor, los permisos y la política del sistema operativo. Bajo sobrecarga, los eventos de dispositivos previos al encolado persistente se procesan según disponibilidad; los ya encolados sobreviven a reinicios. Límites: 20 suscripciones por cuenta, 32 mensajes encolados por suscripción y 30 modificaciones por minuto y cuenta.

iOS/iPadOS puede exigir añadir la consola a la pantalla de inicio antes de activar las notificaciones. La accesibilidad del servicio Push del navegador es independiente de la conexión a Exchange.

## Validación

Ejecuta `cargo test --workspace`, `cargo clippy --workspace --all-targets -- -D warnings` y las pruebas y compilación npm de la consola. Para la integración PostgreSQL/HTTPS, instala `psycopg[binary]`, define `WEB_PUSH_TEST_ADMIN_URL` con una conexión PostgreSQL de prueba con permiso CREATE DATABASE y ejecuta `python scripts/test-webpush.py`. La prueba crea y elimina una base de datos desechable; no utilices un rol con privilegios que no quieras poner a su disposición.

## Enviar desde todos los clientes

Web Console incluye un formulario en las notificaciones del navegador. Los ajustes de Console móvil ofrecen envío Web Push, pruebas y un enlace a Web Console. Los ajustes locales del Agent de escritorio, del Agent Android y de la extensión del navegador envían con las credenciales del dispositivo. Los Agents sin interfaz/OpenWrt utilizan el siguiente comando. Active las notificaciones en Web Console/PWA para recibirlas; no se añaden receptores nativos FCM/APNs.

```sh
remote-cli webpush status
remote-cli webpush subscriptions
remote-cli webpush test
remote-cli webpush send --title "Backup ready" --body "Completed"
remote-cli webpush send --operator-id ACCOUNT_UUID --title "Notice" --body "Hello"
remote-agent --config /path/to/config.json webpush --title "Device ready" --body "Completed"
```

```json
{"notification":{"title":"Backup ready","body":"Completed","url":"/console/","device_id":"edge-1"}}
```

POST `/api/webpush/messages` utiliza autenticación Bearer del operador. Si se omite `operator_id`, se envía a su cuenta; otra cuenta requiere permisos de administrador y pertenecer al mismo espacio. El JSON anterior también funciona con `remote-cli webpush send --json-file message.json`. Los activadores de estado vacíos desactivan avisos automáticos de conexión/desconexión y mantienen los mensajes generales.

POST `/api/webpush/devices/{id}/messages` utiliza el token Bearer del dispositivo y acepta directamente un objeto de notificación (`title`, `body`, `tag` opcional). Solo reciben las suscripciones activas de administradores habilitados que gestionan ese dispositivo. El servidor fija `device_id`, `event=device.message`, `occurred_at` y `/console/`; el remitente no elige cuenta ni suplanta otro dispositivo. Los dispositivos comparten 30 solicitudes/minuto/espacio, con un máximo de 256 suscripciones por envío. Siguen vigentes los límites de cuentas y colas. Sin destinatarios activos se devuelve `202 {"queued":0}`; `queued` cuenta suscripciones, no entregas.
