Skip to content
Client SDK

Documentation

Client SDK

Connect, send, and subscribe to real-time chat events with @emb-chat/react-native-sdk.

@emb-chat/react-native-sdk is the universal client. Despite the name it runs unchanged in browsers, React Native, and Node 20+ — its only dependency is socket.io-client. It handles the WebSocket connection, auto-reconnect, sender hydration, the presence heartbeat, and typing timers for you.

npm install @emb-chat/react-native-sdk
import { ChatClient } from '@emb-chat/react-native-sdk';

Connect

Create a client with the URL and the token your backend signed, then connect(). It resolves once the socket has received the ready signal.

const chat = new ChatClient({
  url: 'https://chat.acme.com',
  token,
  // fetchImpl?: a custom fetch (defaults to global fetch)
  // timeoutMs?: REST request timeout
});

await chat.connect();
// …later…
chat.disconnect();

Reconnects

The socket reconnects automatically. Subscribe to know when it happens (e.g. to refetch missed history):

chat.on('reconnect', () => {
  // re-sync any state you care about
});

Channels

chat.channels is the channel collection; chat.channel(id) returns a cached per-channel handle.

const channels = await chat.channels.list();

const channel = await chat.channels.create({ type: 'group', name: 'Engineering' });
await chat.channels.update(channel.id, { name: 'Eng' });
await chat.channels.delete(channel.id);

await chat.channels.addMembers(channel.id, ['user_xyz789']);
await chat.channels.removeMember(channel.id, 'user_xyz789');
await chat.channels.setMemberRole(channel.id, 'user_xyz789', 'admin');
const members = await chat.channels.members(channel.id);

What a token client may do here depends on the app’s group mode. In external mode, channel management is API-key only; internal and hybrid open it up behind role gates. See the group-mode overview on the home page.

Real-time events

Subscribe per channel with channel.on(event, cb). Event names use the colon namespace.

const channel = chat.channel(channelId);

channel.on('message:new', (msg) => render(msg));
channel.on('message:edited', (msg) => update(msg));
channel.on('message:deleted', (msg) => remove(msg.id));

channel.on('typing:update', (t) => {
  // { channel_id, user_id, typing }
});
channel.on('presence:update', (p) => {
  // { user_id, status: 'online' | 'offline' }
});

channel.on('member:added', (m) => {});
channel.on('member:removed', (m) => {});
channel.on('member:role-updated', (m) => {});
channel.on('channel:updated', (c) => {});

Sending messages

sendMessage resolves on the server ack, so you know the message was accepted.

const msg = await channel.sendMessage({
  body: 'Hello, world',
  type: 'text',
  metadata: { client_id: 'optimistic-42' }, // optional
});

If the ack fails, the SDK throws ChatApiError with code: 'ws_error':

import { ChatApiError } from '@emb-chat/react-native-sdk';

try {
  await channel.sendMessage({ body: 'hi', type: 'text' });
} catch (err) {
  if (err instanceof ChatApiError && err.code === 'ws_error') {
    showToast('Send failed — check your connection');
  }
}

History & pagination

History is cursor-based: pass the oldest message’s id back as before.

let { messages, hasMore } = await channel.getMessages({ limit: 50 });

if (hasMore) {
  const older = await channel.getMessages({ limit: 50, before: messages[0].id });
  messages = [...older.messages, ...messages];
}

Mark a channel read up to a given message for unread counts — pass the message id:

channel.markRead(messages[messages.length - 1].id);

Typing indicators

startTyping() emits typing:start and arms a 3-second auto-stop timer; calling it again debounce-extends. stopTyping() clears it immediately — call it on send or blur.

composer.addEventListener('input', () => channel.startTyping());
composer.addEventListener('blur', () => channel.stopTyping());

async function send(body: string) {
  channel.stopTyping();
  await channel.sendMessage({ body, type: 'text' });
}

Presence

Presence is automatic — the client pings every 15 seconds while connected; the server treats a user as online for a 30-second TTL and broadcasts presence.offline shortly after a real disconnect. Read a user’s presence on demand:

const { status } = await chat.users.presence('user_abc123'); // 'online' | 'offline'
const user = await chat.users.get('user_abc123');