Messaging & Chat
Messaging & Chat
Messaging & Chat
Text messaging in the Browser SDK lives on the Address entity, not
on the Call. Every Address — whether a User (Subscriber), room, or
external contact — has a conversation associated with it: an
append-only log of chat messages and call history. You can send a
text to an Address with or without an active call.
The same conversation is shared across all clients of the same User. Sending a message from your phone client and your laptop client puts both into the same thread.
findAddressIdByURI checks the local cache and falls back to the server, so it works even before directory.loadMore() has populated directory.addresses. See Address Book & Directory for the directory-lookup patterns.
sendText resolves once the message is accepted by the server.
There’s no separate “delivered” / “read” signal in v4 — if you need
those, store delivery state in your own backend.
You can’t message your own address. The platform rejects a
join request whose destination is the same fabric address as the
caller with a 422. If your UI lists the directory, filter
client.user.addresses[0].id out before rendering.
Testing two sides of a conversation requires two different
Subscriber Access Tokens — one per user. Reusing the same SAT
in two tabs will work for the most recently connected tab, while the
other tab logs Discarding stale event: conversation.message as the
platform rotates the session’s event channel.
address.textMessages$ lazy-loads the conversation on first
subscribe and emits a TextMessageCollection. The collection itself
is reactive — its values$ re-emits as new messages arrive or older
ones are paginated in.
Each entry is a TextMessage with id, text, created and a
fromAddress$ observable — the sender is itself a resolved
[Address], so you can render an avatar / name from the same SDK
data without an extra fetch.
textMessages$ initially loads the most recent page. To pull older
messages, watch hasMore$ and call loadMore():
The “scroll near the top → loadMore” pattern is what the kitchen-sink demo uses; the same shape works for any direction.
When you have an active call, the call’s address is reachable as
call.address. Use that to send chat messages within the call’s
conversation:
Even after the call ends, the conversation persists — you can scroll back through messages from previous calls and send asynchronous messages between calls.
The same conversation log also carries call history — same shape,
same pagination, filtered to call entries instead of chat. Each
entry (AddressHistory) has kind, status, started, ended.
textMessages$ and history$ are two filtered views of the same
underlying conversation, so loading one populates both.
Both observables shareReplay(1) — late subscribers get the existing
collection without re-fetching.
In a room call, call.address is the room’s address — sending a
chat message there delivers it to everyone in the room’s
conversation. Each room thus has one chat thread, persisted across
sessions:
For private side-channels within a room (a DM between two
participants), use each participant’s Address directly — look it
up from directory.get$(addressId) using the Participant.addressId
field.
Conversations are reactive whether or not a call is active. To run a
chat-only experience (e.g. a support inbox), subscribe to multiple
addresses’ textMessages$ streams and update your UI as messages
land:
Subscribing to addresses$ (rather than reading the addresses snapshot) means new entries that arrive on later pages or via background updates get watched automatically. Track which address.ids you’ve already wired up if you want to avoid double-subscribing on re-emit.
The platform pushes new messages over the same WebSocket the SDK uses for signaling — no polling required.
The page below stitches together everything above into one runnable
file. Paste a Subscriber Access Token,
hit Connect, pick a conversation from the dropdown, and you can
read history, paginate older pages, send a text, and toggle the call
log for the same address — all from one Address entity.
The demo touches: client.directory$ (to list addresses),
address.textMessages$ and the collection’s values$ / hasMore$ /
loadMore() (to render and paginate the thread), address.sendText()
(to post), and address.history$ (to surface the call log for the
same conversation).
Address.sendText() — send a chat messageAddress.textMessages$ / Address.textMessage — chat thread collectionAddress.history$ / Address.history — call history for the same conversationTextMessage — the message shapeAddressHistory — the call-log entry shapeCall.address — the active call’s address, for in-call chat