coffee-core
Version:
Coffee IT API core library
111 lines (97 loc) • 3.08 kB
text/typescript
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));
}
}