UNPKG

socket-actions

Version:

Websocket implementation to simplify communication and queueing of user actions.

197 lines (194 loc) 7.21 kB
import { createHash } from 'crypto'; import listenerFactory from './helpers/listenerFactory.mjs'; const defaultOnAuthResponse = ({ data }) => { if (data !== "Authenticated") { throw new Error(data); } }; const defaultOptions = { url: "ws://localhost:3000", connectionTryLimit: 0, secondsBetweenRetries: 5, }; class Client { constructor(options = {}) { var _a, _b, _c, _d; this._socket = null; this.connectionTries = 0; this._isAuthenticated = false; this._isConnected = false; this.requests = {}; this.connectionTryLimit = (_a = options.connectionTryLimit) !== null && _a !== void 0 ? _a : defaultOptions.secondsBetweenRetries; this.secondsBetweenRetries = (_b = options.secondsBetweenRetries) !== null && _b !== void 0 ? _b : defaultOptions.secondsBetweenRetries; let { authentication } = options; if (authentication !== undefined && typeof authentication === "object") { authentication = JSON.stringify(authentication); } this._authentication = authentication; this.preparedOnAuthResponse = listenerFactory(this, null, this.authResponse); this.preparedOnMessageResponse = listenerFactory(this, null, this.messageResponse); this.onAuthResponse = (_c = options.onAuthResponse) !== null && _c !== void 0 ? _c : defaultOnAuthResponse; this.onOpen = options.onOpen; this.onClose = options.onClose; this.onAuthFailure = options.onAuthFailure; this.onMessage = options.onMessage; this.url = (_d = options.url) !== null && _d !== void 0 ? _d : defaultOptions.url; this.protocols = options.protocols; this.connect(); } connect() { this._socket = new WebSocket(this.url, this.protocols); this._socket.addEventListener("open", listenerFactory(this, null, this.opening)); this._socket.addEventListener("close", listenerFactory(this, null, this.closing)); } reconnect() { if (this._isConnected) { this.close(); } this.connect(); } async opening() { const { _authentication: authentication } = this; if (authentication !== undefined) { this.tryAuth(); return; } this._isConnected = true; this.enableMessageReceiver(); if (this.onOpen !== undefined) { await this.onOpen(); } this.connectionTries = 0; } async closing() { this._isConnected = false; this._isAuthenticated = false; if (this.connectionTries < this.connectionTryLimit) { this.connectionTries++; console.log(`Connection to server lost. Reconnecting in ${this.secondsBetweenRetries} seconds...`); console.log(`(Attempt ${this.connectionTries} of ${this.connectionTryLimit})`); setTimeout(() => { this.reconnect(); }, this.secondsBetweenRetries * 1000); return; } if (this.onClose !== undefined) { await this.onClose(); } } close(code, reason) { var _a; this._isConnected = false; this._isAuthenticated = false; this.connectionTries = this.connectionTryLimit; (_a = this._socket) === null || _a === void 0 ? void 0 : _a.close(code, reason); } enableMessageReceiver() { var _a; (_a = this._socket) === null || _a === void 0 ? void 0 : _a.addEventListener("message", this.preparedOnMessageResponse); } disableMessageReceiver() { var _a; (_a = this._socket) === null || _a === void 0 ? void 0 : _a.removeEventListener("message", this.preparedOnMessageResponse); } async authResponse(message) { var _a; try { if (this.onAuthResponse !== undefined) { await this.onAuthResponse(message); } if (message.data === "Failed Authentication") { throw new Error(message.data); } this._isAuthenticated = true; (_a = this._socket) === null || _a === void 0 ? void 0 : _a.removeEventListener("message", this.preparedOnAuthResponse); this.enableMessageReceiver(); if (this.onOpen !== undefined) { await this.onOpen(); } } catch (err) { if (this.onAuthFailure !== undefined) { await this.onAuthFailure(message); } } } async messageResponse(message) { if (message.data.includes("requestId")) { try { const { requestId, data } = JSON.parse(message.data); if (requestId) { const request = this.requests[requestId]; if (request) { request(data); } return; } } catch (err) { } } if (this.onMessage !== undefined) { await this.onMessage(message); } } get authentication() { return this._authentication; } get isAuthenticated() { return this._isAuthenticated; } get isConnected() { return this._isConnected; } get socket() { return this._socket; } tryAuth(authentication) { var _a, _b; if (this.isAuthenticated) { console.warn("Already logged in. Execution of tryAuth blocked."); return; } if (authentication !== undefined) { this._authentication = authentication; } this.disableMessageReceiver(); (_a = this._socket) === null || _a === void 0 ? void 0 : _a.addEventListener("message", this.preparedOnAuthResponse); (_b = this._socket) === null || _b === void 0 ? void 0 : _b.send(this._authentication); } sendAction(path, data, extraDetails = {}) { var _a; const messageObj = Object.assign(Object.assign({}, extraDetails), { path }); if (data !== undefined) { messageObj.data = data; } const message = JSON.stringify(messageObj); (_a = this._socket) === null || _a === void 0 ? void 0 : _a.send(message); } sendRequest(path, data, timeout = 5000) { const requestId = createHash("md5").update(Date.now().toString()) .digest("hex"); return new Promise((resolve, reject) => { let resolved = false; let timeoutHolder; this.requests[requestId] = (message) => { resolved = true; delete this.requests[requestId]; clearTimeout(timeoutHolder); resolve(message); }; timeoutHolder = setTimeout(() => { if (resolved) { return; } delete this.requests[requestId]; reject(new Error("Request timed out.")); }, timeout); this.sendAction(path, data, { requestId, }); }); } } export { Client as default };