UNPKG

dht-relay-wss

Version:

hyperswarm-dht-relay websocket server with some instrumentation

124 lines (97 loc) 3.26 kB
const { relay } = require('@hyperswarm/dht-relay') const Stream = require('@hyperswarm/dht-relay/ws') const safetyCatch = require('safety-catch') const websocketPlugin = require('@fastify/websocket') const ReadyResource = require('ready-resource') class DhtRelayWss extends ReadyResource { constructor (app, dht, { sShutdownMargin = 5, wsPort = 0, wsHost = 'localhost' } = {}) { super() this.app = app this.dht = dht this.wsPort = wsPort this.wsHost = wsHost this.sShutdownMargin = sShutdownMargin this.httpAddress = null this.wsAddress = null this._setupWsServer() } async _open () { await this.dht.ready() this.httpAddress = await this.app.listen({ port: this.wsPort, host: this.wsHost }) this.wsAddress = this.httpAddress.replace('http', 'ws') } async _close () { await this.app.close() await this.dht.destroy() } _setupWsServer () { // hack to pass our scope on to closeWssServerConnections // (fastify forces a new scope when registering a plugin) const self = this this.app.register(websocketPlugin, { preClose: async function wssPreClose () { await closeWsServerConnections(this.websocketServer, self) }, options: { clientTracking: true }, connectionOptions: { readableObjectMode: true // See https://github.com/fastify/fastify-websocket/issues/185 } }) this.app.register(async (app) => { app.get('/', { websocket: true }, this._connHandler.bind(this)) }) } async _connHandler (connection, req) { const socket = connection.socket const ip = req.socket.remoteAddress const port = req.socket.remotePort const id = `${ip}:${port}` socket.on('error', (error) => { this.emit('conn-error', { id, error }) }) socket.on('close', () => { this.emit('conn-close', { id }) }) relay(this.dht, new Stream(false, socket)) this.emit('conn-open', { id }) socket.send('You are being relayed') } } async function closeWsServerConnections (wsServer, dhtRelay) { const sShutdownMargin = dhtRelay.sShutdownMargin const closeProm = new Promise(resolve => wsServer.close(resolve)) closeProm.catch(safetyCatch) dhtRelay.emit('ws-closing-signal', { nrClients: wsServer.clients.size, sShutdownMargin }) if (wsServer.clients.size > 0 && sShutdownMargin > 0) { for (const socket of wsServer.clients) { socket.send(`Server closing. Socket will shut down in ${sShutdownMargin}s`) } } let timeout = null let resolveTimeoutProm = null const timeoutProm = new Promise(resolve => { resolveTimeoutProm = resolve timeout = setTimeout(resolve, sShutdownMargin * 1000) }) await Promise.race([ timeoutProm, closeProm // If all connections close before the timeout ]) clearTimeout(timeout) resolveTimeoutProm() const nrRemainingClients = wsServer.clients.size if (nrRemainingClients) { dhtRelay.emit('ws-closing-force', ({ nrRemainingClients })) const goingAwayCode = 1001 for (const socket of wsServer.clients) { socket.close(goingAwayCode, 'Server is going offline') } } dhtRelay.emit('ws-closing-done') } module.exports = DhtRelayWss