Descripción
Cloudflare Pages Function (functions/api/webrtc/signal.ts) que actúa como canal de señalización efímero para las videollamadas WebRTC peer-to-peer del workspace del equipo (Edu/Dani/Txell).
El endpoint no transporta vídeo ni audio: solo intercambia los ~2 KB de metadatos técnicos (SDP offer/answer + ICE candidates) que los browsers necesitan para negociar la conexión directa. Una vez establecida la conexión, el vídeo va browser-a-browser vía STUN, sin pasar por ningún servidor.
Introducido en s79 (22-05-2026) como reemplazo de Jitsi. Ver [[decision—20260522—webrtc-reemplaza-jitsi]].
Operaciones
GET /api/webrtc/signal
Consulta mensajes de señalización pendientes para un peer.
Query params:
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
room | string | ✓ | Identificador de la sala |
peer | string | ✓ | ID del peer que consulta |
since | integer | — | ID del último mensaje recibido (paginación pull-incremental). Default: 0 |
Lógica de filtrado:
- Devuelve mensajes con
id > since. - Excluye mensajes del propio peer (
from_peer != peer). - Incluye mensajes dirigidos al peer (
to_peer = peer) o broadcasts (to_peer IS NULL). - Limit: 100 mensajes por poll.
Cleanup inline: antes de consultar, borra automáticamente todos los mensajes con created_at < now - 60s. Sin cron dedicado.
Respuesta: array JSON de objetos con payload ya parseado.
POST /api/webrtc/signal
Publica un mensaje de señalización.
Body JSON:
{
"room": "string",
"from_peer": "string",
"from_name": "Edu | Dani | Txell",
"kind": "peer_joined | peer_left | offer | answer | ice",
"payload": {},
"to_peer": "string | null"
}
Reglas de validación:
room,from_peer,kindson obligatorios.kinddebe estar en el conjunto{peer_joined, peer_left, offer, answer, ice}.from_namedebe ser un TeamMember válido (isTeamMember()).- Anti-spoofing:
from_namedebe coincidir con el miembro resuelto vía CF Access cookie (resolveTeamMember(request)). Rechaza con 403 si no coincide. payloadserializado no puede superar 8.000 bytes.
Respuesta 201: { id: <last_row_id>, ok: true }
Autenticación
Usa resolveTeamMember() de functions/_lib/staff, que valida la CF Access JWT cookie del request. Solo los 3 miembros del equipo (Edu/Dani/Txell) pueden acceder; cualquier otro recibe 401 Not authenticated as team member.
Flujo de señalización completo
Peer A entra a la sala
→ POST kind=peer_joined (broadcast, to_peer=null)
Peer B hace poll (GET since=0)
→ recibe peer_joined de A
Peer B → POST kind=offer (to_peer=A, payload=SDP offer)
Peer A → GET → recibe offer
Peer A → POST kind=answer (to_peer=B, payload=SDP answer)
Peer B → GET → recibe answer
Ambos intercambian ICE candidates (POST kind=ice, to_peer=otro)
→ Conexión P2P establecida — el vídeo ya no pasa por este endpoint
Polling continúa cada 1.5s para detectar nuevos peers / ICE candidates tardíos
Límites de tamaño de payload
| Tipo de mensaje | Tamaño típico | Límite configurado |
|---|---|---|
| SDP offer/answer | ~2 KB | 8 KB |
| ICE candidate | ~200 B | 8 KB |
| peer_joined/left | < 100 B | 8 KB |
Errores HTTP
| Código | Condición |
|---|---|
| 400 | Faltan params room/peer/kind |
| 400 | kind no permitido |
| 400 | from_name no es TeamMember válido |
| 401 | Cookie CF Access ausente o inválida |
| 403 | from_name no coincide con miembro autenticado (anti-spoofing) |
| 413 | Payload > 8.000 bytes |
| 500 | Fallo en INSERT D1 |
Implementación
- Archivo:
functions/api/webrtc/signal.ts - Runtime: Cloudflare Pages Functions (Edge, V8 isolate)
- Base de datos: D1 tabla
webrtc_signaling(ver [[entity—workspace—table—webrtc-signaling]]) - TTL mensajes: 60 segundos (cleanup inline, sin cron)
- Polling cliente: cada 1.500 ms desde
MeetingRoom.tsx
Véase también
- [[entity—workspace—table—webrtc-signaling]] — Tabla D1 que persiste los mensajes de señalización con TTL 60s
- [[feature—workspace—webrtc-peer-to-peer]] — Feature completa: arquitectura mesh, componente MeetingRoom, contexto de la migración
- [[decision—20260522—webrtc-reemplaza-jitsi]] — ADR que documenta por qué se abandonó Jitsi y se eligió WebRTC nativo
- [[workspace—que-es-workspace]] — Visión general del workspace interno del equipo (contexto de uso)