EdgeStream Integration
EdgeStream is BizFirstGO's real-time push messaging system. WorkDesk subscribes to EdgeStream to receive instant task assignments and notification events — eliminating the need to poll the server. When a new HIL task is created or a workflow event occurs, the employee's WorkDesk UI updates immediately.
Integration Direction
EdgeStream pushes to WorkDesk — WorkDesk subscribes to topics and receives server-pushed events. WorkDesk never publishes to EdgeStream; it only receives. Publishing is done by backend services (HILTaskSyncService, notification services, workflow engine).
WorkDesk Topic Subscriptions
WorkDesk subscribes to exactly two EdgeStream topics per authenticated user session:
Task Events
New HIL task assignments, task status changes (claimed by another, cancelled, deadline updated), task expiry warnings.
Notification Events
New notifications of any type (workflow completed, HIL assigned, approval decision made, system alerts). Drives the notification bell badge count.
Subscription Setup in WorkDeskShell
Subscriptions are established in WorkDeskShell.tsx immediately after the user authenticates. The useSubscription hook manages the WebSocket connection lifecycle:
// WorkDeskShell.tsx — EdgeStream subscriptions
import { useSubscription } from '@bizfirstai/edgestream-react';
import { useInboxStore } from './stores/inboxStore';
import { useNotifStore } from './stores/notifStore';
export function WorkDeskShell() {
const userId = useAuthStore(s => s.userId);
const addTask = useInboxStore(s => s.addTask);
const updateTask = useInboxStore(s => s.updateTask);
const addNotif = useNotifStore(s => s.addNotification);
const incrementBell = useNotifStore(s => s.incrementUnread);
// Subscribe to task events
useSubscription(`tasks.${userId}`, (event: TaskEvent) => {
switch (event.type) {
case 'task_assigned':
addTask(event.task);
showToast(`New task: ${event.task.title}`, 'info');
break;
case 'task_claimed_by_other':
updateTask(event.taskId, { status: 'claimed', claimedBy: event.actorId });
break;
case 'task_cancelled':
updateTask(event.taskId, { status: 'cancelled' });
break;
case 'task_deadline_warning':
showToast(`Task due soon: ${event.taskTitle}`, 'warning');
break;
}
});
// Subscribe to notification events
useSubscription(`notifications.${userId}`, (event: NotificationEvent) => {
addNotif(event.notification);
incrementBell();
if (event.notification.priority === 'high') {
showToast(event.notification.title, 'info');
}
});
return ( <div className="shell">...</div> );
}
Event Payload Schemas
Each EdgeStream event published to WorkDesk's topics follows a typed schema:
// Task events — published to topics.{userId}
interface TaskAssignedEvent {
type: 'task_assigned';
task: {
id: string;
type: 'approval' | 'form' | 'review';
title: string;
workflowName: string;
dueAt: string | null;
createdAt: string;
status: 'pending';
};
}
interface TaskClaimedByOtherEvent {
type: 'task_claimed_by_other';
taskId: string;
actorId: string;
claimedAt: string;
}
interface TaskCancelledEvent {
type: 'task_cancelled';
taskId: string;
cancelledAt: string;
reason: string | null;
}
// Notification events — published to notifications.{userId}
interface NotificationEvent {
type: 'notification_created';
notification: {
id: string;
type: 'hil_task_assigned' | 'workflow_completed' | 'approval_decision' | 'system_alert';
title: string;
body: string;
priority: 'normal' | 'high';
isRead: false;
createdAt: string;
actionUrl: string | null;
};
}
Connection Lifecycle
The EdgeStream WebSocket connection is managed by the useSubscription hook. WorkDesk handles connection failures gracefully with exponential backoff reconnection:
User Authenticates
WorkDeskShell mounts. useSubscription opens a WebSocket connection to EdgeStream using the user's Passport JWT.
Topics Subscribed
After connection established, WorkDesk sends subscription frames for tasks.{userId} and notifications.{userId}. EdgeStream acknowledges each subscription.
Events Received
Backend services publish events to the relevant topic. EdgeStream delivers them to all active subscribers matching that topic — in this case, the employee's WorkDesk browser session.
Connection Lost
If the WebSocket drops, useSubscription enters reconnection mode. WorkDesk shows a subtle "Reconnecting..." indicator in the status bar. Reconnect attempts use exponential backoff: 1s, 2s, 4s, 8s, max 30s.
Reconnected — Catch-Up Fetch
On reconnection, WorkDesk fetches any missed events via REST API (GET /api/workdesk/tasks?createdAfter={lastEventTime}) to cover the offline gap before EdgeStream resumes live delivery.
Multiple Browser Tabs
WorkDesk uses a shared WebSocket approach — if the employee has WorkDesk open in multiple tabs, only one WebSocket connection is maintained per browser using a BroadcastChannel leader election. The leader tab receives EdgeStream events and broadcasts them to other tabs:
// EdgeStream leader election for multi-tab scenarios
// (handled inside @bizfirstai/edgestream-react useSubscription hook)
// One tab wins the leader election and holds the WebSocket
// All other tabs receive events via BroadcastChannel
const bc = new BroadcastChannel('workdesk-edgestream');
// Leader tab — after receiving EdgeStream event:
bc.postMessage({ topic, event }); // fan out to follower tabs
// Follower tabs — receive via BroadcastChannel:
bc.onmessage = ({ data }) => dispatchEvent(data.topic, data.event);
Performance Characteristics
| Metric | Value | Notes |
|---|---|---|
| Event delivery latency | < 200ms p99 | From backend publish to browser event handler |
| Connection overhead | 1 WebSocket per browser | Leader election reduces connections in multi-tab scenarios |
| Reconnection max backoff | 30 seconds | After which WorkDesk shows a manual reconnect button |
| Catch-up fetch window | Last 15 minutes | Events older than 15 minutes are not replayed on reconnection |
| Max subscriptions per session | 10 topics | WorkDesk uses 2; remaining quota available for hosted App Studio apps |
WorkDesk never polls for task updates or notifications. All real-time updates are delivered exclusively via EdgeStream. This eliminates unnecessary API load and ensures instant delivery. The REST APIs are used only for initial page loads (hydrating state) and post-reconnection catch-up fetches.