@cariosan/client
TypeScript client SDK for browsers, Node, and React Native.
The TypeScript SDK for browsers, Node 20+, and React Native. Zero runtime dependencies — uses native fetch and WebSocket.
Setup
Never persist the JWT — token is the JWT your backend issues with the Go SDK's IssueUserToken. The client SDK stores it in memory only. Persisting JWTs to localStorage exposes them to XSS.
Channels
Get a channel handle (cached — the same externalId always returns the same object):
Send
Reply (quote)
Quote a message by passing its id as parentId:
The reply carries parent_id and a quoted_message preview ({ id, user_id, user_name, content, type, deleted }) on the send response, in history, and on the live message event. The parent must be in the same channel or the call rejects with VALIDATION_ERROR. See Quoted replies.
Mention
Pass channel members' external_ids in mentions:
The message gains a resolved mentions array ([{ user_id, external_id, name }]). Each mentioned member is pinged on their own connection, surfaced as a mention event on both the Cariosan instance (for a global badge) and the relevant Channel:
Mentioning a non-member rejects the send with VALIDATION_ERROR. See Mentions.
Receive
Channel events you can listen for:
message— fires for every message in the channelmessage.updated— a message was edited ({ id, content, edited_at })message.deleted— a message was unsent ({ id, deleted_at }) — render a tombstonereaction.add/reaction.remove— an emoji reaction changed ({ message_id, user_id, emoji })mention— the current user was @-mentioned here ({ channel_id, message_id, by_user_id }); also emitted on theCariosaninstancetyping— typing indicators (start / stop)presence— member joins and leaves (channel-scoped; global presence comes fromCariosan#on("presence.update", ...))
Tip
Listeners are automatically unsubscribed when the channel is closed.
History
Search
Relevance-ranked full-text search. Scope to one channel, or search across every channel you belong to via the top-level client:
Supports websearch syntax ("quoted", OR, -exclude). Cross-channel search is member-scoped and excludes deleted messages. See Search.
Reactions, editing & unsending
Messages from getMessages carry their current state: reactions ([{ emoji, count, reacted_by_me }]), edited_at, and deleted_at. A non-author edit/delete returns 403 NOT_AUTHOR; past an optional server-configured window, 422 EDIT_WINDOW_EXPIRED / UNSEND_WINDOW_EXPIRED.
Typing and read markers
Read & delivery receipts
WhatsApp-style ticks. The SDK auto-acks delivery (sends channel.delivered on every inbound message) and markRead() advances the read cursor; both broadcast so senders see ticks flip live. Seed the member cursors once with getReceipts(), then read per-message state with receiptState():
receiptState(message) compares every other member's cursor against the message's created_at: all delivered → delivered, all read → read, else sent. Read receipts can be disabled per workspace (read_receipts_enabled), in which case channel.read is never broadcast — delivery still is. See Read & delivery receipts.
Push notifications
Register a device's FCM token to receive push for new messages (delivered when the workspace's server has Firebase configured — see Push):
Registration is idempotent on the token. Payloads are minimal (title only) — fetch content after the app opens using the channel_id/message_id in the notification's data.
Blocking
Hide another user's messages from your history + search (workspace-wide):
History and search are filtered server-side; use listBlocked() to also drop live WebSocket messages from blocked users client-side. See Blocking & muting.
Presence
Live updates arrive via the top-level Cariosan#on("presence.update", ...) handler.
Group admin management
Group channels expose channel.avatarUrl, a channel.lastMessage preview (on listChannels()), and admin-only management. The acting user must be a channel admin (created_by at create time makes someone admin) — non-admins get a 403. Removing yourself is allowed for anyone (leave group).
See Roles & group admins for the full hybrid (backend + client) model.
Attachments
The SDK handles the presign → PUT → public URL flow for you.
Lifecycle events
Error handling
Every thrown error is a CariosanError with a stable code, an optional status, and a retryable: boolean hint:
Bundle size — ESM min+gzip is around 3 KB. No external runtime dependencies; tree-shakes cleanly. Pair with the React peer (@cariosan/react, ~2.6 KB) for drop-in UI.
Was this page helpful?