Real time
Everything the server tells a browser goes through one endpoint and one connection per tab:
GET /api/v1/events/stream?topic=kind:value
It is a server-sent events
stream: plain HTTP, through the gateway, with the browser's EventSource reconnecting
on its own.
A frame is a signal, never the data
When a notification is created, the server does not push the notification. It pushes a signal on the recipient's topic, and the client re-reads through the normal API.
That one rule removes a whole class of bugs: a dropped frame, a missed reconnect or a slow tab costs a stale second, never a wrong screen, because the data always comes from the same read endpoints as on a page load.
On the server
A domain publishes through a port, IServerEventPublisher, with a topic:
// Blouppy.Notifications — after a notification is stored.
ServerEventTopic.ForUser(notification.RecipientId)
Who may follow a topic is decided by an IServerEventTopicAuthorizer, one per kind of
topic. The Starter ships one, UserServerEventTopicAuthorizer: a user may follow their
own stream and nobody else's.
A new kind of push is a new authorizer — not a new route, and not a second connection.
The registry of open connections lives in each process. To reach a browser connected
to another replica, publishing goes through the bus to a queue that every replica has
its own copy of (ServerEventBroadcastConsumer, registered with
AddInstanceScopedConsumer).
In the Angular app
core/real-time/ServerEventsService owns the single EventSource and hands out a
stream per topic. NotificationService, for instance, re-reads the bell's unread count
when a frame arrives on the user's topic.