Skip to main content

Messaging Feature

Real-time messaging system with SignalR (WebSocket) and REST. The backend uses api/Chat for conversations, messages, and groups; api/Call for calls; api/StrangerChat for stranger chat. Real-time delivery is handled by the ChatWorker (SignalR hub).

Overview

The messaging feature enables:

  • Real-time one-on-one conversations
  • Group conversations with multiple participants
  • Media sharing (images, videos, files)
  • Message status tracking (sent, delivered, read)
  • Typing indicators
  • Message search and history
  • Web Push (browser) and Expo Push (mobile) for chat and call notifications when the tab or app is in the background — see Web Push and Expo Push

Starting a Conversation

Send a Text Message

Send a simple text message to start or continue a conversation:

POST /api/Chat/messages
Authorization: Bearer {token}
Content-Type: application/json

{
"recipientId": "user-guid",
"content": "Hello! How are you?",
"type": "text"
}

Response:

{
"success": true,
"data": {
"id": "message-456",
"conversationId": "conv-789",
"senderId": "current-user-id",
"recipientId": "user-123",
"content": "Hello! How are you?",
"type": "text",
"status": "sent",
"createdAt": "2024-01-01T12:00:00Z",
"readAt": null
}
}

Send Media Message

Share images, videos, or files via api/Chat/upload or api/Chat/upload-multiple:

POST /api/Chat/upload
Authorization: Bearer {token}
Content-Type: multipart/form-data

FormData:
- recipientId: "user-guid"
- conversationId: "conv-guid" // if existing
- file: file.jpg

Supported Media Types:

  • Images: JPEG, PNG, GIF, WebP (max 10MB)
  • Videos: MP4, WebM, MOV (max 100MB)
  • Files: PDF, DOC, DOCX, XLS, XLSX, ZIP (max 50MB)

Response:

{
"success": true,
"data": {
"id": "message-456",
"type": "image",
"content": "Check this out!",
"media": {
"url": "https://cdn.example.com/messages/image-123.jpg",
"thumbnail": "https://cdn.example.com/messages/image-123-thumb.jpg",
"size": 1024000,
"mimeType": "image/jpeg"
},
"status": "sent",
"createdAt": "2024-01-01T12:00:00Z"
}
}

Viewing Conversations

Get All Conversations

Retrieve list of all conversations:

GET /api/Chat/conversations?skip=0&take=50&includeArchived=false
Authorization: Bearer {token}

Response:

