;(function (global) { if ("EventSource" in global) return; var reTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g; var EventSource = function (url) { var eventsource = this, interval = 500, // polling interval lastEventId = null, cache = ''; if (!url || typeof url != 'string') { throw new SyntaxError('Not enough arguments'); } this.URL = url; this.readyState = this.CONNECTING; this._pollTimer = null; this._xhr = null; function pollAgain(interval) { eventsource._pollTimer = setTimeout(function () { poll.call(eventsource); }, interval); } function poll() { try { // force hiding of the error message... insane? if (eventsource.readyState == eventsource.CLOSED) return; // NOTE: IE7 and upwards support var xhr = new XMLHttpRequest(); xhr.open('GET', eventsource.URL, true); xhr.setRequestHeader('Accept', 'text/event-stream'); xhr.setRequestHeader('Cache-Control', 'no-cache'); // we must make use of this on the server side if we're working with Android - because they don't trigger // readychange until the server connection is closed xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); if (lastEventId != null) xhr.setRequestHeader('Last-Event-ID', lastEventId); cache = ''; xhr.timeout = 50000; xhr.onreadystatechange = function () { if (this.readyState == 3 || (this.readyState == 4 && this.status == 200)) { // on success if (eventsource.readyState == eventsource.CONNECTING) { eventsource.readyState = eventsource.OPEN; eventsource.dispatchEvent('open', { type: 'open' }); } var responseText = ''; try { responseText = this.responseText || ''; } catch (e) {} // process this.responseText var parts = responseText.substr(cache.length).split("\n"), eventType = 'message', data = [], i = 0, line = ''; cache = responseText; // TODO handle 'event' (for buffer name), retry for (; i < parts.length; i++) { line = parts[i].replace(reTrim, ''); if (line.indexOf('event') == 0) { eventType = line.replace(/event:?\s*/, ''); } else if (line.indexOf('retry') == 0) { var retry = parseInt(line.replace(/retry:?\s*/, '')); if(!isNaN(retry)) { interval = retry; } } else if (line.indexOf('data') == 0) { data.push(line.replace(/data:?\s*/, '')); } else if (line.indexOf('id:') == 0) { lastEventId = line.replace(/id:?\s*/, ''); } else if (line.indexOf('id') == 0) { // this resets the id lastEventId = null; } else if (line == '') { if (data.length) { var event = new MessageEvent(data.join('\n'), eventsource.url, lastEventId); eventsource.dispatchEvent(eventType, event); data = []; eventType = 'message'; } } } if (this.readyState == 4) pollAgain(interval); // don't need to poll again, because we're long-loading } else if (eventsource.readyState !== eventsource.CLOSED) { if (this.readyState == 4) { // and some other status // dispatch error eventsource.readyState = eventsource.CONNECTING; eventsource.dispatchEvent('error', { type: 'error' }); pollAgain(interval); } else if (this.readyState == 0) { // likely aborted pollAgain(interval); } else { } } }; xhr.send(); setTimeout(function () { if (true || xhr.readyState == 3) xhr.abort(); }, xhr.timeout); eventsource._xhr = xhr; } catch (e) { // in an attempt to silence the errors eventsource.dispatchEvent('error', { type: 'error', data: e.message }); // ??? } }; poll(); // init now }; EventSource.prototype = { close: function () { // closes the connection - disabling the polling this.readyState = this.CLOSED; clearInterval(this._pollTimer); this._xhr.abort(); }, CONNECTING: 0, OPEN: 1, CLOSED: 2, dispatchEvent: function (type, event) { var handlers = this['_' + type + 'Handlers']; if (handlers) { for (var i = 0; i < handlers.length; i++) { handlers[i].call(this, event); } } if (this['on' + type]) { this['on' + type].call(this, event); } }, addEventListener: function (type, handler) { if (!this['_' + type + 'Handlers']) { this['_' + type + 'Handlers'] = []; } this['_' + type + 'Handlers'].push(handler); }, removeEventListener: function (type, handler) { var handlers = this['_' + type + 'Handlers']; if (!handlers) { return; } for (var i = handlers.length - 1; i >= 0; --i) { if (handlers[i] === handler) { handlers.splice(i, 1); break; } } }, onerror: null, onmessage: null, onopen: null, readyState: 0, URL: '' }; var MessageEvent = function (data, origin, lastEventId) { this.data = data; this.origin = origin; this.lastEventId = lastEventId || ''; }; MessageEvent.prototype = { data: null, type: 'message', lastEventId: '', origin: '' }; if ('module' in global) module.exports = EventSource; global.EventSource = EventSource; })(this);