/* eslint-disable @typescript-eslint/no-empty-function */ import { EMessageType, IMessage } from './message'; import { Protocol } from './protocol'; /** * Service is used to run, keep track of and kill Protocols. */ export class Service { private protocols: Map = new Map(); private stopped = false; constructor(private protoType: any) {} public setExternalEvents(pto: Protocol): void { const eventMessage: IMessage = { type: EMessageType.protocolStart, resolve: () => {}, reject: () => {}, mention: () => '', data: pto, source: pto, text: `Protocol ${pto.fullName} running`, time: new Date(), resolved: true, }; pto.on('running', () => { pto.plugin.stream.emitTo('channel', 'event', eventMessage); }); pto.on('stopped', () => { pto.plugin.stream.emitTo('channel', 'event', { ...eventMessage, type: EMessageType.protocolExit, text: `Protocol ${pto.fullName} stopped`, }); // TODO: restart handling }); } /** * Add a new protocol to this service. * @param pto Protocol instance * @param autostart Automatically start the protocol * @returns Protocol instance */ public use(pto: Protocol, autostart = true): Protocol { // This service is no longer accepting new protocols if (this.stopped) { return pto; } const n = pto.name; if (n == null || n === '') { throw new Error('Invalid Protocol configuration: Needs an unique name!'); } if (this.protocols.has(n)) { return this.protocols.get(n) as Protocol; } this.protocols.set(n, pto); this.setExternalEvents(pto); if (autostart) { pto.start(); } return pto; } /** * Stop a protocol running in this service * @param pto Protocol instance or name * @param force Force stop */ public stop(pto: string | Protocol, force = false): void { let proto: Protocol; if (typeof pto === 'string') { if (!this.protocols.get(pto)) { return; } proto = this.protocols.get(pto) as Protocol; } else { proto = pto; if (!this.protocols.get(pto.name)) { // Not part of this service! return; } } proto.stop(force); this.protocols.delete(proto.name); } /** * Find a protocol by name. * @param name Protocol name * @returns Protocol instance or undefined */ public getProtocolByName(name: string): Protocol | undefined { return this.protocols.get(name); } /** * Get all protocol instances running in this service. * @returns List of all protocol instances running in this service */ public getAll(): Protocol[] { return Array.from(this.protocols.values()); } /** * Gracefully stops everything running in this service */ public stopAll(): Promise { return new Promise((resolve, reject) => { if (this.stopped) { return resolve(); } let toStop = +this.protocols.size; let killed = false; // Nothing to stop if (toStop === 0) { return resolve(); } // If the protocols fail to stop within a time frame, we have to kill // them in order to prevent lingering connections. const killTimeout = setTimeout(() => { killed = true; this.die(); resolve(); }, 10000); // Stop everything and wait for them to announce the fact // that they've stopped. this.stopped = true; for (const [name, proto] of this.protocols) { proto.once('stopped', () => { if (killed) { return; } toStop--; if (toStop <= 0) { this.protocols.clear(); clearTimeout(killTimeout); resolve(); } }); proto.stop(); } }); } /** * Forcefully kills everything running in this service, */ public die(): void { this.stopped = true; for (const [name, proto] of this.protocols) { proto.stop(true); } this.protocols.clear(); } }