The inbox is the list of conversations a user is in, newest activity first, each with its last message and how many messages they haven't read. It's the left-hand panel of a messaging app, and the number on the app icon.
Where this fits: your backend has added the user as a
member of some conversations, and your app is
connected. The inbox is read with the user's own chat
token. Your backend keeps GET /v1/chat/conversations for admin listings
of every conversation.
Load one page
queryMyConversations() returns one page from the server. Use it when you
render the list yourself and refresh it on your own schedule:
const { conversations, nextCursor } = await chat.queryMyConversations({ limit: 30 });
for (const c of conversations) {
console.log(c.name, c.unreadCount, c.lastMessage?.content);
}
const next = nextCursor ? await chat.queryMyConversations({ cursor: nextCursor }) : null;const { conversations } = await livqeno.chat!.queryMyConversations({ limit: 30 });final page = await chat.queryMyConversations(limit: 30);
for (final c in page.conversations) {
print('${c.name}: ${c.unreadCount}');
}Each conversation has:
| Field | Meaning |
|---|---|
id, name, type, metadata | The conversation. type is DIRECT, CHANNEL or ROOM. |
role | This user's role in it (MEMBER, MODERATOR, ADMIN). |
muted, notifications | This user's notification settings. |
lastMessage | { id, seq, senderId, content, type, createdAt }, or null. |
unreadCount | Messages from others after this user's read position, not counting deleted ones. Stops at 999. |
unreadMentions | Of those, how many mention this user. |
lastReadSeq | The seq this user has read up to. |
updatedAt | Last activity. The list is sorted by this, newest first. |
Pages hold at most 100 conversations. Only conversations where the user is an active member are listed. Archived conversations are left out, and a token limited to certain conversations only lists those.
A live inbox: chat.inbox
chat.inbox loads the first page and then keeps it current from what the
connection receives: a new message moves its conversation to the top and
adds to its unread count, and reading clears it. No polling.
await chat.inbox.load({ limit: 30 });
const unsubscribe = chat.inbox.subscribe(() => {
const { conversations, totalUnread, loading, hasMore } = chat.inbox.getSnapshot();
render(conversations);
setBadge(totalUnread);
});
await chat.inbox.loadMore(); // next page
await chat.inbox.refresh(); // re-read the first page, e.g. when the tab regains focus
chat.inbox.stop(); // stop following live updatesimport { useInbox, useUnreadCount } from '@livqeno/react/chat';
function Sidebar() {
const { conversations, loading, hasMore, loadMore } = useInbox();
const unread = useUnreadCount(); // total, for a badge
// ...
}Or render the ready-made <ChannelList>. See UI components.
await chat.inbox.load(limit: 30);
// LivqenoInbox is a ChangeNotifier.
ListenableBuilder(
listenable: chat.inbox,
builder: (context, _) => Text('${chat.inbox.totalUnread} unread'),
);totalUnread adds up the unread counts of every loaded conversation,
skipping muted ones. getSnapshot() and subscribe() are shaped for
React's useSyncExternalStore.
Marking a message read with markAsRead() updates that conversation's
count from the server's answer. A read on another device of the same user
also clears it.
Watching: how live updates reach the inbox
The server sends every connection of a member an inbox.updated frame
when a message lands in one of their conversations or their read position
changes, with no room join needed (see the
WebSocket protocol). It isn't sent for
live-stream audience chats or for conversations with more than 1,000
members.
Until the SDKs read inbox.updated, load() joins the listed
conversations instead:
await chat.inbox.load({ watch: true, watchLimit: 15 }); // the defaults- The server allows 20 joined rooms per connection.
watchLimitdefaults to 15 to leave room for the conversation the user has open. - Joining a room shows the user as present in it.
- Conversations beyond the limit, or with
watch: false, update on the nextrefresh(). The inbox refreshes itself after every reconnect.
A message in a conversation that isn't on the loaded pages (a new DIRECT chat, say) triggers a refresh, so it appears at the top.
Unread counts stay correct
The first page comes from the server with counts already worked out. Live
messages are then added on top. A message the server already counted can
also arrive live a moment later; the inbox compares its seq with the
row's lastMessage.seq and counts it once. Your own messages never count
as unread. Sending a message doesn't move your read position on the
server: messages from others before yours stay unread until you mark them
read.
Audience viewers
A live-stream audience token (chat.isAudienceViewer === true) isn't a
member of anything, so it has no inbox. queryMyConversations() rejects
with NOT_A_MEMBER without a request.
Common mistakes
| Mistake | What you'll see | Fix |
|---|---|---|
| Watching more than 20 conversations | TOO_MANY_SUBSCRIPTIONS errors; some rows stop updating. | Keep watchLimit under 20, minus the rooms you join yourself. |
Counting message events yourself on top of chat.inbox | Unread counts double. | Use the inbox's counts; it already handles overlap. |
| Never refreshing in a long-lived tab | Rows outside watchLimit go stale. | Call chat.inbox.refresh() on focus. |