Remove JS backend (#219)

* chore: Remove JS backend

* chore: Update travis to run Rust backend tests

* chore(travis): Install dependencies before building
This commit is contained in:
Maciej Hirsz
2020-01-14 14:04:13 +01:00
committed by GitHub
parent b9d658e2e6
commit 63795e48a0
16 changed files with 30 additions and 2096 deletions
+30 -4
View File
@@ -1,11 +1,37 @@
sudo: false language: rust
language: node_js cache:
cargo: true
rust:
- stable
- beta
- nightly
env:
global:
- CLIPPY_TOOLCHAIN=nightly
matrix: matrix:
include: allow_failures:
- node_js: "10" - rust: nightly
os:
- linux
before_install:
- source $HOME/.nvm/nvm.sh
- nvm install --lts
- nvm use --lts
before_script:
- if [ $TRAVIS_RUST_VERSION = $CLIPPY_TOOLCHAIN ]; then rustup component add clippy-preview --toolchain=$CLIPPY_TOOLCHAIN; fi
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
script: ./test.sh
script: script:
- yarn
- yarn build:all - yarn build:all
- yarn test - yarn test
- cd backend && cargo test
-24
View File
@@ -1,24 +0,0 @@
declare module 'iplocation' {
namespace iplocation {
export interface LocationData {
as?: string;
city: string;
country?: string;
countryCode?: string;
isp?: string;
latitude: number;
longitude: number;
org?: string;
query?: string;
region?: string;
regionName?: string;
status: string;
timezone?: string;
zip?: string;
}
}
function iplocation(ip: string, providers: any[], callback: (err: Error, result: iplocation.LocationData) => void): void;
export = iplocation;
}
-466
View File
@@ -1,466 +0,0 @@
{
"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"
}
}
}
}
-27
View File
@@ -1,27 +0,0 @@
{
"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",
"test": "node ./test | tap-spec"
},
"dependencies": {
"@types/express": "^4.16.0",
"@types/node": "^10.3.3",
"@types/ws": "^5.1.2",
"express": "^4.16.3",
"iplocation": "^6.1.0",
"typescript": "^2.9.2",
"ws": "6.1.2"
},
"devDependencies": {
"tap-spec": "^5.0.0",
"tape": "^4.9.1"
}
}
-101
View File
@@ -1,101 +0,0 @@
import Chain from './Chain';
import Node from './Node';
import Feed from './Feed';
import FeedSet from './FeedSet';
import { Types, FeedMessage, Maybe, timestamp } from '@dotstats/common';
export default class Aggregator {
private readonly chains = new Map<Types.ChainLabel, Chain>();
private readonly feeds = new FeedSet();
constructor() {
setInterval(() => this.timeoutCheck(), 10000);
}
public addNode(node: Node) {
let chain = this.getChain(node.chain);
chain.addNode(node);
this.feeds.broadcast(Feed.addedChain(chain));
}
public addFeed(feed: Feed) {
this.feeds.add(feed);
feed.sendMessage(Feed.feedVersion());
for (const chain of this.chains.values()) {
feed.sendMessage(Feed.addedChain(chain));
}
feed.events.on('subscribe', (label: Types.ChainLabel) => {
const chain = this.chains.get(label);
if (chain) {
feed.sendMessage(Feed.subscribedTo(label));
chain.addFeed(feed);
}
});
feed.events.on('unsubscribe', (label: Types.ChainLabel) => {
const chain = this.chains.get(label);
if (chain) {
chain.removeFeed(feed);
feed.sendMessage(Feed.unsubscribedFrom(label));
}
});
}
public getExistingChain(label: Types.ChainLabel) : Maybe<Chain> {
return this.chains.get(label);
}
private getChain(label: Types.ChainLabel): Chain {
const chain = this.chains.get(label);
if (chain) {
return chain;
} else {
const chain = new Chain(label);
chain.events.on('disconnect', (count: number) => {
if (count !== 0) {
this.feeds.broadcast(Feed.addedChain(chain));
return;
}
chain.events.removeAllListeners();
this.chains.delete(chain.label);
console.log(`Chain: ${label} lost all nodes`);
this.feeds.broadcast(Feed.removedChain(label));
});
this.chains.set(label, chain);
console.log(`New chain: ${label}`);
this.feeds.broadcast(Feed.addedChain(chain));
return chain;
}
}
private timeoutCheck() {
const empty: Types.ChainLabel[] = [];
const now = timestamp();
for (const chain of this.chains.values()) {
chain.timeoutCheck(now);
}
for (const feed of this.feeds.values()) {
feed.ping();
}
}
}
-21
View File
@@ -1,21 +0,0 @@
import { Types } from '@dotstats/common';
export default class Block {
public static readonly ZERO = new Block(0 as Types.BlockNumber, '' as Types.BlockHash);
public readonly number: Types.BlockNumber;
public readonly hash: Types.BlockHash;
constructor(number: Types.BlockNumber, hash: Types.BlockHash) {
this.number = number;
this.hash = hash;
}
gt(other: Block): boolean {
return this.number > other.number;
}
eq(other: Block): boolean {
return this.number === other.number && this.hash === other.hash;
}
}
-223
View File
@@ -1,223 +0,0 @@
import * as EventEmitter from 'events';
import Node from './Node';
import Feed from './Feed';
import FeedSet from './FeedSet';
import Block from './Block';
import { Maybe, Types, NumStats } from '@dotstats/common';
const BLOCK_TIME_HISTORY = 10;
export default class Chain {
private nodes = new Set<Node>();
private feeds = new FeedSet();
private count = 0;
public readonly events = new EventEmitter();
public readonly label: Types.ChainLabel;
public height = 0 as Types.BlockNumber;
public finalized = Block.ZERO;
public blockTimestamp = 0 as Types.Timestamp;
private blockTimes = new NumStats<Types.Milliseconds>(BLOCK_TIME_HISTORY);
private averageBlockTime: Maybe<Types.Milliseconds> = null;
public lastBroadcastedAuthoritySetInfo: Maybe<Types.AuthoritySetInfo> = null;
constructor(label: Types.ChainLabel) {
this.label = label;
}
public get nodeCount(): Types.NodeCount {
return this.nodes.size as Types.NodeCount;
}
public addNode(node: Node) {
console.log(`[${this.label}] new node: ${node.name}`);
this.nodes.add(node);
this.feeds.broadcast(Feed.addedNode(node));
node.events.once('disconnect', () => this.removeNode(node));
node.events.once('stale', () => this.staleNode(node));
node.events.on('block', () => this.updateBlock(node));
node.events.on('finalized', () => this.updateFinalized(node));
node.events.on('afg-finalized', (finalizedNumber, finalizedHash) => this.feeds.each(
f => f.sendConsensusMessage(Feed.afgFinalized(node, finalizedNumber, finalizedHash))
));
node.events.on('afg-received-prevote', (finalizedNumber, finalizedHash, voter) => this.feeds.each(
f => f.sendConsensusMessage(Feed.afgReceivedPrevote(node, finalizedNumber, finalizedHash, voter))
));
node.events.on('afg-received-precommit', (finalizedNumber, finalizedHash, voter) => this.feeds.each(
f => f.sendConsensusMessage(Feed.afgReceivedPrecommit(node, finalizedNumber, finalizedHash, voter))
));
node.events.on('authority-set-changed', (authorities, authoritySetId, blockNumber, blockHash) => {
let newSet;
if (this.lastBroadcastedAuthoritySetInfo == null) {
newSet = true;
} else {
const [lastBroadcastedAuthoritySetId] = this.lastBroadcastedAuthoritySetInfo;
newSet = authoritySetId !== lastBroadcastedAuthoritySetId;
}
if (node.isAuthority() && newSet) {
const addr = node.address != null ? node.address : "" as Types.Address;
const set = [authoritySetId, authorities, addr, blockNumber, blockHash] as Types.AuthoritySetInfo;
this.feeds.broadcast(Feed.afgAuthoritySet(set));
this.lastBroadcastedAuthoritySetInfo = set;
}
});
node.events.on('stats', () => this.feeds.broadcast(Feed.stats(node)));
node.events.on('hardware', () => this.feeds.broadcast(Feed.hardware(node)));
node.events.on('location', (location) => this.feeds.broadcast(Feed.locatedNode(node, location)));
this.updateBlock(node);
this.updateFinalized(node);
}
public removeNode(node: Node) {
node.events.removeAllListeners();
this.nodes.delete(node);
this.feeds.broadcast(Feed.removedNode(node));
this.events.emit('disconnect', this.nodeCount);
if (this.height === node.best.number) {
this.downgradeBlock();
}
}
public staleNode(node: Node) {
node.isStale = true;
this.feeds.broadcast(Feed.staleNode(node));
if (this.height === node.best.number) {
this.downgradeBlock();
}
}
public addFeed(feed: Feed) {
this.feeds.add(feed);
// TODO: this is a bit unclean, find a better way
feed.chain = this.label;
feed.sendMessage(Feed.timeSync());
feed.sendMessage(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
feed.sendMessage(Feed.bestFinalizedBlock(this.finalized));
if (this.lastBroadcastedAuthoritySetInfo != null) {
feed.sendMessage(Feed.afgAuthoritySet(this.lastBroadcastedAuthoritySetInfo));
}
for (const node of this.nodes.values()) {
feed.sendMessage(Feed.addedNode(node));
feed.sendMessage(Feed.finalized(node));
if (node.isStale) {
feed.sendMessage(Feed.staleNode(node));
}
}
}
public removeFeed(feed: Feed) {
this.feeds.remove(feed);
}
public nodeList(): IterableIterator<Node> {
return this.nodes.values();
}
public timeoutCheck(now: Types.Timestamp) {
for (const node of this.nodes.values()) {
node.timeoutCheck(now);
}
this.feeds.broadcast(Feed.timeSync());
}
private updateBlock(node: Node) {
const height = node.best.number;
if (height > this.height) {
// New best block
const { blockTimestamp } = node;
if (this.blockTimestamp) {
this.updateAverageBlockTime(height, blockTimestamp);
}
for (const otherNode of this.nodes) {
otherNode.propagationTime = null;
}
this.height = height;
this.blockTimestamp = blockTimestamp;
node.propagationTime = 0 as Types.PropagationTime;
this.feeds.broadcast(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
console.log(`[${this.label}] New block ${this.height}`);
} else if (height === this.height) {
// Caught up to best block
node.propagationTime = (node.blockTimestamp - this.blockTimestamp) as Types.PropagationTime;
}
if (node.isStale) {
node.isStale = false;
}
this.feeds.broadcast(Feed.imported(node));
console.log(`[${this.label}] ${node.name} imported ${height}, block time: ${node.blockTime / 1000}s, average: ${node.average / 1000}s | latency ${node.latency}`);
}
private downgradeBlock() {
let height = 0 as Types.BlockNumber;
let finalized = Block.ZERO;
for (const node of this.nodes) {
if (node.isStale) {
continue;
}
if (this.height === node.best.number) {
return;
}
if (node.best.number > height) {
height = node.best.number;
}
if (node.finalized.number > finalized.number) {
finalized = node.finalized;
}
}
this.height = height;
this.finalized = finalized;
this.feeds.broadcast(Feed.bestBlock(this.height, this.blockTimestamp, this.averageBlockTime));
this.feeds.broadcast(Feed.bestFinalizedBlock(this.finalized));
}
private updateFinalized(node: Node) {
if (node.finalized.gt(this.finalized)) {
this.finalized = node.finalized;
this.feeds.broadcast(Feed.bestFinalizedBlock(this.finalized));
}
this.feeds.broadcast(Feed.finalized(node));
}
private updateAverageBlockTime(height: Types.BlockNumber, now: Types.Timestamp) {
this.blockTimes.push((now - this.blockTimestamp) as Types.Milliseconds);
// We are guaranteed that count > 0
this.averageBlockTime = this.blockTimes.average();
}
}
-288
View File
@@ -1,288 +0,0 @@
import * as WebSocket from 'ws';
import * as EventEmitter from 'events';
import Node from './Node';
import Chain from './Chain';
import Block from './Block';
import { VERSION, timestamp, Maybe, FeedMessage, Types, idGenerator } from '@dotstats/common';
import { Location } from './location';
const nextId = idGenerator<Types.FeedId>();
const { Actions } = FeedMessage;
export default class Feed {
public id: Types.FeedId;
public chain: Maybe<Types.ChainLabel> = null;
public readonly events = new EventEmitter();
private socket: WebSocket;
private messages: Array<FeedMessage.Message> = [];
private waitingForPong = false;
private sendFinality = false;
constructor(socket: WebSocket) {
this.id = nextId();
this.socket = socket;
socket.on('message', this.handleCommand);
socket.on('error', this.disconnect);
socket.on('close', this.disconnect);
socket.on('pong', this.onPong);
}
public static feedVersion(): FeedMessage.Message {
return {
action: Actions.FeedVersion,
payload: VERSION
};
}
public static bestBlock(height: Types.BlockNumber, ts: Types.Timestamp, avg: Maybe<Types.Milliseconds>): FeedMessage.Message {
return {
action: Actions.BestBlock,
payload: [height, ts, avg]
};
}
public static bestFinalizedBlock(block: Block): FeedMessage.Message {
return {
action: Actions.BestFinalized,
payload: [block.number, block.hash]
};
}
public static addedNode(node: Node): FeedMessage.Message {
return {
action: Actions.AddedNode,
payload: [node.id, node.nodeDetails(), node.nodeStats(), node.nodeHardware(), node.blockDetails(), node.nodeLocation()]
};
}
public static removedNode(node: Node): FeedMessage.Message {
return {
action: Actions.RemovedNode,
payload: node.id
};
}
public static staleNode(node: Node): FeedMessage.Message {
return {
action: Actions.StaleNode,
payload: node.id
}
}
public static locatedNode(node: Node, location: Location): FeedMessage.Message {
return {
action: Actions.LocatedNode,
payload: [node.id, location.lat, location.lon, location.city]
};
}
public static imported(node: Node): FeedMessage.Message {
return {
action: Actions.ImportedBlock,
payload: [node.id, node.blockDetails()]
};
}
public static finalized(node: Node): FeedMessage.Message {
return {
action: Actions.FinalizedBlock,
payload: [node.id, node.finalized.number, node.finalized.hash]
};
}
public static stats(node: Node): FeedMessage.Message {
return {
action: Actions.NodeStats,
payload: [node.id, node.nodeStats()]
};
}
public static afgFinalized(node: Node, finalizedNumber: Types.BlockNumber, finalizedHash: Types.BlockHash): FeedMessage.Message {
const addr = node.address != null ? node.address : "" as Types.Address;
return {
action: Actions.AfgFinalized,
payload: [addr, finalizedNumber, finalizedHash]
};
}
public static afgReceivedPrevote(
node: Node,
targetNumber: Types.BlockNumber,
targetHash: Types.BlockHash,
voter: Types.Address
): FeedMessage.Message {
const addr = node.address != null ? node.address : "" as Types.Address;
return {
action: Actions.AfgReceivedPrevote,
payload: [addr, targetNumber, targetHash, voter]
};
}
public static afgReceivedPrecommit(
node: Node,
targetNumber: Types.BlockNumber,
targetHash: Types.BlockHash,
voter: Types.Address
): FeedMessage.Message {
const addr = node.address != null ? node.address : "" as Types.Address;
return {
action: Actions.AfgReceivedPrecommit,
payload: [addr, targetNumber, targetHash, voter]
};
}
public static afgAuthoritySet(
authoritySetInfo: Types.AuthoritySetInfo,
): FeedMessage.Message {
return {
action: Actions.AfgAuthoritySet,
payload: authoritySetInfo,
};
}
public static hardware(node: Node): FeedMessage.Message {
return {
action: Actions.NodeHardware,
payload: [node.id, node.nodeHardware()]
};
}
public static timeSync(): FeedMessage.Message {
return {
action: Actions.TimeSync,
payload: timestamp()
};
}
public static addedChain(chain: Chain): FeedMessage.Message {
return {
action: Actions.AddedChain,
payload: [chain.label, chain.nodeCount]
};
}
public static removedChain(label: Types.ChainLabel): FeedMessage.Message {
return {
action: Actions.RemovedChain,
payload: label
};
}
public static subscribedTo(label: Types.ChainLabel): FeedMessage.Message {
return {
action: Actions.SubscribedTo,
payload: label
};
}
public static unsubscribedFrom(label: Types.ChainLabel): FeedMessage.Message {
return {
action: Actions.UnsubscribedFrom,
payload: label
};
}
public static pong(payload: string): FeedMessage.Message {
return {
action: Actions.Pong,
payload
};
}
public sendData(data: FeedMessage.Data) {
this.socket.send(data, this.handleError);
}
public sendMessage(message: FeedMessage.Message) {
const queue = this.messages.length === 0;
this.messages.push(message);
if (queue) {
process.nextTick(this.sendMessages);
}
}
public sendConsensusMessage(message: FeedMessage.Message) {
if (!this.sendFinality) {
return;
}
this.sendMessage(message);
}
public ping() {
if (this.waitingForPong) {
this.disconnect();
return;
}
this.waitingForPong = true;
this.socket.ping(this.handleError);
}
private sendMessages = () => {
const data = FeedMessage.serialize(this.messages);
this.messages = [];
this.socket.send(data, this.handleError);
}
private handleCommand = (data: WebSocket.Data) => {
const [tag, payload] = data.toString().split(':', 2) as [string, Maybe<string>];
if (!payload) {
return;
}
switch (tag) {
case 'subscribe':
if (this.chain) {
this.events.emit('unsubscribe', this.chain);
this.chain = null;
}
this.events.emit('subscribe', payload as Types.ChainLabel);
break;
case 'send-finality':
this.sendFinality = true;
break;
case 'no-more-finality':
this.sendFinality = false;
break;
case 'ping':
this.sendMessage(Feed.pong(payload));
break;
default:
console.error('Unknown command tag:', tag);
}
}
private handleError = (err: Maybe<Error>) => {
if (err) {
console.error('Error when sending data to the socket', err);
this.disconnect();
}
}
private disconnect = () => {
this.socket.removeListener('message', this.handleCommand);
this.socket.removeListener('error', this.disconnect);
this.socket.removeListener('close', this.disconnect);
this.socket.removeListener('pong', this.onPong);
this.socket.terminate();
this.events.emit('disconnect');
}
private onPong = () => {
this.waitingForPong = false;
}
}
-62
View File
@@ -1,62 +0,0 @@
import Feed from './Feed';
import { FeedMessage } from '@dotstats/common';
type DisconnectListener = () => void;
export default class FeedSet {
private feeds = new Map<Feed, DisconnectListener>();
private messages: Array<FeedMessage.Message> = [];
public values(): IterableIterator<Feed> {
return this.feeds.keys();
}
public each(fn: (feed: Feed) => void) {
for (const feed of this.values()) {
fn(feed);
}
}
public add(feed: Feed) {
const listener = () => this.remove(feed);
this.feeds.set(feed, listener);
feed.events.once('disconnect', listener);
}
public remove(feed: Feed) {
const listener = this.feeds.get(feed);
if (!listener) {
return;
}
feed.events.removeListener('disconnect', listener);
this.feeds.delete(feed);
}
public broadcast(message: FeedMessage.Message) {
const queue = this.messages.length === 0;
this.messages.push(message);
if (queue) {
process.nextTick(this.sendMessages);
}
}
private sendMessages = () => {
const data = FeedMessage.serialize(this.messages);
this.messages = [];
this.each(feed => {
try {
feed.sendData(data);
} catch (err) {
console.error("Failed to broadcast to feed", err);
}
});
}
}
-71
View File
@@ -1,71 +0,0 @@
import { Maybe, Types, timestamp } from '@dotstats/common';
export default class MeanList<T extends number> {
private periodCount = 0;
private periodSum = 0;
private meanIndex = 0;
private means = Array<T>(20).fill(0 as T);
private ticksPerMean = 1;
/**
* Push a new value, returns true if a new mean value was produced
*
* @param {T} value
*
* @return {boolean}
*/
public push(val: Maybe<T>): boolean {
if (val == null) {
return false;
}
if (this.meanIndex === 20 && this.ticksPerMean < 32) {
this.squashMeans();
}
this.periodSum += val as number;
this.periodCount += 1;
if (this.periodCount === this.ticksPerMean) {
this.pushMean();
return true;
}
return false;
}
public get(): Array<T> {
if (this.meanIndex === 20) {
return this.means;
} else {
return this.means.slice(0, this.meanIndex);
}
}
private pushMean() {
const mean = (this.periodSum / this.periodCount) as T;
if (this.meanIndex === 20 && this.ticksPerMean === 32) {
this.means.copyWithin(0, 1);
this.means[19] = mean;
} else {
this.means[this.meanIndex++] = mean;
}
this.periodSum = 0;
this.periodCount = 0;
}
private squashMeans() {
this.ticksPerMean *= 2;
const means = this.means;
for (let i = 0; i < 10; i++) {
let i2 = i * 2;
means[i] = (((means[i2] as number) + (means[i2 + 1] as number)) / 2) as T;
}
this.meanIndex = 10;
}
}
-433
View File
@@ -1,433 +0,0 @@
import * as WebSocket from 'ws';
import * as EventEmitter from 'events';
import { noop, timestamp, idGenerator, Maybe, Types, NumStats } from '@dotstats/common';
import { BlockHash, BlockNumber, ConsensusView } from "@dotstats/common/build/types";
import {
parseMessage,
getBestBlock,
Message,
BestBlock,
SystemInterval,
SystemNetworkState,
AfgFinalized,
AfgReceivedPrecommit,
AfgReceivedPrevote,
AfgAuthoritySet,
} from './message';
import { locate, Location } from './location';
import MeanList from './MeanList';
import Block from './Block';
const BLOCK_TIME_HISTORY = 10;
const MEMORY_RECORDS = 20;
const CPU_RECORDS = 20;
const TIMEOUT = (1000 * 60 * 1) as Types.Milliseconds; // 1 minute
const NO_BLOCK_TIMEOUT = (1000 * 60 * 1) as Types.Milliseconds; // 1 minute
const nextId = idGenerator<Types.NodeId>();
export interface NodeEvents {
on(event: 'location', fn: (location: Location) => void): void;
emit(event: 'location', location: Location): void;
}
export default class Node {
public readonly id: Types.NodeId;
public readonly name: Types.NodeName;
public readonly chain: Types.ChainLabel;
public readonly implementation: Types.NodeImplementation;
public readonly version: Types.NodeVersion;
public readonly networkId: Maybe<Types.NetworkId>;
public readonly authority: boolean;
public readonly events = new EventEmitter() as EventEmitter & NodeEvents;
public address: Maybe<Types.Address> = null;
public networkState: Maybe<Types.NetworkState> = null;
public location: Maybe<Location> = null;
public lastMessage: Types.Timestamp;
public config: string;
public best = Block.ZERO;
public finalized = Block.ZERO;
public latency = 0 as Types.Milliseconds;
public blockTime = 0 as Types.Milliseconds;
public blockTimestamp = 0 as Types.Timestamp;
public propagationTime: Maybe<Types.PropagationTime> = null;
public isStale = false;
private peers = 0 as Types.PeerCount;
private txcount = 0 as Types.TransactionCount;
private memory = new MeanList<Types.MemoryUse>();
private cpu = new MeanList<Types.CPUUse>();
private upload = new MeanList<Types.BytesPerSecond>();
private download = new MeanList<Types.BytesPerSecond>();
private chartstamps = new MeanList<Types.Timestamp>();
private readonly ip: string;
private readonly socket: WebSocket;
private blockTimes = new NumStats<Types.Milliseconds>(BLOCK_TIME_HISTORY);
private lastBlockAt: Maybe<Date> = null;
private pingStart = 0 as Types.Timestamp;
private throttle = false;
private authorities: Types.Authorities = [] as Types.Authorities;
private authoritySetId: Types.AuthoritySetId = 0 as Types.AuthoritySetId;
constructor(
ip: string,
socket: WebSocket,
name: Types.NodeName,
chain: Types.ChainLabel,
config: string,
implentation: Types.NodeImplementation,
version: Types.NodeVersion,
networkId: Maybe<Types.NetworkId>,
authority: boolean,
messages: Array<Message>,
) {
this.ip = ip;
this.id = nextId();
this.name = name;
this.chain = chain;
this.config = config;
this.implementation = implentation;
this.version = version;
this.authority = authority;
this.networkId = networkId;
this.lastMessage = timestamp();
this.socket = socket;
socket.on('message', this.onMessageData);
socket.on('close', this.disconnect);
socket.on('error', this.disconnect);
socket.on('pong', this.onPong);
process.nextTick(() => {
// Handle cached messages
for (const message of messages) {
this.onMessage(message);
}
});
locate(ip).then((location) => {
if (!location) {
return;
}
this.location = location;
this.events.emit('location', location);
});
}
public static fromSocket(socket: WebSocket, ip: string): Promise<Node> {
return new Promise((resolve, reject) => {
function cleanup() {
clearTimeout(timeout);
socket.removeAllListeners('message');
}
const messages: Array<Message> = [];
function handler(data: WebSocket.Data) {
const message = parseMessage(data);
if (!message || !message.msg) {
return;
}
if (message.msg === "system.connected") {
cleanup();
const { name, chain, config, implementation, version, authority, network_id: networkId } = message;
resolve(new Node(ip, socket, name, chain, config, implementation, version, networkId, authority === true, messages));
} else {
if (messages.length === 10) {
messages.shift();
}
messages.push(message);
}
}
socket.on('message', handler);
const timeout = setTimeout(() => {
cleanup();
socket.close();
socket.terminate();
return reject(new Error('Timeout on waiting for system.connected message'));
}, 5000);
});
}
public timeoutCheck(now: Types.Timestamp) {
if (this.lastMessage + TIMEOUT < now) {
this.disconnect();
} else {
if (!this.isStale && this.blockTimestamp + NO_BLOCK_TIMEOUT < now) {
this.events.emit('stale');
}
this.updateLatency(now);
}
}
public nodeDetails(): Types.NodeDetails {
const authority = this.authority ? this.address : null;
const addr = this.address ? this.address : '' as Types.Address;
return [this.name, this.implementation, this.version, authority, this.networkId, addr];
}
public nodeStats(): Types.NodeStats {
return [this.peers, this.txcount];
}
public nodeHardware(): Types.NodeHardware {
return [this.memory.get(), this.cpu.get(), this.upload.get(), this.download.get(), this.chartstamps.get()];
}
public blockDetails(): Types.BlockDetails {
return [this.best.number, this.best.hash, this.blockTime, this.blockTimestamp, this.propagationTime];
}
public nodeLocation(): Maybe<Types.NodeLocation> {
const { location } = this;
return location ? [location.lat, location.lon, location.city] : null;
}
public get average(): Types.Milliseconds {
return this.blockTimes.average();
}
public get localBlockAt(): Types.Milliseconds {
if (!this.lastBlockAt) {
return 0 as Types.Milliseconds;
}
return +(this.lastBlockAt || 0) as Types.Milliseconds;
}
private disconnect = () => {
console.log(`${this.name} has disconnected`);
this.socket.removeListener('message', this.onMessageData);
this.socket.removeListener('close', this.disconnect);
this.socket.removeListener('error', this.disconnect);
this.socket.removeListener('pong', this.onPong);
this.socket.close();
this.socket.terminate();
this.events.emit('disconnect');
}
private onMessageData = (data: WebSocket.Data) => {
const message = parseMessage(data);
if (!message) {
return;
}
this.onMessage(message);
}
private onMessage(message: Message) {
this.lastMessage = timestamp();
const update = getBestBlock(message);
if (update) {
this.updateBestBlock(update);
}
if (message.msg === 'system.interval') {
this.onSystemInterval(message);
}
if (message.msg === 'system.network_state') {
this.onSystemNetworkState(message);
}
if (message.msg === 'afg.finalized') {
this.onAfgFinalized(message);
}
if (message.msg === 'afg.received_precommit') {
this.onAfgReceivedPrecommit(message);
}
if (message.msg === 'afg.received_prevote') {
this.onAfgReceivedPrevote(message);
}
if (message.msg === 'afg.authority_set') {
this.onAfgAuthoritySet(message);
}
}
private onSystemInterval(message: SystemInterval) {
const {
network_state,
peers,
txcount,
cpu,
memory,
bandwidth_download: download,
bandwidth_upload: upload,
finalized_height: finalized,
finalized_hash: finalizedHash
} = message;
if (this.networkState !== network_state && network_state) {
this.networkState = network_state;
};
if (this.peers !== peers || this.txcount !== txcount) {
this.peers = peers;
this.txcount = txcount;
this.events.emit('stats');
}
if (finalized != null && finalizedHash != null && finalized > this.finalized.number) {
this.finalized = new Block(finalized, finalizedHash);
this.events.emit('finalized');
}
if (cpu != null && memory != null) {
const cpuChange = this.cpu.push(cpu);
const memChange = this.memory.push(memory);
const uploadChange = this.upload.push(upload);
const downloadChange = this.download.push(download);
const stampChange = this.chartstamps.push(timestamp());
if (cpuChange || memChange || uploadChange || downloadChange || stampChange) {
this.events.emit('hardware');
}
}
}
private onSystemNetworkState(message: SystemNetworkState) {
this.networkState = message.state;
}
public isAuthority(): boolean {
return this.authority;
}
private onAfgReceivedPrecommit(message: AfgReceivedPrecommit) {
const {
target_number: targetNumber,
target_hash: targetHash,
} = message;
const voter = this.extractVoter(message.voter);
const number = parseInt(String(targetNumber), 10) as Types.BlockNumber;
this.events.emit('afg-received-precommit', number, targetHash, voter);
}
private onAfgReceivedPrevote(message: AfgReceivedPrevote) {
const {
target_number: targetNumber,
target_hash: targetHash,
} = message;
const voter = this.extractVoter(message.voter);
const number = parseInt(String(targetNumber), 10) as Types.BlockNumber;
this.events.emit('afg-received-prevote', number, targetHash, voter);
}
private onAfgAuthoritySet(message: AfgAuthoritySet) {
const {
authority_id: authorityId,
authority_set_id: authoritySetId,
hash,
number,
} = message;
// we manually parse the authorities message, because the array was formatted as a
// string by substrate before sending it.
const authorities = JSON.parse(String(message.authorities)) as Types.Authorities;
this.address = authorityId;
if (JSON.stringify(this.authorities) !== String(message.authorities) ||
this.authoritySetId !== authoritySetId) {
const no = parseInt(String(number), 10) as Types.BlockNumber;
this.events.emit('authority-set-changed', authorities, authoritySetId, no, hash);
}
}
private onAfgFinalized(message: AfgFinalized) {
const {
finalized_number: finalizedNumber,
finalized_hash: finalizedHash,
} = message;
const number = parseInt(String(finalizedNumber), 10) as Types.BlockNumber;
this.events.emit('afg-finalized', number, finalizedHash);
}
private extractVoter(message_voter: String): Types.Address {
return String(message_voter.replace(/"/g, '')) as Types.Address;
}
private updateLatency(now: Types.Timestamp) {
// if (this.pingStart) {
// console.error(`${this.name} timed out on ping message.`);
// this.disconnect();
// return;
// }
this.pingStart = now;
try {
this.socket.ping(noop);
} catch (err) {
console.error('Failed to send ping to Node', err);
this.disconnect();
}
}
private updateBestBlock(update: BestBlock) {
const { height, ts: time, best } = update;
if (this.best.hash !== best && this.best.number <= height) {
const blockTime = this.getBlockTime(time);
this.best = new Block(height, best);
this.blockTimestamp = timestamp();
this.lastBlockAt = time;
this.blockTimes.push(blockTime);
this.blockTime = blockTime;
if (blockTime > 100) {
this.events.emit('block');
} else if (!this.throttle) {
this.throttle = true;
setTimeout(() => {
this.events.emit('block');
this.throttle = false;
}, 1000);
}
}
}
private getBlockTime(time: Date): Types.Milliseconds {
if (!this.lastBlockAt) {
return 0 as Types.Milliseconds;
}
return (+time - +this.lastBlockAt) as Types.Milliseconds;
}
private onPong = () => {
this.latency = (timestamp() - this.pingStart) as Types.Milliseconds;
this.pingStart = 0 as Types.Timestamp;
}
}
-77
View File
@@ -1,77 +0,0 @@
import * as http from 'http';
import * as url from 'url';
import * as WebSocket from 'ws';
import Node from './Node';
import Feed from './Feed';
import Aggregator from './Aggregator';
import {Types} from '@dotstats/common';
const WS_PORT_TELEMETRY_SERVER = Number(process.env.TELEMETRY_SERVER || 1024);
const WS_PORT_FEED_SERVER = Number(process.env.FEED_SERVER || 8080);
const aggregator = new Aggregator();
// WebSocket for Nodes feeding telemetry data to the server
const incomingTelemetry = new WebSocket.Server({ port: WS_PORT_TELEMETRY_SERVER });
// WebSocket for web clients listening to the telemetry data aggregate
const telemetryFeed = new WebSocket.Server({ port: WS_PORT_FEED_SERVER });
console.log(`Telemetry server listening on port ${WS_PORT_TELEMETRY_SERVER}`);
console.log(`Feed server listening on port ${WS_PORT_FEED_SERVER}`);
const ipv4 = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
incomingTelemetry.on('connection', async (socket, req) => {
try {
const [ ip ] = (req.headers['x-forwarded-for'] || req.connection.remoteAddress || '0.0.0.0')
.toString()
.match(ipv4) || ['0.0.0.0'];
const node = await Node.fromSocket(socket, ip);
aggregator.addNode(node);
} catch (err) {
console.error(err);
}
});
function logClients() {
const feed = telemetryFeed.clients.size;
const node = incomingTelemetry.clients.size;
console.log(`[System] ${node} open telemetry connections; ${feed} open feed connections`);
setTimeout(logClients, 5000);
}
logClients();
telemetryFeed.on('connection', (socket: WebSocket) => {
aggregator.addFeed(new Feed(socket));
});
http.createServer((request, response) => {
const incoming_url = request.url || "";
const parsed_url = url.parse(incoming_url, true);
const path = decodeURI(parsed_url.path || "");
if (path.startsWith("/network_state/")) {
const [chainLabel, strNodeId] = path.split('/').slice(2);
const chain = aggregator.getExistingChain(chainLabel as Types.ChainLabel);
if (chain) {
const nodeList = Array.from(chain.nodeList());
const nodeId = Number(strNodeId);
const node = nodeList.filter((node) => node.id == nodeId)[0];
if (node && node.networkState) {
const { networkState } = node;
response.writeHead(200, {"Content-Type": "application/json"});
response.write(typeof networkState === 'string' ? networkState : JSON.stringify(networkState));
} else {
response.writeHead(404, {"Content-Type": "text/plain"});
response.write("Node has disconnected or has not submitted its network state yet");
}
}
}
response.end();
}).listen(8081);
-45
View File
@@ -1,45 +0,0 @@
import iplocation from 'iplocation';
import { Maybe, Types } from '@dotstats/common';
export interface Location {
lat: Types.Latitude;
lon: Types.Longitude;
city: Types.City;
}
const cache = new Map<string, Maybe<Location>>();
export async function locate(ip: string): Promise<Maybe<Location>> {
if (ip === '127.0.0.1') {
return Promise.resolve({
lat: 52.5166667 as Types.Latitude,
lon: 13.4 as Types.Longitude,
city: 'Berlin' as Types.City,
});
}
if (cache.has(ip)) {
return Promise.resolve(cache.get(ip));
}
const cached = cache.get(ip);
return new Promise<Maybe<Location>>((resolve, _) => {
iplocation(ip, [], (err, result) => {
if (err) {
console.error(`Couldn't locate ${ip}`);
cache.set(ip, null);
return resolve(null);
}
const { latitude: lat, longitude: lon, city } = result;
const location = { lat, lon, city } as Location;
cache.set(ip, location);
resolve(location);
});
})
}
-134
View File
@@ -1,134 +0,0 @@
import { Data } from 'ws';
import { Maybe, Types } 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: Types.BlockHash;
height: Types.BlockNumber;
ts: Date;
}
export interface AfgFinalized {
ts: Date;
finalized_number: Types.BlockNumber;
finalized_hash: Types.BlockHash;
msg: 'afg.finalized';
}
export interface AfgReceived {
ts: Date;
target_number: Maybe<Types.BlockNumber>;
target_hash: Maybe<Types.BlockHash>;
voter: Types.Address;
}
export interface AfgReceivedPrecommit extends AfgReceived {
msg: 'afg.received_precommit';
}
export interface AfgReceivedPrevote extends AfgReceived {
msg: 'afg.received_prevote';
}
export interface AfgReceivedCommit extends AfgReceived {
msg: 'afg.received_commit';
}
export interface AfgAuthoritySet {
msg: 'afg.authority_set';
ts: Date;
authority_id: Types.Address,
authorities: Types.Authorities;
authority_set_id: Types.AuthoritySetId;
number: Types.BlockNumber;
hash: Types.BlockHash;
}
export interface SystemConnected {
msg: 'system.connected';
name: Types.NodeName;
chain: Types.ChainLabel;
config: string;
implementation: Types.NodeImplementation;
version: Types.NodeVersion;
authority: Maybe<boolean>;
network_id: Maybe<Types.NetworkId>;
}
export interface SystemInterval extends BestBlock {
msg: 'system.interval';
network_state: Maybe<Types.NetworkState>;
txcount: Types.TransactionCount;
peers: Types.PeerCount;
memory: Maybe<Types.MemoryUse>;
cpu: Maybe<Types.CPUUse>;
status: 'Idle' | string; // TODO: 'Idle' | ...?
bandwidth_upload: Maybe<Types.BytesPerSecond>;
bandwidth_download: Maybe<Types.BytesPerSecond>;
finalized_height: Maybe<Types.BlockNumber>;
finalized_hash: Maybe<Types.BlockHash>;
}
export interface SystemNetworkState extends MessageBase {
msg: 'system.network_state';
state: Types.NetworkState;
}
export interface NodeStart extends BestBlock {
msg: 'node.start';
}
export interface BlockImport extends BestBlock {
msg: 'block.import';
}
// Union type
export type Message = MessageBase & (
| SystemConnected
| SystemInterval
| SystemNetworkState
| NodeStart
| BlockImport
| AfgFinalized
| AfgReceivedPrecommit
| AfgReceivedPrevote
| AfgReceivedCommit
| AfgAuthoritySet
);
// 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"}
-110
View File
@@ -1,110 +0,0 @@
const test = require('tape');
const MeanList = require('../build/MeanList').default;
test('MeanList', (assert) => {
let list = new MeanList();
assert.same(list.get(), [], 'Inits empty');
list.push(0);
assert.same(list.get(), [0], 'Stores the first value');
list.push(1);
list.push(2);
list.push(3);
list.push(4);
list.push(5);
list.push(6);
list.push(7);
list.push(8);
list.push(9);
assert.same(list.get(), [0,1,2,3,4,5,6,7,8,9], 'Stores the first 10 values');
list.push(10);
list.push(11);
list.push(12);
list.push(13);
list.push(14);
list.push(15);
list.push(16);
list.push(17);
list.push(18);
list.push(19);
assert.same(list.get(), [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19], 'Stores the first 20 values');
list.push(20);
assert.same(list.get(), [0.5,2.5,4.5,6.5,8.5,10.5,12.5,14.5,16.5,18.5], 'Squashes values on 21st entry');
list.push(21);
assert.same(list.get(), [0.5,2.5,4.5,6.5,8.5,10.5,12.5,14.5,16.5,18.5,20.5], 'Adds a mean on 22nd entry');
list.push(22);
assert.same(list.get(), [0.5,2.5,4.5,6.5,8.5,10.5,12.5,14.5,16.5,18.5,20.5], 'Keeps track of 23rd entry internally');
list.push(23);
assert.same(list.get(), [0.5,2.5,4.5,6.5,8.5,10.5,12.5,14.5,16.5,18.5,20.5,22.5], 'Adds a mean on 24th entry');
list.push(24);
list.push(25);
list.push(26);
list.push(27);
list.push(28);
list.push(29);
list.push(30);
list.push(31);
list.push(32);
list.push(33);
list.push(34);
list.push(35);
list.push(36);
list.push(37);
list.push(38);
list.push(39);
assert.same(list.get(), [
0.5, 2.5, 4.5, 6.5, 8.5, 10.5, 12.5, 14.5, 16.5, 18.5,
20.5, 22.5, 24.5, 26.5, 28.5, 30.5, 32.5, 34.5, 36.5, 38.5
], 'Adds means up to 40th entry');
list.push(40);
assert.same(list.get(), [
1.5, 5.5, 9.5, 13.5, 17.5, 21.5, 25.5, 29.5, 33.5, 37.5,
], 'Squashes values on 41st entry');
list = new MeanList();
for (var i = 0; i < 640; i++) {
list.push(i);
}
assert.same(list.get(), [
15.5, 47.5, 79.5, 111.5, 143.5, 175.5, 207.5, 239.5, 271.5, 303.5,
335.5, 367.5, 399.5, 431.5, 463.5, 495.5, 527.5, 559.5, 591.5, 623.5
], 'Squashes values up to 32 degrees');
for (var i = 0; i < 31; i++) {
list.push(i);
}
assert.same(list.get(), [
15.5, 47.5, 79.5, 111.5, 143.5, 175.5, 207.5, 239.5, 271.5, 303.5,
335.5, 367.5, 399.5, 431.5, 463.5, 495.5, 527.5, 559.5, 591.5, 623.5
], 'Keeps track of 31 entries internally');
list.push(31);
assert.same(list.get(), [
47.5, 79.5, 111.5, 143.5, 175.5, 207.5, 239.5, 271.5, 303.5, 335.5,
367.5, 399.5, 431.5, 463.5, 495.5, 527.5, 559.5, 591.5, 623.5, 15.5
], 'Pushes a new mean on 32nd value');
assert.end();
});
-10
View File
@@ -1,10 +0,0 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "build"
},
"include": [
"./src/**/*.ts",
"./declarations/**/*.d.ts"
]
}