Live Updates Service
A flexible, configuration-driven real-time update service that supports both Server-Sent Events (SSE) and WebSocket transports. The service is transport-agnostic and works with any authentication provider.
Overview
The Live Updates service provides:
- Dual Transport Support: Automatic fallback from SSE to WebSocket
- Pluggable Authentication: Works with any auth provider via the
IAuthProviderinterface - Flexible Event System: Define your own event types and action names per project
- Configuration-Driven: No hardcoded business logic or event mappings
- React Integration: Hooks and context provider for seamless React integration
- Automatic Reconnection: Built-in reconnection logic with exponential backoff
Architecture
Core Services
LiveUpdateService
The main service that coordinates SSE and WebSocket services:
interface LiveUpdateConfig {
sseEnabled: boolean;
wsEnabled: boolean;
authProvider: IAuthProvider;
eventMappings?: Record<string, EventMapping>;
}
interface EventMapping {
sseEvent?: SSEEventType;
wsAction?: WS_ACTIONS;
}
SSEService
Server-Sent Events implementation with pluggable auth:
interface SSEConfig {
url: string;
authProvider: IAuthProvider;
headerName?: string; // Default: 'Authorization'
headerPrefix?: string; // Default: 'Bearer'
}
export type SSEEventType = string; // Flexible - define your own event types
WebsocketService
WebSocket implementation with pluggable auth:
interface WebsocketConfig {
url: string;
authProvider: IAuthProvider;
}
export type WS_ACTIONS = string; // Flexible - define your own action types
Installation
npm install @codella-software/utils
Setup
Core Service Usage
1. Initialize with Configuration
import { LiveUpdateService, type LiveUpdateConfig } from '@codella-software/utils';
import { firebaseAuthProvider } from './auth-provider'; // Your IAuthProvider
const config: LiveUpdateConfig = {
sseEnabled: true,
wsEnabled: true,
authProvider: firebaseAuthProvider,
eventMappings: {
notifications: {
sseEvent: 'GET_NOTIFICATIONS',
wsAction: 'NOTIFICATIONS_RESPONSE'
},
userUpdates: {
sseEvent: 'UPDATE_USER',
wsAction: 'UPDATE_USER'
},
invoices: {
sseEvent: 'INVOICE_FOR_VERIFICATION_RESPONSE',
wsAction: 'INVOICE_FOR_VERIFICATION_RESPONSE'
}
}
};
const liveUpdateService = new LiveUpdateService(config);
2. Connect to Server
await liveUpdateService.initialize();
3. Listen to Events
// Listen to specific event by name
liveUpdateService.on('notifications').subscribe((data) => {
console.log('Notification received:', data);
});
// Send WebSocket request
liveUpdateService.send('GET_NOTIFICATIONS', { filter: 'unread' });
// Check connection status
liveUpdateService.onConnectionStatusChange().subscribe((isConnected) => {
console.log('Connected:', isConnected);
});
// Cleanup
liveUpdateService.destroy();
React Integration
1. Wrap Application with Provider
import { LiveUpdateProvider } from '@codella-software/react';
import { firebaseAuthProvider } from './auth-provider';
const App = () => {
return (
<LiveUpdateProvider
config={{
sseEnabled: true,
wsEnabled: true,
authProvider: firebaseAuthProvider,
eventMappings: {
notifications: {
sseEvent: 'GET_NOTIFICATIONS',
wsAction: 'NOTIFICATIONS_RESPONSE'
},
userUpdates: {
sseEvent: 'UPDATE_USER',
wsAction: 'UPDATE_USER'
}
}
}}
>
<YourApp />
</LiveUpdateProvider>
);
};
2. Use React Hooks
useLiveUpdates
Main hook for accessing the service and connection status:
function NotificationCenter() {
const { service, isConnected, error } = useLiveUpdates();
return (
<div>
<status>{isConnected ? 'Connected' : 'Disconnected'}</status>
{error && <error>Connection error: {error.message}</error>}
<button onClick={() => service?.send('GET_NOTIFICATIONS')}>
Refresh
</button>
</div>
);
}
useLiveListener
Listen to specific events:
function NotificationsList() {
const [notifications, setNotifications] = useState([]);
useLiveListener('notifications', (data) => {
setNotifications(prev => [...prev, data]);
});
return (
<ul>
{notifications.map(n => <li key={n.id}>{n.message}</li>)}
</ul>
);
}
useLiveRequest
Request-response pattern with promise-based API:
function FetchInvoices() {
const [invoices, setInvoices] = useState(null);
const { request, loading, error } = useLiveRequest();
const handleFetch = async () => {
const data = await request('invoices', { status: 'pending' });
setInvoices(data);
};
return (
<div>
<button onClick={handleFetch} disabled={loading}>
{loading ? 'Loading...' : 'Fetch Invoices'}
</button>
{error && <error>{error.message}</error>}
{invoices && <InvoicesList data={invoices} />}
</div>
);
}
useLiveRefresh
Auto-refresh data on updates:
function UserProfile({ userId }) {
const { data, refresh } = useLiveRefresh('userUpdates', async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
});
return (
<div>
<h2>{data?.name}</h2>
<p>{data?.email}</p>
<button onClick={() => refresh()}>Manual Refresh</button>
</div>
);
}
useLiveUpdateListener
Listen to updates with callback:
function ReportMonitor() {
const [updates, setUpdates] = useState([]);
useLiveUpdateListener('reports', (update) => {
setUpdates(prev => [...prev, {
timestamp: new Date(),
...update
}]);
});
return (
<div>
<h3>Recent Updates</h3>
{updates.map((u, i) => (
<div key={i}>
{u.timestamp.toLocaleTimeString()} - {u.status}
</div>
))}
</div>
);
}
Creating Custom Auth Provider
Implement the IAuthProvider interface:
import type { IAuthProvider } from '@codella-software/utils';
export class CustomAuthProvider implements IAuthProvider {
private authStateCallbacks: ((isAuthenticated: boolean) => void)[] = [];
async getIdToken(forceRefresh?: boolean): Promise<string | null> {
// Return your authentication token
if (forceRefresh) {
return this.refreshToken();
}
return localStorage.getItem('auth_token');
}
isAuthenticated(): boolean {
// Check if user is authenticated
return !!localStorage.getItem('auth_token');
}
onAuthStateChanged(callback: (isAuthenticated: boolean) => void): () => void {
// Subscribe to auth state changes
this.authStateCallbacks.push(callback);
// Return unsubscribe function
return () => {
this.authStateCallbacks = this.authStateCallbacks.filter(cb => cb !== callback);
};
}
private async refreshToken(): Promise<string | null> {
// Refresh token if needed
const response = await fetch('/api/auth/refresh', {
method: 'POST'
});
const { token } = await response.json();
localStorage.setItem('auth_token', token);
this.notifyAuthStateChanged();
return token;
}
private notifyAuthStateChanged(): void {
const isAuthenticated = this.isAuthenticated();
this.authStateCallbacks.forEach(callback => callback(isAuthenticated));
}
}
export const customAuthProvider = new CustomAuthProvider();
Event Mapping Examples
Minimal Setup
const config: LiveUpdateConfig = {
sseEnabled: true,
wsEnabled: true,
authProvider: myAuthProvider
// No eventMappings - use generic 'on()' calls
};
service.on('any-event-name').subscribe(data => {
console.log(data);
});
Full Configuration
const config: LiveUpdateConfig = {
sseEnabled: true,
wsEnabled: true,
authProvider: myAuthProvider,
eventMappings: {
notifications: {
sseEvent: 'GET_NOTIFICATIONS',
wsAction: 'NOTIFICATIONS_RESPONSE'
},
invoices: {
sseEvent: 'INVOICE_FOR_VERIFICATION_RESPONSE',
wsAction: 'INVOICE_FOR_VERIFICATION_RESPONSE'
},
userProfile: {
sseEvent: 'UPDATE_USER',
wsAction: 'UPDATE_USER'
},
reports: {
sseEvent: 'UPDATE_REPORTS',
wsAction: 'UPDATE_REPORTS'
},
fileUpload: {
sseEvent: 'FILE_UPLOADED',
wsAction: 'FILE_UPLOADED'
}
}
};
Configuration Options
SSEConfig
- url (required): SSE endpoint URL
- authProvider (required): IAuthProvider implementation
- headerName (optional): Auth header name (default: 'Authorization')
- headerPrefix (optional): Auth header prefix (default: 'Bearer')
WebsocketConfig
- url (required): WebSocket endpoint URL (ws:// or wss://)
- authProvider (required): IAuthProvider implementation
LiveUpdateConfig
- sseEnabled (required): Enable SSE transport
- wsEnabled (required): Enable WebSocket transport
- authProvider (required): IAuthProvider implementation
- eventMappings (optional): Map event names to SSE events/WS actions