lisk-framework
Version:
Lisk blockchain application platform
155 lines • 5.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WSServer = void 0;
const lisk_cryptography_1 = require("@liskhq/lisk-cryptography");
const WebSocket = require("ws");
class WSServer {
constructor(options) {
var _a;
this._subscriptions = {};
this._port = options.port;
this._host = options.host;
this._path = options.path;
this._accessControlAllowOrigin = (_a = options.accessControlAllowOrigin) !== null && _a !== void 0 ? _a : '*';
}
start(logger, messageHandler, httpServer) {
var _a;
this._logger = logger;
if (httpServer) {
this.server = new WebSocket.Server({
noServer: true,
});
httpServer.on('upgrade', (request, socket, head) => {
if (request.url === this._path) {
this.server.handleUpgrade(request, socket, head, ws => {
this.server.emit('connection', ws, request);
});
}
else {
socket.destroy();
}
});
}
else {
this.server = new WebSocket.Server({
path: this._path,
port: this._port,
host: this._host,
clientTracking: true,
});
}
this.server.on('connection', (socket, req) => {
const { origin } = req.headers;
if (origin &&
this._accessControlAllowOrigin !== '*' &&
!this._accessControlAllowOrigin.includes(origin)) {
socket.close();
return;
}
this._handleConnection(socket, messageHandler);
});
this.server.on('error', error => {
this._logger.error(error);
});
this.server.on('listening', () => {
this._logger.info({ host: this._host, port: this._port, path: this._path }, 'Websocket Server Ready');
});
this.server.on('close', () => {
clearInterval(this._pingTimer);
});
this._pingTimer = this._setUpPing();
this._logger.info(`RPC WS Server starting at ${(_a = this._host) !== null && _a !== void 0 ? _a : ''}:${this._port}${this._path}`);
return this.server;
}
stop() {
if (this.server) {
for (const cli of this.server.clients) {
cli.terminate();
}
this.server.close();
}
}
broadcast(message) {
for (const client of this.server.clients) {
const wsClient = client;
if (wsClient.readyState === WebSocket.OPEN && wsClient.id) {
const subscription = this._subscriptions[wsClient.id];
if (!subscription) {
continue;
}
if (Array.from(subscription).some(key => message.method === key || message.method.includes(key))) {
client.send(JSON.stringify(message));
}
}
}
}
_handleConnection(socket, messageHandler) {
socket.isAlive = true;
socket.id = lisk_cryptography_1.utils.getRandomBytes(20).toString('hex');
socket.on('message', (message, isBytes) => {
try {
const parsedMessage = JSON.parse(isBytes ? message.toString() : message);
if (parsedMessage.method === 'subscribe') {
this._handleSubscription(socket, parsedMessage);
return;
}
if (parsedMessage.method === 'unsubscribe') {
this._handleUnsubscription(socket, parsedMessage);
return;
}
}
catch (error) {
this._logger.error({ err: error }, 'Received invalid websocket message');
return;
}
messageHandler(socket, message);
});
socket.on('pong', () => this._handleHeartbeat(socket));
socket.on('close', () => {
delete this._subscriptions[socket.id];
});
this._logger.info('New web socket client connected');
}
_handleHeartbeat(socket) {
socket.isAlive = true;
}
_setUpPing() {
return setInterval(() => {
for (const socket of this.server.clients) {
const aClient = socket;
if (aClient.isAlive === false) {
return socket.terminate();
}
aClient.isAlive = false;
aClient.ping(() => { });
}
return null;
}, 3000);
}
_handleSubscription(socket, message) {
const { params } = message;
if (!params || !Array.isArray(params.topics) || !params.topics.length) {
throw new Error('Invalid subscription message.');
}
if (!this._subscriptions[socket.id]) {
this._subscriptions[socket.id] = new Set();
}
for (const eventName of params.topics) {
this._subscriptions[socket.id].add(eventName);
}
}
_handleUnsubscription(socket, message) {
const { params } = message;
if (!params || !Array.isArray(params.topics) || !params.topics.length) {
throw new Error('Invalid unsubscription message.');
}
if (!this._subscriptions[socket.id]) {
return;
}
for (const eventName of params.topics) {
this._subscriptions[socket.id].delete(eventName);
}
}
}
exports.WSServer = WSServer;
//# sourceMappingURL=ws_server.js.map