├── .gitignore ├── .vsconfig └── settings.json ├── readme.md ├── package.json ├── index.js └── browser.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | -------------------------------------------------------------------------------- /.vsconfig/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib/", 3 | "editor.formatOnSave": false 4 | } 5 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Eventsource client library for browser & node.js 2 | 3 | Combined with : 4 | 5 | [npm eventsource](https://github.com/EventSource/eventsource)(node.js) 6 | 7 | & 8 | 9 | [npm event-source-polyfill](https://github.com/Yaffle/EventSource)(browser) 10 | 11 | ## Install 12 | 13 | `npm i shimo-eventsource` 14 | 15 | ## Usage 16 | 17 | ```js 18 | const EventSource = require('shimo-eventsource') 19 | new EventSource('http://your/url') 20 | ``` 21 | 22 | ## LICENSE 23 | 24 | MIT 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shimo-eventsource", 3 | "version": "2.9.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/wanming/eventsource.git" 12 | }, 13 | "author": "wanming", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/wanming/eventsource/issues" 17 | }, 18 | "homepage": "https://github.com/wanming/eventsource#readme", 19 | "dependencies": { 20 | "eventsource": "^1.0.7" 21 | }, 22 | "browser": { 23 | "./index.js": "./browser.js" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var original = require("original"); 2 | var parse = require("url").parse; 3 | var events = require("events"); 4 | var https = require("https"); 5 | var http = require("http"); 6 | var util = require("util"); 7 | 8 | var httpsOptions = [ 9 | "pfx", 10 | "key", 11 | "passphrase", 12 | "cert", 13 | "ca", 14 | "ciphers", 15 | "rejectUnauthorized", 16 | "secureProtocol", 17 | "servername", 18 | "checkServerIdentity" 19 | ]; 20 | 21 | var bom = [239, 187, 191]; 22 | var colon = 58; 23 | var space = 32; 24 | var lineFeed = 10; 25 | var carriageReturn = 13; 26 | 27 | function hasBom(buf) { 28 | return bom.every(function(charCode, index) { 29 | return buf[index] === charCode; 30 | }); 31 | } 32 | 33 | /** 34 | * Creates a new EventSource object 35 | * 36 | * @param {String} url the URL to which to connect 37 | * @param {Object} [eventSourceInitDict] extra init params. See README for details. 38 | * @api public 39 | **/ 40 | function EventSource(url, eventSourceInitDict) { 41 | var readyState = EventSource.CONNECTING; 42 | Object.defineProperty(this, "readyState", { 43 | get: function() { 44 | return readyState; 45 | } 46 | }); 47 | 48 | Object.defineProperty(this, "url", { 49 | get: function() { 50 | return url; 51 | } 52 | }); 53 | 54 | var self = this; 55 | self.reconnectInterval = 1000; 56 | 57 | function onConnectionClosed(message, status) { 58 | if (readyState === EventSource.CLOSED) return; 59 | readyState = EventSource.CONNECTING; 60 | _emit("error", new Event("error", { message: message, status: status })); 61 | 62 | // The url may have been changed by a temporary 63 | // redirect. If that's the case, revert it now. 64 | if (reconnectUrl) { 65 | url = reconnectUrl; 66 | reconnectUrl = null; 67 | } 68 | setTimeout(function() { 69 | if (readyState !== EventSource.CONNECTING) { 70 | return; 71 | } 72 | connect(); 73 | }, self.reconnectInterval); 74 | } 75 | 76 | var req; 77 | var lastEventId = ""; 78 | if ( 79 | eventSourceInitDict && 80 | eventSourceInitDict.headers && 81 | eventSourceInitDict.headers["Last-Event-ID"] 82 | ) { 83 | lastEventId = eventSourceInitDict.headers["Last-Event-ID"]; 84 | delete eventSourceInitDict.headers["Last-Event-ID"]; 85 | } 86 | 87 | var discardTrailingNewline = false; 88 | var data = ""; 89 | var eventName = ""; 90 | 91 | var reconnectUrl = null; 92 | 93 | function connect() { 94 | var options = parse(url); 95 | var isSecure = options.protocol === "https:"; 96 | options.headers = { 97 | "Cache-Control": "no-cache", 98 | Accept: "text/event-stream" 99 | }; 100 | if (lastEventId) options.headers["Last-Event-ID"] = lastEventId; 101 | if (eventSourceInitDict && eventSourceInitDict.headers) { 102 | for (var i in eventSourceInitDict.headers) { 103 | var header = eventSourceInitDict.headers[i]; 104 | if (header) { 105 | options.headers[i] = header; 106 | } 107 | } 108 | } 109 | 110 | // Legacy: this should be specified as `eventSourceInitDict.https.rejectUnauthorized`, 111 | // but for now exists as a backwards-compatibility layer 112 | options.rejectUnauthorized = !( 113 | eventSourceInitDict && !eventSourceInitDict.rejectUnauthorized 114 | ); 115 | 116 | // If specify http proxy, make the request to sent to the proxy server, 117 | // and include the original url in path and Host headers 118 | var useProxy = eventSourceInitDict && eventSourceInitDict.proxy; 119 | if (useProxy) { 120 | var proxy = parse(eventSourceInitDict.proxy); 121 | isSecure = proxy.protocol === "https:"; 122 | 123 | options.protocol = isSecure ? "https:" : "http:"; 124 | options.path = url; 125 | options.headers.Host = options.host; 126 | options.hostname = proxy.hostname; 127 | options.host = proxy.host; 128 | options.port = proxy.port; 129 | } 130 | 131 | // If https options are specified, merge them into the request options 132 | if (eventSourceInitDict && eventSourceInitDict.https) { 133 | for (var optName in eventSourceInitDict.https) { 134 | if (httpsOptions.indexOf(optName) === -1) { 135 | continue; 136 | } 137 | 138 | var option = eventSourceInitDict.https[optName]; 139 | if (option !== undefined) { 140 | options[optName] = option; 141 | } 142 | } 143 | } 144 | 145 | // Pass this on to the XHR 146 | if ( 147 | eventSourceInitDict && 148 | eventSourceInitDict.withCredentials !== undefined 149 | ) { 150 | options.withCredentials = eventSourceInitDict.withCredentials; 151 | } 152 | 153 | req = (isSecure ? https : http).request(options, function(res) { 154 | // Handle HTTP errors 155 | if ( 156 | res.statusCode === 500 || 157 | res.statusCode === 502 || 158 | res.statusCode === 503 || 159 | res.statusCode === 504 160 | ) { 161 | _emit( 162 | "error", 163 | new Event("error", { 164 | status: res.statusCode, 165 | message: res.statusMessage 166 | }) 167 | ); 168 | onConnectionClosed(); 169 | return; 170 | } 171 | 172 | // Handle HTTP redirects 173 | if (res.statusCode === 301 || res.statusCode === 307) { 174 | if (!res.headers.location) { 175 | // Server sent redirect response without Location header. 176 | _emit( 177 | "error", 178 | new Event("error", { 179 | status: res.statusCode, 180 | message: res.statusMessage 181 | }) 182 | ); 183 | return; 184 | } 185 | if (res.statusCode === 307) reconnectUrl = url; 186 | url = res.headers.location; 187 | process.nextTick(connect); 188 | return; 189 | } 190 | 191 | if (res.statusCode !== 200) { 192 | _emit( 193 | "error", 194 | new Event("error", { 195 | status: res.statusCode, 196 | message: res.statusMessage 197 | }) 198 | ); 199 | return self.close(); 200 | } 201 | 202 | readyState = EventSource.OPEN; 203 | res.on("close", function() { 204 | res.removeAllListeners("close"); 205 | res.removeAllListeners("end"); 206 | onConnectionClosed(res.statusMessage, res.statusCode); 207 | }); 208 | 209 | res.on("end", function() { 210 | res.removeAllListeners("close"); 211 | res.removeAllListeners("end"); 212 | onConnectionClosed(res.statusMessage, res.statusCode); 213 | }); 214 | _emit("open", new Event("open")); 215 | 216 | // text/event-stream parser adapted from webkit's 217 | // Source/WebCore/page/EventSource.cpp 218 | var isFirst = true; 219 | var buf; 220 | res.on("data", function(chunk) { 221 | buf = buf ? Buffer.concat([buf, chunk]) : chunk; 222 | if (isFirst && hasBom(buf)) { 223 | buf = buf.slice(bom.length); 224 | } 225 | 226 | isFirst = false; 227 | var pos = 0; 228 | var length = buf.length; 229 | 230 | while (pos < length) { 231 | if (discardTrailingNewline) { 232 | if (buf[pos] === lineFeed) { 233 | ++pos; 234 | } 235 | discardTrailingNewline = false; 236 | } 237 | 238 | var lineLength = -1; 239 | var fieldLength = -1; 240 | var c; 241 | 242 | for (var i = pos; lineLength < 0 && i < length; ++i) { 243 | c = buf[i]; 244 | if (c === colon) { 245 | if (fieldLength < 0) { 246 | fieldLength = i - pos; 247 | } 248 | } else if (c === carriageReturn) { 249 | discardTrailingNewline = true; 250 | lineLength = i - pos; 251 | } else if (c === lineFeed) { 252 | lineLength = i - pos; 253 | } 254 | } 255 | 256 | if (lineLength < 0) { 257 | break; 258 | } 259 | 260 | parseEventStreamLine(buf, pos, fieldLength, lineLength); 261 | 262 | pos += lineLength + 1; 263 | } 264 | 265 | if (pos === length) { 266 | buf = void 0; 267 | } else if (pos > 0) { 268 | buf = buf.slice(pos); 269 | } 270 | }); 271 | }); 272 | 273 | req.on("error", function(err) { 274 | onConnectionClosed(err.message); 275 | }); 276 | 277 | if (req.setNoDelay) req.setNoDelay(true); 278 | req.end(); 279 | } 280 | 281 | connect(); 282 | 283 | function _emit() { 284 | if (self.listeners(arguments[0]).length > 0) { 285 | self.emit.apply(self, arguments); 286 | } 287 | } 288 | 289 | this._close = function() { 290 | if (readyState === EventSource.CLOSED) return; 291 | readyState = EventSource.CLOSED; 292 | if (req.abort) req.abort(); 293 | if (req.xhr && req.xhr.abort) req.xhr.abort(); 294 | }; 295 | 296 | function parseEventStreamLine(buf, pos, fieldLength, lineLength) { 297 | if (lineLength === 0) { 298 | if (data.length > 0) { 299 | var type = eventName || "message"; 300 | _emit( 301 | type, 302 | new MessageEvent(type, { 303 | data: data.slice(0, -1), // remove trailing newline 304 | lastEventId: lastEventId, 305 | origin: original(url) 306 | }) 307 | ); 308 | data = ""; 309 | } 310 | eventName = void 0; 311 | } else if (fieldLength > 0) { 312 | var noValue = fieldLength < 0; 313 | var step = 0; 314 | var field = buf 315 | .slice(pos, pos + (noValue ? lineLength : fieldLength)) 316 | .toString(); 317 | 318 | if (noValue) { 319 | step = lineLength; 320 | } else if (buf[pos + fieldLength + 1] !== space) { 321 | step = fieldLength + 1; 322 | } else { 323 | step = fieldLength + 2; 324 | } 325 | pos += step; 326 | 327 | var valueLength = lineLength - step; 328 | var value = buf.slice(pos, pos + valueLength).toString(); 329 | 330 | if (field === "data") { 331 | data += value + "\n"; 332 | } else if (field === "event") { 333 | eventName = value; 334 | } else if (field === "id") { 335 | lastEventId = value; 336 | } else if (field === "retry") { 337 | var retry = parseInt(value, 10); 338 | if (!Number.isNaN(retry)) { 339 | self.reconnectInterval = retry; 340 | } 341 | } 342 | } 343 | } 344 | } 345 | 346 | module.exports = EventSource; 347 | 348 | util.inherits(EventSource, events.EventEmitter); 349 | EventSource.prototype.constructor = EventSource; // make stacktraces readable 350 | 351 | ["open", "error", "message"].forEach(function(method) { 352 | Object.defineProperty(EventSource.prototype, "on" + method, { 353 | /** 354 | * Returns the current listener 355 | * 356 | * @return {Mixed} the set function or undefined 357 | * @api private 358 | */ 359 | get: function get() { 360 | var listener = this.listeners(method)[0]; 361 | return listener 362 | ? listener._listener 363 | ? listener._listener 364 | : listener 365 | : undefined; 366 | }, 367 | 368 | /** 369 | * Start listening for events 370 | * 371 | * @param {Function} listener the listener 372 | * @return {Mixed} the set function or undefined 373 | * @api private 374 | */ 375 | set: function set(listener) { 376 | this.removeAllListeners(method); 377 | this.addEventListener(method, listener); 378 | } 379 | }); 380 | }); 381 | 382 | /** 383 | * Ready states 384 | */ 385 | Object.defineProperty(EventSource, "CONNECTING", { 386 | enumerable: true, 387 | value: 0 388 | }); 389 | Object.defineProperty(EventSource, "OPEN", { enumerable: true, value: 1 }); 390 | Object.defineProperty(EventSource, "CLOSED", { enumerable: true, value: 2 }); 391 | 392 | EventSource.prototype.CONNECTING = 0; 393 | EventSource.prototype.OPEN = 1; 394 | EventSource.prototype.CLOSED = 2; 395 | 396 | /** 397 | * Closes the connection, if one is made, and sets the readyState attribute to 2 (closed) 398 | * 399 | * @see https://developer.mozilla.org/en-US/docs/Web/API/EventSource/close 400 | * @api public 401 | */ 402 | EventSource.prototype.close = function() { 403 | this._close(); 404 | }; 405 | 406 | /** 407 | * Emulates the W3C Browser based WebSocket interface using addEventListener. 408 | * 409 | * @param {String} type A string representing the event type to listen out for 410 | * @param {Function} listener callback 411 | * @see https://developer.mozilla.org/en/DOM/element.addEventListener 412 | * @see http://dev.w3.org/html5/websockets/#the-websocket-interface 413 | * @api public 414 | */ 415 | EventSource.prototype.addEventListener = function addEventListener( 416 | type, 417 | listener 418 | ) { 419 | if (typeof listener === "function") { 420 | // store a reference so we can return the original function again 421 | listener._listener = listener; 422 | this.on(type, listener); 423 | } 424 | }; 425 | 426 | /** 427 | * Emulates the W3C Browser based WebSocket interface using dispatchEvent. 428 | * 429 | * @param {Event} event An event to be dispatched 430 | * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent 431 | * @api public 432 | */ 433 | EventSource.prototype.dispatchEvent = function dispatchEvent(event) { 434 | if (!event.type) { 435 | throw new Error("UNSPECIFIED_EVENT_TYPE_ERR"); 436 | } 437 | // if event is instance of an CustomEvent (or has 'details' property), 438 | // send the detail object as the payload for the event 439 | this.emit(event.type, event.detail); 440 | }; 441 | 442 | /** 443 | * Emulates the W3C Browser based WebSocket interface using removeEventListener. 444 | * 445 | * @param {String} type A string representing the event type to remove 446 | * @param {Function} listener callback 447 | * @see https://developer.mozilla.org/en/DOM/element.removeEventListener 448 | * @see http://dev.w3.org/html5/websockets/#the-websocket-interface 449 | * @api public 450 | */ 451 | EventSource.prototype.removeEventListener = function removeEventListener( 452 | type, 453 | listener 454 | ) { 455 | if (typeof listener === "function") { 456 | listener._listener = undefined; 457 | this.removeListener(type, listener); 458 | } 459 | }; 460 | 461 | /** 462 | * W3C Event 463 | * 464 | * @see http://www.w3.org/TR/DOM-Level-3-Events/#interface-Event 465 | * @api private 466 | */ 467 | function Event(type, optionalProperties) { 468 | Object.defineProperty(this, "type", { 469 | writable: false, 470 | value: type, 471 | enumerable: true 472 | }); 473 | if (optionalProperties) { 474 | for (var f in optionalProperties) { 475 | if (optionalProperties.hasOwnProperty(f)) { 476 | Object.defineProperty(this, f, { 477 | writable: false, 478 | value: optionalProperties[f], 479 | enumerable: true 480 | }); 481 | } 482 | } 483 | } 484 | } 485 | 486 | /** 487 | * W3C MessageEvent 488 | * 489 | * @see http://www.w3.org/TR/webmessaging/#event-definitions 490 | * @api private 491 | */ 492 | function MessageEvent(type, eventInitDict) { 493 | Object.defineProperty(this, "type", { 494 | writable: false, 495 | value: type, 496 | enumerable: true 497 | }); 498 | for (var f in eventInitDict) { 499 | if (eventInitDict.hasOwnProperty(f)) { 500 | Object.defineProperty(this, f, { 501 | writable: false, 502 | value: eventInitDict[f], 503 | enumerable: true 504 | }); 505 | } 506 | } 507 | } 508 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | /** @license 2 | * eventsource.js 3 | * Available under MIT License (MIT) 4 | * https://github.com/Yaffle/EventSource/ 5 | */ 6 | 7 | var GLOBAL; 8 | 9 | if (typeof self !== "undefined") { 10 | GLOBAL = self; 11 | } else { 12 | GLOBAL = window; 13 | } 14 | 15 | var setTimeout = GLOBAL.setTimeout.bind(GLOBAL); 16 | var clearTimeout = GLOBAL.clearTimeout.bind(GLOBAL); 17 | var XMLHttpRequest = GLOBAL.XMLHttpRequest; 18 | var XDomainRequest = GLOBAL.XDomainRequest; 19 | var document = GLOBAL.document; 20 | var Promise = GLOBAL.Promise; 21 | var fetch = GLOBAL.fetch.bind(GLOBAL); 22 | var Response = GLOBAL.Response; 23 | var TextDecoder = GLOBAL.TextDecoder; 24 | var TextEncoder = GLOBAL.TextEncoder; 25 | var AbortController = GLOBAL.AbortController; 26 | var HEARTBEAT_TIMEOUT = 5000; 27 | 28 | if (Object.create == undefined) { 29 | Object.create = function(C) { 30 | function F() {} 31 | F.prototype = C; 32 | return new F(); 33 | }; 34 | } 35 | 36 | if (Promise != undefined && Promise.prototype["finally"] == undefined) { 37 | Promise.prototype["finally"] = function(callback) { 38 | return this.then( 39 | function(result) { 40 | return Promise.resolve(callback()).then(function() { 41 | return result; 42 | }); 43 | }, 44 | function(error) { 45 | return Promise.resolve(callback()).then(function() { 46 | throw error; 47 | }); 48 | } 49 | ); 50 | }; 51 | } 52 | 53 | // see #118, #123, #125 54 | if (fetch != undefined && true) { 55 | var originalFetch = fetch; 56 | fetch = function(url, options) { 57 | return Promise.resolve(originalFetch(url, options)); 58 | }; 59 | } 60 | 61 | if (AbortController == undefined) { 62 | AbortController = function() { 63 | this.signal = null; 64 | this.abort = function() {}; 65 | }; 66 | } 67 | 68 | function TextDecoderPolyfill() { 69 | this.bitsNeeded = 0; 70 | this.codePoint = 0; 71 | } 72 | 73 | TextDecoderPolyfill.prototype.decode = function(octets) { 74 | function valid(codePoint, shift, octetsCount) { 75 | if (octetsCount === 1) { 76 | return codePoint >= 0x0080 >> shift && codePoint << shift <= 0x07ff; 77 | } 78 | if (octetsCount === 2) { 79 | return ( 80 | (codePoint >= 0x0800 >> shift && codePoint << shift <= 0xd7ff) || 81 | (codePoint >= 0xe000 >> shift && codePoint << shift <= 0xffff) 82 | ); 83 | } 84 | if (octetsCount === 3) { 85 | return codePoint >= 0x010000 >> shift && codePoint << shift <= 0x10ffff; 86 | } 87 | throw new Error(); 88 | } 89 | function octetsCount(bitsNeeded, codePoint) { 90 | if (bitsNeeded === 6 * 1) { 91 | return codePoint >> 6 > 15 ? 3 : codePoint > 31 ? 2 : 1; 92 | } 93 | if (bitsNeeded === 6 * 2) { 94 | return codePoint > 15 ? 3 : 2; 95 | } 96 | if (bitsNeeded === 6 * 3) { 97 | return 3; 98 | } 99 | throw new Error(); 100 | } 101 | var REPLACER = 0xfffd; 102 | var string = ""; 103 | var bitsNeeded = this.bitsNeeded; 104 | var codePoint = this.codePoint; 105 | for (var i = 0; i < octets.length; i += 1) { 106 | var octet = octets[i]; 107 | if (bitsNeeded !== 0) { 108 | if ( 109 | octet < 128 || 110 | octet > 191 || 111 | !valid( 112 | (codePoint << 6) | (octet & 63), 113 | bitsNeeded - 6, 114 | octetsCount(bitsNeeded, codePoint) 115 | ) 116 | ) { 117 | bitsNeeded = 0; 118 | codePoint = REPLACER; 119 | string += String.fromCharCode(codePoint); 120 | } 121 | } 122 | if (bitsNeeded === 0) { 123 | if (octet >= 0 && octet <= 127) { 124 | bitsNeeded = 0; 125 | codePoint = octet; 126 | } else if (octet >= 192 && octet <= 223) { 127 | bitsNeeded = 6 * 1; 128 | codePoint = octet & 31; 129 | } else if (octet >= 224 && octet <= 239) { 130 | bitsNeeded = 6 * 2; 131 | codePoint = octet & 15; 132 | } else if (octet >= 240 && octet <= 247) { 133 | bitsNeeded = 6 * 3; 134 | codePoint = octet & 7; 135 | } else { 136 | bitsNeeded = 0; 137 | codePoint = REPLACER; 138 | } 139 | if ( 140 | bitsNeeded !== 0 && 141 | !valid(codePoint, bitsNeeded, octetsCount(bitsNeeded, codePoint)) 142 | ) { 143 | bitsNeeded = 0; 144 | codePoint = REPLACER; 145 | } 146 | } else { 147 | bitsNeeded -= 6; 148 | codePoint = (codePoint << 6) | (octet & 63); 149 | } 150 | if (bitsNeeded === 0) { 151 | if (codePoint <= 0xffff) { 152 | string += String.fromCharCode(codePoint); 153 | } else { 154 | string += String.fromCharCode( 155 | 0xd800 + ((codePoint - 0xffff - 1) >> 10) 156 | ); 157 | string += String.fromCharCode( 158 | 0xdc00 + ((codePoint - 0xffff - 1) & 0x3ff) 159 | ); 160 | } 161 | } 162 | } 163 | this.bitsNeeded = bitsNeeded; 164 | this.codePoint = codePoint; 165 | return string; 166 | }; 167 | 168 | // Firefox < 38 throws an error with stream option 169 | var supportsStreamOption = function() { 170 | try { 171 | return ( 172 | new TextDecoder().decode(new TextEncoder().encode("test"), { 173 | stream: true 174 | }) === "test" 175 | ); 176 | } catch (error) { 177 | console.log(error); 178 | } 179 | return false; 180 | }; 181 | 182 | // IE, Edge 183 | if ( 184 | TextDecoder == undefined || 185 | TextEncoder == undefined || 186 | !supportsStreamOption() 187 | ) { 188 | TextDecoder = TextDecoderPolyfill; 189 | } 190 | 191 | var k = function() {}; 192 | 193 | function XHRWrapper(xhr) { 194 | this.withCredentials = false; 195 | this.responseType = ""; 196 | this.readyState = 0; 197 | this.status = 0; 198 | this.statusText = ""; 199 | this.responseText = ""; 200 | this.onprogress = k; 201 | this.onreadystatechange = k; 202 | this._contentType = ""; 203 | this._xhr = xhr; 204 | this._sendTimeout = 0; 205 | this._abort = k; 206 | } 207 | 208 | XHRWrapper.prototype.open = function(method, url) { 209 | this._abort(true); 210 | 211 | var that = this; 212 | var xhr = this._xhr; 213 | var state = 1; 214 | var timeout = 0; 215 | 216 | this._abort = function(silent) { 217 | if (that._sendTimeout !== 0) { 218 | clearTimeout(that._sendTimeout); 219 | that._sendTimeout = 0; 220 | } 221 | if (state === 1 || state === 2 || state === 3) { 222 | state = 4; 223 | xhr.onload = k; 224 | xhr.onerror = k; 225 | xhr.onabort = k; 226 | xhr.onprogress = k; 227 | xhr.onreadystatechange = k; 228 | // IE 8 - 9: XDomainRequest#abort() does not fire any event 229 | // Opera < 10: XMLHttpRequest#abort() does not fire any event 230 | xhr.abort(); 231 | if (timeout !== 0) { 232 | clearTimeout(timeout); 233 | timeout = 0; 234 | } 235 | if (!silent) { 236 | that.readyState = 4; 237 | that.onreadystatechange(); 238 | } 239 | } 240 | state = 0; 241 | }; 242 | 243 | var onStart = function() { 244 | if (state === 1) { 245 | //state = 2; 246 | var status = 0; 247 | var statusText = ""; 248 | var contentType = undefined; 249 | if (!("contentType" in xhr)) { 250 | try { 251 | status = xhr.status; 252 | statusText = xhr.statusText; 253 | contentType = xhr.getResponseHeader("Content-Type"); 254 | } catch (error) { 255 | // IE < 10 throws exception for `xhr.status` when xhr.readyState === 2 || xhr.readyState === 3 256 | // Opera < 11 throws exception for `xhr.status` when xhr.readyState === 2 257 | // https://bugs.webkit.org/show_bug.cgi?id=29121 258 | status = 0; 259 | statusText = ""; 260 | contentType = undefined; 261 | // Firefox < 14, Chrome ?, Safari ? 262 | // https://bugs.webkit.org/show_bug.cgi?id=29658 263 | // https://bugs.webkit.org/show_bug.cgi?id=77854 264 | } 265 | } else { 266 | status = 200; 267 | statusText = "OK"; 268 | contentType = xhr.contentType; 269 | } 270 | if (status !== 0) { 271 | state = 2; 272 | that.readyState = 2; 273 | that.status = status; 274 | that.statusText = statusText; 275 | that._contentType = contentType; 276 | that.onreadystatechange(); 277 | } 278 | } 279 | }; 280 | var onProgress = function() { 281 | onStart(); 282 | if (state === 2 || state === 3) { 283 | state = 3; 284 | var responseText = ""; 285 | try { 286 | responseText = xhr.responseText; 287 | } catch (error) { 288 | // IE 8 - 9 with XMLHttpRequest 289 | } 290 | that.readyState = 3; 291 | that.responseText = responseText; 292 | that.onprogress(); 293 | } 294 | }; 295 | var onFinish = function() { 296 | // Firefox 52 fires "readystatechange" (xhr.readyState === 4) without final "readystatechange" (xhr.readyState === 3) 297 | // IE 8 fires "onload" without "onprogress" 298 | onProgress(); 299 | if (state === 1 || state === 2 || state === 3) { 300 | state = 4; 301 | if (timeout !== 0) { 302 | clearTimeout(timeout); 303 | timeout = 0; 304 | } 305 | that.readyState = 4; 306 | that.onreadystatechange(); 307 | } 308 | }; 309 | var onReadyStateChange = function() { 310 | if (xhr != undefined) { 311 | // Opera 12 312 | if (xhr.readyState === 4) { 313 | onFinish(); 314 | } else if (xhr.readyState === 3) { 315 | onProgress(); 316 | } else if (xhr.readyState === 2) { 317 | onStart(); 318 | } 319 | } 320 | }; 321 | var onTimeout = function() { 322 | timeout = setTimeout(function() { 323 | onTimeout(); 324 | }, 500); 325 | if (xhr.readyState === 3) { 326 | onProgress(); 327 | } 328 | }; 329 | 330 | // XDomainRequest#abort removes onprogress, onerror, onload 331 | xhr.onload = onFinish; 332 | xhr.onerror = onFinish; 333 | // improper fix to match Firefox behaviour, but it is better than just ignore abort 334 | // see https://bugzilla.mozilla.org/show_bug.cgi?id=768596 335 | // https://bugzilla.mozilla.org/show_bug.cgi?id=880200 336 | // https://code.google.com/p/chromium/issues/detail?id=153570 337 | // IE 8 fires "onload" without "onprogress 338 | xhr.onabort = onFinish; 339 | 340 | // https://bugzilla.mozilla.org/show_bug.cgi?id=736723 341 | if ( 342 | !("sendAsBinary" in XMLHttpRequest.prototype) && 343 | !("mozAnon" in XMLHttpRequest.prototype) 344 | ) { 345 | xhr.onprogress = onProgress; 346 | } 347 | 348 | // IE 8 - 9 (XMLHTTPRequest) 349 | // Opera < 12 350 | // Firefox < 3.5 351 | // Firefox 3.5 - 3.6 - ? < 9.0 352 | // onprogress is not fired sometimes or delayed 353 | // see also #64 354 | xhr.onreadystatechange = onReadyStateChange; 355 | 356 | if ("contentType" in xhr) { 357 | url += (url.indexOf("?") === -1 ? "?" : "&") + "padding=true"; 358 | } 359 | xhr.open(method, url, true); 360 | 361 | if ("readyState" in xhr) { 362 | // workaround for Opera 12 issue with "progress" events 363 | // #91 364 | timeout = setTimeout(function() { 365 | onTimeout(); 366 | }, 0); 367 | } 368 | }; 369 | XHRWrapper.prototype.abort = function() { 370 | this._abort(false); 371 | }; 372 | XHRWrapper.prototype.getResponseHeader = function(name) { 373 | return this._contentType; 374 | }; 375 | XHRWrapper.prototype.setRequestHeader = function(name, value) { 376 | var xhr = this._xhr; 377 | if ("setRequestHeader" in xhr) { 378 | xhr.setRequestHeader(name, value); 379 | } 380 | }; 381 | XHRWrapper.prototype.getAllResponseHeaders = function() { 382 | return this._xhr.getAllResponseHeaders != undefined 383 | ? this._xhr.getAllResponseHeaders() 384 | : ""; 385 | }; 386 | XHRWrapper.prototype.send = function() { 387 | // loading indicator in Safari < ? (6), Chrome < 14, Firefox 388 | if ( 389 | !("ontimeout" in XMLHttpRequest.prototype) && 390 | document != undefined && 391 | document.readyState != undefined && 392 | document.readyState !== "complete" 393 | ) { 394 | var that = this; 395 | that._sendTimeout = setTimeout(function() { 396 | that._sendTimeout = 0; 397 | that.send(); 398 | }, 4); 399 | return; 400 | } 401 | 402 | var xhr = this._xhr; 403 | // withCredentials should be set after "open" for Safari and Chrome (< 19 ?) 404 | xhr.withCredentials = this.withCredentials; 405 | xhr.responseType = this.responseType; 406 | try { 407 | // xhr.send(); throws "Not enough arguments" in Firefox 3.0 408 | xhr.send(undefined); 409 | } catch (error1) { 410 | // Safari 5.1.7, Opera 12 411 | throw error1; 412 | } 413 | }; 414 | 415 | function toLowerCase(name) { 416 | return name.replace(/[A-Z]/g, function(c) { 417 | return String.fromCharCode(c.charCodeAt(0) + 0x20); 418 | }); 419 | } 420 | 421 | function HeadersPolyfill(all) { 422 | // Get headers: implemented according to mozilla's example code: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/getAllResponseHeaders#Example 423 | var map = Object.create(null); 424 | var array = all.split("\r\n"); 425 | for (var i = 0; i < array.length; i += 1) { 426 | var line = array[i]; 427 | var parts = line.split(": "); 428 | var name = parts.shift(); 429 | var value = parts.join(": "); 430 | map[toLowerCase(name)] = value; 431 | } 432 | this._map = map; 433 | } 434 | HeadersPolyfill.prototype.get = function(name) { 435 | return this._map[toLowerCase(name)]; 436 | }; 437 | 438 | function XHRTransport() {} 439 | 440 | XHRTransport.prototype.open = function( 441 | xhr, 442 | onStartCallback, 443 | onProgressCallback, 444 | onFinishCallback, 445 | url, 446 | withCredentials, 447 | headers 448 | ) { 449 | xhr.open("GET", url); 450 | var offset = 0; 451 | var abortTimeout; 452 | 453 | function setAbortTimeout() { 454 | abortTimeout = setTimeout(function() { 455 | xhr.abort(); 456 | }, HEARTBEAT_TIMEOUT); 457 | } 458 | 459 | setAbortTimeout(); 460 | xhr.onprogress = function() { 461 | var responseText = xhr.responseText; 462 | var chunk = responseText.slice(offset); 463 | offset += chunk.length; 464 | 465 | if (chunk.length > 0) { 466 | clearTimeout(abortTimeout); 467 | setAbortTimeout(); 468 | } 469 | 470 | onProgressCallback(chunk); 471 | }; 472 | xhr.onreadystatechange = function() { 473 | if (xhr.readyState === 2) { 474 | var status = xhr.status; 475 | var statusText = xhr.statusText; 476 | var contentType = xhr.getResponseHeader("Content-Type"); 477 | var headers = xhr.getAllResponseHeaders(); 478 | onStartCallback( 479 | status, 480 | statusText, 481 | contentType, 482 | new HeadersPolyfill(headers), 483 | function() { 484 | xhr.abort(); 485 | } 486 | ); 487 | } else if (xhr.readyState === 4) { 488 | onFinishCallback(); 489 | } 490 | }; 491 | xhr.withCredentials = withCredentials; 492 | xhr.responseType = "text"; 493 | for (var name in headers) { 494 | if (Object.prototype.hasOwnProperty.call(headers, name)) { 495 | xhr.setRequestHeader(name, headers[name]); 496 | } 497 | } 498 | xhr.send(); 499 | }; 500 | 501 | function HeadersWrapper(headers) { 502 | this._headers = headers; 503 | } 504 | HeadersWrapper.prototype.get = function(name) { 505 | return this._headers.get(name); 506 | }; 507 | 508 | function FetchTransport() {} 509 | 510 | FetchTransport.prototype.open = function( 511 | xhr, 512 | onStartCallback, 513 | onProgressCallback, 514 | onFinishCallback, 515 | url, 516 | withCredentials, 517 | headers 518 | ) { 519 | var controller = new AbortController(); 520 | var signal = controller.signal; // see #120 521 | var textDecoder = new TextDecoder(); 522 | fetch(url, { 523 | headers: headers, 524 | credentials: withCredentials ? "include" : "same-origin", 525 | signal: signal, 526 | cache: "no-store" 527 | }) 528 | .then(function(response) { 529 | var reader = response.body.getReader(); 530 | onStartCallback( 531 | response.status, 532 | response.statusText, 533 | response.headers.get("Content-Type"), 534 | new HeadersWrapper(response.headers), 535 | function() { 536 | controller.abort(); 537 | reader.cancel().catch(function() {}); 538 | } 539 | ); 540 | return new Promise(function(resolve, reject) { 541 | var readNextChunk = function() { 542 | new Promise(function(res, rej) { 543 | var timer = setTimeout(function() { 544 | rej(new Error("Reader read timeout")); 545 | timer = null; 546 | }, HEARTBEAT_TIMEOUT); 547 | 548 | reader.read().then(function(result) { 549 | if (timer) { 550 | clearTimeout(timer); 551 | res(result); 552 | } 553 | }, rej); 554 | }) 555 | .then(function(result) { 556 | if (result.done) { 557 | //Note: bytes in textDecoder are ignored 558 | resolve(undefined); 559 | } else { 560 | var chunk = textDecoder.decode(result.value, { stream: true }); 561 | onProgressCallback(chunk); 562 | readNextChunk(); 563 | } 564 | }) 565 | ["catch"](function(error) { 566 | reject(error); 567 | }); 568 | }; 569 | readNextChunk(); 570 | }); 571 | }) 572 | .catch(function() {}) 573 | ["finally"](function() { 574 | onFinishCallback(); 575 | }); 576 | }; 577 | 578 | function EventTarget() { 579 | this._listeners = Object.create(null); 580 | } 581 | 582 | function throwError(e) { 583 | setTimeout(function() { 584 | throw e; 585 | }, 0); 586 | } 587 | 588 | EventTarget.prototype.dispatchEvent = function(event) { 589 | event.target = this; 590 | var typeListeners = this._listeners[event.type]; 591 | if (typeListeners != undefined) { 592 | var length = typeListeners.length; 593 | for (var i = 0; i < length; i += 1) { 594 | var listener = typeListeners[i]; 595 | try { 596 | if (typeof listener.handleEvent === "function") { 597 | listener.handleEvent(event); 598 | } else { 599 | listener.call(this, event); 600 | } 601 | } catch (e) { 602 | throwError(e); 603 | } 604 | } 605 | } 606 | }; 607 | EventTarget.prototype.addEventListener = function(type, listener) { 608 | type = String(type); 609 | var listeners = this._listeners; 610 | var typeListeners = listeners[type]; 611 | if (typeListeners == undefined) { 612 | typeListeners = []; 613 | listeners[type] = typeListeners; 614 | } 615 | var found = false; 616 | for (var i = 0; i < typeListeners.length; i += 1) { 617 | if (typeListeners[i] === listener) { 618 | found = true; 619 | } 620 | } 621 | if (!found) { 622 | typeListeners.push(listener); 623 | } 624 | }; 625 | EventTarget.prototype.removeEventListener = function(type, listener) { 626 | type = String(type); 627 | var listeners = this._listeners; 628 | var typeListeners = listeners[type]; 629 | if (typeListeners != undefined) { 630 | var filtered = []; 631 | for (var i = 0; i < typeListeners.length; i += 1) { 632 | if (typeListeners[i] !== listener) { 633 | filtered.push(typeListeners[i]); 634 | } 635 | } 636 | if (filtered.length === 0) { 637 | delete listeners[type]; 638 | } else { 639 | listeners[type] = filtered; 640 | } 641 | } 642 | }; 643 | 644 | function Event(type, data) { 645 | this.type = type; 646 | this.target = undefined; 647 | 648 | for (var key in data) { 649 | if (!this.hasOwnProperty(key)) { 650 | this[key] = data[key]; 651 | } 652 | } 653 | } 654 | 655 | function MessageEvent(type, options) { 656 | Event.call(this, type); 657 | this.data = options.data; 658 | this.lastEventId = options.lastEventId; 659 | } 660 | 661 | MessageEvent.prototype = Object.create(Event.prototype); 662 | 663 | function ConnectionEvent(type, options) { 664 | Event.call(this, type); 665 | this.status = options.status; 666 | this.statusText = options.statusText; 667 | this.headers = options.headers; 668 | } 669 | 670 | ConnectionEvent.prototype = Object.create(Event.prototype); 671 | 672 | var WAITING = -1; 673 | var CONNECTING = 0; 674 | var OPEN = 1; 675 | var CLOSED = 2; 676 | 677 | var AFTER_CR = -1; 678 | var FIELD_START = 0; 679 | var FIELD = 1; 680 | var VALUE_START = 2; 681 | var VALUE = 3; 682 | 683 | var contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i; 684 | 685 | var MINIMUM_DURATION = 1000; 686 | var MAXIMUM_DURATION = 18000000; 687 | 688 | var parseDuration = function(value, def) { 689 | var n = parseInt(value, 10); 690 | if (n !== n) { 691 | n = def; 692 | } 693 | return clampDuration(n); 694 | }; 695 | var clampDuration = function(n) { 696 | return Math.min(Math.max(n, MINIMUM_DURATION), MAXIMUM_DURATION); 697 | }; 698 | 699 | var fire = function(that, f, event) { 700 | try { 701 | if (typeof f === "function") { 702 | f.call(that, event); 703 | } 704 | } catch (e) { 705 | throwError(e); 706 | } 707 | }; 708 | 709 | function EventSourcePolyfill(url, options) { 710 | EventTarget.call(this); 711 | 712 | this.onopen = undefined; 713 | this.onmessage = undefined; 714 | this.onerror = undefined; 715 | 716 | this.url = undefined; 717 | this.readyState = undefined; 718 | this.withCredentials = undefined; 719 | 720 | this._close = undefined; 721 | 722 | this.status = 0; 723 | start(this, url, options); 724 | } 725 | 726 | function getBestTransport() { 727 | return (XMLHttpRequest != undefined && 728 | "withCredentials" in XMLHttpRequest.prototype) || 729 | XDomainRequest == undefined 730 | ? XMLHttpRequest 731 | : XDomainRequest; 732 | } 733 | 734 | var isFetchSupported = 735 | fetch != undefined && Response != undefined && "body" in Response.prototype; 736 | 737 | function start(es, url, options) { 738 | url = String(url); 739 | var withCredentials = 740 | options != undefined && Boolean(options.withCredentials); 741 | 742 | var initialRetry = clampDuration(1000); 743 | var heartbeatTimeout = 744 | options != undefined && options.heartbeatTimeout != undefined 745 | ? parseDuration(options.heartbeatTimeout, 45000) 746 | : clampDuration(45000); 747 | 748 | var lastEventId = ""; 749 | var retry = initialRetry; 750 | var wasActivity = false; 751 | var headers = 752 | options != undefined && options.headers != undefined 753 | ? JSON.parse(JSON.stringify(options.headers)) 754 | : undefined; 755 | var CurrentTransport = 756 | options != undefined && options.Transport != undefined 757 | ? options.Transport 758 | : getBestTransport(); 759 | var xhr = 760 | isFetchSupported && 761 | !( 762 | options != undefined && 763 | (options.Transport != undefined || options.forceXhr) 764 | ) 765 | ? undefined 766 | : new XHRWrapper(new CurrentTransport()); 767 | var transport = xhr == undefined ? new FetchTransport() : new XHRTransport(); 768 | var cancelFunction = undefined; 769 | var timeout = 0; 770 | var currentState = WAITING; 771 | var dataBuffer = ""; 772 | var lastEventIdBuffer = ""; 773 | var eventTypeBuffer = ""; 774 | 775 | var textBuffer = ""; 776 | var state = FIELD_START; 777 | var fieldStart = 0; 778 | var valueStart = 0; 779 | 780 | var onStart = function(status, statusText, contentType, headers, cancel) { 781 | es.status = status; 782 | if (currentState === CONNECTING) { 783 | cancelFunction = cancel; 784 | if ( 785 | status === 200 && 786 | contentType != undefined && 787 | contentTypeRegExp.test(contentType) 788 | ) { 789 | currentState = OPEN; 790 | wasActivity = true; 791 | retry = initialRetry; 792 | es.readyState = OPEN; 793 | var event = new ConnectionEvent("open", { 794 | status: status, 795 | statusText: statusText, 796 | headers: headers 797 | }); 798 | es.dispatchEvent(event); 799 | fire(es, es.onopen, event); 800 | } else { 801 | var message = ""; 802 | if (status !== 200) { 803 | if (statusText) { 804 | statusText = statusText.replace(/\s+/g, " "); 805 | } 806 | message = 807 | "EventSource's response has a status " + 808 | status + 809 | " " + 810 | statusText + 811 | " that is not 200. Aborting the connection."; 812 | } else { 813 | message = 814 | "EventSource's response has a Content-Type specifying an unsupported type: " + 815 | (contentType == undefined 816 | ? "-" 817 | : contentType.replace(/\s+/g, " ")) + 818 | ". Aborting the connection."; 819 | } 820 | throwError(new Error(message)); 821 | close(); 822 | var event = new ConnectionEvent("error", { 823 | status: status, 824 | statusText: statusText, 825 | headers: headers 826 | }); 827 | es.dispatchEvent(event); 828 | fire(es, es.onerror, event); 829 | } 830 | } 831 | }; 832 | 833 | var onProgress = function(textChunk) { 834 | if (currentState === OPEN) { 835 | var n = -1; 836 | for (var i = 0; i < textChunk.length; i += 1) { 837 | var c = textChunk.charCodeAt(i); 838 | if (c === "\n".charCodeAt(0) || c === "\r".charCodeAt(0)) { 839 | n = i; 840 | } 841 | } 842 | var chunk = (n !== -1 ? textBuffer : "") + textChunk.slice(0, n + 1); 843 | textBuffer = (n === -1 ? textBuffer : "") + textChunk.slice(n + 1); 844 | if (chunk !== "") { 845 | wasActivity = true; 846 | } 847 | for (var position = 0; position < chunk.length; position += 1) { 848 | var c = chunk.charCodeAt(position); 849 | if (state === AFTER_CR && c === "\n".charCodeAt(0)) { 850 | state = FIELD_START; 851 | } else { 852 | if (state === AFTER_CR) { 853 | state = FIELD_START; 854 | } 855 | if (c === "\r".charCodeAt(0) || c === "\n".charCodeAt(0)) { 856 | if (state !== FIELD_START) { 857 | if (state === FIELD) { 858 | valueStart = position + 1; 859 | } 860 | var field = chunk.slice(fieldStart, valueStart - 1); 861 | var value = chunk.slice( 862 | valueStart + 863 | (valueStart < position && 864 | chunk.charCodeAt(valueStart) === " ".charCodeAt(0) 865 | ? 1 866 | : 0), 867 | position 868 | ); 869 | if (field === "data") { 870 | dataBuffer += "\n"; 871 | dataBuffer += value; 872 | } else if (field === "id") { 873 | lastEventIdBuffer = value; 874 | } else if (field === "event") { 875 | eventTypeBuffer = value; 876 | } else if (field === "retry") { 877 | initialRetry = parseDuration(value, initialRetry); 878 | retry = initialRetry; 879 | } else if (field === "heartbeatTimeout") { 880 | heartbeatTimeout = parseDuration(value, heartbeatTimeout); 881 | if (timeout !== 0) { 882 | clearTimeout(timeout); 883 | timeout = setTimeout(function() { 884 | onTimeout(); 885 | }, heartbeatTimeout); 886 | } 887 | } 888 | } 889 | if (state === FIELD_START) { 890 | if (dataBuffer !== "") { 891 | lastEventId = lastEventIdBuffer; 892 | if (eventTypeBuffer === "") { 893 | eventTypeBuffer = "message"; 894 | } 895 | var event = new MessageEvent(eventTypeBuffer, { 896 | data: dataBuffer.slice(1), 897 | lastEventId: lastEventIdBuffer 898 | }); 899 | es.dispatchEvent(event); 900 | if (eventTypeBuffer === "message") { 901 | fire(es, es.onmessage, event); 902 | } 903 | if (currentState === CLOSED) { 904 | return; 905 | } 906 | } 907 | dataBuffer = ""; 908 | eventTypeBuffer = ""; 909 | } 910 | state = c === "\r".charCodeAt(0) ? AFTER_CR : FIELD_START; 911 | } else { 912 | if (state === FIELD_START) { 913 | fieldStart = position; 914 | state = FIELD; 915 | } 916 | if (state === FIELD) { 917 | if (c === ":".charCodeAt(0)) { 918 | valueStart = position + 1; 919 | state = VALUE_START; 920 | } 921 | } else if (state === VALUE_START) { 922 | state = VALUE; 923 | } 924 | } 925 | } 926 | } 927 | } 928 | }; 929 | 930 | var onFinish = function() { 931 | if (currentState === OPEN || currentState === CONNECTING) { 932 | currentState = WAITING; 933 | if (timeout !== 0) { 934 | clearTimeout(timeout); 935 | timeout = 0; 936 | } 937 | timeout = setTimeout(function() { 938 | onTimeout(); 939 | }, retry); 940 | retry = clampDuration(Math.min(initialRetry * 16, retry * 2)); 941 | 942 | es.readyState = CONNECTING; 943 | var event = new Event("error", { status: es.status }); 944 | es.dispatchEvent(event); 945 | fire(es, es.onerror, event); 946 | } 947 | }; 948 | 949 | var close = function() { 950 | currentState = CLOSED; 951 | if (cancelFunction != undefined) { 952 | cancelFunction(); 953 | cancelFunction = undefined; 954 | } 955 | if (timeout !== 0) { 956 | clearTimeout(timeout); 957 | timeout = 0; 958 | } 959 | es.readyState = CLOSED; 960 | }; 961 | 962 | var onTimeout = function() { 963 | timeout = 0; 964 | 965 | if (currentState !== WAITING) { 966 | if (!wasActivity && cancelFunction != undefined) { 967 | throwError( 968 | new Error( 969 | "No activity within " + 970 | heartbeatTimeout + 971 | " milliseconds. Reconnecting." 972 | ) 973 | ); 974 | cancelFunction(); 975 | cancelFunction = undefined; 976 | } else { 977 | wasActivity = false; 978 | timeout = setTimeout(function() { 979 | onTimeout(); 980 | }, heartbeatTimeout); 981 | } 982 | return; 983 | } 984 | 985 | wasActivity = false; 986 | timeout = setTimeout(function() { 987 | onTimeout(); 988 | }, heartbeatTimeout); 989 | 990 | currentState = CONNECTING; 991 | dataBuffer = ""; 992 | eventTypeBuffer = ""; 993 | lastEventIdBuffer = lastEventId; 994 | textBuffer = ""; 995 | fieldStart = 0; 996 | valueStart = 0; 997 | state = FIELD_START; 998 | 999 | // https://bugzilla.mozilla.org/show_bug.cgi?id=428916 1000 | // Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers. 1001 | var requestURL = url; 1002 | if (url.slice(0, 5) !== "data:" && url.slice(0, 5) !== "blob:") { 1003 | if (lastEventId !== "") { 1004 | requestURL += 1005 | (url.indexOf("?") === -1 ? "?" : "&") + 1006 | "lastEventId=" + 1007 | encodeURIComponent(lastEventId); 1008 | } 1009 | } 1010 | var requestHeaders = {}; 1011 | requestHeaders["Accept"] = "text/event-stream"; 1012 | if (headers != undefined) { 1013 | for (var name in headers) { 1014 | if (Object.prototype.hasOwnProperty.call(headers, name)) { 1015 | requestHeaders[name] = headers[name]; 1016 | } 1017 | } 1018 | } 1019 | try { 1020 | transport.open( 1021 | xhr, 1022 | onStart, 1023 | onProgress, 1024 | onFinish, 1025 | requestURL, 1026 | withCredentials, 1027 | requestHeaders 1028 | ); 1029 | } catch (error) { 1030 | close(); 1031 | throw error; 1032 | } 1033 | }; 1034 | 1035 | es.url = url; 1036 | es.readyState = CONNECTING; 1037 | es.withCredentials = withCredentials; 1038 | es._close = close; 1039 | 1040 | onTimeout(); 1041 | } 1042 | 1043 | EventSourcePolyfill.prototype = Object.create(EventTarget.prototype); 1044 | EventSourcePolyfill.prototype.CONNECTING = CONNECTING; 1045 | EventSourcePolyfill.prototype.OPEN = OPEN; 1046 | EventSourcePolyfill.prototype.CLOSED = CLOSED; 1047 | EventSourcePolyfill.prototype.close = function() { 1048 | this._close(); 1049 | }; 1050 | 1051 | EventSourcePolyfill.CONNECTING = CONNECTING; 1052 | EventSourcePolyfill.OPEN = OPEN; 1053 | EventSourcePolyfill.CLOSED = CLOSED; 1054 | EventSourcePolyfill.prototype.withCredentials = undefined; 1055 | 1056 | module.exports = EventSourcePolyfill; 1057 | --------------------------------------------------------------------------------