847 lines
24 KiB
JavaScript
847 lines
24 KiB
JavaScript
|
/* eslint-disable */
|
||
|
// Improved version of https://github.com/Yaffle/EventSource/
|
||
|
// Available under MIT License (MIT)
|
||
|
// Only tries to support IE11 and nothing below
|
||
|
import fetch from 'unfetch'
|
||
|
|
||
|
var document = window.document
|
||
|
var Response = window.Response
|
||
|
var TextDecoder = window.TextDecoder
|
||
|
var TextEncoder = window.TextEncoder
|
||
|
var AbortController = window.AbortController
|
||
|
|
||
|
if (AbortController == undefined) {
|
||
|
AbortController = function () {
|
||
|
this.signal = null
|
||
|
this.abort = function () {
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function TextDecoderPolyfill () {
|
||
|
this.bitsNeeded = 0
|
||
|
this.codePoint = 0
|
||
|
}
|
||
|
|
||
|
TextDecoderPolyfill.prototype.decode = function (octets) {
|
||
|
function valid (codePoint, shift, octetsCount) {
|
||
|
if (octetsCount === 1) {
|
||
|
return codePoint >= 0x0080 >> shift && codePoint << shift <= 0x07FF
|
||
|
}
|
||
|
if (octetsCount === 2) {
|
||
|
return codePoint >= 0x0800 >> shift && codePoint << shift <= 0xD7FF || codePoint >= 0xE000 >> shift && codePoint << shift <= 0xFFFF
|
||
|
}
|
||
|
if (octetsCount === 3) {
|
||
|
return codePoint >= 0x010000 >> shift && codePoint << shift <= 0x10FFFF
|
||
|
}
|
||
|
throw new Error()
|
||
|
}
|
||
|
function octetsCount (bitsNeeded, codePoint) {
|
||
|
if (bitsNeeded === 6 * 1) {
|
||
|
return codePoint >> 6 > 15 ? 3 : codePoint > 31 ? 2 : 1
|
||
|
}
|
||
|
if (bitsNeeded === 6 * 2) {
|
||
|
return codePoint > 15 ? 3 : 2
|
||
|
}
|
||
|
if (bitsNeeded === 6 * 3) {
|
||
|
return 3
|
||
|
}
|
||
|
throw new Error()
|
||
|
}
|
||
|
var REPLACER = 0xFFFD
|
||
|
var string = ''
|
||
|
var bitsNeeded = this.bitsNeeded
|
||
|
var codePoint = this.codePoint
|
||
|
for (var i = 0; i < octets.length; i += 1) {
|
||
|
var octet = octets[i]
|
||
|
if (bitsNeeded !== 0) {
|
||
|
if (octet < 128 || octet > 191 || !valid(codePoint << 6 | octet & 63, bitsNeeded - 6, octetsCount(bitsNeeded, codePoint))) {
|
||
|
bitsNeeded = 0
|
||
|
codePoint = REPLACER
|
||
|
string += String.fromCharCode(codePoint)
|
||
|
}
|
||
|
}
|
||
|
if (bitsNeeded === 0) {
|
||
|
if (octet >= 0 && octet <= 127) {
|
||
|
bitsNeeded = 0
|
||
|
codePoint = octet
|
||
|
} else if (octet >= 192 && octet <= 223) {
|
||
|
bitsNeeded = 6 * 1
|
||
|
codePoint = octet & 31
|
||
|
} else if (octet >= 224 && octet <= 239) {
|
||
|
bitsNeeded = 6 * 2
|
||
|
codePoint = octet & 15
|
||
|
} else if (octet >= 240 && octet <= 247) {
|
||
|
bitsNeeded = 6 * 3
|
||
|
codePoint = octet & 7
|
||
|
} else {
|
||
|
bitsNeeded = 0
|
||
|
codePoint = REPLACER
|
||
|
}
|
||
|
if (bitsNeeded !== 0 && !valid(codePoint, bitsNeeded, octetsCount(bitsNeeded, codePoint))) {
|
||
|
bitsNeeded = 0
|
||
|
codePoint = REPLACER
|
||
|
}
|
||
|
} else {
|
||
|
bitsNeeded -= 6
|
||
|
codePoint = codePoint << 6 | octet & 63
|
||
|
}
|
||
|
if (bitsNeeded === 0) {
|
||
|
if (codePoint <= 0xFFFF) {
|
||
|
string += String.fromCharCode(codePoint)
|
||
|
} else {
|
||
|
string += String.fromCharCode(0xD800 + (codePoint - 0xFFFF - 1 >> 10))
|
||
|
string += String.fromCharCode(0xDC00 + (codePoint - 0xFFFF - 1 & 0x3FF))
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this.bitsNeeded = bitsNeeded
|
||
|
this.codePoint = codePoint
|
||
|
return string
|
||
|
}
|
||
|
|
||
|
// Firefox < 38 throws an error with stream option
|
||
|
var supportsStreamOption = function () {
|
||
|
try {
|
||
|
return new TextDecoder().decode(new TextEncoder().encode('test'), { stream: true }) === 'test'
|
||
|
} catch (error) {
|
||
|
console.log(error)
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// IE, Edge
|
||
|
if (TextDecoder == undefined || TextEncoder == undefined || !supportsStreamOption()) {
|
||
|
TextDecoder = TextDecoderPolyfill
|
||
|
}
|
||
|
|
||
|
var k = function () {
|
||
|
}
|
||
|
|
||
|
function XHRWrapper (xhr) {
|
||
|
this.withCredentials = false
|
||
|
this.responseType = ''
|
||
|
this.readyState = 0
|
||
|
this.status = 0
|
||
|
this.statusText = ''
|
||
|
this.responseText = ''
|
||
|
this.onprogress = k
|
||
|
this.onreadystatechange = k
|
||
|
this._contentType = ''
|
||
|
this._xhr = xhr
|
||
|
this._sendTimeout = 0
|
||
|
this._abort = k
|
||
|
}
|
||
|
|
||
|
XHRWrapper.prototype.open = function (method, url) {
|
||
|
this._abort(true)
|
||
|
|
||
|
var that = this
|
||
|
var xhr = this._xhr
|
||
|
var state = 1
|
||
|
var timeout = 0
|
||
|
|
||
|
this._abort = function (silent) {
|
||
|
if (that._sendTimeout !== 0) {
|
||
|
clearTimeout(that._sendTimeout)
|
||
|
that._sendTimeout = 0
|
||
|
}
|
||
|
if (state === 1 || state === 2 || state === 3) {
|
||
|
state = 4
|
||
|
xhr.onload = k
|
||
|
xhr.onerror = k
|
||
|
xhr.onabort = k
|
||
|
xhr.onprogress = k
|
||
|
xhr.onreadystatechange = k
|
||
|
// IE 8 - 9: XDomainRequest#abort() does not fire any event
|
||
|
// Opera < 10: XMLHttpRequest#abort() does not fire any event
|
||
|
xhr.abort()
|
||
|
if (timeout !== 0) {
|
||
|
clearTimeout(timeout)
|
||
|
timeout = 0
|
||
|
}
|
||
|
if (!silent) {
|
||
|
that.readyState = 4
|
||
|
that.onreadystatechange()
|
||
|
}
|
||
|
}
|
||
|
state = 0
|
||
|
}
|
||
|
|
||
|
var onStart = function () {
|
||
|
if (state === 1) {
|
||
|
// state = 2;
|
||
|
var status = 0
|
||
|
var statusText = ''
|
||
|
var contentType = undefined
|
||
|
if (!('contentType' in xhr)) {
|
||
|
try {
|
||
|
status = xhr.status
|
||
|
statusText = xhr.statusText
|
||
|
contentType = xhr.getResponseHeader('Content-Type')
|
||
|
} catch (error) {
|
||
|
// IE < 10 throws exception for `xhr.status` when xhr.readyState === 2 || xhr.readyState === 3
|
||
|
// Opera < 11 throws exception for `xhr.status` when xhr.readyState === 2
|
||
|
// https://bugs.webkit.org/show_bug.cgi?id=29121
|
||
|
status = 0
|
||
|
statusText = ''
|
||
|
contentType = undefined
|
||
|
// Firefox < 14, Chrome ?, Safari ?
|
||
|
// https://bugs.webkit.org/show_bug.cgi?id=29658
|
||
|
// https://bugs.webkit.org/show_bug.cgi?id=77854
|
||
|
}
|
||
|
} else {
|
||
|
status = 200
|
||
|
statusText = 'OK'
|
||
|
contentType = xhr.contentType
|
||
|
}
|
||
|
if (status !== 0) {
|
||
|
state = 2
|
||
|
that.readyState = 2
|
||
|
that.status = status
|
||
|
that.statusText = statusText
|
||
|
that._contentType = contentType
|
||
|
that.onreadystatechange()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
var onProgress = function () {
|
||
|
onStart()
|
||
|
if (state === 2 || state === 3) {
|
||
|
state = 3
|
||
|
var responseText = ''
|
||
|
try {
|
||
|
responseText = xhr.responseText
|
||
|
} catch (error) {
|
||
|
// IE 8 - 9 with XMLHttpRequest
|
||
|
}
|
||
|
that.readyState = 3
|
||
|
that.responseText = responseText
|
||
|
that.onprogress()
|
||
|
}
|
||
|
}
|
||
|
var onFinish = function () {
|
||
|
// Firefox 52 fires "readystatechange" (xhr.readyState === 4) without final "readystatechange" (xhr.readyState === 3)
|
||
|
// IE 8 fires "onload" without "onprogress"
|
||
|
onProgress()
|
||
|
if (state === 1 || state === 2 || state === 3) {
|
||
|
state = 4
|
||
|
if (timeout !== 0) {
|
||
|
clearTimeout(timeout)
|
||
|
timeout = 0
|
||
|
}
|
||
|
that.readyState = 4
|
||
|
that.onreadystatechange()
|
||
|
}
|
||
|
}
|
||
|
var onReadyStateChange = function () {
|
||
|
if (xhr != undefined) { // Opera 12
|
||
|
if (xhr.readyState === 4) {
|
||
|
onFinish()
|
||
|
} else if (xhr.readyState === 3) {
|
||
|
onProgress()
|
||
|
} else if (xhr.readyState === 2) {
|
||
|
onStart()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
var onTimeout = function () {
|
||
|
timeout = setTimeout(function () {
|
||
|
onTimeout()
|
||
|
}, 500)
|
||
|
if (xhr.readyState === 3) {
|
||
|
onProgress()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// XDomainRequest#abort removes onprogress, onerror, onload
|
||
|
xhr.onload = onFinish
|
||
|
xhr.onerror = onFinish
|
||
|
// improper fix to match Firefox behaviour, but it is better than just ignore abort
|
||
|
// see https://bugzilla.mozilla.org/show_bug.cgi?id=768596
|
||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=880200
|
||
|
// https://code.google.com/p/chromium/issues/detail?id=153570
|
||
|
// IE 8 fires "onload" without "onprogress
|
||
|
xhr.onabort = onFinish
|
||
|
|
||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=736723
|
||
|
if (!('sendAsBinary' in XMLHttpRequest.prototype) && !('mozAnon' in XMLHttpRequest.prototype)) {
|
||
|
xhr.onprogress = onProgress
|
||
|
}
|
||
|
|
||
|
// IE 8 - 9 (XMLHTTPRequest)
|
||
|
// Opera < 12
|
||
|
// Firefox < 3.5
|
||
|
// Firefox 3.5 - 3.6 - ? < 9.0
|
||
|
// onprogress is not fired sometimes or delayed
|
||
|
// see also #64
|
||
|
xhr.onreadystatechange = onReadyStateChange
|
||
|
|
||
|
if ('contentType' in xhr) {
|
||
|
url += (url.indexOf('?') === -1 ? '?' : '&') + 'padding=true'
|
||
|
}
|
||
|
xhr.open(method, url, true)
|
||
|
|
||
|
if ('readyState' in xhr) {
|
||
|
// workaround for Opera 12 issue with "progress" events
|
||
|
// #91
|
||
|
timeout = setTimeout(function () {
|
||
|
onTimeout()
|
||
|
}, 0)
|
||
|
}
|
||
|
}
|
||
|
XHRWrapper.prototype.abort = function () {
|
||
|
this._abort(false)
|
||
|
}
|
||
|
XHRWrapper.prototype.getResponseHeader = function (name) {
|
||
|
return this._contentType
|
||
|
}
|
||
|
XHRWrapper.prototype.setRequestHeader = function (name, value) {
|
||
|
var xhr = this._xhr
|
||
|
if ('setRequestHeader' in xhr) {
|
||
|
xhr.setRequestHeader(name, value)
|
||
|
}
|
||
|
}
|
||
|
XHRWrapper.prototype.getAllResponseHeaders = function () {
|
||
|
return this._xhr.getAllResponseHeaders != undefined ? this._xhr.getAllResponseHeaders() : ''
|
||
|
}
|
||
|
XHRWrapper.prototype.send = function () {
|
||
|
// loading indicator in Safari < ? (6), Chrome < 14, Firefox
|
||
|
if (!('ontimeout' in XMLHttpRequest.prototype) &&
|
||
|
document != undefined &&
|
||
|
document.readyState != undefined &&
|
||
|
document.readyState !== 'complete') {
|
||
|
var that = this
|
||
|
that._sendTimeout = setTimeout(function () {
|
||
|
that._sendTimeout = 0
|
||
|
that.send()
|
||
|
}, 4)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var xhr = this._xhr
|
||
|
// withCredentials should be set after "open" for Safari and Chrome (< 19 ?)
|
||
|
xhr.withCredentials = this.withCredentials
|
||
|
xhr.responseType = this.responseType
|
||
|
try {
|
||
|
// xhr.send(); throws "Not enough arguments" in Firefox 3.0
|
||
|
xhr.send(undefined)
|
||
|
} catch (error1) {
|
||
|
// Safari 5.1.7, Opera 12
|
||
|
throw error1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function toLowerCase (name) {
|
||
|
return name.replace(/[A-Z]/g, function (c) {
|
||
|
return String.fromCharCode(c.charCodeAt(0) + 0x20)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
function HeadersPolyfill (all) {
|
||
|
// Get headers: implemented according to mozilla's example code: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/getAllResponseHeaders#Example
|
||
|
var map = Object.create(null)
|
||
|
var array = all.split('\r\n')
|
||
|
for (var i = 0; i < array.length; i += 1) {
|
||
|
var line = array[i]
|
||
|
var parts = line.split(': ')
|
||
|
var name = parts.shift()
|
||
|
var value = parts.join(': ')
|
||
|
map[toLowerCase(name)] = value
|
||
|
}
|
||
|
this._map = map
|
||
|
}
|
||
|
HeadersPolyfill.prototype.get = function (name) {
|
||
|
return this._map[toLowerCase(name)]
|
||
|
}
|
||
|
|
||
|
function XHRTransport () {
|
||
|
}
|
||
|
|
||
|
XHRTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) {
|
||
|
xhr.open('GET', url)
|
||
|
var offset = 0
|
||
|
xhr.onprogress = function () {
|
||
|
var responseText = xhr.responseText
|
||
|
var chunk = responseText.slice(offset)
|
||
|
offset += chunk.length
|
||
|
onProgressCallback(chunk)
|
||
|
}
|
||
|
xhr.onreadystatechange = function () {
|
||
|
if (xhr.readyState === 2) {
|
||
|
var status = xhr.status
|
||
|
var statusText = xhr.statusText
|
||
|
var contentType = xhr.getResponseHeader('Content-Type')
|
||
|
var headers = xhr.getAllResponseHeaders()
|
||
|
onStartCallback(status, statusText, contentType, new HeadersPolyfill(headers), function () {
|
||
|
xhr.abort()
|
||
|
})
|
||
|
} else if (xhr.readyState === 4) {
|
||
|
onFinishCallback()
|
||
|
}
|
||
|
}
|
||
|
xhr.withCredentials = withCredentials
|
||
|
xhr.responseType = 'text'
|
||
|
for (var name in headers) {
|
||
|
if (Object.prototype.hasOwnProperty.call(headers, name)) {
|
||
|
xhr.setRequestHeader(name, headers[name])
|
||
|
}
|
||
|
}
|
||
|
xhr.send()
|
||
|
}
|
||
|
|
||
|
function HeadersWrapper (headers) {
|
||
|
this._headers = headers
|
||
|
}
|
||
|
HeadersWrapper.prototype.get = function (name) {
|
||
|
return this._headers.get(name)
|
||
|
}
|
||
|
|
||
|
function FetchTransport () {
|
||
|
}
|
||
|
|
||
|
FetchTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) {
|
||
|
var controller = new AbortController()
|
||
|
var signal = controller.signal// see #120
|
||
|
var textDecoder = new TextDecoder()
|
||
|
fetch(url, {
|
||
|
headers: headers,
|
||
|
credentials: withCredentials ? 'include' : 'same-origin',
|
||
|
signal: signal,
|
||
|
cache: 'no-store'
|
||
|
}).then(function (response) {
|
||
|
var reader = response.body.getReader()
|
||
|
onStartCallback(response.status, response.statusText, response.headers.get('Content-Type'), new HeadersWrapper(response.headers), function () {
|
||
|
controller.abort()
|
||
|
reader.cancel()
|
||
|
})
|
||
|
return new Promise(function (resolve, reject) {
|
||
|
var readNextChunk = function () {
|
||
|
reader.read().then(function (result) {
|
||
|
if (result.done) {
|
||
|
// Note: bytes in textDecoder are ignored
|
||
|
resolve(undefined)
|
||
|
} else {
|
||
|
var chunk = textDecoder.decode(result.value, { stream: true })
|
||
|
onProgressCallback(chunk)
|
||
|
readNextChunk()
|
||
|
}
|
||
|
})['catch'](function (error) {
|
||
|
reject(error)
|
||
|
})
|
||
|
}
|
||
|
readNextChunk()
|
||
|
})
|
||
|
})['finally'](function () {
|
||
|
onFinishCallback()
|
||
|
})
|
||
|
}
|
||
|
|
||
|
function EventTarget () {
|
||
|
this._listeners = Object.create(null)
|
||
|
}
|
||
|
|
||
|
function throwError (e) {
|
||
|
setTimeout(function () {
|
||
|
throw e
|
||
|
}, 0)
|
||
|
}
|
||
|
|
||
|
EventTarget.prototype.dispatchEvent = function (event) {
|
||
|
event.target = this
|
||
|
var typeListeners = this._listeners[event.type]
|
||
|
if (typeListeners != undefined) {
|
||
|
var length = typeListeners.length
|
||
|
for (var i = 0; i < length; i += 1) {
|
||
|
var listener = typeListeners[i]
|
||
|
try {
|
||
|
if (typeof listener.handleEvent === 'function') {
|
||
|
listener.handleEvent(event)
|
||
|
} else {
|
||
|
listener.call(this, event)
|
||
|
}
|
||
|
} catch (e) {
|
||
|
throwError(e)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
EventTarget.prototype.addEventListener = function (type, listener) {
|
||
|
type = String(type)
|
||
|
var listeners = this._listeners
|
||
|
var typeListeners = listeners[type]
|
||
|
if (typeListeners == undefined) {
|
||
|
typeListeners = []
|
||
|
listeners[type] = typeListeners
|
||
|
}
|
||
|
var found = false
|
||
|
for (var i = 0; i < typeListeners.length; i += 1) {
|
||
|
if (typeListeners[i] === listener) {
|
||
|
found = true
|
||
|
}
|
||
|
}
|
||
|
if (!found) {
|
||
|
typeListeners.push(listener)
|
||
|
}
|
||
|
}
|
||
|
EventTarget.prototype.removeEventListener = function (type, listener) {
|
||
|
type = String(type)
|
||
|
var listeners = this._listeners
|
||
|
var typeListeners = listeners[type]
|
||
|
if (typeListeners != undefined) {
|
||
|
var filtered = []
|
||
|
for (var i = 0; i < typeListeners.length; i += 1) {
|
||
|
if (typeListeners[i] !== listener) {
|
||
|
filtered.push(typeListeners[i])
|
||
|
}
|
||
|
}
|
||
|
if (filtered.length === 0) {
|
||
|
delete listeners[type]
|
||
|
} else {
|
||
|
listeners[type] = filtered
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function Event (type) {
|
||
|
this.type = type
|
||
|
this.target = undefined
|
||
|
}
|
||
|
|
||
|
function MessageEvent (type, options) {
|
||
|
Event.call(this, type)
|
||
|
this.data = options.data
|
||
|
this.lastEventId = options.lastEventId
|
||
|
}
|
||
|
|
||
|
MessageEvent.prototype = Object.create(Event.prototype)
|
||
|
|
||
|
function ConnectionEvent (type, options) {
|
||
|
Event.call(this, type)
|
||
|
this.status = options.status
|
||
|
this.statusText = options.statusText
|
||
|
this.headers = options.headers
|
||
|
}
|
||
|
|
||
|
ConnectionEvent.prototype = Object.create(Event.prototype)
|
||
|
|
||
|
var WAITING = -1
|
||
|
var CONNECTING = 0
|
||
|
var OPEN = 1
|
||
|
var CLOSED = 2
|
||
|
|
||
|
var AFTER_CR = -1
|
||
|
var FIELD_START = 0
|
||
|
var FIELD = 1
|
||
|
var VALUE_START = 2
|
||
|
var VALUE = 3
|
||
|
|
||
|
var contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i
|
||
|
|
||
|
var MINIMUM_DURATION = 1000
|
||
|
var MAXIMUM_DURATION = 18000000
|
||
|
|
||
|
var parseDuration = function (value, def) {
|
||
|
var n = parseInt(value, 10)
|
||
|
if (n !== n) {
|
||
|
n = def
|
||
|
}
|
||
|
return clampDuration(n)
|
||
|
}
|
||
|
var clampDuration = function (n) {
|
||
|
return Math.min(Math.max(n, MINIMUM_DURATION), MAXIMUM_DURATION)
|
||
|
}
|
||
|
|
||
|
var fire = function (that, f, event) {
|
||
|
try {
|
||
|
if (typeof f === 'function') {
|
||
|
f.call(that, event)
|
||
|
}
|
||
|
} catch (e) {
|
||
|
throwError(e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function EventSourcePolyfill (url, options) {
|
||
|
EventTarget.call(this)
|
||
|
|
||
|
this.onopen = undefined
|
||
|
this.onmessage = undefined
|
||
|
this.onerror = undefined
|
||
|
|
||
|
this.url = undefined
|
||
|
this.readyState = undefined
|
||
|
this.withCredentials = undefined
|
||
|
|
||
|
this._close = undefined
|
||
|
|
||
|
start(this, url, options)
|
||
|
}
|
||
|
|
||
|
var isFetchSupported = fetch != undefined && Response != undefined && 'body' in Response.prototype
|
||
|
|
||
|
function start (es, url, options) {
|
||
|
url = String(url)
|
||
|
var withCredentials = options != undefined && Boolean(options.withCredentials)
|
||
|
|
||
|
var initialRetry = clampDuration(1000)
|
||
|
var heartbeatTimeout = options != undefined && options.heartbeatTimeout != undefined ? parseDuration(options.heartbeatTimeout, 45000) : clampDuration(45000)
|
||
|
|
||
|
var lastEventId = ''
|
||
|
var retry = initialRetry
|
||
|
var wasActivity = false
|
||
|
var headers = options != undefined && options.headers != undefined ? JSON.parse(JSON.stringify(options.headers)) : undefined
|
||
|
var CurrentTransport = options != undefined && options.Transport != undefined ? options.Transport : XMLHttpRequest
|
||
|
var xhr = isFetchSupported && !(options != undefined && options.Transport != undefined) ? undefined : new XHRWrapper(new CurrentTransport())
|
||
|
var transport = xhr == undefined ? new FetchTransport() : new XHRTransport()
|
||
|
var cancelFunction = undefined
|
||
|
var timeout = 0
|
||
|
var currentState = WAITING
|
||
|
var dataBuffer = ''
|
||
|
var lastEventIdBuffer = ''
|
||
|
var eventTypeBuffer = ''
|
||
|
|
||
|
var textBuffer = ''
|
||
|
var state = FIELD_START
|
||
|
var fieldStart = 0
|
||
|
var valueStart = 0
|
||
|
|
||
|
var onStart = function (status, statusText, contentType, headers, cancel) {
|
||
|
if (currentState === CONNECTING) {
|
||
|
cancelFunction = cancel
|
||
|
if (status === 200 && contentType != undefined && contentTypeRegExp.test(contentType)) {
|
||
|
currentState = OPEN
|
||
|
wasActivity = true
|
||
|
retry = initialRetry
|
||
|
es.readyState = OPEN
|
||
|
var event = new ConnectionEvent('open', {
|
||
|
status: status,
|
||
|
statusText: statusText,
|
||
|
headers: headers
|
||
|
})
|
||
|
es.dispatchEvent(event)
|
||
|
fire(es, es.onopen, event)
|
||
|
} else {
|
||
|
var message = ''
|
||
|
if (status !== 200) {
|
||
|
if (statusText) {
|
||
|
statusText = statusText.replace(/\s+/g, ' ')
|
||
|
}
|
||
|
message = "EventSource's response has a status " + status + ' ' + statusText + ' that is not 200. Aborting the connection.'
|
||
|
} else {
|
||
|
message = "EventSource's response has a Content-Type specifying an unsupported type: " + (contentType == undefined ? '-' : contentType.replace(/\s+/g, ' ')) + '. Aborting the connection.'
|
||
|
}
|
||
|
throwError(new Error(message))
|
||
|
close()
|
||
|
var event = new ConnectionEvent('error', {
|
||
|
status: status,
|
||
|
statusText: statusText,
|
||
|
headers: headers
|
||
|
})
|
||
|
es.dispatchEvent(event)
|
||
|
fire(es, es.onerror, event)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var onProgress = function (textChunk) {
|
||
|
if (currentState === OPEN) {
|
||
|
var n = -1
|
||
|
for (var i = 0; i < textChunk.length; i += 1) {
|
||
|
var c = textChunk.charCodeAt(i)
|
||
|
if (c === '\n'.charCodeAt(0) || c === '\r'.charCodeAt(0)) {
|
||
|
n = i
|
||
|
}
|
||
|
}
|
||
|
var chunk = (n !== -1 ? textBuffer : '') + textChunk.slice(0, n + 1)
|
||
|
textBuffer = (n === -1 ? textBuffer : '') + textChunk.slice(n + 1)
|
||
|
if (chunk !== '') {
|
||
|
wasActivity = true
|
||
|
}
|
||
|
for (var position = 0; position < chunk.length; position += 1) {
|
||
|
var c = chunk.charCodeAt(position)
|
||
|
if (state === AFTER_CR && c === '\n'.charCodeAt(0)) {
|
||
|
state = FIELD_START
|
||
|
} else {
|
||
|
if (state === AFTER_CR) {
|
||
|
state = FIELD_START
|
||
|
}
|
||
|
if (c === '\r'.charCodeAt(0) || c === '\n'.charCodeAt(0)) {
|
||
|
if (state !== FIELD_START) {
|
||
|
if (state === FIELD) {
|
||
|
valueStart = position + 1
|
||
|
}
|
||
|
var field = chunk.slice(fieldStart, valueStart - 1)
|
||
|
var value = chunk.slice(valueStart + (valueStart < position && chunk.charCodeAt(valueStart) === ' '.charCodeAt(0) ? 1 : 0), position)
|
||
|
if (field === 'data') {
|
||
|
dataBuffer += '\n'
|
||
|
dataBuffer += value
|
||
|
} else if (field === 'id') {
|
||
|
lastEventIdBuffer = value
|
||
|
} else if (field === 'event') {
|
||
|
eventTypeBuffer = value
|
||
|
} else if (field === 'retry') {
|
||
|
initialRetry = parseDuration(value, initialRetry)
|
||
|
retry = initialRetry
|
||
|
} else if (field === 'heartbeatTimeout') {
|
||
|
heartbeatTimeout = parseDuration(value, heartbeatTimeout)
|
||
|
if (timeout !== 0) {
|
||
|
clearTimeout(timeout)
|
||
|
timeout = setTimeout(function () {
|
||
|
onTimeout()
|
||
|
}, heartbeatTimeout)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (state === FIELD_START) {
|
||
|
if (dataBuffer !== '') {
|
||
|
lastEventId = lastEventIdBuffer
|
||
|
if (eventTypeBuffer === '') {
|
||
|
eventTypeBuffer = 'message'
|
||
|
}
|
||
|
var event = new MessageEvent(eventTypeBuffer, {
|
||
|
data: dataBuffer.slice(1),
|
||
|
lastEventId: lastEventIdBuffer
|
||
|
})
|
||
|
es.dispatchEvent(event)
|
||
|
if (eventTypeBuffer === 'message') {
|
||
|
fire(es, es.onmessage, event)
|
||
|
}
|
||
|
if (currentState === CLOSED) {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
dataBuffer = ''
|
||
|
eventTypeBuffer = ''
|
||
|
}
|
||
|
state = c === '\r'.charCodeAt(0) ? AFTER_CR : FIELD_START
|
||
|
} else {
|
||
|
if (state === FIELD_START) {
|
||
|
fieldStart = position
|
||
|
state = FIELD
|
||
|
}
|
||
|
if (state === FIELD) {
|
||
|
if (c === ':'.charCodeAt(0)) {
|
||
|
valueStart = position + 1
|
||
|
state = VALUE_START
|
||
|
}
|
||
|
} else if (state === VALUE_START) {
|
||
|
state = VALUE
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var onFinish = function () {
|
||
|
if (currentState === OPEN || currentState === CONNECTING) {
|
||
|
currentState = WAITING
|
||
|
if (timeout !== 0) {
|
||
|
clearTimeout(timeout)
|
||
|
timeout = 0
|
||
|
}
|
||
|
timeout = setTimeout(function () {
|
||
|
onTimeout()
|
||
|
}, retry)
|
||
|
retry = clampDuration(Math.min(initialRetry * 16, retry * 2))
|
||
|
|
||
|
es.readyState = CONNECTING
|
||
|
var event = new Event('error')
|
||
|
es.dispatchEvent(event)
|
||
|
fire(es, es.onerror, event)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var close = function () {
|
||
|
currentState = CLOSED
|
||
|
if (cancelFunction != undefined) {
|
||
|
cancelFunction()
|
||
|
cancelFunction = undefined
|
||
|
}
|
||
|
if (timeout !== 0) {
|
||
|
clearTimeout(timeout)
|
||
|
timeout = 0
|
||
|
}
|
||
|
es.readyState = CLOSED
|
||
|
}
|
||
|
|
||
|
var onTimeout = function () {
|
||
|
timeout = 0
|
||
|
|
||
|
if (currentState !== WAITING) {
|
||
|
if (!wasActivity && cancelFunction != undefined) {
|
||
|
throwError(new Error('No activity within ' + heartbeatTimeout + ' milliseconds. Reconnecting.'))
|
||
|
cancelFunction()
|
||
|
cancelFunction = undefined
|
||
|
} else {
|
||
|
wasActivity = false
|
||
|
timeout = setTimeout(function () {
|
||
|
onTimeout()
|
||
|
}, heartbeatTimeout)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
wasActivity = false
|
||
|
timeout = setTimeout(function () {
|
||
|
onTimeout()
|
||
|
}, heartbeatTimeout)
|
||
|
|
||
|
currentState = CONNECTING
|
||
|
dataBuffer = ''
|
||
|
eventTypeBuffer = ''
|
||
|
lastEventIdBuffer = lastEventId
|
||
|
textBuffer = ''
|
||
|
fieldStart = 0
|
||
|
valueStart = 0
|
||
|
state = FIELD_START
|
||
|
|
||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=428916
|
||
|
// Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers.
|
||
|
var requestURL = url
|
||
|
if (url.slice(0, 5) !== 'data:' && url.slice(0, 5) !== 'blob:') {
|
||
|
if (lastEventId !== '') {
|
||
|
requestURL += (url.indexOf('?') === -1 ? '?' : '&') + 'lastEventId=' + encodeURIComponent(lastEventId)
|
||
|
}
|
||
|
}
|
||
|
var requestHeaders = {}
|
||
|
requestHeaders['Accept'] = 'text/event-stream'
|
||
|
if (headers != undefined) {
|
||
|
for (var name in headers) {
|
||
|
if (Object.prototype.hasOwnProperty.call(headers, name)) {
|
||
|
requestHeaders[name] = headers[name]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
try {
|
||
|
transport.open(xhr, onStart, onProgress, onFinish, requestURL, withCredentials, requestHeaders)
|
||
|
} catch (error) {
|
||
|
close()
|
||
|
throw error
|
||
|
}
|
||
|
}
|
||
|
|
||
|
es.url = url
|
||
|
es.readyState = CONNECTING
|
||
|
es.withCredentials = withCredentials
|
||
|
es._close = close
|
||
|
|
||
|
onTimeout()
|
||
|
}
|
||
|
|
||
|
EventSourcePolyfill.prototype = Object.create(EventTarget.prototype)
|
||
|
EventSourcePolyfill.prototype.CONNECTING = CONNECTING
|
||
|
EventSourcePolyfill.prototype.OPEN = OPEN
|
||
|
EventSourcePolyfill.prototype.CLOSED = CLOSED
|
||
|
EventSourcePolyfill.prototype.close = function () {
|
||
|
this._close()
|
||
|
}
|
||
|
|
||
|
EventSourcePolyfill.CONNECTING = CONNECTING
|
||
|
EventSourcePolyfill.OPEN = OPEN
|
||
|
EventSourcePolyfill.CLOSED = CLOSED
|
||
|
EventSourcePolyfill.prototype.withCredentials = undefined
|
||
|
|
||
|
export default EventSourcePolyfill
|