pax_global_header00006660000000000000000000000064124660671520014523gustar00rootroot0000000000000052 comment=c93edb547891f7cbd8d576751427445029bb782b eventsource-node-0.1.6/000077500000000000000000000000001246606715200150145ustar00rootroot00000000000000eventsource-node-0.1.6/.gitignore000066400000000000000000000000471246606715200170050ustar00rootroot00000000000000/node_modules/ npm-debug.log .DS_Store eventsource-node-0.1.6/.travis.yml000066400000000000000000000000761246606715200171300ustar00rootroot00000000000000language: node_js node_js: - 0.8.28 - 0.10.36 - 0.12.0 eventsource-node-0.1.6/CONTRIBUTING.md000066400000000000000000000003521246606715200172450ustar00rootroot00000000000000# Contributing to EventSource If you add or fix something, add tests. ## Release process Update `History.md`, Then: npm outdated --depth 0 # See if you can upgrade something npm version [major|minor|patch] npm publish eventsource-node-0.1.6/History.md000066400000000000000000000120211246606715200167730ustar00rootroot00000000000000# [0.1.6](https://github.com/aslakhellesoy/eventsource-node/compare/v0.1.5...v0.1.6) * Ignore headers without a value. ([#41](https://github.com/aslakhellesoy/eventsource-node/issues/41), [#43](https://github.com/aslakhellesoy/eventsource-node/pull/43) Adriano Raiano) # [0.1.5](https://github.com/aslakhellesoy/eventsource-node/compare/v0.1.4...v0.1.5) * Refactor tests to support Node.js 0.12.0 and Io.js 1.1.0. (Aslak Hellesøy) # [0.1.4](https://github.com/aslakhellesoy/eventsource-node/compare/v0.1.3...master) * Bugfix: Added missing origin property. ([#39](https://github.com/aslakhellesoy/eventsource-node/pull/39), [#38](https://github.com/aslakhellesoy/eventsource-node/issues/38) Arnout Kazemier) * Expose `status` property on `error` events. ([#40](https://github.com/aslakhellesoy/eventsource-node/pull/40) Adriano Raiano) # [0.1.3](https://github.com/aslakhellesoy/eventsource-node/compare/v0.1.2...v0.1.3) * Bugfix: Made message properties enumerable. ([#37](https://github.com/aslakhellesoy/eventsource-node/pull/37) Golo Roden) # [0.1.2](https://github.com/aslakhellesoy/eventsource-node/compare/v0.1.1...v0.1.2) * Bugfix: Blank lines not read. ([#35](https://github.com/aslakhellesoy/eventsource-node/issues/35), [#36](https://github.com/aslakhellesoy/eventsource-node/pull/36) Lesterpig) # [0.1.1](https://github.com/aslakhellesoy/eventsource-node/compare/v0.1.0...v0.1.1) * Bugfix: Fix message type. ([#33](https://github.com/aslakhellesoy/eventsource-node/pull/33) Romain Gauthier) # [0.1.0](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.10...v0.1.0) * Bugfix: High CPU usage by replacing Jison with port of WebKit's parser. ([#25](https://github.com/aslakhellesoy/eventsource-node/issues/25), [#32](https://github.com/aslakhellesoy/eventsource-node/pull/32), [#18](https://github.com/aslakhellesoy/eventsource-node/issues/18) qqueue) * Reformatted all code to 2 spaces. # [0.0.10](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.9...v0.0.10) * Provide `Event` argument on `open` and `error` event ([#30](https://github.com/aslakhellesoy/eventsource-node/issues/30), [#31](https://github.com/aslakhellesoy/eventsource-node/pull/31) Donghwan Kim) * Expose `lastEventId` on messages. ([#28](https://github.com/aslakhellesoy/eventsource-node/pull/28) mbieser) # [0.0.9](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.8...v0.0.9) * Bugfix: old "last-event-id" used on reconnect ([#27](https://github.com/aslakhellesoy/eventsource-node/pull/27) Aslak Hellesøy) # [0.0.8](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.7...v0.0.8) * Bugfix: EventSource still reconnected when closed ([#24](https://github.com/aslakhellesoy/eventsource-node/pull/24) FrozenCow) * Allow unauthorized HTTPS connections by setting `rejectUnauthorized` to false. (Aslak Hellesøy) # [0.0.7](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.6...v0.0.7) * Explicitly raise an error when server returns http 403 and don't continue ([#20](https://github.com/aslakhellesoy/eventsource-node/pull/20) Scott Moak) * Added ability to send custom http headers to server ([#21](https://github.com/aslakhellesoy/eventsource-node/pull/21), [#9](https://github.com/aslakhellesoy/eventsource-node/issues/9) Scott Moak) * Fix Unicode support to cope with Javascript Unicode size limitations ([#23](https://github.com/aslakhellesoy/eventsource-node/pull/23), [#22](https://github.com/aslakhellesoy/eventsource-node/issues/22) Devon Adkisson) * Graceful handling of parse errors ([#19](https://github.com/aslakhellesoy/eventsource-node/issues/19) Aslak Hellesøy) * Switched from testing with Nodeunit to Mocha (Aslak Hellesøy) # [0.0.6](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.5...v0.0.6) * Add Accept: text/event-stream header ([#17](https://github.com/aslakhellesoy/eventsource-node/pull/17) William Wicks) # [0.0.5](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.4...v0.0.5) * Add no-cache and https support ([#10](https://github.com/aslakhellesoy/eventsource-node/pull/10) Einar Otto Stangvik) * Ensure that Last-Event-ID is sent to the server for reconnects, as defined in the spec ([#8](https://github.com/aslakhellesoy/eventsource-node/pull/8) Einar Otto Stangvik) * Verify that CR and CRLF are accepted alongside LF ([#7](https://github.com/aslakhellesoy/eventsource-node/pull/7) Einar Otto Stangvik) * Emit 'open' event ([#4](https://github.com/aslakhellesoy/eventsource-node/issues/4) Einar Otto Stangvik) # [0.0.4](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.3...v0.0.4) * Automatic reconnect every second if the server is down. Reconnect interval can be set with `reconnectInterval` (not in W3C spec). (Aslak Hellesøy) # [0.0.3](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.2...v0.0.3) * Jison based eventstream parser ([#2](https://github.com/aslakhellesoy/eventsource-node/pull/2) Einar Otto Stangvik) # [0.0.2](https://github.com/aslakhellesoy/eventsource-node/compare/v0.0.1...v0.0.2) * Use native EventListener (Aslak Hellesøy) # 0.0.1 * First release eventsource-node-0.1.6/LICENSE000066400000000000000000000021001246606715200160120ustar00rootroot00000000000000The MIT License Copyright (c) 2012, 2013, 2014 Aslak Hellesøy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. eventsource-node-0.1.6/README.md000066400000000000000000000045151246606715200163000ustar00rootroot00000000000000# EventSource [![Build Status](https://secure.travis-ci.org/aslakhellesoy/eventsource-node.png)](http://travis-ci.org/aslakhellesoy/eventsource-node) [![Dependencies](https://david-dm.org/aslakhellesoy/eventsource-node.png)](https://david-dm.org/aslakhellesoy/eventsource-node) [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/aslakhellesoy/eventsource-node/trend.png)](https://bitdeli.com/free "Bitdeli Badge") [![NPM](https://nodei.co/npm/eventsource.png?stars&downloads)](https://nodei.co/npm/eventsource/) [![NPM](https://nodei.co/npm-dl/eventsource.png)](https://nodei.co/npm/eventsource/) This library implements the [EventSource](http://dev.w3.org/html5/eventsource/) client for Node.js. The API aims to be W3C compatible. ## Install npm install eventsource ## Usage ```javascript var EventSource = require('eventsource'); var es = new EventSource('http://demo-eventsource.rhcloud.com/'); es.onmessage = function(e) { console.log(e.data); }; es.onerror = function() { console.log('ERROR!'); }; ``` See the [spec](http://dev.w3.org/html5/eventsource/) for API docs. ## Example See https://github.com/einaros/sse-example ## Extensions to the W3C API ### 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: ```javascript var eventSourceInitDict = {headers: {'Cookie': 'test=test'}}; var es = new EventSource(url, eventSourceInitDict); ``` ### Allow unauthorized HTTPS requests By default, https requests that cannot be authorized will cause connection to fail and an exception to be emitted. You can override this behaviour: ```javascript var eventSourceInitDict = {rejectUnauthorized: false}; var es = new EventSource(url, eventSourceInitDict); ``` Note that for Node.js < v0.10.x this option has no effect - unauthorized HTTPS requests are *always* allowed. ### HTTP status code on error events Unauthorized and redirect error status codes (for example 401, 403, 301, 307) are available in the `status` property in the error event. ```javascript es.onerror = function (err) { if (err) { if (err.status === 401 || err.status === 403) { console.log('not authorized'); } } }; ``` eventsource-node-0.1.6/example.js000066400000000000000000000003401246606715200170020ustar00rootroot00000000000000var EventSource = require('./lib/eventsource'); var es = new EventSource('http://demo-eventsource.rhcloud.com/'); es.onmessage = function(e) { console.log(e.data); }; es.onerror = function() { console.log('ERROR!'); }; eventsource-node-0.1.6/lib/000077500000000000000000000000001246606715200155625ustar00rootroot00000000000000eventsource-node-0.1.6/lib/eventsource.js000066400000000000000000000213011246606715200204570ustar00rootroot00000000000000var original = require('original') , parse = require('url').parse , events = require('events') , https = require('https') , http = require('http') , util = require('util'); function isPlainObject(obj) { return Object.getPrototypeOf(obj) === Object.prototype; } /** * Creates a new EventSource object * * @param {String} url the URL to which to connect * @param {Object} eventSourceInitDict extra init params. See README for details. * @api public **/ function EventSource(url, eventSourceInitDict) { var readyState = EventSource.CONNECTING; Object.defineProperty(this, 'readyState', { get: function () { return readyState; } }); Object.defineProperty(this, 'url', { get: function () { return url; } }); var self = this; self.reconnectInterval = 1000; var connectPending = false; function onConnectionClosed() { if (connectPending || readyState === EventSource.CLOSED) return; connectPending = true; readyState = EventSource.CONNECTING; _emit('error', new Event('error')); // The url may have been changed by a temporary // redirect. If that's the case, revert it now. if (reconnectUrl) { url = reconnectUrl; reconnectUrl = null; } setTimeout(function () { if (readyState !== EventSource.CONNECTING) { return; } connect(); }, self.reconnectInterval); } var req; var lastEventId = ''; if (eventSourceInitDict && eventSourceInitDict.headers && isPlainObject(eventSourceInitDict.headers) && eventSourceInitDict.headers['Last-Event-ID']) { lastEventId = eventSourceInitDict.headers['Last-Event-ID']; delete eventSourceInitDict.headers['Last-Event-ID']; } var discardTrailingNewline = false , data = '' , eventName = ''; var reconnectUrl = null; function connect() { connectPending = false; var options = parse(url); var isSecure = options.protocol == 'https:'; options.headers = { 'Cache-Control': 'no-cache', 'Accept': 'text/event-stream' }; if (lastEventId) options.headers['Last-Event-ID'] = lastEventId; if (eventSourceInitDict && eventSourceInitDict.headers && isPlainObject(eventSourceInitDict.headers)) { for (var i in eventSourceInitDict.headers) { var header = eventSourceInitDict.headers[i]; if (header) { options.headers[i] = header; } } } options.rejectUnauthorized = !(eventSourceInitDict && eventSourceInitDict.rejectUnauthorized == false); req = (isSecure ? https : http).request(options, function (res) { // Handle HTTP redirects if (res.statusCode == 301 || res.statusCode == 307) { if (!res.headers.location) { // Server sent redirect response without Location header. _emit('error', new Event('error', {status: res.statusCode})); return; } if (res.statusCode == 307) reconnectUrl = url; url = res.headers.location; process.nextTick(connect); return; } if (res.statusCode !== 200) { _emit('error', new Event('error', {status: res.statusCode})); return self.close(); } readyState = EventSource.OPEN; res.on('close', onConnectionClosed); res.on('end', onConnectionClosed); _emit('open', new Event('open')); // text/event-stream parser adapted from webkit's // Source/WebCore/page/EventSource.cpp var buf = ''; res.on('data', function (chunk) { buf += chunk; var pos = 0 , length = buf.length; while (pos < length) { if (discardTrailingNewline) { if (buf[pos] === '\n') { ++pos; } discardTrailingNewline = false; } var lineLength = -1 , fieldLength = -1 , c; for (var i = pos; lineLength < 0 && i < length; ++i) { c = buf[i]; if (c === ':') { if (fieldLength < 0) { fieldLength = i - pos; } } else if (c === '\r') { discardTrailingNewline = true; lineLength = i - pos; } else if (c === '\n') { lineLength = i - pos; } } if (lineLength < 0) { break; } parseEventStreamLine(buf, pos, fieldLength, lineLength); pos += lineLength + 1; } if (pos === length) { buf = ''; } else if (pos > 0) { buf = buf.slice(pos); } }); }); req.on('error', onConnectionClosed); req.setNoDelay(true); req.end(); } connect(); function _emit() { if (self.listeners(arguments[0]).length > 0) { self.emit.apply(self, arguments); } } this.close = function () { if (readyState == EventSource.CLOSED) return; readyState = EventSource.CLOSED; req.abort(); }; function parseEventStreamLine(buf, pos, fieldLength, lineLength) { if (lineLength === 0) { if (data.length > 0) { var type = eventName || 'message'; _emit(type, new MessageEvent(type, { data: data.slice(0, -1), // remove trailing newline lastEventId: lastEventId, origin: original(url) })); data = ''; } eventName = void 0; } else if (fieldLength > 0) { var noValue = fieldLength < 0 , step = 0 , field = buf.slice(pos, pos + (noValue ? lineLength : fieldLength)); if (noValue) { step = lineLength; } else if (buf[pos + fieldLength + 1] !== ' ') { step = fieldLength + 1; } else { step = fieldLength + 2; } pos += step; var valueLength = lineLength - step , value = buf.slice(pos, pos + valueLength); if (field === 'data') { data += value + '\n'; } else if (field === 'event') { eventName = value; } else if (field === 'id') { lastEventId = value; } else if (field === 'retry') { var retry = parseInt(value, 10); if (!Number.isNaN(retry)) { self.reconnectInterval = retry; } } } } } module.exports = EventSource; util.inherits(EventSource, events.EventEmitter); EventSource.prototype.constructor = EventSource; // make stacktraces readable ['open', 'error', 'message'].forEach(function (method) { Object.defineProperty(EventSource.prototype, 'on' + method, { /** * Returns the current listener * * @return {Mixed} the set function or undefined * @api private */ get: function get() { var listener = this.listeners(method)[0]; return listener ? (listener._listener ? listener._listener : listener) : undefined; }, /** * Start listening for events * * @param {Function} listener the listener * @return {Mixed} the set function or undefined * @api private */ set: function set(listener) { this.removeAllListeners(method); this.addEventListener(method, listener); } }); }); /** * Ready states */ Object.defineProperty(EventSource, 'CONNECTING', { enumerable: true, value: 0}); Object.defineProperty(EventSource, 'OPEN', { enumerable: true, value: 1}); Object.defineProperty(EventSource, 'CLOSED', { enumerable: true, value: 2}); /** * Emulates the W3C Browser based WebSocket interface using addEventListener. * * @param {String} method Listen for an event * @param {Function} listener callback * @see https://developer.mozilla.org/en/DOM/element.addEventListener * @see http://dev.w3.org/html5/websockets/#the-websocket-interface * @api public */ EventSource.prototype.addEventListener = function addEventListener(method, listener) { if (typeof listener === 'function') { // store a reference so we can return the original function again listener._listener = listener; this.on(method, listener); } }; /** * W3C Event * * @see http://www.w3.org/TR/DOM-Level-3-Events/#interface-Event * @api private */ function Event(type, optionalProperties) { Object.defineProperty(this, 'type', { writable: false, value: type, enumerable: true }); if (optionalProperties) { for (var f in optionalProperties) { if (optionalProperties.hasOwnProperty(f)) { Object.defineProperty(this, f, { writable: false, value: optionalProperties[f], enumerable: true }); } } } } /** * W3C MessageEvent * * @see http://www.w3.org/TR/webmessaging/#event-definitions * @api private */ function MessageEvent(type, eventInitDict) { Object.defineProperty(this, 'type', { writable: false, value: type, enumerable: true }); for (var f in eventInitDict) { if (eventInitDict.hasOwnProperty(f)) { Object.defineProperty(this, f, { writable: false, value: eventInitDict[f], enumerable: true }); } } } eventsource-node-0.1.6/package.json000066400000000000000000000017151246606715200173060ustar00rootroot00000000000000{ "name": "eventsource", "version": "0.1.6", "description": "W3C compliant EventSource client for Node.js", "keywords": [ "eventsource", "http", "streaming", "sse" ], "homepage": "http://github.com/aslakhellesoy/eventsource-node", "author": "Aslak Hellesøy ", "repository": { "type": "git", "url": "git://github.com/aslakhellesoy/eventsource-node.git" }, "bugs": { "url": "http://github.com/aslakhellesoy/eventsource-node/issues" }, "directories": { "lib": "./lib" }, "main": "./lib/eventsource", "licenses": [ { "type": "MIT", "url": "http://github.com/aslakhellesoy/eventsource-node/raw/master/LICENSE" } ], "devDependencies": { "mocha": ">=1.21.4" }, "scripts": { "test": "mocha --reporter spec", "postpublish": "git push && git push --tags" }, "engines": { "node": ">=0.8.0" }, "dependencies": { "original": ">=0.0.5" } } eventsource-node-0.1.6/test/000077500000000000000000000000001246606715200157735ustar00rootroot00000000000000eventsource-node-0.1.6/test/certificate.pem000066400000000000000000000013651246606715200207650ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICATCCAWoCCQDPufXH86n2QzANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJu bzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 cyBQdHkgTHRkMB4XDTEyMDEwMTE0NDQwMFoXDTIwMDMxOTE0NDQwMFowRTELMAkG A1UEBhMCbm8xEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtrQ7 +r//2iV/B6F+4boH0XqFn7alcV9lpjvAmwRXNKnxAoa0f97AjYPGNLKrjpkNXXhB JROIdbRbZnCNeC5fzX1a+JCo7KStzBXuGSZr27TtFmcV4H+9gIRIcNHtZmJLnxbJ sIhkGR8yVYdmJZe4eT5ldk1zoB1adgPF1hZhCBMCAwEAATANBgkqhkiG9w0BAQUF AAOBgQCeWBEHYJ4mCB5McwSSUox0T+/mJ4W48L/ZUE4LtRhHasU9hiW92xZkTa7E QLcoJKQiWfiLX2ysAro0NX4+V8iqLziMqvswnPzz5nezaOLE/9U/QvH3l8qqNkXu rNbsW1h/IO6FV8avWFYVFoutUwOaZ809k7iMh2F2JMgXQ5EymQ== -----END CERTIFICATE----- eventsource-node-0.1.6/test/eventsource_test.js000066400000000000000000000605501246606715200217400ustar00rootroot00000000000000var EventSource = require('../lib/eventsource') , http = require('http') , https = require('https') , fs = require('fs') , assert = require('assert') , u = require('url'); var _port = 20000; var servers = 0; process.on('exit', function () { if (servers != 0) { console.error("************ Didn't kill all servers - there is still %d running.", servers); } }); function createServer(callback) { var server = http.createServer(); configureServer(server, 'http', _port++, callback); } function createHttpsServer(callback) { var options = { key: fs.readFileSync(__dirname + '/key.pem'), cert: fs.readFileSync(__dirname + '/certificate.pem') }; var server = https.createServer(options); configureServer(server, 'https', _port++, callback); } function configureServer(server, protocol, port, callback) { var responses = []; var oldClose = server.close; server.close = function() { responses.forEach(function (res) { res.end(); }); servers--; oldClose.apply(this, arguments); }; server.on('request', function (req, res) { responses.push(res); }); server.url = protocol + '://localhost:' + port; server.listen(port, function onOpen(err) { servers++; callback(err, server); }); } function writeEvents(chunks) { return function (req, res) { res.writeHead(200, {'Content-Type': 'text/event-stream'}); chunks.forEach(function (chunk) { res.write(chunk); }); res.write(':'); // send a dummy comment to ensure that the head is flushed }; } describe('Parser', function () { it('parses multibyte characters', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["id: 1\ndata: €豆腐\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { assert.equal("€豆腐", m.data); server.close(done); }; }); }); it('parses empty lines with multibyte characters', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["\n\n\n\nid: 1\ndata: 我現在都看實況不玩遊戲\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { assert.equal("我現在都看實況不玩遊戲", m.data); server.close(done); }; }); }); it('parses one one-line message in one chunk', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["data: Hello\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { assert.equal("Hello", m.data); server.close(done); }; }); }); it('parses one one-line message in two chunks', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["data: Hel", "lo\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { assert.equal("Hello", m.data); server.close(done); }; }); }); it('parses two one-line messages in one chunk', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["data: Hello\n\n", "data: World\n\n"])); var es = new EventSource(server.url); es.onmessage = first; function first(m) { assert.equal("Hello", m.data); es.onmessage = second; } function second(m) { assert.equal("World", m.data); server.close(done); } }); }); it('parses one two-line message in one chunk', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["data: Hello\ndata:World\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { assert.equal("Hello\nWorld", m.data); server.close(done); }; }); }); it('parses really chopped up unicode data', function (done) { createServer(function (err, server) { if (err) return done(err); var chopped = "data: Aslak\n\ndata: Hellesøy\n\n".split(""); server.on('request', writeEvents(chopped)); var es = new EventSource(server.url); es.onmessage = first; function first(m) { assert.equal("Aslak", m.data); es.onmessage = second; } function second(m) { assert.equal("Hellesøy", m.data); server.close(done); } }); }); it('accepts CRLF as separator', function (done) { createServer(function (err, server) { if (err) return done(err); var chopped = "data: Aslak\r\n\r\ndata: Hellesøy\r\n\r\n".split(""); server.on('request', writeEvents(chopped)); var es = new EventSource(server.url); es.onmessage = first; function first(m) { assert.equal("Aslak", m.data); es.onmessage = second; } function second(m) { assert.equal("Hellesøy", m.data); server.close(done); } }); }); it('accepts CR as separator', function (done) { createServer(function (err, server) { if (err) return done(err); var chopped = "data: Aslak\r\rdata: Hellesøy\r\r".split(""); server.on('request', writeEvents(chopped)); var es = new EventSource(server.url); es.onmessage = first; function first(m) { assert.equal("Aslak", m.data); es.onmessage = second; } function second(m) { assert.equal("Hellesøy", m.data); server.close(done); } }); }); it('delivers message with explicit event', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["event: greeting\ndata: Hello\n\n"])); var es = new EventSource(server.url); es.addEventListener('greeting', function (m) { assert.equal("Hello", m.data); server.close(done); }); }); }); it('ignores comments', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["data: Hello\n\n:nothing to see here\n\ndata: World\n\n"])); var es = new EventSource(server.url); es.onmessage = first; function first(m) { assert.equal("Hello", m.data); es.onmessage = second; } function second(m) { assert.equal("World", m.data); server.close(done); } }); }); it('ignores empty comments', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["data: Hello\n\n:\n\ndata: World\n\n"])); var es = new EventSource(server.url); es.onmessage = first; function first(m) { assert.equal("Hello", m.data); es.onmessage = second; } function second(m) { assert.equal("World", m.data); server.close(done); } }); }); it('does not ignore multilines strings', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["data: line one\ndata:\ndata: line two\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { assert.equal('line one\n\nline two', m.data); server.close(done); }; }); }); it('does not ignore multilines strings even in data beginning', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["data:\ndata:line one\ndata: line two\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { assert.equal('\nline one\nline two', m.data); server.close(done); }; }); }); it('causes entire event to be ignored for empty event field', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', writeEvents(["event:\n\ndata: Hello\n\n"])); var es = new EventSource(server.url); var originalEmit = es.emit; es.emit = function (event) { assert.ok(event === 'message' || event === 'newListener'); return originalEmit.apply(this, arguments); }; es.onmessage = function (m) { assert.equal('Hello', m.data); server.close(done); }; }); }); it('parses relatively huge messages efficiently', function (done) { this.timeout(1000); createServer(function (err, server) { if (err) return done(err); var longMessage = "data: " + new Array(100000).join('a') + "\n\n"; server.on('request', writeEvents([longMessage])); var es = new EventSource(server.url); es.onmessage = function () { server.close(done); }; }); }); }); describe('HTTP Request', function () { it('passes cache-control: no-cache to server', function (done) { createServer(function (err, server) { if (err) return done(err); server.on('request', function (req) { assert.equal('no-cache', req.headers['cache-control']); server.close(done); }); new EventSource(server.url); }); }); it('sets request headers', function (done) { var server = createServer(function (err, server) { if (err) return done(err); server.on('request', function (req) { assert.equal(req.headers['user-agent'], 'test'); assert.equal(req.headers['cookie'], 'test=test'); assert.equal(req.headers['last-event-id'], '99'); server.close(done); }); var headers = { 'User-Agent': 'test', 'Cookie': 'test=test', 'Last-Event-ID': '99' }; new EventSource(server.url, {headers: headers}); }); }); it("does not set request headers that don't have a value", function (done) { var server = createServer(function (err, server) { if (err) return done(err); server.on('request', function (req) { assert.equal(req.headers['user-agent'], 'test'); assert.equal(req.headers['cookie'], 'test=test'); assert.equal(req.headers['last-event-id'], '99'); assert.equal(req.headers['X-Something'], undefined); server.close(done); }); var headers = { 'User-Agent': 'test', 'Cookie': 'test=test', 'Last-Event-ID': '99', 'X-Something': null }; assert.doesNotThrow( function() { new EventSource(server.url, {headers: headers}); } ); }); }); [301, 307].forEach(function (status) { it('follows http ' + status + ' redirect', function (done) { var redirectSuffix = '/foobar'; var clientRequestedRedirectUrl = false; createServer(function (err, server) { if(err) return done(err); server.on('request', function (req, res) { if (req.url === '/') { res.writeHead(status, { 'Connection': 'Close', 'Location': server.url + redirectSuffix }); res.end(); } else if (req.url === redirectSuffix) { clientRequestedRedirectUrl = true; res.writeHead(200, {'Content-Type': 'text/event-stream'}); res.end(); } }); var es = new EventSource(server.url); es.onopen = function () { assert.ok(clientRequestedRedirectUrl); assert.equal(server.url + redirectSuffix, es.url); server.close(done); }; }); }); it('causes error event when response is ' + status + ' with missing location', function (done) { var redirectSuffix = '/foobar'; var clientRequestedRedirectUrl = false; createServer(function (err, server) { if(err) return done(err); server.on('request', function (req, res) { res.writeHead(status, { 'Connection': 'Close' }); res.end(); }); var es = new EventSource(server.url); es.onerror = function (err) { assert.equal(err.status, status); server.close(done); }; }); }); }); [401, 403].forEach(function (status) { it('causes error event when response status is ' + status, function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', function (req, res) { res.writeHead(status, {'Content-Type': 'text/html'}); res.end(); }); var es = new EventSource(server.url); es.onerror = function (err) { assert.equal(err.status, status); server.close(done); }; }); }); }); }); describe('HTTPS Support', function () { it('uses https for https urls', function (done) { createHttpsServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(["data: hello\n\n"])); var es = new EventSource(server.url, {rejectUnauthorized: false}); es.onmessage = function (m) { assert.equal("hello", m.data); server.close(done); } }); }); }); describe('Reconnection', function () { it('is attempted when server is down', function (done) { var es = new EventSource('http://localhost:' + _port); es.reconnectInterval = 0; es.onerror = function () { es.onerror = null; createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(["data: hello\n\n"])); es.onmessage = function (m) { assert.equal("hello", m.data); server.close(done); } }); }; }); it('is attempted when server goes down after connection', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(["data: hello\n\n"])); var es = new EventSource(server.url); es.reconnectInterval = 0; es.onmessage = function (m) { assert.equal("hello", m.data); server.close(function (err) { if(err) return done(err); var port = u.parse(es.url).port; configureServer(http.createServer(), 'http', port, function (err, server2) { if(err) return done(err); server2.on('request', writeEvents(["data: world\n\n"])); es.onmessage = function (m) { assert.equal("world", m.data); server2.close(done); }; }); }); }; }); }); it('is stopped when server goes down and eventsource is being closed', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(["data: hello\n\n"])); var es = new EventSource(server.url); es.reconnectInterval = 0; es.onmessage = function (m) { assert.equal("hello", m.data); server.close(function (err) { if(err) return done(err); // The server has closed down. es.onerror should now get called, // because es's remote connection was dropped. }); }; es.onerror = function () { // We received an error because the remote connection was closed. // We close es, so we do not want es to reconnect. es.close(); var port = u.parse(es.url).port; configureServer(http.createServer(), 'http', port, function (err, server2) { if(err) return done(err); server2.on('request', writeEvents(["data: world\n\n"])); es.onmessage = function (m) { return done(new Error("Unexpected message: " + m.data)); }; setTimeout(function () { // We have not received any message within 100ms, we can // presume this works correctly. server2.close(done); }, 100); }); }; }); }); it('is not attempted when server responds with HTTP 204', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', function (req, res) { res.writeHead(204); res.end(); }); var es = new EventSource(server.url); es.reconnectInterval = 0; es.onerror = function (e) { assert.equal(e.status, 204); server.close(function (err) { if(err) return done(err); var port = u.parse(es.url).port; configureServer(http.createServer(), 'http', port, function (err, server2) { if(err) return done(err); // this will be verified by the readyState // going from CONNECTING to CLOSED, // along with the tests verifying that the // state is CONNECTING when a server closes. // it's next to impossible to write a fail-safe // test for this, though. var ival = setInterval(function () { if (es.readyState == EventSource.CLOSED) { clearInterval(ival); server2.close(done); } }, 5); }); }); }; }); }); it('sends Last-Event-ID http header when it has previously been passed in an event from the server', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(['id: 10\ndata: Hello\n\n'])); var es = new EventSource(server.url); es.reconnectInterval = 0; es.onmessage = function () { server.close(function (err) { if(err) return done(err); var port = u.parse(es.url).port; configureServer(http.createServer(), 'http', port, function (err, server2) { server2.on('request', function (req, res) { assert.equal('10', req.headers['last-event-id']); server2.close(done); }); }); }); }; }); }); it('sends correct Last-Event-ID http header when an initial Last-Event-ID header was specified in the constructor', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', function (req, res) { assert.equal('9', req.headers['last-event-id']); server.close(done); }); new EventSource(server.url, {headers: {'Last-Event-ID': '9'}}); }); }); it('does not send Last-Event-ID http header when it has not been previously sent by the server', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(['data: Hello\n\n'])); var es = new EventSource(server.url); es.reconnectInterval = 0; es.onmessage = function () { server.close(function (err) { if(err) return done(err); var port = u.parse(es.url).port; configureServer(http.createServer(), 'http', port, function (err, server2) { server2.on('request', function (req, res) { assert.equal(undefined, req.headers['last-event-id']); server2.close(done); }); }); }); }; }); }); }); describe('readyState', function () { it('has CONNECTING constant', function () { assert.equal(0, EventSource.CONNECTING); }); it('has OPEN constant', function () { assert.equal(1, EventSource.OPEN); }); it('has CLOSED constant', function () { assert.equal(2, EventSource.CLOSED); }); it('is CONNECTING before connection has been established', function (done) { var es = new EventSource('http://localhost:' + _port); assert.equal(EventSource.CONNECTING, es.readyState); es.onerror = function () { es.close(); done(); } }); it('is CONNECTING when server has closed the connection', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents([])); var es = new EventSource(server.url); es.reconnectInterval = 0; es.onopen = function (m) { server.close(function (err) { if(err) return done(err); es.onerror = function () { es.onerror = null; assert.equal(EventSource.CONNECTING, es.readyState); done(); }; }); }; }); }); it('is OPEN when connection has been established', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents([])); var es = new EventSource(server.url); es.onopen = function () { assert.equal(EventSource.OPEN, es.readyState); server.close(done); } }); }); it('is CLOSED after connection has been closed', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents([])); var es = new EventSource(server.url); es.onopen = function () { es.close(); assert.equal(EventSource.CLOSED, es.readyState); server.close(done); } }); }); }); describe('Properties', function () { it('url exposes original request url', function () { var url = 'http://localhost:' + _port; var es = new EventSource(url); assert.equal(url, es.url); }); }); describe('Events', function () { it('calls onopen when connection is established', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents([])); var es = new EventSource(server.url); es.onopen = function (event) { assert.equal(event.type, 'open'); server.close(done); } }); }); it('supplies the correct origin', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(["data: hello\n\n"])); var es = new EventSource(server.url); es.onmessage = function (event) { assert.equal(event.origin, server.url); server.close(done); } }); }); it('emits open event when connection is established', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents([])); var es = new EventSource(server.url); es.addEventListener('open', function (event) { assert.equal(event.type, 'open'); server.close(done); }); }); }); it('does not emit error when connection is closed by client', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents([])); var es = new EventSource(server.url); es.addEventListener('open', function () { es.close(); process.nextTick(function () { server.close(done); }); }); es.addEventListener('error', function () { done(new Error('error should not be emitted')); }); }); }); it('populates message\'s lastEventId correctly when the last event has an associated id', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(["id: 123\ndata: hello\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { assert.equal(m.lastEventId, "123"); server.close(done); }; }); }); it('populates message\'s lastEventId correctly when the last event doesn\'t have an associated id', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(["id: 123\ndata: Hello\n\n", "data: World\n\n"])); var es = new EventSource(server.url); es.onmessage = first; function first() { es.onmessage = second; } function second(m) { assert.equal(m.data, "World"); assert.equal(m.lastEventId, "123"); //expect to get back the previous event id server.close(done); } }); }); it('populates messages with enumerable properties so they can be inspected via console.log().', function (done) { createServer(function (err, server) { if(err) return done(err); server.on('request', writeEvents(["data: World\n\n"])); var es = new EventSource(server.url); es.onmessage = function (m) { var enumerableAttributes = Object.keys(m); assert.notEqual(enumerableAttributes.indexOf("data"), -1); assert.notEqual(enumerableAttributes.indexOf("type"), -1); server.close(done); }; }); }); }); eventsource-node-0.1.6/test/key.pem000066400000000000000000000015671246606715200172770ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQC2tDv6v//aJX8HoX7hugfReoWftqVxX2WmO8CbBFc0qfEChrR/ 3sCNg8Y0squOmQ1deEElE4h1tFtmcI14Ll/NfVr4kKjspK3MFe4ZJmvbtO0WZxXg f72AhEhw0e1mYkufFsmwiGQZHzJVh2Yll7h5PmV2TXOgHVp2A8XWFmEIEwIDAQAB AoGAAlVY8sHi/aE+9xT77twWX3mGHV0SzdjfDnly40fx6S1Gc7bOtVdd9DC7pk6l 3ENeJVR02IlgU8iC5lMHq4JEHPE272jtPrLlrpWLTGmHEqoVFv9AITPqUDLhB9Kk Hjl7h8NYBKbr2JHKICr3DIPKOT+RnXVb1PD4EORbJ3ooYmkCQQDfknUnVxPgxUGs ouABw1WJIOVgcCY/IFt4Ihf6VWTsxBgzTJKxn3HtgvE0oqTH7V480XoH0QxHhjLq DrgobWU9AkEA0TRJ8/ouXGnFEPAXjWr9GdPQRZ1Use2MrFjneH2+Sxc0CmYtwwqL Kr5kS6mqJrxprJeluSjBd+3/ElxURrEXjwJAUvmlN1OPEhXDmRHd92mKnlkyKEeX OkiFCiIFKih1S5Y/sRJTQ0781nyJjtJqO7UyC3pnQu1oFEePL+UEniRztQJAMfav AtnpYKDSM+1jcp7uu9BemYGtzKDTTAYfoiNF42EzSJiGrWJDQn4eLgPjY0T0aAf/ yGz3Z9ErbhMm/Ysl+QJBAL4kBxRT8gM4ByJw4sdOvSeCCANFq8fhbgm8pGWlCPb5 JGmX3/GHFM8x2tbWMGpyZP1DLtiNEFz7eCGktWK5rqE= -----END RSA PRIVATE KEY-----