Eclair Websocket updates for payment and channel events (#840)
parent
a906c46405
commit
a41a7ba7f8
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
(()=>{"use strict";var e,r,t,o={},a={};function n(e){var r=a[e];if(void 0!==r)return r.exports;var t=a[e]={id:e,loaded:!1,exports:{}};return o[e].call(t.exports,t,t.exports,n),t.loaded=!0,t.exports}n.m=o,e=[],n.O=(r,t,o,a)=>{if(!t){var l=1/0;for(s=0;s<e.length;s++){for(var[t,o,a]=e[s],i=!0,d=0;d<t.length;d++)(!1&a||l>=a)&&Object.keys(n.O).every(e=>n.O[e](t[d]))?t.splice(d--,1):(i=!1,a<l&&(l=a));i&&(e.splice(s--,1),r=o())}return r}a=a||0;for(var s=e.length;s>0&&e[s-1][2]>a;s--)e[s]=e[s-1];e[s]=[t,o,a]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce((r,t)=>(n.f[t](e,r),r),[])),n.u=e=>e+"."+{145:"118ca353c539b86870f5",432:"9db359c61436bdcbc11f",891:"b3aa0591a52b13f0db13",958:"16231e91ce5e6e57c552"}[e]+".js",n.miniCssF=e=>"styles.25fb6eaa0bedeee33fea.css",n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="rtl:",n.l=(e,o,a,l)=>{if(r[e])r[e].push(o);else{var i,d;if(void 0!==a)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var c=s[u];if(c.getAttribute("src")==e||c.getAttribute("data-webpack")==t+a){i=c;break}}i||(d=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,n.nc&&i.setAttribute("nonce",n.nc),i.setAttribute("data-webpack",t+a),i.src=e),r[e]=[o];var f=(t,o)=>{i.onerror=i.onload=null,clearTimeout(p);var a=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),a&&a.forEach(e=>e(o)),t)return t(o)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=f.bind(null,i.onerror),i.onload=f.bind(null,i.onload),d&&document.head.appendChild(i)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.p="",(()=>{var e={666:0};n.f.j=(r,t)=>{var o=n.o(e,r)?e[r]:void 0;if(0!==o)if(o)t.push(o[2]);else if(666!=r){var a=new Promise((t,a)=>o=e[r]=[t,a]);t.push(o[2]=a);var l=n.p+n.u(r),i=new Error;n.l(l,t=>{if(n.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var a=t&&("load"===t.type?"missing":t.type),l=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+a+": "+l+")",i.name="ChunkLoadError",i.type=a,i.request=l,o[1](i)}},"chunk-"+r,r)}else e[r]=0},n.O.j=r=>0===e[r];var r=(r,t)=>{var o,a,[l,i,d]=t,s=0;for(o in i)n.o(i,o)&&(n.m[o]=i[o]);if(d)var u=d(n);for(r&&r(t);s<l.length;s++)n.o(e,a=l[s])&&e[a]&&e[a][0](),e[l[s]]=0;return n.O(u)},t=self.webpackChunkrtl=self.webpackChunkrtl||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})()})();
|
||||
(()=>{"use strict";var e,r,t,o={},a={};function n(e){var r=a[e];if(void 0!==r)return r.exports;var t=a[e]={id:e,loaded:!1,exports:{}};return o[e].call(t.exports,t,t.exports,n),t.loaded=!0,t.exports}n.m=o,e=[],n.O=(r,t,o,a)=>{if(!t){var l=1/0;for(s=0;s<e.length;s++){for(var[t,o,a]=e[s],d=!0,i=0;i<t.length;i++)(!1&a||l>=a)&&Object.keys(n.O).every(e=>n.O[e](t[i]))?t.splice(i--,1):(d=!1,a<l&&(l=a));d&&(e.splice(s--,1),r=o())}return r}a=a||0;for(var s=e.length;s>0&&e[s-1][2]>a;s--)e[s]=e[s-1];e[s]=[t,o,a]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce((r,t)=>(n.f[t](e,r),r),[])),n.u=e=>e+"."+{145:"162f4fba7559fca28c13",432:"214076a4aec175d96522",891:"d267f212990dff4dce6e",958:"b484cd840add91e5a7b2"}[e]+".js",n.miniCssF=e=>"styles.3d3ee2a46a070a00611c.css",n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="rtl:",n.l=(e,o,a,l)=>{if(r[e])r[e].push(o);else{var d,i;if(void 0!==a)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var c=s[u];if(c.getAttribute("src")==e||c.getAttribute("data-webpack")==t+a){d=c;break}}d||(i=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,n.nc&&d.setAttribute("nonce",n.nc),d.setAttribute("data-webpack",t+a),d.src=e),r[e]=[o];var f=(t,o)=>{d.onerror=d.onload=null,clearTimeout(p);var a=r[e];if(delete r[e],d.parentNode&&d.parentNode.removeChild(d),a&&a.forEach(e=>e(o)),t)return t(o)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=f.bind(null,d.onerror),d.onload=f.bind(null,d.onload),i&&document.head.appendChild(d)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.p="",(()=>{var e={666:0};n.f.j=(r,t)=>{var o=n.o(e,r)?e[r]:void 0;if(0!==o)if(o)t.push(o[2]);else if(666!=r){var a=new Promise((t,a)=>o=e[r]=[t,a]);t.push(o[2]=a);var l=n.p+n.u(r),d=new Error;n.l(l,t=>{if(n.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var a=t&&("load"===t.type?"missing":t.type),l=t&&t.target&&t.target.src;d.message="Loading chunk "+r+" failed.\n("+a+": "+l+")",d.name="ChunkLoadError",d.type=a,d.request=l,o[1](d)}},"chunk-"+r,r)}else e[r]=0},n.O.j=r=>0===e[r];var r=(r,t)=>{var o,a,[l,d,i]=t,s=0;for(o in d)n.o(d,o)&&(n.m[o]=d[o]);if(i)var u=i(n);for(r&&r(t);s<l.length;s++)n.o(e,a=l[s])&&e[a]&&e[a][0](),e[l[s]]=0;return n.O(u)},t=self.webpackChunkrtl=self.webpackChunkrtl||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})()})();
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,66 @@
|
||||
import WebSocket from 'ws';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { WSServer } from '../../utils/webSocketServer.js';
|
||||
export class ECLWebSocketClient {
|
||||
constructor() {
|
||||
this.logger = Logger;
|
||||
this.common = Common;
|
||||
this.wsServer = WSServer;
|
||||
this.webSocketClient = null;
|
||||
this.reconnectTimeOut = null;
|
||||
this.waitTime = 0.5;
|
||||
this.reconnet = () => {
|
||||
if (this.reconnectTimeOut) {
|
||||
return;
|
||||
}
|
||||
this.waitTime = (this.waitTime >= 16) ? 16 : (this.waitTime * 2);
|
||||
this.reconnectTimeOut = setTimeout(() => {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Reconnecting to the Eclair\'s Websocket Server.' });
|
||||
this.connect();
|
||||
this.reconnectTimeOut = null;
|
||||
}, this.waitTime * 1000);
|
||||
};
|
||||
this.connect = () => {
|
||||
try {
|
||||
const UpdatedLNServerURL = this.common.getSelLNServerUrl().replace(/^http/, 'ws');
|
||||
const firstSubStrIndex = (UpdatedLNServerURL.indexOf('//') + 2);
|
||||
const WS_LINK = UpdatedLNServerURL.slice(0, firstSubStrIndex) + ':' + this.common.selectedNode.ln_api_password + '@' + UpdatedLNServerURL.slice(firstSubStrIndex) + '/ws';
|
||||
this.webSocketClient = new WebSocket(WS_LINK);
|
||||
this.webSocketClient.onopen = this.onClientOpen;
|
||||
this.webSocketClient.onclose = this.onClientClose;
|
||||
this.webSocketClient.onmessage = this.onClientMessage;
|
||||
this.webSocketClient.onerror = this.onClientError;
|
||||
}
|
||||
catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
this.onClientOpen = () => {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Connected to the Eclair\'s Websocket Server.' });
|
||||
this.waitTime = 0.5;
|
||||
};
|
||||
this.onClientClose = (e) => {
|
||||
if (this.common.selectedNode.ln_implementation === 'ECL') {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Web socket disconnected, will reconnect again..' });
|
||||
this.reconnet();
|
||||
}
|
||||
};
|
||||
this.onClientMessage = (msg) => {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Received message from the server..', data: msg.data });
|
||||
this.wsServer.sendEventsToAllWSClients(msg.data);
|
||||
};
|
||||
this.onClientError = (err) => {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'ECLWebSocket', msg: 'Web socket error', error: err });
|
||||
this.wsServer.sendErrorToAllWSClients(err);
|
||||
this.reconnet();
|
||||
};
|
||||
this.disconnect = () => {
|
||||
if (this.webSocketClient && this.webSocketClient.readyState === 1) {
|
||||
this.logger.log({ level: 'INFO', fileName: 'ECLWebSocket', msg: 'Disconnecting from the Eclair\'s Websocket Server.' });
|
||||
this.webSocketClient.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
export const ECLWSClient = new ECLWebSocketClient();
|
@ -0,0 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
import { isAuthenticated } from '../../utils/authCheck';
|
||||
import { SSEventControl } from '../../controllers/eclair/webSocket';
|
||||
const router = Router();
|
||||
router.get('/', isAuthenticated, SSEventControl);
|
||||
export default router;
|
@ -0,0 +1,75 @@
|
||||
import * as crypto from 'crypto';
|
||||
import WebSocket from 'ws';
|
||||
import { Logger } from './logger.js';
|
||||
import { Common } from './common.js';
|
||||
import { verifyWSUser } from './authCheck.js';
|
||||
export class WebSocketServer {
|
||||
constructor() {
|
||||
this.logger = Logger;
|
||||
this.common = Common;
|
||||
this.webSocketServer = null;
|
||||
this.mount = (httpServer) => {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Connecting Websocket Server.' });
|
||||
this.webSocketServer = new WebSocket.Server({ noServer: true, path: this.common.baseHref + '/api/ws', verifyClient: (process.env.NODE_ENV === 'development') ? null : verifyWSUser });
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
if (request.headers['upgrade'] !== 'websocket') {
|
||||
socket.end('HTTP/1.1 400 Bad Request');
|
||||
return;
|
||||
}
|
||||
const acceptKey = request.headers['sec-websocket-key'];
|
||||
const hash = this.generateAcceptValue(acceptKey);
|
||||
const responseHeaders = ['HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + hash];
|
||||
const protocols = !request.headers['sec-websocket-protocol'] ? [] : request.headers['sec-websocket-protocol'].split(',').map((s) => s.trim());
|
||||
if (protocols.includes('json')) {
|
||||
responseHeaders.push('Sec-WebSocket-Protocol: json');
|
||||
}
|
||||
this.webSocketServer.handleUpgrade(request, socket, head, this.upgradeCallback);
|
||||
});
|
||||
this.webSocketServer.on('connection', this.mountEventsOnConnection);
|
||||
};
|
||||
this.upgradeCallback = (websocket, request) => {
|
||||
this.webSocketServer.emit('connection', websocket, request);
|
||||
};
|
||||
this.mountEventsOnConnection = (websocket, request) => {
|
||||
websocket.clientId = Date.now();
|
||||
this.logger.log({ level: 'INFO', fileName: 'WebSocketServer', msg: 'Connected: ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size });
|
||||
websocket.on('error', this.sendErrorToAllWSClients);
|
||||
websocket.on('message', this.sendEventsToAllWSClients);
|
||||
websocket.on('close', () => { this.logger.log({ level: 'INFO', fileName: 'WebSocketServer', msg: 'Disconnected: ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size }); });
|
||||
};
|
||||
this.sendErrorToClient = (client, serverError) => {
|
||||
try {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Sending error to client...: ' + JSON.stringify(serverError) });
|
||||
client.send(JSON.stringify({ error: serverError }));
|
||||
client.close();
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while sending error: ' + JSON.stringify(err) });
|
||||
}
|
||||
};
|
||||
this.sendErrorToAllWSClients = (serverError) => {
|
||||
try {
|
||||
this.webSocketServer.clients.forEach((client) => {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Broadcasting error to clients...: ' + JSON.stringify(serverError) });
|
||||
client.send(JSON.stringify({ error: serverError }));
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while broadcasting message: ' + JSON.stringify(err) });
|
||||
}
|
||||
};
|
||||
this.sendEventsToAllWSClients = (newMessage) => {
|
||||
try {
|
||||
this.webSocketServer.clients.forEach((client) => {
|
||||
this.logger.log({ level: 'INFO', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId });
|
||||
client.send(newMessage);
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while broadcasting message: ' + JSON.stringify(err) });
|
||||
}
|
||||
};
|
||||
this.generateAcceptValue = (acceptKey) => crypto.createHash('sha1').update(acceptKey + crypto.randomBytes(64).toString('hex')).digest('base64');
|
||||
}
|
||||
}
|
||||
export const WSServer = new WebSocketServer();
|
@ -0,0 +1,73 @@
|
||||
import WebSocket from 'ws';
|
||||
|
||||
import { Logger, LoggerService } from '../../utils/logger.js';
|
||||
import { Common, CommonService } from '../../utils/common.js';
|
||||
import { WSServer } from '../../utils/webSocketServer.js';
|
||||
|
||||
export class ECLWebSocketClient {
|
||||
|
||||
public logger: LoggerService = Logger;
|
||||
public common: CommonService = Common;
|
||||
public wsServer = WSServer;
|
||||
public webSocketClient = null;
|
||||
public reconnectTimeOut = null;
|
||||
public waitTime = 0.5;
|
||||
|
||||
public reconnet = () => {
|
||||
if (this.reconnectTimeOut) { return; }
|
||||
this.waitTime = (this.waitTime >= 16) ? 16 : (this.waitTime * 2);
|
||||
this.reconnectTimeOut = setTimeout(() => {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Reconnecting to the Eclair\'s Websocket Server.' });
|
||||
this.connect();
|
||||
this.reconnectTimeOut = null;
|
||||
}, this.waitTime * 1000);
|
||||
};
|
||||
|
||||
public connect = () => {
|
||||
try {
|
||||
const UpdatedLNServerURL = this.common.getSelLNServerUrl().replace(/^http/, 'ws');
|
||||
const firstSubStrIndex = (UpdatedLNServerURL.indexOf('//') + 2);
|
||||
const WS_LINK = UpdatedLNServerURL.slice(0, firstSubStrIndex) + ':' + this.common.selectedNode.ln_api_password + '@' + UpdatedLNServerURL.slice(firstSubStrIndex) + '/ws';
|
||||
this.webSocketClient = new WebSocket(WS_LINK);
|
||||
this.webSocketClient.onopen = this.onClientOpen;
|
||||
this.webSocketClient.onclose = this.onClientClose;
|
||||
this.webSocketClient.onmessage = this.onClientMessage;
|
||||
this.webSocketClient.onerror = this.onClientError;
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
|
||||
public onClientOpen = () => {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Connected to the Eclair\'s Websocket Server.' });
|
||||
this.waitTime = 0.5;
|
||||
};
|
||||
|
||||
public onClientClose = (e) => {
|
||||
if (this.common.selectedNode.ln_implementation === 'ECL') {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Web socket disconnected, will reconnect again..' });
|
||||
this.reconnet();
|
||||
}
|
||||
};
|
||||
|
||||
public onClientMessage = (msg) => {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Received message from the server..', data: msg.data });
|
||||
this.wsServer.sendEventsToAllWSClients(msg.data);
|
||||
};
|
||||
|
||||
public onClientError = (err) => {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'ECLWebSocket', msg: 'Web socket error', error: err });
|
||||
this.wsServer.sendErrorToAllWSClients(err);
|
||||
this.reconnet();
|
||||
};
|
||||
|
||||
public disconnect = () => {
|
||||
if (this.webSocketClient && this.webSocketClient.readyState === 1) {
|
||||
this.logger.log({ level: 'INFO', fileName: 'ECLWebSocket', msg: 'Disconnecting from the Eclair\'s Websocket Server.' });
|
||||
this.webSocketClient.close();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export const ECLWSClient = new ECLWebSocketClient();
|
@ -0,0 +1,81 @@
|
||||
import * as crypto from 'crypto';
|
||||
import WebSocket from 'ws';
|
||||
import { Application } from 'express';
|
||||
import { Logger, LoggerService } from './logger.js';
|
||||
import { Common, CommonService } from './common.js';
|
||||
import { verifyWSUser } from './authCheck.js';
|
||||
|
||||
export class WebSocketServer {
|
||||
|
||||
public logger: LoggerService = Logger;
|
||||
public common: CommonService = Common;
|
||||
public webSocketServer = null;
|
||||
|
||||
public mount = (httpServer: Application): Application => {
|
||||
this.logger.log({ level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Connecting Websocket Server.' });
|
||||
this.webSocketServer = new WebSocket.Server({ noServer: true, path: this.common.baseHref + '/api/ws', verifyClient: (process.env.NODE_ENV === 'development') ? null : verifyWSUser });
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
if (request.headers['upgrade'] !== 'websocket') {
|
||||
socket.end('HTTP/1.1 400 Bad Request');
|
||||
return;
|
||||
}
|
||||
const acceptKey = request.headers['sec-websocket-key'];
|
||||
const hash = this.generateAcceptValue(acceptKey);
|
||||
const responseHeaders = ['HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + hash];
|
||||
const protocols = !request.headers['sec-websocket-protocol'] ? [] : request.headers['sec-websocket-protocol'].split(',').map((s) => s.trim());
|
||||
if (protocols.includes('json')) { responseHeaders.push('Sec-WebSocket-Protocol: json'); }
|
||||
this.webSocketServer.handleUpgrade(request, socket, head, this.upgradeCallback);
|
||||
});
|
||||
this.webSocketServer.on('connection', this.mountEventsOnConnection);
|
||||
}
|
||||
|
||||
public upgradeCallback = (websocket, request) => {
|
||||
this.webSocketServer.emit('connection', websocket, request);
|
||||
};
|
||||
|
||||
public mountEventsOnConnection = (websocket, request) => {
|
||||
websocket.clientId = Date.now();
|
||||
this.logger.log({ level: 'INFO', fileName: 'WebSocketServer', msg: 'Connected: ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size });
|
||||
websocket.on('error', this.sendErrorToAllWSClients);
|
||||
websocket.on('message', this.sendEventsToAllWSClients);
|
||||
websocket.on('close', () => { this.logger.log({ level: 'INFO', fileName: 'WebSocketServer', msg: 'Disconnected: ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size }); });
|
||||
};
|
||||
|
||||
public sendErrorToClient = (client, serverError) => {
|
||||
try {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Sending error to client...: ' + JSON.stringify(serverError) });
|
||||
client.send(JSON.stringify({ error: serverError }));
|
||||
client.close();
|
||||
} catch (err) {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while sending error: ' + JSON.stringify(err) });
|
||||
}
|
||||
};
|
||||
|
||||
public sendErrorToAllWSClients = (serverError) => {
|
||||
try {
|
||||
this.webSocketServer.clients.forEach((client) => {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Broadcasting error to clients...: ' + JSON.stringify(serverError) });
|
||||
client.send(JSON.stringify({ error: serverError }));
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while broadcasting message: ' + JSON.stringify(err) });
|
||||
}
|
||||
};
|
||||
|
||||
public sendEventsToAllWSClients = (newMessage) => {
|
||||
try {
|
||||
this.webSocketServer.clients.forEach((client) => {
|
||||
this.logger.log({ level: 'INFO', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId });
|
||||
client.send(newMessage);
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.log({ level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while broadcasting message: ' + JSON.stringify(err) });
|
||||
}
|
||||
};
|
||||
|
||||
public generateAcceptValue = (acceptKey) => crypto.createHash('sha1').update(acceptKey + crypto.randomBytes(64).toString('hex')).digest('base64');
|
||||
|
||||
}
|
||||
|
||||
export const WSServer = new WebSocketServer();
|
||||
|
@ -0,0 +1,90 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { WebSocketSubject } from 'rxjs/webSocket';
|
||||
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
import { WSEventTypeEnum } from './consts-enums-functions';
|
||||
import { SessionService } from './session.service';
|
||||
|
||||
@Injectable()
|
||||
export class WebSocketClientService implements OnDestroy {
|
||||
|
||||
public wsMessages: BehaviorSubject<any> = new BehaviorSubject(null);
|
||||
private prevMessage = {};
|
||||
private wsUrl = '';
|
||||
private socket: WebSocketSubject<any> | null;
|
||||
private RETRY_SECONDS = 5;
|
||||
private RECONNECT_TIMEOUT = null;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private sessionService: SessionService) {}
|
||||
|
||||
connectWebSocket(finalWSUrl: string) {
|
||||
this.wsUrl = finalWSUrl;
|
||||
this.logger.info('Websocket Url: ' + this.wsUrl);
|
||||
if (!this.socket || this.socket.closed) {
|
||||
this.socket = new WebSocketSubject({
|
||||
url: finalWSUrl,
|
||||
protocol: [this.sessionService.getItem('token')]
|
||||
});
|
||||
this.subscribeToMessages();
|
||||
}
|
||||
}
|
||||
|
||||
reconnectOnError() {
|
||||
if (this.RECONNECT_TIMEOUT) { return; }
|
||||
this.RETRY_SECONDS = (this.RETRY_SECONDS >= 160) ? 160 : (this.RETRY_SECONDS * 2);
|
||||
this.RECONNECT_TIMEOUT = setTimeout(() => {
|
||||
this.logger.info('Reconnecting Web Socket.');
|
||||
this.connectWebSocket(this.wsUrl);
|
||||
this.RECONNECT_TIMEOUT = null;
|
||||
}, this.RETRY_SECONDS * 1000);
|
||||
}
|
||||
|
||||
closeConnection() {
|
||||
if (this.socket) {
|
||||
this.socket.complete();
|
||||
this.socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(msg: any) {
|
||||
if (this.socket) {
|
||||
const payload = { token: 'token_from_session_service', message: msg };
|
||||
this.socket.next(payload);
|
||||
}
|
||||
}
|
||||
|
||||
private subscribeToMessages() {
|
||||
this.socket.pipe(takeUntil(this.unSubs[1])).subscribe({
|
||||
next: (msg) => {
|
||||
msg = (typeof msg === 'string') ? JSON.parse(msg) : msg;
|
||||
if (msg.error) {
|
||||
this.handleError(msg.error);
|
||||
} else {
|
||||
const msgStr = JSON.stringify(msg);
|
||||
if (this.prevMessage.hasOwnProperty(msg.type) && this.prevMessage[msg.type] === msgStr) { return; }
|
||||
this.prevMessage[msg.type] = msgStr;
|
||||
this.logger.info('Next Message from WS:' + JSON.stringify(msg));
|
||||
this.wsMessages.next(msg);
|
||||
}
|
||||
},
|
||||
error: (err) => this.handleError(err),
|
||||
complete: () => { this.logger.info('Web Socket Closed'); }
|
||||
});
|
||||
}
|
||||
|
||||
private handleError(err) {
|
||||
this.logger.error(err);
|
||||
this.wsMessages.error(err);
|
||||
this.reconnectOnError();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.closeConnection();
|
||||
this.wsMessages.next(null);
|
||||
this.wsMessages.complete();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue