Server-Sent Events(SSE) Web(JS EventSource) Client

If you want to consume Server-Sent Events(SSE) from your web front-end then you need to create a new object of EvenSource and use that.

This article contains detailed information about EventSource.

More details about SSE including all headers and options are discussed in a separate article, check that to know the subject in detail (Server-Sent Events (SSE)).

Basic SSE Event Listener

Say you have an SSE API endpoint http://localhost:3000/events and you want to consume data/events from that endpoint. Then create an EventSource Object like the below:

const eventSource = new EventSource('http://localhost:3000/events');

In typical cases, if the event is not a custom event (if the event name/type is not defined separately), then you will receive the messages in the onmessage method of your EventSource.

const eventSource = new EventSource('http://localhost:3000/events');

eventSource.onmessage = (currentEvent) => {
  console.log(currentEvent.data); // This will log the data sent by SSE
}

Note: The default event type for SSE is ‘message‘.

Before going to a more complex event listening part and handling status, it’s necessary to understand the definition of EventSource. Check the EventSource definition below.

EventSource Definition

Check the implementation details of the EventSource API from the link below.

Source: https://html.spec.whatwg.org/multipage/server-sent-events.html

All browsers and other clients need to follow this implementation definition while implementing EvenSource. Here is the definition:

// Source: https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface

[Exposed=(Window,Worker)]
interface EventSource : EventTarget {
  constructor(USVString url, optional EventSourceInit eventSourceInitDict = {});

  readonly attribute USVString url;
  readonly attribute boolean withCredentials;

  // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSED = 2;
  readonly attribute unsigned short readyState;

  // networking
  attribute EventHandler onopen;
  attribute EventHandler onmessage;
  attribute EventHandler onerror;
  undefined close();
};

dictionary EventSourceInit {
  boolean withCredentials = false;
};

And the definition of EventTarget, used in the code above is below:

// Source: https://dom.spec.whatwg.org/#interface-eventtarget

[Exposed=*]
interface EventTarget {
  constructor();

  undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {});
  undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {});
  boolean dispatchEvent(Event event);
};

callback interface EventListener {
  undefined handleEvent(Event event);
};

dictionary EventListenerOptions {
  boolean capture = false;
};

dictionary AddEventListenerOptions : EventListenerOptions {
  boolean passive;
  boolean once = false;
  AbortSignal signal;
};

Let’s discuss the important parts of the definition of EventSource one by one.

EventSource Availability

In the definition, the following line means that EventSource is available in Window(the global object) and in web Workers.

[Exposed=(Window,Worker)]

EventSource Constructor

EventSource constructor signature is:

constructor(USVString url, optional EventSourceInit eventSourceInitDict = {});

So it accepts 2 params:

  1. url (Required): url of the SSE endpoint
  2. evnetSourceInitDict (Optional): which is an object and of type EventSourceInit. And EventSourceInit definition is like below, so we can pass only `withCredentials` (boolean) in the object.

Additional headers can not be sent using the EventSource (window.EventSource). If you want to send headers, you can use a polyfill like https://github.com/EventSource/eventsource, which provides the option to send headers.

dictionary EventSourceInit {
  boolean withCredentials = false;
};

If you set the value of withCredentials to true like below,

const eventSource = new EvnetSource('http://localhost:3000/events', {
  withCredentials: true
});

This will set the request credential mode to ‘include‘, which means – always send user credentials like cookies, HTTP authentication, etc. with the request (same for cross-origin requests).

You can check the request credential modes here: https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials

EventSource Status

Request statuses are maintained internally, we don’t control those from our implementation. The status is saved in readyStatus and can be of 3 types:

  1. CONNECTING = 0 (when connection is being established – connecting for the first time or trying to reconnect)
  2. OPEN = 1 (when connection is open and active)
  3. CLOSED = 2 (when connection is closed)

Though these statutes are maintained internally, we can access these statuses from our code using readyStatus property. Like below:

const eventSource = new EventSource('http://localhost:3000/events');

console.log(eventSource.readyState);

EventSource Event Handler