{
"success": true,
"data": [
{
"id": "conv-123",
"type": "direct", // or "group"
"participants": [
{
"id": "user-1",
"fullName": "John Doe",
"username": "johndoe",
"avatar": "https://..."
},
{
"id": "user-2",
"fullName": "Jane Smith",
"username": "janesmith",
"avatar": "https://..."
}
],
"lastMessage": {
"id": "msg-456",
"content": "See you tomorrow!",
"senderId": "user-1",
"createdAt": "2024-01-01T11:00:00Z"
},
"unreadCount": 2,
"updatedAt": "2024-01-01T11:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 15,
"totalPages": 1
}
}

Get Messages in a Conversation

Retrieve messages from a specific conversation:

GET /api/Chat/messages?chatId={conversationId}&skip=0&take=50&before={isoDate}
Authorization: Bearer {token}

Query Parameters:

  • page - Page number (default: 1)
  • limit - Messages per page (default: 50, max: 100)
  • before - Get messages before this timestamp
  • after - Get messages after this timestamp

Response:

{
"success": true,
"data": {
"conversationId": "conv-123",
"messages": [
{
"id": "msg-456",
"senderId": "user-1",
"content": "Hello!",
"type": "text",
"status": "read",
"createdAt": "2024-01-01T10:00:00Z",
"readAt": "2024-01-01T10:01:00Z"
},
{
"id": "msg-457",
"senderId": "user-2",
"content": "Hi there!",
"type": "text",
"status": "read",
"createdAt": "2024-01-01T10:00:30Z",
"readAt": "2024-01-01T10:00:35Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"hasMore": false
}
}
}

Real-time Features (SignalR)

The chat real-time layer uses SignalR (not raw WebSockets). The ChatWorker hosts the hub at /hubs/chat (and /hubs/chat-online for presence). The frontend uses @microsoft/signalr and connects to the URL configured by VITE_CHAT_HUB_URL (or fallback from API base).

Connecting to the Chat Hub

import * as signalR from '@microsoft/signalr';

const connection = new signalR.HubConnectionBuilder()
.withUrl(`${chatHubBaseUrl}/hubs/chat?userId=${userId}`, {
accessTokenFactory: () => getAccessToken()
})
.withAutomaticReconnect()
.build();

await connection.start();

Joining a Conversation and Receiving Messages

// Join a conversation to receive real-time messages
await connection.invoke('JoinConversation', conversationId);

// Listen for new messages
connection.on('ReceiveMessage', (message) => {
handleNewMessage(message); // message has messageId, content, senderId, etc.
});

// Leave when switching away
await connection.invoke('LeaveConversation', conversationId);

Sending Messages via SignalR

Messages can be sent via REST (POST /api/Chat/messages) or SignalR when connected. The frontend typically tries SignalR first and falls back to REST.

// Send text (SignalR)
await connection.invoke('SendMessage', {
recipientId: 'user-guid',
content: 'Hello!',
type: 'text'
});

// Typing indicator
await connection.invoke('SendTypingIndicator', conversationId, true); // start
await connection.invoke('SendTypingIndicator', conversationId, false); // stop

Message and Presence Events

EventDescriptionPayload
ReceiveMessageNew message in conversationMessage object (messageId, content, senderId, type, etc.)
MessageReactionReaction added/updated on a message{ messageId, userId, reaction }
ConversationDeletedConversation removed{ conversationId }
Presence (chat-online hub)User online/offlineVaries by hub method

Read receipts and delivery status are supported via REST (POST /api/Chat/messages/{messageId}/read) and may be reflected in SignalR events depending on the backend implementation.

Group Conversations

Create Group

Create a group conversation with multiple participants:

POST /api/Chat/groups
Authorization: Bearer {token}
Content-Type: application/json

{
"name": "Project Team",
"description": "Team discussion for the project",
"memberIds": ["user-1", "user-2", "user-3"],
"avatar": "optional-avatar-url"
}

Response:

{
"success": true,
"data": {
"id": "group-123",
"conversationId": "conv-456",
"name": "Project Team",
"description": "Team discussion for the project",
"avatar": "https://...",
"adminId": "current-user-id",
"members": [
{
"id": "user-1",
"fullName": "John Doe",
"role": "admin" // or "member"
},
{
"id": "user-2",
"fullName": "Jane Smith",
"role": "member"
}
],
"createdAt": "2024-01-01T12:00:00Z"
}
}

Manage Group Members

Add Members:

POST /api/Chat/groups/{conversationId}/participants
Authorization: Bearer {token}
Content-Type: application/json

{
"memberIds": ["user-4", "user-5"]
}

Remove Members:

DELETE /api/Chat/groups/{conversationId}/participants
Authorization: Bearer {token}

Update Member Role:

PUT /api/Chat/groups/{conversationId}/participants (body: role)
Authorization: Bearer {token}
Content-Type: application/json

{
"role": "admin" // or "member"
}

Group Settings

Update Group Info:

PUT /api/Chat/groups/{conversationId} (update group info)
Authorization: Bearer {token}
Content-Type: application/json

{
"name": "Updated Group Name",
"description": "Updated description",
"avatar": "new-avatar-url"
}

Leave Group:

POST /api/Chat/conversations/{conversationId}/leave (or remove self via participants endpoint)
Authorization: Bearer {token}

Message Status

Messages have different statuses that indicate their delivery state:

StatusDescriptionWhen It Occurs
SendingMessage is being sentImmediately after sending
SentMessage delivered to serverAfter server confirms receipt
DeliveredMessage delivered to recipientWhen recipient's device receives it
ReadMessage read by recipientWhen recipient opens the message
FailedMessage failed to sendOn error or timeout

Status Updates

Status updates are sent via SignalR in real-time when connected:

// Message sent
{
"type": "message.sent",
"data": {
"messageId": "msg-123",
"status": "sent",
"timestamp": "2024-01-01T12:00:00Z"
}
}

// Message delivered
{
"type": "message.delivered",
"data": {
"messageId": "msg-123",
"status": "delivered",
"timestamp": "2024-01-01T12:00:05Z"
}
}

// Message read
{
"type": "message.read",
"data": {
"messageId": "msg-123",
"status": "read",
"readAt": "2024-01-01T12:00:10Z"
}
}

Search for messages across all conversations:

GET /api/Chat/messages/search?chatId=...&searchTerm=hello&skip=0&take=50
Authorization: Bearer {token}

Query Parameters:

  • q - Search query (required)
  • conversationId - Limit search to specific conversation (optional)
  • page - Page number
  • limit - Results per page

Message Management

Delete Message

Delete a message (only your own messages):

DELETE /api/Chat/conversations/{conversationId} (delete conversation) or message delete if exposed
Authorization: Bearer {token}

Edit Message

Edit a message (only your own messages, within time limit):

PUT /api/Chat/messages/{messageId} (edit message, if supported)
Authorization: Bearer {token}
Content-Type: application/json

{
"content": "Updated message content"
}

Mark as Read

Mark messages as read:

POST /api/Chat/messages/{messageId}/read
Authorization: Bearer {token}
Content-Type: application/json

{
"messageIds": ["msg-1", "msg-2"] // Optional: specific messages, or all if empty
}

Best Practices

Performance

  • Use SignalR for real-time features when the ChatWorker is available
  • Implement message pagination
  • Cache recent conversations
  • Lazy load message history

User Experience

  • Show typing indicators
  • Display message status
  • Handle offline scenarios
  • Implement message queuing for offline messages

Security

  • Always authenticate SignalR connections (token via accessTokenFactory or query)
  • Validate message content
  • Rate limit message sending
  • Sanitize user inputs

API Reference

Chat endpoints use api/Chat:

  • Send message: POST /api/Chat/messages
  • Get messages: GET /api/Chat/messages?chatId=...&skip=0&take=50
  • Get conversations: GET /api/Chat/conversations
  • Create group: POST /api/Chat/groups
  • Add/remove participants: POST /api/Chat/groups/{conversationId}/participants, DELETE /api/Chat/groups/{conversationId}/participants
  • Upload media: POST /api/Chat/upload, POST /api/Chat/upload-multiple
  • Mark read: POST /api/Chat/messages/{messageId}/read
  • Mute/archive: POST /api/Chat/conversations/{conversationId}/mute, .../archive

Push notifications for chat and calls

When the recipient’s tab or app is in the background, they can receive Web Push (browser) or Expo Push (mobile) for new messages and incoming calls. The API publishes chat/call events to RabbitMQ; WebPushNotificationWorker and ExpoPushNotificationWorker consume and send push. For setup, endpoints, and troubleshooting, see Web Push and Expo Push.