rsnext/packages/next/client/dev/event-source-polyfill.js
Tim Neutkens 7e7f2c0a6d
Simplify a few parts of the codebase (#7506)
* Move client-side dev JS to dev folder

* Move eventsource polyfill

* Move source-map-support

* Move error boundary

* Deprecate Container in _app

* Make initialRender check better

* Remove unused code

* Only support one subscription as there is only one

* Don’t spread object

* Shorten property name

* Add container in development too

* Simplify query update logic
2019-06-05 20:15:42 +02:00

966 lines
25 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()
})
})
.then(
function(result) {
onFinishCallback()
return result
},
function(error) {
onFinishCallback()
return Promise.reject(error)
}
)
}
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