Events & Real-time Updates

The Asterisms JS SDK provides a comprehensive event system that enables real-time updates and reactive programming patterns. This guide covers how to work with events, observables, and real-time data flows.

Event System Overview

The SDK uses an observer pattern with strongly-typed events for:

  • Authentication State Changes: Login/logout, token refresh
  • Resource Updates: File uploads, drive operations, notifications
  • Real-time Data: Live updates from backend services
  • Error Handling: Centralized error event handling
  • Progress Tracking: Upload progress, batch operation status

Core Event Concepts

Event Subscription

// Basic event subscription
const unsubscribe = sdk.auth.onAuthStateChanged((state) => {
  console.log('Authentication changed:', state);
});

// Unsubscribe when done
unsubscribe();

Event Types

// Strongly-typed events
interface AuthStateEvent {
  isAuthenticated: boolean;
  user?: User;
  timestamp: Date;
}

interface UploadProgressEvent {
  fileId: string;
  progress: {
    percentage: number;
    loaded: number;
    total: number;
  };
}

Authentication Events

Monitor authentication state and token lifecycle:

// Authentication state changes
sdk.auth.onAuthStateChanged((state) => {
  if (state.isAuthenticated) {
    console.log('User logged in:', state.user);
    // Update UI, redirect to dashboard
  } else {
    console.log('User logged out');
    // Clear user data, redirect to login
  }
});

// Token refresh events
sdk.auth.onTokenRefresh((tokenInfo) => {
  console.log('Token refreshed:', tokenInfo.expiresAt);
});

// Authentication errors
sdk.auth.onAuthError((error) => {
  console.error('Auth error:', error);
  if (error.code === 'TOKEN_EXPIRED') {
    // Handle token expiration
  }
});

// Login attempts
sdk.auth.onLoginAttempt((attempt) => {
  console.log('Login attempt:', attempt.provider, attempt.status);
});

React Integration Example

import { useEffect, useState } from 'react';
import { sdk } from './sdk';

function useAuth() {
  const [authState, setAuthState] = useState({
    isAuthenticated: false,
    user: null,
    loading: true
  });

  useEffect(() => {
    const unsubscribe = sdk.auth.onAuthStateChanged((state) => {
      setAuthState({
        isAuthenticated: state.isAuthenticated,
        user: state.user,
        loading: false
      });
    });

    return unsubscribe;
  }, []);

  return authState;
}

// Usage in component
function App() {
  const { isAuthenticated, user, loading } = useAuth();

  if (loading) return <div>Loading...</div>;

  return isAuthenticated ?
    <Dashboard user={user} /> :
    <LoginForm />;
}

Storage & Upload Events

Track file uploads and storage operations:

// Upload progress for individual files
sdk.storage.onUploadProgress((progress) => {
  console.log(`File ${progress.fileId}: ${progress.percentage}%`);

  // Update progress bar
  updateProgressBar(progress.fileId, progress.percentage);
});

// Upload completion
sdk.storage.onUploadComplete((result) => {
  console.log('Upload completed:', result.fileId, result.url);

  // Show success notification
  showNotification('File uploaded successfully!');
});

// Upload errors
sdk.storage.onUploadError((error) => {
  console.error('Upload failed:', error.fileId, error.error);

  // Show error message
  showError(`Failed to upload ${error.fileName}: ${error.error.message}`);
});

// Batch upload progress
sdk.storage.onBatchProgress((batch) => {
  const progress = (batch.completedCount / batch.totalCount) * 100;
  console.log(`Batch upload: ${progress}% (${batch.completedCount}/${batch.totalCount})`);
});

Upload Progress Component

import { useEffect, useState } from 'react';

function UploadProgress({ fileId }) {
  const [progress, setProgress] = useState(0);
  const [status, setStatus] = useState('uploading');

  useEffect(() => {
    const unsubscribeProgress = sdk.storage.onUploadProgress((progressEvent) => {
      if (progressEvent.fileId === fileId) {
        setProgress(progressEvent.percentage);
      }
    });

    const unsubscribeComplete = sdk.storage.onUploadComplete((result) => {
      if (result.fileId === fileId) {
        setStatus('completed');
        setProgress(100);
      }
    });

    const unsubscribeError = sdk.storage.onUploadError((error) => {
      if (error.fileId === fileId) {
        setStatus('error');
      }
    });

    return () => {
      unsubscribeProgress();
      unsubscribeComplete();
      unsubscribeError();
    };
  }, [fileId]);

  return (
    <div className="upload-progress">
      <div className="progress-bar">
        <div
          className="progress-fill"
          style={{ width: `${progress}%` }}
        />
      </div>
      <div className="status">{status}: {progress}%</div>
    </div>
  );
}

