Files
smartsocket/ts/smartsocket.classes.socketconnection.ts

312 lines
9.5 KiB
TypeScript
Raw Normal View History

2022-03-14 22:40:55 +01:00
import * as plugins from './smartsocket.plugins.js';
import * as pluginsTyped from './smartsocket.pluginstyped.js';
import * as interfaces from './interfaces/index.js';
2016-08-08 18:20:00 +02:00
// import classes
2022-03-14 22:40:55 +01:00
import { Smartsocket } from './smartsocket.classes.smartsocket.js';
import { SocketFunction } from './smartsocket.classes.socketfunction.js';
2023-07-21 03:53:41 +02:00
import { SocketRequest, type ISocketRequestDataObject } from './smartsocket.classes.socketrequest.js';
2018-03-15 02:29:40 +01:00
// socket.io
2022-03-14 22:40:55 +01:00
import { SmartsocketClient } from './smartsocket.classes.smartsocketclient.js';
import { logger } from './smartsocket.logging.js';
2016-08-08 18:20:00 +02:00
2016-08-09 11:42:21 +02:00
// export interfaces
/**
* defines is a SocketConnection is server or client side. Important for mesh setups.
*/
2018-03-15 02:29:40 +01:00
export type TSocketConnectionSide = 'server' | 'client';
2016-08-09 11:42:21 +02:00
/**
* interface for constructor of class SocketConnection
*/
2016-08-12 01:32:57 +02:00
export interface ISocketConnectionConstructorOptions {
2018-03-15 02:29:40 +01:00
alias: string;
authenticated: boolean;
side: TSocketConnectionSide;
2019-08-12 22:31:40 +02:00
smartsocketHost: Smartsocket | SmartsocketClient;
socket: WebSocket | pluginsTyped.ws.WebSocket;
2017-07-07 22:02:19 +02:00
}
2016-08-08 18:20:00 +02:00
2016-08-09 11:42:21 +02:00
/**
* interface for authentication data
*/
export interface ISocketConnectionAuthenticationObject {
2022-01-19 15:34:52 +01:00
alias: string;
2017-07-07 22:02:19 +02:00
}
2016-08-09 11:42:21 +02:00
// export classes
2020-09-24 18:03:01 +00:00
export let allSocketConnections = new plugins.lik.ObjectMap<SocketConnection>();
2016-08-09 11:42:21 +02:00
/**
* class SocketConnection represents a websocket connection
*/
2016-08-08 18:20:00 +02:00
export class SocketConnection {
2019-06-07 08:40:24 +02:00
public alias: string;
public side: TSocketConnectionSide;
public authenticated: boolean = false;
2019-08-12 22:31:40 +02:00
public smartsocketRef: Smartsocket | SmartsocketClient;
public socket: WebSocket | pluginsTyped.ws.WebSocket;
2019-11-07 00:26:47 +01:00
2019-11-08 18:41:08 +01:00
public eventSubject = new plugins.smartrx.rxjs.Subject<interfaces.TConnectionStatus>();
public eventStatus: interfaces.TConnectionStatus = 'new';
2019-11-07 00:26:47 +01:00
2021-01-28 01:30:27 +00:00
private tagStore: interfaces.TTagStore = {};
public tagStoreObservable = new plugins.smartrx.rxjs.Subject<interfaces.TTagStore>();
public remoteTagStoreObservable = new plugins.smartrx.rxjs.Subject<interfaces.TTagStore>();
2018-03-15 02:29:40 +01:00
constructor(optionsArg: ISocketConnectionConstructorOptions) {
this.alias = optionsArg.alias;
this.authenticated = optionsArg.authenticated;
this.side = optionsArg.side;
2019-08-12 22:31:40 +02:00
this.smartsocketRef = optionsArg.smartsocketHost;
2018-03-15 02:29:40 +01:00
this.socket = optionsArg.socket;
2016-08-12 03:22:36 +02:00
2017-07-07 22:02:19 +02:00
// standard behaviour that is always true
2018-03-15 02:29:40 +01:00
allSocketConnections.add(this);
}
2019-11-08 18:48:39 +01:00
/**
* Sends a message through the socket
*/
public sendMessage(message: interfaces.ISocketMessage): void {
if (this.socket.readyState === 1) { // WebSocket.OPEN
this.socket.send(JSON.stringify(message));
}
}
/**
* Handles incoming messages
*/
public handleMessage(messageData: interfaces.ISocketMessage): void {
switch (messageData.type) {
case 'function':
this.handleFunctionCall(messageData);
break;
case 'functionResponse':
this.handleFunctionResponse(messageData);
break;
case 'tagUpdate':
this.handleTagUpdate(messageData);
break;
default:
// Authentication messages are handled by the server/client classes
break;
}
}
private handleFunctionCall(messageData: interfaces.ISocketMessage): void {
const requestData: ISocketRequestDataObject<any> = {
funcCallData: {
funcName: messageData.payload.funcName,
funcDataArg: messageData.payload.funcData,
},
shortId: messageData.id,
};
const referencedFunction: SocketFunction<any> =
this.smartsocketRef.socketFunctions.findSync((socketFunctionArg) => {
return socketFunctionArg.name === requestData.funcCallData.funcName;
});
if (referencedFunction) {
const localSocketRequest = new SocketRequest(this.smartsocketRef, {
side: 'responding',
originSocketConnection: this,
shortId: requestData.shortId,
funcCallData: requestData.funcCallData,
});
localSocketRequest.createResponse();
} else {
logger.log('warn', `function ${requestData.funcCallData.funcName} not found or out of scope`);
}
}
private handleFunctionResponse(messageData: interfaces.ISocketMessage): void {
const responseData: ISocketRequestDataObject<any> = {
funcCallData: {
funcName: messageData.payload.funcName,
funcDataArg: messageData.payload.funcData,
},
shortId: messageData.id,
};
const targetSocketRequest = SocketRequest.getSocketRequestById(
this.smartsocketRef,
responseData.shortId
);
if (targetSocketRequest) {
targetSocketRequest.handleResponse(responseData);
}
}
private handleTagUpdate(messageData: interfaces.ISocketMessage): void {
const tagStoreArg = messageData.payload.tags as interfaces.TTagStore;
if (!plugins.smartjson.deepEqualObjects(this.tagStore, tagStoreArg)) {
this.tagStore = tagStoreArg;
// Echo back to confirm
this.sendMessage({
type: 'tagUpdate',
payload: { tags: this.tagStore },
});
this.tagStoreObservable.next(this.tagStore);
}
this.remoteTagStoreObservable.next(tagStoreArg);
2017-07-07 22:02:19 +02:00
}
2016-08-12 03:22:36 +02:00
2021-01-28 01:30:27 +00:00
/**
* 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) => {
2022-01-19 07:01:58 +01:00
if (!remoteTagStore[tagArg.id]) {
return;
}
2021-01-28 01:30:27 +00:00
const localTagString = plugins.smartjson.stringify(tagArg);
2021-01-28 01:31:42 +00:00
const remoteTagString = plugins.smartjson.stringify(remoteTagStore[tagArg.id]);
2021-01-28 01:30:27 +00:00
if (localTagString === remoteTagString) {
remoteSubscription.unsubscribe();
done.resolve();
}
2021-01-28 01:31:42 +00:00
});
this.sendMessage({
type: 'tagUpdate',
payload: { tags: this.tagStore },
});
2021-01-28 01:30:27 +00:00
await done.promise;
}
/**
* gets a tag by id
* @param tagIdArg
*/
public async getTagById(tagIdArg: interfaces.ITag['id']) {
return this.tagStore[tagIdArg];
2021-01-28 01:31:42 +00:00
}
2021-01-28 01:30:27 +00:00
/**
* removes a tag from a connection
*/
public async removeTagById(tagIdArg: interfaces.ITag['id']) {
delete this.tagStore[tagIdArg];
this.tagStoreObservable.next(this.tagStore);
this.sendMessage({
type: 'tagUpdate',
payload: { tags: this.tagStore },
});
2021-01-28 01:30:27 +00:00
}
2017-07-07 22:02:19 +02:00
// authenticating --------------------------
2016-08-08 18:20:00 +02:00
2017-07-07 22:02:19 +02:00
/**
* authenticate the socket (server side)
2017-07-07 22:02:19 +02:00
*/
public authenticate(): Promise<SocketConnection> {
const done = plugins.smartpromise.defer<SocketConnection>();
// Set up message handler for authentication
const messageHandler = (event: MessageEvent | { data: string }) => {
try {
const data = typeof event.data === 'string' ? event.data : event.data.toString();
const message: interfaces.ISocketMessage = JSON.parse(data);
if (message.type === 'auth') {
const authData = message.payload as interfaces.IAuthPayload;
logger.log('info', 'received authentication data...');
if (authData.alias) {
this.alias = authData.alias;
this.authenticated = true;
// Send authentication response
this.sendMessage({
type: 'authResponse',
payload: { success: true },
});
logger.log('ok', `socket with >>alias ${this.alias} is authenticated!`);
done.resolve(this);
} else {
this.authenticated = false;
this.sendMessage({
type: 'authResponse',
payload: { success: false, error: 'No alias provided' },
});
this.disconnect();
done.reject('a socket tried to connect, but could not authenticate.');
}
}
} catch (err) {
// Not a valid message, ignore
2017-07-07 22:02:19 +02:00
}
2019-11-07 00:26:47 +01:00
};
this.socket.addEventListener('message', messageHandler as any);
// Request authentication
const requestAuthPayload: interfaces.TAuthRequestMessage = {
type: 'authRequest',
payload: {
serverAlias: (this.smartsocketRef as Smartsocket).alias,
},
};
this.sendMessage(requestAuthPayload);
2018-03-15 02:29:40 +01:00
return done.promise;
2017-07-07 22:02:19 +02:00
}
2016-08-12 03:22:36 +02:00
2017-07-07 22:02:19 +02:00
// listening -------------------------------
/**
* listen to function requests
*/
2019-08-12 22:31:40 +02:00
public listenToFunctionRequests() {
const done = plugins.smartpromise.defer();
2017-07-07 22:02:19 +02:00
if (this.authenticated) {
// Set up message handler for all messages
const messageHandler = (event: MessageEvent | { data: string }) => {
try {
const data = typeof event.data === 'string' ? event.data : event.data.toString();
const message: interfaces.ISocketMessage = JSON.parse(data);
this.handleMessage(message);
} catch (err) {
// Not a valid JSON message, ignore
2017-07-07 22:02:19 +02:00
}
};
2021-01-28 01:30:27 +00:00
this.socket.addEventListener('message', messageHandler as any);
2021-01-28 01:30:27 +00:00
2022-12-28 13:52:16 +01:00
logger.log(
'info',
`now listening to function requests for ${this.alias} on side ${this.side}`
);
2018-03-15 02:29:40 +01:00
done.resolve(this);
2017-07-07 22:02:19 +02:00
} else {
2019-08-12 22:31:40 +02:00
const errMessage = 'socket needs to be authenticated first';
2020-09-24 18:03:01 +00:00
logger.log('error', errMessage);
2018-03-15 02:29:40 +01:00
done.reject(errMessage);
2017-07-07 22:02:19 +02:00
}
2018-03-15 02:29:40 +01:00
return done.promise;
2017-07-07 22:02:19 +02:00
}
2019-11-07 00:26:47 +01:00
// disconnecting ----------------------
public async disconnect() {
if (this.socket.readyState === 1 || this.socket.readyState === 0) {
this.socket.close();
}
allSocketConnections.remove(this);
2019-11-08 18:41:08 +01:00
this.updateStatus('disconnected');
}
2020-09-24 18:04:11 +00:00
private updateStatus(statusArg: interfaces.TConnectionStatus) {
2019-11-08 18:41:08 +01:00
if (this.eventStatus !== statusArg) {
this.eventSubject.next(statusArg);
}
this.eventStatus = statusArg;
2019-11-07 00:26:47 +01:00
}
2017-07-07 22:02:19 +02:00
}