(function (global) { "use strict"; function Map() { this.data = {} } function EventTarget() { this.listeners = new Map } function throwError(e) { setTimeout(function () { throw e }, 0) } function Event(type) { this.type = type } function MessageEvent(type, options) { Event.call(this, type), this.data = options.data, this.lastEventId = options.lastEventId } function getDuration(value, def) { var n = Number(value); return (n < 1 ? 1 : n > 18e6 ? 18e6 : n) || def } function fire(that, property, event) { try { typeof that[property] == "function" && that[property](event) } catch (e) { throwError(e) } } function EventSource(url, options) { function close() { currentState = CLOSED, xhr !== null && (xhr.abort(), xhr = null), timeout !== 0 && (clearTimeout(timeout), timeout = 0), that.readyState = CLOSED } function onProgress(isLoadEnd) { var responseText = currentState === OPEN || currentState === CONNECTING ? xhr.responseText || "" : "", event = null; if (currentState === CONNECTING) { var contentType = isXHR ? responseText !== "" ? xhr.getResponseHeader("Content-Type") : "" : xhr.contentType; if (contentType && contentTypeRegExp.test(contentType)) { currentState = OPEN, wasActivity = !0, retry = initialRetry, that.readyState = OPEN, event = new Event("open"), that.dispatchEvent(event), fire(that, "onopen", event); if (currentState === CLOSED) return } } if (currentState === OPEN) { responseText.length > charOffset && (wasAct = !0, wasActivity = !0); var i = 0, i1 = responseText.indexOf("\r", charOffset), i2 = responseText.indexOf("\n", charOffset); while (i1 !== -1 || i2 !== -1) { i1 === -1 || i2 !== -1 && i2 < i1 ? (i = i2, i2 = responseText.indexOf("\n", i + 1)) : (i = i1, i1 = responseText.indexOf("\r", i + 1)); var line = responseText.slice(charOffset, i), oldWasCR = wasCR; wasCR = responseText.slice(i, i + 1) === "\r", charOffset = i + 1; if (!oldWasCR || line.length !== 0 || wasCR) { responseBuffer.push(line); var field = responseBuffer.join(""); responseBuffer.length = 0; if (field !== "") { var value = "", j = field.indexOf(":", 0); j !== -1 && (value = field.slice(j + (field.slice(j + 1, j + 2) === " " ? 2 : 1)), field = field.slice(0, j)), field === "data" ? dataBuffer.push(value) : field === "id" ? lastEventIdBuffer = value : field === "event" ? eventTypeBuffer = value : field === "retry" ? (initialRetry = getDuration(value, initialRetry), retry = initialRetry, retryLimit < initialRetry && (retryLimit = initialRetry)) : field === "retryLimit" ? retryLimit = getDuration(value, retryLimit) : field === "heartbeatTimeout" && (heartbeatTimeout = getDuration(value, heartbeatTimeout), timeout !== 0 && (clearTimeout(timeout), timeout = setTimeout(onTimeout, heartbeatTimeout))) } else { if (dataBuffer.length !== 0) { lastEventId = lastEventIdBuffer; var type = eventTypeBuffer || "message"; event = new MessageEvent(type, { data: dataBuffer.join("\n"), lastEventId: lastEventIdBuffer }), that.dispatchEvent(event), type === "message" && fire(that, "onmessage", event); if (currentState === CLOSED) return } dataBuffer.length = 0, eventTypeBuffer = "" } } } charOffset !== responseText.length && (responseBuffer.push(responseText.slice(charOffset)), charOffset = responseText.length) } wasAct && progressTimeout === 0 && (wasAct = !1, progressTimeout = setTimeout(p, 80)), currentState !== OPEN && currentState !== CONNECTING || !(isLoadEnd || charOffset > 1048576 || timeout === 0 && !wasActivity) ? timeout === 0 && (wasActivity = !1, timeout = setTimeout(onTimeout, heartbeatTimeout)) : (currentState = WAITING, xhr.abort(), timeout !== 0 && (clearTimeout(timeout), timeout = 0), retry > retryLimit && (retry = retryLimit), timeout = setTimeout(onTimeout, retry), retry = retry * 2 + 1, that.readyState = CONNECTING, event = new Event("error"), that.dispatchEvent(event), fire(that, "onerror", event)) } function p() { progressTimeout = 0, onProgress(!1) } function onProgress2() { onProgress(!1) } function onLoadEnd() { onProgress(!0) } function onTimeout() { timeout = 0; if (currentState !== WAITING) { onProgress(!1); return } if (navigator.onLine === !1) { timeout = setTimeout(onTimeout, 500); return } if (webkitBefore535 && global.document && (global.document.readyState === "loading" || global.document.readyState === "interactive")) { timeout = setTimeout(onTimeout, 100); return } xhr.onload = xhr.onerror = onLoadEnd, xhr.mozAnon === undefined ? xhr.onprogress = onProgress2 : xhr.onreadystatechange = onProgress2, wasActivity = !1, timeout = setTimeout(onTimeout, heartbeatTimeout), charOffset = 0, currentState = CONNECTING, dataBuffer.length = 0, eventTypeBuffer = "", lastEventIdBuffer = lastEventId, responseBuffer.length = 0, wasCR = !1, xhr.open("GET", url + ((url.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId) + "&r=" + String(Math.random() + 1).slice(2)), !0), xhr.withCredentials = withCredentials, xhr.responseType = "text", isXHR && xhr.setRequestHeader("Accept", "text/event-stream"), xhr.send(null) } url = String(url); var withCredentials = Boolean(xhr2 && options && options.withCredentials), initialRetry = getDuration(options ? options.retry : NaN, 1e3), retryLimit = getDuration(options ? options.retryLimit : NaN, 3e5), heartbeatTimeout = getDuration(options ? options.heartbeatTimeout : NaN, 45e3), lastEventId = options && options.lastEventId && String(options.lastEventId) || "", that = this, retry = initialRetry, wasActivity = !1, xhr = new Transport, timeout = 0, charOffset = 0, currentState = WAITING, dataBuffer = [], lastEventIdBuffer = "", eventTypeBuffer = "", responseBuffer = [], wasCR = !1, progressTimeout = 0, wasAct = !1; options = null, EventTarget.call(this), this.close = close, this.url = url, this.readyState = CONNECTING, this.withCredentials = withCredentials, onTimeout() } function F() { this.CONNECTING = CONNECTING, this.OPEN = OPEN, this.CLOSED = CLOSED } Map.prototype = { get: function (key) { return this.data[key + "~"] }, set: function (key, value) { this.data[key + "~"] = value }, "delete": function (key) { delete this.data[key + "~"] } }, EventTarget.prototype = { dispatchEvent: function (event) { var type = String(event.type), listeners = this.listeners, typeListeners = listeners.get(type); if (!typeListeners) return; var length = typeListeners.length, i = -1; while (++i < length) { var listener = typeListeners[i]; try { listener.call(this, event) } catch (e) { throwError(e) } } }, addEventListener: function (type, callback) { type = String(type); var listeners = this.listeners, typeListeners = listeners.get(type); typeListeners || listeners.set(type, typeListeners = []); var i = typeListeners.length; while (--i >= 0) if (typeListeners[i] === callback) return; typeListeners.push(callback) }, removeEventListener: function (type, callback) { type = String(type); var listeners = this.listeners, typeListeners = listeners.get(type); if (!typeListeners) return; var length = typeListeners.length, filtered = [], i = -1; while (++i < length) typeListeners[i] !== callback && filtered.push(typeListeners[i]); filtered.length === 0 ? listeners["delete"](type) : listeners.set(type, filtered) } }, MessageEvent.prototype = Event.prototype; var XHR = global.XMLHttpRequest, XDR = global.XDomainRequest, xhr2 = Boolean(XHR && (new XHR).withCredentials !== undefined), isXHR = xhr2, Transport = xhr2 ? XHR : XDR, WAITING = -1, CONNECTING = 0, OPEN = 1, CLOSED = 2, contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i, webkitBefore535 = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent); F.prototype = EventTarget.prototype, EventSource.prototype = new F, F.call(EventSource), Transport && (global.EventSource = EventSource) })(this), function () { function record(name) { (new Image).src = "http://xkcd.com/events/" + name } function log() { location.hash == "#verbose" && console.log.apply(console, arguments) } try { var esURL = "http://c0.xkcd.com/stream/comic/time?method=EventSource", source = new EventSource(esURL); log("connecting to event source:", esURL), source.addEventListener("open", function (ev) { record("connect_start") }, !1), source.addEventListener("error", function (ev) { record("connect_error") }, !1), source.addEventListener("loadtest", log, !1), source.addEventListener("comic/time", log, !1), source.addEventListener("comic/time", function (ev) { var data = JSON.parse(ev.data), img = document.getElementById("comic").getElementsByTagName("img")[0], delay = Math.round(Math.random() * data.spread); log("waiting", delay, "seconds before displaying comic", data.image), setTimeout(function () { img.src = "http://imgs.xkcd.com/comics/time/" + data.image }, delay * 1e3) }, !1) } catch (e) { record("js_error") } }();