Skip to main content
Markdown

Events from the app

The app sends events to the rooms it is in, straight through WordPlus Cloud, with no request to your server: chat messages, typing, read ticks, reactions. A room rule says which events may be sent to it (events in depth):

realtime.room( 'chat:{a}:{b}', { history: 50, emit: [ 'message', 'typing', 'reaction', 'read' ], webhook: [ 'message', 'reaction' ] },
( user, { a, b } ) => [ a, b ].includes( String( user ) ) );

Sending​

val chat = rt.join("chat:34:42")

chat.emit("message", buildJsonObject { put("text", "Hi") }) // sent, nothing waited for
val ack = chat.emitWithAck("message", buildJsonObject { put("text", "Hi") }) // WordPlus Cloud took it: ack.id, ack.at
chat.volatile.emit("typing", buildJsonObject { put("on", true) }) // dropped rather than queued while off line, never kept
chat.emitWithAck("message", buildJsonObject { put("text", "Hi") }, timeoutMs = 5_000)

@Serializable data class Message(val text: String)
chat.emitWithAck("message", Json.encodeToJsonElement(Message("Hi"))) // your own types, through kotlinx.serialization
  • Everyone in the room gets it, the other pages and devices of the sender too. This app does not.
  • emitWithAck suspends until WordPlus Cloud has checked the event, kept it (when the room keeps history) and sent it on, and returns the kept event id and time. It throws a RealtimeException with its code: not_allowed (the rule does not list it), forbidden (not in the room), rate_limited, too_large, bad_event, timeout, or offline while the connection is down.
  • Before the room is joined, an emit waits for it.
  • meta.from on the other side is this app user, as your server vouched for them: an app cannot send as someone else.
  • Binary: bytes.toJson() puts a ByteArray in the data, and element.bytes() reads one back.
  • Limits: 1 MB an event (kept in history up to 64 KB), 50 a second a connection, in bursts of up to 100.

Hearing them​

@Serializable data class ChatMessage(val text: String = "")
@Serializable data class Typing(val on: Boolean = false)

viewModelScope.launch { chat.eventsOf<ChatMessage>("message").collect { (message, meta) -> add(meta.from, message.text, meta.id) } }
viewModelScope.launch { chat.eventsOf<Typing>("typing").collect { (typing, _) -> showTyping(typing.on) } }

A message the app sent with emitWithAck does not come back to it: add it from the ack, with ack.id, so it is not shown twice when your server page or the room history has it too.

Keeping the conversation​

The conversation is for your app to keep, on your server, as Better Messages keeps its own in the WordPress database. A room keeps its events for 30 minutes, for apps that reconnect. Your server gets the events the rule names in webhook and saves them by event_id, the id the app saw as meta.id, and a route of yours gives them back a page at a time, with the user token (keeping the conversation):

// What was said before: the conversation your server keeps, then what the room has that it may not have yet.
chat.on("joined") { _, _ ->
viewModelScope.launch {
val page = withContext(Dispatchers.IO) {
val request = Request.Builder().url("$server/chat/34/42/messages").header("Authorization", "Bearer $token").build() // ?before=<id> for the page before
http.newCall(request).execute().use { Json.parseToJsonElement(it.body!!.string()).jsonObject } // { messages: [ { id, from, text, at, reactions } ], more }
}
for (m in page["messages"]!!.jsonArray) add(m.jsonObject["from"]!!.jsonPrimitive.content, m.jsonObject["text"]!!.jsonPrimitive.content, m.jsonObject["id"]!!.jsonPrimitive.content)
for (e in chat.history(limit = 50).events) if (e.event == "message") add(e.meta.from, e.data.jsonObject["text"]!!.jsonPrimitive.content, e.meta.id)
}
}
  • One id for each message, whichever way it came, so add keeps the first.
  • The events do not go through your server before they are sent on, so check there anything that matters.

Talking with the web chat​

The Better Messages chat on your web pages (<wordplus-chat>) sends these events, so an app that sends the same reads with it, both ways:

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