CreaRack-SL

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ámetroTipoObligatorioDescripción
roomstring✓Identificador de la sala
peerstring✓ID del peer que consulta
sinceinteger—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, kind son obligatorios.
  • kind debe estar en el conjunto {peer_joined, peer_left, offer, answer, ice}.
  • from_name debe ser un TeamMember válido (isTeamMember()).
  • Anti-spoofing: from_name debe coincidir con el miembro resuelto vía CF Access cookie (resolveTeamMember(request)). Rechaza con 403 si no coincide.
  • payload serializado 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 mensajeTamaño típicoLímite configurado
SDP offer/answer~2 KB8 KB
ICE candidate~200 B8 KB
peer_joined/left< 100 B8 KB

Errores HTTP

CódigoCondición
400Faltan params room/peer/kind
400kind no permitido
400from_name no es TeamMember válido
401Cookie CF Access ausente o inválida
403from_name no coincide con miembro autenticado (anti-spoofing)
413Payload > 8.000 bytes
500Fallo 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)