Skip to main content
Version: Latest

7. SignalR

7.1 Hub

Route: /whatsapp-hub (mapped in Shumoul.Api/Program.cs) · Class: WhatsAppHub : Hub · Authentication: [Authorize] — requires an authenticated connection (JWT via query string or header, per the platform's standard SignalR auth setup).

endpoints.MapHub<WhatsAppHub>("/whatsapp-hub", options => {
options.Transports = HttpTransportType.WebSockets | HttpTransportType.LongPolling;
});

A companion server-side pusher, WhatsAppHubService (IWhatsAppHubService), wraps IHubContext<WhatsAppHub> and is injected into WhatsAppInboxService so REST actions can broadcast real-time events.

7.2 Groups

Group name patternMembership
whatsapp-tenant-{tenantId}Every connection for a tenant
whatsapp-conv-{conversationId}Clients currently viewing a specific conversation
whatsapp-user-{userId}A specific user's own connections (for cross-device sync)

7.3 Connection Lifecycle

  • OnConnectedAsync: joins the tenant group and the user's own group, marks the agent online via IWhatsAppPresenceService.SetOnline, broadcasts AgentPresenceChanged to the tenant group.
  • OnDisconnectedAsync: leaves the tenant group, marks the agent offline, broadcasts AgentPresenceChanged.

7.4 Client-Invokable Hub Methods

MethodParametersEffect
JoinConversationconversationId: GuidJoins the conversation group, sets the agent's current conversation, broadcasts AgentJoined
LeaveConversationconversationId: GuidLeaves the conversation group, clears current conversation, broadcasts AgentLeft
StartTypingconversationId: GuidBroadcasts TypingStarted; presence service auto-broadcasts TypingStopped after a timeout
StopTypingconversationId: GuidBroadcasts TypingStopped immediately
MarkConversationOpenedconversationId: GuidUpdates presence's current conversation, no broadcast
MarkConversationClosednoneClears presence's current conversation, no broadcast

7.5 Server-to-Client Events

Event namePayloadTriggered by
AgentPresenceChanged{userId, userName, isOnline, currentConversationId, lastSeenOn}Connect/disconnect
AgentJoined / AgentLeftsame shape as aboveJoinConversation / LeaveConversation
TypingStarted / TypingStopped{conversationId, userId, userName, isTyping}StartTyping / StopTyping
ConversationAssigned{conversationId, assignedToUserId, assignedToUserName, assignedByUserId, assignedByUserName, assignedOn, comment}Assign
ConversationTransferredsame shape as ConversationAssignedTransfer
ConversationClosed / ConversationReopened / ConversationArchived / ConversationUnarchived / ConversationPinned / ConversationUnpinned{conversationId, status, isPinned, isArchived, triggeredByUserId, triggeredOn}respective lifecycle actions
UnreadCountChanged{conversationId, unreadCount}MarkAsRead / MarkAsUnread

Reserved, not yet wired up: WhatsAppNewMessageEvent and WhatsAppMessageStatusEvent DTOs exist in WhatsAppRealtimeEventDtos.cs, but no current broadcast call site uses them — the webhook controller persists inbound messages and status updates to the database but does not currently push a SignalR event for them. Frontend clients should poll or re-fetch GetMessages/GetConversations after receiving any of the lifecycle events above, rather than assuming a dedicated "new message" push exists today.

7.6 Angular Usage

const connection = new signalR.HubConnectionBuilder()
.withUrl('/whatsapp-hub', { accessTokenFactory: () => authService.getToken() })
.withAutomaticReconnect()
.build();

connection.on('ConversationAssigned', (evt) => inboxStore.applyAssignment(evt));
connection.on('UnreadCountChanged', (evt) => inboxStore.updateUnreadCount(evt));
connection.on('TypingStarted', (evt) => chatStore.setTyping(evt.conversationId, evt.userId, true));

await connection.start();
await connection.invoke('JoinConversation', conversationId);

7.7 Flutter Usage

Use the signalr_netcore (or equivalent) package with the same /whatsapp-hub URL and bearer token; invoke JoinConversation/LeaveConversation when a conversation screen is opened/closed, and listen for the same event names as the Angular client. Because no dedicated "new message" event exists yet (§7.5), pair the hub listener with a pull-to-refresh on the message list rather than relying solely on push updates for message content.