Initially you get 3 event handlers:

  • onopen: triggered when the connection is opened and established
  • onerror: triggered when there is an error.
  • onmessage: triggered when the event is received(of default type message).

These event handlers can be used like below:

const eventSource = new EventSource('http://localhost:3000/events');

eventSource.onopen = (currentEvent) => {
  console.log('on open:');

  console.log(currentEvent); // an Event object with type="open"
}

eventSource.onerror = (currentEvent) => {
  console.log('on error:');

  console.log(currentEvent); // an Event object with type="error"
}

eventSource.onmessage = (currentEvent) => {
  console.log('on message:');

  console.log(currentEvent); // a MessageEvent object
}

EventSource Custom Event Handler

As a part of EventTarget we get the addEventListener (to add an event handler) and removeEventListener (to remove an event handler). Here is the signature of those methods.

undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {});
undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {});

Both of these functions accept 3 parameters:

  1. type (Required): this is the string identifier of the event type.
  2. callback (Required): callback function.
  3. options (Optional): can be of type boolean or AddEventListenerOptions.

Check how to send a custom event from the backend here: SSE Send Custom Events

So if you want to add a custom event handler for event type ‘custom_event_1‘ then it can be written below:

const eventSource = new EventSource('http://localhost:3000/events');

eventSource.addEventListener('custom_event_1', (currentEvent) => {
  console.log(currentEvent);
});

Even the events for open, error and message events can be handled using addEventListener. like below:

const eventSource = new EventSource('http://localhost:3000/events');

eventSource.addEventListener('open', (event) => {
  console.log('on open:');

  console.log(event); // an Event object with type="open"
});

eventSource.addEventListener('error', (event) => {
  console.log('on error:');

  console.log(event); // an Event object with type="error"
});

eventSource.addEventListener('message', (event) => {
  console.log('on message:');

  console.log(event); // a MessageEvent object
});

EventSource Close Connection

The connection can be closed by calling the close() function of EventSource.

const eventSource = new EventSource('http://localhost:3000/events');

//  use the following line any place, to close connection
eventSource.close(); // close connection

Handling lastEventId

If an id is sent from the backend then to receive that id you need to access lastEventId like below:

eventSource.addEventListener("custom_event_1", (currentEvent) => {
  console.log(currentEvent.lastEventId);
});

Check how to send the id here: SSE Send Additional Data

EventSource Checking

In your implementation, you can check if the browser supports EventSource or not. If EventSource is not supported then you can use EventSource polyfill like https://github.com/eventsource/eventsource or you can use any other method like long polling.

if (!!window.EventSource) {
  const eventSource = new EventSource('http://localhost:3000/events');
  // do something with the eventSource
} else {
  // Implement alternative methods like SSE polyfill or long pooling
}

SSE Max Reconnect Checking

EventSource will try to reconnect to the source every time any error occurs. So if in any case if the server is down, or can not accept any more connections, even then EventSource will try to reconnect. And this reconnection retry attempt will keep going infinitely.

So, it’s better to put some limit on the number of max reconnect attempts. To active that you can use the following code:

const maxReconnect = 20;
let reconnectCount = 0;

const eventSource = new EventSource('http://localhost:3000/events');

eventSource.onmessage = (currentEvent) => {
  console.log(currentEvent.data);
}

eventSource.onerror = (currentEvent) => {
  if (reconnectCount > maxReconnect) {
    eventSource.close();

    console.log("Event source reconnect attempt exceds max try");
  } else {
    reconnectCount++
  }
}

eventSource.onopen = (currentEvent) => {
  reconnectCount = 0;

  console.log("Event source connection open");
}

What is happening here?

  • We have set our max reconnect retry limit to 20 (you can set a limit that suits your need).
  • Declared a counter for counting how many retries are performed at any point in time.
  • When there is an error we know that Eventsource will retry so we are increasing the count of retries.
  • If the retry count exceeds the limit then we close the connection by using eventSource.close().
  • If at any point the connection is established then we are making the reconnectCount=0, so the reconnectCount resets here.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.