Capítulo salas de la guía Neblla — salas, lobby, gathering y emparejamiento. El contrato base está en core.txt.

Salas y emparejamiento

El lobby

Todo usuario conectado aterriza en la sala lobby. Quién hay y quién soy:

memberJoinedRoom se dispara una vez por cada usuario que ya estaba al llegar y otra vez por cada entrada nueva; el flag alreadyPresent los distingue:


neblla.on('memberJoinedRoom', event => {
  if (event.alreadyPresent) {
    // ya estaba — añadirlo a la lista en silencio
  } else {
    // acaba de entrar — sonido, animación
  }
});

neblla.on('memberLeftRoom', event => {
  // event.user se ha ido — quitarlo de la lista
});

El lobby es buen momento para publicar el perfil visible del usuario:


neblla.setPublicInfo({ name: 'John', color: '#ff8800' });

Queda en user.publicInfo para todos los de la misma sala y se restaura en reconexiones. Cuando alguien lo actualiza, todos reciben el evento publicInfo:


neblla.on('publicInfo', event => {
  // event.user.publicInfo tiene los nuevos datos
});

Render de lista de jugadores:


const renderPlayers = () => {
  neblla.room.users.forEach(user => {
    console.log(user.publicInfo?.name || 'Guest',
      user.isPresenter ? '(host)' : '',
      user.isMe ? '(you)' : '');
  });
};
neblla.on('memberJoinedRoom', renderPlayers);
neblla.on('memberLeftRoom', renderPlayers);

Salas con nombre (estilo chat)

Mismo nombre de sala = mismo grupo. Un usuario solo puede estar en una sala a la vez.


neblla.joinRoom('eurovision');
neblla.sendMessage({ type: 'msg', text: '¡me encanta esta canción!' });

Los mensajes solo llegan a quien esté dentro. Cambiar de sala: joinRoom('otra'). Volver al lobby: leaveRoom().

Por defecto cualquier usuario crea salas con solo hacer joinRoom('nombre-nuevo'). Para limitar la app a una lista fija de salas válidas: developers.neblla.com → Apps → Mi app → Settings → Rooms.

Tipos de sala

La política se elige por app desde el dashboard:

Una misma app puede mezclar tipos: las salas nacen ephemeral y se promueven con persistRoom.

Partidas: bookRoom → gathering → startRoom

Una partida necesita reservar espacio, fijar reglas, esperar jugadores y empezar:


const roomId = await neblla.bookRoom({ config: { tiempoMax: 180, modo: 'blitz' } });

// Desde el lobby, listar las partidas abiertas:
neblla.gathering.forEach(room => {
  console.log(room.name, room.users.length, room.config);
});

Flujo correcto (no uses createRoom, es @legacy y se salta el estado gathering):


// El nombre de la sala lo genera el servidor — viene en el ack del bookRoom.
const roomName = await neblla.bookRoom({ config: { maxPlayers: 4 } });
await neblla.joinRoom(roomName);
// usuarios entran, esperan, se llena
neblla.startRoom();

Ni bookRoom ni createRoom aceptan un nombre de cliente — el id de sala siempre lo elige el servidor para evitar colisiones. Sí puedes pedir su longitud: bookRoom({ length: 3 }) da un código de 3 caracteres (3–12, por defecto 5; alfabeto sin 0/O/1/I/L).

Gathering configurado vs custom

server/gathering.js decide *cuándo* empieza la partida. Para los casos comunes no hace falta código: desde Settings → Rooms se configuran reglas declarativas — empezar con un mínimo de jugadores, cuando pasen X segundos desde la última entrada, cuando llegue un mensaje específico (botón "Empezar" del anfitrión), o combinaciones. Escribe gathering.js solo si la lógica de arranque es más compleja.

server/lobby.js igual: solo si hay decisiones reales en el lobby (matchmaking inteligente, cola de espera, agrupación por nivel). Patrón de matchmaking:


// lobby.js
const queue = [];
neblla.on('messageToPresenter', event => {
  if (event.data.type === 'ready') {
    queue.push(event.user.id);
    if (queue.length >= 4) {
      neblla.bookRoom().then(roomId => {
        const players = queue.splice(0, 4);
        neblla.sendMessage({ type: 'match-found', roomId, players });
      });
    }
  }
});

// client.js
neblla.on('message', event => {
  if (event.data.type === 'match-found' &&
      event.data.players.includes(neblla.me.id)) {
    neblla.joinRoom(event.data.roomId);
  }
});

API de salas persistentes

neblla.persistRoom({ name, type, whitelist? }) (CLIENT) — sube una sala recién reservada de ephemeral a public o private. Hay que llamarlo entre bookRoom y el primer joinRoom — una vez alguien entra, el tipo queda fijado. La whitelist (para private) bootstrappea miembros iniciales; el llamador se añade automáticamente como primer owner. Los ids de la whitelist son per-app userIds (los id de neblla.room.users[i]), no personIds — solo se resuelven contra usuarios online en esta app en ese momento. Los offline se añaden después vía inviteToRoom.

neblla.listRooms({ type? }) (CLIENT) — lista salas persistentes accesibles. Cada entrada: { name, type, creator, memberCount, activeUsers, lastActivityAt }.

neblla.getRoom(name?) (CLIENT) — inspecciona una sala por nombre (por defecto, la actual).

neblla.getMembers(name?) (CLIENT) — solo private. Devuelve { members[], invites[] }.

neblla.deleteRoom(name?) (CLIENT) — borra una sala persistente. Moderadores (cualquier sala) y owners de privadas (la suya). Echa a todos al lobby y emite roomDeleted.

neblla.setRoomType(name, type) (CLIENT) — promociona/baja entre public y private. Solo moderadores.

Membresía de salas privadas:

room.data

neblla.setRoomData(data) (CLIENT, presenter o server peer) — sustituye neblla.room.data (persistente en salas public/private). Todos los miembros reciben 'roomDataUpdated'. Sujeto al cap maxDataSize; un exceso emite 'roomDataError'.

Historial de chat (salas persistentes)

neblla.getRoomHistory({ name?, before?, limit? }) (CLIENT) — paginación del chat persistido. Newest-first. Default limit 50, max 200. Los mensajes borrados vienen con body: null y deleted: true.

neblla.deleteMessage(messageId, name?) (CLIENT) — soft-delete. Autor, owner o moderador. Emite messageDeleted a todos.

Eventos de salas

Ciclo de vida general:

Salas persistentes:

Chat persistido: