Last active
December 15, 2015 10:29
-
-
Save cincodenada/5246094 to your computer and use it in GitHub Desktop.
The main part of Javascript that drives xkcd's "Time" comic (http://xkcd.com/1190/), deobfuscated and annotated. The bulk of the script seems to be an implementation of EventSource - which, while important, is not terribly interesting for our purposes, so I've omitted it here. After some Googling around, I am in fact fairly certain that the Even…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This event appears to ping xkcd's servers when various things happen | |
//Probably for serverside logging/analytics/debugging/statistics | |
function ping_event(evt_name) { | |
(new Image).src = "http://xkcd.com/events/" + evt_name | |
} | |
//This function ouputs debug info into the javascript console if the URL has a "#verbose" anchor appended | |
function log() { | |
location.hash == "#verbose" && console.log.apply(console, arguments) | |
} | |
//A list of event streams to choose from at random, for load balancing | |
var loadbalance_urls = ["http://c0.xkcd.com", "http://c1.xkcd.com", "http://c2.xkcd.com", "http://c3.xkcd.com", "http://c4.xkcd.com", "http://c5.xkcd.com", "http://c6.xkcd.com", "http://c7.xkcd.com"]; | |
try { | |
//Choose a stream and initialize and EventSource watching it | |
var baseurl = loadbalance_urls[Math.floor(Math.random() * loadbalance_urls.length)], | |
evturl = baseurl + "/stream/comic/time?method=EventSource", | |
s = new EventSource(evturl); | |
log("connecting to event source:", evturl); | |
//Connect functions to listen for certain events, mostly diagnostic | |
//Ping xkcd's server when the stream is opened | |
s.addEventListener("open", function (msg) { ping_event("connect_start") }, false); | |
//Ping xkcd's server and log to the console on an error | |
s.addEventListener("error", function (msg) { log("connection error", msg); ping_event("connect_error") }, false); | |
//Log pings and events from xkcd's server in the console | |
s.addEventListener("comic/time/ping", log, false); | |
s.addEventListener("comic/time", log, false); | |
//This is the meat of the function - responds to "comic/time" events from xckd's event streams | |
var first_event = true; | |
s.addEventListener("comic/time", function (msg) { | |
//Parse the message from the stream | |
var data = JSON.parse(msg.data); | |
//Grab the image so we can change it | |
var img = document.getElementById("comic").getElementsByTagName("img")[0]; | |
//If it's the first ping, display the image immediately | |
//otherwise delay it by a random number of seconds | |
//Between zero and the maximum spread defined in the event | |
//This is presumably another load-balancing mechanism | |
var delay = first_event ? 0 : Math.round(Math.random() * data.spread); | |
//Change the image and log as such | |
log("waiting", delay, "seconds before displaying comic", data.image); | |
setTimeout(function () { img.src = "http://imgs.xkcd.com/comics/time/" + data.image }, delay * 1e3); | |
//It's not the first event any more | |
first_event = false | |
}, false); | |
//Also respond to requests for page reloads | |
//Presumably this is in case Randall needs to update the javascript, which he did several times (this is version 7) | |
s.addEventListener("comic/time/reload", function (msg) { | |
//Also introduce a random delay here, again likely for load-balancing | |
//So not everyone reloads the page at once | |
var delay = Math.round(Math.random() * 55); | |
//Reload the page, whee! | |
log("reloading in", delay + 5, "seconds"); | |
setTimeout(function () { | |
ping_event("reloading"); | |
setTimeout(function () { location.reload() }, 5e3) | |
}, delay * 1e3) | |
}, false) | |
} catch (err) { | |
//If we have javascript errors, send them back to xkcd's servers | |
ping_event("js_error") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//For reference | |
//The entirety of the original source, beautified/initially deobfuscated | |
(function (e) { | |
"use strict"; | |
function t() { | |
this.data = {} | |
} | |
function n() { | |
this.listeners = new t | |
} | |
function r(e) { | |
setTimeout(function () { | |
throw e | |
}, 0) | |
} | |
function i(e) { | |
this.type = e | |
} | |
function s(e, t) { | |
i.call(this, e), this.data = t.data, this.lastEventId = t.lastEventId | |
} | |
function g(e, t) { | |
var n = Number(e); | |
return (n < 1 ? 1 : n > 18e6 ? 18e6 : n) || t | |
} | |
function y(e, t, n) { | |
try { | |
typeof e[t] == "function" && e[t](n) | |
} catch (i) { | |
r(i) | |
} | |
} | |
function b(t, r) { | |
function B() { | |
L = d, N !== null && (N.abort(), N = null), C !== 0 && (clearTimeout(C), C = 0), S.readyState = d | |
} | |
function j(e) { | |
var t = L === p || L === h ? N.responseText || "" : "", | |
n = null; | |
if (L === h) { | |
var r = f ? t !== "" ? N.getResponseHeader("Content-Type") : "" : N.contentType; | |
if (r && v.test(r)) { | |
L = p, T = !0, x = u, S.readyState = p, n = new i("open"), S.dispatchEvent(n), y(S, "onopen", n); | |
if (L === d) return | |
} | |
} | |
if (L === p) { | |
t.length > k && (H = !0, T = !0); | |
var o = 0, | |
a = t.indexOf("\r", k), | |
l = t.indexOf("\n", k); | |
while (a !== -1 || l !== -1) { | |
a === -1 || l !== -1 && l < a ? (o = l, l = t.indexOf("\n", o + 1)) : (o = a, a = t.indexOf("\r", o + 1)); | |
var m = t.slice(k, o), | |
B = D; | |
D = t.slice(o, o + 1) === "\r", k = o + 1; | |
if (!B || m.length !== 0 || D) { | |
_.push(m); | |
var j = _.join(""); | |
_.length = 0; | |
if (j !== "") { | |
var I = "", | |
q = j.indexOf(":", 0); | |
q !== -1 && (I = j.slice(q + (j.slice(q + 1, q + 2) === " " ? 2 : 1)), j = j.slice(0, q)), j === "data" ? A.push(I) : j === "id" ? O = I : j === "event" ? M = I : j === "retry" ? (u = g(I, u), x = u, b < u && (b = u)) : j === "retryLimit" ? b = g(I, b) : j === "heartbeatTimeout" && (w = g(I, w), C !== 0 && (clearTimeout(C), C = setTimeout(R, w))) | |
} else { | |
if (A.length !== 0) { | |
E = O; | |
var U = M || "message"; | |
n = new s(U, { | |
data: A.join("\n"), | |
lastEventId: O | |
}), S.dispatchEvent(n), U === "message" && y(S, "onmessage", n); | |
if (L === d) return | |
} | |
A.length = 0, M = "" | |
} | |
} | |
} | |
k !== t.length && (_.push(t.slice(k)), k = t.length) | |
} | |
H && P === 0 && (H = !1, P = setTimeout(F, 80)), L !== p && L !== h || !(e || k > 1048576 || C === 0 && !T) ? C === 0 && (T = !1, C = setTimeout(R, w)) : (L = c, N.abort(), C !== 0 && (clearTimeout(C), C = 0), x > b && (x = b), C = setTimeout(R, x), x = x * 2 + 1, S.readyState = h, n = new i("error"), S.dispatchEvent(n), y(S, "onerror", n)) | |
} | |
function F() { | |
P = 0, j(!1) | |
} | |
function I() { | |
j(!1) | |
} | |
function q() { | |
j(!0) | |
} | |
function R() { | |
C = 0; | |
if (L !== c) { | |
j(!1); | |
return | |
} | |
if (navigator.onLine === !1) { | |
C = setTimeout(R, 500); | |
return | |
} | |
if (m && e.document && (e.document.readyState === "loading" || e.document.readyState === "interactive")) { | |
C = setTimeout(R, 100); | |
return | |
} | |
N.onload = N.onerror = q, N.mozAnon === undefined ? N.onprogress = I : N.onreadystatechange = I, T = !1, C = setTimeout(R, w), k = 0, L = h, A.length = 0, M = "", O = E, _.length = 0, D = !1, N.open("GET", t + ((t.indexOf("?", 0) === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(E) + "&r=" + String(Math.random() + 1).slice(2)), !0), N.withCredentials = o, N.responseType = "text", f && N.setRequestHeader("Accept", "text/event-stream"), N.send(null) | |
} | |
t = String(t); | |
var o = Boolean(a && r && r.withCredentials), | |
u = g(r ? r.retry : NaN, 1e3), | |
b = g(r ? r.retryLimit : NaN, 3e5), | |
w = g(r ? r.heartbeatTimeout : NaN, 45e3), | |
E = r && r.lastEventId && String(r.lastEventId) || "", | |
S = this, | |
x = u, | |
T = !1, | |
N = new l, | |
C = 0, | |
k = 0, | |
L = c, | |
A = [], | |
O = "", | |
M = "", | |
_ = [], | |
D = !1, | |
P = 0, | |
H = !1; | |
r = null, n.call(this), this.close = B, this.url = t, this.readyState = h, this.withCredentials = o, R() | |
} | |
function w() { | |
this.CONNECTING = h, this.OPEN = p, this.CLOSED = d | |
} | |
t.prototype = { | |
get: function (e) { | |
return this.data[e + "~"] | |
}, | |
set: function (e, t) { | |
this.data[e + "~"] = t | |
}, | |
"delete": function (e) { | |
delete this.data[e + "~"] | |
} | |
}, n.prototype = { | |
dispatchEvent: function (e) { | |
var t = String(e.type), | |
n = this.listeners, | |
i = n.get(t); | |
if (!i) return; | |
var s = i.length, | |
o = -1; | |
while (++o < s) { | |
var u = i[o]; | |
try { | |
u.call(this, e) | |
} catch (a) { | |
r(a) | |
} | |
} | |
}, | |
addEventListener: function (e, t) { | |
e = String(e); | |
var n = this.listeners, | |
r = n.get(e); | |
r || n.set(e, r = []); | |
var i = r.length; | |
while (--i >= 0) if (r[i] === t) return; | |
r.push(t) | |
}, | |
removeEventListener: function (e, t) { | |
e = String(e); | |
var n = this.listeners, | |
r = n.get(e); | |
if (!r) return; | |
var i = r.length, | |
s = [], | |
o = -1; | |
while (++o < i) r[o] !== t && s.push(r[o]); | |
s.length === 0 ? n["delete"](e) : n.set(e, s) | |
} | |
}, s.prototype = i.prototype; | |
var o = e.XMLHttpRequest, | |
u = e.XDomainRequest, | |
a = Boolean(o && (new o).withCredentials !== undefined), | |
f = a, | |
l = a ? o : u, | |
c = -1, | |
h = 0, | |
p = 1, | |
d = 2, | |
v = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i, | |
m = /AppleWebKit\/5([0-2][0-9]|3[0-4])[^\d]/.test(navigator.userAgent); | |
w.prototype = n.prototype, b.prototype = new w, w.call(b), l && (e.EventSource = b) | |
})(this), | |
function () { | |
function t(e) { | |
(new Image).src = "http://xkcd.com/events/" + e | |
} | |
function n() { | |
location.hash == "#verbose" && console.log.apply(console, arguments) | |
} | |
var e = ["http://c0.xkcd.com", "http://c1.xkcd.com", "http://c2.xkcd.com", "http://c3.xkcd.com", "http://c4.xkcd.com", "http://c5.xkcd.com", "http://c6.xkcd.com", "http://c7.xkcd.com"]; | |
try { | |
var r = e[Math.floor(Math.random() * e.length)], | |
i = r + "/stream/comic/time?method=EventSource", | |
s = new EventSource(i); | |
n("connecting to event source:", i), s.addEventListener("open", function (e) { | |
t("connect_start") | |
}, !1), s.addEventListener("error", function (e) { | |
n("connection error", e), t("connect_error") | |
}, !1), s.addEventListener("comic/time/ping", n, !1), s.addEventListener("comic/time", n, !1); | |
var o = !0; | |
s.addEventListener("comic/time", function (e) { | |
var t = JSON.parse(e.data), | |
r = document.getElementById("comic").getElementsByTagName("img")[0], | |
i = o ? 0 : Math.round(Math.random() * t.spread); | |
n("waiting", i, "seconds before displaying comic", t.image), setTimeout(function () { | |
r.src = "http://imgs.xkcd.com/comics/time/" + t.image | |
}, i * 1e3), o = !1 | |
}, !1), s.addEventListener("comic/time/reload", function (e) { | |
var r = Math.round(Math.random() * 55); | |
n("reloading in", r + 5, "seconds"), setTimeout(function () { | |
t("reloading"), setTimeout(function () { | |
location.reload() | |
}, 5e3) | |
}, r * 1e3) | |
}, !1) | |
} catch (u) { | |
t("js_error") | |
} | |
}(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment