livqenoDocs

ChatEvents

Chat Events

Every event a ChatClient sends, what it carries, and when it fires.

Events are how your app learns that something happened in chat: a new message, someone typing, the connection dropping. You listen for the ones your UI cares about and update the screen when they fire. This page lists all 15 (from ChatEventMap in @livqeno/chat).

Where this fits: your app has connected with a chat token (see the Chat quickstart). Events only fire while connected. For what happened before that, load Message history.

These are client events, in your app. To tell your backend about chat activity (for push notifications, say), use Webhooks instead.

Which events does my UI need?

UI pieceListen for
Message listmessage, messageUpdated, messageDeleted
Reaction countsreactionAdded, reactionRemoved
"Bob is typing…"typing
Online dotspresence
"Seen by"read
"Reconnecting…" bannerconnectionStateChanged (or reconnecting / reconnected)
Error toastserror

Listen to an event

In your app:

const unsubscribe = chat.on('message', (message) => {
  render(message.senderId, message.text);
});
 
unsubscribe();      // idempotent — calling it twice is safe
  • chat.on() returns a function. Call it to stop listening, for example when the chat screen closes. Forgetting this leaks listeners, and you'll see each message rendered twice after the screen opens again.
  • chat.off() also works, if you prefer it.
  • This is different from room.on() in @livqeno/rtc, which returns the room.

Messages

EventPayloadFires when
messagemessage: ChatMessageA message arrives, including your own (with its final id)
messageUpdatedmessage: ChatMessage, info: { reason? }A message was edited, or (reason: 'link_preview') its link preview arrived
messageDeletedevent: MessageDeletedEventA message was deleted (soft delete)
messagePinned / messageUnpinnedevent: MessagePinEventA moderator pinned or unpinned a message
outboxChangedentries: OutboxEntry[]A send was queued, went out, failed, or was saved. See Offline & sync
outboxSent{ clientMessageId, room, message }A queued send was saved. Fires just before the entry leaves the outbox, so an optimistic row can be swapped for message without flicker

Why you get your own messages back:

  • sendMessage() finishing means the message is saved.
  • The message event means it was delivered.
  • Draw messages from the event. Then everyone, including the sender, sees the same order.

Reactions

EventPayloadFires when
reactionAddedevent: ReactionEventSomeone reacted
reactionRemovedevent: ReactionEventA reaction was removed

Adding the same reaction twice is harmless. See Reactions.

Typing, presence, and read receipts

These are short-lived signals about people, not about messages.

EventPayloadFires when
typingevent: TypingEventSomeone started or stopped typing
presenceevent: PresenceEventSomeone went online or offline
readevent: ReadReceiptEventSomeone read further
presenceCountevent: { roomId, online }Large rooms only: the online count, at most every 5 s. See Presence
  • None of these are saved, except the read position.
  • A user goes offline when their presence expires. A crashed browser shows offline within 45 seconds.
  • Don't rely on a read event for your own read marks on this device; use the value that markAsRead() returns. Marks made on the same user's other devices do arrive as read events, and chat.inbox uses them to clear unread counts.

Moderation

EventPayloadFires when
memberBanned / memberUnbannedevent: MemberModerationEventA moderator banned or unbanned someone
memberMuted / memberUnmutedevent: MemberModerationEventA moderator muted or unmuted someone
removedFromRoomevent: { roomId, reason }This user lost access to a room: reason: 'BANNED', or removed as a member. The SDK stops rejoining it

See Moderation.

Connection

Phones lose signal and laptops sleep. These events let you show the user what's going on, while the SDK reconnects for you.

EventPayloadFires when
connectionStateChangedstate: ChatConnectionStateThe connection state changed
connected—connect() finished
disconnected—The connection closed and the SDK won't retry: you called disconnect(), or retrying can't help (state failed)
reconnectingattempt: numberTrying to reconnect
reconnected—Reconnected
recoveredsummary: RecoverySummaryFinished catching up after a reconnect
resyncevent: { roomId, page }The client was away too long to catch up event by event, so history was re-read. Replace your copy with page

What happens when the network drops:

  1. The state changes to reconnecting, and reconnecting fires with attempt 1, 2, 3…. (disconnected does not fire, because the SDK is still trying.)
  2. When the network is back, reconnected fires.
  3. The SDK fetches what was missed. Those messages arrive as normal message events, followed by any missed edits, deletes, reactions and moderation events, through their normal events.
  4. recovered fires when catch-up is done.

About recovered: missed messages already came through message. Then check:

  • summary.gap: some history was skipped. Reload the view.
  • summary.errors: rooms that couldn't catch up. They retry on the next reconnect.
  • summary.events: missed edits, deletes, reactions and moderation events applied.
  • summary.resynced: rooms whose history was re-read (a resync fired for each).

reconnecting gives the attempt number, so you can show "Still trying (3)". RTC's version doesn't.

Errors

Listen for error in every app. A refused action (like a send without permission) is reported here, not as a missing message.

EventPayload
errorerror: LivqenoChatError
import { isLivqenoChatError } from '@livqeno/chat';
 
chat.on('error', (error) => {
  if (!isLivqenoChatError(error)) return;
  if (error.code === 'TOKEN_EXPIRED') refreshToken();
  if (error.code === 'RATE_LIMITED') backOff(error.retryAfterSeconds);
});

If a newer server sends an error code your client doesn't know, you still get a LivqenoChatError with that code. Older clients keep working.

All codes are in Errors.

Error subclasses

Every chat error is really a LivqenoChatError underneath, so checking instanceof LivqenoChatError (or calling isLivqenoChatError()) always catches all of them, no matter which kind it is. If you want to handle just one kind of error, use one of these more specific classes instead of checking error.code yourself:

ClassDefault codeWhen it happens
LivqenoChatConnectionErrorCONNECTION_FAILEDThe socket dropped, or never connected.
LivqenoChatAuthenticationErrorINVALID_TOKENThe chat token is missing, badly formed, expired, or revoked. Mint a new one.
LivqenoChatPermissionErrorPERMISSION_DENIEDYour token is valid, but you're not allowed to do this — wrong scope, or you're not a member.
LivqenoMessageErrorINVALID_MESSAGESomething was wrong with the message itself — too large, not found, or already deleted.
LivqenoRoomErrorROOM_NOT_FOUNDThe conversation doesn't exist, or you're not in it.
LivqenoAttachmentErrorATTACHMENT_NOT_FOUNDUploading, downloading, or looking up an attachment failed.

There's also a second set of subclasses just for moderation — LivqenoModerationError, LivqenoSlowModeError, LivqenoMessageBlockedError, and LivqenoRateLimitError. See Moderation.

These map onto the wire

Each event comes from a WebSocket frame. For example, typing combines the typing.started and typing.stopped frames. Building a client in another language? See WebSocket protocol.

Other platforms

React gives you hooks that listen for you. Flutter gives you one stream per event type.

const { messages, send, loadMore, hasMore } = useMessages();
const { typingUsers, onInput } = useTyping();
const presence = usePresence();
const state = useChatConnectionState();
const error = useChatError();

Common mistakes

MistakeWhat you'll seeFix
Not calling unsubscribe() on cleanupMessages render twice after reopening the chat.Call the function on() returned.
Only listening for messageA refused send just "disappears".Listen for error too.
Adding your own message locally and from the eventYour messages show twice.Only render from the message event.

Next steps