Skip to main content

Heartbeat Synchronization

Maintain accurate presence status with automatic heartbeat synchronization that intelligently adapts to application state, network conditions, and user activity. The social.plus SDK provides robust heartbeat management with smart intervals and comprehensive lifecycle handling.

Overview

Heartbeat synchronization is the foundation of presence tracking, automatically signaling to the server that a user is active and available. The SDK handles all the complexity of heartbeat management, including adaptive intervals, network resilience, and application lifecycle events.

Quick Start

Enable automatic heartbeat synchronization in seconds:
import { PresenceManager } from '@social-plus/sdk';

// Initialize presence manager
const presence = new PresenceManager();

// Enable presence and start heartbeat
await presence.enable();
presence.startHeartbeat();

// Heartbeat now runs automatically every 20-30 seconds

// Stop when user goes offline
presence.stopHeartbeat();

Heartbeat Lifecycle

The heartbeat system automatically manages presence state throughout the application lifecycle:

Automatic Heartbeat Management

import { PresenceManager, HeartbeatConfig } from '@social-plus/sdk';

class ApplicationPresenceManager {
  private presence: PresenceManager;
  private isHeartbeatActive = false;

  constructor() {
    this.presence = new PresenceManager();
    this.setupApplicationLifecycle();
  }

  async initialize(): Promise<void> {
    try {
      // Check if presence is available
      const isAvailable = await this.presence.isEnabled();
      if (!isAvailable) {
        console.log('Presence not available for this network');
        return;
      }

      // Enable presence
      await this.presence.enable();
      
      // Start heartbeat
      this.startHeartbeat();
      
      console.log('Presence and heartbeat initialized');
    } catch (error) {
      console.error('Failed to initialize presence:', error);
    }
  }

  startHeartbeat(): void {
    if (this.isHeartbeatActive) {
      console.log('Heartbeat already active');
      return;
    }

    try {
      this.presence.startHeartbeat();
      this.isHeartbeatActive = true;
      console.log('Heartbeat started - syncing every 20-30 seconds');
    } catch (error) {
      console.error('Failed to start heartbeat:', error);
    }
  }

  stopHeartbeat(): void {
    if (!this.isHeartbeatActive) {
      console.log('Heartbeat already stopped');
      return;
    }

    try {
      this.presence.stopHeartbeat();
      this.isHeartbeatActive = false;
      console.log('Heartbeat stopped');
    } catch (error) {
      console.error('Failed to stop heartbeat:', error);
    }
  }

  private setupApplicationLifecycle(): void {
    // Browser/Web environment
    if (typeof window !== 'undefined') {
      // Stop heartbeat when page is hidden
      document.addEventListener('visibilitychange', () => {
        if (document.hidden) {
          this.stopHeartbeat();
        } else {
          this.startHeartbeat();
        }
      });

      // Handle page unload
      window.addEventListener('beforeunload', () => {
        this.stopHeartbeat();
      });

      // Handle online/offline events
      window.addEventListener('online', () => {
        if (!this.isHeartbeatActive) {
          this.startHeartbeat();
        }
      });

      window.addEventListener('offline', () => {
        this.stopHeartbeat();
      });
    }
  }

  // Get heartbeat status
  getHeartbeatStatus(): { isActive: boolean; lastSync?: Date } {
    return {
      isActive: this.isHeartbeatActive,
      lastSync: this.presence.getLastHeartbeatSync()
    };
  }

  async cleanup(): Promise<void> {
    this.stopHeartbeat();
    await this.presence.disable();
  }
}
Automatic Intervals: The SDK automatically determines optimal heartbeat intervals (typically 20-30 seconds) based on network conditions and server configuration. Manual interval configuration is not required.

Smart Heartbeat Features

Adaptive Heartbeat Management

The SDK provides intelligent heartbeat management that adapts to different application states:
class AdaptiveHeartbeatManager {
  private presence: PresenceManager;
  private activityMonitor: ActivityMonitor;
  private currentState: AppState = 'active';

  constructor() {
    this.presence = new PresenceManager();
    this.activityMonitor = new ActivityMonitor();
    this.setupAdaptiveHeartbeat();
  }

  private setupAdaptiveHeartbeat(): void {
    // Monitor user activity
    this.activityMonitor.onActivityChange((isActive) => {
      if (isActive && this.currentState === 'active') {
        this.ensureHeartbeatRunning();
      } else if (!isActive) {
        this.handleInactivity();
      }
    });

    // Monitor app state changes
    this.monitorAppState();
  }

  private ensureHeartbeatRunning(): void {
    if (!this.presence.isHeartbeatActive()) {
      this.presence.startHeartbeat();
      console.log('Heartbeat resumed due to activity');
    }
  }

  private handleInactivity(): void {
    // Keep heartbeat running but reduce frequency on mobile
    if (this.isMobileEnvironment()) {
      console.log('User inactive on mobile - heartbeat continues with standard interval');
    }
  }

  private monitorAppState(): void {
    // Platform-specific app state monitoring
    if (typeof document !== 'undefined') {
      // Web environment
      document.addEventListener('visibilitychange', () => {
        this.currentState = document.hidden ? 'background' : 'active';
        this.handleStateChange();
      });
    }
  }

  private handleStateChange(): void {
    switch (this.currentState) {
      case 'active':
        this.presence.startHeartbeat();
        break;
      case 'background':
        // Mobile: continue heartbeat for a while
        // Web: stop heartbeat
        if (!this.isMobileEnvironment()) {
          this.presence.stopHeartbeat();
        }
        break;
      case 'inactive':
        this.presence.stopHeartbeat();
        break;
    }
  }

  private isMobileEnvironment(): boolean {
    return /Mobi|Android/i.test(navigator.userAgent);
  }
}

type AppState = 'active' | 'background' | 'inactive';

class ActivityMonitor {
  private isActive = true;
  private inactivityTimer: NodeJS.Timeout | null = null;
  private readonly INACTIVITY_THRESHOLD = 5 * 60 * 1000; // 5 minutes
  private listeners: ((isActive: boolean) => void)[] = [];

  constructor() {
    this.setupActivityListeners();
  }

  private setupActivityListeners(): void {
    if (typeof window !== 'undefined') {
      const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart', 'click'];
      
      events.forEach(event => {
        document.addEventListener(event, () => this.resetInactivityTimer(), { passive: true });
      });
    }
  }

  private resetInactivityTimer(): void {
    if (this.inactivityTimer) {
      clearTimeout(this.inactivityTimer);
    }

    if (!this.isActive) {
      this.isActive = true;
      this.notifyListeners(true);
    }

    this.inactivityTimer = setTimeout(() => {
      this.isActive = false;
      this.notifyListeners(false);
    }, this.INACTIVITY_THRESHOLD);
  }

  onActivityChange(callback: (isActive: boolean) => void): void {
    this.listeners.push(callback);
  }

  private notifyListeners(isActive: boolean): void {
    this.listeners.forEach(listener => listener(isActive));
  }

  cleanup(): void {
    if (this.inactivityTimer) {
      clearTimeout(this.inactivityTimer);
    }
    this.listeners = [];
  }
}