Drive Events

Monitor file system operations and sharing activities:

// File/folder operations
sdk.drive.onItemCreated((item) => {
  console.log('New item created:', item.name, item.type);
  // Refresh file list
});

sdk.drive.onItemUpdated((item) => {
  console.log('Item updated:', item.id, item.name);
  // Update item in UI
});

sdk.drive.onItemDeleted((itemId) => {
  console.log('Item deleted:', itemId);
  // Remove from UI
});

// Sharing events
sdk.drive.onItemShared((shareEvent) => {
  console.log('Item shared:', shareEvent.itemId, 'with', shareEvent.sharedWith);
  // Update sharing indicators
});

sdk.drive.onPermissionChanged((permissionEvent) => {
  console.log('Permission changed:', permissionEvent.itemId, permissionEvent.newPermission);
  // Update UI permissions
});

// Folder synchronization
sdk.drive.onFolderSynced((folder) => {
  console.log('Folder synced:', folder.id, folder.syncStatus);
  // Update sync indicators
});

Notification Events

Handle real-time notifications and messages:

// New notifications
sdk.notification.onNotificationReceived((notification) => {
  console.log('New notification:', notification);

  // Show notification in UI
  showNotification({
    id: notification.id,
    title: notification.title,
    message: notification.message,
    type: notification.type,
    timestamp: notification.timestamp
  });

  // Play notification sound
  if (notification.priority === 'high') {
    playNotificationSound();
  }
});

// Notification read status changes
sdk.notification.onNotificationRead((notificationId) => {
  console.log('Notification marked as read:', notificationId);
  // Update UI read status
});

// Bulk notification operations
sdk.notification.onBulkOperation((operation) => {
  console.log('Bulk operation:', operation.type, operation.count);
  // Update notification counters
});

Notification Center Component

import { useEffect, useState } from 'react';

function NotificationCenter() {
  const [notifications, setNotifications] = useState([]);
  const [unreadCount, setUnreadCount] = useState(0);

  useEffect(() => {
    // Load initial notifications
    sdk.notification.getNotifications().then(setNotifications);

    // Subscribe to new notifications
    const unsubscribe = sdk.notification.onNotificationReceived((notification) => {
      setNotifications(prev => [notification, ...prev]);
      setUnreadCount(prev => prev + 1);

      // Show browser notification if permission granted
      if (Notification.permission === 'granted') {
        new Notification(notification.title, {
          body: notification.message,
          icon: '/notification-icon.png'
        });
      }
    });

    // Subscribe to read status changes
    const unsubscribeRead = sdk.notification.onNotificationRead((notificationId) => {
      setNotifications(prev =>
        prev.map(n =>
          n.id === notificationId ? { ...n, read: true } : n
        )
      );
      setUnreadCount(prev => Math.max(0, prev - 1));
    });

    return () => {
      unsubscribe();
      unsubscribeRead();
    };
  }, []);

  const markAsRead = (notificationId) => {
    sdk.notification.markAsRead([notificationId]);
  };

  return (
    <div className="notification-center">
      <h3>Notifications ({unreadCount})</h3>
      {notifications.map(notification => (
        <div
          key={notification.id}
          className={`notification ${notification.read ? 'read' : 'unread'}`}
          onClick={() => markAsRead(notification.id)}
        >
          <h4>{notification.title}</h4>
          <p>{notification.message}</p>
          <span className="timestamp">
            {new Date(notification.timestamp).toLocaleString()}
          </span>
        </div>
      ))}
    </div>
  );
}

Platform Events

Monitor platform-wide events and system status:

// Application events
sdk.platform.onApplicationLaunched((app) => {
  console.log('Application launched:', app.id, app.name);
});

sdk.platform.onApplicationClosed((appId) => {
  console.log('Application closed:', appId);
});

