WebSocket API Reference
Introduction
The MyAppAPI WebSocket API provides real-time communication capabilities for your applications. Unlike the request-response pattern of REST APIs, WebSockets enable persistent, bidirectional communication channels that allow both the client and server to send messages at any time.
This makes WebSockets ideal for applications that require real-time updates, notifications, live data feeds, collaborative features, or any scenario where low-latency, bidirectional communication is essential.
When to Use WebSockets
WebSockets are particularly valuable for:
- Real-time dashboards and analytics
- Live feeds and notifications
- Chat and messaging applications
- Collaborative editing tools
- Real-time multiplayer games
- Financial trading platforms
- IoT device monitoring
WebSockets vs. REST and GraphQL
MyAppAPI offers multiple API access methods (REST, GraphQL, and WebSockets) to fit different application requirements. Here's how they compare:
Feature | REST API | GraphQL API | WebSocket API |
---|---|---|---|
Communication Pattern | Request-response | Request-response | Bidirectional, persistent |
Connection Type | New connection per request | New connection per request | Persistent connection |
Real-time Updates | Polling required | Subscriptions via separate WebSocket | Native real-time |
Data Efficiency | Medium (HTTP overhead per request) | High (specific data selection) | Highest (minimal framing overhead) |
Latency | Higher (connection setup each time) | Higher (connection setup each time) | Lowest (persistent connection) |
Best For | CRUD operations, standard integrations | Complex data requirements, flexible queries | Real-time data, notifications, live updates |
Choosing the Right API
Many applications benefit from combining these APIs for different purposes:
- Use REST for standard CRUD operations and admin functions
- Use GraphQL for complex data fetching and flexible queries
- Use WebSockets for real-time updates, notifications, and live data feeds
Establishing Connections
To establish a WebSocket connection with the MyAppAPI WebSocket server, connect to the following endpoint:
wss://ws.myappapi.com/v1/
For regional endpoints, use:
Region | WebSocket Endpoint |
---|---|
United States (Default) | wss://ws.myappapi.com/v1/ |
European Union | wss://eu.ws.myappapi.com/v1/ |
Asia-Pacific | wss://ap.ws.myappapi.com/v1/ |
Important
Always use secure WebSocket connections (wss://
) in production environments to ensure data encryption during transmission.
Connection Parameters
You can provide optional query parameters when establishing the connection:
Parameter | Description | Required |
---|---|---|
v |
API version (defaults to the latest version) | No |
client_id |
A unique identifier for the client (useful for connection tracking) | No |
wss://ws.myappapi.com/v1/?client_id=your_client_identifier
Authentication
WebSocket connections require authentication to identify the requesting user or application. MyAppAPI supports multiple authentication methods for WebSockets.
Query Parameter Authentication
The simplest method is to include your API key as a query parameter when establishing the connection:
wss://ws.myappapi.com/v1/?api_key=your_api_key_here
Security Consideration
While convenient, query parameter authentication may expose your API key in server logs. For production environments, consider using one of the more secure methods below.
Authentication Message
A more secure approach is to establish the connection first, then send an authentication message immediately after connection:
{
"type": "auth",
"api_key": "your_api_key_here"
}
const socket = new WebSocket('wss://ws.myappapi.com/v1/');
socket.onopen = () => {
// Send authentication message immediately after connection
socket.send(JSON.stringify({
type: 'auth',
api_key: 'your_api_key_here'
}));
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'auth_success') {
console.log('Authentication successful!');
// Now you can subscribe to events
} else if (data.type === 'auth_error') {
console.error('Authentication failed:', data.message);
}
};
JWT Authentication
For applications using JWT authentication, you can provide the JWT token:
{
"type": "auth",
"token": "your_jwt_token_here"
}
const socket = new WebSocket('wss://ws.myappapi.com/v1/');
socket.onopen = () => {
// Send JWT authentication message
socket.send(JSON.stringify({
type: 'auth',
token: 'your_jwt_token_here'
}));
};
Authentication Timeout
You must authenticate within 30 seconds of establishing the connection, or the server will automatically close the connection.
Message Format
All messages exchanged over the WebSocket connection use JSON format. Each message has a specific structure depending on its type and purpose.
Common Message Structure
All messages, both sent and received, include a type
field that indicates the purpose of the message:
{
"type": "message_type",
// Additional fields specific to the message type
}
Message Types
The WebSocket API uses the following common message types:
Type | Direction | Description |
---|---|---|
auth |
Client → Server | Authenticate the WebSocket connection |
auth_success |
Server → Client | Authentication was successful |
auth_error |
Server → Client | Authentication failed |
subscribe |
Client → Server | Subscribe to an event or data channel |
unsubscribe |
Client → Server | Unsubscribe from an event or data channel |
subscription_success |
Server → Client | Subscription was successful |
subscription_error |
Server → Client | Subscription failed |
event |
Server → Client | An event has occurred (data update, notification, etc.) |
ping |
Client → Server | Check connection status |
pong |
Server → Client | Response to a ping message |
error |
Server → Client | General error message |
Request IDs
To correlate requests with responses, you can include a request_id
field in your messages. The server will include the same request_id
in its response:
{
"type": "subscribe",
"channel": "projects.updates",
"project_id": "proj_12345",
"request_id": "req_abc123"
}
{
"type": "subscription_success",
"channel": "projects.updates",
"project_id": "proj_12345",
"subscription_id": "sub_xyz789",
"request_id": "req_abc123"
}
Event Subscriptions
The primary purpose of the WebSocket API is to subscribe to real-time events and updates. You can subscribe to various event channels based on your application's needs.
Subscribing to Events
To subscribe to events, send a subscribe
message specifying the channel and any required parameters:
{
"type": "subscribe",
"channel": "projects.updates",
"project_id": "proj_12345",
"request_id": "req_abc123"
}
If the subscription is successful, you'll receive a subscription_success
response:
{
"type": "subscription_success",
"channel": "projects.updates",
"project_id": "proj_12345",
"subscription_id": "sub_xyz789",
"request_id": "req_abc123"
}
Available Event Channels
MyAppAPI offers various event channels you can subscribe to:
Channel | Description | Required Parameters |
---|---|---|
users.presence |
User online/offline status changes | None (for all users you can access) or user_id |
projects.updates |
Project creation, updates, or deletion | None (for all projects) or project_id |
tasks.updates |
Task creation, updates, status changes, or deletion | project_id (optional: task_id ) |
comments.updates |
New comments or comment edits | project_id or task_id |
files.updates |
File uploads, updates, or deletion | project_id (optional: file_id ) |
notifications |
User notifications (mentions, assignments, etc.) | None (applies to authenticated user) |
data.{resource_type} |
Real-time data feeds for specific resource types | Varies by resource type |
Receiving Events
When an event occurs that matches your subscription, you'll receive an event
message:
{
"type": "event",
"channel": "projects.updates",
"event": "project.updated",
"subscription_id": "sub_xyz789",
"timestamp": "2025-05-11T14:32:10.456Z",
"data": {
"project_id": "proj_12345",
"name": "Updated Project Name",
"description": "This project has been updated",
"updated_by": "user_98765",
"updated_at": "2025-05-11T14:32:10.456Z"
}
}
Unsubscribing from Events
To stop receiving events for a specific subscription, send an unsubscribe
message:
{
"type": "unsubscribe",
"subscription_id": "sub_xyz789",
"request_id": "req_def456"
}
You'll receive a confirmation:
{
"type": "unsubscription_success",
"subscription_id": "sub_xyz789",
"request_id": "req_def456"
}
Error Handling
When an error occurs, the WebSocket server sends an error message with details about what went wrong.
Error Message Format
{
"type": "error",
"code": "invalid_subscription",
"message": "Unable to subscribe to the specified channel",
"request_id": "req_abc123",
"details": {
"reason": "Missing required parameter: project_id",
"channel": "tasks.updates"
}
}
Common Error Codes
Error Code | Description |
---|---|
authentication_required |
The connection is not authenticated |
invalid_credentials |
The provided authentication credentials are invalid |
permission_denied |
The authenticated user doesn't have permission for the requested action |
invalid_message_format |
The message format is invalid or missing required fields |
invalid_subscription |
The subscription request is invalid |
subscription_limit_exceeded |
The maximum number of active subscriptions has been reached |
rate_limit_exceeded |
The client has exceeded the rate limit for WebSocket messages |
resource_not_found |
The requested resource does not exist |
server_error |
An unexpected error occurred on the server |
Handling Errors
Here's an example of how to handle WebSocket errors in your client application:
const socket = new WebSocket('wss://ws.myappapi.com/v1/');
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'error') {
console.error(`Error (${data.code}): ${data.message}`);
// Handle specific error types
switch (data.code) {
case 'authentication_required':
// Attempt to re-authenticate
authenticateSocket();
break;
case 'invalid_subscription':
// Fix subscription parameters and try again
console.error('Subscription error details:', data.details);
break;
case 'rate_limit_exceeded':
// Implement exponential backoff
setTimeout(() => {
retrySendMessage(data.request_id);
}, calculateBackoff());
break;
case 'server_error':
// Report to monitoring system
reportServerError(data);
break;
}
}
};
import json
import websocket
import time
def on_message(ws, message):
data = json.loads(message)
if data.get('type') == 'error':
print(f"Error ({data.get('code')}): {data.get('message')}")
# Handle specific error types
error_code = data.get('code')
if error_code == 'authentication_required':
# Attempt to re-authenticate
authenticate_socket(ws)
elif error_code == 'invalid_subscription':
# Fix subscription parameters and try again
print('Subscription error details:', data.get('details'))
elif error_code == 'rate_limit_exceeded':
# Implement exponential backoff
retry_delay = calculate_backoff()
print(f"Rate limit exceeded. Retrying in {retry_delay} seconds.")
time.sleep(retry_delay)
retry_send_message(ws, data.get('request_id'))
elif error_code == 'server_error':
# Report to monitoring system
report_server_error(data)
# Connect to WebSocket
ws = websocket.WebSocketApp("wss://ws.myappapi.com/v1/",
on_message=on_message)
ws.run_forever()
Rate Limits
To ensure fair usage and system stability, MyAppAPI implements rate limits on WebSocket connections and messages.
Connection Limits
Plan | Maximum Connections | Maximum Subscriptions per Connection |
---|---|---|
Free | 5 | 10 |
Professional | 25 | 50 |
Business | 100 | 200 |
Enterprise | Custom | Custom |
Message Rate Limits
Plan | Messages per Minute per Connection |
---|---|
Free | 60 |
Professional | 300 |
Business | 1,000 |
Enterprise | Custom |
Rate Limit Notifications
When you approach or exceed rate limits, the server will send warning messages. If limits are consistently exceeded, connections may be temporarily throttled or disconnected.
Best Practices for Rate Limits
- Batch subscription requests when possible
- Implement exponential backoff for retries
- Monitor your usage to avoid hitting limits
- Optimize your subscription strategy (subscribe only to what you need)
- Consider upgrading your plan if you consistently approach limits
Connection Management
Proper connection management is essential for maintaining reliable WebSocket communications.
Ping/Pong for Connection Health
To verify that a connection is still active, you can send ping messages periodically:
{
"type": "ping",
"request_id": "ping_1"
}
The server will respond with a pong message:
{
"type": "pong",
"timestamp": "2025-05-11T14:45:23.789Z",
"request_id": "ping_1"
}
Server-Initiated Pings
The server also sends periodic ping messages to check client health. Clients should respond with pong messages to avoid connection termination.
Reconnection Strategy
WebSocket connections may disconnect due to network issues or server maintenance. Implement a robust reconnection strategy:
class WebSocketClient {
constructor(url) {
this.url = url;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10;
this.baseReconnectDelay = 1000; // 1 second
this.maxReconnectDelay = 30000; // 30 seconds
this.subscriptions = new Map();
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.authenticate();
this.restoreSubscriptions();
};
this.socket.onclose = (event) => {
console.log(`WebSocket disconnected: ${event.code}`);
this.scheduleReconnect();
};
this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
// Set up message handling
this.socket.onmessage = this.handleMessage.bind(this);
}
scheduleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Maximum reconnection attempts reached');
return;
}
this.reconnectAttempts++;
// Exponential backoff with jitter
const delay = Math.min(
this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
this.maxReconnectDelay
);
const jitter = delay * 0.1 * Math.random();
const reconnectDelay = delay + jitter;
console.log(`Reconnecting in ${reconnectDelay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => {
this.connect();
}, reconnectDelay);
}
authenticate() {
this.sendMessage({
type: 'auth',
api_key: this.apiKey
});
}
restoreSubscriptions() {
// Re-subscribe to all previous subscriptions
for (const [channel, params] of this.subscriptions.entries()) {
this.subscribe(channel, params);
}
}
// Other methods (subscribe, unsubscribe, sendMessage, etc.)
}
import json
import threading
import time
import random
import websocket
class WebSocketClient:
def __init__(self, url, api_key):
self.url = url
self.api_key = api_key
self.reconnect_attempts = 0
self.max_reconnect_attempts = 10
self.base_reconnect_delay = 1 # 1 second
self.max_reconnect_delay = 30 # 30 seconds
self.subscriptions = {}
self.ws = None
self.connect()
def connect(self):
self.ws = websocket.WebSocketApp(
self.url,
on_open=self.on_open,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close
)
# Start WebSocket connection in a separate thread
self.thread = threading.Thread(target=self.ws.run_forever)
self.thread.daemon = True
self.thread.start()
def on_open(self, ws):
print("WebSocket connected")
self.reconnect_attempts = 0
self.authenticate()
self.restore_subscriptions()
def on_message(self, ws, message):
data = json.loads(message)
# Handle different message types
def on_error(self, ws, error):
print(f"WebSocket error: {error}")
def on_close(self, ws, close_status_code, close_reason):
print(f"WebSocket closed: {close_status_code} - {close_reason}")
self.schedule_reconnect()
def schedule_reconnect(self):
if self.reconnect_attempts >= self.max_reconnect_attempts:
print("Maximum reconnection attempts reached")
return
self.reconnect_attempts += 1
# Exponential backoff with jitter
delay = min(
self.base_reconnect_delay * (2 ** (self.reconnect_attempts - 1)),
self.max_reconnect_delay
)
jitter = delay * 0.1 * random.random()
reconnect_delay = delay + jitter
print(f"Reconnecting in {reconnect_delay} seconds (attempt {self.reconnect_attempts})")
time.sleep(reconnect_delay)
self.connect()
def authenticate(self):
self.send_message({
"type": "auth",
"api_key": self.api_key
})
def restore_subscriptions(self):
# Re-subscribe to all previous subscriptions
for channel, params in self.subscriptions.items():
self.subscribe(channel, params)
def send_message(self, message):
if self.ws and self.ws.sock and self.ws.sock.connected:
self.ws.send(json.dumps(message))
else:
print("Cannot send message: WebSocket not connected")
Graceful Disconnect
When your application no longer needs the WebSocket connection, close it gracefully to free up server resources:
// Unsubscribe from all subscriptions first
function disconnect() {
// Unsubscribe from all active subscriptions
for (const subscriptionId of activeSubscriptions) {
socket.send(JSON.stringify({
type: 'unsubscribe',
subscription_id: subscriptionId
}));
}
// Wait a moment for unsubscribe messages to be processed
setTimeout(() => {
// Close the connection
socket.close(1000, 'Client disconnecting');
}, 500);
}
def disconnect(ws, active_subscriptions):
# Unsubscribe from all active subscriptions
for subscription_id in active_subscriptions:
ws.send(json.dumps({
"type": "unsubscribe",
"subscription_id": subscription_id
}))
# Wait a moment for unsubscribe messages to be processed
time.sleep(0.5)
# Close the connection
ws.close()
Implementation Examples
Here are complete examples of how to implement WebSocket connections for common scenarios.
Real-Time Project Updates
This example shows how to subscribe to real-time updates for a specific project:
const API_KEY = 'your_api_key_here';
const PROJECT_ID = 'proj_12345';
// Connect to WebSocket
const socket = new WebSocket('wss://ws.myappapi.com/v1/');
// Track subscription IDs
let projectSubscriptionId = null;
// Set up event handlers
socket.onopen = () => {
console.log('WebSocket connection established');
// Authenticate
socket.send(JSON.stringify({
type: 'auth',
api_key: API_KEY
}));
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received message:', data);
switch (data.type) {
case 'auth_success':
console.log('Authentication successful');
// Subscribe to project updates
socket.send(JSON.stringify({
type: 'subscribe',
channel: 'projects.updates',
project_id: PROJECT_ID,
request_id: 'req_project_updates'
}));
break;
case 'subscription_success':
console.log(`Subscribed to ${data.channel}`);
if (data.request_id === 'req_project_updates') {
projectSubscriptionId = data.subscription_id;
}
break;
case 'event':
if (data.channel === 'projects.updates') {
handleProjectUpdate(data);
}
break;
case 'error':
console.error(`Error: ${data.message}`);
break;
}
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
socket.onclose = (event) => {
console.log(`WebSocket connection closed: ${event.code} - ${event.reason}`);
};
// Handle project update events
function handleProjectUpdate(data) {
const { event, data: eventData } = data;
switch (event) {
case 'project.updated':
console.log(`Project updated: ${eventData.name}`);
updateProjectUI(eventData);
break;
case 'project.member_added':
console.log(`New member added: ${eventData.user.name}`);
updateMembersList(eventData);
break;
case 'project.deleted':
console.log(`Project deleted`);
redirectToProjectsList();
break;
}
}
// Clean up function
function cleanup() {
if (projectSubscriptionId) {
socket.send(JSON.stringify({
type: 'unsubscribe',
subscription_id: projectSubscriptionId
}));
}
socket.close();
}
import json
import websocket
import threading
import time
API_KEY = 'your_api_key_here'
PROJECT_ID = 'proj_12345'
# Create WebSocket client
class ProjectUpdatesClient:
def __init__(self):
self.subscription_id = None
self.ws = None
self.connect()
def connect(self):
self.ws = websocket.WebSocketApp(
'wss://ws.myappapi.com/v1/',
on_open=self.on_open,
on_message=self.on_message,
on_error=self.on_error,
on_close=self.on_close
)
# Start WebSocket connection in a separate thread
self.ws_thread = threading.Thread(target=self.ws.run_forever)
self.ws_thread.daemon = True
self.ws_thread.start()
def on_open(self, ws):
print('WebSocket connection established')
# Authenticate
self.ws.send(json.dumps({
'type': 'auth',
'api_key': API_KEY
}))
def on_message(self, ws, message):
data = json.loads(message)
print('Received message:', data)
if data['type'] == 'auth_success':
print('Authentication successful')
# Subscribe to project updates
self.ws.send(json.dumps({
'type': 'subscribe',
'channel': 'projects.updates',
'project_id': PROJECT_ID,
'request_id': 'req_project_updates'
}))
elif data['type'] == 'subscription_success':
print(f"Subscribed to {data['channel']}")
if data['request_id'] == 'req_project_updates':
self.subscription_id = data['subscription_id']
elif data['type'] == 'event':
if data['channel'] == 'projects.updates':
self.handle_project_update(data)
elif data['type'] == 'error':
print(f"Error: {data['message']}")
def on_error(self, ws, error):
print('WebSocket error:', error)
def on_close(self, ws, close_status_code, close_reason):
print(f"WebSocket connection closed: {close_status_code} - {close_reason}")
def handle_project_update(self, data):
event = data['event']
event_data = data['data']
if event == 'project.updated':
print(f"Project updated: {event_data['name']}")
self.update_project_ui(event_data)
elif event == 'project.member_added':
print(f"New member added: {event_data['user']['name']}")
self.update_members_list(event_data)
elif event == 'project.deleted':
print("Project deleted")
self.redirect_to_projects_list()
def update_project_ui(self, data):
# Update UI with new project data
pass
def update_members_list(self, data):
# Update UI with new member
pass
def redirect_to_projects_list(self):
# Handle project deletion
pass
def cleanup(self):
if self.subscription_id:
self.ws.send(json.dumps({
'type': 'unsubscribe',
'subscription_id': self.subscription_id
}))
self.ws.close()
# Create and start client
client = ProjectUpdatesClient()
# Keep main thread running
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
client.cleanup()
Real-Time Chat Application
This example demonstrates how to implement a real-time chat using WebSockets:
class ChatClient {
constructor(apiKey, roomId) {
this.apiKey = apiKey;
this.roomId = roomId;
this.socket = null;
this.subscriptionId = null;
this.messageCallbacks = [];
this.userStatusCallbacks = [];
this.connectionState = 'disconnected';
this.connect();
}
connect() {
this.connectionState = 'connecting';
this.socket = new WebSocket('wss://ws.myappapi.com/v1/');
this.socket.onopen = () => {
console.log('Chat WebSocket connection established');
this.connectionState = 'authenticating';
// Authenticate
this.socket.send(JSON.stringify({
type: 'auth',
api_key: this.apiKey
}));
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'auth_success':
this.connectionState = 'authenticated';
this.subscribeToChat();
break;
case 'subscription_success':
if (data.channel === 'chat.messages') {
this.connectionState = 'connected';
this.subscriptionId = data.subscription_id;
this.notifyConnectionStatus(true);
}
break;
case 'event':
if (data.channel === 'chat.messages') {
if (data.event === 'chat.message_received') {
this.notifyNewMessage(data.data);
} else if (data.event === 'chat.user_presence_changed') {
this.notifyUserStatus(data.data);
}
}
break;
case 'error':
console.error(`Chat WebSocket error: ${data.message}`);
this.notifyError(data);
break;
}
};
this.socket.onerror = (error) => {
console.error('Chat WebSocket error:', error);
this.connectionState = 'error';
this.notifyConnectionStatus(false, 'error');
};
this.socket.onclose = (event) => {
console.log(`Chat WebSocket closed: ${event.code} - ${event.reason}`);
this.connectionState = 'disconnected';
this.notifyConnectionStatus(false, 'closed');
// Attempt to reconnect after delay
setTimeout(() => {
if (this.connectionState === 'disconnected') {
this.connect();
}
}, 5000);
};
}
subscribeToChat() {
this.socket.send(JSON.stringify({
type: 'subscribe',
channel: 'chat.messages',
room_id: this.roomId,
request_id: `chat_${this.roomId}`
}));
}
sendMessage(message) {
if (this.connectionState !== 'connected') {
throw new Error('Not connected to chat');
}
// Send message via REST API
return fetch('https://api.myappapi.com/v1/chats/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.apiKey
},
body: JSON.stringify({
room_id: this.roomId,
content: message
})
}).then(response => response.json());
}
onMessage(callback) {
this.messageCallbacks.push(callback);
return this;
}
onUserStatus(callback) {
this.userStatusCallbacks.push(callback);
return this;
}
onConnectionChange(callback) {
this.connectionCallbacks.push(callback);
return this;
}
notifyNewMessage(message) {
this.messageCallbacks.forEach(callback => callback(message));
}
notifyUserStatus(status) {
this.userStatusCallbacks.forEach(callback => callback(status));
}
notifyConnectionStatus(connected, reason) {
this.connectionCallbacks.forEach(callback => callback(connected, reason));
}
notifyError(error) {
this.errorCallbacks.forEach(callback => callback(error));
}
disconnect() {
if (this.subscriptionId) {
this.socket.send(JSON.stringify({
type: 'unsubscribe',
subscription_id: this.subscriptionId
}));
}
this.socket.close(1000, 'User disconnected');
this.connectionState = 'disconnected';
}
}
// Usage example
const chatClient = new ChatClient('your_api_key_here', 'room_12345');
chatClient
.onMessage(message => {
console.log(`New message from ${message.user.name}: ${message.content}`);
// Add message to UI
addMessageToUI(message);
})
.onUserStatus(status => {
console.log(`User ${status.user.name} is now ${status.status}`);
// Update user status in UI
updateUserStatusUI(status);
});
// Send a message
document.getElementById('send-button').addEventListener('click', () => {
const messageInput = document.getElementById('message-input');
const message = messageInput.value.trim();
if (message) {
chatClient.sendMessage(message)
.then(() => {
messageInput.value = '';
})
.catch(error => {
console.error('Error sending message:', error);
});
}
});
// Disconnect when leaving the page
window.addEventListener('beforeunload', () => {
chatClient.disconnect();
});