Appearance
Chat
FreshMessaging & Chat
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Text messaging in the Browser SDK lives on the Address entity, not on the Call. Every Address, whether a User (Subscriber), room, or external contact, has a conversation associated with it: an append-only log of chat messages and call history. You can send a text to an Address with or without an active call.
The same conversation is shared across all clients of the same User. Sending a message from your phone client and your laptop client puts both into the same thread.
Sending a message
import { firstValueFrom } from "rxjs";
const directory = await firstValueFrom(client.directory$);
const id = await directory.findAddressIdByURI("/private/alice");
const address = directory.get(id);
await address.sendText("Heading over to the call now");findAddressIdByURI checks the local cache and falls back to the server, so it works even before directory.loadMore() has populated directory.addresses. See Address Book & Directory for the directory-lookup patterns.
sendText resolves once the message is accepted by the server. There’s no separate “delivered” / “read” signal in v4, if you need those, store delivery state in your own backend.
You can’t message your own address. The platform rejects a join request whose destination is the same fabric address as the caller with a 422. If your UI lists the directory, filter client.user.addresses[0].id out before rendering.
Testing two sides of a conversation requires two differentSubscriber Access Tokens, one per user. Reusing the same SAT in two tabs will work for the most recently connected tab, while the other tab logs Discarding stale event: conversation.message as the platform rotates the session’s event channel.
Reading the conversation
address.textMessages$ lazy-loads the conversation on first subscribe and emits a TextMessageCollection. The collection itself is reactive, its values$ re-emits as new messages arrive or older ones are paginated in.
address.textMessages$.subscribe((collection) => {
if (!collection) return;
collection.values$.subscribe((messages) => {
chatList.innerHTML = "";
for (const m of messages) {
const li = document.createElement("li");
li.textContent = `${m.text}, ${new Date(m.created).toLocaleTimeString()}`;
chatList.appendChild(li);
}
});
});Each entry is a TextMessage with id, text, created and a fromAddress$ observable, the sender is itself a resolved [Address], so you can render an avatar / name from the same SDK data without an extra fetch.
Paging older messages
textMessages$ initially loads the most recent page. To pull older messages, watch hasMore$ and call loadMore():
collection.hasMore$.subscribe((hasMore) => {
if (!hasMore) return;
chatList.onscroll = () => {
if (chatList.scrollTop < 50) collection.loadMore();
};
});The “scroll near the top → loadMore” pattern is what the kitchen-sink demo uses; the same shape works for any direction.
In-call chat
When you have an active call, the call’s address is reachable as call.address. Use that to send chat messages within the call’s conversation:
const sendButton = document.querySelector("#send-chat");
const input = document.querySelector("#chat-input");
sendButton.onclick = async () => {
const text = input.value.trim();
if (!text || !call.address) return;
await call.address.sendText(text);
input.value = "";
};
call.address?.textMessages$.subscribe((collection) => {
collection?.values$.subscribe(renderMessages);
});Even after the call ends, the conversation persists, you can scroll back through messages from previous calls and send asynchronous messages between calls.
Call history
The same conversation log also carries call history, same shape, same pagination, filtered to call entries instead of chat. Each entry ( AddressHistory) has kind, status, started, ended.
address.history$.subscribe((collection) => {
collection?.values$.subscribe((entries) => renderCallLog(entries));
});textMessages$ and history$ are two filtered views of the same underlying conversation, so loading one populates both.
Both observables shareReplay(1), late subscribers get the existing collection without re-fetching.
Group chat in a room
In a room call, call.address is the room’s address, sending a chat message there delivers it to everyone in the room’s conversation. Each room thus has one chat thread, persisted across sessions:
const call = await client.dial("/public/team-standup", { audio: true });
call.address?.textMessages$.subscribe((collection) => {
collection?.values$.subscribe((messages) => renderChat(messages));
});
sendButton.onclick = () => call.address?.sendText(input.value);For private side-channels within a room (a DM between two participants), use each participant’s Address directly, look it up from directory.get$(addressId) using the Participant.addressId field.
Realtime delivery without a call
Conversations are reactive whether or not a call is active. To run a chat-only experience (e.g. a support inbox), subscribe to multiple addresses’ textMessages$ streams and update your UI as messages land:
directory.addresses$.subscribe((addresses) => {
for (const address of addresses) {
address.textMessages$.subscribe((collection) => {
collection?.values$.subscribe((messages) => {
const unread = messages.filter((m) => !isRead(m.id));
updateUnreadBadge(address.id, unread.length);
});
});
}
});
directory.loadMore();Subscribing to addresses$ (rather than reading the addresses snapshot) means new entries that arrive on later pages or via background updates get watched automatically. Track which address.ids you’ve already wired up if you want to avoid double-subscribing on re-emit.
The platform pushes new messages over the same WebSocket the SDK uses for signaling, no polling required.
Try it: a tiny chat client
The page below stitches together everything above into one runnable file. Paste a Subscriber Access Token, hit Connect, pick a conversation from the dropdown, and you can read history, paginate older pages, send a text, and toggle the call log for the same address, all from one Address entity.
The demo touches: client.directory$ (to list addresses), address.textMessages$ and the collection’s values$ / hasMore$ / loadMore() (to render and paginate the thread), address.sendText() (to post), and address.history$ (to surface the call log for the same conversation).
messaging-chat-demo.html, full source
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SignalWire SDK messaging & chat demo</title>
<style>
body { font: 14px/1.5 system-ui, sans-serif; max-width: 720px; margin: 2rem auto; padding: 0 1rem; }
label { display: block; margin: 0.75rem 0 0.25rem; font-weight: 600; }
input, select { width: 100%; padding: 0.5rem; font: 13px ui-monospace, monospace; box-sizing: border-box; }
button { margin: 0.5rem 0.5rem 0 0; padding: 0.5rem 1rem; font: 14px system-ui; cursor: pointer; }
button[disabled] { opacity: 0.5; cursor: not-allowed; }
#thread { margin-top: 1rem; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; height: 18rem; overflow-y: auto; background: #fafafa; }
#thread li { list-style: none; padding: 0.25rem 0; border-bottom: 1px solid #eee; }
#thread li:last-child { border-bottom: none; }
.meta { color: #666; font-size: 12px; }
#history { margin-top: 0.5rem; padding: 0.5rem; border: 1px solid #ddd; border-radius: 4px; max-height: 10rem; overflow-y: auto; background: #fafafa; display: none; font-size: 13px; }
#log { margin-top: 1rem; padding: 1rem; background: #111; color: #0f0; font: 13px ui-monospace, monospace; min-height: 4rem; white-space: pre-wrap; border-radius: 4px; }
.row { display: flex; gap: 0.5rem; align-items: stretch; }
.row input { flex: 1; }
</style>
</head>
<body>
<h1>SignalWire SDK messaging & chat demo</h1>
<label for="token">Subscriber Access Token</label>
<input id="token" type="password" placeholder="Paste your SAT here" />
<button id="connect">Connect</button>
<label for="address">Conversation</label>
<select id="address" disabled>
<option>, connect first, </option>
</select>
<button id="loadOlder" disabled>Load older</button>
<button id="toggleHistory" disabled>Show call history</button>
<ul id="thread"></ul>
<div id="history"></div>
<div class="row" style="margin-top: 0.75rem;">
<input id="text" placeholder="Type a message…" disabled />
<button id="send" disabled>Send</button>
</div>
<pre id="log"></pre>
<script type="module">
import { SignalWire, StaticCredentialProvider } from "https://esm.sh/@signalwire/js@dev?bundle-deps";
import { firstValueFrom, filter } from "https://esm.sh/rxjs@7.8.2?bundle-deps";
const $ = (id) => document.getElementById(id);
const log = (msg) => ($("log").textContent += msg + "\n");
let client, currentAddress, threadSub, valuesSub, hasMoreSub, historySub, currentCollection;
$("connect").addEventListener("click", async () => {
const token = $("token").value.trim();
if (!token) return log("Paste a token first.");
$("connect").disabled = true;
log("Connecting…");
client = new SignalWire(new StaticCredentialProvider({ token }));
try {
await firstValueFrom(client.ready$.pipe(filter((r) => r)));
} catch (err) {
log("Failed: " + (err.message || err));
$("connect").disabled = false;
return;
}
// `directory.addresses` starts empty, subscribing to `addresses$`
// and calling `loadMore()` triggers the initial fetch and keeps
// the UI in sync as pages stream in.
const directory = await firstValueFrom(client.directory$);
// The directory includes the current user's own address, // filter it out, since you can't open a conversation with yourself
// (the server rejects join with a 422).
const selfAddressId = client.user?.addresses?.[0]?.id;
const select = $("address");
let addresses = [];
let pickedFirst = false;
directory.addresses$.subscribe((next) => {
addresses = next.filter((a) => a.id !== selfAddressId);
select.innerHTML = "";
if (!addresses.length) {
const opt = document.createElement("option");
opt.textContent = ", no addresses in directory, ";
select.appendChild(opt);
return;
}
for (const a of addresses) {
const opt = document.createElement("option");
opt.value = a.id;
opt.textContent = a.name + " (" + a.type + ")";
select.appendChild(opt);
}
select.disabled = false;
if (!pickedFirst) {
pickedFirst = true;
log("Connected. Pick a conversation above.");
selectAddress(addresses[0]);
}
});
select.addEventListener("change", () => {
const a = addresses.find((x) => x.id === select.value);
if (a) selectAddress(a);
});
directory.loadMore();
});
function selectAddress(address) {
currentAddress = address;
$("thread").innerHTML = "";
$("history").innerHTML = "";
$("history").style.display = "none";
$("toggleHistory").textContent = "Show call history";
$("text").disabled = false;
$("send").disabled = false;
$("toggleHistory").disabled = false;
threadSub?.unsubscribe();
valuesSub?.unsubscribe();
hasMoreSub?.unsubscribe();
historySub?.unsubscribe();
log("Loading conversation with " + address.name + "…");
threadSub = address.textMessages$.subscribe((collection) => {
if (!collection) return;
currentCollection = collection;
valuesSub?.unsubscribe();
valuesSub = collection.values$.subscribe((messages) => {
$("thread").innerHTML = "";
for (const m of messages) {
const li = document.createElement("li");
const time = new Date(m.created).toLocaleTimeString();
li.innerHTML =
"<div>" + escapeHtml(m.text) + "</div>" +
"<div class='meta'>" + time + "</div>";
$("thread").appendChild(li);
}
$("thread").scrollTop = $("thread").scrollHeight;
});
hasMoreSub?.unsubscribe();
hasMoreSub = collection.hasMore$.subscribe((hasMore) => {
$("loadOlder").disabled = !hasMore;
});
});
}
$("loadOlder").addEventListener("click", () => {
if (!currentCollection) return;
log("Loading older messages…");
currentCollection.loadMore();
});
$("send").addEventListener("click", async () => {
const text = $("text").value.trim();
if (!text || !currentAddress) return;
$("send").disabled = true;
try {
await currentAddress.sendText(text);
$("text").value = "";
} catch (err) {
log("Send failed: " + (err.message || err));
} finally {
$("send").disabled = false;
}
});
$("text").addEventListener("keydown", (e) => {
if (e.key === "Enter") $("send").click();
});
$("toggleHistory").addEventListener("click", () => {
if (!currentAddress) return;
const panel = $("history");
if (panel.style.display === "block") {
panel.style.display = "none";
$("toggleHistory").textContent = "Show call history";
historySub?.unsubscribe();
return;
}
panel.style.display = "block";
$("toggleHistory").textContent = "Hide call history";
historySub = currentAddress.history$.subscribe((collection) => {
collection?.values$.subscribe((entries) => {
panel.innerHTML = "";
if (!entries.length) {
panel.textContent = "(no call history)";
return;
}
for (const e of entries) {
const div = document.createElement("div");
div.textContent =
e.kind + " · " + e.status + " · " +
new Date(e.started).toLocaleString();
panel.appendChild(div);
}
});
});
});
function escapeHtml(s) {
return s.replace(/[&<>"']/g, (c) => ({
"&": "&", "<": "<", ">": ">", '"': """, "'": "'",
})[c]);
}
</script>
</body>
</html>Reference
Address.sendText(), send a chat messageAddress.textMessages$/Address.textMessage, chat thread collectionAddress.history$/Address.history, call history for the same conversationTextMessage, the message shapeAddressHistory, the call-log entry shapeCall.address, the active call’s address, for in-call chat
Build a chat application with the Browser SDK
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
In this guide we will explore a simple chat application built using the SignalWire SDK.
Chat application demo screenshot showing multiple users and messages
The chat application you will build.
The Frontend
Using the Browser SDK you can easily integrate chat features into any web application. It only takes a few minutes to set up a basic example.
Connection
To build your own chat application, you first need to include the SDK in your HTML.
<!-- Import SignalWire library -->
<script src="https://cdn.signalwire.com/@signalwire/js@3"></script>Then you can interact with the SDK using the global variable SignalWire. We’ll mainly be interested in the SignalWire.Chat.Client class for this guide, but if you’d like to explore this API, feel free to browse the SDK documentation.
To get started, we need to instantiate a Client object and then subscribe to the channels that we want to be part of.
const chatClient = new SignalWire.Chat.Client({
token: token
})
try {
await chatClient.subscribe(channels) // channels is an array such as ['office', 'test']
} catch (error) {
console.error('Error', error)
}The Client constructor takes a token parameter. This is an authentication token that defines (among other things) the channels which the client is allowed to read and write. When you call chatClient.subscribe, you must make sure that the channels you’re subscribing to are allowed by the token.
How to obtain a token?
Tokens are provided to the client by your own custom server. Your server determines whether the user is actually authorized to access the chat and, if they are, asks SignalWire to emit a token. The token is supplied to us by the Backend section of this guide, which we will explore shortly. For now, replace <localhost:port> with the localhost address and port of your server. If you’re using the code sample below, replace <localhost:port> with http://localhost:8080.
const reply = await axios.post("http://<localhost:port>/get_chat_token", {
member_id: memberId,
channels: channels
});
const token = reply.data.token;Notice how we specify a member_id (which can be any unique string of your choice) and a list of channels that should be allowed in the token. This interface is not specific to the SignalWire SDK: when you will write your own server, you will be free to specify any parameters you need for the /get_chat_token endpoint.
The Backend
The backend is the proxy which should handle all your authentication logic. The backend is responsible to ensure the user requesting the token is authorized to access the chat and, if they are, ask SignalWire to emit a token. The token from SignalWire is then sent to the frontend to be used to initialize the chat client.
Consider the Express.js example below:
require("dotenv").config();
const auth = {
username: process.env.PROJECT_ID, // Project-ID
password: process.env.API_TOKEN, // API token
};
const apiurl = `https://${process.env.SPACE_URL}`;
const axios = require("axios")
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express()
const port = 8080
app.use(bodyParser.json());
app.use(cors());
app.use(express.static('frontend'))
app.post("/get_chat_token", async (req, res) => {
const { member_id, channels } = req.body;
const channelsPerms = {}
for (const c of channels) {
channelsPerms[c] = { read: true, write: true }
}
const reply = await axios.post(
apiurl + "/api/chat/tokens",
{
ttl: 50,
channels: channelsPerms,
member_id,
state: {},
},
{ auth }
)
res.json({
token: reply.data.token
})
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})The backend technology and stack is not relevant. The important part is that the SignalWire Chat REST API is called to Generate a new Chat Token with the appropriate parameters.
This is all you need to get the chat up and running.
Downloading the existing messages
After you join a channel, you will likely want to download a list of messages that were sent into that channel. SignalWire stores the messages for you so that you can use the getMessages method of the SDK to get the JSON list.
/**
* Download a list of existing messages from the server.
* @param {string} channel
*/
async function downloadExistingMessages(channel) {
const messages = await chatClient.getMessages({
channel: channel
});
if (!messages?.messages) return;
for (const msg of messages.messages.reverse()) {
displayMessage(msg, channel);
}
}
// Download the already existing messages
for (const channel of channels) {
downloadExistingMessages(channel);
}For each of the channels we’re subscribing to, we call chatClient.getMessages. For each message, we then call displayMessage to display it in the UI.
Receiving incoming messages
If you want to receive incoming messages for the channels you’ve subscribed to, you just have to listen to the message event. For example:
/**
* Subscribe to the "message" event.
* This is triggered each time a new message is sent in one of
* the channels we're subscribed to.
*/
chatClient.on("message", (message) => {
displayMessage(message, message.channel);
});This will call displayMessage each time a new message is received.
Sending a message
Sending a message is just a method call. This will send your message into the specified channel:
await chatClient.publish({
channel: channel,
content: message
});Note that your message doesn’t necessarily have to be a string! It can be any JSON-serializable object.
Displaying “is typing” indicator
To display a “member is typing” indicator we can exploit the member state management of the SDK. Each member has an associated state, which can be controlled from the SDK. We can use this state to store a boolean which indicates whether a member is currently typing. The state is shared across all members, so they can update their UI to show the indicator.
To set the typing state, you will need to subscribe to the keyup event for the textarea in which users type their messages. At the end, here is how the state is updated:
chatClient.setMemberState({
channels: channels, // list of channels
state: {
typing: true
}
});You can specify any JSON-serializable object as your state. The other members can subscribe to the member.updated event to get the state updates. For example:
// Set of ids of the members who are typing
const typingMemberIds = new Set();
chatClient.on("member.updated", (member) => {
if (member.state?.typing) {
typingMemberIds.add(member.id);
} else {
typingMemberIds.delete(member.id);
}
});Here, we check the value of member.state.typing. If it’s true, we add the member to the set of members who are currently typing. Otherwise, we remove it.
Wrap up
We have built a basic chat application. With just a few lines of code, our application can send and receive messages, subscribe to multiple channels at the same time, access the message history, and display typing indicators.
Here are a few resources to learn more about the chat:
- Technical Reference
Sign Up Here
If you would like to test this example using your private credentials, you can create a SignalWire account and Space here.
Please feel free to reach out to us on our Community Discord or create a Support ticket if you need guidance!
Chat application demo screenshot showing multiple users and messages
Chat
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
The Chat namespace contains the classes and functions that you need to create a real-time chat application.
Classes
- ChatMember
- ChatMessage
- Client
ChatMember
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Represents a member in a chat.
Properties
channel
Accesses the channel of this member.
Syntax:ChatMember.channel()
Returns:string
id
Accesses the ID of this member.
Syntax:ChatMember.id()
Returns:string
state
Accesses the state of this member.
Syntax:ChatMember.state()
Returns:any
ChatMemberEntity
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
An object representing a Chat Member with only the state properties of ChatMember.
Properties
channel
stringRequired
The channel of this member.
id
stringRequired
The id of this member.
state
Record<any,any toc={true}>Required
The state of this member.
ChatMessage
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Represents a message in a chat.
Constructors
constructor
• new ChatMessage(payload)
Parameters
payload
ChatMessageRequired
Chat message payload
Properties
channel
Accesses the channel in which this message was sent.
Syntax:ChatMessage.channel()
Returns:string
content
Accesses the content of this message. This can be any JSON-serializable object or value.
Syntax:ChatMessage.content()
Returns:string
id
Accesses the id of this message.
Syntax:ChatMessage.id()
Returns:string
member
Accesses the member which sent this message.
Syntax:ChatMessage.member()
Returns: ChatMember
meta
Accesses any metadata associated with this message.
Syntax:ChatMessage.meta()
Returns:any
publishedAt
Accesses the date and time at which this message was published.
Syntax:ChatMessage.publishedAt()
Returns:Date
ChatMessageEntity
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
An object representing a Chat Message with only the state properties of ChatMessage.
Properties
content
anyRequired
The content of this message. This can be any JSON-serializable object or value.
id
stringRequired
The id of this message.
member
ChatMemberRequired
The member which sent this message. See ChatMember for more details.
meta
any
Any metadata associated with this message.
publishedAt
DateRequired
The date and time at which this message was published.
Chat.Client
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
You can use the Client object to build a messaging system into the browser.
Example usage:
import { Chat } from "@signalwire/js";
const chatClient = new Chat.Client({
token: "<your_chat_token>", // get this from the REST APIs
});
await chatClient.subscribe(["mychannel1", "mychannel2"]);
chatClient.on("message", (message) => {
// prettier-ignore
console.log("Received", message.content,
"on", message.channel,
"at", message.publishedAt);
});
await chatClient.publish({
channel: "mychannel1",
content: "hello world",
});Constructors
constructor
- new Client(
chatOptions)
Creates a new Chat client.
Parameters
chatOptions
objectRequired
Configuration options for the Chat client
chatOptions.token
stringRequired
SignalWire Chat token that can be obtained from the REST APIs.
Example
import { Chat } from "@signalwire/js";
const chatClient = new Chat.Client({
token: "<your_chat_token>",
});Type Aliases
PaginationCursor
This is a utility object that aids in pagination. It is specifically used in conjunction with the getMessages method.
Properties
after
string
This property signifies the cursor for the subsequent page.
before
string
This property signifies the cursor for the preceding page.
disconnect
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- disconnect():
void
Disconnects this client. The client will stop receiving events and you will need to create a new instance if you want to use it again.
Returns
void
Example
client.disconnect();Events
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
member.joined
- member.joined(
member)
A new member joined the chat.
Parameters
member
ChatMemberRequired
The member who joined. See ChatMember for more details.
member.left
- member.left(
member)
A member left the chat.
Parameters
member
ChatMemberRequired
The member who left. See ChatMember for more details.
member.updated
- member.updated(
member)
A member updated its state.
Parameters
member
ChatMemberRequired
The member who updated. See ChatMember for more details.
message
- message(
message)
A new message has been received.
Parameters
message
ChatMessageRequired
The received message. See ChatMessage for more details.
session.expiring
- session.expiring()
The session is going to expire. Use the updateToken method to refresh your token.
getAllowedChannels
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- getAllowedChannels():
Promise<Object>
Returns the channels that the current token allows you to subscribe to.
Returns
Promise<Object>
An object whose keys are the channel names, and whose values are the permissions. For example:
{
"my-channel-1": { "read": true, "write": false },
"my-channel-2": { "read": true, "write": true },
}Examples
const chatClient = new Chat.Client({
token: "<your chat token>",
});
const channels = await chatClient.getAllowedChannels();
console.log(channels);getMemberState
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- getMemberState(
params):Promise<{ channels: Record<string, ChatChannelState> }>
Returns the states of a member in the specified channels.
Parameters
params
objectRequired
Configuration object for getting member state
params.channels
string | string[]
Channels for which to get the state.
params.memberId
stringRequired
Id of the member for which to get the state.
Returns
Promise<{ channels: Record<string, ChatChannelState> }>
Example
const s = await chatClient.getMemberState({
channels: ["chan1", "chan2"],
memberId: "my-member-id",
});
s.channels.length; // 2
s.channels.chan1.state; // the state object for chan1getMembers
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- getMembers(
params):Promise<{ members: ChatMemberEntity[] }>- See ChatMemberEntity documentation for more details.
Returns the list of members in the given channel.
Parameters
params
objectRequired
Configuration object for getting members
params.channel
stringRequired
The channel for which to get the list of members.
Returns
Promise<{ members: ChatMemberEntity[] }> - See ChatMemberEntity documentation for more details.
Example
const m = await chatClient.getMembers({ channel: "my-channel" });
m.members.length; // 7
m.members[0]; // { id: ..., channel: ..., state: ... }getMessages
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- getMessages(
params):Promise<{ cursor: PagingCursor; messages: ChatMessageEntity[] }>
See PagingCursor documentation and ChatMessageEntity documentation for more details.
Returns the list of messages that were sent to the specified channel.
Parameters
params
objectRequired
Configuration object for getting messages
params.channel
stringRequired
Channel for which to retrieve the messages.
params.cursor
PagingCursor
Cursor for pagination. See PagingCursor for more details.
Returns
Promise<{ cursor: PagingCursor; messages: ChatMessageEntity[] }>
See PagingCursor documentation and ChatMessageEntity documentation for more details.
Example
const m = await chatClient.getMessages({ channel: "chan1" });
m.messages.length; // 23
m.messages[0]; // the most recent message
m.messages[0].member; // the sender
m.messages[0].content; // the content
m.messages[0].meta; // the metadata (if any)
m.cursor.next; // if not null, there are more messages.
// Get the next page using the cursor
const next = await chatClient.getMessages({
channel: "chan1",
cursor: {
after: m.cursor.after,
},
});off
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- off(
event,fn?)
Remove an event handler.
Parameters
| Name | Type | Description |
|---|---|---|
event | string | Name of the event. See the list of events. |
fn? | Function | An event handler which had been previously attached. |
on
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- on(
event,fn)
Attaches an event handler to the specified event.
Parameters
| Name | Type | Description |
|---|---|---|
event | string | Name of the event. See the list of events. |
fn | Function | An event handler. |
Example
In the below example, we are listening for the call.state event and logging the current call state to the console. This means this will be triggered every time the call state changes.
call.on("call.state", (call) => {
console.log("call state changed:", call.state);
});once
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- once(
event,fn)
Attaches an event handler to the specified event. The handler will fire only once.
Parameters
| Name | Type | Description |
|---|---|---|
event | string | Name of the event. See the list of events. |
fn | Function | An event handler. |
publish
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- publish(
params):Promise<void>
Publish a message into the specified channel.
Parameters
params
objectRequired
Configuration object for publishing a message
params.channel
stringRequired
Channel in which to send the message.
params.content
anyRequired
The message to send. This can be any JSON-serializable object or value.
params.meta
Record<any, any toc={true}>
Metadata associated with the message. There are no requirements on the content of metadata.
Returns
Promise<void>
Examples
Publishing a message as a string:
await chatClient.publish({
channel: "my-channel",
content: "Hello, world."
});Publishing a message as an object:
await chatClient.publish({
channel: "my-channel",
content: {
field_one: "value_one",
field_two: "value_two",
},
});removeAllListeners
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- removeAllListeners(
event?)
Detaches all event listeners for the specified event.
Parameters
| Name | Type | Description |
|---|---|---|
event? | string | Name of the event (leave this undefined to detach listeners for all events). See the list of events. |
setMemberState
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- setMemberState(
params):Promise<void>
Sets a state object for a member, for the specified channels. The previous state object will be completely replaced.
Parameters
params
objectRequired
Configuration object for setting member state
params.channels
string | string[]Required
Channels for which to set the state.
params.memberId
stringRequired
Id of the member to affect. If not provided, defaults to the current member.
params.state
Record<any, any toc={true}>Required
The state to set. There are no requirements on the content of the state.
Returns
Promise<void>
Example
await chatClient.setMemberState({
channels: ["chan1", "chan2"],
state: {
online: true,
typing: false,
},
});subscribe
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- subscribe(
channels):Promise<void>
List of channels for which you want to receive messages. You can only subscribe to those channels for which your token has read permission.
Note that the subscribe function is idempotent, and calling it again with a different set of channels will not unsubscribe you from the old ones. To unsubscribe, use unsubscribe.
Parameters
channels
string | string[]Required
The channels to subscribe to, either in the form of a string (for one channel) or an array of strings.
Returns
Promise<void>
Example
const chatClient = new Chat.Client({
token: "<your chat token>",
});
chatClient.on("message", (m) => console.log(m));
await chatClient.subscribe("my-channel");
await chatClient.subscribe(["chan-2", "chan-3"]);unsubscribe
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- unsubscribe(
channels):Promise<void>
List of channels from which you want to unsubscribe.
Parameters
channels
string | string[]Required
The channels to unsubscribe from, either in the form of a string (for one channel) or an array of strings.
Returns
Promise<void>
Example
await chatClient.unsubscribe("my-channel");
await chatClient.unsubscribe(["chan-2", "chan-3"]);updateToken
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
- updateToken(
token):Promise<void>
Replaces the token used by the client with a new one. You can use this method to replace the token when, for example, it is expiring, in order to keep the session alive.
The new token can contain different channels from the previous one. In that case, you will need to subscribe to the new channels if you want to receive messages for those. Channels that were in the previous token but are not in the new one will get unsubscribed automatically.
Parameters
token
stringRequired
The new token.
Returns
Promise<void>
Example
const chatClient = new Chat.Client({
token: '<your chat token>'
})
chatClient.on('session.expiring', async () => {
const newToken = await fetchNewToken(..)
await chatClient.updateToken(newToken)
})Chat Namespace
For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
The Chat namespace includes methods that allows you to send and receive chat messages.
The ConversationChatMessage object
ConversationChatMessage objects represent the specific interactions that have taken place in the Chat:
id: A unique identifier for the conversation message.conversation_id: A unique identifier of the parent conversation.user_id: A unique identifier for the subscriber.ts: The timestamp, in Unix Epoch, when the message was created.details: Additional metadata associated with the conversation.type: The type of the conversation message.subtype: The subtype of the conversation message.
Here is an example of a ConversationChatMessage object:
{
"id": "3c114417-5cc0-4d03-a30f-c90659028d73",
"conversation_id": "9dbe9e94-c797-461e-b5a3-af6d095deaf4",
"user_id": "cecbe021-ff86-4ac6-bdf3-399ca477ad6f",
"ts": 1708192187.6779745,
"details": {},
"type": "message",
"subtype": "log"
}Methods
getMessages
- getMessages(
options):Promise<{ data: ConversationChatMessage[], hasNext, hasPrev, nextPage(), prevPage() }>
Returns the list of chat messages for a given addressId.
Parameters
| Name | Type | Default value | Description |
|---|---|---|---|
options | object | - | |
options.addressId? | string | undefined | Chat messages exchanged with this particular address id will be fetched. |
options.pageSize? | number | 10 | The amount of messages per pagination page. |
Returns
Promise<{ data: Address[], hasNext, hasPrev, nextPage(), prevPage() }>
Example
await client.chat.getMessages({ addressId: "9dbe9e94-c797-461e-b5a3-af6d095deaf4" });subscribe
- subscribe(
options):{cancel()}
Returns a list of Addresses.
Parameters
| Name | Type | Default value | Description |
|---|---|---|---|
options | object | - | |
options.addressId | string | - | The address to subscribe to |
options.onMessage | (ConversationEventParams)=>void | - | The callback that gets called when a new message event happens |
Returns
An object will be returned with a cancel() function which can be used to unsubscribe to the event.
Example
const { cancel } = await client.chat.subscribe({
addressId: "9dbe9e94-c797-461e-b5a3-af6d095deaf4",
onMessage(msg) {
console.log(msg);
},
});
cancel();sendMessage
- sendMessage(
options):Promise<Conversation>
Send a Chat Message to a given Address ID. If no Conversation exists with that Address ID, one will be created. This is the same function as conversation.sendMessage.
Parameters
| Name | Type | Description |
|---|---|---|
options | object | |
options.addressId | string | The ID of the Address where to send the message. |
options.text | string | The Message text content. |
options.metadata? | object | Metadata to go along with the Message. |
options.details? | object | Extra Message event details. Can be used to construct custom UIs, for example. |
Example
await client.chat.sendMessage({
addressId: "12345",
text: "Hello from SignalWire!",
});join
- join(
options):Promise<JoinConversationResponse>
Joins a conversation given an address without having to send a message. Does nothing if you were already joined to the conversation. This is the same function as conversation.join.
You automatically join a conversation when you send the first message to an address. The join method allows you to join a conversation without first sending a message.
Parameters
| Name | Type | Description |
|---|---|---|
options | object | |
options.addressId | string | The id of the address to join the conversation of. |
Example
await chat.join({
addressId: "9dbe9e94-c797-461e-b5a3-af6d095deaf4",
});