From 5c636246fb1560c82fc48985483c476b59424b3f Mon Sep 17 00:00:00 2001
From: Kevin Froman
Date: Sat, 25 Jan 2020 02:23:18 -0600
Subject: [PATCH] added tor circuit stats to main page
---
src/httpapi/miscclientapi/endpoints.py | 2 +-
src/httpapi/sse/private/__init__.py | 10 +-
src/onionrcommands/daemonlaunch.py | 2 +-
src/{ => statistics}/serializeddata.py | 0
static-data/www/private/index.html | 4 +-
static-data/www/private/main.css | 4 +
static-data/www/shared/eventsource.js | 1028 +++++++++++++++++++++++
static-data/www/shared/main/style.css | 2 +-
static-data/www/shared/main/torstats.js | 23 +
9 files changed, 1066 insertions(+), 9 deletions(-)
rename src/{ => statistics}/serializeddata.py (100%)
create mode 100644 static-data/www/shared/eventsource.js
create mode 100644 static-data/www/shared/main/torstats.js
diff --git a/src/httpapi/miscclientapi/endpoints.py b/src/httpapi/miscclientapi/endpoints.py
index 0c92f11f..576d619f 100644
--- a/src/httpapi/miscclientapi/endpoints.py
+++ b/src/httpapi/miscclientapi/endpoints.py
@@ -26,7 +26,7 @@ import unpaddedbase32
from httpapi import apiutils
import onionrcrypto, config
from netcontroller import NetController
-from serializeddata import SerializedData
+from statistics.serializeddata import SerializedData
from onionrutils import mnemonickeys
from onionrutils import bytesconverter
from etc import onionrvalues
diff --git a/src/httpapi/sse/private/__init__.py b/src/httpapi/sse/private/__init__.py
index 1862a83e..804748f8 100644
--- a/src/httpapi/sse/private/__init__.py
+++ b/src/httpapi/sse/private/__init__.py
@@ -4,6 +4,7 @@ SSE API for node client access
"""
from flask import g, Blueprint
from gevent import sleep
+#from time import sleep
from statistics.transports.tor import TorStats
from .. import wrapper
@@ -38,9 +39,8 @@ def stream_hello():
@private_sse_blueprint.route('/torcircuits')
def stream_tor_circuits():
tor_stats = g.too_many.get(TorStats)
- def stream():
+ def circuit_stat_stream():
while True:
- yield tor_stats.get_json()
-
- sleep(3)
- return SSEWrapper.handle_sse_request(stream)
+ yield "data: " + tor_stats.get_json() + "\n\n"
+ sleep(10)
+ return SSEWrapper.handle_sse_request(circuit_stat_stream)
diff --git a/src/onionrcommands/daemonlaunch.py b/src/onionrcommands/daemonlaunch.py
index 585edff9..679f94be 100755
--- a/src/onionrcommands/daemonlaunch.py
+++ b/src/onionrcommands/daemonlaunch.py
@@ -25,7 +25,7 @@ from etc import onionrvalues, cleanup
from onionrcrypto import getourkeypair
from utils import hastor, logoheader
from . import version
-import serializeddata
+from statistics import serializeddata
import runtests
from httpapi import daemoneventsapi
"""
diff --git a/src/serializeddata.py b/src/statistics/serializeddata.py
similarity index 100%
rename from src/serializeddata.py
rename to src/statistics/serializeddata.py
diff --git a/static-data/www/private/index.html b/static-data/www/private/index.html
index 089b4b0a..cf81bfec 100755
--- a/static-data/www/private/index.html
+++ b/static-data/www/private/index.html
@@ -14,9 +14,11 @@
+
+
@@ -177,7 +179,7 @@
diff --git a/static-data/www/private/main.css b/static-data/www/private/main.css
index 7a1a0d3b..a5eaa464 100755
--- a/static-data/www/private/main.css
+++ b/static-data/www/private/main.css
@@ -28,4 +28,8 @@
.modal img{
border-radius: 5px;
+}
+
+.torStats{
+ color: red;
}
\ No newline at end of file
diff --git a/static-data/www/shared/eventsource.js b/static-data/www/shared/eventsource.js
new file mode 100644
index 00000000..14578835
--- /dev/null
+++ b/static-data/www/shared/eventsource.js
@@ -0,0 +1,1028 @@
+/** @license
+ * eventsource.js
+ * Available under MIT License (MIT)
+ * https://github.com/Yaffle/EventSource/
+ */
+
+/*jslint indent: 2, vars: true, plusplus: true */
+/*global setTimeout, clearTimeout */
+
+(function (global) {
+ "use strict";
+
+ var setTimeout = global.setTimeout;
+ var clearTimeout = global.clearTimeout;
+ var XMLHttpRequest = global.XMLHttpRequest;
+ var XDomainRequest = global.XDomainRequest;
+ var ActiveXObject = global.ActiveXObject;
+ var NativeEventSource = global.EventSource;
+
+ var document = global.document;
+ var Promise = global.Promise;
+ var fetch = global.fetch;
+ var Response = global.Response;
+ var TextDecoder = global.TextDecoder;
+ var TextEncoder = global.TextEncoder;
+ var AbortController = global.AbortController;
+
+ if (typeof window !== "undefined" && !("readyState" in document) && document.body == null) { // Firefox 2
+ document.readyState = "loading";
+ window.addEventListener("load", function (event) {
+ document.readyState = "complete";
+ }, false);
+ }
+
+ if (XMLHttpRequest == null) { // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest_in_IE6
+ XMLHttpRequest = function () {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ };
+ }
+
+ if (Object.create == undefined) {
+ Object.create = function (C) {
+ function F(){}
+ F.prototype = C;
+ return new F();
+ };
+ }
+
+ // see #118 (Promise#finally with polyfilled Promise)
+ // see #123 (data URLs crash Edge)
+ // see #125 (CSP violations)
+ // see pull/#138
+ // => No way to polyfill Promise#finally
+
+ if (AbortController == undefined) {
+ var originalFetch2 = fetch;
+ fetch = function (url, options) {
+ var signal = options.signal;
+ return originalFetch2(url, {headers: options.headers, credentials: options.credentials, cache: options.cache}).then(function (response) {
+ var reader = response.body.getReader();
+ signal._reader = reader;
+ if (signal._aborted) {
+ signal._reader.cancel();
+ }
+ return {
+ status: response.status,
+ statusText: response.statusText,
+ headers: response.headers,
+ body: {
+ getReader: function () {
+ return reader;
+ }
+ }
+ };
+ });
+ };
+ AbortController = function () {
+ this.signal = {
+ _reader: null,
+ _aborted: false
+ };
+ this.abort = function () {
+ if (this.signal._reader != null) {
+ this.signal._reader.cancel();
+ }
+ this.signal._aborted = true;
+ };
+ };
+ }
+
+ function TextDecoderPolyfill() {
+ this.bitsNeeded = 0;
+ this.codePoint = 0;
+ }
+
+ TextDecoderPolyfill.prototype.decode = function (octets) {
+ function valid(codePoint, shift, octetsCount) {
+ if (octetsCount === 1) {
+ return codePoint >= 0x0080 >> shift && codePoint << shift <= 0x07FF;
+ }
+ if (octetsCount === 2) {
+ return codePoint >= 0x0800 >> shift && codePoint << shift <= 0xD7FF || codePoint >= 0xE000 >> shift && codePoint << shift <= 0xFFFF;
+ }
+ if (octetsCount === 3) {
+ return codePoint >= 0x010000 >> shift && codePoint << shift <= 0x10FFFF;
+ }
+ throw new Error();
+ }
+ function octetsCount(bitsNeeded, codePoint) {
+ if (bitsNeeded === 6 * 1) {
+ return codePoint >> 6 > 15 ? 3 : codePoint > 31 ? 2 : 1;
+ }
+ if (bitsNeeded === 6 * 2) {
+ return codePoint > 15 ? 3 : 2;
+ }
+ if (bitsNeeded === 6 * 3) {
+ return 3;
+ }
+ throw new Error();
+ }
+ var REPLACER = 0xFFFD;
+ var string = "";
+ var bitsNeeded = this.bitsNeeded;
+ var codePoint = this.codePoint;
+ for (var i = 0; i < octets.length; i += 1) {
+ var octet = octets[i];
+ if (bitsNeeded !== 0) {
+ if (octet < 128 || octet > 191 || !valid(codePoint << 6 | octet & 63, bitsNeeded - 6, octetsCount(bitsNeeded, codePoint))) {
+ bitsNeeded = 0;
+ codePoint = REPLACER;
+ string += String.fromCharCode(codePoint);
+ }
+ }
+ if (bitsNeeded === 0) {
+ if (octet >= 0 && octet <= 127) {
+ bitsNeeded = 0;
+ codePoint = octet;
+ } else if (octet >= 192 && octet <= 223) {
+ bitsNeeded = 6 * 1;
+ codePoint = octet & 31;
+ } else if (octet >= 224 && octet <= 239) {
+ bitsNeeded = 6 * 2;
+ codePoint = octet & 15;
+ } else if (octet >= 240 && octet <= 247) {
+ bitsNeeded = 6 * 3;
+ codePoint = octet & 7;
+ } else {
+ bitsNeeded = 0;
+ codePoint = REPLACER;
+ }
+ if (bitsNeeded !== 0 && !valid(codePoint, bitsNeeded, octetsCount(bitsNeeded, codePoint))) {
+ bitsNeeded = 0;
+ codePoint = REPLACER;
+ }
+ } else {
+ bitsNeeded -= 6;
+ codePoint = codePoint << 6 | octet & 63;
+ }
+ if (bitsNeeded === 0) {
+ if (codePoint <= 0xFFFF) {
+ string += String.fromCharCode(codePoint);
+ } else {
+ string += String.fromCharCode(0xD800 + (codePoint - 0xFFFF - 1 >> 10));
+ string += String.fromCharCode(0xDC00 + (codePoint - 0xFFFF - 1 & 0x3FF));
+ }
+ }
+ }
+ this.bitsNeeded = bitsNeeded;
+ this.codePoint = codePoint;
+ return string;
+ };
+
+ // Firefox < 38 throws an error with stream option
+ var supportsStreamOption = function () {
+ try {
+ return new TextDecoder().decode(new TextEncoder().encode("test"), {stream: true}) === "test";
+ } catch (error) {
+ console.log(error);
+ }
+ return false;
+ };
+
+ // IE, Edge
+ if (TextDecoder == undefined || TextEncoder == undefined || !supportsStreamOption()) {
+ TextDecoder = TextDecoderPolyfill;
+ }
+
+ var k = function () {
+ };
+
+ function XHRWrapper(xhr) {
+ this.withCredentials = false;
+ this.readyState = 0;
+ this.status = 0;
+ this.statusText = "";
+ this.responseText = "";
+ this.onprogress = k;
+ this.onload = k;
+ this.onerror = k;
+ this.onreadystatechange = k;
+ this._contentType = "";
+ this._xhr = xhr;
+ this._sendTimeout = 0;
+ this._abort = k;
+ }
+
+ XHRWrapper.prototype.open = function (method, url) {
+ this._abort(true);
+
+ var that = this;
+ var xhr = this._xhr;
+ var state = 1;
+ var timeout = 0;
+
+ this._abort = function (silent) {
+ if (that._sendTimeout !== 0) {
+ clearTimeout(that._sendTimeout);
+ that._sendTimeout = 0;
+ }
+ if (state === 1 || state === 2 || state === 3) {
+ state = 4;
+ xhr.onload = k;
+ xhr.onerror = k;
+ xhr.onabort = k;
+ xhr.onprogress = k;
+ xhr.onreadystatechange = k;
+ // IE 8 - 9: XDomainRequest#abort() does not fire any event
+ // Opera < 10: XMLHttpRequest#abort() does not fire any event
+ xhr.abort();
+ if (timeout !== 0) {
+ clearTimeout(timeout);
+ timeout = 0;
+ }
+ if (!silent) {
+ that.readyState = 4;
+ that.onabort(null);
+ that.onreadystatechange();
+ }
+ }
+ state = 0;
+ };
+
+ var onStart = function () {
+ if (state === 1) {
+ //state = 2;
+ var status = 0;
+ var statusText = "";
+ var contentType = undefined;
+ if (!("contentType" in xhr)) {
+ try {
+ status = xhr.status;
+ statusText = xhr.statusText;
+ contentType = xhr.getResponseHeader("Content-Type");
+ } catch (error) {
+ // IE < 10 throws exception for `xhr.status` when xhr.readyState === 2 || xhr.readyState === 3
+ // Opera < 11 throws exception for `xhr.status` when xhr.readyState === 2
+ // https://bugs.webkit.org/show_bug.cgi?id=29121
+ status = 0;
+ statusText = "";
+ contentType = undefined;
+ // Firefox < 14, Chrome ?, Safari ?
+ // https://bugs.webkit.org/show_bug.cgi?id=29658
+ // https://bugs.webkit.org/show_bug.cgi?id=77854
+ }
+ } else {
+ status = 200;
+ statusText = "OK";
+ contentType = xhr.contentType;
+ }
+ if (status !== 0) {
+ state = 2;
+ that.readyState = 2;
+ that.status = status;
+ that.statusText = statusText;
+ that._contentType = contentType;
+ that.onreadystatechange();
+ }
+ }
+ };
+ var onProgress = function () {
+ onStart();
+ if (state === 2 || state === 3) {
+ state = 3;
+ var responseText = "";
+ try {
+ responseText = xhr.responseText;
+ } catch (error) {
+ // IE 8 - 9 with XMLHttpRequest
+ }
+ that.readyState = 3;
+ that.responseText = responseText;
+ that.onprogress();
+ }
+ };
+ var onFinish = function (type, event) {
+ if (event == null || event.preventDefault == null) {
+ event = {
+ preventDefault: k
+ };
+ }
+ // Firefox 52 fires "readystatechange" (xhr.readyState === 4) without final "readystatechange" (xhr.readyState === 3)
+ // IE 8 fires "onload" without "onprogress"
+ onProgress();
+ if (state === 1 || state === 2 || state === 3) {
+ state = 4;
+ if (timeout !== 0) {
+ clearTimeout(timeout);
+ timeout = 0;
+ }
+ that.readyState = 4;
+ if (type === "load") {
+ that.onload(event);
+ } else if (type === "error") {
+ that.onerror(event);
+ } else if (type === "abort") {
+ that.onabort(event);
+ } else {
+ throw new TypeError();
+ }
+ that.onreadystatechange();
+ }
+ };
+ var onReadyStateChange = function (event) {
+ if (xhr != undefined) { // Opera 12
+ if (xhr.readyState === 4) {
+ if (!("onload" in xhr) || !("onerror" in xhr) || !("onabort" in xhr)) {
+ onFinish(xhr.responseText === "" ? "error" : "load", event);
+ }
+ } else if (xhr.readyState === 3) {
+ onProgress();
+ } else if (xhr.readyState === 2) {
+ onStart();
+ }
+ }
+ };
+ var onTimeout = function () {
+ timeout = setTimeout(function () {
+ onTimeout();
+ }, 500);
+ if (xhr.readyState === 3) {
+ onProgress();
+ }
+ };
+
+ // XDomainRequest#abort removes onprogress, onerror, onload
+ if ("onload" in xhr) {
+ xhr.onload = function (event) {
+ onFinish("load", event);
+ };
+ }
+ if ("onerror" in xhr) {
+ xhr.onerror = function (event) {
+ onFinish("error", event);
+ };
+ }
+ // improper fix to match Firefox behaviour, but it is better than just ignore abort
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=768596
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=880200
+ // https://code.google.com/p/chromium/issues/detail?id=153570
+ // IE 8 fires "onload" without "onprogress
+ if ("onabort" in xhr) {
+ xhr.onabort = function (event) {
+ onFinish("abort", event);
+ };
+ }
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=736723
+ if (!("sendAsBinary" in XMLHttpRequest.prototype) && !("mozAnon" in XMLHttpRequest.prototype)) {
+ if ("onprogress" in xhr) {
+ xhr.onprogress = onProgress;
+ }
+ }
+
+ // IE 8 - 9 (XMLHTTPRequest)
+ // Opera < 12
+ // Firefox < 3.5
+ // Firefox 3.5 - 3.6 - ? < 9.0
+ // onprogress is not fired sometimes or delayed
+ // see also #64 (significant lag in IE 11)
+ xhr.onreadystatechange = function (event) {
+ onReadyStateChange(event);
+ };
+
+ if ("contentType" in xhr || !("ontimeout" in XMLHttpRequest.prototype)) {
+ url += (url.indexOf("?") === -1 ? "?" : "&") + "padding=true";
+ }
+ xhr.open(method, url, true);
+
+ if ("readyState" in xhr) {
+ // workaround for Opera 12 issue with "progress" events
+ // #91 (XMLHttpRequest onprogress not fired for streaming response in Edge 14-15-?)
+ timeout = setTimeout(function () {
+ onTimeout();
+ }, 0);
+ }
+ };
+ XHRWrapper.prototype.abort = function () {
+ this._abort(false);
+ };
+ XHRWrapper.prototype.getResponseHeader = function (name) {
+ return this._contentType;
+ };
+ XHRWrapper.prototype.setRequestHeader = function (name, value) {
+ var xhr = this._xhr;
+ if ("setRequestHeader" in xhr) {
+ xhr.setRequestHeader(name, value);
+ }
+ };
+ XHRWrapper.prototype.getAllResponseHeaders = function () {
+ // XMLHttpRequest#getAllResponseHeaders returns null for CORS requests in Firefox 3.6.28
+ return this._xhr.getAllResponseHeaders != undefined ? this._xhr.getAllResponseHeaders() || "" : "";
+ };
+ XHRWrapper.prototype.send = function () {
+ // loading indicator in Safari < ? (6), Chrome < 14, Firefox
+ if (!("ontimeout" in XMLHttpRequest.prototype) &&
+ document != undefined &&
+ document.readyState != undefined &&
+ document.readyState !== "complete") {
+ var that = this;
+ that._sendTimeout = setTimeout(function () {
+ that._sendTimeout = 0;
+ that.send();
+ }, 4);
+ return;
+ }
+
+ var xhr = this._xhr;
+ // withCredentials should be set after "open" for Safari and Chrome (< 19 ?)
+ if ("withCredentials" in xhr) {
+ xhr.withCredentials = this.withCredentials;
+ }
+ try {
+ // xhr.send(); throws "Not enough arguments" in Firefox 3.0
+ xhr.send(undefined);
+ } catch (error1) {
+ // Safari 5.1.7, Opera 12
+ throw error1;
+ }
+ };
+
+ function toLowerCase(name) {
+ return name.replace(/[A-Z]/g, function (c) {
+ return String.fromCharCode(c.charCodeAt(0) + 0x20);
+ });
+ }
+
+ function HeadersPolyfill(all) {
+ // Get headers: implemented according to mozilla's example code: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/getAllResponseHeaders#Example
+ var map = Object.create(null);
+ var array = all.split("\r\n");
+ for (var i = 0; i < array.length; i += 1) {
+ var line = array[i];
+ var parts = line.split(": ");
+ var name = parts.shift();
+ var value = parts.join(": ");
+ map[toLowerCase(name)] = value;
+ }
+ this._map = map;
+ }
+ HeadersPolyfill.prototype.get = function (name) {
+ return this._map[toLowerCase(name)];
+ };
+
+ if (XMLHttpRequest != null && XMLHttpRequest.HEADERS_RECEIVED == null) { // IE < 9, Firefox 3.6
+ XMLHttpRequest.HEADERS_RECEIVED = 2;
+ }
+
+ function XHRTransport() {
+ }
+
+ XHRTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) {
+ xhr.open("GET", url);
+ var offset = 0;
+ xhr.onprogress = function () {
+ var responseText = xhr.responseText;
+ var chunk = responseText.slice(offset);
+ offset += chunk.length;
+ onProgressCallback(chunk);
+ };
+ xhr.onerror = function (event) {
+ event.preventDefault();
+ onFinishCallback(new Error("NetworkError"));
+ };
+ xhr.onload = function () {
+ onFinishCallback(null);
+ };
+ xhr.onabort = function () {
+ onFinishCallback(null);
+ };
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
+ var status = xhr.status;
+ var statusText = xhr.statusText;
+ var contentType = xhr.getResponseHeader("Content-Type");
+ var headers = xhr.getAllResponseHeaders();
+ onStartCallback(status, statusText, contentType, new HeadersPolyfill(headers));
+ }
+ };
+ xhr.withCredentials = withCredentials;
+ for (var name in headers) {
+ if (Object.prototype.hasOwnProperty.call(headers, name)) {
+ xhr.setRequestHeader(name, headers[name]);
+ }
+ }
+ xhr.send();
+ return xhr;
+ };
+
+ function HeadersWrapper(headers) {
+ this._headers = headers;
+ }
+ HeadersWrapper.prototype.get = function (name) {
+ return this._headers.get(name);
+ };
+
+ function FetchTransport() {
+ }
+
+ FetchTransport.prototype.open = function (xhr, onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) {
+ var reader = null;
+ var controller = new AbortController();
+ var signal = controller.signal;
+ var textDecoder = new TextDecoder();
+ fetch(url, {
+ headers: headers,
+ credentials: withCredentials ? "include" : "same-origin",
+ signal: signal,
+ cache: "no-store"
+ }).then(function (response) {
+ reader = response.body.getReader();
+ onStartCallback(response.status, response.statusText, response.headers.get("Content-Type"), new HeadersWrapper(response.headers));
+ // see https://github.com/promises-aplus/promises-spec/issues/179
+ return new Promise(function (resolve, reject) {
+ var readNextChunk = function () {
+ reader.read().then(function (result) {
+ if (result.done) {
+ //Note: bytes in textDecoder are ignored
+ resolve(undefined);
+ } else {
+ var chunk = textDecoder.decode(result.value, {stream: true});
+ onProgressCallback(chunk);
+ readNextChunk();
+ }
+ })["catch"](function (error) {
+ reject(error);
+ });
+ };
+ readNextChunk();
+ });
+ })["catch"](function (error) {
+ if (error.name === "AbortError") {
+ return undefined;
+ } else {
+ return error;
+ }
+ }).then(function (error) {
+ onFinishCallback(error);
+ });
+ return {
+ abort: function () {
+ if (reader != null) {
+ reader.cancel(); // https://bugzilla.mozilla.org/show_bug.cgi?id=1583815
+ }
+ controller.abort();
+ }
+ };
+ };
+
+ function EventTarget() {
+ this._listeners = Object.create(null);
+ }
+
+ function throwError(e) {
+ setTimeout(function () {
+ throw e;
+ }, 0);
+ }
+
+ EventTarget.prototype.dispatchEvent = function (event) {
+ event.target = this;
+ var typeListeners = this._listeners[event.type];
+ if (typeListeners != undefined) {
+ var length = typeListeners.length;
+ for (var i = 0; i < length; i += 1) {
+ var listener = typeListeners[i];
+ try {
+ if (typeof listener.handleEvent === "function") {
+ listener.handleEvent(event);
+ } else {
+ listener.call(this, event);
+ }
+ } catch (e) {
+ throwError(e);
+ }
+ }
+ }
+ };
+ EventTarget.prototype.addEventListener = function (type, listener) {
+ type = String(type);
+ var listeners = this._listeners;
+ var typeListeners = listeners[type];
+ if (typeListeners == undefined) {
+ typeListeners = [];
+ listeners[type] = typeListeners;
+ }
+ var found = false;
+ for (var i = 0; i < typeListeners.length; i += 1) {
+ if (typeListeners[i] === listener) {
+ found = true;
+ }
+ }
+ if (!found) {
+ typeListeners.push(listener);
+ }
+ };
+ EventTarget.prototype.removeEventListener = function (type, listener) {
+ type = String(type);
+ var listeners = this._listeners;
+ var typeListeners = listeners[type];
+ if (typeListeners != undefined) {
+ var filtered = [];
+ for (var i = 0; i < typeListeners.length; i += 1) {
+ if (typeListeners[i] !== listener) {
+ filtered.push(typeListeners[i]);
+ }
+ }
+ if (filtered.length === 0) {
+ delete listeners[type];
+ } else {
+ listeners[type] = filtered;
+ }
+ }
+ };
+
+ function Event(type) {
+ this.type = type;
+ this.target = undefined;
+ this.defaultPrevented = false;
+ }
+
+ Event.prototype.preventDefault = function () {
+ this.defaultPrevented = true;
+ };
+
+ function MessageEvent(type, options) {
+ Event.call(this, type);
+ this.data = options.data;
+ this.lastEventId = options.lastEventId;
+ }
+
+ MessageEvent.prototype = Object.create(Event.prototype);
+
+ function ConnectionEvent(type, options) {
+ Event.call(this, type);
+ this.status = options.status;
+ this.statusText = options.statusText;
+ this.headers = options.headers;
+ }
+
+ ConnectionEvent.prototype = Object.create(Event.prototype);
+
+ var WAITING = -1;
+ var CONNECTING = 0;
+ var OPEN = 1;
+ var CLOSED = 2;
+
+ var AFTER_CR = -1;
+ var FIELD_START = 0;
+ var FIELD = 1;
+ var VALUE_START = 2;
+ var VALUE = 3;
+
+ var contentTypeRegExp = /^text\/event\-stream;?(\s*charset\=utf\-8)?$/i;
+
+ var MINIMUM_DURATION = 1000;
+ var MAXIMUM_DURATION = 18000000;
+
+ var parseDuration = function (value, def) {
+ var n = value == null ? def : parseInt(value, 10);
+ if (n !== n) {
+ n = def;
+ }
+ return clampDuration(n);
+ };
+ var clampDuration = function (n) {
+ return Math.min(Math.max(n, MINIMUM_DURATION), MAXIMUM_DURATION);
+ };
+
+ var fire = function (that, f, event) {
+ try {
+ if (typeof f === "function") {
+ f.call(that, event);
+ }
+ } catch (e) {
+ throwError(e);
+ }
+ };
+
+ function EventSourcePolyfill(url, options) {
+ EventTarget.call(this);
+ options = options || {};
+
+ this.onopen = undefined;
+ this.onmessage = undefined;
+ this.onerror = undefined;
+
+ this.url = undefined;
+ this.readyState = undefined;
+ this.withCredentials = undefined;
+ this.headers = undefined;
+
+ this._close = undefined;
+
+ start(this, url, options);
+ }
+
+ function getBestXHRTransport() {
+ return (XMLHttpRequest != undefined && ("withCredentials" in XMLHttpRequest.prototype)) || XDomainRequest == undefined
+ ? new XMLHttpRequest()
+ : new XDomainRequest();
+ }
+
+ var isFetchSupported = fetch != undefined && Response != undefined && "body" in Response.prototype;
+
+ function start(es, url, options) {
+ url = String(url);
+ var withCredentials = Boolean(options.withCredentials);
+
+ var initialRetry = clampDuration(1000);
+ var heartbeatTimeout = parseDuration(options.heartbeatTimeout, 45000);
+
+ var lastEventId = "";
+ var retry = initialRetry;
+ var wasActivity = false;
+ var headers = options.headers || {};
+ var TransportOption = options.Transport;
+ var xhr = isFetchSupported && TransportOption == undefined ? undefined : new XHRWrapper(TransportOption != undefined ? new TransportOption() : getBestXHRTransport());
+ var transport = TransportOption != null && typeof TransportOption !== "string" ? new TransportOption() : (xhr == undefined ? new FetchTransport() : new XHRTransport());
+ var abortController = undefined;
+ var timeout = 0;
+ var currentState = WAITING;
+ var dataBuffer = "";
+ var lastEventIdBuffer = "";
+ var eventTypeBuffer = "";
+
+ var textBuffer = "";
+ var state = FIELD_START;
+ var fieldStart = 0;
+ var valueStart = 0;
+
+ var onStart = function (status, statusText, contentType, headers) {
+ if (currentState === CONNECTING) {
+ if (status === 200 && contentType != undefined && contentTypeRegExp.test(contentType)) {
+ currentState = OPEN;
+ wasActivity = true;
+ retry = initialRetry;
+ es.readyState = OPEN;
+ var event = new ConnectionEvent("open", {
+ status: status,
+ statusText: statusText,
+ headers: headers
+ });
+ es.dispatchEvent(event);
+ fire(es, es.onopen, event);
+ } else {
+ var message = "";
+ if (status !== 200) {
+ if (statusText) {
+ statusText = statusText.replace(/\s+/g, " ");
+ }
+ message = "EventSource's response has a status " + status + " " + statusText + " that is not 200. Aborting the connection.";
+ } else {
+ message = "EventSource's response has a Content-Type specifying an unsupported type: " + (contentType == undefined ? "-" : contentType.replace(/\s+/g, " ")) + ". Aborting the connection.";
+ }
+ close();
+ var event = new ConnectionEvent("error", {
+ status: status,
+ statusText: statusText,
+ headers: headers
+ });
+ es.dispatchEvent(event);
+ fire(es, es.onerror, event);
+ if (!event.defaultPrevented) {
+ throwError(new Error(message));
+ }
+ }
+ }
+ };
+
+ var onProgress = function (textChunk) {
+ if (currentState === OPEN) {
+ var n = -1;
+ for (var i = 0; i < textChunk.length; i += 1) {
+ var c = textChunk.charCodeAt(i);
+ if (c === "\n".charCodeAt(0) || c === "\r".charCodeAt(0)) {
+ n = i;
+ }
+ }
+ var chunk = (n !== -1 ? textBuffer : "") + textChunk.slice(0, n + 1);
+ textBuffer = (n === -1 ? textBuffer : "") + textChunk.slice(n + 1);
+ if (chunk !== "") {
+ wasActivity = true;
+ }
+ for (var position = 0; position < chunk.length; position += 1) {
+ var c = chunk.charCodeAt(position);
+ if (state === AFTER_CR && c === "\n".charCodeAt(0)) {
+ state = FIELD_START;
+ } else {
+ if (state === AFTER_CR) {
+ state = FIELD_START;
+ }
+ if (c === "\r".charCodeAt(0) || c === "\n".charCodeAt(0)) {
+ if (state !== FIELD_START) {
+ if (state === FIELD) {
+ valueStart = position + 1;
+ }
+ var field = chunk.slice(fieldStart, valueStart - 1);
+ var value = chunk.slice(valueStart + (valueStart < position && chunk.charCodeAt(valueStart) === " ".charCodeAt(0) ? 1 : 0), position);
+ if (field === "data") {
+ dataBuffer += "\n";
+ dataBuffer += value;
+ } else if (field === "id") {
+ lastEventIdBuffer = value;
+ } else if (field === "event") {
+ eventTypeBuffer = value;
+ } else if (field === "retry") {
+ initialRetry = parseDuration(value, initialRetry);
+ retry = initialRetry;
+ } else if (field === "heartbeatTimeout") {
+ heartbeatTimeout = parseDuration(value, heartbeatTimeout);
+ if (timeout !== 0) {
+ clearTimeout(timeout);
+ timeout = setTimeout(function () {
+ onTimeout();
+ }, heartbeatTimeout);
+ }
+ }
+ }
+ if (state === FIELD_START) {
+ if (dataBuffer !== "") {
+ lastEventId = lastEventIdBuffer;
+ if (eventTypeBuffer === "") {
+ eventTypeBuffer = "message";
+ }
+ var event = new MessageEvent(eventTypeBuffer, {
+ data: dataBuffer.slice(1),
+ lastEventId: lastEventIdBuffer
+ });
+ es.dispatchEvent(event);
+ if (eventTypeBuffer === "open") {
+ fire(es, es.onopen, event);
+ } else if (eventTypeBuffer === "message") {
+ fire(es, es.onmessage, event);
+ } else if (eventTypeBuffer === "error") {
+ fire(es, es.onerror, event);
+ }
+ if (currentState === CLOSED) {
+ return;
+ }
+ }
+ dataBuffer = "";
+ eventTypeBuffer = "";
+ }
+ state = c === "\r".charCodeAt(0) ? AFTER_CR : FIELD_START;
+ } else {
+ if (state === FIELD_START) {
+ fieldStart = position;
+ state = FIELD;
+ }
+ if (state === FIELD) {
+ if (c === ":".charCodeAt(0)) {
+ valueStart = position + 1;
+ state = VALUE_START;
+ }
+ } else if (state === VALUE_START) {
+ state = VALUE;
+ }
+ }
+ }
+ }
+ }
+ };
+
+ var onFinish = function (error) {
+ if (currentState === OPEN || currentState === CONNECTING) {
+ currentState = WAITING;
+ if (timeout !== 0) {
+ clearTimeout(timeout);
+ timeout = 0;
+ }
+ timeout = setTimeout(function () {
+ onTimeout();
+ }, retry);
+ retry = clampDuration(Math.min(initialRetry * 16, retry * 2));
+
+ es.readyState = CONNECTING;
+ var event = new Event("error");
+ es.dispatchEvent(event);
+ fire(es, es.onerror, event);
+ if (error != null) {
+ if (!event.defaultPrevented) {
+ throwError(error);
+ }
+ }
+ }
+ };
+
+ var close = function () {
+ currentState = CLOSED;
+ if (abortController != undefined) {
+ abortController.abort();
+ abortController = undefined;
+ }
+ if (timeout !== 0) {
+ clearTimeout(timeout);
+ timeout = 0;
+ }
+ es.readyState = CLOSED;
+ };
+
+ var onTimeout = function () {
+ timeout = 0;
+
+ if (currentState !== WAITING) {
+ if (!wasActivity && abortController != undefined) {
+ onFinish(new Error("No activity within " + heartbeatTimeout + " milliseconds. Reconnecting."));
+ abortController.abort();
+ abortController = undefined;
+ } else {
+ wasActivity = false;
+ timeout = setTimeout(function () {
+ onTimeout();
+ }, heartbeatTimeout);
+ }
+ return;
+ }
+
+ wasActivity = false;
+ timeout = setTimeout(function () {
+ onTimeout();
+ }, heartbeatTimeout);
+
+ currentState = CONNECTING;
+ dataBuffer = "";
+ eventTypeBuffer = "";
+ lastEventIdBuffer = lastEventId;
+ textBuffer = "";
+ fieldStart = 0;
+ valueStart = 0;
+ state = FIELD_START;
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=428916
+ // Request header field Last-Event-ID is not allowed by Access-Control-Allow-Headers.
+ var requestURL = url;
+ if (url.slice(0, 5) !== "data:" && url.slice(0, 5) !== "blob:") {
+ if (lastEventId !== "") {
+ requestURL += (url.indexOf("?") === -1 ? "?" : "&") + "lastEventId=" + encodeURIComponent(lastEventId);
+ }
+ }
+ var withCredentials = es.withCredentials;
+ var requestHeaders = {};
+ requestHeaders["Accept"] = "text/event-stream";
+ var headers = es.headers;
+ if (headers != undefined) {
+ for (var name in headers) {
+ if (Object.prototype.hasOwnProperty.call(headers, name)) {
+ requestHeaders[name] = headers[name];
+ }
+ }
+ }
+ try {
+ abortController = transport.open(xhr, onStart, onProgress, onFinish, requestURL, withCredentials, requestHeaders);
+ } catch (error) {
+ close();
+ throw error;
+ }
+ };
+
+ es.url = url;
+ es.readyState = CONNECTING;
+ es.withCredentials = withCredentials;
+ es.headers = headers;
+ es._close = close;
+
+ onTimeout();
+ }
+
+ EventSourcePolyfill.prototype = Object.create(EventTarget.prototype);
+ EventSourcePolyfill.prototype.CONNECTING = CONNECTING;
+ EventSourcePolyfill.prototype.OPEN = OPEN;
+ EventSourcePolyfill.prototype.CLOSED = CLOSED;
+ EventSourcePolyfill.prototype.close = function () {
+ this._close();
+ };
+
+ EventSourcePolyfill.CONNECTING = CONNECTING;
+ EventSourcePolyfill.OPEN = OPEN;
+ EventSourcePolyfill.CLOSED = CLOSED;
+ EventSourcePolyfill.prototype.withCredentials = undefined;
+
+ var R = NativeEventSource
+ if (XMLHttpRequest != undefined && (NativeEventSource == undefined || !("withCredentials" in NativeEventSource.prototype))) {
+ // Why replace a native EventSource ?
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=444328
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=831392
+ // https://code.google.com/p/chromium/issues/detail?id=260144
+ // https://code.google.com/p/chromium/issues/detail?id=225654
+ // ...
+ R = EventSourcePolyfill;
+ }
+
+ (function (factory) {
+ if (typeof module === "object" && typeof module.exports === "object") {
+ var v = factory(exports);
+ if (v !== undefined) module.exports = v;
+ }
+ else if (typeof define === "function" && define.amd) {
+ define(["exports"], factory);
+ }
+ else {
+ factory(global);
+ }
+ })(function (exports) {
+ exports.EventSourcePolyfill = EventSourcePolyfill;
+ exports.NativeEventSource = NativeEventSource;
+ exports.EventSource = R;
+ });
+}(typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : this));
diff --git a/static-data/www/shared/main/style.css b/static-data/www/shared/main/style.css
index 8181df8d..0661055b 100755
--- a/static-data/www/shared/main/style.css
+++ b/static-data/www/shared/main/style.css
@@ -175,7 +175,7 @@ body{
}
.primaryBtn{
- background-color:#396BAC;
+ background-color:#396BAC;
}
.btn:hover{
diff --git a/static-data/www/shared/main/torstats.js b/static-data/www/shared/main/torstats.js
new file mode 100644
index 00000000..91f00162
--- /dev/null
+++ b/static-data/www/shared/main/torstats.js
@@ -0,0 +1,23 @@
+var torSource = new EventSourcePolyfill("/torcircuits")
+var displays = document.getElementsByClassName('torInfo')
+
+for (x = 0; x < displays.length; x++){
+ displays[x].style.whiteSpace = 'pre'
+}
+
+torSource.onmessage = function(e){
+ let data = JSON.parse(e.data)
+ let i = 0
+ let displaying = true
+ for (x = 0; x < displays.length; x++){
+ let circuitCount = Object.keys(data).length
+ let node = Object.keys(data)[0]
+ if (circuitCount > 0){
+ displays[x].innerText = "Using " + circuitCount + " Tor circuits with " + data[node]['nodes'][0]['finger'] + " as guard.\nGuard nick: " + data[node]['nodes'][0]['nick']
+ }
+ else{
+ displays[x].innerText = "Using 0 Tor circuits."
+ }
+
+ }
+}