@appsensorlike/appsensorlike_websocket
Version:
Base class/types utilized by websocket client/server
177 lines (176 loc) • 7.15 kB
JavaScript
import { Utils } from "@appsensorlike/appsensorlike/utils/Utils.js";
import { Logger } from "@appsensorlike/appsensorlike/logging/logging.js";
import { ActionRequest, ActionResponse, UUID_QUERY_PARAM, ACTION_CONFIG } from "../appsensor-websocket.js";
import EventEmitter from "events";
import WebSocket from "ws";
import { v4 as uuidv4 } from 'uuid';
class WebSocketClientConfig {
constructor() {
this.address = '';
this.reconnectOnConnectionLost = true;
this.reconnectRetryInterval = WebSocketClientConfig.DEFAULT_RETRY_INTERVAL;
}
checkValidInitialize() {
if (this.reconnectOnConnectionLost === true) { //mind undefined value
if (!this.reconnectRetryInterval) {
this.reconnectRetryInterval = WebSocketClientConfig.DEFAULT_RETRY_INTERVAL;
}
}
}
}
WebSocketClientConfig.DEFAULT_RETRY_INTERVAL = 20000;
class AppSensorWebSocketClient {
constructor(config) {
this.socket = null;
this.reconnectTimer = null;
this.accessDenied = false;
this.eventEmmiter = new EventEmitter();
this.config = config;
this.myUUID = uuidv4();
this.address = config.address + '?' + UUID_QUERY_PARAM + '=' + this.myUUID;
}
async connect(configParameters) {
let result = false;
try {
result = await new Promise((resolve, reject) => {
this.socket = new WebSocket(this.address, this.config.options);
this.socket.on('error', this.onError.bind(this));
this.socket.on('close', this.onClose.bind(this));
this.socket.on('message', this.onMessage.bind(this));
this.socket.on('open', this.onOpen.bind(this));
this.socket.on('open', () => {
resolve(true);
});
this.socket.on('error', (err) => {
reject(err);
});
});
await this.sendConfigMsg(configParameters);
}
catch (error) {
result = false;
Logger.getClientLogger().error(error);
}
return result;
}
async sendConfigMsg(configParameters) {
const request = this.createRequest(ACTION_CONFIG, configParameters);
await this.sendRequest(request);
}
onOpen() {
if (this.reconnectTimer) {
clearInterval(this.reconnectTimer);
this.reconnectTimer = null;
Logger.getClientLogger().info('AppSensorWebSocketClient.onOpen:', 'Reconnected');
}
Logger.getClientLogger().trace(`AppSensorWebSocketClient.socket: ${this.myUUID}:`, 'open');
}
onError(error) {
Logger.getClientLogger().error(`AppSensorWebSocketClient.socket: ${this.myUUID}:`, 'error:', error);
}
onClose(code, reason) {
Logger.getClientLogger().trace(`AppSensorWebSocketClient.socket: ${this.myUUID}:`, 'close:', ' CODE: ', code, ' REASON: ', reason.toString());
if (code !== 1005 &&
this.config.reconnectOnConnectionLost &&
!this.reconnectTimer) {
this.reconnectTimer = setInterval(this.reconnect.bind(this), this.config.reconnectRetryInterval);
}
}
async reconnect() {
Logger.getClientLogger().info('AppSensorWebSocketClient.reconnect:', 'Retry reconnect...');
await this.connect();
}
onMessage(data, isBinary) {
Logger.getClientLogger().trace(`AppSensorWebSocketClient.socket: ${this.myUUID}:`, 'message');
const response = JSON.parse(data.toString());
Object.setPrototypeOf(response, ActionResponse.prototype);
if (response.accessDenied) {
this.accessDenied = true;
Logger.getClientLogger().warn('Access denied for this client application! Configure server!');
}
else if (response.unauthorizedAction) {
Logger.getClientLogger().warn(`This client application is not authorized to perform '${response.actionName}' on server! Configure server!`);
this.onServerResponse(response);
}
else {
this.onServerResponse(response);
}
}
onServerResponse(response) {
const responseStatus = response.error ? 'Error' : 'OK';
Logger.getClientLogger().trace('AppSensorWebSocketClient.onServerResponse: ', responseStatus);
this.eventEmmiter.emit(response.id, response);
}
createRequest(actionName, parameters) {
const uuid = uuidv4();
return new ActionRequest(uuid, actionName, parameters);
}
async sendRequest(request) {
let waited = 0;
const timeout = 500;
if (!this.socket) {
throw new Error("socket cannot be null!");
}
while (this.socket.readyState === WebSocket.CONNECTING && waited < 5000) {
await Utils.sleep(timeout);
waited += timeout;
}
await new Promise((resolve, reject) => {
if (!this.socket) {
reject(new Error("socket cannot be null!"));
return;
}
if (this.socket.readyState === WebSocket.CONNECTING) {
reject(new Error('WebSocket in CONNECTING state for too long!'));
}
else if (this.socket.readyState === WebSocket.CLOSING) {
reject(new Error('WebSocket in CLOSING state!'));
}
else if (this.socket.readyState === WebSocket.CLOSED) {
reject(new Error('WebSocket has already been closed!'));
}
else if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(request), (err) => {
if (err) {
reject(err);
return;
}
resolve();
});
}
});
}
async addRequest(request) {
const promise = new Promise((resolve, reject) => {
this.eventEmmiter.addListener(request.id, (response) => {
this.eventEmmiter.removeAllListeners(request.id);
if (response.error) {
const serverError = new Error(response.error);
Logger.getClientLogger().error('Server error:', serverError);
reject(serverError);
}
else {
resolve(response.result);
}
});
});
return await this.sendRequest(request)
.then((res) => {
return promise;
})
.catch((error) => {
Logger.getClientLogger().error('Communication error:', error);
return Promise.reject(error);
});
}
async closeSocket() {
if (this.socket) {
Logger.getClientLogger().info('AppSensorWebSocketClient.closeSocket');
this.socket.close();
if (this.reconnectTimer) {
clearInterval(this.reconnectTimer);
}
}
}
}
export { AppSensorWebSocketClient, WebSocketClientConfig };