Capítulo core de la guía Neblla — el contrato base del SDK. Imprescindible: el resto de capítulos asumen lo que hay aquí.
Neblla — core
Qué es
Neblla es un SDK multijugador que se carga con una etiqueta <script>. Da salas, mensajería en tiempo real, identidad, persistencia, amigos, pagos e IA — sin servidor propio, sin npm, sin build. El mismo código vale desde el prototipo hasta producción: la lógica de servidor corre en un peer, en WASM o en infraestructura de Neblla sin reescritura (ver servidor.txt). El SDK también funciona desde Node.js sin HTML.
Direcciones
- developers.neblla.com — crear, editar y gestionar apps (dashboard y backoffice).
- play.neblla.com — galería pública. Cada app vive en
play.neblla.com/<slug>(slug autogenerado del nombre al crear la app, renombrable en developers.neblla.com). Obtén la URL canónica conneblla_get_app_url(appId)— nunca la construyas a mano. - neblla.com/mcp — servidor MCP: una IA puede crear apps, subir ficheros y publicarlos (ver mcp.txt).
El SDK se sirve desde neblla.com/connect/app/:appId. Esa URL no cambia nunca, se aloje donde se aloje la app.
Conexión
Antes de cualquier código hay que crear la app (en developers.neblla.com o vía MCP); eso devuelve un APP_ID. App mínima:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Mi App</title>
<script src="https://neblla.com/connect/app/APP_ID"></script>
</head>
<body>
<script>
window.onNeblla = (neblla) => {
console.log("hola");
};
</script>
</body>
</html>
Cuando la página carga, onNeblla se ejecuta con el SDK ya conectado. El script tag NO necesita type="module".
Estructura canónica — 6 ficheros
Si la app vive alojada en Neblla, el link público apunta a index.html. La plantilla canónica son seis archivos; solo index.html es obligatorio, el resto existen si la app los necesita.
Front-end (corre en el navegador):
- index.html — markup. Carga
main.css,client.jsy el SDK de Neblla. - main.css — estilos.
- client.js — corre en cada dispositivo conectado. Tiene acceso al DOM. El código va dentro de
window.onNeblla = (neblla) => { ... }.
Server-side (lógica aislada, sin DOM, carpeta server/):
- server/lobby.js — usuarios que aún no están en partida. Matchmaking: recoger "estoy listo", agrupar, reservar sala, anunciar emparejamiento.
- server/gathering.js — sala creada esperando a empezar. Decide *cuándo* arranca la partida (los casos comunes se configuran sin código en
Settings → Rooms). - server/room.js — la sala en marcha. Autoridad del juego: valida acciones, mantiene estado, anuncia cambios.
Contrato de la estructura:
index.htmlreferenciamain.cssyclient.jscon<link>y<script>. NUNCA referencia ningún archivo deserver/— el SDK los carga solo.- Los archivos de
server/son top-level:nebllaestá como global, sin callbackonNeblla. - Los archivos de
server/no tocan el DOM.windowydocumentestán sombreados. - No existe
neblla.meen server-side. Los métodos de usuario (save,login,setPublicInfo,buy, ...) son solo cliente. - Los archivos están en producción en cuanto se suben. Publicar en la galería es un paso aparte (ver publicar.txt).
- Si estos archivos están presentes, Neblla los activa y desactiva en el momento adecuado para cada sala.
Modelo cliente / presentador / servidor
- Cliente — cada dispositivo conectado ejecutando
client.js. Tiene DOM yneblla.me. - Presentador — un cliente con rol social distinguido (el cantador del bingo, la pantalla de un Jackbox). Es el primer dispositivo que entra a la sala; si se va, el SDK promueve a otro (NO por FIFO — lo elige Neblla) y dispara
newPresenter. Se consulta conneblla.me.isPresenter, se le habla contellPresenter. Es opcional: muchos juegos no lo necesitan. NO ejecuta archivos deserver/. - Servidor — quien ejecuta los archivos de
server/. En modo peer es un dispositivo elegido por el SDK; en infraestructura, un proceso dedicado de Neblla. Es la autoridad del juego. Se le habla contellServer. No hayneblla.serverniuser.isServer— su identidad es opaca a propósito, para que el código siga funcionando al pasar de peer a infra.
Presentador y servidor son roles distintos. En modo peer pueden coincidir en el mismo dispositivo por casualidad; en infraestructura no, porque el servidor no es un dispositivo.
Contrato events-only
CLIENT y SERVER nunca se llaman directamente. Solo se comunican por eventos:
| Emisor (método) | Receptor (evento) | Semántica | |--------------------------------------|--------------------------|-----------| | sendMessage(data) desde cliente | 'message' en todos | Broadcast entre pares. Te llega a ti mismo con fromMe: true. | | sendMessage(data) desde server | 'messageFromServer' | Voz autoritaria del juego, llega a todos los clientes. | | tellServer(data) (cliente) | 'messageToServer' | Privado al server. Llega a room.js/gathering.js/lobby.js según fase. No se difunde. | | tellPresenter(data) (cliente) | 'messageToPresenter' | Privado al cliente con isPresenter === true. No se difunde. | | setState(state) (server) | 'state' en todos | Estado autoritario. Actualiza neblla.state en todos, incluidos peers de respaldo. |
Los mensajes son JSON abierto y efímeros: usa el server para reenviar estado a los nuevos. Registro de listeners: neblla.on(event, cb) / neblla.off(event, cb) ('*' escucha todos).
Ciclo de vida de una sala
1. lobby — todo usuario conectado aterriza en la sala especial lobby. Para matchmaking, elegir nombre, ver salas — no para jugar. Los eventos llevan un booleano isLobby. 2. gathering — bookRoom({ config? }) reserva una sala con id único (lo elige el servidor, nunca el cliente). Nace en estado gathering, visible desde el lobby vía neblla.gathering. Los jugadores entran con joinRoom(roomId). 3. running — alguien (normalmente el presentador) llama a startRoom(). El SDK elige al server y arranca server/room.js. A partir de ahí funciona tellServer / messageFromServer / setState.
leaveRoom() vuelve al lobby. Un usuario solo puede estar en una sala a la vez. Mismo nombre de sala = mismo grupo. Detalle completo en salas.txt.
Cinco patrones de app
Decide el patrón ANTES de codificar — los esqueletos son distintos. Cuatro preguntas: ¿es multi-usuario? ¿hay autoridad (un server que valida)? ¿esa autoridad muestra UI distinta? y si es multi sin autoridad, ¿salas separadas o un único espacio común?
| Patrón | Autoridad | UI doble | Multi | Salas | Ejemplos | |------------------|----------------|----------|-------|-------|----------| | Turn-based | Sí (server) | No | Sí | Sí | Ajedrez, UNO, conecta 4, hundir la flota | | Host-controllers | Sí (server) | Sí | Sí | Sí | Kahoot, Jeopardy, Pictionary, Hombres lobo | | Collab realtime | No | No | Sí | Sí | Pizarra compartida, canvas, watch party | | Lobby-only | No | No | Sí | No | Mural global, pixel-canvas, cursores en vivo | | Solo + save | — | — | No | No | Hábitos, sudoku, diario |
- Turn-based y Host-controllers: la autoridad en
room.js. En host-controllers,client.jsramifica conif (neblla.me.isPresenter) renderPresenter(); else renderController();. - Collab realtime:
room.jsvacío. Salas separadas, cada una una superficie compartida. Todos los peers iguales:sendMessagecon cambios, aplicar los entrantes. Last-write-wins salvo CRDT propio. - Lobby-only: como collab pero SIN salas — todos comparten el único lobby global. Sin
joinRoom/bookRoom/startRoom, singatheringniroom.js: solo presencia (memberJoinedRoom/memberLeftRoom) +sendMessagep2p. El único fichero de servidor esserver/lobby.js, opcional, para guardar una foto canónica a los que entran tarde. Elígelo (en vez de collab) cuando todo el mundo va a UN mismo espacio y no hace falta separar grupos. - Solo + save: sin salas ni mensajes. Solo
neblla.save()yneblla.me.data.
Se pueden mezclar o inventar patrones propios.
Patrón mínimo de app funcional
Lógica autoritaria — el cliente pide, room.js valida y emite:
// client.js — pedir acción:
neblla.tellServer({ action: 'play-card', card: 5 });
// room.js — validar y emitir estado:
let gameState = neblla.state || initialState();
neblla.on('messageToServer', event => {
gameState = applyAction(gameState, event.user.id, event.data);
neblla.setState(gameState);
});
// client.js — reaccionar:
neblla.on('state', state => render(state));
Multiplexar mensajes con un campo type:
neblla.sendMessage({ type: 'state-update', state });
neblla.on('message', event => {
switch (event.data.type) {
case 'chat': handleChat(event); break;
case 'state-update': handleState(event); break;
}
});
Reglas clave
sendMessage()desdeclient.jsllega a TODOS incluyéndote — usaevent.fromMepara distinguir. Desde server no hay "yo".setPublicInfo()es para datos visibles a otros;save()para datos privados persistentes.neblla.me.datanunca llega a otros clientes, ni al peer que ejecutaroom.js.bookRoom()reserva un nombre pero NO entra. Sigue conjoinRoom().- En modo peer, los archivos de
server/pueden re-instanciarse si cambia el dispositivo-servidor. UsasetState()desderoom.js— el sucesor arranca conneblla.stateya cargado (ver servidor.txt). - WebRTC P2P se negocia automáticamente; las rutas son transparentes.
- IA (
neblla.ai) requiere login excepto en modo browser-directo. Las comunicaciones con el dev requieren login. createRoom()es @legacy: para código nuevo usabookRoom → joinRoom → startRoom.