Docs

Chat

Add in-app chat with @foony/chat, a chat layer that rides your existing realtime connection.

@foony/chat adds in-app chat to a product that already has, or is adding, a Foony Realtime connection. Rooms, messages with edit and delete, history, typing indicators, reactions, and presence all ride the same WebSocket as the rest of your realtime traffic. There is no second service to connect to and nothing new to bill separately, because everything a room does is messages on one channel.

Install

npm install @foony/chat @foony/realtime

The package is JavaScript and TypeScript for browsers and Node. React bindings are planned but not shipped yet.

Create a client and join a room

A ChatClient wraps the Realtime instance you already have:

import { Realtime } from '@foony/realtime';
import { ChatClient } from '@foony/chat';

const realtime = new Realtime({ authCallback: fetchToken });
const chat = new ChatClient(realtime);

const room = chat.rooms.get('general');
await room.attach();

rooms.get returns the same room instance for the same name, just like channels. Attaching starts delivery for everything the room does.

Send and receive messages

room.messages.subscribe((event) => {
  console.log(event.type, event.message.clientId, event.message.text);
});

await room.messages.send({ text: 'hello everyone' });

send resolves with the message, including its id, so your UI can render it immediately. Subscribers receive it as a created event. Editing, deleting, and loading the backlog are covered in Messages, and typing indicators, reactions, and presence in Rooms.

A room is a channel

A room named general lives on the channel chat:general. That one fact explains most of how chat behaves:

  • History persists by default. The default channel rule stores chat: messages at your plan’s retention, so room.messages.history() works with no setup.
  • Capabilities are channel capabilities. A token for a chat user grants operations on the room’s channel, for example { "chat:general": ["subscribe", "publish", "presence", "history"] }. See Authentication and capabilities.
  • Messages meter like messages, because they are messages.
  • Encryption works. Pass a cipher in the room options and every payload is end-to-end encrypted.
const room = chat.rooms.get('secret-plans', { cipher: { key } });

Leaving

room.detach() stops delivery but keeps your listeners for a later re-attach. chat.rooms.release('general') also forgets the instance, which is the right call when the user leaves the room for good.