Connection Management
WebSocket connection lifecycle, state monitoring, error handling, and best practices
Connection Management
Complete guide to managing WebSocket connections, monitoring states, handling errors, and implementing best practices for robust real-time applications.
Core Connection APIs
joinChannel(channel: string): void
Sets the channel for this client instance. Must be called before connect()
.
client.joinChannel("chat-room");
Important: A channel groups related topics together and defines your authorization scope.
connect(timeout?: number): Promise<void>
Establishes WebSocket connection. Automatically fetches grant tokens from authBaseUrl
.
Parameters:
timeout?: number
- Connection timeout in milliseconds (default: 30000)
await client.connect();
// or with custom timeout
await client.connect(10000);
close(): void
Closes the WebSocket connection and cleans up resources.
client.close();
Connection State Monitoring
Connection State Properties
Monitor your connection status in real-time:
isConnected: boolean
Check if WebSocket is connected.
if (client.isConnected) {
console.log("Ready to send messages");
}
isReadable: boolean
Check if client can receive messages.
if (client.isReadable) {
console.log("Receiving messages");
}
isWritable: boolean
Check if client can send messages.
if (client.isWritable) {
await client.publish("topic", "message");
}
Real-time State Monitoring
// Check connection state periodically
setInterval(() => {
console.log(`Connection: ${client.isConnected ? "✅" : "❌"}`);
console.log(`Readable: ${client.isReadable ? "✅" : "❌"}`);
console.log(`Writable: ${client.isWritable ? "✅" : "❌"}`);
}, 5000);
Connection Lifecycle Management
Connection States
The client maintains connection state that you can monitor:
console.log(client.isConnected); // boolean - WebSocket connected
console.log(client.isReadable); // boolean - Can receive messages
console.log(client.isWritable); // boolean - Can send messages
Automatic Reconnection
The SDK handles reconnection automatically with exponential backoff. Connection errors are thrown from async methods:
// Monitor connection state
setInterval(() => {
if (!client.isConnected) {
console.log("Connection lost - SDK will reconnect automatically");
}
}, 5000);
// Errors are thrown from operations
try {
await client.connect();
} catch (error) {
console.log("Connection error:", error);
// SDK will attempt to reconnect automatically
}
Manual Connection Management
// Graceful shutdown
process.on('SIGINT', () => {
client.close();
process.exit(0);
});
// Connection with custom timeout
try {
await client.connect(10000); // 10 second timeout
} catch (error) {
console.error("Connection failed:", error);
}
Error Handling
Common Error Patterns
All async methods can throw errors. Always use try-catch:
try {
await client.connect();
await client.subscribe("topic", handler);
} catch (error) {
console.error("Operation failed:", error);
}
Error Types and Handling
Connection Errors
try {
await client.connect();
} catch (error) {
if (error.message.includes("Channel must be set")) {
console.error("Call joinChannel() first");
} else if (error.message.includes("timeout")) {
console.error("Connection timeout - check server");
} else if (error.message.includes("Invalid WebSocket URL")) {
console.error("Check wsBaseUrl configuration");
}
}
Publishing Errors
try {
await client.publish("topic", "message");
} catch (error) {
if (error.message.includes("Not connected")) {
console.error("Connection lost - will reconnect automatically");
} else if (error.message.includes("Invalid topic")) {
console.error("Topic name must be non-empty string");
}
}
History API Errors
try {
const history = await client.getHistory("topic");
} catch (error) {
if (error.message.includes("HTTP base URL required")) {
console.error("Configure httpBaseUrl in client options");
} else if (error.message.includes("History API failed")) {
console.error("Server error - check API status");
}
}
ACK and Subscription Errors
// Handle ACK errors
client.publishWithAck("topic", "message", (ack) => {
if (!ack.success) {
console.error("Publish failed:", ack.error?.code, ack.error?.message);
}
});
// Handle subscription errors
client.subscribeWithCallback("topic", handler, (response) => {
if (!response.success) {
console.error("Subscription failed:", response.error?.message);
}
});
Error Recovery Patterns
Retry with Backoff
async function connectWithRetry(maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
await client.connect();
console.log("Connected successfully");
return;
} catch (error) {
console.log(`Retry ${i + 1}/${maxRetries}`);
if (i === maxRetries - 1) throw error;
// Exponential backoff
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
}
Graceful Degradation
// Continue without history if HTTP API unavailable
let historySupported = true;
try {
await client.getHistory("topic", { limit: 1 });
} catch (error) {
if (error.message.includes("HTTP base URL required")) {
historySupported = false;
console.warn("History API not available");
}
}
// Adapt UI based on capabilities
if (!historySupported) {
hideHistoryButton();
}
Common Error Messages
Error Message | Cause | Solution |
---|---|---|
"Channel must be set before connecting" | No channel set | Call joinChannel() first |
"HTTP base URL required for history API" | Missing HTTP config | Add httpBaseUrl to client options |
"Connection timeout" | Server unreachable | Check wsBaseUrl and network |
"Not connected" | Publishing without connection | Wait for connection or handle gracefully |
"Invalid topic" | Empty/invalid topic name | Use non-empty string topics |
"ACK callback required" | Missing callback in publishWithAck | Provide ACK callback function |
Best Practices
1. Always set channel before connecting
client.joinChannel("my-channel");
await client.connect();
2. Handle reconnection
async function connectWithRetry(maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
await client.connect();
return;
} catch (error) {
console.log(`Retry ${i + 1}/${maxRetries}`);
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
throw new Error("Failed to connect after retries");
}
3. Clean up on exit
process.on("SIGINT", () => {
client.close();
process.exit(0);
});
4. Connection Health Monitoring
class ConnectionMonitor {
private client: ErebusPubSubClient;
private healthCheckInterval?: NodeJS.Timeout;
constructor(client: ErebusPubSubClient) {
this.client = client;
}
startMonitoring() {
this.healthCheckInterval = setInterval(() => {
if (!this.client.isConnected) {
console.warn("Connection unhealthy");
this.onConnectionLost();
}
}, 5000);
}
stopMonitoring() {
if (this.healthCheckInterval) {
clearInterval(this.healthCheckInterval);
}
}
private onConnectionLost() {
// Implement custom reconnection logic
// Show user notification
// Pause outgoing messages
}
}
5. Production Configuration
const client = ErebusClient.createClient({
client: ErebusClientState.PubSub,
authBaseUrl: process.env.EREBUS_AUTH_URL!,
wsBaseUrl: process.env.EREBUS_WS_URL,
heartbeatMs: 30000, // Longer heartbeat for production
connectionTimeoutMs: 10000, // Connection timeout
debug: process.env.NODE_ENV === 'development',
log: (level, msg, meta) => {
// Use your logging framework
logger[level](msg, meta);
}
});
Advanced Patterns
Connection Pooling for Multiple Channels
class ErebusConnectionPool {
private connections = new Map<string, ErebusPubSubClient>();
async getConnection(channel: string): Promise<ErebusPubSubClient> {
if (!this.connections.has(channel)) {
const client = ErebusClient.createClient({
client: ErebusClientState.PubSub,
authBaseUrl: process.env.EREBUS_AUTH_URL!,
});
client.joinChannel(channel);
await client.connect();
this.connections.set(channel, client);
}
return this.connections.get(channel)!;
}
async closeAll() {
for (const client of this.connections.values()) {
client.close();
}
this.connections.clear();
}
}
Circuit Breaker Pattern
class ErebusCircuitBreaker {
private failures = 0;
private lastFailure = 0;
private state: 'closed' | 'open' | 'half-open' = 'closed';
async execute<T>(operation: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailure > 60000) { // 1 minute
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failures = 0;
this.state = 'closed';
}
private onFailure() {
this.failures++;
this.lastFailure = Date.now();
if (this.failures >= 3) {
this.state = 'open';
}
}
}
Next Steps
- Messaging APIs - Publish and subscribe patterns
- Presence Tracking - Monitor user activity
- Types Reference - TypeScript definitions
- Quick Reference - Common patterns cheat sheet