boardgames

Servidor online de Mesa de Juegos (Cloudflare Workers)

Un Worker + Durable Objects que (1) sirve el juego (public/index.html, generado) y (2) reenvía mensajes WebSocket entre jugadores. El anfitrión sigue siendo quien decide la partida; el servidor solo retransmite. No guarda datos.

Desplegar

Requisitos: Node 18+ y tu cuenta de Cloudflare.

npm install
npx wrangler login     # abre el navegador, autoriza
npm run deploy         # wrangler deploy (el hook `build` minifica antes)

Al terminar muestra https://boardgames.<tu-subdominio>.workers.dev. Ábrelo: ya es la web completa con salas online (comprueba /api/ping → mesa-ok).

Enlaces directos a cada juego

https://<tu-dominio>/game/<id> abre ese juego directamente (por ejemplo /game/sudoku, /game/texas, /game/ajedrez), ideal para marcadores. Los assets sirven el mismo index.html (que ya lleva window.MJ_PATHS=1 desde build.js) para / y /game/* sin pasar por el worker (not_found_handling: "single-page-application" en wrangler.jsonc; solo /ws/* y /api/* van al worker); el cliente abre el juego y mantiene la URL y el título de la pestaña. Un id desconocido lleva al inicio. Sin servidor (archivo abierto, visor de Claude) el mismo enlace se escribe como #/game/<id>. Ids: el id de cada línea de src/app/core/13-catalog.js (el mismo nombre que su fichero en src/app/mods/).

Actualizar el juego

El código del cliente vive en src/app/: shell.html (esqueleto), style.css, core/*.js (núcleo, por orden de nombre), mods/<id>.js (un fichero por juego o módulo compartido), i18n/<id>.js (diccionarios de inglés) y mods.json (lista ordenada de módulos con sus dependencias). Para actualizar:

  1. Edita los ficheros de src/app/.
  2. git push → Cloudflare (Workers Builds) ejecuta wrangler deploy, cuyo hook build (node build.js) genera public/index.html, public/m/*.js, sw.js y offline-es.html y offline-en.html. En local: npm run deploy.

Solo generar: npm run build. Elimina todos los comentarios (las fuentes los conservan). npm run page escribe dev.html (la página completa sin minificar, con los dos idiomas) para abrirla como archivo o depurar. El núcleo pesa ≈120 KB (63 KB gzip); cada juego se descarga al abrirlo (/m/<id>.<hash>.js, caché inmutable) y un service worker permite jugar sin conexión tras pulsar «Guardar juegos en este dispositivo». «Descarga offline» baja offline-es.html u offline-en.html (todo en un archivo, del idioma actual). public/index.html es un fichero generado: está en .gitignore, no se edita ni se versiona.

PWA: instalar y jugar sin conexión

En Chrome/Edge/Android: menú → «Instalar aplicación» (o el icono de instalar de la barra). En iPhone/iPad (Safari): Compartir → «Añadir a pantalla de inicio». Una vez instalada, el service worker guarda en segundo plano el núcleo y todos los juegos: la app abre al instante y funciona sin conexión. En el navegador (sin instalar) cada juego se guarda al abrirlo, y «Guardar juegos en este dispositivo» los guarda todos. Cuando hay una versión nueva, la app avisa y el menú ofrece «Actualizar ahora». Sin internet se juega solo/contra IA y en sala sin conexión: los móviles deben estar en la misma Wi-Fi (o punto de acceso); el anfitrión enseña un QR, el invitado lo escanea con la cámara dentro de la app (o lo lee con otra app de QR y pega el texto), y viceversa con la respuesta. Las salas online sí necesitan internet. La cámara requiere https (la web desplegada o localhost). «Descarga offline» sigue entregando un único mesa-de-juegos.html sin servidor ni instalación, para enviarlo por mensajería.

Usarlo desde otra web (GitHub Pages, opcional) Pages (o el HTML offline)

Abre una vez https://TU-PAGINA/?srv=https://boardgames.<subdominio>.workers.dev; la dirección queda guardada en ese navegador. O fíjala al construir: MJ_SERVER=https://… node build.js. Si quieres limitar quién puede conectar, edita ALLOWED_ORIGINS en wrangler.jsonc (p. ej. "https://USUARIO.github.io,null"; null es el HTML abierto desde archivo) y vuelve a desplegar.

Notas

Avisos push (opcional)

Notificaciones para quien bloquea el móvil en una sala online («Empieza la partida», «Te toca», «X quiere entrar en tu sala»). Sin la clave el resto funciona igual y la opción no aparece activa.

  1. Generar la clave VAPID (una vez; guárdala, si cambia hay que volver a activar los avisos en cada dispositivo):
    node -e "const k=require('crypto').generateKeyPairSync('ec',{namedCurve:'prime256v1'});console.log(JSON.stringify(k.privateKey.export({format:'jwk'})))"
    
  2. Producción: npx wrangler secret put VAPID_JWK y pegar el JSON.
  3. Local (wrangler dev): fichero .dev.vars (no se versiona) con VAPID_JWK='{"kty":"EC",…}'.
  4. Opcional: VAPID_SUBJECT en wrangler.jsonc (mailto:… o https://…); vacío = el origen de la página.

Las suscripciones se guardan en el Durable Object de cada sala y se borran 12 h después de que quede vacía. En iPhone los avisos solo funcionan con la app instalada (iOS 16.4+).