UNPKG

@ably/chat

Version:

Ably Chat is a set of purpose-built APIs for a host of chat features enabling you to create 1:1, 1:Many, Many:1 and Many:Many chat rooms for any scale. It is designed to meet a wide range of chat use cases, such as livestreams, in-game communication, cust

217 lines (184 loc) 5.63 kB
import * as Ably from 'ably'; import { Logger } from './logger.js'; import { on } from './realtime-subscriptions.js'; import { StatusSubscription } from './subscription.js'; import EventEmitter, { emitterHasListeners, wrap } from './utils/event-emitter.js'; /** * The different states that the connection can be in through its lifecycle. */ export enum ConnectionStatus { /** * A temporary state for when the library is first initialized. */ Initialized = 'initialized', /** * The library is currently connecting to Ably. */ Connecting = 'connecting', /** * The library is currently connected to Ably. */ Connected = 'connected', /** * The library is currently disconnected from Ably, but will attempt to reconnect. */ Disconnected = 'disconnected', /** * The library is in an extended state of disconnection, but will attempt to reconnect. */ Suspended = 'suspended', /** * The library is currently disconnected from Ably and will not attempt to reconnect. */ Failed = 'failed', } /** * Represents a change in the status of the connection. */ export interface ConnectionStatusChange { /** * The new status of the connection. */ current: ConnectionStatus; /** * The previous status of the connection. */ previous: ConnectionStatus; /** * An error that provides a reason why the connection has * entered the new status, if applicable. */ error?: Ably.ErrorInfo; /** * The time in milliseconds that the client will wait before attempting to reconnect. */ retryIn?: number; } /** * A function that can be called when the connection status changes. * @param change The change in status. */ export type ConnectionStatusListener = (change: ConnectionStatusChange) => void; /** * Represents a connection to Ably. */ export interface Connection { /** * The current status of the connection. */ get status(): ConnectionStatus; /** * The current error, if any, that caused the connection to enter the current status. */ get error(): Ably.ErrorInfo | undefined; /** * Registers a listener that will be called whenever the connection status changes. * @param listener The function to call when the status changes. * @returns An object that can be used to unregister the listener. */ onStatusChange(listener: ConnectionStatusListener): StatusSubscription; /** * Disposes of the connection instance, cleaning up any registered listeners. * This method should be called when the connection is no longer needed. */ dispose(): void; } type ConnectionEventsMap = Record<ConnectionStatus, ConnectionStatusChange>; /** * An implementation of the `Connection` interface. * @internal */ export class DefaultConnection implements Connection { private _status: ConnectionStatus = ConnectionStatus.Initialized; private _error?: Ably.ErrorInfo; private readonly _logger: Logger; private _emitter = new EventEmitter<ConnectionEventsMap>(); private readonly _clearAblyConnectionListener: () => void; /** * Constructs a new `DefaultConnection` instance. * @param ably The Ably Realtime client. * @param logger The logger to use. */ constructor(ably: Ably.Realtime, logger: Logger) { this._logger = logger; // Set our initial status and error // CHA-RS5 this._status = this._mapAblyStatusToChat(ably.connection.state); this._error = ably.connection.errorReason; // Store the listener function so we can dispose of it later const connectionListener = (change: Ably.ConnectionStateChange) => { const chatState = this._mapAblyStatusToChat(change.current); if (chatState === this._status) { return; } const stateChange: ConnectionStatusChange = { current: chatState, previous: this._status, error: change.reason, retryIn: change.retryIn, }; this._applyStatusChange(stateChange); }; // Use subscription helper to create cleanup function this._clearAblyConnectionListener = on(ably.connection, connectionListener); } /** * @inheritdoc */ get status(): ConnectionStatus { return this._status; } /** * @inheritdoc */ get error(): Ably.ErrorInfo | undefined { return this._error; } /** * @inheritdoc */ onStatusChange(listener: ConnectionStatusListener): StatusSubscription { const wrapped = wrap(listener); this._emitter.on(wrapped); return { off: () => { this._emitter.off(wrapped); }, }; } /** * @inheritdoc */ dispose(): void { this._logger.trace('DefaultConnection.dispose();'); // Remove the connection state listener from the Ably connection this._clearAblyConnectionListener(); // Clear all listeners from the internal emitter this._emitter.off(); } /** * Checks if there are any listeners registered on the connection. * @internal * @returns true if there are listeners, false otherwise. */ hasListeners(): boolean { return emitterHasListeners(this._emitter); } private _applyStatusChange(change: ConnectionStatusChange): void { this._status = change.current; this._error = change.error; this._logger.info(`Connection state changed`, change); this._emitter.emit(change.current, change); } private _mapAblyStatusToChat(status: Ably.ConnectionState): ConnectionStatus { switch (status) { case 'closing': case 'closed': { return ConnectionStatus.Failed; } default: { return status as ConnectionStatus; } } } }