UNPKG

@bazilio-san/af-stream

Version:
190 lines (160 loc) 5.77 kB
/* eslint-disable class-methods-use-this,no-await-in-loop */ import { io, Socket } from 'socket.io-client'; import { sleep } from '../utils/utils'; import AbstractSender from './AbstractSender'; import { cyan, lBlue, reset } from '../utils/color'; import { IRecordsComposite, ISenderConstructorOptions, Nullable, TAccessPoint } from '../interfaces'; const AWAIT_SOCKET_TIMEOUT = 10_000; const LOOP_SLEEP_MILLIS = 1000; class WSSender extends AbstractSender { private lastConfigServiceAddress: string; private address: string; private mConsulServiceName: string; private socketClient: Nullable<Socket>; private readonly accessPointId: string; private token: string; private readonly socketRequestId: string; constructor (options: ISenderConstructorOptions) { super(options); this.lastConfigServiceAddress = ''; const { senderConfig, eventEmitter } = options; const { host, port } = senderConfig; const ap = senderConfig.accessPoint as TAccessPoint; this.address = `http://${host}:${port}`; this.mConsulServiceName = `${cyan}${ap.consulServiceName}${reset}`; this.socketClient = null; this.accessPointId = ap.id as string; this.token = ap.token as string; this.socketRequestId = ap.socketRequestId as string; eventEmitter.on('access-point-updated', ({ accessPoint }: { accessPoint: TAccessPoint }) => { if (accessPoint.id === this.accessPointId) { this.reconnect().then(() => 0); } }); this.reconnect().then(() => 0); } isConnected (): boolean { return Boolean(this.socketClient?.emit && this.socketClient?.connected); } async connect (): Promise<boolean> { const { address, mConsulServiceName, token, options } = this; const { echo, logger, serviceName } = options; const mAddress = `${lBlue}${address}${reset}`; echo.info(`Connect to ${cyan}WEB SOCKET${reset} on ${lBlue}${address}${reset}`); const opt = { query: { fromService: serviceName }, // VVT auth: { token }, extraHeaders: { authorization: token }, }; const socketClient = io(address, opt); this.socketClient = socketClient; return new Promise((resolve) => { socketClient.on('connect', () => { echo.info(` ====================== Web Socket Sender ======================= Connection established with WEBSOCKET ${mConsulServiceName} on ${mAddress} ================================================================`); resolve(true); }); socketClient.on('unauthorized', (reason, callback) => { logger.error(`Error on "unauthorized" event while connecting to config service via socket. Reason: ${reason}`); resolve(false); callback(); }); socketClient.on('disconnect', () => { logger.warn(`Config service instance ${mConsulServiceName} disconnected`); resolve(false); }); socketClient.on('error', (err) => { logger.error(err); resolve(false); }); }); } async reconnect (force?: boolean): Promise<boolean> { const { options: { senderConfig: { port, host } }, lastConfigServiceAddress } = this; if (!host || !port) { return this.isConnected(); } const address = `http://${host}:${port}`; if (force || (lastConfigServiceAddress !== address)) { this.lastConfigServiceAddress = address; this.address = address; return this.connect(); } return false; } async awaitSocket () { if (this.isConnected()) { return true; } const { logger } = this.options; const start = Date.now(); while (!this.isConnected() && (Date.now() - start < AWAIT_SOCKET_TIMEOUT)) { if (Date.now() - start < LOOP_SLEEP_MILLIS) { logger.silly('Try to connect to the socket...'); } else { logger.warn('Socket is not still connected...'); } await this.reconnect(true); await sleep(LOOP_SLEEP_MILLIS); } return this.isConnected(); } async remoteSocket (rqId: string, ...args: any[]): Promise<{ error?: any, result?: any }> { const self = this; const { logger } = this.options; const error = `NOT connected to the socket. Request id: ${rqId}`; if (await this.awaitSocket()) { return new Promise((resolve) => { if (!self.isConnected()) { logger.error(error); resolve({ error }); return; } args.push((a: any) => { resolve(a); }); this.socketClient?.emit(rqId, ...args); }); } return { error }; } async sendEvents (recordsComposite: IRecordsComposite): Promise<boolean> { const MAX_PACKET_SIZE = 100; // Max number of events in a batch const { eventsPacket, first } = recordsComposite; if (!eventsPacket.length) { return false; } const { logger } = this.options; let stop = false; recordsComposite.sentBufferLength = 0; recordsComposite.sendCount = 0; recordsComposite.last = first; while (!stop && eventsPacket.length > 0) { const packet = eventsPacket.splice(0, MAX_PACKET_SIZE); const pl = packet.length; if (pl) { /* // receiving side signature: socket.on(socketRequestId, (request, callback) => { callback({ result: true }); }); */ const { error, result } = await this.remoteSocket(this.socketRequestId, packet); stop = !result; if (stop) { if (error) { logger.error(error); } eventsPacket.splice(0, 0, ...packet); } else { recordsComposite.sendCount += pl; recordsComposite.last = packet[pl - 1]; } } } return true; } } export default WSSender;