socket-actions
Version:
Websocket implementation to simplify communication and queueing of user actions.
197 lines (194 loc) • 7.21 kB
JavaScript
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 };