// System status changes
sdk.platform.onStatusChanged((status) => {
  console.log('Platform status:', status);
  if (status === 'maintenance') {
    showMaintenanceNotice();
  }
});

// Feature availability changes
sdk.platform.onFeatureChanged((feature) => {
  console.log('Feature changed:', feature.name, feature.enabled);
  // Update UI based on feature availability
});

Custom Events

Create and handle custom events for your application:

// Define custom event types
interface CustomEvent {
  type: string;
  data: any;
  timestamp: Date;
}

// Create custom event emitter
class CustomEventEmitter {
  private listeners = new Map<string, Function[]>();

  on(eventType: string, callback: Function) {
    if (!this.listeners.has(eventType)) {
      this.listeners.set(eventType, []);
    }
    this.listeners.get(eventType)!.push(callback);

    // Return unsubscribe function
    return () => {
      const callbacks = this.listeners.get(eventType);
      if (callbacks) {
        const index = callbacks.indexOf(callback);
        if (index > -1) {
          callbacks.splice(index, 1);
        }
      }
    };
  }

  emit(eventType: string, data: any) {
    const callbacks = this.listeners.get(eventType);
    if (callbacks) {
      callbacks.forEach((callback) => callback(data));
    }
  }
}

// Usage
const eventEmitter = new CustomEventEmitter();

const unsubscribe = eventEmitter.on('data-updated', (data) => {
  console.log('Data updated:', data);
});

eventEmitter.emit('data-updated', { id: 1, name: 'Updated Item' });

Event Debugging

Debug events and troubleshoot issues:

// Enable event debugging
sdk.enableEventDebugging(true);

// Log all events
sdk.onAnyEvent((event) => {
  console.log('Event:', event.type, event.data, event.timestamp);
});

// Track event performance
sdk.onEventPerformance((metrics) => {
  console.log('Event performance:', metrics);
  if (metrics.duration > 1000) {
    console.warn('Slow event handler:', metrics.eventType, metrics.duration);
  }
});

// Error tracking
sdk.onEventError((error) => {
  console.error('Event error:', error.eventType, error.error);
  // Send to error tracking service
});

Event Best Practices

1. Proper Cleanup

// Always clean up event subscriptions
useEffect(() => {
  const unsubscribe = sdk.auth.onAuthStateChanged(handleAuthChange);

  return () => {
    unsubscribe(); // Prevent memory leaks
  };
}, []);

2. Error Boundaries

// Wrap event handlers in try-catch
sdk.notification.onNotificationReceived((notification) => {
  try {
    handleNotification(notification);
  } catch (error) {
    console.error('Error handling notification:', error);
    // Fallback behavior
  }
});

3. Debouncing

import { debounce } from 'lodash';

// Debounce frequent events
const debouncedHandler = debounce((progress) => {
  updateProgressBar(progress);
}, 100);

sdk.storage.onUploadProgress(debouncedHandler);

4. Conditional Subscriptions

// Subscribe only when needed
useEffect(() => {
  if (isUploading) {
    const unsubscribe = sdk.storage.onUploadProgress(handleProgress);
    return unsubscribe;
  }
}, [isUploading]);

Framework-Specific Patterns

SvelteKit

// src/lib/stores/auth.ts
import { writable } from 'svelte/store';
import { sdk } from './sdk';

function createAuthStore() {
  const { subscribe, set } = writable({
    isAuthenticated: false,
    user: null
  });

  // Subscribe to auth changes
  sdk.auth.onAuthStateChanged((state) => {
    set({
      isAuthenticated: state.isAuthenticated,
      user: state.user
    });
  });

  return { subscribe };
}

export const auth = createAuthStore();

Vue.js

// composables/useEvents.js
import { ref, onMounted, onUnmounted } from 'vue';
import { sdk } from './sdk';

export function useAuthState() {
  const authState = ref({
    isAuthenticated: false,
    user: null
  });

  let unsubscribe;

  onMounted(() => {
    unsubscribe = sdk.auth.onAuthStateChanged((state) => {
      authState.value = state;
    });
  });

  onUnmounted(() => {
    if (unsubscribe) {
      unsubscribe();
    }
  });

  return { authState };
}

Next Steps