Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

launchdarkly-eventsource

launchdarkly4.4mMIT2.0.3

Fork of eventsource package - W3C compliant EventSource client for Node.js and browser (polyfill)

eventsource, http, streaming, sse, polyfill

readme

EventSource

ci npm version NPM Downloads

This library is a pure JavaScript implementation of the EventSource client. The API aims to be W3C compatible.

You can use it with Node.js, or as a browser polyfill for browsers that don't have native EventSource support. However, the current implementation is inefficient in a browser due to the use of Node API shims, and is not recommended for use as a polyfill; a future release will improve this.

This is a fork of the original EventSource project by Aslak Hellesøy, with additions to support the requirements of the LaunchDarkly SDKs. Note that as described in the changelog, the API is not backward-compatible with the original package, although it can be used with minimal changes.

Install

npm install launchdarkly-eventsource

Example

npm install
node ./example/sse-server.js
node ./example/sse-client.js    # Node.js client
open http://localhost:8080      # Browser client - both native and polyfill
curl http://localhost:8080/sse  # Enjoy the simplicity of SSE

Browser Polyfill

Just add example/eventsource-polyfill.js file to your web page:

<script src="/eventsource-polyfill.js"></script>

Now you will have two global constructors:

window.EventSourcePolyfill
window.EventSource // Unchanged if browser has defined it. Otherwise, same as window.EventSourcePolyfill

If you're using webpack or browserify you can of course build your own. (The example/eventsource-polyfill.js is built with webpack).

Extensions to the W3C API

All configurable extended behaviors use the optional eventSourceInitDict argument to the EventSource constructor. For instance:

var eventSourceInitDict = { initialRetryDelayMillis: 1000, maxRetryDelayMillis: 30000 };
var es = new EventSource(url, eventSourceInitDict);

In a browser that is using native EventSource the extra argument would simply be ignored, so any code that will run in a browser, if it might or might not be using this polyfill, should assume that these options might or might be used.

Extensions to event behavior

Like the standard EventSource, this implementation emits the event open when a stream has been started and error when a stream has failed for any reason. All events have a single parameter which is an instance of the Event class.

The error event has the following extended behavior: for an HTTP error response, the event object will have a status property (such as 401) and optionally a message property (such as "Unauthorized").

es.onerror = function (err) {
  if (err) {
    if (err.status === 401 || err.status === 403) {
      console.log('not authorized');
    }
  }
};

The following additional events may be emitted:

  • closed: The stream has been permanently closed, either due to a non-retryable error or because close() was called.
  • end: The server ended the stream. For backward compatibility, this is not reported as an error, but it is still treated as one in terms of the retry logic.
  • retrying: After an error, this indicates that EventSource will try to reconnect after some delay. The event object's delayMillis property indicates the delay in milliseconds.

Configuring backoff and jitter

By default, EventSource automatically attempts to reconnect if a connection attempt fails or if an existing connection is broken. To prevent a flood of requests, there is always a delay before retrying the connection; the default value for this is 1000 milliseconds.

For backward compatibility, the default behavior is to use the same delay each time. However, it is highly recommended that you enable both exponential backoff (the delay doubles on each successive retry, up to a configurable maximum) and jitter (a random amount is subtracted from each delay), so that if a server outage causes clients to all lose their connections at the same time they will not all retry at the same time. The backoff can also be configured to reset back to the initial delay if the stream has remained active for some amount of time.

var eventSourceInitDict = {
  initialRetryDelayMillis: 2000,   // sets initial retry delay to 2 seconds
  maxBackoffMillis: 30000,         // enables backoff, with a maximum of 30 seconds
  retryResetIntervalMillis: 60000, // backoff will reset to initial level if stream got an event at least 60 seconds before failing
  jitterRatio: 0.5                 // each delay will be reduced by a randomized jitter of up to 50%
};

Configuring error retry behavior

By default, to mimic the behavior of built-in browser EventSource implementations, EventSource will retry if a connection cannot be made or if the connection is broken (using whatever retry behavior you have configured, as described above)-- but if it connects and receives an HTTP error status, it will only retry for statuses 500, 502, 503, or 504; otherwise it will just raise an error event and disconnect, so the application is responsible for starting a new connection.

