Documentation Index Fetch the complete documentation index at: https://mintlify.com/sockudo/sockudo/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Presence channels allow you to track which users are subscribed to a channel, providing real-time visibility into who’s online. Each member can provide user information (name, avatar, etc.) that’s automatically shared with other subscribers.
Key Features:
Real-time member tracking
User information sharing
Member join/leave notifications
Configurable member limits
Automatic cleanup on disconnect
Quick Start
Server Configuration
{
"presence" : {
"max_members_per_channel" : 100 ,
"max_member_size_in_kb" : 2
},
"apps" : [
{
"id" : "my-app" ,
"key" : "my-key" ,
"secret" : "my-secret" ,
"enable_user_authentication" : true
}
]
}
Client Usage
const pusher = new Pusher ( 'my-app-key' , {
cluster: 'mt1' ,
authEndpoint: '/pusher/auth'
});
// Subscribe to presence channel
const channel = pusher . subscribe ( 'presence-chat-room' );
// Get current members
channel . bind ( 'pusher:subscription_succeeded' , () => {
console . log ( 'Current members:' , channel . members . count );
channel . members . each (( member ) => {
console . log ( 'Member:' , member . id , member . info );
});
});
// Listen for new members
channel . bind ( 'pusher:member_added' , ( member ) => {
console . log ( 'Member joined:' , member . id , member . info );
});
// Listen for members leaving
channel . bind ( 'pusher:member_removed' , ( member ) => {
console . log ( 'Member left:' , member . id );
});
Authentication
Presence channels require authentication to associate users with channel members.
Server-Side Authentication
const express = require ( 'express' );
const crypto = require ( 'crypto' );
const app = express ();
app . post ( '/pusher/auth' , ( req , res ) => {
const socketId = req . body . socket_id ;
const channel = req . body . channel_name ;
const user = req . session . user ; // Your auth system
// Presence channels must start with "presence-"
if ( ! channel . startsWith ( 'presence-' )) {
return res . status ( 403 ). send ( 'Invalid channel' );
}
// User info that will be shared with other members
const presenceData = {
user_id: user . id ,
user_info: {
name: user . name ,
avatar: user . avatar_url
}
};
// Create auth signature
const stringToSign = ` ${ socketId } : ${ channel } : ${ JSON . stringify ( presenceData ) } ` ;
const signature = crypto
. createHmac ( 'sha256' , process . env . PUSHER_SECRET )
. update ( stringToSign )
. digest ( 'hex' );
res . json ({
auth: ` ${ process . env . PUSHER_KEY } : ${ signature } ` ,
channel_data: JSON . stringify ( presenceData )
});
});
Authentication Flow
Client subscribes to presence channel
Client sends auth request to your server (/pusher/auth)
Server validates user and generates auth signature
Server returns auth with user info (user_id, user_info)
Client sends auth to Sockudo
Sockudo validates signature and adds member
Other members notified of new member
Configuration
Global Presence Settings
{
"presence" : {
"max_members_per_channel" : 100 ,
"max_member_size_in_kb" : 2
}
}
Option Default Description max_members_per_channel100Maximum members per presence channel max_member_size_in_kb2Maximum size of member info (KB)
Per-App Settings
{
"apps" : [
{
"id" : "my-app" ,
"enable_user_authentication" : true ,
"user_authentication_timeout" : 3600
}
]
}
Option Default Description enable_user_authenticationfalseEnable presence channels user_authentication_timeout3600Auth timeout (seconds)
Channel Naming
Presence channels must follow naming convention:
Type Format Example Public presence presence-*presence-lobbyPrivate presence private-presence-*private-presence-vip-room
Invalid names:
presence (no suffix)
room-presence (wrong prefix)
presence_room (wrong separator)
User ID
Required: Unique identifier for the user
const presenceData = {
user_id: '12345' , // Required, unique
user_info: { ... }
};
Rules:
Must be unique per user
String or number
Used to deduplicate members
User Info
Optional: Additional information about the user
const presenceData = {
user_id: '12345' ,
user_info: {
name: 'John Doe' ,
avatar: 'https://example.com/avatar.jpg' ,
role: 'admin' ,
status: 'online'
}
};
Best practices:
Keep info small (<2KB)
Only include necessary data
Use CDN URLs for images
Don’t include sensitive data
Size Limits
Member info is limited by max_member_size_in_kb:
// Good: 0.5KB
{
name : 'John Doe' ,
avatar : 'https://cdn.example.com/avatar.jpg'
}
// Bad: 3KB (exceeds 2KB limit)
{
name : 'John Doe' ,
avatar : 'data:image/png;base64,...' , // Large base64 image
bio : 'Very long biography...' ,
preferences : { ... } // Large nested object
}
Events
pusher:subscription_succeeded
Triggered when subscription succeeds:
channel . bind ( 'pusher:subscription_succeeded' , () => {
// Access all members
console . log ( 'Total members:' , channel . members . count );
// Get specific member
const member = channel . members . get ( 'user-123' );
console . log ( 'Member:' , member );
// Iterate all members
channel . members . each (( member ) => {
console . log ( 'Member:' , member . id , member . info );
});
});
Payload:
{
"presence" : {
"ids" : [ "user-1" , "user-2" , "user-3" ],
"hash" : {
"user-1" : { "name" : "Alice" , "avatar" : "..." },
"user-2" : { "name" : "Bob" , "avatar" : "..." },
"user-3" : { "name" : "Charlie" , "avatar" : "..." }
},
"count" : 3
}
}
pusher:member_added
Triggered when a member joins:
channel . bind ( 'pusher:member_added' , ( member ) => {
console . log ( 'Member joined:' , member . id );
console . log ( 'Member info:' , member . info );
// Update UI
addMemberToList ( member );
});
Payload:
{
"id" : "user-123" ,
"info" : {
"name" : "John Doe" ,
"avatar" : "https://example.com/avatar.jpg"
}
}
pusher:member_removed
Triggered when a member leaves:
channel . bind ( 'pusher:member_removed' , ( member ) => {
console . log ( 'Member left:' , member . id );
// Update UI
removeMemberFromList ( member . id );
});
Payload:
Use Cases
1. Chat Room
Show who’s online in a chat room:
const channel = pusher . subscribe ( 'presence-chat-room' );
// Display initial members
channel . bind ( 'pusher:subscription_succeeded' , () => {
updateMemberList ( channel . members );
});
// Add new members
channel . bind ( 'pusher:member_added' , ( member ) => {
addMember ( member );
showNotification ( ` ${ member . info . name } joined` );
});
// Remove members
channel . bind ( 'pusher:member_removed' , ( member ) => {
removeMember ( member . id );
showNotification ( ` ${ member . info . name } left` );
});
function updateMemberList ( members ) {
const list = document . getElementById ( 'members' );
list . innerHTML = '' ;
members . each (( member ) => {
const item = document . createElement ( 'li' );
item . innerHTML = `
<img src=" ${ member . info . avatar } " />
<span> ${ member . info . name } </span>
` ;
list . appendChild ( item );
});
}
2. Collaborative Document Editing
Show who’s editing a document:
const channel = pusher . subscribe ( `presence-doc- ${ docId } ` );
channel . bind ( 'pusher:subscription_succeeded' , () => {
// Show all current editors
const editors = [];
channel . members . each (( member ) => {
editors . push ({
id: member . id ,
name: member . info . name ,
color: assignColor ( member . id )
});
});
displayEditors ( editors );
});
channel . bind ( 'pusher:member_added' , ( member ) => {
// Show new editor with assigned color
const editor = {
id: member . id ,
name: member . info . name ,
color: assignColor ( member . id )
};
addEditor ( editor );
showNotification ( ` ${ member . info . name } started editing` );
});
channel . bind ( 'pusher:member_removed' , ( member ) => {
// Remove editor indicator
removeEditor ( member . id );
});
3. Live Video Call
Track participants in a video call:
const channel = pusher . subscribe ( `presence-call- ${ callId } ` );
channel . bind ( 'pusher:subscription_succeeded' , () => {
// Initialize video grid with all participants
channel . members . each (( member ) => {
addVideoStream ( member . id , member . info );
});
});
channel . bind ( 'pusher:member_added' , ( member ) => {
// Add new video stream
addVideoStream ( member . id , member . info );
playJoinSound ();
});
channel . bind ( 'pusher:member_removed' , ( member ) => {
// Remove video stream
removeVideoStream ( member . id );
playLeaveSound ();
});
4. Gaming Lobby
Show players in a game lobby:
const channel = pusher . subscribe ( `presence-lobby- ${ lobbyId } ` );
channel . bind ( 'pusher:subscription_succeeded' , () => {
// Display all players
updatePlayerList ( channel . members );
// Check if lobby is full
if ( channel . members . count >= MAX_PLAYERS ) {
enableStartButton ();
}
});
channel . bind ( 'pusher:member_added' , ( member ) => {
// Add player to list
addPlayer ({
id: member . id ,
name: member . info . name ,
level: member . info . level ,
avatar: member . info . avatar
});
// Check if lobby is now full
if ( channel . members . count >= MAX_PLAYERS ) {
enableStartButton ();
}
});
channel . bind ( 'pusher:member_removed' , ( member ) => {
removePlayer ( member . id );
disableStartButton ();
});
Best Practices
1. Keep Member Info Small
Only include necessary information:
// Good: 200 bytes
{
user_id : '123' ,
user_info : {
name : 'John' ,
avatar : 'https://cdn.example.com/123.jpg'
}
}
// Bad: 2KB
{
user_id : '123' ,
user_info : {
name : 'John' ,
avatar : 'data:image/...' , // Large base64
bio : '...' , // Unnecessary
preferences : { ... } // Unnecessary
}
}
2. Handle Disconnections Gracefully
Users may disconnect unexpectedly:
channel . bind ( 'pusher:member_removed' , ( member ) => {
// Don't show "left" notification immediately
// User might be reconnecting
setTimeout (() => {
if ( ! channel . members . get ( member . id )) {
showNotification ( ` ${ member . info . name } left` );
}
}, 5000 ); // Wait 5 seconds
});
3. Limit Channel Size
Set appropriate limits:
{
"presence" : {
"max_members_per_channel" : 100 // Adjust based on use case
}
}
Use Case Recommended Limit Chat room 100-500 Video call 10-50 Collaborative editing 10-20 Gaming lobby 4-100
4. Implement User Deduplication
Prevent duplicate members:
app . post ( '/pusher/auth' , ( req , res ) => {
const user = req . session . user ;
// Use consistent user_id
const presenceData = {
user_id: `user- ${ user . id } ` , // Same format always
user_info: { ... }
};
// ...
});
5. Validate Member Limits
Check limits before allowing join:
channel . bind ( 'pusher:subscription_error' , ( error ) => {
if ( error . type === 'PresenceLimitReached' ) {
showError ( 'Room is full, please try again later' );
}
});
Troubleshooting
Members Not Showing Up
Check 1: Is user authentication enabled?
{
"apps" : [{
"enable_user_authentication" : true
}]
}
Check 2: Is auth endpoint configured?
const pusher = new Pusher ( 'key' , {
authEndpoint: '/pusher/auth' // Must be set
});
Check 3: Is auth signature correct?
// Verify signature format
const stringToSign = ` ${ socketId } : ${ channel } : ${ JSON . stringify ( presenceData ) } ` ;
const signature = crypto
. createHmac ( 'sha256' , secret )
. update ( stringToSign )
. digest ( 'hex' );
Channel Limit Reached
Symptom: New members can’t join
Check limit:
grep "max_members_per_channel" config/config.json
Increase limit:
{
"presence" : {
"max_members_per_channel" : 500 // Increase from 100
}
}
Member Info Too Large
Symptom: Subscription fails with size error
Check size:
const presenceData = { user_id: '123' , user_info: { ... } };
const size = JSON . stringify ( presenceData ). length ;
console . log ( 'Size:' , size , 'bytes' );
Reduce size:
// Before: 3KB
user_info : {
avatar : 'data:image/png;base64,...' , // Remove
bio : 'Long text...' // Remove
}
// After: 200 bytes
user_info : {
avatar : 'https://cdn.example.com/123.jpg' // CDN URL
}
Members Not Leaving
Symptom: Members stay in channel after disconnect
Check 1: Is connection closed properly?
window . addEventListener ( 'beforeunload' , () => {
pusher . disconnect ();
});
Check 2: Check activity timeout:
{
"activity_timeout" : 120 // Seconds until disconnect
}
Migration Guide
Enabling Presence Channels
1. Enable user authentication:
{
"apps" : [{
"enable_user_authentication" : true
}]
}
2. Implement auth endpoint:
app . post ( '/pusher/auth' , ( req , res ) => {
// Implement authentication (see above)
});
3. Update client to use presence channels:
const channel = pusher . subscribe ( 'presence-room' );
4. Handle presence events:
channel . bind ( 'pusher:member_added' , ( member ) => {
console . log ( 'Member joined:' , member );
});
Next Steps
Webhooks Set up webhooks to track presence events
Tag Filtering Filter messages with server-side tag filtering