aboutsummaryrefslogtreecommitdiffstats
path: root/web/js/nms-data.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/js/nms-data.js')
-rw-r--r--web/js/nms-data.js429
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--;
+ });
};