Skip to main content
Markdown

Rooms in Express

A room is a name pages join to get its events. Your server declares your rooms once, by pattern, with what they keep and allow, and answers for each user who asks to join.

Declaring rooms​

// The updates of an order: its customer and the staff, the last 20 kept.
realtime.room( 'order:{id}', { history: 20 }, async ( user, { id } ) => {
if ( user === null ) return false;
const order = await db.orders.find( id );
return order !== null && ( order.customerId === user || ( await isStaff( user ) ) );
} );

// A chat between two people, chat:34:42: those two only. They send messages and typing to it, and the messages are kept.
realtime.room( 'chat:{a}:{b}', { history: 50, emit: [ 'message', 'typing' ] }, ( user, { a, b } ) => [ a, b ].includes( String( user ) ) );

// The page of a product: anyone, signed in or not, with a live count of who is looking.
realtime.room( 'product:{id}', { public: true, count: true } );
  • user is what realtime.node( ( req ) => … ) gave for the request: your session user id, or null.
  • The parts of the pattern come as strings: { a: '34', b: '42' }.
  • The answer: false (or null) for no, true for yes, or { info } for yes with what the others see of them in a presence room. The rule may be async.
  • For one user, the answer may narrow the room: { emit: [ 'typing' ] } lets them send only that, { readOnly: true } nothing.

Names and patterns​

  • Room names: up to 200 characters of A-Z a-z 0-9 _ - = @ , . ; :.
  • {name} matches one or more characters other than : and ..
  • The most specific rule wins: the pattern with the most fixed characters, and on a tie the one declared first. A room no pattern matches is refused (no_rule).
  • When the rule runs: when a page joins, and when its token is renewed (every 6 hours by default). A page asks for every room it joins in the same moment in one request, and keeps the tokens across reconnects and moves.

What a room keeps and allows​

OptionDefault
historynoneEvents kept for pages that reconnect: a number (50), up to 1,000, for 30 minutes. Or { last: 50, days: 0.01 } for less (0.01 of a day is about 15 minutes)
emitnoneEvents pages may send to the room: names, or '*' for any (events from the page)
presenceoffIts members are listed, with their info, and join and leave tell the others (presence)
countoffA live count of who is in it, at any size, without a list
webhooknoneWhich of the page events your server gets too (sending from your server)
publicoffAnyone may join, signed in or not. Its grant names no user, so it may sit in a page a CDN caches
sealonIts events, its member info and its history are sealed with the room key (encryption)

The options travel in the room token, signed by your server, so WordPlus Cloud enforces them and nothing is set in two places.

Joining​

const order = rt.join( 'order:1042' );
order.on( 'updated', ( data, meta ) => showStatus( data.status ) ); // meta: { from, id, at }
order.on( 'joined', () => showLive() );
order.on( 'error', ( e ) => console.warn( e.code ) ); // forbidden, no_rule, full …

rt.leave( 'order:1042' );
  • join() gives the same room for the same name. The tabs of a user ride one connection (the worker), each with its own rooms.
  • Events reach a listener opened: you never handle sealed values.
  • meta.from is the user who sent it from a page, or nothing for your server. meta.id and meta.at are the kept event id and time.
  • Reserved names: joined, join, leave, count and error are kept for the room itself. Your server and pages cannot send them.

History, and nothing lost​

A room keeps its events for 30 minutes at most, for recovery. Your app keeps its own records, such as the messages of a chat, from the webhook.

A room that keeps history gives a page that comes back what it missed:

  • A reconnect within the history span gets the missed events first, in order, with meta.replayed, then the new ones.
  • Paging back:
const { events, more } = await order.history( { limit: 50 } );          // the newest 50, oldest first
const older = await order.history( { before: events[ 0 ].id, limit: 50 } );
  • Events over 64 KB are sent but not kept. volatile ones are never kept.
  • Your server reads and erases history too: realtime.in( 'order:1042' ).history() and .forget() (sending from your server).

More, with PHP and Python side by side: rooms in depth.