diff --git a/HISTORY.md b/HISTORY.md index 7b3757ea..52d5f594 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,9 @@ +# [1.1.0](https://github.com/dpatty/eventsource/compare/v1.0.8...v1.1.0) +* Allow configuring of the reconnection interval using an exponential backoff ([#126](https://github.com/EventSource/eventsource/pull/126) David Patty) + +# [1.0.8](https://github.com/dpatty/eventsource/compare/v1.0.7...v1.0.8) +* Prevent simultaneous reconnection attempts on multiple errors ([#125](https://github.com/EventSource/eventsource/pull/125) David Patty) + # [1.0.7](https://github.com/EventSource/eventsource/compare/v1.0.6...v1.0.7) * Add dispatchEvent to EventSource ([#101](https://github.com/EventSource/eventsource/pull/101) Ali Afroozeh) diff --git a/lib/eventsource.js b/lib/eventsource.js index ac8f6c8a..84a7848c 100644 --- a/lib/eventsource.js +++ b/lib/eventsource.js @@ -44,7 +44,7 @@ function EventSource (url, eventSourceInitDict) { }) var self = this - self.reconnectInterval = 1000 + self.reconnectInterval = startingReconnectInterval self.connectionInProgress = false function onConnectionClosed (message) { @@ -65,6 +65,13 @@ function EventSource (url, eventSourceInitDict) { self.connectionInProgress = true connect() }, self.reconnectInterval) + + increaseReconnectInterval() + } + + function increaseReconnectInterval () { + self.reconnectInterval *= reconnectIntervalBackoffMultiple + self.reconnectInterval = Math.min(maxReconnectInterval, self.reconnectInterval) } var req @@ -133,7 +140,9 @@ function EventSource (url, eventSourceInitDict) { } req = (isSecure ? https : http).request(options, function (res) { + // release the lock on making connections self.connectionInProgress = false + // Handle HTTP errors if (res.statusCode === 500 || res.statusCode === 502 || res.statusCode === 503 || res.statusCode === 504) { _emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage})) @@ -160,6 +169,9 @@ function EventSource (url, eventSourceInitDict) { } readyState = EventSource.OPEN + // reset reconnect interval now that we've actually made a connection + self.reconnectInterval = startingReconnectInterval + res.on('close', function () { res.removeAllListeners('close') res.removeAllListeners('end') @@ -351,6 +363,25 @@ EventSource.prototype.close = function () { this._close() } +// set defaults to 30s max, and exponentially back off @ 2^n +var startingReconnectInterval = 1000 +var maxReconnectInterval = 1000 * 30 +var reconnectIntervalBackoffMultiple = 2 +// setMaxReconnectInterval allows consumers to configure the max reconnect interval, and the backoff +EventSource.prototype.setupReconnectInterval = function (starting, max, multiple) { + if (Number.isInteger(starting) && starting >= 0) { + startingReconnectInterval = starting + } + if (Number.isInteger(max) && max >= 0) { + maxReconnectInterval = max + } + if (Number.isFinite(multiple) && multiple > 0) { + reconnectIntervalBackoffMultiple = multiple + } + + this.reconnectInterval = startingReconnectInterval +} + /** * Emulates the W3C Browser based WebSocket interface using addEventListener. * diff --git a/package.json b/package.json index b7d77e59..8a3de144 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "eventsource2", - "version": "1.0.8", - "description": "W3C compliant EventSource client for Node.js and browser (polyfill). Forked from http://github.com/EventSource/eventsource", + "version": "1.1.0", + "description": "W3C compliant EventSource client for Node.js and browser (polyfill) Forked from http://github.com/EventSource/eventsource", + "keywords": [ "eventsource", "http", diff --git a/test/eventsource_test.js b/test/eventsource_test.js index a734cad4..287d5702 100644 --- a/test/eventsource_test.js +++ b/test/eventsource_test.js @@ -646,7 +646,7 @@ describe('HTTPS Client Certificate Support', function () { describe('Reconnection', function () { it('is attempted when server is down', function (done) { var es = new EventSource('http://localhost:' + _port) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onerror = function () { es.onerror = null @@ -669,7 +669,7 @@ describe('Reconnection', function () { server.on('request', writeEvents(['data: hello\n\n'])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onmessage = function (m) { assert.equal('hello', m.data) @@ -701,7 +701,7 @@ describe('Reconnection', function () { }) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) var errored = false @@ -732,7 +732,7 @@ describe('Reconnection', function () { server.on('request', writeEvents(['data: hello\n\n'])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onmessage = function (m) { assert.equal('hello', m.data) @@ -777,7 +777,7 @@ describe('Reconnection', function () { }) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onerror = function (e) { assert.equal(e.status, 204) @@ -814,7 +814,7 @@ describe('Reconnection', function () { server.on('request', writeEvents(['id: 10\ndata: Hello\n\n'])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onmessage = function () { server.close(function (err) { @@ -854,7 +854,7 @@ describe('Reconnection', function () { server.on('request', writeEvents(['data: Hello\n\n'])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onmessage = function () { server.close(function (err) { @@ -885,7 +885,7 @@ describe('Reconnection', function () { var fn = writeEvents(events) fn(req, res) eventsSent++ - // now cause a parse error! + // now cause a few errors fn(req, res) eventsSent++ } else { @@ -897,13 +897,64 @@ describe('Reconnection', function () { var es = new EventSource(server.url) assert.equal(EventSource.CONNECTING, es.readyState) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onerror = function (err) { - console.log(err); errorOccurred = !!(errorOccurred || err) } }) }) + + it('should allow configurability of reconnect interval', function (done) { + createServer(function (err, server) { + if (err) return done(err) + + server.on('request', function (req, res) { + res.writeHead(500) + res.end() + }) + + var es = new EventSource(server.url) + es.setupReconnectInterval(5, 60, 1.5) + assert.equal(es.reconnectInterval, 5) + + var errored = false + es.onerror = function () { + if (errored) return + errored = true + server.close(function (err) { + if (err) return done(err) + assert.equal(es.reconnectInterval, 5 * 1.5) + done() + }) + } + }) + }) + + it('attempts to reconnect should increase the reconnect interval exponentially', function (done) { + createServer(function (err, server) { + if (err) return done(err) + var eventsSent = 0 + server.on('request', function (req, res) { + if (eventsSent === 0) { + res.writeHead(500) + res.end() + } + }) + + var es = new EventSource(server.url) + es.setupReconnectInterval(5, 25, 10) + var errored = false + es.onerror = function () { + if (errored) return + errored = true + server.close(function (err) { + if (err) return done(err) + assert.equal(es.reconnectInterval, 25) + done() + }) + } + }) + }) }) describe('readyState', function () { @@ -946,7 +997,7 @@ describe('readyState', function () { server.on('request', writeEvents([])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onopen = function (m) { server.close(function (err) {