@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
JavaScript
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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