smartsocket/ts/smartsocket.classes.socketconnection.ts
2023-07-21 03:53:41 +02:00

225 lines
7.5 KiB
TypeScript

import * as plugins from './smartsocket.plugins.js';
import * as pluginsTyped from './smartsocket.pluginstyped.js';
import * as interfaces from './interfaces/index.js';
// import classes
import { Smartsocket } from './smartsocket.classes.smartsocket.js';
import { SocketFunction } from './smartsocket.classes.socketfunction.js';
import { SocketRequest, type ISocketRequestDataObject } from './smartsocket.classes.socketrequest.js';
// socket.io
import { SmartsocketClient } from './smartsocket.classes.smartsocketclient.js';
import { logger } from './smartsocket.logging.js';
// export interfaces
/**
* defines is a SocketConnection is server or client side. Important for mesh setups.
*/
export type TSocketConnectionSide = 'server' | 'client';
/**
* interface for constructor of class SocketConnection
*/
export interface ISocketConnectionConstructorOptions {
alias: string;
authenticated: boolean;
side: TSocketConnectionSide;
smartsocketHost: Smartsocket | SmartsocketClient;
socket: pluginsTyped.socketIo.Socket | pluginsTyped.socketIoClient.Socket;
}
/**
* interface for authentication data
*/
export interface ISocketConnectionAuthenticationObject {
alias: string;
}
// export classes
export let allSocketConnections = new plugins.lik.ObjectMap<SocketConnection>();
/**
* class SocketConnection represents a websocket connection
*/
export class SocketConnection {
public alias: string;
public side: TSocketConnectionSide;
public authenticated: boolean = false;
public smartsocketRef: Smartsocket | SmartsocketClient;
public socket: pluginsTyped.socketIo.Socket | pluginsTyped.socketIoClient.Socket;
public eventSubject = new plugins.smartrx.rxjs.Subject<interfaces.TConnectionStatus>();
public eventStatus: interfaces.TConnectionStatus = 'new';
private tagStore: interfaces.TTagStore = {};
public tagStoreObservable = new plugins.smartrx.rxjs.Subject<interfaces.TTagStore>();
public remoteTagStoreObservable = new plugins.smartrx.rxjs.Subject<interfaces.TTagStore>();
constructor(optionsArg: ISocketConnectionConstructorOptions) {
this.alias = optionsArg.alias;
this.authenticated = optionsArg.authenticated;
this.side = optionsArg.side;
this.smartsocketRef = optionsArg.smartsocketHost;
this.socket = optionsArg.socket;
// standard behaviour that is always true
allSocketConnections.add(this);
// handle connection
this.socket.on('connect', async () => {
this.updateStatus('connected');
});
this.socket.on('disconnect', async () => {
logger.log(
'info',
`SocketConnection with >alias ${this.alias} on >side ${this.side} disconnected`
);
await this.disconnect();
allSocketConnections.remove(this);
this.eventSubject.next('disconnected');
});
}
/**
* adds a tag to a connection
*/
public async addTag(tagArg: interfaces.ITag) {
const done = plugins.smartpromise.defer();
this.tagStore[tagArg.id] = tagArg;
this.tagStoreObservable.next(this.tagStore);
const remoteSubscription = this.remoteTagStoreObservable.subscribe((remoteTagStore) => {
if (!remoteTagStore[tagArg.id]) {
return;
}
const localTagString = plugins.smartjson.stringify(tagArg);
const remoteTagString = plugins.smartjson.stringify(remoteTagStore[tagArg.id]);
if (localTagString === remoteTagString) {
remoteSubscription.unsubscribe();
done.resolve();
}
});
this.socket.emit('updateTagStore', this.tagStore);
await done.promise;
}
/**
* gets a tag by id
* @param tagIdArg
*/
public async getTagById(tagIdArg: interfaces.ITag['id']) {
return this.tagStore[tagIdArg];
}
/**
* removes a tag from a connection
*/
public async removeTagById(tagIdArg: interfaces.ITag['id']) {
delete this.tagStore[tagIdArg];
this.tagStoreObservable.next(this.tagStore);
this.socket.emit('updateTagStore', this.tagStore);
}
// authenticating --------------------------
/**
* authenticate the socket
*/
public authenticate() {
const done = plugins.smartpromise.defer();
this.socket.on('dataAuth', async (dataArg: ISocketConnectionAuthenticationObject) => {
logger.log('info', 'received authentication data...');
this.socket.removeAllListeners('dataAuth');
if (dataArg.alias) {
// TODO: authenticate password
this.alias = dataArg.alias;
this.authenticated = true;
this.socket.emit('authenticated');
logger.log('ok', `socket with >>alias ${this.alias} is authenticated!`);
done.resolve(this);
} else {
this.authenticated = false;
await this.disconnect();
done.reject('a socket tried to connect, but could not authenticated.');
}
});
const requestAuthPayload: interfaces.IRequestAuthPayload = {
serverAlias: this.smartsocketRef.alias,
};
this.socket.emit('requestAuth', requestAuthPayload);
return done.promise;
}
// listening -------------------------------
/**
* listen to function requests
*/
public listenToFunctionRequests() {
const done = plugins.smartpromise.defer();
if (this.authenticated) {
this.socket.on('function', (dataArg: ISocketRequestDataObject<any>) => {
// check if requested function is available to the socket's scope
// logger.log('info', 'function request received');
const referencedFunction: SocketFunction<any> =
this.smartsocketRef.socketFunctions.findSync((socketFunctionArg) => {
return socketFunctionArg.name === dataArg.funcCallData.funcName;
});
if (referencedFunction) {
// logger.log('ok', 'function in access scope');
const localSocketRequest = new SocketRequest(this.smartsocketRef, {
side: 'responding',
originSocketConnection: this,
shortId: dataArg.shortId,
funcCallData: dataArg.funcCallData,
});
localSocketRequest.createResponse(); // takes care of creating response and sending it back
} else {
logger.log('warn', 'function not existent or out of access scope');
}
});
this.socket.on('functionResponse', (dataArg: ISocketRequestDataObject<any>) => {
// logger.log('info', `received response for request with id ${dataArg.shortId}`);
const targetSocketRequest = SocketRequest.getSocketRequestById(
this.smartsocketRef,
dataArg.shortId
);
targetSocketRequest.handleResponse(dataArg);
});
this.socket.on('updateTagStore', async (tagStoreArg: interfaces.TTagStore) => {
if (!plugins.smartjson.deepEqualObjects(this.tagStore, tagStoreArg)) {
this.tagStore = tagStoreArg;
this.socket.emit('updateTagStore', this.tagStore);
this.tagStoreObservable.next(this.tagStore);
}
this.remoteTagStoreObservable.next(tagStoreArg);
});
logger.log(
'info',
`now listening to function requests for ${this.alias} on side ${this.side}`
);
done.resolve(this);
} else {
const errMessage = 'socket needs to be authenticated first';
logger.log('error', errMessage);
done.reject(errMessage);
}
return done.promise;
}
// disconnecting ----------------------
public async disconnect() {
this.socket.disconnect(true);
this.updateStatus('disconnected');
}
private updateStatus(statusArg: interfaces.TConnectionStatus) {
if (this.eventStatus !== statusArg) {
this.eventSubject.next(statusArg);
}
this.eventStatus = statusArg;
}
}