LatteStreamยฎ
Quick Start
Getting Started
Building Custom SDKs
SDKs & Libraries
JavaScript/TypeScript
Node.js / Bun / Deno
Python
Go
PHP
API Reference
WebSocket API
REST API
Webhooks
Authentication
The official LatteStream SDK for JavaScript and TypeScript applications
The LatteStream JavaScript SDK provides a powerful, type-safe way to add real-time messaging to any frontend application. Built with TypeScript-first design and Pusher-compatible APIs for easy migration.
npm install @lattestream/client # or yarn add @lattestream/client # or pnpm add @lattestream/client
import LatteStream from '@lattestream/client'; // Initialize client const lattestream = new LatteStream('your-app-key', { cluster: 'us-east-1', authEndpoint: '/auth', }); // Connect lattestream.connect(); // Subscribe to a channel const channel = lattestream.subscribe('my-channel'); // Listen for events channel.bind('my-event', (data) => { console.log('Received:', data); });
The LatteStream client manages connection states automatically:
lattestream.connection.bind('connecting', () => { console.log('Connecting to LatteStream...'); }); lattestream.connection.bind('connected', () => { console.log('Connected! Socket ID:', lattestream.getSocketId()); }); lattestream.connection.bind('disconnected', () => { console.log('Disconnected from LatteStream'); }); lattestream.connection.bind('reconnecting', () => { console.log('Attempting to reconnect...'); }); lattestream.connection.bind('failed', () => { console.log('Connection failed'); });
No authentication required. Perfect for broadcasting public updates:
const publicChannel = lattestream.subscribe('news-updates'); publicChannel.bind('breaking-news', (data) => { showNotification(data.title, data.summary); });
Require authentication. Channel names must start with private-:
const privateChannel = lattestream.subscribe('private-user-123'); privateChannel.bind('personal-message', (data) => { displayPersonalMessage(data); });
Track who's online. Channel names must start with presence-:
const presenceChannel = lattestream.subscribe('presence-chat-room'); // When someone joins presenceChannel.bind('lattestream:member_added', (member) => { console.log(`${member.info.name} joined the chat`); updateUserList(); }); // When someone leaves presenceChannel.bind('lattestream:member_removed', (member) => { console.log(`${member.info.name} left the chat`); updateUserList(); }); // Get all current members const members = presenceChannel.getMembers(); console.log('Current members:', members);
Bind to events at the global or channel level:
// Global events (across all channels) lattestream.bind('global-announcement', (data) => { showGlobalAlert(data.message); }); // Channel-specific events channel.bind('user-typing', (data) => { showTypingIndicator(data.userId); }); // Multiple event handlers channel.bind('message', handleMessage); channel.bind('message', logMessage); channel.bind('message', updateUnreadCount); // Unbind specific handlers channel.unbind('message', logMessage); // Unbind all handlers for an event channel.unbind('message');
interface LatteStreamOptions { // Connection settings wsEndpoint?: string; // Custom WebSocket endpoint cluster?: string; // Geographic cluster (us-east-1, eu-west-1, etc.) forceTLS?: boolean; // Force secure connections (default: true) // Authentication authEndpoint?: string; // Endpoint for channel authorization authTransport?: 'ajax' | 'jsonp'; // Authorization transport method auth?: { headers?: Record<string, string>; // Custom auth headers params?: Record<string, string>; // Custom auth parameters }; // Connection behavior enableLogging?: boolean; // Enable debug logging activityTimeout?: number; // Activity timeout in ms (default: 120000) pongTimeout?: number; // Pong timeout in ms (default: 30000) maxReconnectionAttempts?: number; // Max reconnection attempts (default: 6) maxReconnectGapInSeconds?: number; // Max delay between reconnects (default: 30) // Performance enableStats?: boolean; // Enable connection statistics disableStats?: boolean; // Disable connection statistics (deprecated) }
const lattestream = new LatteStream('your-app-key', { cluster: 'eu-west-1', enableLogging: true, activityTimeout: 60000, maxReconnectionAttempts: 10, authEndpoint: 'https://yourapi.com/lattestream/auth', auth: { headers: { Authorization: 'Bearer ' + userToken, }, params: { user_id: currentUser.id, }, }, });
For private and presence channels, you need an authentication endpoint:
// Client configuration const lattestream = new LatteStream('your-app-key', { authEndpoint: '/lattestream/auth', }); // Your server endpoint should return: // { // auth: "app-key:signature", // channel_data: "{\"user_id\":\"123\",\"user_info\":{\"name\":\"John\"}}" // for presence channels // }
You can also provide a custom authorizer function:
const lattestream = new LatteStream('your-app-key', { authorizer: (channel, options) => { return { authorize: (socketId, callback) => { // Your custom authorization logic fetch('/custom-auth', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ socket_id: socketId, channel_name: channel.name, }), }) .then((response) => response.json()) .then((data) => callback(null, data)) .catch((error) => callback(error, null)); }, }; }, });
Reuse connections across your application:
// Create a single instance const lattestream = new LatteStream('your-app-key'); // Export for use across components export default lattestream; // In other files import lattestream from './lattestream-client'; const channel = lattestream.subscribe('my-channel');
Subscribe to channels only when needed:
class ChatManager { constructor(lattestream) { this.lattestream = lattestream; this.channels = new Map(); } joinRoom(roomId) { if (!this.channels.has(roomId)) { const channel = this.lattestream.subscribe(`presence-room-${roomId}`); this.channels.set(roomId, channel); channel.bind('message', this.handleMessage.bind(this)); } return this.channels.get(roomId); } leaveRoom(roomId) { const channel = this.channels.get(roomId); if (channel) { this.lattestream.unsubscribe(`presence-room-${roomId}`); this.channels.delete(roomId); } } }
Import only what you need:
// Tree-shaking friendly imports import { LatteStream } from '@lattestream/client/core'; import { PresenceChannel } from '@lattestream/client/presence'; // Or use dynamic imports for code splitting async function loadLatteStream() { const { default: LatteStream } = await import('@lattestream/client'); return new LatteStream('your-app-key'); }
lattestream.connection.bind('error', (error) => { console.error('Connection error:', error); // Handle specific error types switch (error.type) { case 'WebSocketError': showErrorMessage( 'Connection failed. Please check your internet connection.' ); break; case 'AuthError': showErrorMessage('Authentication failed. Please log in again.'); break; case 'LimitExceeded': showErrorMessage('Connection limit exceeded. Please try again later.'); break; default: showErrorMessage('An unexpected error occurred.'); } });
channel.bind('lattestream:subscription_error', (error) => { console.error('Subscription error:', error); if (error.status === 403) { showErrorMessage('You do not have permission to access this channel.'); } else if (error.status === 401) { showErrorMessage('Authentication required. Please log in.'); } });
const lattestream = new LatteStream('your-app-key', { // Fallback options when WebSocket fails fallbackTransports: ['xhr_polling', 'xhr_streaming'], }); // Detect connection quality lattestream.connection.bind('state_change', (states) => { if (states.current === 'connected') { hideOfflineIndicator(); } else { showOfflineIndicator(); // Switch to polling mode for better reliability enablePollingMode(); } });
// test-utils.js export const mockLatteStream = { connect: jest.fn(), disconnect: jest.fn(), subscribe: jest.fn(() => ({ bind: jest.fn(), unbind: jest.fn(), trigger: jest.fn(), })), unsubscribe: jest.fn(), getSocketId: jest.fn(() => 'mock-socket-id'), }; // In your tests import { mockLatteStream } from './test-utils'; jest.mock('@lattestream/client', () => ({ default: jest.fn(() => mockLatteStream), }));
// integration-test.js import LatteStream from '@lattestream/client'; describe('LatteStream Integration', () => { let lattestream; beforeAll(async () => { lattestream = new LatteStream(process.env.TEST_APP_KEY); await new Promise((resolve) => { lattestream.connection.bind('connected', resolve); lattestream.connect(); }); }); test('should receive messages on subscribed channel', (done) => { const channel = lattestream.subscribe('test-channel'); channel.bind('test-event', (data) => { expect(data.message).toBe('Hello Test'); done(); }); // Trigger event from server (separate test setup) triggerTestEvent('test-channel', 'test-event', { message: 'Hello Test' }); }); });
new LatteStream(appKey: string, options?: LatteStreamOptions)
connect(): void - Connect to LatteStreamdisconnect(): void - Disconnect from LatteStreamsubscribe(channelName: string): Channel - Subscribe to a channelunsubscribe(channelName: string): void - Unsubscribe from a channelbind(eventName: string, callback: Function): void - Listen for global eventsunbind(eventName: string, callback?: Function): void - Stop listening for global eventsgetSocketId(): string | null - Get current socket IDgetConnectionState(): string - Get connection stateconnection: Connection - Connection object with state and eventschannels: Channels - Channel collection objectbind(eventName: string, callback: Function): Channel - Listen for channel eventsunbind(eventName: string, callback?: Function): Channel - Stop listening for eventstrigger(eventName: string, data: any): boolean - Trigger client event (private/presence only)name: string - Channel namesubscribed: boolean - Subscription statusExtends Channel with additional presence functionality:
getMembers(): Members - Get all online membersgetMember(id: string): Member | null - Get specific membergetMyId(): string | null - Get your user IDgetMemberCount(): number - Get member countNext Steps: Check out the Server SDK documentation to learn how to send messages from your backend.