eventsource
eventsource copied to clipboard
Header Last-Event-ID is not automatically sent on reconnect
I set up a simple test where the following happens:
- on client-side, we connect to the server with
EventSource; - the server will start sending events, once every two seconds: the events contain the
idfield; - on client-side, we log the received events, to verify that everything is working correctly;
- then the server is intentionally restarted, in order to simulate a loss of connection;
- the client will wait a few seconds, then correctly re-attempt connection: however, in this case the header
Last-Event-IDis not sent to the server.
IIUC this does not conform to the specs. This is also confirmed by running the test described above with the native implementation of EventSource in Chrome (i.e. not this repo), which correctly sends the header.
I am currently implementing and testing SSE too.
Here that last-event-id works.
On reconnect the last received id is sent back as last-event-id.
We use eventsource 2.0.2 for the test-client and nestjs 9.1.2 as backend
@Sse('sse') public async sseEndpoint( @Headers('last-event-id') lastEventId: string, @Query('request') requestStr: string ): Promise<Observable<MessageEvent>> { return await data(requestStr, lastEventId); }
A more detail testing shows that there is a problem with multiple reconnects in a row.
If one reconnect fails with some http-error-code then the next successful reconnect sends the first ever received id as last-event-id.
A single successful reconnect attempt always sends the correct last-event-id.
BTW: Actually the last-event-id that is sent after multiple failed reconnects is always '1'.
I was hitting rate limits with this bug as there would be continuous reconnects to my server, rather than waiting the 10 seconds like I wanted.
To get around this, I've written some code on the client side of my application that others might find helpful.
// Add some variables
let hasSseTimerExpired = false
let sseTimer = null
let isFirstSseRequest = true
let sseRetries = 0
function start_sse_timer () {
hasSseTimerExpired = false
sseTimer = setTimeout(() => {
hasSseTimerExpired = true
}, 9000)
}
function start_sse () {
// If we already have a timer running, stop it
if (sseTimer) {
clearTimeout(this.sseTimer)
}
// Start the timer as we are starting the connection
set_sse_timer()
// Set isFirstSseRequest to true, as this time, we receive a response immediately
isFirstSseRequest = true
// Code to start the SSE connection
...
}
function stop_sse () {
// Code to stop your SSE connection
...
}
function on_sse_message_received() {
// If the timer has expired, it means it's been longer than 9 seconds so we can continue to process the request
// We also check if this is the first request, if it is, we can ignore the timer as a response is returned immediately
if (hasSseTimerExpired || isFirstSseRequest) {
// We set the timer as we are now waiting on the next SSE request
set_sse_timer()
// We set isFirstSseRequest to false as we have now processed the first request
if (isfirstSseRequest) {
isFirstSseRequest = false
}
// Code to process the response from the SSE connection
...
// We only want to retry this 3 times to avoid hammering the server
} else if (sseRetries < 3) {
// Restart SSE as something has gone wrong and a response has been returned sooner than 9 seconds
sse_end()
sse_start()
sseRetries++
}
}