Skip to content

Instantly share code, notes, and snippets.

@cincodenada
Last active December 15, 2015 10:29
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
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 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")
}
//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