If you set the option errorFilter to a function that receives an Error object and returns true or false, then EventSource will call it for any error-- either an HTTP error or an I/O error. If the function returns true, it will proceed with retrying the connection; if it returns false, it will end the connection and raise an error event. In this example, connections will always be retried unless there is a 401 error:

var eventSourceInitDict = {
  errorFilter: function(e) {
    return e.status !== 401;
  }
};

HTTP redirect responses (301/307) with a valid Location header are not considered errors, and are always immediately retried with the new URL.

Setting HTTP request headers

You can define custom HTTP headers for the initial HTTP request. This can be useful for e.g. sending cookies or to specify an initial Last-Event-ID value.

HTTP headers are defined by assigning a headers attribute to the optional eventSourceInitDict argument:

var eventSourceInitDict = { headers: { Cookie: 'test=test' } };

Normally, EventSource will always add the headers Cache-Control: no-cache (since an SSE stream should always contain live content, not cached content), and Accept: text/event-stream. This could cause problems if you are making a cross-origin request, since CORS has restrictions on what headers can be sent. To turn off the default headers, so that it will only send the headers you specify, set the skipDefaultHeaders option to true:

var eventSourceInitDict = {
  headers: { Cookie: 'test=test' },
  skipDefaultHeaders: true
};

Setting HTTP request method/body

By default, EventSource makes a GET request. You can specify a different HTTP verb and/or a request body:

var eventSourceInitDict = { method: 'POST', body: 'n=100' };

Read timeout

TCP connections can sometimes fail without the client detecting an I/O error, in which case EventSource could hang forever waiting for events. Setting a readTimeoutMillis will cause EventSource to drop and retry the connection if that number of milliseconds ever elapses without receiving any new data from the server. If the server is known to send any "heartbeat" data at regular intervals (such as a : comment line, which is ignored in SSE) to indicate that the connection is still alive, set the read timeout to some number longer than that interval.

var eventSourceInitDict = { readTimeoutMillis: 30000 };
`

Special HTTPS configuration

In Node.js, you can customize the behavior of HTTPS requests by specifying, for instance, additional trusted CA certificates. You may use any of the special TLS options supported by Node's tls.connect() and tls.createSecureContext() (depending on what version of Node you are using) by putting them in an object in the https property of your configuration:

var eventSourceInitDict = {
  https: {
    rejectUnauthorized: false  // causes requests to succeed even if the certificate cannot be validated
  }
};

This only works in Node.js, not in a browser.

HTTP/HTTPS proxy

You can define a proxy option for the HTTP request to be used. This is typically useful if you are behind a corporate firewall.

var eventSourceInitDict = { proxy: 'http://your.proxy.com' };

In Node.js, you can also set the agent option to use a tunneling agent.

Detecting supported features

In cases where the EventSource implementation is determined at runtime (for instance, if you are in a browser that may or may not have native support for EventSource, and you are only loading this polyfill if there is no native support), you may want to know ahead of time whether certain nonstandard features are available or not.

The special property EventSource.supportedOptions is an object containing a true value for each property name that is allowed in the constructor parameters. If EventSource.supportedOptions.somePropertyName is true, then you are using a version of this polyfill that supports the somePropertyName option. If EventSource.supportedOptions.somePropertyName is false or undefined, or if EventSource.supportedOptions does not exist, then you are using either an older version of the polyfill, or some completely different EventSource implementation.

For instance, if you want to use the POST method-- which built-in EventSource implementations in browsers cannot do-- but you do not know for sure whether the current EventSource is this polyfill or a built-in browser version, you should check first whether that option is supported; otherwise, if it's the browser version, it will simply ignore the method option and do a GET request instead which probably won't work. So your logic might look like this:

if (EventSource.supportedOptions && EventSource.supportedOptions.method) {
  var es = new EventSource(url, { method: 'POST' });
} else {
  // do whatever you want to do if you can't do a POST request
}

License

MIT-licensed. See LICENSE.

changelog

Change log

All notable changes to this package will be documented in this file.

2.0.3 (2024-05-22)

Bug Fixes

  • Ensure that each request can only fail once. (#28) (bcceb35)
  • Stop reconnect timer when event source is closed. (#29) (a73a118)

2.0.2 (2024-04-26)

Bug Fixes

  • Include message for closed or ended event source connections. (#24) (48f4cc1)

[2.0.1] - 2023-08-28

Fixed:

  • Fixed an issue in the pre-allocation algorithm handling partial messages.

[2.0.0] - 2023-07-05

Changed:

  • Changed the way allocations are handled when receiving a line over multiple chunks. Instead of concatenating buffers together as they are received, instead an exponential doubling capacity algorithm will be used to allocate buffers to which multiple chunks may be copied. The algorithm will double until the excess capacity allocated reaches 1MiB. At that point subsequent re-allocations will allocate at maximum 1MiB of extra capacity.

[1.4.4] - 2022-03-10

Fixed:

  • Removed the dependency on the original package. A transitive dependency of this package was flagged in CVE-2022-0686.

[1.4.3] - 2022-01-10

This release fixes a number of SSE spec compliance issues which do not affect usage in the LaunchDarkly SDKs, but could be relevant in other use cases.

Fixed:

  • If an event's id: field contains a null character, the whole field should be ignored.
  • The parser was incorrectly ignoring lines that did not contain a colon, instead of treating them as a field with an empty value. For instance, data on a line by itself should be equivalent to data:.
  • The parser should ignore any incomplete messages at the end of a stream if the stream disconnects.

[1.4.2] - 2022-01-04

Fixed:

  • If the stream URL contained user/password basicauth fields, they were not being included in the request.
  • Some unsupported options were accidentally being passed to http.request and https.request. This did not affect Node itself, but it could cause problems when using interceptors that inspect the options, as discussed here.

[1.4.1] - 2021-05-10

Fixed:

  • Updated the dependency on the package original in order to pick up a newer version of the transitive dependency url-parse. Older versions of url-parse had a known vulnerability. (Thanks, m-schrepel!)

[1.4.0] - 2021-01-25

Added:

  • Added readTimeoutMillis option for automatically dropping and restarting a connection if too much time has elapsed without receiving any data.

[1.3.1] - 2020-06-29

Fixed:

  • Incorporated a fix from the upstream repository that avoids unnecessary delays when parsing a long message that is received in multiple chunks.

[1.3.0] - 2020-04-23

Added:

  • A Node.js http.Agent can be specified using the agent option.

[1.2.0] - 2020-04-03

Added:

  • New configuration options: errorFilter (determines how errors should be handled), initialRetryDelayMillis (delay interval for connection retries), jitterRatio (enables delay jitter), maxBackoffMillis (enables exponential backoff), retryResetIntervalMillis (enables reset of backoff).
  • New event types: end (the server has closed the connection), retrying (provides information about upcoming connection retries).
  • See README.md for more about these features. This project adheres to Semantic Versioning.

[1.1.0] - 2019-07- 09

Added:

  • The new option skipDefaultHeaders, if set to true, makes EventSource not add the Accept and Cache-Control request headers that it would normally add. This may be necessary to avoid CORS problems in browsers if the stream URL is in another domain, since there are more restrictions on cross-origin requests that contain these headers.
  • There is a new property, EventSource.supportedOptions, that indicates which custom options are available. See "Detecting supported features" in README.md.

[1.0.0] - 2019-01-29

First release from this fork. Changes from the previous release of the upstream code (1.0.7) are as follows:

Added:

  • The optional method and body properties of the constructor options allow you to specify a different HTTP method from the default of GET, and to provide a request body if the specified method allows a body.

Changed:

  • The EventSource constructor is now a named export, not a default export. This is necessary in order to avoid a problem that can happen when using Babel with ES6 code: the logic for converting CJS imports to ES6 imports does not work correctly if the default import has properties (CONNECTING, OPEN, CLOSED) but is also a function. Note that this is a breaking change if you were previously using the upstream code, but the only thing that needs to be changed is the import statement.