Theory of Operation
The fundamental premise is that each connection needs to have a unique id, so that the server can associate a connection with a client.
The server maintains all connections in a map, keyed by the unique client id. The unique client id is obtained from the server, since it knows all current ids and can guarantee uniqueness.
So, the first thing the client does is request that id from the server. It then opens a connection (instantiates an EventSource object) to the server, which REST endpoint is ‘register-listener’. The server responds with an acknowledgement (‘registered’: id), creates a resObj as follows:
resObj = {
notifyRes: res, // the response object
'registered-ts': Date.now(), // the registered timestamp
listenKeys: [] // identifier for to-be-registered keys
}
The object is stored in a module-scoped ‘connections’ object, keyed by the id.
On the client side, the client registers a callback object to receive various notifications.
Two Types of Listen Requests
The client can request one or more instances of listen requests:
Long-termed (LT)
These are requests that are globally sent to all listeners of the specified name. Things like timed loops, listeners-changed, etc are messages that can be sent by the server at any time. The connection is typically held open for the duration of the app session.
Task
A task is defined on the server that may take a long time to complete - longe than the http-request timeout. The request is made to the server to initiate the task and then the client continues (not unlike normal asynchronous requests, except they may take longer to complete - like minutes, hours or days.) When the task is complete, the server notifies the client it is complete by id, and the client can handle it.
To handle the id and connections on the client side, the client instantiates a ‘server-event-client’ object, which in turn does the following:
- requests a unique id from the server
- instantiates an
EventSource
object - requests the ‘register-listener’ endpoint from the server using the
EventSource
constructor. - assigns a callback object to the
EventSource
object.
This establishes the contact with the server. From here, the client register to general LT listeners, or submit a task. Multiple listeners and tasks can be registered/submitted on a single connection.
General LT Notification
To register for general LT notification, the client calls
<sseObj>.listen(listenKey);
supplying the key for the events it wants notification (note these must be active processes defined on the server.) When the server sends a notification, it is via the callback object provided to the server-event-client
object on instantiation:
<cbObj>.sseNotify(listenKey);
Task Notification
To submit a task, the client calls
<sseObj>.submitTask(taskname);
Once again, the ‘taskname’ is an identifier to a specific task implemented on the server.
When the task is completed, the notification is recieved via the callback object provided to the server-event-client
object on instantiation:
<cbObj>.sseCompleted(taskname);
After the task is completed, the client may elect to close the connection and remove the server-event-client
object.
Closing connection
Making and closing connections are controlled from the client, always. The server will not close a connection on its own.
To close a connection, the client calls:
<sseObj>.disconnect();
This will notify the server of the disconnection (so it can clean up), call close() on the EventSource
object, and then delete it when it responds with a ‘closed’ callback.
Finally, it will the ‘closed’ callback to the client-registered callback object, which can (should) delete the server-event-client
object.
Other Notifications
There are other maintenance/book-keeping/debugging notifications provided by the server and the server-event-client
object - they are detailed in their respective documentation pages:
- server-event-client - the client-side object.
- server-event-mgr - the server-side object.