UNPKG

@tapsioss/client-socket-manager

Version:
261 lines (260 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const socket_io_client_1 = require("socket.io-client"); const constants_ts_1 = require("./constants.js"); const utils_ts_1 = require("./utils.js"); class ClientSocketManager { constructor(uri, options) { var _a; this._disposed = false; this._socket = null; this._inputListeners = {}; const { path = "/socket.io", reconnectionDelay = 500, reconnectionDelayMax = 2000, eventHandlers, ...restOptions } = options !== null && options !== void 0 ? options : {}; try { this._socket = (0, socket_io_client_1.io)(uri, { ...restOptions, path, reconnectionDelay, reconnectionDelayMax, }); this._inputListeners = eventHandlers !== null && eventHandlers !== void 0 ? eventHandlers : {}; this._handleVisibilityChange = this._handleVisibilityChange.bind(this); this._attachPageEvents(); this._attachSocketEvents(); this._attachManagerEvents(); (_a = this._inputListeners.onInit) === null || _a === void 0 ? void 0 : _a.call(this); } catch (err) { // eslint-disable-next-line no-console console.error("Failed to initialize socket connection", { uri, path, err, }); } } _attachPageEvents() { if (!(0, utils_ts_1.isBrowser)()) return; document.addEventListener("visibilitychange", this._handleVisibilityChange); } _attachSocketEvents() { if (!this._socket) return; const { onSocketConnection, onSocketConnectionError } = this._inputListeners; if (onSocketConnection) { this._socket.on(constants_ts_1.SocketReservedEvents.CONNECTION, onSocketConnection.bind(this)); } if (onSocketConnectionError) { this._socket.on(constants_ts_1.SocketReservedEvents.CONNECTION_ERROR, onSocketConnectionError.bind(this)); } this._socket.on(constants_ts_1.SocketReservedEvents.DISCONNECTION, (reason, details) => { var _a; (_a = this._inputListeners.onSocketDisconnection) === null || _a === void 0 ? void 0 : _a.call(this, reason, details); if (!this.autoReconnectable) { if (reason === "io server disconnect") { this.connect(); } } }); } _attachManagerEvents() { var _a; const manager = (_a = this._socket) === null || _a === void 0 ? void 0 : _a.io; if (!manager) return; const { onServerPing, onConnectionError, onReconnecting, onReconnectingError, onReconnectionFailure, onSuccessfulReconnection, } = this._inputListeners; if (onConnectionError) { manager.on(constants_ts_1.ManagerReservedEvents.CONNECTION_ERROR, onConnectionError.bind(this)); } if (onServerPing) { manager.on(constants_ts_1.ManagerReservedEvents.SERVER_PING, onServerPing.bind(this)); } if (onReconnecting) { manager.on(constants_ts_1.ManagerReservedEvents.RECONNECTING, onReconnecting.bind(this)); } if (onReconnectingError) { manager.on(constants_ts_1.ManagerReservedEvents.RECONNECTING_ERROR, onReconnectingError.bind(this)); } if (onReconnectionFailure) { manager.on(constants_ts_1.ManagerReservedEvents.RECONNECTION_FAILURE, onReconnectionFailure.bind(this)); } if (onSuccessfulReconnection) { manager.on(constants_ts_1.ManagerReservedEvents.SUCCESSFUL_RECONNECTION, onSuccessfulReconnection.bind(this)); } } _detachPageEvents() { if (!(0, utils_ts_1.isBrowser)()) return; document.removeEventListener("visibilitychange", this._handleVisibilityChange); } _detachSocketEvents() { var _a; (_a = this._socket) === null || _a === void 0 ? void 0 : _a.off(); } _detachManagerEvents() { var _a; (_a = this._socket) === null || _a === void 0 ? void 0 : _a.io.off(); } _handleVisibilityChange() { var _a, _b; const isPageVisible = document.visibilityState === "visible"; const isPageHidden = document.visibilityState === "hidden"; if (!isPageVisible && !isPageHidden) return; if (isPageVisible) { (_a = this._inputListeners.onVisiblePage) === null || _a === void 0 ? void 0 : _a.call(this); if (!this.connected) this.connect(); } else { (_b = this._inputListeners.onHiddenPage) === null || _b === void 0 ? void 0 : _b.call(this); this.disconnect(); } } /** * Whether the client is disposed. */ get disposed() { return this._disposed; } /** * Emits an event to the socket identified by the channel name. */ emit(channel, ...args) { (0, utils_ts_1.warnDisposedClient)(this.disposed); if (!this._socket) return; this._socket.emit(channel, ...args); } /** * A unique identifier for the session. * * `null` when the socket is not connected. */ get id() { var _a, _b; (0, utils_ts_1.warnDisposedClient)(this.disposed); return (_b = (_a = this._socket) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null; } /** * Whether the socket is currently connected to the server. */ get connected() { var _a, _b; (0, utils_ts_1.warnDisposedClient)(this.disposed); return (_b = (_a = this._socket) === null || _a === void 0 ? void 0 : _a.connected) !== null && _b !== void 0 ? _b : false; } /** * Whether the connection state was recovered after a temporary disconnection. * In that case, any missed packets will be transmitted by the server. */ get recovered() { var _a, _b; (0, utils_ts_1.warnDisposedClient)(this.disposed); return (_b = (_a = this._socket) === null || _a === void 0 ? void 0 : _a.recovered) !== null && _b !== void 0 ? _b : false; } /** * Whether the Socket will try to reconnect when its Manager connects * or reconnects. */ get autoReconnectable() { var _a, _b; (0, utils_ts_1.warnDisposedClient)(this.disposed); return (_b = (_a = this._socket) === null || _a === void 0 ? void 0 : _a.active) !== null && _b !== void 0 ? _b : false; } /** * Subscribes to a specified channel with a callback function. */ subscribe( /** * The name of the channel to subscribe to. */ channel, /** * The callback function to invoke when a message is received on the channel. */ cb, options) { (0, utils_ts_1.warnDisposedClient)(this.disposed); if (!this._socket) return; (0, utils_ts_1.assertCallbackType)(cb, `Expected a valid callback function. Received \`${typeof cb}\`.`); const { onSubscriptionComplete, signal } = options !== null && options !== void 0 ? options : {}; const listener = (...args) => { var _a; (0, utils_ts_1.warnDisposedClient)(this.disposed); if (!this._socket) return; (_a = this._inputListeners.onAnySubscribedMessageReceived) === null || _a === void 0 ? void 0 : _a.call(this, channel, args); cb.apply(this, args); }; this._socket.on(channel, listener); const unsubscribe = () => { this.unsubscribe(channel, listener); signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", unsubscribe); }; signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", unsubscribe); if (signal === null || signal === void 0 ? void 0 : signal.aborted) unsubscribe(); onSubscriptionComplete === null || onSubscriptionComplete === void 0 ? void 0 : onSubscriptionComplete.call(this, channel); } /** * Removes the listener for the specified channel. * If no callback is provided, it removes all listeners for that channel. */ unsubscribe( /** * The name of the channel whose listener should be deleted. */ channel, /** * The subscriber callback function to remove. */ cb) { (0, utils_ts_1.warnDisposedClient)(this.disposed); if (!this._socket) return; if (cb) this._socket.off(channel, cb); else this._socket.off(channel); } /** * Manually connects/reconnects the socket. */ connect() { var _a; (0, utils_ts_1.warnDisposedClient)(this.disposed); (_a = this._socket) === null || _a === void 0 ? void 0 : _a.connect(); } /** * Manually disconnects the socket. * In that case, the socket will not try to reconnect. * * If this is the last active Socket instance of the Manager, * the low-level connection will be closed. */ disconnect() { var _a; (0, utils_ts_1.warnDisposedClient)(this.disposed); (_a = this._socket) === null || _a === void 0 ? void 0 : _a.disconnect(); } /** * Disposes of the socket, manager, and engine, ensuring all connections are * closed and cleaned up. */ dispose() { var _a, _b; (0, utils_ts_1.warnDisposedClient)(this.disposed); (_a = this._inputListeners.onDispose) === null || _a === void 0 ? void 0 : _a.call(this); this._detachPageEvents(); this._detachSocketEvents(); this._detachManagerEvents(); this.disconnect(); (_b = this._socket) === null || _b === void 0 ? void 0 : _b.io.engine.close(); this._socket = null; this._inputListeners = {}; this._disposed = true; } } exports.default = ClientSocketManager;