UNPKG

@push.rocks/smartsocket

Version:

Provides easy and secure websocket communication mechanisms, including server and client implementation, function call routing, connection management, and tagging.

303 lines 25.5 kB
import * as plugins from './smartsocket.plugins.js'; import * as pluginsTyped from './smartsocket.pluginstyped.js'; import * as interfaces from './interfaces/index.js'; import { SocketConnection } from './smartsocket.classes.socketconnection.js'; import { SocketFunction, } from './smartsocket.classes.socketfunction.js'; import { SocketRequest } from './smartsocket.classes.socketrequest.js'; import { logger } from './smartsocket.logging.js'; export class SmartsocketClient { /** * adds a tag to a connection */ async addTag(tagArg) { if (this.socketConnection) { await this.socketConnection.addTag(tagArg); } else { this.tagStore[tagArg.id] = tagArg; } } /** * gets a tag by id * @param tagIdArg */ async getTagById(tagIdArg) { return this.tagStore[tagIdArg]; } /** * removes a tag from a connection */ async removeTagById(tagIdArg) { if (this.socketConnection) { this.socketConnection.removeTagById(tagIdArg); } else { delete this.tagStore[tagIdArg]; } } constructor(optionsArg) { // a unique id this.shortId = plugins.isounique.uni(); // the shortId of the remote we connect to this.remoteShortId = null; this.currentRetryCount = 0; // status handling this.eventSubject = new plugins.smartrx.rxjs.Subject(); this.eventStatus = 'new'; this.socketFunctions = new plugins.lik.ObjectMap(); this.socketRequests = new plugins.lik.ObjectMap(); // tagStore this.tagStore = {}; this.isReconnecting = false; this.isConnecting = false; this.disconnectRunning = false; this.currentSocket = null; this.connectionAttemptId = 0; // Increment on each connect attempt to invalidate old timers this.alias = optionsArg.alias; this.serverUrl = optionsArg.url; this.serverPort = optionsArg.port; this.autoReconnect = optionsArg.autoReconnect ?? false; this.maxRetries = optionsArg.maxRetries ?? 100; // Default to 100 retries this.initialBackoffDelay = optionsArg.initialBackoffDelay ?? 1000; // Default to 1 second this.maxBackoffDelay = optionsArg.maxBackoffDelay ?? 60000; // Default to 1 minute this.currentBackoffDelay = this.initialBackoffDelay; } addSocketFunction(socketFunction) { this.socketFunctions.add(socketFunction); } /** * connect the client to the server */ async connect() { // Prevent duplicate connection attempts if (this.isConnecting) { return; } this.isConnecting = true; // Only reset retry counters on fresh connection (not during auto-reconnect) if (!this.isReconnecting) { this.currentRetryCount = 0; this.currentBackoffDelay = this.initialBackoffDelay; } this.isReconnecting = false; const done = plugins.smartpromise.defer(); const smartenvInstance = new plugins.smartenv.Smartenv(); logger.log('info', 'trying to connect...'); // Construct WebSocket URL const protocol = this.serverUrl.startsWith('https') ? 'wss' : 'ws'; const host = this.serverUrl.replace(/^https?:\/\//, ''); const socketUrl = `${protocol}://${host}:${this.serverPort}`; // Get WebSocket implementation (native in browser, ws in Node) let WebSocketClass; if (typeof WebSocket !== 'undefined') { // Browser environment WebSocketClass = WebSocket; } else { // Node.js environment const wsModule = await smartenvInstance.getSafeNodeModule('ws'); WebSocketClass = wsModule.default || wsModule; } const socket = new WebSocketClass(socketUrl); this.currentSocket = socket; this.socketConnection = new SocketConnection({ alias: this.alias, authenticated: false, side: 'client', smartsocketHost: this, socket: socket, }); const socketConnection = this.socketConnection; // Increment attempt ID to invalidate any pending timers from previous attempts this.connectionAttemptId++; const currentAttemptId = this.connectionAttemptId; const timer = new plugins.smarttime.Timer(5000); timer.start(); timer.completed.then(() => { // Only fire timeout if this is still the current connection attempt if (currentAttemptId === this.connectionAttemptId && this.eventStatus !== 'connected') { this.updateStatus('timedOut'); logger.log('warn', 'connection to server timed out.'); this.disconnect(true); } }); // Handle connection open socket.addEventListener('open', () => { timer.reset(); }); // Handle messages socket.addEventListener('message', async (event) => { try { const data = typeof event.data === 'string' ? event.data : event.data.toString(); const message = JSON.parse(data); switch (message.type) { case 'authRequest': timer.reset(); const authRequestPayload = message.payload; logger.log('info', `server ${authRequestPayload.serverAlias} requested authentication`); this.remoteShortId = authRequestPayload.serverAlias; // Send authentication data socketConnection.sendMessage({ type: 'auth', payload: { alias: this.alias }, }); break; case 'authResponse': const authResponse = message.payload; if (authResponse.success) { logger.log('info', 'client is authenticated'); socketConnection.authenticated = true; } else { logger.log('warn', `authentication failed: ${authResponse.error}`); await this.disconnect(); } break; case 'serverReady': // Set up function request listening await socketConnection.listenToFunctionRequests(); // Handle retagging const oldTagStore = this.tagStore; this.tagStoreSubscription?.unsubscribe(); for (const keyArg of Object.keys(this.tagStore)) { socketConnection.addTag(this.tagStore[keyArg]); } this.tagStoreSubscription = socketConnection.tagStoreObservable.subscribe((tagStoreArg) => { this.tagStore = tagStoreArg; }); for (const tag of Object.keys(oldTagStore)) { await this.addTag(oldTagStore[tag]); } this.updateStatus('connected'); this.isConnecting = false; done.resolve(); break; default: // Other messages are handled by SocketConnection socketConnection.handleMessage(message); break; } } catch (err) { // Not a valid JSON message, ignore } }); // Handle disconnection and errors const closeHandler = async () => { // Only handle close if this is still the current socket and we're not already disconnecting if (this.currentSocket === socket && !this.disconnectRunning) { logger.log('info', `SocketConnection with >alias ${this.alias} on >side client disconnected`); await this.disconnect(true); } }; const errorHandler = async () => { if (this.currentSocket === socket && !this.disconnectRunning) { await this.disconnect(true); } }; socket.addEventListener('close', closeHandler); socket.addEventListener('error', errorHandler); return done.promise; } /** * disconnect from the server */ async disconnect(useAutoReconnectSetting = false) { if (this.disconnectRunning) { return; } this.disconnectRunning = true; this.isConnecting = false; this.updateStatus('disconnecting'); this.tagStoreSubscription?.unsubscribe(); // Store reference to current socket before cleanup const socketToClose = this.currentSocket; this.currentSocket = null; if (this.socketConnection) { await this.socketConnection.disconnect(); this.socketConnection = undefined; logger.log('ok', 'disconnected socket!'); } else if (!socketToClose) { this.disconnectRunning = false; logger.log('warn', 'tried to disconnect, without a SocketConnection'); return; } logger.log('warn', `disconnected from server ${this.remoteShortId}`); this.remoteShortId = null; if (this.autoReconnect && useAutoReconnectSetting && this.eventStatus !== 'connecting') { this.updateStatus('connecting'); // Check if we've exceeded the maximum number of retries if (this.currentRetryCount >= this.maxRetries) { logger.log('warn', `Maximum reconnection attempts (${this.maxRetries}) reached. Giving up.`); this.disconnectRunning = false; return; } // Increment retry counter this.currentRetryCount++; // Calculate backoff with jitter (±20% randomness) const jitter = this.currentBackoffDelay * 0.2 * (Math.random() * 2 - 1); const delay = Math.min(this.currentBackoffDelay + jitter, this.maxBackoffDelay); logger.log('info', `Reconnect attempt ${this.currentRetryCount}/${this.maxRetries} in ${Math.round(delay)}ms`); // Apply exponential backoff for next time (doubling with each attempt) this.currentBackoffDelay = Math.min(this.currentBackoffDelay * 2, this.maxBackoffDelay); await plugins.smartdelay.delayFor(delay); this.disconnectRunning = false; this.isReconnecting = true; await this.connect(); } else { this.disconnectRunning = false; } } /** * stops the client completely */ async stop() { this.autoReconnect = false; this.currentRetryCount = 0; this.currentBackoffDelay = this.initialBackoffDelay; await this.disconnect(); } /** * dispatches a server call * @param functionNameArg * @param dataArg */ async serverCall(functionNameArg, dataArg) { if (!this.socketConnection) { throw new Error('Cannot call server without an active socket connection'); } const socketRequest = new SocketRequest(this, { side: 'requesting', originSocketConnection: this.socketConnection, shortId: plugins.isounique.uni(), funcCallData: { funcName: functionNameArg, funcDataArg: dataArg, }, }); const response = await socketRequest.dispatch(); const result = response.funcDataArg; return result; } updateStatus(statusArg) { if (this.eventStatus !== statusArg) { this.eventSubject.next(statusArg); } this.eventStatus = statusArg; // Reset reconnection state when connection is successful if (statusArg === 'connected') { this.currentRetryCount = 0; this.currentBackoffDelay = this.initialBackoffDelay; } } /** * Resets the reconnection state */ resetReconnectionState() { this.currentRetryCount = 0; this.currentBackoffDelay = this.initialBackoffDelay; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRzb2NrZXQuY2xhc3Nlcy5zbWFydHNvY2tldGNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0c29ja2V0LmNsYXNzZXMuc21hcnRzb2NrZXRjbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSwwQkFBMEIsQ0FBQztBQUNwRCxPQUFPLEtBQUssWUFBWSxNQUFNLCtCQUErQixDQUFDO0FBQzlELE9BQU8sS0FBSyxVQUFVLE1BQU0sdUJBQXVCLENBQUM7QUFFcEQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDN0UsT0FBTyxFQUVMLGNBQWMsR0FDZixNQUFNLHlDQUF5QyxDQUFDO0FBQ2pELE9BQU8sRUFBaUMsYUFBYSxFQUFFLE1BQU0sd0NBQXdDLENBQUM7QUFDdEcsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBZWxELE1BQU0sT0FBTyxpQkFBaUI7SUE2QjVCOztPQUVHO0lBQ0ksS0FBSyxDQUFDLE1BQU0sQ0FBQyxNQUF1QjtRQUN6QyxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1lBQzFCLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUM3QyxDQUFDO2FBQU0sQ0FBQztZQUNOLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQztRQUNwQyxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7T0FHRztJQUNJLEtBQUssQ0FBQyxVQUFVLENBQUMsUUFBK0I7UUFDckQsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2pDLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxhQUFhLENBQUMsUUFBK0I7UUFDeEQsSUFBSSxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUMx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