diff --git a/src/index.ts b/src/index.ts index 4988ebc..2d22256 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,76 +1,8 @@ import * as WebSocket from 'ws'; +import Node from './node'; const wss = new WebSocket.Server({ port: 1024 }); wss.on('connection', async (socket: WebSocket) => { - await Node.fromSocket(socket); + await Node.fromSocket(socket); }); - -type Level = "INFO" | "WARN"; - -interface SystemConnected { - msg: "system.connected", - name: string, - ts: string, - chain: string, - config: string, - implementation: string, - version: string, -} - -type Message = SystemConnected; - -class Node { - private socket: WebSocket; - private name: string; - private config: string; - private implementation: string; - private version: string; - - constructor(socket: WebSocket, name: string, config: string, implentation: string, version: string) { - this.socket = socket; - this.name = name; - this.config = config; - this.implementation = implentation; - this.version = version; - - console.log(`Started listening to a new node: ${name}`); - - socket.on('message', (message: WebSocket.Data) => { - console.log('received: %s', message); - }); - } - - static fromSocket(socket: WebSocket): Promise { - return new Promise((resolve, reject) => { - function handler(msg: WebSocket.Data) { - let message: Message; - - try { - message = JSON.parse(msg.toString()); - } catch (err) { - socket.removeEventListener('message'); - - return reject(err); - } - - if (message.msg === "system.connected") { - socket.removeEventListener('message'); - - const { name, config, implementation, version } = message; - - resolve(new Node(socket, name, config, implementation, version)); - } - } - - socket.on('message', handler); - - // TODO: timeout - }); - } -} - - -// 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"} diff --git a/src/maybe.ts b/src/maybe.ts new file mode 100644 index 0000000..fc44aea --- /dev/null +++ b/src/maybe.ts @@ -0,0 +1 @@ +export type Maybe = T | null | undefined; diff --git a/src/message.ts b/src/message.ts new file mode 100644 index 0000000..7142bc3 --- /dev/null +++ b/src/message.ts @@ -0,0 +1,76 @@ +import { Data } from 'ws'; +import { Maybe } from './maybe'; + +export function parseMessage(data: Data): Maybe { + try { + const message = JSON.parse(data.toString()); + + if (message && typeof message.msg === 'string') { + return message; + } + } catch (_) { + console.warn('Error parsing message JSON'); + } + + return null; +} + +export function getBestBlock(message: Message): Maybe { + switch (message.msg) { + case 'node.start': + case 'system.interval': + case 'block.import': + return message; + default: + return null; + } +} + +interface MessageBase { + ts: string, // Timestamp + level: 'INFO' | 'WARN', +} + +export interface BestBlock { + best: string, + height: number, + ts: string, +} + +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"} diff --git a/src/node.ts b/src/node.ts new file mode 100644 index 0000000..5188c58 --- /dev/null +++ b/src/node.ts @@ -0,0 +1,78 @@ +import * as WebSocket from 'ws'; +import { parseMessage, getBestBlock, Message, BestBlock } from './message'; + +export default class Node { + private socket: WebSocket; + private name: string; + private config: string; + private implementation: string; + private version: string; + private height: number = 0; + + constructor(socket: WebSocket, name: string, config: string, implentation: string, version: string) { + 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: WebSocket.Data) => { + const message = parseMessage(data); + + if (!message) return; + + // console.log('received', message); + + const update = getBestBlock(message); + + if (update) { + this.updateBestBlock(update); + } + }); + } + + updateBestBlock(update: BestBlock) { + if (this.height < update.height) { + this.height = update.height; + + console.log(`Best block for ${this.name} is ${this.height}`); + } + } + + static fromSocket(socket: WebSocket): Promise { + return new Promise((resolve, reject) => { + function cleanup() { + clearTimeout(timeout); + socket.removeEventListener('message'); + } + + function handler(data: WebSocket.Data) { + const message = parseMessage(data); + + if (!message) { + cleanup(); + + return reject(new Error('Invalid message')); + } + + if (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(); + + return reject(new Error('Timeout on waiting for system.connected message')); + }, 5000); + }); + } +} diff --git a/tsconfig.json b/tsconfig.json index 2d7f96b..f9d94c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,17 @@ { "compilerOptions": { - "target": "es6", + "target": "es2017", "module": "commonjs", "outDir": "dist", - "sourceMap": true + "strictNullChecks": true, + "sourceMap": true, + "moduleResolution": "node", + "noEmitOnError": false, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + "pretty": true, + "noErrorTruncation": true, + "noImplicitAny": true }, "files": [ "./node_modules/@types/node/index.d.ts",