Error Handling
This guide provides comprehensive information about error handling patterns, error codes, and best practices for building robust ChatMinder integrations.
Overview
ChatMinder uses structured error responses with consistent patterns across all APIs, making it easier to build reliable error handling into your applications.
Error Response Structure
All API errors follow a consistent structure:
{
"success": false,
"message": "Human-readable error description",
"errorCode": "MACHINE_READABLE_ERROR_CODE"
}
HTTP Status Codes
ChatMinder uses standard HTTP status codes to indicate different types of errors:
| Status Code | Meaning | When Used |
|---|---|---|
200 OK |
Success | Request completed successfully |
400 Bad Request |
Validation errors or invalid request | Missing required fields, invalid data formats |
401 Unauthorized |
Authentication required | Missing or invalid JWT token |
403 Forbidden |
Insufficient permissions | User lacks permission for requested action |
404 Not Found |
Resource not found | Channel, user, or message doesn't exist |
500 Internal Server Error |
Server error | Unexpected server-side error |
Error Code Categories
Authentication Errors
| Error Code | Description | Common Causes |
|---|---|---|
MISSING_AUTH_CODE |
Authorization code not provided | OAuth callback missing code parameter |
MISSING_REDIRECT_URI |
Redirect URI not provided | OAuth request missing redirect URI |
TOKEN_EXCHANGE_FAILED |
Failed to exchange auth code | Invalid authorization code or expired |
USER_INFO_FAILED |
Failed to get user info from provider | Provider API error or invalid token |
USER_CREATION_FAILED |
Account creation failed | Database error or validation failure |
OAUTH_NOT_CONFIGURED |
OAuth provider not configured | Missing client ID/secret in server config |
Guardian Authority Errors
| Error Code | Description | Common Causes |
|---|---|---|
UNAUTHORIZED_GUARDIAN_ACTION |
Guardian lacks authority for this user | Trying to manage user not under guardianship |
MESSAGE_ALREADY_PROCESSED |
Message already approved/rejected | Duplicate approval attempt |
CHANNEL_CREATION_DENIED |
User cannot create channels | Protection level doesn't allow channel creation |
CHANNEL_ACCESS_DENIED |
User cannot access channel | User not a member or lacks permission |
Validation Errors
| Error Code | Description | Common Causes |
|---|---|---|
INVALID_DEVICE_ID |
Device ID format invalid | Push notification registration errors |
INVALID_PLATFORM |
Platform not supported | Unsupported device platform |
INVALID_CHANNEL_ID |
Channel ID format invalid | Negative numbers or malformed IDs |
MISSING_FCM_TOKEN |
FCM token required | Push notification setup incomplete |
Real-time Connection Errors
| Error Code | Description | Common Causes |
|---|---|---|
ACCESS_DENIED |
SignalR access denied | User tried to access unauthorized channel |
INVALID_CHANNEL_ID |
Channel ID invalid in SignalR context | Malformed channel ID in hub method |
CONNECTION_FAILED |
SignalR connection failed | Network issues or authentication problems |
Error Handling Patterns
Basic Error Handling
async function apiCall(endpoint, options) {
try {
const response = await fetch(endpoint, options);
if (!response.ok) {
const error = await response.json();
throw new APIError(error.message, error.errorCode, response.status);
}
return await response.json();
} catch (error) {
if (error instanceof APIError) {
// Handle known API errors
handleAPIError(error);
} else {
// Handle network or other unexpected errors
console.error('Unexpected error:', error);
throw new Error('An unexpected error occurred');
}
}
}
class APIError extends Error {
constructor(message, errorCode, statusCode) {
super(message);
this.errorCode = errorCode;
this.statusCode = statusCode;
this.name = 'APIError';
}
}
Authentication Error Handling
async function handleAuthenticationError(response) {
if (!response.ok) {
const error = await response.json();
switch (error.errorCode) {
case 'MISSING_AUTH_CODE':
case 'MISSING_REDIRECT_URI':
// Client-side configuration error
showErrorMessage('OAuth configuration error. Please check your setup.');
break;
case 'TOKEN_EXCHANGE_FAILED':
// OAuth flow interrupted or expired
showErrorMessage('Authentication failed. Please try again.');
redirectToLogin();
break;
case 'USER_INFO_FAILED':
// Provider API issue
showErrorMessage('Unable to retrieve account information.');
break;
case 'OAUTH_NOT_CONFIGURED':
// Server configuration issue
showErrorMessage('OAuth authentication is currently unavailable.');
break;
default:
// General error handling
showErrorMessage('Authentication failed. Please try again.');
}
}
}
Guardian Authority Error Handling
async function handleGuardianApproval(messageId, guardianToken) {
try {
const response = await fetch(`/api/guardian/pending-messages/${messageId}/approve`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${guardianToken}` }
});
if (!response.ok) {
const error = await response.json();
switch (error.errorCode) {
case 'UNAUTHORIZED_GUARDIAN_ACTION':
showErrorMessage('You do not have authority to manage this user.');
return false;
case 'MESSAGE_ALREADY_PROCESSED':
showWarningMessage('This message has already been processed.');
return false;
default:
showErrorMessage(`Approval failed: ${error.message}`);
return false;
}
}
showSuccessMessage('Message approved successfully.');
return true;
} catch (error) {
console.error('Network error during approval:', error);
showErrorMessage('Network error. Please check your connection and try again.');
return false;
}
}
SignalR Error Handling
// Set up comprehensive SignalR error handling
const connection = new HubConnectionBuilder()
.withUrl('/chatHub', { accessTokenFactory: () => getJwtToken() })
.withAutomaticReconnect()
.build();
// Handle SignalR-specific errors
connection.on('ErrorOccurred', (error) => {
switch(error) {
case 'ACCESS_DENIED':
showErrorMessage('You do not have access to this channel.');
break;
case 'INVALID_CHANNEL_ID':
console.error('Invalid channel ID provided to SignalR hub');
break;
default:
console.error('SignalR Error:', error);
showErrorMessage('A real-time communication error occurred.');
}
});
// Handle connection failures
connection.onclose((error) => {
if (error) {
console.error('SignalR connection lost:', error);
showWarningMessage('Real-time connection lost. Attempting to reconnect...');
// Implement custom reconnection logic if needed
setTimeout(() => {
connection.start().catch(err => {
console.error('Reconnection failed:', err);
showErrorMessage('Unable to restore real-time connection. Please refresh the page.');
});
}, 5000);
}
});
Error Recovery Strategies
Automatic Retry Logic
async function retryableRequest(url, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return await response.json();
}
// Don't retry on client errors (4xx)
if (response.status >= 400 && response.status < 500) {
throw new APIError('Client error', 'CLIENT_ERROR', response.status);
}
// Retry on server errors (5xx) if we haven't exceeded max attempts
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw new APIError('Server error', 'SERVER_ERROR', response.status);
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
// Wait before retrying
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
Graceful Degradation
class ChatManager {
constructor() {
this.isOnline = true;
this.queuedMessages = [];
this.retryInterval = null;
}
async sendMessage(channelId, content) {
try {
const response = await fetch(`/api/messages/channel/${channelId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content, messageType: 'text' })
});
if (response.ok) {
this.isOnline = true;
this.processQueuedMessages(); // Process any queued messages
return await response.json();
}
} catch (error) {
// Network error - queue message for later
this.isOnline = false;
this.queueMessage(channelId, content);
this.startRetryTimer();
showWarningMessage('Message queued. Will send when connection is restored.');
}
}
queueMessage(channelId, content) {
this.queuedMessages.push({ channelId, content, timestamp: Date.now() });
}
async processQueuedMessages() {
while (this.queuedMessages.length > 0 && this.isOnline) {
const message = this.queuedMessages.shift();
try {
await this.sendMessage(message.channelId, message.content);
} catch (error) {
// Re-queue if still failing
this.queuedMessages.unshift(message);
break;
}
}
}
startRetryTimer() {
if (this.retryInterval) return;
this.retryInterval = setInterval(async () => {
if (this.queuedMessages.length > 0) {
await this.processQueuedMessages();
}
if (this.queuedMessages.length === 0) {
clearInterval(this.retryInterval);
this.retryInterval = null;
}
}, 10000); // Retry every 10 seconds
}
}
User Experience Best Practices
Error Message Guidelines
Do:
- ✅ Use clear, non-technical language
- ✅ Provide actionable next steps
- ✅ Distinguish between temporary and permanent errors
- ✅ Show progress for retry attempts
Don't:
- ❌ Expose internal error codes to end users
- ❌ Use technical jargon
- ❌ Show stack traces in production
- ❌ Leave users without guidance
Error Message Examples
// Good error messages
const userFriendlyMessages = {
'UNAUTHORIZED_GUARDIAN_ACTION': 'You don\'t have permission to manage this user.',
'MESSAGE_ALREADY_PROCESSED': 'This message has already been reviewed.',
'CONNECTION_FAILED': 'Unable to connect. Please check your internet connection.',
'TOKEN_EXCHANGE_FAILED': 'Sign-in failed. Please try signing in again.',
'INVALID_DEVICE_ID': 'There was a problem setting up notifications. Please try again.'
};
function showUserFriendlyError(errorCode, fallbackMessage) {
const message = userFriendlyMessages[errorCode] || fallbackMessage;
showErrorToast(message);
}
Loading States and Error Recovery
// UI state management during API calls
async function performAction(actionFn, loadingMessage = 'Processing...') {
showLoadingState(loadingMessage);
try {
const result = await actionFn();
showSuccessState();
return result;
} catch (error) {
if (error instanceof APIError) {
showErrorState(error.message, () => {
// Retry button
performAction(actionFn, loadingMessage);
});
} else {
showErrorState('An unexpected error occurred', () => {
window.location.reload();
});
}
throw error;
}
}
// Usage
await performAction(
() => approveMessage(messageId),
'Approving message...'
);
Debugging and Monitoring
Client-Side Error Logging
class ErrorLogger {
static log(error, context = {}) {
const errorData = {
timestamp: new Date().toISOString(),
message: error.message,
errorCode: error.errorCode,
statusCode: error.statusCode,
stack: error.stack,
context: context,
userAgent: navigator.userAgent,
url: window.location.href
};
// Log to console in development
if (process.env.NODE_ENV === 'development') {
console.error('Error logged:', errorData);
}
// Send to monitoring service in production
if (process.env.NODE_ENV === 'production') {
this.sendToMonitoring(errorData);
}
}
static async sendToMonitoring(errorData) {
try {
await fetch('/api/errors/log', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorData)
});
} catch (err) {
// Fail silently for error logging
console.warn('Failed to send error to monitoring:', err);
}
}
}
// Usage
try {
await riskyOperation();
} catch (error) {
ErrorLogger.log(error, { operation: 'message-approval', userId: currentUserId });
throw error;
}
Testing Error Scenarios
Mock Error Responses
// Testing error handling in Jest
const mockErrorResponse = (errorCode, message, statusCode = 400) => {
return Promise.resolve({
ok: false,
status: statusCode,
json: () => Promise.resolve({
success: false,
errorCode,
message
})
});
};
test('handles guardian authority error', async () => {
global.fetch = jest.fn(() =>
mockErrorResponse('UNAUTHORIZED_GUARDIAN_ACTION', 'Access denied', 403)
);
const result = await handleGuardianApproval('msg123', 'token');
expect(result).toBe(false);
expect(console.error).toHaveBeenCalledWith(
'Guardian does not have authority for this user'
);
});
Related Documentation
- Authentication Guide: Authentication-specific error handling
- Real-time Features: SignalR error handling patterns
- Walkthroughs: Practical error handling examples
- Core Concepts: Understanding error response patterns