// Copyright (c) Cratis. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. import { Guid } from '@cratis/fundamentals'; export type ConnectedHandler = () => Promise; export type DisconnectedHandler = () => Promise; /** * Represents the lifecycle state for a Chronicle connection. */ export class ConnectionLifecycle { private readonly _onConnected = new Set(); private readonly _onDisconnected = new Set(); private readonly _onFailed = new Set<(error: Error) => Promise>(); private _failure?: Error; private _isConnected = false; private _connectionId: string = Guid.create().toString(); /** * Gets whether the connection is currently connected. */ get isConnected(): boolean { return this._isConnected; } /** Gets the terminal connection failure, if recovery has stopped. */ get failure(): Error | undefined { return this._failure; } /** * Gets the current connection identifier. */ get connectionId(): string { return this._connectionId; } /** * Registers a callback for when the connection is connected. * @param handler - Callback to invoke on connect. * @returns A function that unsubscribes the callback. */ onConnected(handler: ConnectedHandler): () => void { this._onConnected.add(handler); return () => this._onConnected.delete(handler); } /** * Registers a callback for when the connection is disconnected. * @param handler - Callback to invoke on disconnect. * @returns A function that unsubscribes the callback. */ onDisconnected(handler: DisconnectedHandler): () => void { this._onDisconnected.add(handler); return () => this._onDisconnected.delete(handler); } /** Registers a callback for a terminal connection failure. */ onFailed(handler: (error: Error) => Promise): () => void { this._onFailed.add(handler); return () => this._onFailed.delete(handler); } /** Stops recovery and notifies observers of the terminal connection failure. */ async failed(error: Error, onError: (error: unknown) => void): Promise { if (this._failure) return; this._failure = error; this._isConnected = false; await Promise.all([...this._onFailed].map(handler => this.invokeHandler(() => handler(error), onError))); } /** * Marks lifecycle as connected and invokes connected handlers. */ async connected(onError: (error: unknown) => void): Promise { if (this._failure) return; this._isConnected = true; await Promise.all([...this._onConnected].map(handler => this.invokeHandler(handler, onError))); } /** * Marks lifecycle as disconnected and invokes disconnected handlers. */ async disconnected(onError: (error: unknown) => void): Promise { this._isConnected = false; await Promise.all([...this._onDisconnected].map(handler => this.invokeHandler(handler, onError))); this._connectionId = Guid.create().toString(); } private async invokeHandler(handler: () => Promise, onError: (error: unknown) => void): Promise { try { await handler(); } catch (error) { onError(error); } } }