Permalink
retry is not declared as a variable, this is just to prevent it from landing up on global.
186 lines (159 sloc)
5.5 KB
| ;(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); |