diff options
Diffstat (limited to 'web/js/nms-data.js')
-rw-r--r-- | web/js/nms-data.js | 429 |
1 files changed, 226 insertions, 203 deletions
diff --git a/web/js/nms-data.js b/web/js/nms-data.js index c923e08..6a00fba 100644 --- a/web/js/nms-data.js +++ b/web/js/nms-data.js @@ -23,78 +23,82 @@ * nmsData.invalidate() - Invalidate browser-cache. */ - var nmsData = nmsData || { - old: {}, // Single copy of previous data. Automatically populated. - stats: { - identicalFetches:0, - outstandingAjaxRequests:0, - ajaxOverflow:0, - pollClearsEmpty:0, - pollClears:0, - pollSets:0, - newSource:0, - oldSource:0 - }, - _pulseBeat: 0, - /* - * The last time stamp of any data received, regardless of source. - * - * Used as a fallback for blank now, but can also be used to check - * "freshness", I suppose. - */ - _last: undefined, - _now: undefined, + old: {}, // Single copy of previous data. Automatically populated. + stats: { + identicalFetches: 0, + outstandingAjaxRequests: 0, + ajaxOverflow: 0, + pollClearsEmpty: 0, + pollClears: 0, + pollSets: 0, + newSource: 0, + oldSource: 0, + allFetches: 0, + allRequests: 0, + failedFetches: 0, + jsonParse: 0, + }, + _pulseBeat: 0, + /* + * The last time stamp of any data received, regardless of source. + * + * Used as a fallback for blank now, but can also be used to check + * "freshness", I suppose. + */ + _last: undefined, + _now: undefined, - /* - * These are provided so we can introduce error checking when we - * have time. - * - * now() represents the data, not the intent. That means that if - * you want to check if we are traveling in time you should not - * check nmsData.now. That will always return a value as long as - * we've had a single piece of data. - */ - get now() { return this._now || this._last; }, - set now(val) { - if (val == undefined || !val) { - nmsData._now = undefined; - } else { - // FIXME: Check if now is valid syntax. - nmsData._now = val; - } - }, - /* - * List of sources, name, handler, etc - */ - _sources: {}, + /* + * These are provided so we can introduce error checking when we + * have time. + * + * now() represents the data, not the intent. That means that if + * you want to check if we are traveling in time you should not + * check nmsData.now. That will always return a value as long as + * we've had a single piece of data. + */ + get now() { + return this._now || this._last; + }, + set now(val) { + if (val == undefined || !val) { + nmsData._now = undefined; + } else { + // FIXME: Check if now is valid syntax. + nmsData._now = val; + } + }, + /* + * List of sources, name, handler, etc + */ + _sources: {}, - /* - * Maximum number of AJAX requests in transit before we start - * skipping updates. - * - * A problem right now is that it will typically always hit the - * same thing since everything starts at the same time... - */ - _ajaxThreshold: 10 + /* + * Maximum number of AJAX requests in transit before we start + * skipping updates. + * + * A problem right now is that it will typically always hit the + * same thing since everything starts at the same time... + */ + _ajaxThreshold: 10, }; - nmsData._dropData = function (name) { - delete this[name]; - delete this.old[name]; + delete this[name]; + delete this.old[name]; }; nmsData.removeSource = function (name) { - if (this._sources[name] == undefined) { - this.stats.pollClearsEmpty++; - return true; - } - if (this._sources[name]['handle']) { - this.stats.pollClears++; - clearInterval(this._sources[name]['handle']); - } - delete this._sources[name]; + if (this._sources[name] == undefined) { + this.stats.pollClearsEmpty++; + return true; + } + if (this._sources[name]["handle"]) { + this.stats.pollClears++; + clearInterval(this._sources[name]["handle"]); + } + delete this._sources[name]; }; /* @@ -112,15 +116,17 @@ nmsData.removeSource = function (name) { * * FIXME: Should be unified with nmsTimers() somehow. */ -nmsData.registerSource = function(name, target) { - if (this._sources[name] == undefined) { - this._sources[name] = { target: target, cbs: {}, fresh: true }; - this._sources[name]['handle'] = setInterval(function(){nmsData.updateSource(name)}, 1000); - this.stats.newSource++; - } else { - this.stats.oldSource++; - } - this.stats.pollSets++; +nmsData.registerSource = function (name, target) { + if (this._sources[name] == undefined) { + this._sources[name] = { target: target, cbs: {}, fresh: true }; + this._sources[name]["handle"] = setInterval(function () { + nmsData.updateSource(name); + }, 1000); + this.stats.newSource++; + } else { + this.stats.oldSource++; + } + this.stats.pollSets++; }; /* @@ -132,45 +138,44 @@ nmsData.registerSource = function(name, target) { * The actual html might not be the best choice, but I think the general * idea of some sort of heartbeat is needed. */ -nmsData._pulse = function() { - if (nmsData._pulseElement == undefined) { - try { - nmsData._pulseElement = document.getElementById("heartbeat"); - } catch(e) { - nmsData._pulseElement = null; - } - } - if (nmsData._pulseElement == null) - return; - if (nmsData._pulseBeat > 20) { - if (nmsData._pulseElement.classList.contains("pulse-on")) { - nmsData._pulseElement.classList.remove("pulse-on"); - } else { - nmsData._pulseElement.classList.add("pulse-on"); - } - nmsData._pulseBeat = 0; - } - nmsData._pulseBeat++; -} +nmsData._pulse = function () { + if (nmsData._pulseElement == undefined) { + try { + nmsData._pulseElement = document.getElementById("heartbeat"); + } catch (e) { + nmsData._pulseElement = null; + } + } + if (nmsData._pulseElement == null) return; + if (nmsData._pulseBeat > 20) { + if (nmsData._pulseElement.classList.contains("pulse-on")) { + nmsData._pulseElement.classList.remove("pulse-on"); + } else { + nmsData._pulseElement.classList.add("pulse-on"); + } + nmsData._pulseBeat = 0; + } + nmsData._pulseBeat++; +}; /* * Add a handler (callback) for a source, using an id. * * This is idempotent: if the id is the same, it will just overwrite the * old id, not add a copy. */ -nmsData.addHandler = function(name, id, cb, cbdata) { - var cbob = { - id: id, - name: name, - cb: cb, - fresh: true, - cbdata: cbdata - }; - if (id == undefined) { - return; - } - this._sources[name].cbs[id] = cbob; - this.updateSource(name); +nmsData.addHandler = function (name, id, cb, cbdata) { + var cbob = { + id: id, + name: name, + cb: cb, + fresh: true, + cbdata: cbdata, + }; + if (id == undefined) { + return; + } + this._sources[name].cbs[id] = cbob; + this.updateSource(name); }; /* @@ -179,14 +184,14 @@ nmsData.addHandler = function(name, id, cb, cbdata) { * Mainly used to avoid fini() functions in the map handlers. E.g.: just * reuse "mapHandler" as id. */ -nmsData.unregisterHandlerWildcard = function(id) { - for (var v in nmsData._sources) { - this.unregisterHandler(v, id); - } +nmsData.unregisterHandlerWildcard = function (id) { + for (var v in nmsData._sources) { + this.unregisterHandler(v, id); + } }; -nmsData.unregisterHandler = function(name, id) { - delete this._sources[name].cbs[id]; +nmsData.unregisterHandler = function (name, id) { + delete this._sources[name].cbs[id]; }; /* @@ -196,34 +201,34 @@ nmsData.unregisterHandler = function(name, id) { * known action that updates the underlying data (e.g: update comments * after a comment is posted). */ -nmsData.updateSource = function(name) { - /* - * See comment in nms.js nmsINIT(); - */ - if (name == "ticker" ) { - for (var i in nmsData._sources[name].cbs) { - var tmp = nmsData._sources[name].cbs[i]; - if (tmp.cb != undefined) { - tmp.cb(tmp.cbdata); - } - } - return; - } - this._genericUpdater(name, true); +nmsData.updateSource = function (name) { + /* + * See comment in nms.js nmsINIT(); + */ + if (name == "ticker") { + for (var i in nmsData._sources[name].cbs) { + var tmp = nmsData._sources[name].cbs[i]; + if (tmp.cb != undefined) { + tmp.cb(tmp.cbdata); + } + } + return; + } + this._genericUpdater(name, true); }; -nmsData.invalidate = function(name) { - this._genericUpdater(name, false); +nmsData.invalidate = function (name) { + this._genericUpdater(name, false); }; /* * Reset a source, deleting all data, including old. * * Useful if traveling in time, for example. */ -nmsData.resetSource = function(name) { - this[name] = {}; - this.old[name] = {}; - this.updateSource(name); +nmsData.resetSource = function (name) { + this[name] = {}; + this.old[name] = {}; + this.updateSource(name); }; /* @@ -233,77 +238,95 @@ nmsData.resetSource = function(name) { * Do not use this directly. Use updateSource(). * */ -nmsData._genericUpdater = function(name, cacheok) { - if (this.stats.outstandingAjaxRequests++ > this._ajaxThreshold) { - this.stats.outstandingAjaxRequests--; - this.stats.ajaxOverflow++; - return; - } - var now = ""; - if (this._now != undefined) - now = "now=" + this._now; - if (now != "") { - if (this._sources[name].target.match("\\?")) - now = "&" + now; - else - now = "?" + now; - } - var heads = {}; - if (cacheok == false) { - heads['Cache-Control'] = "max-age=0, no-cache, stale-while-revalidate=0"; - } +nmsData._genericUpdater = function (name, cacheok) { + if (this.stats.outstandingAjaxRequests++ > this._ajaxThreshold) { + this.stats.outstandingAjaxRequests--; + this.stats.ajaxOverflow++; + return; + } + var now = ""; + if (this._now != undefined) now = "now=" + this._now; + if (now != "") { + if (this._sources[name].target.match("\\?")) now = "&" + now; + else now = "?" + now; + } + + // TODO + var heads = {}; + if (cacheok == false) { + heads["Cache-Control"] = "max-age=0, no-cache, stale-while-revalidate=0"; + } + + /* + * + * We can be smarter than fetch here. We know that the ETag can be + * used to evaluate against our cached copy. If the ETag is a + * match, we never have to do the potentially extensive JSON + * parsing. + * + * Also note that we accept weakened ETags too (ETags with W/ + * prefixed). This is typically if the intermediate cache has + * compressed the content for us, so this is fine. DO WE? + * + * This is particularly important because we poll everything even + * though we _know_ it will hit both browser cache and most likely + * Varnish. JSON.Parse was one of the biggest CPU hogs before this. + */ + + const request = new Request(this._sources[name].target + now, { + method: "GET", + headers: { "Content-Type": "application/json", "If-None-Match": nmsData[name] != undefined ? nmsData[name]["hash"] : null }, + signal: AbortSignal.timeout(2000), // 2s timeout + }); - /* - * Note that we intentionally set dataType: "text" here. - * - * We can be smarter than jQuery here. We know that the ETag can be - * used to evaluate against our cached copy. If the ETag is a - * match, we never have to do the potentially extensive JSON - * parsing. - * - * Also note that we accept weakened ETags too (ETags with W/ - * prefixed). This is typically if the intermediate cache has - * compressed the content for us, so this is fine. - * - * This is particularly important because we poll everything even - * though we _know_ it will hit both browser cache and most likely - * Varnish. JSON.Parse was one of the biggest CPU hogs before this. - */ - $.ajax({ - type: "GET", - headers: heads, - url: this._sources[name].target + now, - dataType: "text", - success: function (indata, textStatus, jqXHR) { - var etag = jqXHR.getResponseHeader("ETag"); - if (nmsData[name] == undefined || (nmsData[name]['hash'] != etag && nmsData[name]['hash'] != etag.slice(2))) { - var data = JSON.parse(indata); - if (name == "ping") { - nmsData._last = data['time']; - nmsMap.drawNow(); - } - nmsData.old[name] = nmsData[name]; - nmsData[name] = data; - for (var i in nmsData._sources[name].cbs) { - var tmp2 = nmsData._sources[name].cbs[i]; - if (tmp2.cb != undefined) { - tmp2.cb(tmp2.cbdata); - } - } - } else { - for (var j in nmsData._sources[name].cbs) { - var tmp = nmsData._sources[name].cbs[j]; - if (tmp.cb != undefined && tmp.fresh) { - nmsData._sources[name].cbs[j].fresh = false; - tmp.cb(tmp.cbdata); - } - } - nmsData.stats.identicalFetches++; - } - nmsData._pulse(); - }, - complete: function(jqXHR, textStatus) { - nmsData.stats.outstandingAjaxRequests--; - } - }); + nmsData.stats.allRequests++; + fetch(request) + .then((r) => { + nmsData.stats.allFetches++; + if (!r.ok && r.status != 304) { + throw new Error("Fetch failed with status: " + r.status); + } + var etag = r.headers.get("etag"); + if ( + r.status != 304 && ( + etag == null || + nmsData[name] == undefined || + (nmsData[name]["hash"] != etag && + nmsData[name]["hash"] != etag.slice(2)) + ) + ) { + r.json().then((data) => { + nmsData.stats.jsonParse++; + if (name == "ping") { + nmsData._last = data["time"]; + nmsMap.drawNow(); + } + nmsData.old[name] = nmsData[name]; + nmsData[name] = data; + for (var i in nmsData._sources[name].cbs) { + var tmp2 = nmsData._sources[name].cbs[i]; + if (tmp2.cb != undefined) { + tmp2.cb(tmp2.cbdata); + } + } + }); + } else { + for (var j in nmsData._sources[name].cbs) { + var tmp = nmsData._sources[name].cbs[j]; + if (tmp.cb != undefined && tmp.fresh) { + nmsData._sources[name].cbs[j].fresh = false; + tmp.cb(tmp.cbdata); + } + } + nmsData.stats.identicalFetches++; + } + }) + .catch((err) => { + nmsData.stats.failedFetches++; + console.log("(" + name + "): " + err); + }) + .finally(() => { + nmsData._pulse(); + nmsData.stats.outstandingAjaxRequests--; + }); }; |