Getting Started
Welcome to the Syarah Chat API documentation. This guide will help you integrate real-time chat functionality into your application using WebSocket connections.
Server URL: Connect to ws://localhost:3005 (or your server URL)
Connection Setup
To connect to the chat server, you need to establish a WebSocket connection using Socket.IO client library. Choose your platform below:
Installation
npm install socket.io-client
# or
yarn add socket.io-client
Basic Connection
import { io } from 'socket.io-client';
// Connect to the server with userId as query parameter
const socket = io('http://localhost:3005', {
query: {
userId: 'your-user-id-here' // Use GUID format
},
transports: ['websocket', 'polling']
});
// Connection event handlers
socket.on('connect', () => {
console.log('Connected to chat server:', socket.id);
});
socket.on('disconnect', () => {
console.log('Disconnected from chat server');
});
socket.on('connect_error', (error) => {
console.error('Connection error:', error);
});
Important: The userId query parameter is required for connection. The server uses this to identify and manage user connections. Use GUID format (e.g., "550e8400-e29b-41d4-a716-446655440000").
Client Events (Client → Server)
These are events that your client application sends to the server.
REST API Endpoints
These REST API endpoints work alongside the WebSocket events. Use them for file uploads and other HTTP operations.
POST /api/chat/uploadFile
Upload a file (image, video, audio, or document) for use in chat messages. Returns the file path that should be sent in the message field of the createMessage event.
Request
POST /api/chat/uploadFile
Content-Type: multipart/form-data
Authorization: Bearer {accessToken}
Form Data:
- file: [File] (required) - The file to upload
- messageType: number (required) - Message type:
* 1 = Audio
* 2 = Image
* 3 = File/Document
* 4 = Video
Response
{
"success": true,
"data": "/uploads/chat-messages/images/abc123.webp",
"message": null
}
JavaScript Example
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('messageType', 2); // 2 = Image
const response = await fetch('https://your-api.com/api/chat/uploadFile', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
},
body: formData
});
const result = await response.json();
const filePath = result.data; // Use this in createMessage event
File Type Handling
- Image (messageType: 2): Automatically converted to WebP format, max 1920x1920px
- Video (messageType: 4): Max size 500MB, formats: mp4, avi, mov, wmv, flv, webm, mkv
- Audio (messageType: 1): Max size 50MB, formats: mp3, wav, ogg, m4a, aac, flac, wma
- File (messageType: 3): Max size 50MB, all file types allowed
createMessage
Send a new message to a chat room. The backend automatically sets the UserId from the access token. For file messages (images, videos, audio), upload the file first using the REST API endpoint, then send the returned file path in the message field.
Parameters
{
message: string; // Message content (text) OR file path (for media)
chatHeaderId: number; // Chat room ID
accessToken: string; // Authentication token (Bearer token)
messageType: number; // Type of message:
// 0 = Text
// 1 = Audio
// 2 = Image
// 3 = File/Document
// 4 = Video
caption?: string; // Optional caption for media messages
}
📎 File Upload Flow: For images, videos, audio, or files:
- Upload file using
POST /api/chat/uploadFile - Receive file path from response (e.g.,
"/uploads/chat-messages/images/abc123.webp") - Send
createMessageevent with file path inmessagefield
Text Message Example
socket.emit('createMessage', {
message: 'Hello, world!',
chatHeaderId: 123,
accessToken: 'your-bearer-token-here',
messageType: 0, // 0 = Text
caption: ''
});
📱 Push Notifications: When you send a message via createMessage, the backend automatically sends FCM push notifications to:
- Offline members (users not connected via WebSocket)
- Online members who are not currently viewing this chat (have not called
joinRoomor have calledleaveRoom)
Online members who are viewing the chat will receive the message via WebSocket and won't get a push notification.
Image Message Example (with file upload)
// Step 1: Upload file
const formData = new FormData();
formData.append('file', imageFile);
formData.append('messageType', 2); // Image
const uploadResponse = await fetch('/api/chat/uploadFile', {
method: 'POST',
headers: { 'Authorization': `Bearer ${accessToken}` },
body: formData
});
const uploadResult = await uploadResponse.json();
const filePath = uploadResult.data; // e.g., "/uploads/chat-messages/images/abc123.webp"
// Step 2: Send message with file path
socket.emit('createMessage', {
message: filePath, // Use the uploaded file path
chatHeaderId: 123,
accessToken: 'your-bearer-token-here',
messageType: 2, // 2 = Image
caption: 'Check out this image!' // Optional caption
});
Video Message Example
// Upload video first
const formData = new FormData();
formData.append('file', videoFile);
formData.append('messageType', 4); // Video
const uploadResponse = await fetch('/api/chat/uploadFile', {
method: 'POST',
headers: { 'Authorization': `Bearer ${accessToken}` },
body: formData
});
const uploadResult = await uploadResponse.json();
// Send message with video path
socket.emit('createMessage', {
message: uploadResult.data,
chatHeaderId: 123,
accessToken: 'your-bearer-token-here',
messageType: 4, // 4 = Video
caption: 'Watch this video!'
});
Flutter Example (Dart)
// Upload file first
var request = http.MultipartRequest(
'POST',
Uri.parse('https://your-api.com/api/chat/uploadFile')
);
request.headers['Authorization'] = 'Bearer $accessToken';
request.files.add(await http.MultipartFile.fromPath('file', filePath));
request.fields['messageType'] = '2'; // Image
var uploadResponse = await request.send();
var uploadResult = jsonDecode(await uploadResponse.stream.bytesToString());
// Send message with file path
socket.emit('createMessage', {
'message': uploadResult['data'],
'chatHeaderId': 123,
'accessToken': 'your-bearer-token-here',
'messageType': 2,
'caption': 'Check out this image!'
});
Note: The backend automatically extracts the UserId from the access token, so you don't need to send it. For text messages, use messageType: 0 and put your text in the message field. For media messages, upload the file first, then use the returned file path.
findAllMessages
Retrieve all messages from a chat room.
Parameters
{
chatHeaderId: string; // Chat room ID
accessToken: string; // Authentication token
lastMessageId?: number; // Optional: Last message ID for pagination
skipCount?: number; // Optional: Number of messages to skip
maxResultCount?: number; // Optional: Maximum number of results
LastMessageId?: number; // Optional: Alternative last message ID
}
Example
socket.emit('findAllMessages', {
chatHeaderId: '123',
accessToken: 'your-access-token',
skipCount: 0,
maxResultCount: 50
}, (response) => {
console.log('Messages:', response);
});
findAllChatHeaders
Retrieve all chat conversations for a user.
Parameters
{
AccessToken: string; // Authentication token
UserId?: string; // Optional: User ID
userId?: string; // Optional: Alternative user ID
RoomsWithLastMessageIds?: Array<{ // Optional: Rooms with last message IDs
ChatHeaderId: number;
LastMessageId: number;
}>;
SkipCount?: number; // Optional: Number to skip
MaxResultCount?: number; // Optional: Maximum results
}
Example
socket.emit('findAllChatHeaders', {
AccessToken: 'your-access-token',
UserId: 'user-123',
SkipCount: 0,
MaxResultCount: 20
}, (response) => {
console.log('Chat headers:', response);
});
joinRoom
Join a chat room. The server will automatically handle socket room management. When you join a room, your IsViewingChat status is set to true for that chat, which prevents FCM push notifications from being sent to you while you're viewing it.
✨ New Feature: You can now provide otherUserId instead of chatHeaderId to automatically get or create a chat header!
💡 Important: When you join a room, you're marked as "viewing" that chat. This means:
- You'll receive real-time messages via WebSocket
- You won't receive FCM push notifications for messages in this chat
- When you leave the chat UI, call
leaveRoomto stop receiving WebSocket messages and resume FCM notifications
Parameters
{
chatHeaderId?: number; // Chat room ID (optional - use if you already have it)
userId: string; // Your User ID
socketId: string; // Socket ID (usually auto-filled)
accessToken: string; // Authentication token
otherUserId?: string; // Other user's ID (optional - use to auto-create/get chat header)
}
📝 Note: Provide either chatHeaderId OR otherUserId. If you provide otherUserId, the system will automatically get or create a chat header between you and that user.
💡 Use Case: When chatHeaderId is null in getMyFriendships response, use joinRoom with otherUserId to create/get the chat header. The response will contain the chatHeaderId that you can use for future operations.
Example 1: With chatHeaderId (existing way)
socket.emit('joinRoom', {
chatHeaderId: 123,
userId: 'user-456',
socketId: socket.id,
accessToken: 'your-access-token'
}, (response) => {
console.log('Joined room:', response);
});
Example 2: With otherUserId (auto-create/get chat header)
socket.emit('joinRoom', {
userId: 'your-user-id', // Optional: auto-extracted from accessToken if connected
otherUserId: 'other-user-id', // System will auto-create/get chat header
socketId: socket.id, // Optional: usually auto-filled
accessToken: 'your-access-token' // Optional: auto-used from connection if connected
}, (response) => {
if (response.error) {
console.error('Error:', response.error);
return;
}
// Response contains chatHeaderId
const chatHeaderId = response.chatHeaderId || response.chatHeader?.id;
console.log('Chat Header ID:', chatHeaderId);
console.log('Joined room:', response);
// Store chatHeaderId for future use
// Now you can send messages, etc.
});
Response Structure
{
chatHeaderId: 123, // The chat header ID (use this for future operations!)
joined: true, // Whether successfully joined
roomMembers: [...], // List of room members
currentMember: {...}, // Your member info
members: [...], // Alternative field name for members
friendshipStatus: 1 // Friendship status (0=Pending, 1=Accepted, 2=Rejected, 3=Blocked, null if no friendship exists) - only for individual chats
}
Important: When using otherUserId, the joinRoom event will:
- Check if a chat header exists between you and the other user
- Create a new chat header if none exists
- Return the existing chat header ID if one already exists
- Automatically join you to the socket room for real-time messaging
Friendship Status: The friendshipStatus field indicates the relationship status between you and the other user in individual chats:
0= Pending (friend request sent, awaiting response)1= Accepted (friends)2= Rejected (friend request was rejected)3= Blocked (user is blocked)null= No friendship exists
leaveRoom
Leave a chat room. This sets your IsViewingChat status to false for that chat, which means you'll start receiving FCM push notifications for new messages in that chat again.
⚠️ Important: Call this event when the user navigates away from the chat UI or closes the chat window. This ensures proper notification behavior.
Parameters
{
chatHeaderId: number; // The chat room ID to leave
accessToken?: string; // Optional: auto-extracted from connection if not provided
}
Example
socket.emit('leaveRoom', {
chatHeaderId: 123,
accessToken: 'your-access-token' // Optional if connected with token
}, (response) => {
if (response.error) {
console.error('Error leaving room:', response.error);
} else {
console.log('Left room successfully');
}
});
When to Call
- When user navigates away from the chat screen
- When user closes the chat window/tab
- When user switches to a different chat
- When the app goes to background (mobile apps)
✅ Best Practice: Always call leaveRoom when leaving a chat UI to ensure proper notification behavior. The system will automatically clear viewing status on disconnect, but it's better to be explicit.
roomTyping
Notify other users in a chat room that you are typing.
Parameters
{
isTyping: boolean; // true when typing starts, false when stops
name: string; // Username of the person typing
chatHeaderId: number; // Chat room ID
}
Example
// User starts typing
socket.emit('roomTyping', {
isTyping: true,
name: 'John Doe',
chatHeaderId: 123
});
// User stops typing
socket.emit('roomTyping', {
isTyping: false,
name: 'John Doe',
chatHeaderId: 123
});
updateMessage
Update an existing message in a chat room.
Web Example
socket.emit('updateMessage', {
id: 789,
message: 'Updated message content',
chatHeaderId: 123,
accessToken: 'your-bearer-token-here',
caption: '' // Optional
});
Flutter Example
socket.emit('updateMessage', {
'id': 789,
'message': 'Updated message content',
'chatHeaderId': 123,
'accessToken': 'your-bearer-token-here',
'caption': '', // Optional
});
removeMessage
Delete one or multiple messages from a chat room.
Parameters
{
id?: number; // Single message ID (for backward compatibility)
ids?: number[]; // Array of message IDs (for multiple deletion)
chatHeaderId: number; // Chat room ID
accessToken: string; // Authentication token
}
Note: You can use either id (single message) or ids (multiple messages). If both are provided, ids takes precedence.
Single Message Deletion (Web Example)
socket.emit('removeMessage', {
id: 789,
chatHeaderId: 123,
accessToken: 'your-bearer-token-here'
});
Multiple Messages Deletion (Web Example)
socket.emit('removeMessage', {
ids: [789, 790, 791],
chatHeaderId: 123,
accessToken: 'your-bearer-token-here'
});
Single Message Deletion (Flutter Example)
socket.emit('removeMessage', {
'id': 789,
'chatHeaderId': 123,
'accessToken': 'your-bearer-token-here'
});
Multiple Messages Deletion (Flutter Example)
socket.emit('removeMessage', {
'ids': [789, 790, 791],
'chatHeaderId': 123,
'accessToken': 'your-bearer-token-here'
});
readMessage
Mark messages in a chat room as read.
Parameters
{
chatHeaderId: number; // Chat room ID
}
Example
socket.emit('readMessage', {
chatHeaderId: 123
});
getMyFriendships
Get all accepted friendships for the current user with online status. Returns user ID, name, avatar, and whether the friend is currently online.
✨ New Feature: This event returns friendships with real-time online status!
Parameters
{
accessToken?: string; // Optional: Authentication token (if not provided, uses token from connection)
}
Response
[
{
userId: "550e8400-e29b-41d4-a716-446655440000", // Friend's user ID (GUID)
name: "John Doe", // Friend's full name
avatar: "https://example.com/avatar.jpg", // Friend's avatar URL (nullable)
isOnline: true, // Whether friend is currently online
chatHeaderId: 123 // Chat header ID if exists, null otherwise
},
// ... more friends
]
Web Example
socket.emit('getMyFriendships', {
accessToken: 'your-access-token' // Optional if connected with accessToken
}, (response) => {
console.log('My friendships:', response);
response.forEach(friend => {
console.log(`${friend.name} is ${friend.isOnline ? 'online' : 'offline'}`);
});
});
Flutter Example
socket.emitWithAck('getMyFriendships', {
'accessToken': 'your-access-token' // Optional if connected with accessToken
}, ack: (response) {
print('My friendships: $response');
for (var friend in response) {
print('${friend['name']} is ${friend['isOnline'] ? 'online' : 'offline'}');
}
});
Note: The accessToken parameter is optional. If you connected to the socket with an accessToken (using the auth object), the server will automatically use that token. Online status is determined by whether the friend has an active socket connection.
Handling Null chatHeaderId
Important: If chatHeaderId is null, it means no chat exists yet between you and that friend. To create or get a chat header, use the joinRoom event with otherUserId (see joinRoom documentation).
Example: Creating Chat When chatHeaderId is Null
socket.emit('getMyFriendships', {
accessToken: 'your-access-token'
}, (friendships) => {
friendships.forEach(friend => {
if (!friend.chatHeaderId) {
// No chat exists yet - create/get chat header using joinRoom
socket.emit('joinRoom', {
otherUserId: friend.userId, // Use friend's userId
accessToken: 'your-access-token'
}, (joinResponse) => {
if (joinResponse.chatHeaderId) {
// Chat header created/retrieved successfully
friend.chatHeaderId = joinResponse.chatHeaderId;
console.log(`Chat created with ${friend.name}: ${joinResponse.chatHeaderId}`);
// Now you can send messages, join the room, etc.
// The joinRoom event also automatically joins you to the socket room
} else if (joinResponse.error) {
console.error('Failed to create chat:', joinResponse.error);
}
});
} else {
// Chat already exists - use existing chatHeaderId
console.log(`Existing chat with ${friend.name}: ${friend.chatHeaderId}`);
}
});
});
Alternative: REST API Approach
If you prefer using REST API instead of socket events:
// Using fetch
const response = await fetch(
`${BACKEND_URL}api/chat/getChatHeaderAsync?user1Id=${yourUserId}&user2Id=${friend.userId}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
);
const data = await response.json();
const chatHeaderId = data.data?.id; // Chat header ID
// Note: This only gets/creates the chat header, it doesn't join the socket room
// You'll still need to call joinRoom if you want real-time messaging
getMyChats
Get all individual chats for the current user with other user info, last message, creation time, and online status. Returns chat header ID, user details, last message, and whether the other user is online.
✨ New Feature: This event returns chats with real-time online status and last message info!
Parameters
{
accessToken?: string; // Optional: Authentication token (if not provided, uses token from connection)
}
Response
[
{
chatHeaderId: 123, // Chat header ID
userId: "550e8400-e29b-41d4-a716-446655440000", // Other user's ID (GUID)
userName: "Jane Smith", // Other user's full name
userAvatar: "https://example.com/avatar.jpg", // Other user's avatar URL (nullable)
lastMessage: "Hello, how are you?", // Last message content (nullable)
lastMessageTime: "2024-01-15T10:30:00Z", // Last message timestamp (nullable)
creationTime: "2024-01-10T08:00:00Z", // Chat creation timestamp
isOnline: true, // Whether other user is currently online
unreadCount: 3, // Number of unread messages
friendshipStatus: 1 // Friendship status (0=Pending, 1=Accepted, 2=Rejected, 3=Blocked, null if no friendship exists)
},
// ... more chats
]
Web Example
socket.emit('getMyChats', {
accessToken: 'your-access-token' // Optional if connected with accessToken
}, (response) => {
console.log('My chats:', response);
response.forEach(chat => {
console.log(`Chat with ${chat.userName} (${chat.isOnline ? 'online' : 'offline'})`);
console.log(`Last message: ${chat.lastMessage || 'No messages yet'}`);
console.log(`Unread: ${chat.unreadCount}`);
console.log(`Friendship status: ${chat.friendshipStatus ?? 'No friendship'}`);
});
});
Flutter Example
socket.emitWithAck('getMyChats', {
'accessToken': 'your-access-token' // Optional if connected with accessToken
}, ack: (response) {
print('My chats: $response');
for (var chat in response) {
print('Chat with ${chat['userName']} (${chat['isOnline'] ? 'online' : 'offline'})');
print('Last message: ${chat['lastMessage'] ?? 'No messages yet'}');
print('Unread: ${chat['unreadCount']}');
print('Friendship status: ${chat['friendshipStatus'] ?? 'No friendship'}');
}
});
Note: The accessToken parameter is optional. If you connected to the socket with an accessToken (using the auth object), the server will automatically use that token. This endpoint only returns individual chats (one-on-one conversations), not group chats. Results are sorted by last message time (most recent first). Online status is determined by whether the other user has an active socket connection.
Friendship Status: Each chat includes friendshipStatus indicating the relationship between you and the other user:
0= Pending (friend request sent, awaiting response)1= Accepted (friends)2= Rejected (friend request was rejected)3= Blocked (user is blocked)null= No friendship exists
Server Events (Server → Client)
These are events that the server emits to your client application. You need to listen for these events.
clientStatus
Emitted when a user connects or disconnects from the chat server.
Event Data
{
userId: string; // User/client ID
clientType: string; // Type of client
activeNow: boolean; // true if online, false if offline
}
Example
socket.on('clientStatus', (data) => {
if (data.activeNow) {
console.log(`User ${data.userId} is now online`);
} else {
console.log(`User ${data.userId} is now offline`);
}
});
message
Emitted when a new message is received in a chat room you're part of.
Example
socket.on('message', (messageData) => {
console.log('New message received:', messageData);
// messageData.chatMessage contains the message details
// Update your UI with the new message
});
onMemberJoin
Emitted when a new member joins a chat room you're part of.
Example
socket.on('onMemberJoin', (memberData) => {
console.log('New member joined:', memberData);
// memberData contains user information
// Show notification or update member list
});
typing
Emitted when someone in a chat room is typing.
Event Data
{
name: string; // Username of person typing
isTyping: boolean; // Typing status
chatHeaderId: number; // Chat room ID
}
Example
socket.on('typing', (data) => {
if (data.isTyping) {
console.log(`${data.name} is typing...`);
// Show typing indicator in UI
} else {
// Hide typing indicator
}
});
messageUpdated
Emitted when a message in a chat room is updated.
Example
socket.on('messageUpdated', (updatedMessage) => {
console.log('Message updated:', updatedMessage);
// Update the message in your UI
});
messageDeleted
Emitted when a message is deleted from a chat room. When multiple messages are deleted, this event is emitted once for each deleted message.
Event Data
messageId: number // ID of the deleted message
Example
socket.on('messageDeleted', (messageId) => {
console.log('Message deleted:', messageId);
// Remove the message from your UI
// Note: When deleting multiple messages, this handler
// will be called once for each deleted message ID
});
Important: When multiple messages are deleted using the ids parameter, the messageDeleted event will be emitted multiple times (once per deleted message). Make sure your handler can handle multiple rapid events.
messageHasBeenRead
Emitted when messages in a chat room have been marked as read.
Event Data
chatHeaderId: number // Chat room ID
Example
socket.on('messageHasBeenRead', (chatHeaderId) => {
console.log('Messages marked as read in room:', chatHeaderId);
// Update read status indicators in your UI
});
Data Transfer Objects (DTOs)
Reference for all data structures used in the API.
CreateMessageDto
class CreateMessageDto {
message: string; // Message content (text) OR file path (for media)
// - For text: plain text string
// - For media: file path from uploadFile API
// (e.g., "/uploads/chat-messages/images/abc123.webp")
chatHeaderId: number; // Chat room ID
accessToken: string; // Authentication token (Bearer token)
messageType: number; // Type of message:
// 0 = Text
// 1 = Audio
// 2 = Image
// 3 = File/Document
// 4 = Video
caption?: string; // Optional caption for media messages
}
// Note: UserId is automatically set by backend from access token
// For media messages, upload file first using POST /api/chat/uploadFile,
// then use the returned file path in the message field
File Upload: For images, videos, audio, or files, you must upload the file first using the POST /api/chat/uploadFile endpoint, then use the returned file path in the message field.
UpdateMessageDto
class UpdateMessageDto {
id: number; // Message ID to update
message: string; // Updated message content
chatHeaderId: number; // Chat room ID
accessToken: string; // Authentication token (Bearer token)
caption?: string; // Optional caption for media messages
}
DeleteMessageDto
class DeleteMessageDto {
id?: number; // Single message ID (for backward compatibility)
ids?: number[]; // Array of message IDs (for multiple deletion)
chatHeaderId: number; // Chat room ID (for broadcasting)
accessToken: string; // Authentication token (Bearer token)
}
Note: Use id for single message deletion or ids for multiple message deletion. If both are provided, ids takes precedence.
GetAllChatMessages
class GetAllChatMessages {
chatHeaderId: string; // Chat room ID
accessToken: string; // Authentication token
lastMessageId?: number; // Optional: Last message ID
skipCount?: number; // Optional: Number to skip
maxResultCount?: number; // Optional: Maximum results
LastMessageId?: number; // Optional: Alternative last message ID
}
GetAllChatHeaders
class GetAllChatHeaders {
AccessToken: string; // Authentication token
UserId?: string; // Optional: User ID
userId?: string; // Optional: Alternative user ID
RoomsWithLastMessageIds?: Array<{ // Optional: Rooms with last message IDs
ChatHeaderId: number;
LastMessageId: number;
}>;
SkipCount?: number; // Optional: Number to skip
MaxResultCount?: number; // Optional: Maximum results
}
JoinRoomInput
class JoinRoomInput {
chatHeaderId?: number; // Chat room ID (optional - use if you already have it)
userId: string; // Your User ID
socketId: string; // Socket ID
accessToken: string; // Authentication token
otherUserId?: string; // Other user's ID (optional - use to auto-create/get chat header)
}
📝 Note: Provide either chatHeaderId OR otherUserId. If you provide otherUserId, the system will automatically get or create a chat header between you and that user.
GetMyFriendshipsInput
class GetMyFriendshipsInput {
accessToken?: string; // Optional: Authentication token (if not provided, uses token from connection)
}
📝 Note: The accessToken is optional if you connected to the socket with an accessToken. The response includes online status for each friend.
GetMyChatsInput
class GetMyChatsInput {
accessToken?: string; // Optional: Authentication token (if not provided, uses token from connection)
}
📝 Note: The accessToken is optional if you connected to the socket with an accessToken. Returns only individual chats (one-on-one), sorted by last message time (most recent first).
Complete Code Examples
Full Chat Integration Example (JavaScript/TypeScript)
import { io } from 'socket.io-client';
class ChatClient {
constructor(serverUrl, userId, accessToken) {
this.userId = userId;
this.accessToken = accessToken;
this.socket = io(serverUrl, {
query: { userId }
});
this.setupEventListeners();
}
setupEventListeners() {
// Connection events
this.socket.on('connect', () => {
console.log('Connected:', this.socket.id);
});
this.socket.on('disconnect', () => {
console.log('Disconnected');
});
// Message events
this.socket.on('message', (data) => {
console.log('New message:', data);
this.onMessageReceived(data);
});
this.socket.on('typing', (data) => {
console.log(`${data.name} is typing`);
this.onTyping(data);
});
this.socket.on('clientStatus', (data) => {
console.log('User status:', data);
this.onUserStatusChanged(data);
});
this.socket.on('onMemberJoin', (member) => {
console.log('Member joined:', member);
this.onMemberJoined(member);
});
this.socket.on('messageUpdated', (message) => {
console.log('Message updated:', message);
this.onMessageUpdated(message);
});
this.socket.on('messageDeleted', (messageId) => {
console.log('Message deleted:', messageId);
this.onMessageDeleted(messageId);
});
this.socket.on('messageHasBeenRead', (chatHeaderId) => {
console.log('Messages read:', chatHeaderId);
this.onMessagesRead(chatHeaderId);
});
}
// Join a chat room
// Option 1: With chatHeaderId (if you already have it)
joinRoom(chatHeaderId) {
this.socket.emit('joinRoom', {
chatHeaderId,
userId: this.userId,
socketId: this.socket.id,
accessToken: this.accessToken
}, (response) => {
console.log('Joined room:', response);
});
}
// Option 2: With otherUserId (auto-create/get chat header)
joinRoomWithUser(otherUserId, callback) {
this.socket.emit('joinRoom', {
userId: this.userId,
otherUserId: otherUserId,
socketId: this.socket.id,
accessToken: this.accessToken
}, (response) => {
if (response.error) {
console.error('Error joining room:', response.error);
if (callback) callback(null, response.error);
return;
}
const chatHeaderId = response.chatHeaderId || response.chatHeader?.id;
console.log('Joined room:', response);
console.log('Chat Header ID:', chatHeaderId);
// Store chatHeaderId for future use
if (callback) callback(response, null);
});
}
// Send a message
sendMessage(chatHeaderId, message, messageType = 1, caption = '') {
this.socket.emit('createMessage', {
message,
chatHeaderId,
accessToken: this.accessToken,
messageType,
caption: caption
});
}
// Get all messages
getAllMessages(chatHeaderId, skipCount = 0, maxResultCount = 50) {
this.socket.emit('findAllMessages', {
chatHeaderId: chatHeaderId.toString(),
accessToken: this.accessToken,
skipCount,
maxResultCount
}, (response) => {
console.log('Messages:', response);
return response;
});
}
// Get all chat headers
getAllChatHeaders(skipCount = 0, maxResultCount = 20) {
this.socket.emit('findAllChatHeaders', {
AccessToken: this.accessToken,
UserId: this.userId,
SkipCount: skipCount,
MaxResultCount: maxResultCount
}, (response) => {
console.log('Chat headers:', response);
return response;
});
}
// Get friendships and handle null chatHeaderId
getFriendshipsAndCreateChats(callback) {
this.socket.emit('getMyFriendships', {
accessToken: this.accessToken
}, (friendships) => {
const pendingChats = [];
friendships.forEach(friend => {
if (!friend.chatHeaderId) {
// Create/get chat header for friends without chat
pendingChats.push(
new Promise((resolve) => {
this.joinRoomWithUser(friend.userId, (response, error) => {
if (!error && response?.chatHeaderId) {
friend.chatHeaderId = response.chatHeaderId;
}
resolve(friend);
});
})
);
}
});
Promise.all(pendingChats).then(() => {
if (callback) callback(friendships);
});
});
}
// Typing indicator
setTyping(chatHeaderId, isTyping, userName) {
this.socket.emit('roomTyping', {
isTyping,
name: userName,
chatHeaderId
});
}
// Update a message
updateMessage(messageId, chatHeaderId, newMessage, caption = '') {
this.socket.emit('updateMessage', {
id: messageId,
message: newMessage,
chatHeaderId,
accessToken: this.accessToken,
caption: caption
});
}
// Delete a message
deleteMessage(messageId, chatHeaderId) {
this.socket.emit('removeMessage', {
id: messageId,
chatHeaderId,
accessToken: this.accessToken
});
}
// Mark messages as read
markAsRead(chatHeaderId) {
this.socket.emit('readMessage', {
chatHeaderId
});
}
// Event handlers (override these in your implementation)
onMessageReceived(data) {}
onTyping(data) {}
onUserStatusChanged(data) {}
onMemberJoined(member) {}
onMessageUpdated(message) {}
onMessageDeleted(messageId) {}
onMessagesRead(chatHeaderId) {}
// Disconnect
disconnect() {
this.socket.disconnect();
}
}
// Usage example
const chat = new ChatClient('http://localhost:3005', 'user-123', 'your-access-token');
// Option 1: Join with existing chatHeaderId
chat.joinRoom(456);
// Option 2: Join by providing otherUserId (auto-creates/gets chat header)
chat.joinRoomWithUser('other-user-789', (response, error) => {
if (!error && response?.chatHeaderId) {
const chatHeaderId = response.chatHeaderId;
console.log('Chat Header ID:', chatHeaderId);
// Store chatHeaderId for future use
}
});
// After joining, you can get the chatHeaderId from the response
// Example: Handling null chatHeaderId from getMyFriendships
chat.getFriendshipsAndCreateChats((friendships) => {
console.log('All friendships with chat headers:', friendships);
// Now all friendships have chatHeaderId (either existing or newly created)
});
chat.sendMessage(456, 'Hello, world!');
Best Practices
Connection Management
- Always handle connection and disconnection events
- Implement automatic reconnection logic
- Store the socket instance in a way that's accessible throughout your application
- Clean up event listeners when disconnecting
Error Handling
- Always listen for 'connect_error' events
- Implement retry logic for failed operations
- Validate data before emitting events
- Handle server errors gracefully
Performance
- Use pagination when fetching messages (skipCount, maxResultCount)
- Debounce typing indicators to avoid excessive events
- Cache chat headers and messages locally when possible
- Only join rooms that are currently active
Security
- Never expose access tokens in client-side code (use environment variables)
- Validate all user inputs before sending to server
- Implement proper authentication checks
- Use HTTPS/WSS in production
Reconnection Strategy
const socket = io('http://localhost:3005', {
query: { userId: 'user-123' },
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: Infinity
});
socket.on('reconnect', (attemptNumber) => {
console.log('Reconnected after', attemptNumber, 'attempts');
// Rejoin rooms, resync state, etc.
});