@yellowdotai/yellow-chat-sdk
v1.1.1
Published
Yellow Chat SDK - A TypeScript SDK for Yellow.ai chat integration
Readme
Yellow Chat SDK
A TypeScript SDK for integrating Yellow.ai chat functionality into web applications. This SDK provides event-based communication for a clean separation between the data layer and UI layer.
Installation
npm install @yellowdotai/yellow-chat-sdkQuick Start
import { YellowChat } from '@yellowdotai/yellow-chat-sdk';
// Create SDK instance
const sdk = new YellowChat();
// Subscribe to events BEFORE connecting
sdk.on('connection:connected', ({ userId }) => {
console.log('Connected as:', userId);
});
sdk.on('message:received', (message) => {
console.log('New message:', message);
// message.type = 'text' | 'image' | 'video' | 'audio' | 'file' | 'location' | 'system'
// message.content = { message: "..." } for text, { url: "..." } for media
// Render message in your UI based on type
});
sdk.on('agent:assigned', (agentProfile) => {
// Update UI to show agent info
console.log('Agent joined:', agentProfile.name);
});
sdk.on('agent:left', () => {
// Agent has left the conversation
console.log('Agent left the conversation');
});
// Initialize and connect
await sdk.init({
bot: 'your-bot-id',
host: 'https://cloud.yellow.ai'
});
await sdk.connect();
// Send messages
await sdk.sendMessage('Hello!');Configuration
interface SDKConfig {
// Required
bot: string; // Your bot ID
// Server
host?: string; // API host URL (default: 'https://cloud.yellow.ai')
// Message Source (IMPORTANT for live agent integrations)
source?: string; // 'yellowmessenger' (default) or 'syncApi'
// User
userId?: string; // User ID (auto-generated if not provided)
name?: string; // User display name
// Authentication
ymAuthenticationToken?: string; // Auth token (if required by your bot)
// Custom Payload
payload?: Record<string, unknown>;
// Journey Triggers
triggerJourney?: string;
// UTM Parameters
utmSource?: string;
utmCampaign?: string;
utmMedium?: string;
// Debug
debug?: boolean; // Enable debug logging
}Source Configuration
The source parameter is critical when integrating with live agent functionality:
| Source | Use Case | Description |
|--------|----------|-------------|
| yellowmessenger | Fully Headless | Default. Use when SDK is your only communication channel. All messages sent/received via WebSocket. |
| syncApi | Hybrid Mode (REST + SDK) | Use when integrating with /integrations/sync/v1/message REST API. Required for hybrid architectures. |
When to use yellowmessenger (default)
Use this for a fully headless approach where the SDK handles all communication:
await sdk.init({
bot: 'your-bot-id',
source: 'yellowmessenger' // or omit - this is the default
});
await sdk.connect(userId);
// All communication via SDK
await sdk.sendMessage('Hello');When to use syncApi
Use this for a hybrid approach where you:
- Start with REST API calls to
/integrations/sync/v1/messagefor bot interactions - Switch to SDK WebSocket connection when a live agent connects
- Continue using SDK for real-time agent communication
// Step 1: Initially use REST API for bot messages
const response = await fetch('/integrations/sync/v1/message', {
method: 'POST',
body: JSON.stringify({
botId: 'your-bot-id',
sender: senderId,
data: { message: 'Hello' }
})
});
// Step 2: When live agent connects, switch to SDK with syncApi source
if (response.data.includes('live agent connected')) {
await sdk.init({
bot: 'your-bot-id',
source: 'syncApi' // IMPORTANT: Must match REST API source for ticket consistency
});
await sdk.connect(senderId); // Use same senderId from REST API
// Now receive agent messages via SDK events
sdk.on('message:received', (msg) => console.log('Agent:', msg));
}Important: When using hybrid mode, the
sourcemust be set tosyncApito maintain ticket consistency between REST API and WebSocket channels. Using mismatched sources will cause live agent tickets to close unexpectedly.
Live agent (agent inbox) — use yellowmessenger
If your custom UI needs to receive live-agent messages (agent inbox), use
source: 'yellowmessenger' and send all messages through the SDK
(sdk.sendMessage(...)). This keeps sending and receiving on the same
widget/WebSocket channel, which is how the backend routes agent replies — and
ticket lifecycle events like ticket:resolved — back to your widget over XMPP.
await sdk.init({ bot: 'your-bot-id', source: 'yellowmessenger' });
await sdk.connect(userId);
await sdk.sendMessage('Hi'); // send via SDK; agent replies arrive on 'message:received'Why not
syncApifor agent chat? The sync API (/integrations/sync/v1/message) is a request/response channel — the agent's reply comes later, asynchronously, and there's no open channel on the sync side to push it back. So insyncApimode, agent → user messages are not guaranteed to reach the SDK. They only arrive if (a) the same user id is used for the syncsenderandsdk.connect(userId), and (b) the platform is configured to route that bot's agent messages to XMPP. If you must keep sending via the sync API, either arrange that backend routing, or poll conversation history to fetch agent replies. For a purely headless widget that must show agent messages, preferyellowmessenger.
API
Lifecycle
// Initialize
await sdk.init(config);
// Connect (optionally with a specific user ID)
await sdk.connect(userId?);
// Disconnect
sdk.disconnect();Messaging
// Send text message
await sdk.sendMessage('Hello!');
// Send a file (see File Upload below)
await sdk.sendFile(file);Note: Message history is automatically fetched when you call
connect(). Listen to thehistory:previousMessagesevent to receive it.
File Upload
sendFile(file: File): Promise<FileUploadResponse>Uploads a File and posts it into the conversation. This is a single call — the
SDK uploads the file to the server and then delivers the file message into the
conversation, so both the bot/agent and your own UI receive it. Works in bot and live
agent conversations alike.
// From a file input
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const result = await sdk.sendFile(file);
console.log('Uploaded:', result.url); // { url, name, size }
} catch (error) {
console.error('Upload failed:', error);
}
});
// Track progress/status via events
sdk.on('file:uploadStarted', ({ fileId, fileName }) => showSpinner(fileName));
sdk.on('file:uploadCompleted', ({ fileId, file }) => showPreview(file.url));
sdk.on('file:uploadFailed', ({ fileId, error }) => showError(error.message));FileUploadResponse:
interface FileUploadResponse {
url: string; // Hosted URL of the uploaded file
name: string; // Original file name
size: number; // File size in bytes
}Uploads are sent as
multipart/form-data; noContent-Typeheader should be set manually (the SDK handles this). Failures throw aYellowChatErrorwith codeFILE_UPLOAD_FAILEDorFILE_TOO_LARGE.
State
// Check connection
const connected = sdk.isConnected();
// Get user ID
const userId = sdk.getUserId();
// Get bot info
const botInfo = sdk.getBotInfo();Events
// Subscribe to events (returns unsubscribe function)
const unsubscribe = sdk.on('message:received', (message) => {
console.log(message);
});
// Unsubscribe later
unsubscribe();
// Or use off()
sdk.off('message:received', handler);Events
Connection Events
| Event | Payload | Description |
|-------|---------|-------------|
| connection:connecting | - | Connection attempt started |
| connection:connected | { userId } | Successfully connected |
| connection:disconnected | { code, message, wasClean } | Disconnected |
| connection:reconnecting | { attempt, delay } | Reconnecting |
| connection:reconnected | { userId } | Successfully reconnected |
| connection:error | SDKError | Connection error |
Message Events
| Event | Payload | Description |
|-------|---------|-------------|
| message:received | IncomingMessage | Message from bot/agent |
| message:sent | { id, message } | Message sent successfully |
| message:failed | { messageId, error } | Message failed to send |
Agent Events
| Event | Payload | Description |
|-------|---------|-------------|
| agent:assigned | AgentProfile | Live agent joined the conversation |
| agent:left | - | Agent left or ticket closed/resolved |
| agent:typing | boolean | Agent is typing |
Ticket Events
| Event | Payload | Description |
|-------|---------|-------------|
| ticket:closed | - | Ticket has been closed |
| ticket:resolved | - | Ticket has been resolved |
File Events
| Event | Payload | Description |
|-------|---------|-------------|
| file:uploadStarted | { fileId, fileName } | File upload started |
| file:uploadProgress | { fileId, progress } | Upload progress (0–100), if supported |
| file:uploadCompleted | { fileId, file } | Upload completed (file = { url, name, size }) |
| file:uploadFailed | { fileId, error } | Upload failed |
History Events
| Event | Payload | Description |
|-------|---------|-------------|
| history:previousMessages | Message[] | Previous conversation messages (auto-fetched on connect) |
History is automatically fetched when
connect()is called. On reconnection, only missed messages are fetched.
Network Events
| Event | Payload | Description |
|-------|---------|-------------|
| network:online | - | Network connection restored |
| network:offline | - | Network connection lost |
Integration Modes
Mode 1: Fully Headless (SDK Only)
Use the SDK as your only communication channel. Best for custom chat widgets.
import { YellowChat } from 'yellow-chat-sdk';
const sdk = new YellowChat();
sdk.on('message:received', (message) => {
console.log('Message:', message);
});
await sdk.init({
bot: 'your-bot-id',
source: 'yellowmessenger' // Default - can be omitted
});
await sdk.connect();
await sdk.sendMessage('Hello');Mode 2: Hybrid Mode (REST API + SDK)
Start with REST API for bot interactions, switch to SDK when live agent connects.
Why use Hybrid Mode?
- Server-side control over bot conversations
- Reduced client-side complexity for bot-only flows
- Real-time WebSocket only when needed (live agent)
import { YellowChat } from 'yellow-chat-sdk';
let chatMode = 'rest';
let sdk = null;
const senderId = 'unique-user-id';
// REST API for bot messages
async function sendMessageViaREST(message) {
const response = await fetch('https://cloud.yellow.ai/integrations/sync/v1/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key'
},
body: JSON.stringify({
botId: 'your-bot-id',
sender: senderId,
data: { message }
})
});
const data = await response.json();
// Check if live agent connected
if (data.data?.messageArray?.some(m =>
m.message?.toLowerCase().includes('live agent') &&
m.message?.toLowerCase().includes('connected')
)) {
await switchToWebSocketMode();
}
return data;
}
// Switch to SDK when live agent connects
async function switchToWebSocketMode() {
sdk = new YellowChat();
sdk.on('message:received', (message) => {
console.log('Agent message:', message);
});
sdk.on('ticket:closed', () => {
console.log('Ticket closed - switching back to REST');
chatMode = 'rest';
});
sdk.on('ticket:resolved', () => {
console.log('Ticket resolved - switching back to REST');
chatMode = 'rest';
});
await sdk.init({
bot: 'your-bot-id',
source: 'syncApi' // CRITICAL: Must be 'syncApi' for hybrid mode!
});
await sdk.connect(senderId); // Use same senderId from REST API
chatMode = 'websocket';
}
// Send message based on current mode
async function sendMessage(message) {
if (chatMode === 'rest') {
return sendMessageViaREST(message);
} else {
return sdk.sendMessage(message);
}
}Critical: When using hybrid mode, you MUST set
source: 'syncApi'in the SDK configuration. This ensures that messages sent via the SDK are correctly associated with tickets created via the REST API. Using mismatched sources will cause tickets to close unexpectedly.
Message Types
All messages from the SDK follow a standardized, flattened format:
interface IncomingMessage {
id: string; // Unique message ID
timestamp: Date; // Message timestamp
type: MessageType; // Message type
content: MessageContent; // Flattened content
sender: 'bot' | 'agent' | 'user' | 'system'; // Who sent the message
}
// Message types
type MessageType = 'text' | 'image' | 'video' | 'audio' | 'file' | 'location' | 'system' | 'cards' | 'quickReplies';
// Content is FLATTENED based on type:
// Text: { message: "Hello" }
// Image: { url: "https://..." }
// Video: { url: "https://...", controls: true, autoplay: false, ... }
// Audio: { url: "https://..." }
// File: { url: "https://...", name: "document.pdf", size?: 1024, mimeType?: "application/pdf" }
// Location: { latitude: 12.34, longitude: 56.78 }
// System: { message: "Agent joined the conversation" }Example: Handling Different Message Types
sdk.on('message:received', (message) => {
switch (message.type) {
case 'text':
console.log('Text:', message.content.message);
break;
case 'image':
console.log('Image URL:', message.content.url);
break;
case 'video':
console.log('Video URL:', message.content.url);
// Optional: message.content.controls, message.content.autoplay
break;
case 'file':
console.log('File:', message.content.name, message.content.url);
break;
case 'system':
console.log('System:', message.content.message);
// e.g., "Agent joined the conversation", "Conversation ended"
break;
}
});Error Handling
import { YellowChatError } from 'yellow-chat-sdk';
try {
await sdk.sendMessage('Hello');
} catch (error) {
if (error instanceof YellowChatError) {
console.error('SDK Error:', error.code, error.message);
if (error.recoverable) {
// Retry logic
}
}
}
// Or listen for error events
sdk.on('connection:error', (error) => {
console.error('Connection error:', error);
});
sdk.on('message:failed', ({ messageId, error }) => {
console.error('Message failed:', messageId, error);
});TypeScript
The SDK is written in TypeScript and includes full type definitions.
import type {
SDKConfig,
IncomingMessage,
AgentProfile,
SDKEvents,
SDKEventName
} from 'yellow-chat-sdk';Browser Support
- Chrome 60+
- Firefox 55+
- Safari 11+
- Edge 79+
