UNPKG

coffee-core

Version:

Coffee IT API core library

111 lines (97 loc) 3.08 kB
import { Logger } from '@nestjs/common'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import { ClientConfigInterface } from './client-config.interface'; import { ConnectableClient } from './connectable-client.interface'; const DISCONNECTED_STATUS = 'disconnected'; export class ClientConnector { private statusSubscription?: Subscription; private destroyed = false; private reconnectInFlight = false; constructor( private readonly config: ClientConfigInterface, private readonly client: ConnectableClient, private readonly serviceName: string, private readonly context: string, ) {} public async connect(): Promise<void> { try { await this.client.connect(); Logger.log(`Connected to ${this.serviceName}`, this.context); this.watchStatusIfSupported(); } catch (error) { Logger.warn( `Initial connection to ${this.serviceName} failed, retrying in background: ${error}`, this.context, ); this.scheduleBackgroundReconnect(); } } private scheduleBackgroundReconnect(): void { void this.connectWithRetry(0) .then(() => { if (!this.destroyed) { this.watchStatusIfSupported(); } }) .catch((error) => { Logger.error( `Unexpected error while reconnecting to ${this.serviceName}: ${error}`, this.context, ); }); } public destroy(): void { this.destroyed = true; this.statusSubscription?.unsubscribe(); this.statusSubscription = undefined; } private watchStatusIfSupported(): void { this.statusSubscription?.unsubscribe(); this.statusSubscription = this.client.status .pipe(filter((status) => status === DISCONNECTED_STATUS)) .subscribe(() => { void this.handleDisconnect(); }); } private async handleDisconnect(): Promise<void> { if (this.destroyed || this.reconnectInFlight) { return; } this.reconnectInFlight = true; try { Logger.warn( `Disconnected from ${this.serviceName}, reconnecting...`, this.context, ); await this.client.close(); await this.connectWithRetry(0); } finally { this.reconnectInFlight = false; } } private async connectWithRetry(initialRetries: number): Promise<void> { let retries = initialRetries; while (!this.destroyed) { try { await this.client.connect(); Logger.log(`Connected to ${this.serviceName}`, this.context); return; } catch { await this.sleep(this.getRetryTime(retries)); retries += 1; Logger.log( `Retrying ${this.serviceName} connection: ${retries}th time`, this.context, ); } } } private getRetryTime(retries: number): number { const time = this.config.INITIAL_RETRY_TIMEOUT_MS * 2 ** retries; return Math.min(time, this.config.MAX_RETRY_TIME_MS); } private sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } }