Server Configuration
Express Server Setup
Entry Point: general-socket/index.js
const express = require('express');
const app = express();
// Middleware Stack
app.use(cors()); // Cross-origin requests
app.use(bodyParser.json()); // JSON body parsing
app.use(helmet()); // Security headers
app.use(nocache()); // Disable caching
// Health Check
app.get('/status', (req, res) => {
res.json({ success: true, message: 'General socket is running' });
});
// REST API Routes
app.use('/', RestAPI);
// Server Initialization
const server = http.createServer(app);
const io = socketConfig(server); // Socket.IO configuration
Socket.IO Configuration
Configuration File: general-socket/Utils/socket.js
Key Features:
- Singleton Pattern: Single Socket.IO instance across service
- Redis Adapter: Horizontal scaling with pub/sub
- WebSocket-Only: Disables long-polling fallback
- CORS Enabled: Supports cross-origin connections
- Custom Headers: Accepts
x-tokenfor authentication
const io = require('socket.io')(server, {
transports: ['websocket'], // WebSocket-only
cors: {
origin: '*',
methods: ['GET', 'POST'],
allowedHeaders: ['x-token'],
credentials: true,
},
});
// Redis Adapter for Scaling
const redisAdapter = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
io.adapter(redisAdapter(pubClient, subClient));
Redis Adapter Configuration
Purpose: Enable horizontal scaling with multiple General Socket instances
Setup:
const redisAdapter = require('@socket.io/redis-adapter');
const { createClient } = require('redis');
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
io.adapter(redisAdapter(pubClient, subClient));
Benefits:
- Events broadcast across all instances
- User can maintain single socket connection
- Load balancing friendly
- Session persistence
Environment Variable:
REDIS_URL=redis://localhost:6379
Counter Provider System
Location: general-socket/Integrations/Utils/counter.js
Purpose: Calculate and cache conversation counters per channel
Calculated Counters:
- Open - Active conversations (not snoozed)
- Unread - Conversations with unread messages
- Closed - Closed conversations
- Priority - Pinned conversations (show_on_top)
- Snoozed - Temporarily hidden conversations
Special Channels:
- You - Aggregated counters across all channels
- Mentions - Conversations where user is @mentioned
- Prospect - All prospect conversations
Cache Strategy: Counters are recalculated on each channel event and stored for performance.
Error Handling
Socket Event Errors
All socket event handlers follow this error pattern:
async onEventName(data, callback) {
try {
data = await isAuth(data); // Authenticate
await verifyScope(data, ['conversation']); // Check permissions
// Business logic...
sendResponse({ success: true, message: 'SUCCESS', data }, callback, false);
} catch (error) {
let errBody = {
success: false,
message: error.message,
additional_info: error
};
sendResponse(errBody, callback);
}
}
REST API Errors
router.use((error, req, res, next) => {
console.log('error--------------', error);
const status = error.statusCode || 400;
const message = error.message;
res.status(status).json({
success: false,
errno: status,
message: message,
});
});
Common Error Codes
| Code | Error | Cause |
|---|---|---|
| 401 | INVALID_TOKEN | JWT verification failed |
| 403 | NOT_ENOUGH_SCOPE | User lacks required permission scope |
| 404 | NOT_FOUND | Conversation/Message not found |
| 400 | VALIDATION_ERROR | Missing required parameters |
Graceful Shutdown
Signal Handlers:
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
async function shutdown() {
console.log('Shutting down gracefully...');
// Close Socket.IO connections
io.close(() => {
console.log('All sockets closed');
});
// Close database connection
await mongoose.connection.close();
// Exit process
process.exit(0);
}
Performance Considerations
- Connection Pooling: Mongoose maintains connection pool to MongoDB
- Redis Caching: Socket IDs cached in Redis for fast lookups
- Selective Population: Only populate required fields in database queries
- Event Batching: Multiple socket emissions batched when possible
- Async Processing: All socket handlers are async to prevent blocking