Real-Time (SignalR)
SignalR hubs (AuditHub, NotificationHub), React hooks, connection management, and tenant group scoping.
SCRIPE uses SignalR for real-time bi-directional communication between the backend and frontend. Two hubs are available: AuditHub for live audit log streaming and NotificationHub for instant notification delivery.
Real-Time Architecture
ASP.NET Core
Custom Hooks
uses context
SignalRProvider — React Context
connects
SignalR Hubs — WebSocket + fallback
AuditHub
NotificationHub
SignalR Hubs
| Hub | Path | Purpose | Events |
|---|---|---|---|
| AuditHub | /hubs/audit | Real-time audit log streaming | AuditLogCreated |
| NotificationHub | /hubs/notification | Push notifications | NotificationReceived, UnreadCountChanged |
SignalR Provider
SignalRProvider — Connection ManagementTypeScript
// Manages SignalR connections with automatic reconnection
/**
* Exported function defining parameters and fields for signal r provider configurations.
*/
export function SignalRProvider({ children }: { children: ReactNode }) {
const { token } = useAuthStore();
const [auditConnection, setAuditConnection] = useState<HubConnection | null>(null);
const [notifConnection, setNotifConnection] = useState<HubConnection | null>(null);
useEffect(() => {
if (!token) return;
// Create connection with auto-reconnect
const audit = new HubConnectionBuilder()
.withUrl(`${API_URL}/hubs/audit`, {
accessTokenFactory: () => token,
})
.withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
.configureLogging(LogLevel.Warning)
.build();
const notif = new HubConnectionBuilder()
.withUrl(`${API_URL}/hubs/notification`, {
accessTokenFactory: () => token,
})
.withAutomaticReconnect([0, 2000, 5000, 10000, 30000])
.build();
audit.start().catch(console.error);
notif.start().catch(console.error);
setAuditConnection(audit);
setNotifConnection(notif);
return () => {
audit.stop();
notif.stop();
};
}, [token]);
return (
<SignalRContext.Provider value={{ auditConnection, notifConnection }}>
{children}
</SignalRContext.Provider>
);
}React Hooks
useAuditStream.tsTypeScript
export function useAuditStream(maxItems = 100) {
const [logs, setLogs] = useState<AuditLogDto[]>([]);
const { auditConnection } = useSignalR();
useEffect(() => {
if (!auditConnection) return;
const handler = (log: AuditLogDto) => {
setLogs(prev => [log, ...prev].slice(0, maxItems));
};
auditConnection.on("AuditLogCreated", handler);
return () => auditConnection.off("AuditLogCreated", handler);
}, [auditConnection, maxItems]);
return { logs, clearLogs: () => setLogs([]) };
}Connection States
| State | Description | Retry Timing |
|---|---|---|
| Connected | Active WebSocket connection | — |
| Reconnecting | Lost connection, attempting reconnect | 0s → 2s → 5s → 10s → 30s |
| Disconnected | All retry attempts exhausted | Manual reconnect required |
| Connecting | Initial connection in progress | — |