Messaging Events and Retrieving Messages
This guide explains how to retrieve chat messages from the Agglestone Messaging Service. You can retrieve individual messages or paginated lists of messages for a chat.
Setting Up the WebSocket Connection
Before you can receive message events, you need to establish a WebSocket connection using SignalR:
// Install: npm install @microsoft/signalr@^10.0.0
import * as signalR from '@microsoft/signalr';
const tenantId = 'your-tenant-id'; // Get this from https://portal.agglestone.com
const apiBaseUrl = 'https://messaging.agglestone.com'; // Agglestone's messaging service URL
// Generate a unique client ID for this browser tab/window
// This allows the server to exclude this specific client from messaging events when you send a message
const clientId = crypto.randomUUID();
// Create SignalR connection
const connection = new signalR.HubConnectionBuilder()
.withUrl(`${apiBaseUrl}/tenant/${tenantId}/v1/hub?clientId=${encodeURIComponent(clientId)}`, {
accessTokenFactory: () => accessToken // JWT token from authentication service
})
.withAutomaticReconnect()
.build();
// Listen for chat message notifications
connection.on("MessagingEvent", async (event) => {
// IMPORTANT: The WebSocket event only contains lightweight information
// You must fetch the full message details using the REST API
if (event.type === 'Chat' && event.parentId) {
const chatId = event.parentId;
const messageId = event.id;
// Fetch the full message details
const message = await getChatMessage(chatId, messageId);
// Display the message in your UI
displayMessage(message);
}
});
// Start the connection
try {
await connection.start();
console.log('Connected to SignalR hub');
} catch (error) {
console.error('Connection error:', error);
}
Retrieving Individual Messages
When you receive a WebSocket MessagingEvent for a chat message, you need to fetch the full message details from the REST API. The WebSocket event only contains lightweight information (message ID, chat ID, timestamp), so you must make an API call to get the complete message data.
When to Retrieve Individual Messages
You should retrieve an individual message when:
- You receive a
MessagingEventthrough the WebSocket connection - You need to display a specific message that was referenced by ID
- You want to refresh or verify message details
Retrieving a Message by ID
Endpoint: GET /tenant/{tenantId}/v1/Chat/{chatId}/messages/{messageId}
Response:
{
"id": "message-id-456",
"chatId": "chat-id-123",
"senderUserId": "user-id-1",
"message": "Hello, everyone!",
"createdAt": "2024-01-15T10:35:00Z"
}
Example: Retrieving an Individual Message
The getChatMessage function is called from the MessagingEvent handler shown in the connection setup above:
async function getChatMessage(chatId: string, messageId: string) {
const response = await fetch(
`${apiBaseUrl}/tenant/${tenantId}/v1/Chat/${chatId}/messages/${messageId}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
);
if (!response.ok) {
throw new Error(`Failed to fetch message: ${response.statusText}`);
}
return await response.json();
}
Retrieving Paginated Message Lists
When you need to load conversation history, display a chat interface, or implement infinite scroll, you should retrieve a paginated list of messages.
When to Retrieve Paginated Message Lists
You should retrieve paginated message lists when:
- Loading conversation history: When a user opens a chat, you need to load recent messages to display the conversation
- Implementing infinite scroll: As users scroll up in a chat, you load older messages
- Catching up after reconnection: When a user reconnects after being offline, you fetch messages they missed
- Initial chat load: When displaying a chat for the first time, you load the most recent messages
Note: It’s perfectly fine to retrieve messages that you may have already loaded. You can avoid showing duplicates on the screen by checking the id field of each message and only displaying messages that haven’t been shown before. This is especially useful when loading older messages for infinite scroll or when catching up after reconnection, as there may be overlap between the messages you’ve already displayed and the new messages you’re fetching.
Retrieving Messages with Pagination
Endpoint: GET /tenant/{tenantId}/v1/Chat/{chatId}/messages
Query Parameters:
pageNumber(optional, default: 1): The page number to retrievepageSize(optional, default: 10, max: 50): The number of messages per pagefromDate(optional): Start date/time in ISO 8601 format – only return messages created on or after this datetoDate(optional): End date/time in ISO 8601 format – only return messages created on or before this date
Response:
{
"data": [
{
"id": "message-id-456",
"chatId": "chat-id-123",
"senderUserId": "user-id-1",
"message": "Hello, everyone!",
"createdAt": "2024-01-15T10:35:00Z"
},
{
"id": "message-id-455",
"chatId": "chat-id-123",
"senderUserId": "user-id-2",
"message": "Hi there!",
"createdAt": "2024-01-15T10:34:00Z"
}
],
"pageNumber": 1,
"pageSize": 10,
"totalCount": 25,
"totalPages": 3,
"hasPreviousPage": false,
"hasNextPage": true
}
The response includes:
data: Array of message objectspageNumber: Current page numberpageSize: Number of items per pagetotalCount: Total number of messages in the chattotalPages: Total number of pageshasPreviousPage: Whether there are older messageshasNextPage: Whether there are newer messages
Example: Retrieving Paginated Messages
async function getChatMessages(
chatId: string,
pageNumber: number = 1,
pageSize: number = 20,
fromDate?: Date,
toDate?: Date
) {
const params = new URLSearchParams({
pageNumber: pageNumber.toString(),
pageSize: pageSize.toString()
});
if (fromDate) {
params.append('fromDate', fromDate.toISOString());
}
if (toDate) {
params.append('toDate', toDate.toISOString());
}
const response = await fetch(
`${apiBaseUrl}/tenant/${tenantId}/v1/Chat/${chatId}/messages?${params}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
);
if (!response.ok) {
throw new Error(`Failed to fetch messages: ${response.statusText}`);
}
return await response.json();
}
// Load recent messages when opening a chat
async function loadChatHistory(chatId: string) {
const result = await getChatMessages(chatId, 1, 20);
// Display messages in your UI (most recent first, typically)
result.data.reverse().forEach(message => {
displayMessage(message);
});
return result;
}
// Load older messages for infinite scroll
async function loadOlderMessages(chatId: string, currentPage: number) {
const result = await getChatMessages(chatId, currentPage + 1, 20);
// Prepend older messages to your chat UI
result.data.reverse().forEach(message => {
prependMessage(message);
});
return result;
}
// Load messages from a specific date range
async function loadMessagesInRange(chatId: string, startDate: Date, endDate: Date) {
const result = await getChatMessages(chatId, 1, 50, startDate, endDate);
// Display messages in the date range
result.data.forEach(message => {
displayMessage(message);
});
return result;
}
Best Practices
Loading Conversation History
When a user opens a chat, load the most recent messages first:
// Load the most recent 20 messages when opening a chat
const messages = await getChatMessages(chatId, 1, 20);
// Messages are returned in reverse chronological order (newest first)
// Reverse them to display oldest to newest
messages.data.reverse();
Implementing Infinite Scroll
For infinite scroll, load older messages as the user scrolls up:
let currentPage = 1;
let hasMoreMessages = true;
async function loadMoreMessages() {
if (!hasMoreMessages) return;
const result = await getChatMessages(chatId, currentPage + 1, 20);
if (result.data.length > 0) {
// Prepend older messages
result.data.reverse().forEach(message => {
prependMessage(message);
});
currentPage++;
hasMoreMessages = result.hasNextPage;
} else {
hasMoreMessages = false;
}
}
Catching Up After Reconnection
When a user reconnects after being offline, fetch messages they missed:
// Store the timestamp of the last message you received
let lastMessageTimestamp: Date | null = null;
// When reconnecting, fetch messages since the last message
async function catchUpAfterReconnect(chatId: string) {
if (lastMessageTimestamp) {
const now = new Date();
const missedMessages = await getChatMessages(
chatId,
1,
50,
lastMessageTimestamp,
now
);
// Display missed messages
missedMessages.data.forEach(message => {
displayMessage(message);
lastMessageTimestamp = new Date(message.createdAt);
});
}
}
Error Handling
Common errors you may encounter:
- 401 Unauthorized: Your access token is missing, invalid, or expired. Refresh your token and retry.
- 403 Forbidden: You’re not a member of the chat, or the tenant ID doesn’t match your authentication.
- 404 Not Found: The chat or message doesn’t exist, or you don’t have access to it.
- 400 Bad Request: Invalid query parameters (e.g., invalid date format, page size too large).
Always check the response status and handle errors appropriately in your application.
—
Ready to manage chat members? Check out Managing Chat Members.