Skip to main content
Markdown

Waiting rooms and breakout rooms

Guests wait in a lobby until the host admits them, and during the meeting the host sends people to smaller rooms and brings them back. Both are your rules and a few routes of yours: the lobby is a presence room the host watches, each room is a group call your rule opens to whom you chose, and your server tells each page where to go.

The rules​

Three places, each named after the meeting: the lobby lobby:5, where guests wait, the meeting meeting:5, and its breakout rooms meeting:5:breakout:1, meeting:5:breakout:2 and so on.

Your server decides who may be where. A page that joins asks your server first, and a rule answers: a function your server registers once for every room or call whose name fits a pattern, such as lobby:{id}. WordPlus Cloud then holds the page to the answer (permissions). These rules say:

  1. The lobby lets in anyone signed in, and the host sees who waits.
  2. The meeting lets in its host and the guests the host admitted.
  3. A breakout room lets in the host, and those the host sent there.
// 1. The lobby, such as lobby:5: anyone signed in waits there, and the host sees who.
realtime.room( 'lobby:{id}', { presence: true }, ( user ) => user !== null );

// 2. The meeting, such as meeting:5: its host, and those the host admitted.
realtime.groupCalls( 'meeting:{id}', async ( user, { id } ) => {
if ( await isHostOf( id, user ) ) return true;
return isAdmitted( id, user );
} );

// 3. A breakout room, such as meeting:5:breakout:1: the host, and those sent there.
realtime.groupCalls( 'meeting:{id}:breakout:{n}', async ( user, { id, n } ) => {
if ( await isHostOf( id, user ) ) return true;
return String( await breakoutOf( id, user ) ) === n;
} );

meeting:{id} never matches meeting:5:breakout:1, since a part of a pattern stops at :, so each room keeps its own rule.

Admitting a guest​

The Admit button of the host calls a route of yours, which keeps the choice for the rule and tells the guest:

app.post( '/meetings/:id/admit', async ( req, res ) => {
if ( !( await isHostOf( req.params.id, req.user.id ) ) ) return res.sendStatus( 403 );
await admit( req.params.id, req.body.user );
await realtime.toUser( req.body.user ).emit( 'admitted', { meeting: req.params.id } );
res.sendStatus( 204 );
} );

The page of the guest waits in the lobby, and joins when admitted:

const lobby = rt.join( 'lobby:5' );
const calls = await rt.calls();

rt.on( 'admitted', async ( { meeting } ) => {
rt.leave( 'lobby:5' );
showMeeting( await calls.group( 'meeting:' + meeting, { type: 'video' } ) );
} );

The page of the host lists who waits, from the presence of the lobby:

const lobby = rt.join( 'lobby:5' );
lobby.on( 'joined', () => showWaiting( [ ...lobby.members ] ) );
lobby.on( 'join', ( { id, info } ) => addWaiting( id, info.name ) );
lobby.on( 'leave', ( { id } ) => removeWaiting( id ) );
  • Before being admitted, the guest is refused by the meeting with forbidden, whatever their page tries.
  • A guest who closes the page leaves the lobby, and the list of the host follows.

Breakout rooms​

The host sends people to a room, and later back, through a route of yours:

app.post( '/meetings/:id/breakouts', async ( req, res ) => {
if ( !( await isHostOf( req.params.id, req.user.id ) ) ) return res.sendStatus( 403 );
const { user, room } = req.body; // room: 1, 2 … or null for back
await setBreakout( req.params.id, user, room );
await realtime.toUser( user ).emit( 'breakout', { meeting: req.params.id, room } );
res.sendStatus( 204 );
} );

The page moves its user:

let current = await calls.group( 'meeting:5', { type: 'video' } );

rt.on( 'breakout', async ( { meeting, room } ) => {
current.leave();
current = await calls.group( room === null ? `meeting:${ meeting }` : `meeting:${ meeting }:breakout:${ room }`, { type: 'video' } );
showMeeting( current );
} );
  • A breakout room is a group call of its own: its people see and hear only each other.
  • Only those sent there get in. Once back, the room refuses them, since your rule no longer names it for them.
  • Everyone back at once: your server clears the rooms and sends breakout with room: null to each, in one realtime.batch().
  • The host visits any room, since the rule lets the host into each.

Variations​

  • A lobby for a consultation: the doctor admits the patient when ready (consultations).
  • Waiting in an app: admitted reaches the iOS and Android apps of the user too, with rt.on("admitted").
  • A lobby for a class: the teacher admits the late, then lets students speak (classes).