Presence & Real-time Status
Create engaging, real-time experiences by tracking user availability, activity status, and channel presence. The social.plus SDK provides comprehensive presence management with automatic heartbeat synchronization, smart offline detection, and scalable real-time updates.Overview
Transform your application into a live, interactive platform where users can see who’s online, active in channels, and available for engagement. Whether building chat applications, collaborative tools, or social platforms, presence state drives user engagement and creates vibrant communities.Core Capabilities
- User Presence - Track online/offline status with automatic detection
- Channel Activity - Monitor member activity in conversation channels
- Smart Heartbeat - Automatic synchronization with configurable intervals
- Real-time Updates - Live presence changes across all connected clients
- Scalable Sync - Efficient batch processing for large user lists
Network Configuration: Presence features require network-level enablement. Contact the social.plus team to activate presence tracking for your application.
Quick Start
Get started with presence tracking in under 5 minutes:import { PresenceManager, UserPresenceRepository } from '@social-plus/sdk';
// Initialize presence tracking
const presenceManager = new PresenceManager();
// Enable presence for current user
await presenceManager.enable();
// Start automatic heartbeat
presenceManager.startHeartbeat();
// Track other users' presence
const userPresence = new UserPresenceRepository();
await userPresence.syncUserPresence(['user-1', 'user-2']);
// Listen for presence updates
userPresence.onPresenceChange((updates) => {
updates.forEach(update => {
console.log(`${update.userId} is ${update.isOnline ? 'online' : 'offline'}`);
});
});
Architecture
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Client Apps │ │ Presence Hub │ │ social.plus Core │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │Heartbeat │ │───▶│ │Sync Manager │ │───▶│ │Presence │ │
│ │Manager │ │ │ │& Batching │ │ │ │Database │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │Presence │ │◀───│ │Real-time │ │◀───│ │Event │ │
│ │Observer │ │ │ │Broadcaster │ │ │ │Processor │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Presence States
User Status
- Online: Active within the last 60 seconds
- Away: Last activity 1-15 minutes ago
- Offline: No activity for 15+ minutes
- Invisible: User chose to appear offline
Channel Activity
- Active: Currently viewing/participating in channel
- Idle: Channel open but not actively engaged
- Away: Not currently in channel but recently active
Platform Guides
User Presence
Track individual user online status and activity
Channel Presence
Monitor member activity in conversation channels
Heartbeat Sync
Automatic presence synchronization and lifecycle management
Implementation Examples
Basic Presence Setup
- TypeScript
- iOS
- Android
- React Native
import { AmityClient, PresenceManager } from '@social-plus/sdk';
class AppPresenceManager {
private presence: PresenceManager;
private isEnabled: boolean = false;
constructor(private client: AmityClient) {
this.presence = new PresenceManager(client);
}
async initialize() {
// Check if presence is available
const isAvailable = await this.presence.isEnabled();
if (!isAvailable) {
console.log('Presence not available for this network');
return;
}
// Enable presence tracking
try {
await this.presence.enable();
this.isEnabled = true;
// Start heartbeat
this.presence.startHeartbeat();
console.log('Presence tracking enabled');
} catch (error) {
console.error('Failed to enable presence:', error);
}
}
async cleanup() {
if (this.isEnabled) {
this.presence.stopHeartbeat();
await this.presence.disable();
this.isEnabled = false;
}
}
}
import SocialPlusSDK
class PresenceManager {
private let client: AmityClient
private var isPresenceEnabled = false
init(client: AmityClient) {
self.client = client
}
func initialize() {
// Check availability
if !client.presence.isEnabled() {
print("Presence not available")
return
}
// Enable presence
client.presence.enable { [weak self] result in
switch result {
case .success:
self?.isPresenceEnabled = true
self?.client.presence.startHeartbeat()
print("Presence enabled successfully")
case .failure(let error):
print("Failed to enable presence: \(error)")
}
}
}
func cleanup() {
if isPresenceEnabled {
client.presence.stopHeartbeat()
client.presence.disable { _ in }
isPresenceEnabled = false
}
}
}
import io.amity.client.AmityClient
import io.reactivex.rxjava3.disposables.CompositeDisposable
class PresenceManager(private val client: AmityClient) {
private val disposables = CompositeDisposable()
private var isPresenceEnabled = false
fun initialize() {
// Check if presence is available
if (!client.presence.isEnabled()) {
println("Presence not available for this network")
return
}
// Enable presence tracking
client.presence.enable()
.subscribe({
isPresenceEnabled = true
client.presence.startHeartbeat()
println("Presence tracking enabled")
}, { error ->
println("Failed to enable presence: $error")
})
.let(disposables::add)
}
fun cleanup() {
if (isPresenceEnabled) {
client.presence.stopHeartbeat()
client.presence.disable()
.subscribe({
isPresenceEnabled = false
}, { error ->
println("Error disabling presence: $error")
})
}
disposables.clear()
}
}
import { AmityClient, PresenceManager } from '@amityco/react-native-sdk';
import { useEffect, useState } from 'react';
export const usePresenceManager = (client: AmityClient) => {
const [isEnabled, setIsEnabled] = useState(false);
const [presence] = useState(() => new PresenceManager(client));
useEffect(() => {
const initializePresence = async () => {
try {
const available = await presence.isEnabled();
if (!available) return;
await presence.enable();
presence.startHeartbeat();
setIsEnabled(true);
} catch (error) {
console.error('Presence initialization failed:', error);
}
};
initializePresence();
return () => {
if (isEnabled) {
presence.stopHeartbeat();
presence.disable();
}
};
}, [presence, isEnabled]);
return { isEnabled, presence };
};
Online Users Display
- React
- SwiftUI
import React, { useEffect, useState } from 'react';
import { UserPresenceRepository, UserPresence } from '@social-plus/sdk';
interface OnlineUsersProps {
userIds: string[];
}
export const OnlineUsersList: React.FC<OnlineUsersProps> = ({ userIds }) => {
const [userPresences, setUserPresences] = useState<UserPresence[]>([]);
const [onlineCount, setOnlineCount] = useState(0);
useEffect(() => {
const presenceRepo = new UserPresenceRepository();
// Sync presence for visible users
userIds.forEach(userId => {
presenceRepo.syncUserPresence(userId);
});
// Listen for presence updates
const subscription = presenceRepo.getSyncingUserPresence()
.subscribe(presences => {
setUserPresences(presences);
setOnlineCount(presences.filter(p => p.isOnline).length);
});
return () => {
subscription.unsubscribe();
presenceRepo.unsyncAllUserPresence();
};
}, [userIds]);
return (
<div className="online-users">
<h3>Online Users ({onlineCount})</h3>
<div className="users-list">
{userPresences.map(presence => (
<div
key={presence.userId}
className={`user-item ${presence.isOnline ? 'online' : 'offline'}`}
>
<div className={`status-indicator ${presence.isOnline ? 'online' : 'offline'}`} />
<span className="user-id">{presence.userId}</span>
<span className="last-seen">
{presence.isOnline
? 'Online'
: `Last seen ${formatTime(presence.lastHeartbeat)}`
}
</span>
</div>
))}
</div>
</div>
);
};
const formatTime = (timestamp: number): string => {
const date = new Date(timestamp);
const now = new Date();
const diff = now.getTime() - date.getTime();
if (diff < 60000) return 'Just now';
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
return date.toLocaleDateString();
};
import SwiftUI
import SocialPlusSDK
struct OnlineUsersView: View {
@State private var userPresences: [AmityUserPresence] = []
@State private var onlineCount: Int = 0
let userIds: [String]
private let presenceRepository = AmityUserPresenceRepository()
var body: some View {
VStack(alignment: .leading) {
Text("Online Users (\(onlineCount))")
.font(.headline)
LazyVStack {
ForEach(userPresences, id: \.userId) { presence in
HStack {
Circle()
.fill(presence.isOnline ? Color.green : Color.gray)
.frame(width: 12, height: 12)
Text(presence.userId)
.font(.body)
Spacer()
Text(presence.isOnline ? "Online" : formatLastSeen(presence.lastHeartbeat))
.font(.caption)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
}
}
}
.onAppear {
syncUserPresence()
}
.onDisappear {
presenceRepository.unsyncAllUserPresence()
}
}
private func syncUserPresence() {
userIds.forEach { userId in
presenceRepository.syncUserPresence(userId)
}
presenceRepository.getSyncingUserPresence()
.subscribe(onNext: { presences in
self.userPresences = presences
self.onlineCount = presences.filter { $0.isOnline }.count
})
}
private func formatLastSeen(_ timestamp: Date) -> String {
let formatter = RelativeDateTimeFormatter()
return formatter.localizedString(for: timestamp, relativeTo: Date())
}
}
Presence Settings Management
The SDK provides comprehensive presence configuration options:- TypeScript
- iOS
- Android
class PresenceSettings {
private presence: PresenceManager;
constructor(presence: PresenceManager) {
this.presence = presence;
}
async checkAvailability(): Promise<boolean> {
return await this.presence.isEnabled();
}
async enablePresence(): Promise<void> {
const isAvailable = await this.checkAvailability();
if (!isAvailable) {
throw new Error('Presence not available for this network');
}
await this.presence.enable();
console.log('Presence enabled');
}
async disablePresence(): Promise<void> {
await this.presence.disable();
console.log('Presence disabled');
}
async togglePresence(): Promise<boolean> {
const isCurrentlyEnabled = await this.presence.isEnabled();
if (isCurrentlyEnabled) {
await this.disablePresence();
return false;
} else {
await this.enablePresence();
return true;
}
}
startHeartbeat(): void {
this.presence.startHeartbeat();
console.log('Heartbeat started');
}
stopHeartbeat(): void {
this.presence.stopHeartbeat();
console.log('Heartbeat stopped');
}
}
class PresenceSettings {
private let client: AmityClient
init(client: AmityClient) {
self.client = client
}
func checkAvailability() -> Bool {
return client.presence.isEnabled()
}
func enablePresence(completion: @escaping (Result<Void, Error>) -> Void) {
guard checkAvailability() else {
completion(.failure(PresenceError.notAvailable))
return
}
client.presence.enable { result in
switch result {
case .success:
print("Presence enabled")
case .failure(let error):
print("Failed to enable presence: \(error)")
}
completion(result)
}
}
func disablePresence(completion: @escaping (Result<Void, Error>) -> Void) {
client.presence.disable { result in
switch result {
case .success:
print("Presence disabled")
case .failure(let error):
print("Failed to disable presence: \(error)")
}
completion(result)
}
}
func startHeartbeat() {
client.presence.startHeartbeat()
print("Heartbeat started")
}
func stopHeartbeat() {
client.presence.stopHeartbeat()
print("Heartbeat stopped")
}
}
enum PresenceError: Error {
case notAvailable
case alreadyEnabled
case alreadyDisabled
}
class PresenceSettings(private val client: AmityClient) {
private val disposables = CompositeDisposable()
fun checkAvailability(): Boolean {
return client.presence.isEnabled()
}
fun enablePresence(): Completable {
return if (checkAvailability()) {
client.presence.enable()
.doOnComplete { println("Presence enabled") }
.doOnError { error -> println("Failed to enable presence: $error") }
} else {
Completable.error(Exception("Presence not available for this network"))
}
}
fun disablePresence(): Completable {
return client.presence.disable()
.doOnComplete { println("Presence disabled") }
.doOnError { error -> println("Failed to disable presence: $error") }
}
fun togglePresence(): Single<Boolean> {
return Single.fromCallable { checkAvailability() }
.flatMap { isEnabled ->
if (isEnabled) {
disablePresence().andThen(Single.just(false))
} else {
enablePresence().andThen(Single.just(true))
}
}
}
fun startHeartbeat() {
client.presence.startHeartbeat()
println("Heartbeat started")
}
fun stopHeartbeat() {
client.presence.stopHeartbeat()
println("Heartbeat stopped")
}
fun cleanup() {
disposables.clear()
}
}
Advanced Features
Bulk Presence Operations
Handle large-scale presence operations efficiently:class BulkPresenceManager {
private readonly MAX_SYNC_USERS = 20;
private readonly MAX_SYNC_CHANNELS = 20;
async syncMultipleUsers(userIds: string[]): Promise<void> {
const batches = this.createBatches(userIds, this.MAX_SYNC_USERS);
const presenceRepo = new UserPresenceRepository();
for (const batch of batches) {
await Promise.all(
batch.map(userId => presenceRepo.syncUserPresence(userId))
);
}
}
async getOnlineUsersSnapshot(limit: number = 100): Promise<OnlineUsersSnapshot> {
const presenceRepo = new UserPresenceRepository();
const snapshot = await presenceRepo.getOnlineUsersSnapshot();
return {
users: snapshot.users.slice(0, limit),
totalCount: await presenceRepo.getOnlineUsersCount(),
canLoadMore: snapshot.canLoadMore,
loadMore: () => snapshot.loadMore()
};
}
private createBatches<T>(items: T[], batchSize: number): T[][] {
const batches: T[][] = [];
for (let i = 0; i < items.length; i += batchSize) {
batches.push(items.slice(i, i + batchSize));
}
return batches;
}
}
Smart Presence Detection
Implement intelligent presence detection with activity monitoring:class SmartPresenceDetector {
private activityTimer: NodeJS.Timeout | null = null;
private isUserActive = true;
private readonly IDLE_THRESHOLD = 5 * 60 * 1000; // 5 minutes
constructor(private presenceManager: PresenceManager) {
this.setupActivityListeners();
}
private setupActivityListeners(): void {
// Browser/Web environment
if (typeof window !== 'undefined') {
['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'].forEach(event => {
document.addEventListener(event, () => this.resetActivityTimer(), { passive: true });
});
}
}
private resetActivityTimer(): void {
if (this.activityTimer) {
clearTimeout(this.activityTimer);
}
// If user was idle, mark as active again
if (!this.isUserActive) {
this.isUserActive = true;
this.presenceManager.startHeartbeat();
}
// Set new idle timer
this.activityTimer = setTimeout(() => {
this.isUserActive = false;
this.presenceManager.stopHeartbeat();
}, this.IDLE_THRESHOLD);
}
getActivityStatus(): { isActive: boolean; lastActivity: Date } {
return {
isActive: this.isUserActive,
lastActivity: new Date(Date.now() - (this.isUserActive ? 0 : this.IDLE_THRESHOLD))
};
}
cleanup(): void {
if (this.activityTimer) {
clearTimeout(this.activityTimer);
}
}
}
Presence Analytics
Track presence patterns and user engagement:class PresenceAnalytics {
private sessionStart: Date = new Date();
private presenceEvents: PresenceEvent[] = [];
trackPresenceChange(userId: string, isOnline: boolean, context?: string): void {
this.presenceEvents.push({
userId,
isOnline,
timestamp: new Date(),
context,
type: 'presence_change'
});
}
getSessionStats(): SessionStats {
const now = new Date();
const sessionDuration = now.getTime() - this.sessionStart.getTime();
return {
sessionDuration,
totalPresenceChanges: this.presenceEvents.length,
uniqueUsersTracked: new Set(this.presenceEvents.map(e => e.userId)).size,
averageOnlineTime: this.calculateAverageOnlineTime(),
peakOnlineUsers: this.calculatePeakOnlineUsers()
};
}
private calculateAverageOnlineTime(): number {
const userSessions = new Map<string, { start: Date, end?: Date }[]>();
this.presenceEvents.forEach(event => {
if (!userSessions.has(event.userId)) {
userSessions.set(event.userId, []);
}
const sessions = userSessions.get(event.userId)!;
if (event.isOnline) {
sessions.push({ start: event.timestamp });
} else {
const lastSession = sessions[sessions.length - 1];
if (lastSession && !lastSession.end) {
lastSession.end = event.timestamp;
}
}
});
// Calculate average session duration
let totalDuration = 0;
let sessionCount = 0;
userSessions.forEach(sessions => {
sessions.forEach(session => {
if (session.end) {
totalDuration += session.end.getTime() - session.start.getTime();
sessionCount++;
}
});
});
return sessionCount > 0 ? totalDuration / sessionCount : 0;
}
private calculatePeakOnlineUsers(): number {
const timeSlots = new Map<number, Set<string>>();
const SLOT_DURATION = 60000; // 1 minute slots
this.presenceEvents.forEach(event => {
const slot = Math.floor(event.timestamp.getTime() / SLOT_DURATION);
if (!timeSlots.has(slot)) {
timeSlots.set(slot, new Set());
}
if (event.isOnline) {
timeSlots.get(slot)!.add(event.userId);
}
});
return Math.max(...Array.from(timeSlots.values()).map(users => users.size));
}
}
interface PresenceEvent {
userId: string;
isOnline: boolean;
timestamp: Date;
context?: string;
type: string;
}
interface SessionStats {
sessionDuration: number;
totalPresenceChanges: number;
uniqueUsersTracked: number;
averageOnlineTime: number;
peakOnlineUsers: number;
}
Best Practices
Performance Optimization
Performance Optimization
Efficient Sync Management: Only sync presence for visible users to minimize resource usage.Smart Heartbeat Management: Adjust heartbeat frequency based on app state.
class OptimizedPresenceManager {
private visibleUsers = new Set<string>();
private syncedUsers = new Set<string>();
// Only sync users that are actually visible
updateVisibleUsers(userIds: string[]): void {
const newVisibleUsers = new Set(userIds);
// Unsync users no longer visible
this.syncedUsers.forEach(userId => {
if (!newVisibleUsers.has(userId)) {
this.unsyncUserPresence(userId);
this.syncedUsers.delete(userId);
}
});
// Sync newly visible users
newVisibleUsers.forEach(userId => {
if (!this.syncedUsers.has(userId)) {
this.syncUserPresence(userId);
this.syncedUsers.add(userId);
}
});
this.visibleUsers = newVisibleUsers;
}
// Batch operations for better performance
private async syncUserPresence(userId: string): Promise<void> {
// Implementation with batching logic
}
}
class AdaptiveHeartbeat {
private heartbeatInterval: number = 30000; // Default 30s
adjustHeartbeatFrequency(appState: 'active' | 'background' | 'inactive'): void {
switch (appState) {
case 'active':
this.heartbeatInterval = 20000; // 20s for active usage
break;
case 'background':
this.heartbeatInterval = 60000; // 60s for background
break;
case 'inactive':
this.heartbeatInterval = 120000; // 2min for inactive
break;
}
}
}
User Experience
User Experience
Progressive Loading: Show presence information as it becomes available.
interface PresenceUIState {
loading: boolean;
users: UserWithPresence[];
error?: string;
}
const PresenceList: React.FC = () => {
const [state, setState] = useState<PresenceUIState>({
loading: true,
users: []
});
useEffect(() => {
const presenceRepo = new UserPresenceRepository();
// Show loading state
setState(prev => ({ ...prev, loading: true }));
// Subscribe to presence updates
const subscription = presenceRepo.getSyncingUserPresence()
.subscribe({
next: (presences) => {
setState(prev => ({
...prev,
loading: false,
users: mergePresenceData(prev.users, presences)
}));
},
error: (error) => {
setState(prev => ({
...prev,
loading: false,
error: error.message
}));
}
});
return () => subscription.unsubscribe();
}, []);
return (
<div>
{state.loading && <LoadingSkeleton />}
{state.users.map(user => (
<UserPresenceItem key={user.id} user={user} />
))}
</div>
);
};
Error Handling & Reliability
Error Handling & Reliability
Robust Error Recovery: Handle network interruptions and service errors gracefully.
class ResilientPresenceManager {
private retryAttempts = 0;
private readonly maxRetries = 3;
private readonly retryDelays = [1000, 3000, 5000];
async enablePresenceWithRetry(): Promise<void> {
try {
await this.presenceManager.enable();
this.retryAttempts = 0; // Reset on success
} catch (error) {
if (this.retryAttempts < this.maxRetries) {
const delay = this.retryDelays[this.retryAttempts];
await this.delay(delay);
this.retryAttempts++;
return this.enablePresenceWithRetry();
}
throw new Error('Failed to enable presence after multiple attempts');
}
}
private handleNetworkError(error: any): void {
if (error.code === 'NETWORK_ERROR') {
// Implement exponential backoff
this.scheduleRetry();
} else if (error.code === 'UNAUTHORIZED') {
// Handle auth issues
this.handleAuthError();
}
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Security & Privacy
Security & Privacy
Privacy Controls: Respect user privacy preferences and provide granular controls.
class PrivacyAwarePresence {
private privacySettings: PresencePrivacySettings;
constructor(settings: PresencePrivacySettings) {
this.privacySettings = settings;
}
shouldShowPresence(userId: string, context: string): boolean {
// Check user privacy settings
if (!this.privacySettings.allowPresenceSharing) {
return false;
}
// Check context-specific settings
if (context === 'channel' && !this.privacySettings.allowChannelPresence) {
return false;
}
// Check blocked users
if (this.privacySettings.blockedUsers.includes(userId)) {
return false;
}
return true;
}
getFilteredPresence(presences: UserPresence[]): UserPresence[] {
return presences.filter(presence =>
this.shouldShowPresence(presence.userId, 'general')
);
}
}
interface PresencePrivacySettings {
allowPresenceSharing: boolean;
allowChannelPresence: boolean;
blockedUsers: string[];
invisibleMode: boolean;
}
Common Use Cases
Chat Applications
Show who’s online and active in conversations:const ChatPresenceIndicator: React.FC<{ userId: string }> = ({ userId }) => {
const [presence, setPresence] = useState<UserPresence | null>(null);
useEffect(() => {
const repo = new UserPresenceRepository();
repo.syncUserPresence(userId);
const subscription = repo.getSyncingUserPresence()
.subscribe(presences => {
const userPresence = presences.find(p => p.userId === userId);
setPresence(userPresence || null);
});
return () => {
subscription.unsubscribe();
repo.unsyncUserPresence(userId);
};
}, [userId]);
return (
<div className="presence-indicator">
<div className={`status-dot ${presence?.isOnline ? 'online' : 'offline'}`} />
<span className="status-text">
{presence ? (presence.isOnline ? 'Online' : 'Offline') : 'Unknown'}
</span>
</div>
);
};
Team Collaboration
Monitor team member availability:const TeamPresenceBoard: React.FC<{ teamId: string }> = ({ teamId }) => {
const [teamMembers, setTeamMembers] = useState<TeamMember[]>([]);
const [presenceData, setPresenceData] = useState<Map<string, UserPresence>>(new Map());
const organizedMembers = useMemo(() => {
const online = teamMembers.filter(member =>
presenceData.get(member.userId)?.isOnline
);
const offline = teamMembers.filter(member =>
!presenceData.get(member.userId)?.isOnline
);
return { online, offline };
}, [teamMembers, presenceData]);
return (
<div className="team-presence-board">
<section className="online-section">
<h3>Online ({organizedMembers.online.length})</h3>
{organizedMembers.online.map(member => (
<TeamMemberCard key={member.userId} member={member} isOnline={true} />
))}
</section>
<section className="offline-section">
<h3>Offline ({organizedMembers.offline.length})</h3>
{organizedMembers.offline.map(member => (
<TeamMemberCard key={member.userId} member={member} isOnline={false} />
))}
</section>
</div>
);
};
Live Events
Track participant engagement in live sessions:class LiveEventPresence {
private participantPresence = new Map<string, ParticipantStatus>();
async trackEventParticipants(eventId: string, participantIds: string[]): Promise<void> {
const channelRepo = new ChannelPresenceRepository();
// Sync channel presence for the event
await channelRepo.syncChannelPresence(eventId);
// Track individual participants
const userRepo = new UserPresenceRepository();
participantIds.forEach(id => userRepo.syncUserPresence(id));
// Listen for presence changes
userRepo.getSyncingUserPresence().subscribe(presences => {
presences.forEach(presence => {
this.participantPresence.set(presence.userId, {
isOnline: presence.isOnline,
lastSeen: presence.lastHeartbeat,
engagementLevel: this.calculateEngagement(presence)
});
});
this.notifyPresenceUpdate();
});
}
private calculateEngagement(presence: UserPresence): 'high' | 'medium' | 'low' {
const timeSinceLastSeen = Date.now() - presence.lastHeartbeat;
if (timeSinceLastSeen < 30000) return 'high'; // < 30s
if (timeSinceLastSeen < 120000) return 'medium'; // < 2min
return 'low';
}
getEventStats(): EventPresenceStats {
const participants = Array.from(this.participantPresence.values());
return {
totalParticipants: participants.length,
onlineCount: participants.filter(p => p.isOnline).length,
highEngagement: participants.filter(p => p.engagementLevel === 'high').length,
mediumEngagement: participants.filter(p => p.engagementLevel === 'medium').length,
lowEngagement: participants.filter(p => p.engagementLevel === 'low').length
};
}
}
Troubleshooting
Presence Not Working
Presence Not Working
Network Configuration: Ensure presence is enabled at the network level.
const debugPresence = async () => {
const presence = new PresenceManager();
// Check if feature is available
const isAvailable = await presence.isEnabled();
console.log('Presence available:', isAvailable);
if (!isAvailable) {
console.error('Presence not enabled for this network. Contact social.plus support.');
return;
}
// Check user-level settings
try {
await presence.enable();
console.log('Presence enabled successfully');
} catch (error) {
console.error('Failed to enable presence:', error);
}
};
Heartbeat Issues
Heartbeat Issues
Connection Problems: Debug heartbeat synchronization failures.
class HeartbeatDiagnostics {
private heartbeatCount = 0;
private failureCount = 0;
startDiagnostics(presence: PresenceManager): void {
const originalStartHeartbeat = presence.startHeartbeat.bind(presence);
const originalStopHeartbeat = presence.stopHeartbeat.bind(presence);
presence.startHeartbeat = () => {
console.log('Starting heartbeat...');
try {
originalStartHeartbeat();
this.heartbeatCount++;
console.log(`Heartbeat started (count: ${this.heartbeatCount})`);
} catch (error) {
this.failureCount++;
console.error(`Heartbeat start failed (failures: ${this.failureCount}):`, error);
}
};
presence.stopHeartbeat = () => {
console.log('Stopping heartbeat...');
originalStopHeartbeat();
};
}
getStats(): { heartbeats: number; failures: number; successRate: number } {
const total = this.heartbeatCount + this.failureCount;
return {
heartbeats: this.heartbeatCount,
failures: this.failureCount,
successRate: total > 0 ? (this.heartbeatCount / total) * 100 : 0
};
}
}
Performance Issues
Performance Issues
Sync Limit Exceeded: Handle too many concurrent presence syncs.
class PresenceSyncManager {
private syncedUsers = new Set<string>();
private syncQueue: string[] = [];
private readonly MAX_CONCURRENT_SYNCS = 20;
async queueUserSync(userId: string): Promise<void> {
if (this.syncedUsers.size >= this.MAX_CONCURRENT_SYNCS) {
this.syncQueue.push(userId);
console.warn(`Sync queue full. Queued user ${userId}`);
return;
}
await this.syncUserPresence(userId);
}
private async syncUserPresence(userId: string): Promise<void> {
if (this.syncedUsers.has(userId)) return;
try {
const repo = new UserPresenceRepository();
await repo.syncUserPresence(userId);
this.syncedUsers.add(userId);
} catch (error) {
console.error(`Failed to sync user ${userId}:`, error);
}
}
async unsyncUserPresence(userId: string): Promise<void> {
if (!this.syncedUsers.has(userId)) return;
const repo = new UserPresenceRepository();
await repo.unsyncUserPresence(userId);
this.syncedUsers.delete(userId);
// Process queue if space available
if (this.syncQueue.length > 0 && this.syncedUsers.size < this.MAX_CONCURRENT_SYNCS) {
const nextUserId = this.syncQueue.shift()!;
await this.syncUserPresence(nextUserId);
}
}
}
Next Steps
User Presence
Deep dive into individual user presence tracking and management
Channel Presence
Monitor member activity and engagement in conversation channels
Heartbeat Sync
Understand automatic presence synchronization and lifecycle management
Real-time Events
Learn about real-time event handling and reactive programming patterns
Production Ready: All examples include comprehensive error handling, performance optimizations, and privacy considerations suitable for production applications.