Syarah Chat API

WebSocket Documentation

v0.0.1

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:

  1. Upload file using POST /api/chat/uploadFile
  2. Receive file path from response (e.g., "/uploads/chat-messages/images/abc123.webp")
  3. Send createMessage event with file path in message field

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 joinRoom or have called leaveRoom)

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 leaveRoom to 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.
});