Reorganized repo using yarn and workspaces

This commit is contained in:
maciejhirsz
2018-06-27 14:04:34 +02:00
parent 8c3b1ee749
commit 0580e25380
84 changed files with 8434 additions and 701 deletions
+59
View File
@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const EventEmitter = require("events");
const feed_1 = require("./feed");
const common_1 = require("@dotstats/common");
class Aggregator extends EventEmitter {
constructor() {
super();
this.nodes = new common_1.IdSet();
this.feeds = new common_1.IdSet();
this.height = 0;
setInterval(() => this.timeoutCheck(), 10000);
}
addNode(node) {
this.nodes.add(node);
this.broadcast(feed_1.default.addedNode(node));
node.once('disconnect', () => {
node.removeAllListeners('block');
this.nodes.remove(node);
this.broadcast(feed_1.default.removedNode(node));
});
node.on('block', () => this.updateBlock(node));
}
addFeed(feed) {
this.feeds.add(feed);
feed.send(feed_1.default.bestBlock(this.height));
for (const node of this.nodes.entries) {
feed.send(feed_1.default.addedNode(node));
}
feed.once('disconnect', () => {
this.feeds.remove(feed);
});
}
nodeList() {
return this.nodes.entries;
}
broadcast(data) {
for (const feed of this.feeds.entries) {
feed.send(data);
}
}
timeoutCheck() {
const now = Date.now();
for (const node of this.nodes.entries) {
node.timeoutCheck(now);
}
}
updateBlock(node) {
if (node.height > this.height) {
this.height = node.height;
this.broadcast(feed_1.default.bestBlock(this.height));
console.log(`New block ${this.height}`);
}
this.broadcast(feed_1.default.imported(node));
console.log(`${node.name} imported ${node.height}, block time: ${node.blockTime / 1000}s, average: ${node.average / 1000}s | latency ${node.latency}`);
}
}
exports.default = Aggregator;
//# sourceMappingURL=aggregator.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"aggregator.js","sourceRoot":"","sources":["../src/aggregator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const EventEmitter = require("events");
const feed_1 = require("./feed");
const shared_1 = require("@dotstats/shared");
class Aggregator extends EventEmitter {
constructor() {
super();
this.nodes = new shared_1.IdSet();
this.feeds = new shared_1.IdSet();
this.height = 0;
setInterval(() => this.timeoutCheck(), 10000);
}
addNode(node) {
this.nodes.add(node);
this.broadcast(feed_1.default.addedNode(node));
node.once('disconnect', () => {
node.removeAllListeners('block');
this.nodes.remove(node);
this.broadcast(feed_1.default.removedNode(node));
});
node.on('block', () => this.updateBlock(node));
}
addFeed(feed) {
this.feeds.add(feed);
feed.send(feed_1.default.bestBlock(this.height));
for (const node of this.nodes.entries) {
feed.send(feed_1.default.addedNode(node));
}
feed.once('disconnect', () => {
this.feeds.remove(feed);
});
}
nodeList() {
return this.nodes.entries;
}
broadcast(data) {
for (const feed of this.feeds.entries) {
feed.send(data);
}
}
timeoutCheck() {
const now = Date.now();
for (const node of this.nodes.entries) {
node.timeoutCheck(now);
}
}
updateBlock(node) {
if (node.height > this.height) {
this.height = node.height;
this.broadcast(feed_1.default.bestBlock(this.height));
console.log(`New block ${this.height}`);
}
this.broadcast(feed_1.default.imported(node));
console.log(`${node.name} imported ${node.height}, block time: ${node.blockTime / 1000}s, average: ${node.average / 1000}s | latency ${node.latency}`);
}
}
exports.default = Aggregator;
//# sourceMappingURL=aggregator.js.map
@@ -0,0 +1 @@
{"version":3,"file":"aggregator.js","sourceRoot":"","sources":["../../../src/aggregator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const EventEmitter = require("events");
const shared_1 = require("@dotstats/shared");
const nextId = shared_1.idGenerator();
function serialize(msg) {
return JSON.stringify(msg);
}
class Feed extends EventEmitter {
constructor(socket) {
super();
this.id = nextId();
this.socket = socket;
socket.on('error', () => this.disconnect());
socket.on('close', () => this.disconnect());
}
static bestBlock(height) {
return serialize({
action: 'best',
payload: height
});
}
static addedNode(node) {
return serialize({
action: 'added',
payload: [node.id, node.nodeInfo(), node.blockInfo()]
});
}
static removedNode(node) {
return serialize({
action: 'removed',
payload: node.id
});
}
static imported(node) {
return serialize({
action: 'imported',
payload: [node.id, node.blockInfo()]
});
}
send(data) {
this.socket.send(data);
}
disconnect() {
this.socket.removeAllListeners();
this.socket.close();
this.emit('disconnect');
}
}
exports.default = Feed;
//# sourceMappingURL=feed.js.map
@@ -0,0 +1 @@
{"version":3,"file":"feed.js","sourceRoot":"","sources":["../../../src/feed.ts"],"names":[],"mappings":";;AACA,uCAAuC;AAEvC,6CAA2D;AAE3D,MAAM,MAAM,GAAG,oBAAW,EAAQ,CAAC;AAoCnC,mBAAmB,GAAY;IAC3B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAa,CAAC;AAC3C,CAAC;AAED,UAA0B,SAAQ,YAAY;IAK1C,YAAY,MAAiB;QACzB,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAChD,CAAC;IAEM,MAAM,CAAC,SAAS,CAAC,MAAc;QAClC,OAAO,SAAS,CAAC;YACb,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,MAAM;SAClB,CAAC,CAAC;IACP,CAAC;IAEM,MAAM,CAAC,SAAS,CAAC,IAAU;QAC9B,OAAO,SAAS,CAAC;YACb,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;SACxD,CAAC,CAAA;IACN,CAAC;IAEM,MAAM,CAAC,WAAW,CAAC,IAAU;QAChC,OAAO,SAAS,CAAC;YACb,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,IAAI,CAAC,EAAE;SACnB,CAAC,CAAC;IACP,CAAC;IAEM,MAAM,CAAC,QAAQ,CAAC,IAAU;QAC7B,OAAO,SAAS,CAAC;YACb,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;SACvC,CAAC,CAAC;IACP,CAAC;IAEM,IAAI,CAAC,IAAc;QACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAEO,UAAU;QACd,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAEpB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;CACJ;AArDD,uBAqDC"}
@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const WebSocket = require("ws");
const express = require("express");
const http_1 = require("http");
const node_1 = require("./node");
const feed_1 = require("./feed");
const aggregator_1 = require("./aggregator");
const shared_1 = require("@dotstats/shared");
const aggregator = new aggregator_1.default;
const app = express();
const server = http_1.createServer(app);
// WebSocket for Nodes feeding telemetry data to the server
const incomingTelemetry = new WebSocket.Server({ port: 1024 });
// WebSocket for web clients listening to the telemetry data aggregate
const telemetryFeed = new WebSocket.Server({ server });
app.get('/', function (req, res) {
function nodeInfo(node) {
return `${node.name} | ${node.height} | Block time ${node.blockTime / 1000}s`;
}
res.send(`<pre>
Best block: ${aggregator.height}
Node list:
${shared_1.join(shared_1.map(aggregator.nodeList(), nodeInfo), '\n')}
</pre>`);
});
incomingTelemetry.on('connection', async (socket) => {
try {
aggregator.addNode(await node_1.default.fromSocket(socket));
}
catch (err) {
console.error(err);
}
});
telemetryFeed.on('connection', (socket) => {
aggregator.addFeed(new feed_1.default(socket));
});
console.log('Starting server on port 8080');
server.listen(8080);
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":";;AAAA,gCAAgC;AAChC,mCAAmC;AACnC,+BAAoC;AACpC,iCAA0B;AAC1B,iCAA0B;AAC1B,6CAAsC;AACtC,6CAA6C;AAE7C,MAAM,UAAU,GAAG,IAAI,oBAAU,CAAC;AAClC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AACtB,MAAM,MAAM,GAAG,mBAAY,CAAC,GAAG,CAAC,CAAC;AAEjC,2DAA2D;AAC3D,MAAM,iBAAiB,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAE/D,sEAAsE;AACtE,MAAM,aAAa,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AAEvD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,EAAE,GAAG;IAC3B,kBAAkB,IAAU;QACxB,OAAO,GAAG,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,MAAM,iBAAiB,IAAI,CAAC,SAAS,GAAG,IAAI,GAAG,CAAC;IAClF,CAAC;IAED,GAAG,CAAC,IAAI,CAEZ;cACc,UAAU,CAAC,MAAM;;;EAG5B,aAAI,CAAC,YAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAE;OAC7C,CAEF,CAAC;AACN,CAAC,CAAC,CAAC;AAEH,iBAAiB,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,EAAE,MAAiB,EAAE,EAAE;IAC3D,IAAI;QACA,UAAU,CAAC,OAAO,CAAC,MAAM,cAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;KACrD;IAAC,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;KACtB;AACL,CAAC,CAAC,CAAC;AAEH,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAiB,EAAE,EAAE;IACjD,UAAU,CAAC,OAAO,CAAC,IAAI,cAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACzC,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;AAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function parseMessage(data) {
try {
const message = JSON.parse(data.toString());
if (message && typeof message.msg === 'string' && typeof message.ts === 'string') {
message.ts = new Date(message.ts);
return message;
}
}
catch (_) {
console.warn('Error parsing message JSON');
}
return null;
}
exports.parseMessage = parseMessage;
function getBestBlock(message) {
switch (message.msg) {
case 'node.start':
case 'system.interval':
case 'block.import':
return message;
default:
return null;
}
}
exports.getBestBlock = getBestBlock;
// received: {"msg":"block.import","level":"INFO","ts":"2018-06-18T17:30:35.285406538+02:00","best":"3d4fdc7960078ddc9be87dddc48324a6d64afdf1f65fffe89529ce9965cd5f29","height":526}
// received: {"msg":"node.start","level":"INFO","ts":"2018-06-18T17:30:40.038731057+02:00","best":"3d4fdc7960078ddc9be87dddc48324a6d64afdf1f65fffe89529ce9965cd5f29","height":526}
// received: {"msg":"system.connected","level":"INFO","ts":"2018-06-18T17:30:40.038975471+02:00","chain":"dev","config":"","version":"0.2.0","implementation":"parity-polkadot","name":"Majestic Widget"}
// received: {"msg":"system.interval","level":"INFO","ts":"2018-06-19T14:00:05.091355364+02:00","txcount":0,"best":"360c9563857308703398f637932b7ffe884e5c7b09692600ff09a4d753c9d948","height":7559,"peers":0,"status":"Idle"}
//# sourceMappingURL=message.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../src/message.ts"],"names":[],"mappings":";;AAGA,sBAA6B,IAAU;IACnC,IAAI;QACA,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAE5C,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,EAAE,KAAK,QAAQ,EAAE;YAC9E,OAAO,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAElC,OAAO,OAAO,CAAC;SAClB;KACJ;IAAC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;KAC9C;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAdD,oCAcC;AAED,sBAA6B,OAAgB;IACzC,QAAQ,OAAO,CAAC,GAAG,EAAE;QACjB,KAAK,YAAY,CAAC;QAClB,KAAK,iBAAiB,CAAC;QACvB,KAAK,cAAc;YACf,OAAO,OAAO,CAAC;QACnB;YACI,OAAO,IAAI,CAAC;KACnB;AACL,CAAC;AATD,oCASC;AA8CD,oLAAoL;AACpL,kLAAkL;AAClL,yMAAyM;AACzM,8NAA8N"}
+124
View File
@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const EventEmitter = require("events");
const shared_1 = require("@dotstats/shared");
const message_1 = require("./message");
const BLOCK_TIME_HISTORY = 10;
const TIMEOUT = 1000 * 60 * 5; // 5 seconds
const nextId = shared_1.idGenerator();
class Node extends EventEmitter {
constructor(socket, name, config, implentation, version) {
super();
this.height = 0;
this.latency = 0;
this.blockTime = 0;
this.blockTimes = new Array(BLOCK_TIME_HISTORY);
this.lastBlockAt = null;
this.lastMessage = Date.now();
this.id = nextId();
this.socket = socket;
this.name = name;
this.config = config;
this.implementation = implentation;
this.version = version;
console.log(`Listening to a new node: ${name}`);
socket.on('message', (data) => {
const message = message_1.parseMessage(data);
if (!message)
return;
this.lastMessage = Date.now();
this.updateLatency(message.ts);
const update = message_1.getBestBlock(message);
if (update) {
this.updateBestBlock(update);
}
});
socket.on('close', () => {
console.log(`${this.name} has disconnected`);
this.disconnect();
});
socket.on('error', (error) => {
console.error(`${this.name} has errored`, error);
this.disconnect();
});
}
static fromSocket(socket) {
return new Promise((resolve, reject) => {
function cleanup() {
clearTimeout(timeout);
socket.removeAllListeners('message');
}
function handler(data) {
const message = message_1.parseMessage(data);
if (message && message.msg === "system.connected") {
cleanup();
const { name, config, implementation, version } = message;
resolve(new Node(socket, name, config, implementation, version));
}
}
socket.on('message', handler);
const timeout = setTimeout(() => {
cleanup();
socket.close();
return reject(new Error('Timeout on waiting for system.connected message'));
}, 5000);
});
}
timeoutCheck(now) {
if (this.lastMessage + TIMEOUT < now) {
this.disconnect();
}
}
nodeInfo() {
return {
name: this.name,
};
}
blockInfo() {
return {
height: this.height,
blockTime: this.blockTime,
};
}
get average() {
let accounted = 0;
let sum = 0;
for (const time of this.blockTimes) {
if (time) {
accounted += 1;
sum += time;
}
}
if (accounted === 0) {
return 0;
}
return sum / accounted;
}
disconnect() {
this.socket.removeAllListeners();
this.socket.close();
this.emit('disconnect');
}
updateLatency(time) {
this.latency = this.lastMessage - +time;
}
updateBestBlock(update) {
const { height, ts: time, best } = update;
if (this.height < height) {
const blockTime = this.getBlockTime(time);
this.height = height;
this.lastBlockAt = time;
this.blockTimes[height % BLOCK_TIME_HISTORY] = blockTime;
this.blockTime = blockTime;
this.emit('block');
}
}
getBlockTime(time) {
if (!this.lastBlockAt) {
return 0;
}
return +time - +this.lastBlockAt;
}
}
exports.default = Node;
//# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../../src/node.ts"],"names":[],"mappings":";;AACA,uCAAuC;AACvC,6CAA0D;AAC1D,uCAA2E;AAE3E,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,YAAY;AAE3C,MAAM,MAAM,GAAG,oBAAW,EAAQ,CAAC;AAWnC,UAA0B,SAAQ,YAAY;IAe1C,YAAY,MAAiB,EAAE,IAAY,EAAE,MAAc,EAAE,YAAoB,EAAE,OAAe;QAC9F,KAAK,EAAE,CAAC;QAVL,WAAM,GAAW,CAAC,CAAC;QAEnB,YAAO,GAAW,CAAC,CAAC;QACpB,cAAS,GAAW,CAAC,CAAC;QAGrB,eAAU,GAAkB,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QAC1D,gBAAW,GAAgB,IAAI,CAAC;QAKpC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,cAAc,GAAG,YAAY,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QAEvB,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;QAEhD,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YAC1B,MAAM,OAAO,GAAG,sBAAY,CAAC,IAAI,CAAC,CAAC;YAEnC,IAAI,CAAC,OAAO;gBAAE,OAAO;YAErB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC9B,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAE/B,MAAM,MAAM,GAAG,sBAAY,CAAC,OAAO,CAAC,CAAC;YAErC,IAAI,MAAM,EAAE;gBACR,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;aAChC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACpB,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,mBAAmB,CAAC,CAAC;YAE7C,IAAI,CAAC,UAAU,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACzB,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,cAAc,EAAE,KAAK,CAAC,CAAC;YAEjD,IAAI,CAAC,UAAU,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;IACP,CAAC;IAEM,MAAM,CAAC,UAAU,CAAC,MAAiB;QACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnC;gBACI,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;YACzC,CAAC;YAED,iBAAiB,IAAoB;gBACjC,MAAM,OAAO,GAAG,sBAAY,CAAC,IAAI,CAAC,CAAC;gBAEnC,IAAI,OAAO,IAAI,OAAO,CAAC,GAAG,KAAK,kBAAkB,EAAE;oBAC/C,OAAO,EAAE,CAAC;oBAEV,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;oBAE1D,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;iBACpE;YACL,CAAC;YAED,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAE9B,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,OAAO,EAAE,CAAC;gBAEV,MAAM,CAAC,KAAK,EAAE,CAAC;gBAEf,OAAO,MAAM,CAAC,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC,CAAC;YAChF,CAAC,EAAE,IAAI,CAAC,CAAC;QACb,CAAC,CAAC,CAAC;IACP,CAAC;IAEM,YAAY,CAAC,GAAW;QAC3B,IAAI,IAAI,CAAC,WAAW,GAAG,OAAO,GAAG,GAAG,EAAE;YAClC,IAAI,CAAC,UAAU,EAAE,CAAC;SACrB;IACL,CAAC;IAEM,QAAQ;QACX,OAAO;YACH,IAAI,EAAE,IAAI,CAAC,IAAI;SAClB,CAAC;IACN,CAAC;IAEM,SAAS;QACZ,OAAO;YACH,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC5B,CAAC;IACN,CAAC;IAED,IAAW,OAAO;QACd,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,GAAG,GAAG,CAAC,CAAC;QAEZ,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE;YAChC,IAAI,IAAI,EAAE;gBACN,SAAS,IAAI,CAAC,CAAC;gBACf,GAAG,IAAI,IAAI,CAAC;aACf;SACJ;QAED,IAAI,SAAS,KAAK,CAAC,EAAE;YACjB,OAAO,CAAC,CAAC;SACZ;QAED,OAAO,GAAG,GAAG,SAAS,CAAC;IAC3B,CAAC;IAIO,UAAU;QACd,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAEpB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;IAEO,aAAa,CAAC,IAAU;QAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,WAAW,GAAG,CAAC,IAAI,CAAC;IAC5C,CAAC;IAEO,eAAe,CAAC,MAAiB;QACrC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;QAE1C,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE;YACtB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YAE1C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YACrB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,kBAAkB,CAAC,GAAG,SAAS,CAAC;YACzD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;YAE3B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SACtB;IACL,CAAC;IAEO,YAAY,CAAC,IAAU;QAC3B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YACnB,OAAO,CAAC,CAAC;SACZ;QAED,OAAO,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC;IACrC,CAAC;CACJ;AA/JD,uBA+JC"}
+51
View File
@@ -0,0 +1,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const EventEmitter = require("events");
const common_1 = require("@dotstats/common");
const nextId = common_1.idGenerator();
function serialize(msg) {
return JSON.stringify(msg);
}
class Feed extends EventEmitter {
constructor(socket) {
super();
this.id = nextId();
this.socket = socket;
socket.on('error', () => this.disconnect());
socket.on('close', () => this.disconnect());
}
static bestBlock(height) {
return serialize({
action: 'best',
payload: height
});
}
static addedNode(node) {
return serialize({
action: 'added',
payload: [node.id, node.nodeInfo(), node.blockInfo()]
});
}
static removedNode(node) {
return serialize({
action: 'removed',
payload: node.id
});
}
static imported(node) {
return serialize({
action: 'imported',
payload: [node.id, node.blockInfo()]
});
}
send(data) {
this.socket.send(data);
}
disconnect() {
this.socket.removeAllListeners();
this.socket.close();
this.emit('disconnect');
}
}
exports.default = Feed;
//# sourceMappingURL=feed.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"feed.js","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":";;AACA,uCAAuC;AAEvC,6CAA2D;AAE3D,MAAM,MAAM,GAAG,oBAAW,EAAQ,CAAC;AAoCnC,mBAAmB,GAAY;IAC3B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAa,CAAC;AAC3C,CAAC;AAED,UAA0B,SAAQ,YAAY;IAK1C,YAAY,MAAiB;QACzB,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QAErB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAChD,CAAC;IAEM,MAAM,CAAC,SAAS,CAAC,MAAc;QAClC,OAAO,SAAS,CAAC;YACb,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,MAAM;SAClB,CAAC,CAAC;IACP,CAAC;IAEM,MAAM,CAAC,SAAS,CAAC,IAAU;QAC9B,OAAO,SAAS,CAAC;YACb,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;SACxD,CAAC,CAAA;IACN,CAAC;IAEM,MAAM,CAAC,WAAW,CAAC,IAAU;QAChC,OAAO,SAAS,CAAC;YACb,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,IAAI,CAAC,EAAE;SACnB,CAAC,CAAC;IACP,CAAC;IAEM,MAAM,CAAC,QAAQ,CAAC,IAAU;QAC7B,OAAO,SAAS,CAAC;YACb,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;SACvC,CAAC,CAAC;IACP,CAAC;IAEM,IAAI,CAAC,IAAc;QACtB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAEO,UAAU;QACd,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;QACjC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAEpB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;CACJ;AArDD,uBAqDC"}
+41
View File
@@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const WebSocket = require("ws");
const express = require("express");
const http_1 = require("http");
const node_1 = require("./node");
const feed_1 = require("./feed");
const aggregator_1 = require("./aggregator");
const common_1 = require("@dotstats/common");
const aggregator = new aggregator_1.default;
const app = express();
const server = http_1.createServer(app);
// WebSocket for Nodes feeding telemetry data to the server
const incomingTelemetry = new WebSocket.Server({ port: 1024 });
// WebSocket for web clients listening to the telemetry data aggregate
const telemetryFeed = new WebSocket.Server({ server });
app.get('/', function (req, res) {
function nodeInfo(node) {
return `${node.name} | ${node.height} | Block time ${node.blockTime / 1000}s`;
}
res.send(`<pre>
Best block: ${aggregator.height}
Node list:
${common_1.join(common_1.map(aggregator.nodeList(), nodeInfo), '\n')}
</pre>`);
});
incomingTelemetry.on('connection', async (socket) => {
try {
aggregator.addNode(await node_1.default.fromSocket(socket));
}
catch (err) {
console.error(err);
}
});
telemetryFeed.on('connection', (socket) => {
aggregator.addFeed(new feed_1.default(socket));
});
console.log('Starting server on port 8080');
server.listen(8080);
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,gCAAgC;AAChC,mCAAmC;AACnC,+BAAoC;AACpC,iCAA0B;AAC1B,iCAA0B;AAC1B,6CAAsC;AACtC,6CAA6C;AAE7C,MAAM,UAAU,GAAG,IAAI,oBAAU,CAAC;AAClC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AACtB,MAAM,MAAM,GAAG,mBAAY,CAAC,GAAG,CAAC,CAAC;AAEjC,2DAA2D;AAC3D,MAAM,iBAAiB,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAE/D,sEAAsE;AACtE,MAAM,aAAa,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AAEvD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,EAAE,GAAG;IAC3B,kBAAkB,IAAU;QACxB,OAAO,GAAG,IAAI,CAAC,IAAI,MAAM,IAAI,CAAC,MAAM,iBAAiB,IAAI,CAAC,SAAS,GAAG,IAAI,GAAG,CAAC;IAClF,CAAC;IAED,GAAG,CAAC,IAAI,CAEZ;cACc,UAAU,CAAC,MAAM;;;EAG5B,aAAI,CAAC,YAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAE;OAC7C,CAEF,CAAC;AACN,CAAC,CAAC,CAAC;AAEH,iBAAiB,CAAC,EAAE,CAAC,YAAY,EAAE,KAAK,EAAE,MAAiB,EAAE,EAAE;IAC3D,IAAI;QACA,UAAU,CAAC,OAAO,CAAC,MAAM,cAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;KACrD;IAAC,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;KACtB;AACL,CAAC,CAAC,CAAC;AAEH,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,MAAiB,EAAE,EAAE;IACjD,UAAU,CAAC,OAAO,CAAC,IAAI,cAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AACzC,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;AAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC"}
+32
View File
@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function parseMessage(data) {
try {
const message = JSON.parse(data.toString());
if (message && typeof message.msg === 'string' && typeof message.ts === 'string') {
message.ts = new Date(message.ts);
return message;
}
}
catch (_) {
console.warn('Error parsing message JSON');
}
return null;
}
exports.parseMessage = parseMessage;
function getBestBlock(message) {
switch (message.msg) {
case 'node.start':
case 'system.interval':
case 'block.import':
return message;
default:
return null;
}
}
exports.getBestBlock = getBestBlock;
// received: {"msg":"block.import","level":"INFO","ts":"2018-06-18T17:30:35.285406538+02:00","best":"3d4fdc7960078ddc9be87dddc48324a6d64afdf1f65fffe89529ce9965cd5f29","height":526}
// received: {"msg":"node.start","level":"INFO","ts":"2018-06-18T17:30:40.038731057+02:00","best":"3d4fdc7960078ddc9be87dddc48324a6d64afdf1f65fffe89529ce9965cd5f29","height":526}
// received: {"msg":"system.connected","level":"INFO","ts":"2018-06-18T17:30:40.038975471+02:00","chain":"dev","config":"","version":"0.2.0","implementation":"parity-polkadot","name":"Majestic Widget"}
// received: {"msg":"system.interval","level":"INFO","ts":"2018-06-19T14:00:05.091355364+02:00","txcount":0,"best":"360c9563857308703398f637932b7ffe884e5c7b09692600ff09a4d753c9d948","height":7559,"peers":0,"status":"Idle"}
//# sourceMappingURL=message.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"message.js","sourceRoot":"","sources":["../src/message.ts"],"names":[],"mappings":";;AAGA,sBAA6B,IAAU;IACnC,IAAI;QACA,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAE5C,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,EAAE,KAAK,QAAQ,EAAE;YAC9E,OAAO,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAElC,OAAO,OAAO,CAAC;SAClB;KACJ;IAAC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;KAC9C;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAdD,oCAcC;AAED,sBAA6B,OAAgB;IACzC,QAAQ,OAAO,CAAC,GAAG,EAAE;QACjB,KAAK,YAAY,CAAC;QAClB,KAAK,iBAAiB,CAAC;QACvB,KAAK,cAAc;YACf,OAAO,OAAO,CAAC;QACnB;YACI,OAAO,IAAI,CAAC;KACnB;AACL,CAAC;AATD,oCASC;AA8CD,oLAAoL;AACpL,kLAAkL;AAClL,yMAAyM;AACzM,8NAA8N"}
+124
View File
@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const EventEmitter = require("events");
const common_1 = require("@dotstats/common");
const message_1 = require("./message");
const BLOCK_TIME_HISTORY = 10;
const TIMEOUT = 1000 * 60 * 5; // 5 seconds
const nextId = common_1.idGenerator();
class Node extends EventEmitter {
constructor(socket, name, config, implentation, version) {
super();
this.height = 0;
this.latency = 0;
this.blockTime = 0;
this.blockTimes = new Array(BLOCK_TIME_HISTORY);
this.lastBlockAt = null;
this.lastMessage = Date.now();
this.id = nextId();
this.socket = socket;
this.name = name;
this.config = config;
this.implementation = implentation;
this.version = version;
console.log(`Listening to a new node: ${name}`);
socket.on('message', (data) => {
const message = message_1.parseMessage(data);
if (!message)
return;
this.lastMessage = Date.now();
this.updateLatency(message.ts);
const update = message_1.getBestBlock(message);
if (update) {
this.updateBestBlock(update);
}
});
socket.on('close', () => {
console.log(`${this.name} has disconnected`);
this.disconnect();
});
socket.on('error', (error) => {
console.error(`${this.name} has errored`, error);
this.disconnect();
});
}
static fromSocket(socket) {
return new Promise((resolve, reject) => {
function cleanup() {
clearTimeout(timeout);
socket.removeAllListeners('message');
}
function handler(data) {
const message = message_1.parseMessage(data);
if (message && message.msg === "system.connected") {
cleanup();
const { name, config, implementation, version } = message;
resolve(new Node(socket, name, config, implementation, version));
}
}
socket.on('message', handler);
const timeout = setTimeout(() => {
cleanup();
socket.close();
return reject(new Error('Timeout on waiting for system.connected message'));
}, 5000);
});
}
timeoutCheck(now) {
if (this.lastMessage + TIMEOUT < now) {
this.disconnect();
}
}
nodeInfo() {
return {
name: this.name,
};
}
blockInfo() {
return {
height: this.height,
blockTime: this.blockTime,
};
}
get average() {
let accounted = 0;
let sum = 0;
for (const time of this.blockTimes) {
if (time) {
accounted += 1;
sum += time;
}
}
if (accounted === 0) {
return 0;
}
return sum / accounted;
}
disconnect() {
this.socket.removeAllListeners();
this.socket.close();
this.emit('disconnect');
}
updateLatency(time) {
this.latency = this.lastMessage - +time;
}
updateBestBlock(update) {
const { height, ts: time, best } = update;
if (this.height < height) {
const blockTime = this.getBlockTime(time);
this.height = height;
this.lastBlockAt = time;
this.blockTimes[height % BLOCK_TIME_HISTORY] = blockTime;
this.blockTime = blockTime;
this.emit('block');
}
}
getBlockTime(time) {
if (!this.lastBlockAt) {
return 0;
}
return +time - +this.lastBlockAt;
}
}
exports.default = Node;
//# sourceMappingURL=node.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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"}
+26
View File
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Higher order function producing new auto-incremented `Id`s.
*/
function idGenerator() {
let current = 0;
return () => current++;
}
exports.idGenerator = idGenerator;
class IdSet {
constructor() {
this.map = new Map();
}
add(item) {
this.map.set(item.id, item);
}
remove(item) {
this.map.delete(item.id);
}
get entries() {
return this.map.values();
}
}
exports.IdSet = IdSet;
//# sourceMappingURL=id.js.map
@@ -0,0 +1 @@
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../../../shared/src/id.ts"],"names":[],"mappings":";;AAOA;;GAEG;AACH;IACI,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,OAAO,GAAG,EAAE,CAAC,OAAO,EAAW,CAAC;AACpC,CAAC;AAJD,kCAIC;AAMD;IAAA;QACY,QAAG,GAAkB,IAAI,GAAG,EAAE,CAAC;IAa3C,CAAC;IAXU,GAAG,CAAC,IAAkB;QACzB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAEM,MAAM,CAAC,IAAkB;QAC5B,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IAED,IAAW,OAAO;QACd,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;IAC7B,CAAC;CACJ;AAdD,sBAcC"}
@@ -0,0 +1,9 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(require("./id"));
__export(require("./iterators"));
__export(require("./types"));
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../shared/src/index.ts"],"names":[],"mappings":";;;;;AAAA,0BAAqB;AACrB,iCAA4B;AAC5B,6BAAwB"}
@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function* map(iter, fn) {
for (const item of iter) {
yield fn(item);
}
}
exports.map = map;
function* chain(a, b) {
yield* a;
yield* b;
}
exports.chain = chain;
function* zip(a, b) {
let itemA = a.next();
let itemB = b.next();
while (!itemA.done && !itemB.done) {
yield [itemA.value, itemB.value];
itemA = a.next();
itemB = b.next();
}
}
exports.zip = zip;
function* take(iter, n) {
for (const item of iter) {
if (n-- === 0) {
return;
}
yield item;
}
}
exports.take = take;
function skip(iter, n) {
while (n-- !== 0 && !iter.next().done) { }
return iter;
}
exports.skip = skip;
function reduce(iter, fn, accumulator) {
for (const item of iter) {
accumulator = fn(accumulator, item);
}
return accumulator;
}
exports.reduce = reduce;
function join(iter, glue) {
const first = iter.next();
if (first.done) {
return '';
}
let result = first.value.toString();
for (const item of iter) {
result += glue + item;
}
return result;
}
exports.join = join;
//# sourceMappingURL=iterators.js.map
@@ -0,0 +1 @@
{"version":3,"file":"iterators.js","sourceRoot":"","sources":["../../../../shared/src/iterators.ts"],"names":[],"mappings":";;AAAA,QAAe,CAAC,KAAW,IAAyB,EAAE,EAAkB;IACpE,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE;QACrB,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;KAClB;AACL,CAAC;AAJD,kBAIC;AAED,QAAe,CAAC,OAAU,CAAsB,EAAE,CAAsB;IACpE,KAAK,CAAC,CAAC,CAAC,CAAC;IACT,KAAK,CAAC,CAAC,CAAC,CAAC;AACb,CAAC;AAHD,sBAGC;AAED,QAAe,CAAC,KAAW,CAAsB,EAAE,CAAsB;IACrE,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAErB,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAC/B,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAEjC,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;KACpB;AACL,CAAC;AAVD,kBAUC;AAED,QAAe,CAAC,MAAS,IAAyB,EAAE,CAAS;IACzD,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE;QACrB,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE;YACX,OAAO;SACV;QAED,MAAM,IAAI,CAAC;KACd;AACL,CAAC;AARD,oBAQC;AAED,cAAwB,IAAyB,EAAE,CAAS;IACxD,OAAO,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,GAAE;IAEzC,OAAO,IAAI,CAAC;AAChB,CAAC;AAJD,oBAIC;AAED,gBAA6B,IAAyB,EAAE,EAA2B,EAAE,WAAc;IAC/F,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE;QACrB,WAAW,GAAG,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;KACvC;IAED,OAAO,WAAW,CAAC;AACvB,CAAC;AAND,wBAMC;AAED,cAAqB,IAAkD,EAAE,IAAY;IACjF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE1B,IAAI,KAAK,CAAC,IAAI,EAAE;QACZ,OAAO,EAAE,CAAC;KACb;IAED,IAAI,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE;QACrB,MAAM,IAAI,IAAI,GAAG,IAAI,CAAC;KACzB;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAdD,oBAcC"}
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* PhantomData akin to Rust, because sometimes you need to be smarter than
* the compiler.
*/
class PhantomData {
}
exports.PhantomData = PhantomData;
//# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../shared/src/types.ts"],"names":[],"mappings":";;AAAA;;;GAGG;AACH;CAAsD;AAAtD,kCAAsD"}
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../../../../node_modules/typescript/bin/tsc
+1
View File
@@ -0,0 +1 @@
../../../../node_modules/typescript/bin/tsserver
+466
View File
@@ -0,0 +1,466 @@
{
"name": "dotstats-server",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/body-parser": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz",
"integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==",
"requires": {
"@types/connect": "3.4.32",
"@types/node": "10.3.3"
}
},
"@types/connect": {
"version": "3.4.32",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
"integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
"requires": {
"@types/node": "10.3.3"
}
},
"@types/events": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz",
"integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA=="
},
"@types/express": {
"version": "4.16.0",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz",
"integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==",
"requires": {
"@types/body-parser": "1.17.0",
"@types/express-serve-static-core": "4.16.0",
"@types/serve-static": "1.13.2"
}
},
"@types/express-serve-static-core": {
"version": "4.16.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz",
"integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==",
"requires": {
"@types/events": "1.2.0",
"@types/node": "10.3.3",
"@types/range-parser": "1.2.2"
}
},
"@types/mime": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz",
"integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA=="
},
"@types/node": {
"version": "10.3.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.3.tgz",
"integrity": "sha512-/gwCgiI2e9RzzZTKbl+am3vgNqOt7a9fJ/uxv4SqYKxenoEDNVU3KZEadlpusWhQI0A0dOrZ0T68JYKVjzmgdQ=="
},
"@types/range-parser": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz",
"integrity": "sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw=="
},
"@types/serve-static": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz",
"integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==",
"requires": {
"@types/express-serve-static-core": "4.16.0",
"@types/mime": "2.0.0"
}
},
"@types/ws": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz",
"integrity": "sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg==",
"requires": {
"@types/events": "1.2.0",
"@types/node": "10.3.3"
}
},
"accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
"requires": {
"mime-types": "2.1.18",
"negotiator": "0.6.1"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"body-parser": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
"integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
"requires": {
"bytes": "3.0.0",
"content-type": "1.0.4",
"debug": "2.6.9",
"depd": "1.1.2",
"http-errors": "1.6.3",
"iconv-lite": "0.4.19",
"on-finished": "2.3.0",
"qs": "6.5.1",
"raw-body": "2.3.2",
"type-is": "1.6.16"
}
},
"bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
},
"content-disposition": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"express": {
"version": "4.16.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz",
"integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
"requires": {
"accepts": "1.3.5",
"array-flatten": "1.1.1",
"body-parser": "1.18.2",
"content-disposition": "0.5.2",
"content-type": "1.0.4",
"cookie": "0.3.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "1.1.2",
"encodeurl": "1.0.2",
"escape-html": "1.0.3",
"etag": "1.8.1",
"finalhandler": "1.1.1",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "1.1.2",
"on-finished": "2.3.0",
"parseurl": "1.3.2",
"path-to-regexp": "0.1.7",
"proxy-addr": "2.0.3",
"qs": "6.5.1",
"range-parser": "1.2.0",
"safe-buffer": "5.1.1",
"send": "0.16.2",
"serve-static": "1.13.2",
"setprototypeof": "1.1.0",
"statuses": "1.4.0",
"type-is": "1.6.16",
"utils-merge": "1.0.1",
"vary": "1.1.2"
}
},
"finalhandler": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
"requires": {
"debug": "2.6.9",
"encodeurl": "1.0.2",
"escape-html": "1.0.3",
"on-finished": "2.3.0",
"parseurl": "1.3.2",
"statuses": "1.4.0",
"unpipe": "1.0.0"
}
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-errors": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"requires": {
"depd": "1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.0",
"statuses": "1.4.0"
}
},
"iconv-lite": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz",
"integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
},
"mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
},
"mime-types": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
"requires": {
"mime-db": "1.33.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"negotiator": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
},
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"proxy-addr": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz",
"integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==",
"requires": {
"forwarded": "0.1.2",
"ipaddr.js": "1.6.0"
}
},
"qs": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
},
"range-parser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
},
"raw-body": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
"integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
"requires": {
"bytes": "3.0.0",
"http-errors": "1.6.2",
"iconv-lite": "0.4.19",
"unpipe": "1.0.0"
},
"dependencies": {
"depd": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
"integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
},
"http-errors": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
"integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
"requires": {
"depd": "1.1.1",
"inherits": "2.0.3",
"setprototypeof": "1.0.3",
"statuses": "1.4.0"
}
},
"setprototypeof": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
"integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
}
}
},
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"send": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
"requires": {
"debug": "2.6.9",
"depd": "1.1.2",
"destroy": "1.0.4",
"encodeurl": "1.0.2",
"escape-html": "1.0.3",
"etag": "1.8.1",
"fresh": "0.5.2",
"http-errors": "1.6.3",
"mime": "1.4.1",
"ms": "2.0.0",
"on-finished": "2.3.0",
"range-parser": "1.2.0",
"statuses": "1.4.0"
}
},
"serve-static": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
"requires": {
"encodeurl": "1.0.2",
"escape-html": "1.0.3",
"parseurl": "1.3.2",
"send": "0.16.2"
}
},
"setprototypeof": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
},
"statuses": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
},
"type-is": {
"version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
"requires": {
"media-typer": "0.3.0",
"mime-types": "2.1.18"
}
},
"typescript": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w=="
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
},
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
},
"ws": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.0.tgz",
"integrity": "sha512-c18dMeW+PEQdDFzkhDsnBAlS4Z8KGStBQQUcQ5mf7Nf689jyGk0594L+i9RaQuf4gog6SvWLJorz2NfSaqxZ7w==",
"requires": {
"async-limiter": "1.0.0"
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"name": "@dotstats/backend",
"version": "0.1.0",
"author": "Parity Technologies Ltd. <admin@parity.io>",
"license": "GPL-3.0",
"description": "Polkadot Telemetry frontend",
"main": "build/index.js",
"scripts": {
"start": "tsc && node build/index.js",
"build": "tsc",
"check": "tsc --noEmit"
},
"dependencies": {
"@types/express": "^4.16.0",
"@types/node": "^10.3.3",
"@types/ws": "^5.1.2",
"express": "^4.16.3",
"typescript": "^2.9.2",
"ws": "5.2.0"
}
}
+77
View File
@@ -0,0 +1,77 @@
import * as EventEmitter from 'events';
import Node from './node';
import Feed, { FeedData } from './feed';
import { Id, IdSet } from '@dotstats/common';
export default class Aggregator extends EventEmitter {
private nodes: IdSet<Node> = new IdSet<Node>();
private feeds: IdSet<Feed> = new IdSet<Feed>();
public height: number = 0;
constructor() {
super();
setInterval(() => this.timeoutCheck(), 10000);
}
public addNode(node: Node) {
this.nodes.add(node);
this.broadcast(Feed.addedNode(node));
node.once('disconnect', () => {
node.removeAllListeners('block');
this.nodes.remove(node);
this.broadcast(Feed.removedNode(node));
});
node.on('block', () => this.updateBlock(node));
}
public addFeed(feed: Feed) {
this.feeds.add(feed);
feed.send(Feed.bestBlock(this.height));
for (const node of this.nodes.entries) {
feed.send(Feed.addedNode(node));
}
feed.once('disconnect', () => {
this.feeds.remove(feed);
})
}
public nodeList(): IterableIterator<Node> {
return this.nodes.entries;
}
private broadcast(data: FeedData) {
for (const feed of this.feeds.entries) {
feed.send(data);
}
}
private timeoutCheck() {
const now = Date.now();
for (const node of this.nodes.entries) {
node.timeoutCheck(now);
}
}
private updateBlock(node: Node) {
if (node.height > this.height) {
this.height = node.height;
this.broadcast(Feed.bestBlock(this.height));
console.log(`New block ${this.height}`);
}
this.broadcast(Feed.imported(node));
console.log(`${node.name} imported ${node.height}, block time: ${node.blockTime / 1000}s, average: ${node.average / 1000}s | latency ${node.latency}`);
}
}
+99
View File
@@ -0,0 +1,99 @@
import * as WebSocket from 'ws';
import * as EventEmitter from 'events';
import Node, { NodeInfo, BlockInfo } from './node';
import { Opaque, Id, idGenerator } from '@dotstats/common';
const nextId = idGenerator<Feed>();
export interface BlockInfo {
height: number;
blockTime: number;
}
interface BestBlock {
action: 'best';
payload: number;
}
interface AddedNode {
action: 'added';
payload: [Id<Node>, NodeInfo, BlockInfo];
}
interface RemovedNode {
action: 'removed';
payload: Id<Node>;
}
interface Imported {
action: 'imported';
payload: [Id<Node>, BlockInfo];
}
type Message = BestBlock | AddedNode | RemovedNode | Imported;
/**
* Opaque data type to be sent to the feed. Passing through
* strings means we can only serialize once, no matter how
* many feed clients are listening in.
*/
export type FeedData = Opaque<string, Message>;
function serialize(msg: Message): FeedData {
return JSON.stringify(msg) as FeedData;
}
export default class Feed extends EventEmitter {
public id: Id<Feed>;
private socket: WebSocket;
constructor(socket: WebSocket) {
super();
this.id = nextId();
this.socket = socket;
socket.on('error', () => this.disconnect());
socket.on('close', () => this.disconnect());
}
public static bestBlock(height: number): FeedData {
return serialize({
action: 'best',
payload: height
});
}
public static addedNode(node: Node): FeedData {
return serialize({
action: 'added',
payload: [node.id, node.nodeInfo(), node.blockInfo()]
})
}
public static removedNode(node: Node): FeedData {
return serialize({
action: 'removed',
payload: node.id
});
}
public static imported(node: Node): FeedData {
return serialize({
action: 'imported',
payload: [node.id, node.blockInfo()]
});
}
public send(data: FeedData) {
this.socket.send(data);
}
private disconnect() {
this.socket.removeAllListeners();
this.socket.close();
this.emit('disconnect');
}
}
+49
View File
@@ -0,0 +1,49 @@
import * as WebSocket from 'ws';
import * as express from 'express';
import { createServer } from 'http';
import Node from './node';
import Feed from './feed';
import Aggregator from './aggregator';
import { map, join } from '@dotstats/common';
const aggregator = new Aggregator;
const app = express();
const server = createServer(app);
// WebSocket for Nodes feeding telemetry data to the server
const incomingTelemetry = new WebSocket.Server({ port: 1024 });
// WebSocket for web clients listening to the telemetry data aggregate
const telemetryFeed = new WebSocket.Server({ server });
app.get('/', function (req, res) {
function nodeInfo(node: Node) {
return `${node.name} | ${node.height} | Block time ${node.blockTime / 1000}s`;
}
res.send(
`<pre>
Best block: ${aggregator.height}
Node list:
${ join(map(aggregator.nodeList(), nodeInfo), '\n') }
</pre>`
);
});
incomingTelemetry.on('connection', async (socket: WebSocket) => {
try {
aggregator.addNode(await Node.fromSocket(socket));
} catch (err) {
console.error(err);
}
});
telemetryFeed.on('connection', (socket: WebSocket) => {
aggregator.addFeed(new Feed(socket));
});
console.log('Starting server on port 8080');
server.listen(8080);
+78
View File
@@ -0,0 +1,78 @@
import { Data } from 'ws';
import { Maybe, Opaque } from '@dotstats/common';
export function parseMessage(data: Data): Maybe<Message> {
try {
const message = JSON.parse(data.toString());
if (message && typeof message.msg === 'string' && typeof message.ts === 'string') {
message.ts = new Date(message.ts);
return message;
}
} catch (_) {
console.warn('Error parsing message JSON');
}
return null;
}
export function getBestBlock(message: Message): Maybe<BestBlock> {
switch (message.msg) {
case 'node.start':
case 'system.interval':
case 'block.import':
return message;
default:
return null;
}
}
interface MessageBase {
ts: Date,
level: 'INFO' | 'WARN',
}
export interface BestBlock {
best: string,
height: number,
ts: Date,
}
interface SystemConnected {
msg: 'system.connected',
name: string,
chain: string,
config: string,
implementation: string,
version: string,
}
interface SystemInterval extends BestBlock {
msg: 'system.interval',
txcount: number,
peers: number,
status: 'Idle' | string, // TODO: 'Idle' | ...?
}
interface NodeStart extends BestBlock {
msg: 'node.start',
}
interface BlockImport extends BestBlock {
msg: 'block.import',
}
// Union type
export type Message = MessageBase & (
SystemConnected |
SystemInterval |
NodeStart |
BlockImport
);
// received: {"msg":"block.import","level":"INFO","ts":"2018-06-18T17:30:35.285406538+02:00","best":"3d4fdc7960078ddc9be87dddc48324a6d64afdf1f65fffe89529ce9965cd5f29","height":526}
// received: {"msg":"node.start","level":"INFO","ts":"2018-06-18T17:30:40.038731057+02:00","best":"3d4fdc7960078ddc9be87dddc48324a6d64afdf1f65fffe89529ce9965cd5f29","height":526}
// received: {"msg":"system.connected","level":"INFO","ts":"2018-06-18T17:30:40.038975471+02:00","chain":"dev","config":"","version":"0.2.0","implementation":"parity-polkadot","name":"Majestic Widget"}
// received: {"msg":"system.interval","level":"INFO","ts":"2018-06-19T14:00:05.091355364+02:00","txcount":0,"best":"360c9563857308703398f637932b7ffe884e5c7b09692600ff09a4d753c9d948","height":7559,"peers":0,"status":"Idle"}
+179
View File
@@ -0,0 +1,179 @@
import * as WebSocket from 'ws';
import * as EventEmitter from 'events';
import { Maybe, Id, idGenerator } from '@dotstats/common';
import { parseMessage, getBestBlock, Message, BestBlock } from './message';
const BLOCK_TIME_HISTORY = 10;
const TIMEOUT = 1000 * 60 * 5; // 5 seconds
const nextId = idGenerator<Node>();
export interface NodeInfo {
name: string;
}
export interface BlockInfo {
height: number;
blockTime: number;
}
export default class Node extends EventEmitter {
public lastMessage: number;
public id: Id<Node>;
public name: string;
public implementation: string;
public version: string;
public height: number = 0;
public config: string;
public latency: number = 0;
public blockTime: number = 0;
private socket: WebSocket;
private blockTimes: Array<number> = new Array(BLOCK_TIME_HISTORY);
private lastBlockAt: Maybe<Date> = null;
constructor(socket: WebSocket, name: string, config: string, implentation: string, version: string) {
super();
this.lastMessage = Date.now();
this.id = nextId();
this.socket = socket;
this.name = name;
this.config = config;
this.implementation = implentation;
this.version = version;
console.log(`Listening to a new node: ${name}`);
socket.on('message', (data) => {
const message = parseMessage(data);
if (!message) return;
this.lastMessage = Date.now();
this.updateLatency(message.ts);
const update = getBestBlock(message);
if (update) {
this.updateBestBlock(update);
}
});
socket.on('close', () => {
console.log(`${this.name} has disconnected`);
this.disconnect();
});
socket.on('error', (error) => {
console.error(`${this.name} has errored`, error);
this.disconnect();
});
}
public static fromSocket(socket: WebSocket): Promise<Node> {
return new Promise((resolve, reject) => {
function cleanup() {
clearTimeout(timeout);
socket.removeAllListeners('message');
}
function handler(data: WebSocket.Data) {
const message = parseMessage(data);
if (message && message.msg === "system.connected") {
cleanup();
const { name, config, implementation, version } = message;
resolve(new Node(socket, name, config, implementation, version));
}
}
socket.on('message', handler);
const timeout = setTimeout(() => {
cleanup();
socket.close();
return reject(new Error('Timeout on waiting for system.connected message'));
}, 5000);
});
}
public timeoutCheck(now: number) {
if (this.lastMessage + TIMEOUT < now) {
this.disconnect();
}
}
public nodeInfo(): NodeInfo {
return {
name: this.name,
};
}
public blockInfo(): BlockInfo {
return {
height: this.height,
blockTime: this.blockTime,
};
}
public get average(): number {
let accounted = 0;
let sum = 0;
for (const time of this.blockTimes) {
if (time) {
accounted += 1;
sum += time;
}
}
if (accounted === 0) {
return 0;
}
return sum / accounted;
}
private disconnect() {
this.socket.removeAllListeners();
this.socket.close();
this.emit('disconnect');
}
private updateLatency(time: Date) {
this.latency = this.lastMessage - +time;
}
private updateBestBlock(update: BestBlock) {
const { height, ts: time, best } = update;
if (this.height < height) {
const blockTime = this.getBlockTime(time);
this.height = height;
this.lastBlockAt = time;
this.blockTimes[height % BLOCK_TIME_HISTORY] = blockTime;
this.blockTime = blockTime;
this.emit('block');
}
}
private getBlockTime(time: Date): number {
if (!this.lastBlockAt) {
return 0;
}
return +time - +this.lastBlockAt;
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "build",
},
"include": [
"src/**/*.ts"
],
}