Shared Integration
Namespace: /v1/shared
Purpose: General platform events and notifications broadcasting
Authentication: Required (JWT + scope: conversation)
Socket Events
Client → Server Events
| Event | Description | Authentication | Scope Required |
|---|---|---|---|
join | Join shared namespace | Required | conversation |
disconnect | User disconnected (automatic) | N/A | N/A |
Database Models
- Socket - User socket ID tracking
Schema:
{
user: ObjectId, // User ID (ref: User)
socket_ids: [String], // Array of socket connection IDs
createdAt: Date,
updatedAt: Date
}
Event Details
join
Purpose: Register user connection in shared namespace
Request:
socket.emit('join', {}, callback);
Response:
{ success: true, message: 'SUCCESS' }
Side Effects:
- Creates or updates Socket document for user
- Adds socketId to
socket_idsarray - Enables receiving broadcast events
Example:
// On join
const existingDoc = await SocketModel.findOne({ user: uid }).exec();
if (existingDoc) {
await existingDoc.updateOne({ $addToSet: { socket_ids: connectionID } });
} else {
await new SocketModel({ user: uid, socket_ids: [connectionID] }).save();
}
disconnect
Purpose: Clean up socket IDs when user disconnects
Trigger: Automatic when socket disconnects
Side Effects:
- Removes stale socket IDs from Socket document
- Keeps only active socket IDs
- Document remains for future connections
Implementation:
async disconnect(connectionID) {
const socket = await SocketModel.findOne({ socket_ids: connectionID }).exec();
if (!socket) return;
// Get all active sockets
const activeClients = [...this.io.sockets].map(c => c[0]);
// Remove inactive socket IDs
const socketIds = socket.socket_ids || [];
const socketToBeDeleted = socketIds.filter(ID => !activeClients.includes(ID));
await socket.updateOne({ $pullAll: { socket_ids: socketToBeDeleted } });
}
REST API Integration
The Shared namespace is primarily controlled via REST API for broadcasting events:
Emit to Specific User
Endpoint: POST /emit/:userId/:emitTo
Purpose: Send event to single user
Request:
POST /emit/64abc123def456789/notification
Content-Type: application/json
{
"title": "New Lead",
"message": "You have a new lead from John Doe",
"type": "lead",
"data": {
"lead_id": "64lead...",
"contact_name": "John Doe"
}
}
Response:
{ success: true, message: 'SUCCESS' }
Controller Logic:
exports.emit = async (req, res, next) => {
const io = req.io; // sharedIo namespace
const userID = req.params.userId;
const payload = req.body;
const emitTo = req.params.emitTo; // Event name
// Find user's socket IDs
let socketIds = await SocketModel.findOne({ user: userID }, { socket_ids: true }).lean().exec();
// Emit to all user's sockets
if (socketIds) {
for (const socketID of socketIds.socket_ids || []) {
io.to(socketID).emit(emitTo, payload);
}
}
return res.status(200).json({ success: true, message: 'SUCCESS' });
};
Emit to Multiple Users
Endpoint: POST /emit/:emitTo
Purpose: Broadcast event to multiple users
Request:
POST /emit/system_announcement
Content-Type: application/json
{
"userIds": ["64abc...", "64def...", "64ghi..."],
"data": {
"title": "System Maintenance",
"message": "Scheduled maintenance on Monday 2AM-4AM",
"priority": "high"
}
}
Response:
{ success: true, message: 'SUCCESS' }
Controller Logic:
exports.emitMultiple = async (req, res, next) => {
const io = req.io;
const payload = req.body.data;
const emitTo = req.params.emitTo;
let userIDs = req.body.userIds;
// Handle JSON string input
if (typeof userIDs === 'string' && userIDs.includes('[')) {
userIDs = JSON.parse(userIDs);
}
// Find all users' socket IDs
let socketIds = await SocketModel.find(
{
user: Array.isArray(userIDs)
? { $in: userIDs.map(id => new mongoose.Types.ObjectId(id)) }
: new mongoose.Types.ObjectId(userIDs),
},
{ socket_ids: true },
)
.lean()
.exec();
// Emit to all sockets
if (socketIds) {
for (const sockets of socketIds || []) {
for (const socketID of sockets.socket_ids || []) {
io.to(socketID).emit(emitTo, payload);
}
}
}
return res.status(200).json({ success: true, message: 'SUCCESS' });
};
Use Cases
1. Real-Time Notifications
Scenario: New lead assigned to user
// Internal API - when lead assigned
await axios.post('http://general-socket:4000/emit/64user.../notification', {
title: 'New Lead Assigned',
message: 'John Doe has been assigned to you',
type: 'lead_assigned',
data: {
lead_id: '64lead...',
lead_name: 'John Doe',
assigned_by: 'Manager Name',
},
timestamp: new Date().toISOString(),
});
2. System Announcements
Scenario: Broadcast maintenance notice
// Internal API - system announcement
const allActiveUsers = await User.find({ status: 'active' }).distinct('_id');
await axios.post('http://general-socket:4000/emit/system_announcement', {
userIds: allActiveUsers,
data: {
title: 'Scheduled Maintenance',
message: 'System will be down Monday 2AM-4AM EST',
priority: 'high',
action_url: '/maintenance-info',
},
});
3. Data Sync Events
Scenario: Notify users of data updates
// External API - after webhook processes data
await axios.post('http://general-socket:4000/emit/data_sync', {
userIds: [workspace.owner_id],
data: {
type: 'google_ads_sync_complete',
account_id: '64acc...',
campaigns_synced: 15,
timestamp: new Date().toISOString(),
},
});
4. Collaboration Events
Scenario: User mentions another user in comment
// Internal API - when user @mentions another
await axios.post(`http://general-socket:4000/emit/${mentionedUserId}/mention`, {
type: 'comment_mention',
source: 'project_task',
task_id: '64task...',
task_title: 'Design Homepage',
mentioned_by: {
id: currentUserId,
name: 'John Smith',
},
comment: 'Hey @Sarah, can you review this design?',
});
Client Example
import io from 'socket.io-client';
const sharedSocket = io('http://localhost:4000/v1/shared', {
transports: ['websocket'],
query: { token: 'your_jwt_token' },
});
// Join shared namespace
sharedSocket.emit('join', {}, response => {
console.log('Joined shared namespace:', response);
});
// Listen for notifications
sharedSocket.on('notification', data => {
console.log('Notification:', data);
// Show toast notification
showToast(data.title, data.message, data.type);
});
// Listen for system announcements
sharedSocket.on('system_announcement', data => {
console.log('System announcement:', data);
// Show banner
showBanner(data.data.title, data.data.message, data.data.priority);
});
// Listen for data sync events
sharedSocket.on('data_sync', data => {
console.log('Data sync:', data);
// Refresh UI component
if (data.type === 'google_ads_sync_complete') {
refreshGoogleAdsData();
}
});
// Listen for mentions
sharedSocket.on('mention', data => {
console.log('You were mentioned:', data);
// Show notification + navigate
showMentionNotification(data);
});
// Listen for custom events
sharedSocket.on('custom_event', data => {
console.log('Custom event:', data);
// Handle custom logic
});
Event Naming Conventions
Recommended event names:
notification- General notificationssystem_announcement- Platform-wide announcementsdata_sync- Data synchronization eventsmention- User mentionstask_update- Task/project updatesdeal_update- CRM deal updateslead_update- New/updated leadsreport_ready- Report generation completeexport_complete- Data export finishedalert- Urgent alertsreminder- Task/event reminders
Best Practices
- Event Naming: Use descriptive, consistent event names
- Payload Structure: Keep consistent data structure across events
- Timestamp: Always include timestamp in payload
- Type Field: Include event type for client-side routing
- Batch Emits: For multiple users, use
emitMultipleendpoint - Error Handling: Handle user offline scenarios gracefully
- Testing: Test with multiple user connections
Performance Considerations
- Large User Lists: For more than 100 users, consider batch processing
- Payload Size: Keep payloads small (less than 1KB recommended)
- Rate Limiting: Implement limits on emit frequency per user
- Socket Cleanup: Regularly clean up inactive socket IDs
- Monitoring: Track emit success/failure rates
Security Considerations
- Authentication Required: All connections must be authenticated
- Scope Verification:
conversationscope required - Payload Validation: Validate all incoming data
- User Isolation: Ensure users only receive their own events
- Sensitive Data: Avoid sending sensitive data in broadcasts