Skip to main content
Markdown

Events from the page

Pages and apps send events to the rooms they are in, straight through WordPlus Cloud, with no request to your server: chat messages, typing, read ticks, a cursor. A room rule says which events may be sent to it, and every one is sealed like those your server sends.

The chat window of Ben shows “Ann typing” while Ann, in her own browser, writes a reply

What a room allows​

realtime.room( 'chat:{a}:{b}', { history: 50, emit: [ 'message', 'typing', 'reaction', 'read' ], webhook: [ 'message', 'reaction' ] },
( user, { a, b } ) => [ a, b ].includes( String( user ) ) );
  • emit lists the names, or '*' for any. None by default: a room your server alone sends to.
  • webhook names those your server gets too, to keep them (below).
  • history keeps the last events of the room for 30 minutes: what a page that reconnects missed comes again from it.
  • The answer for one user can narrow it: { emit: [ 'typing' ] }, or { readOnly: true } for none.
  • The names kept for the room itself (joined, join, leave, count, error) are never allowed.

Sending​

A page sends to a room it has joined, and hears what the others send:

const chat = rt.join( 'chat:34:42' );

chat.emit( 'message', { text: 'Hi' } ); // sent, nothing waited for
const { id, at } = await chat.emitWithAck( 'message', { text: 'Hi' } ); // WordPlus Cloud took it: checked, kept, sent on
chat.volatile.emit( 'typing', { on: true } ); // dropped rather than queued while off line, never kept
await chat.timeout( 5000 ).emitWithAck( 'message', { text: 'Hi' } ); // an ack waited for 5 seconds at most

chat.on( 'message', ( data, meta ) => draw( data.text, meta.from ) ); // meta: { from, id, at, replayed? }
  • Everyone in the room gets it, the other tabs and devices of the sender too. The tab that sent it does not.
  • emitWithAck resolves with { id, at } once WordPlus Cloud has checked the event, kept it (when the room keeps history) and sent it on. It rejects with a code: not_allowed (the rule does not list it), forbidden (not in the room), rate_limited, too_large, bad_event.
  • An emit before the room is joined waits for it.
  • meta.from is the sender user id, as your server vouched for them: a page cannot send as someone else.
  • Binary: an ArrayBuffer, Uint8Array or Blob may be anywhere in the data. It is sealed as binary, and counted in the event size.
  • Limits: 1 MB an event (kept in history up to 64 KB), 50 a second a connection, in bursts of up to 100.

Keeping the conversation​

The conversation is for your app to keep, in its own database, as Better Messages keeps its own in the WordPress database. A room keeps its events for 30 minutes, for pages that reconnect. Your server gets the events a room rule names in webhook (webhooks) and saves them by event_id, the id the pages saw as meta.id:

realtime.on( 'message', ( { room, from, data, event_id, at } ) => db.messages.upsert( { id: event_id, room, from, text: data.text, at } ) );
  • Check there anything that matters: the events do not go through your server before they are sent on.
  • A route of yours gives a page the conversation back: the newest 50 messages, or with ?before=<id> the 50 before that one, oldest first, as { messages: [ { id, from, text, at, reactions } ], more }. at is in milliseconds, and reactions maps each reaction to who chose it. Only the two people in the chat may read it.
  • A reaction comes as { id, key }: the one reaction of the user to message id, an empty key taking it back.

Ready-made: the chat​

The Better Messages conversation on a room, with what a chat needs: your server conversation paged back as the reader scrolls up, the room filling in what came since, the day separators and unread line, the messages of a sender stacked, typing, reactions, the read ticks and the composer.

<wordplus-chat room="chat:34:42" messages="/chat/34/42/messages" placeholder="Write a message" reactions="1f44d 2764 1f602" max-length="2000"></wordplus-chat>
<wordplus-typing room="chat:34:42"></wordplus-typing> <!-- the typing line alone -->
  • messages is your route, asked with the page cookies.
  • chat.load replaces it, for a token or another shape: async ( before ) => ( { messages, more } ), where before is null for the newest page, or the oldest message shown ({ id, at }).
  • With neither, the chat shows what the room still has.

It sends what an app of your own would, so the two read each other:

EventData
message{ text }with an ack. Your server keeps it
typing{ on }volatile
read{ id }, the newest message seenvolatile, for the sender ticks
reaction{ id, key }, key empty to take it backwith an ack. Your server keeps it

await rt.callScreen() loads it with the call screen, rt.ui() without. In React, <Chat room="…" messages="…" /> or load={ … } (React). Names and pictures come from your directory.