import * as SocketIO from 'socket.io'; import { DyFM_AnyError, DyFM_Array, DyFM_Error, DyFM_Error_Settings, DyFM_errorFlag, DyFM_Log } from '@futdevpro/fsm-dynamo'; import { DyFM_SocketEvent, DyFM_SocketEvent_Key, DyFM_SocketEventPreprocessTask, DyFM_SocketEventTask } from '@futdevpro/fsm-dynamo/socket'; import { DyNTS_global_settings } from '../../../_collections/global-settings.const'; import { DyNTS_SingletonService } from '../../../_services/base/singleton.service'; import { DyNTS_SocketSecurity } from '../_enums/socket-security.enum'; import { DyNTS_SocketPresence } from '../_models/socket-presence.control-model'; import { DyNTS_SocketServerService_Params } from '../_models/socket-server-service-params.control-model'; /** Opciók a setupSocketServer híváshoz: attacholt HTTP szerver esetén ne hívjuk a listen()-t. */ export interface DyNTS_SocketServerSetupOptions { /** Ha true, a Socket.IO egy már listenelő HTTP/HTTPS szerverre van attacholva – ne hívjuk a listen()-t. */ attachedToExistingServer?: boolean; /** A tényleges port (pl. defaultolt httpPort/httpsPort) – logokban használjuk. */ effectivePort?: number; } /** * You need to define the following functions: * ```ts * getServiceParams(): DyNTS_SocketServerServiceParams; * * getIncomingEvents(): DyFM_SocketEvent[]; * * getPresenceFromSubscriptionEventContent( * socketSubscription: T_SubscriptionContent, * socket: SocketIO.Socket * ): Promise; * ``` */ export abstract class DyNTS_SocketServerService< T_Presence extends DyNTS_SocketPresence = DyNTS_SocketPresence, T_SubscriptionContent = any, T_ErrorContent = any > extends DyNTS_SingletonService { protected params: DyNTS_SocketServerService_Params; get name(): string { return this.params.name; } get port(): number | undefined { return this.params.port; } get security(): DyNTS_SocketSecurity { return this.params.security; } get activeSubscriptionsCount(): number { return this.presences.length; } protected openSocketServer: SocketIO.Server; protected secureSocketServer: SocketIO.Server; /* private connectEvent: DyFM_SocketEvent; */ protected incomingEvents: DyFM_SocketEvent[]; /* private subscriptionEvent: DyFM_SocketEvent; */ /* private unsubscribeEvent: DyFM_SocketEvent; */ protected presences: T_Presence[] = []; get logSetup(): boolean { return DyNTS_global_settings.log_settings.setup; } get highDetailedLogs(): boolean { return DyNTS_global_settings.log_settings.highDetailedLogs; } override readonly defaultErrorUserMsg = `We encountered an unhandled Socket Server Error, ` + `\nplease contact the responsible development team.`; /* readonly thisLocationStack: string; */ protected constructor(){ super(); /* this.thisLocationStack = DyNTS_getStackLocation(); */ this.asyncConstructor().catch((error: any) => { DyFM_Log.error( `Socket Server Service setup failed: "${this.params?.name}" (${this.params?.port})`, error ); }); } /** * You need to setup a function that converts subscription event content to presence. * You should do authentication and authorization here. */ protected abstract getPresenceFromSubscriptionEventContent( socketSubscription: T_SubscriptionContent, socket: SocketIO.Socket ): Promise; private async asyncConstructor(): Promise { try { this.params = this.getServiceParams(); this.incomingEvents = this.getIncomingEvents() ?? []; await this.prepareEvents(); if (this.logSetup) console.log( `Socket Server Service setup done: "${this.params.name}" (${this.params.port}) ` + `security: ${this.params.security}\n` ); } catch (error) { DyFM_Log.error( `Socket Server Service setup failed: "${this.params?.name}" (${this.params?.port})`, error ); throw new DyFM_Error({ errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-GPFSRC0`, error: error, }); } } /** * You must setup events and required services for this function */ private async prepareEvents(): Promise { try { /** Setup connect event */ /* const connectEventIndex = this.incomingEvents.findIndex( (event: DyFM_SocketEvent) => event.eventKey === DyFM_SocketEvent_Key.connection ); if (0 <= connectEventIndex) { this.connectEvent = this.incomingEvents.splice(connectEventIndex)[0]; } else { this.connectEvent = new DyFM_SocketEvent({ eventKey: DyFM_SocketEvent_Key.connection, }); } this.connectEvent.serviceName = this.params.name; */ if (!this.getPresenceFromSubscriptionEventContent) { throw new DyFM_Error({ error: new Error('getPresenceFromSubscriptionEventContent must be set'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-PPE1`, }); } if (!this.getSubscriptionTasks) { DyFM_Log.warn( `You should not set the subscription event, ` + `in case you need additional steps for your subscriptions.`, `${this.params.name} (${this.params.port})` ); } if (this.incomingEvents.find( (event: DyFM_SocketEvent) => event.eventKey === DyFM_SocketEvent_Key.subscribe )) { DyFM_Log.warn( `You need to use getSubscriptionTasks() ` + `instead of setting the subscribe event in getIncomingEvents().`, `${this.params.name} (${this.params.port})` ); } if (!this.incomingEvents.find( (event: DyFM_SocketEvent) => event.eventKey === DyFM_SocketEvent_Key.unsubscribe )) { DyFM_Log.warn( `You should not set the unsubscribe event, ` + `in case you need additional steps for your unsubscribe.`, `${this.params.name} (${this.params.port})` ); } if (this.incomingEvents.find( (event: DyFM_SocketEvent) => !event.eventKey )) { throw new DyFM_Error({ error: new Error('eventKey must be set'), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-PPE2`, }); } this.incomingEvents.push( new DyFM_SocketEvent({ eventKey: DyFM_SocketEvent_Key.disconnect, tasks: [ async (socket: SocketIO.Socket, issuer: string) => this.disconnectBaseTask(socket, issuer), ], }) ); if (this.errorHandling) { this.incomingEvents.push(new DyFM_SocketEvent({ serviceName: this.params.name, eventKey: DyFM_SocketEvent_Key.error, tasks: [ async (content: any) => this.errorHandling(content), ], })); } else { DyFM_Log.warn( `You should not set the error event, ` + `in case you need additional steps for your error.`, `${this.params.name} (${this.params.port})` ); this.incomingEvents.push(new DyFM_SocketEvent({ serviceName: this.params.name, eventKey: DyFM_SocketEvent_Key.error, tasks: [ async (content: any) => { if (content instanceof DyFM_Error) { content.logSimple(`=--> socket-client (${this.params.name}) ERROR`); } else { DyFM_Log.error(`=--> socket-client (${this.params.name}) ERROR:`, content); } } ], })); } // Merge incoming events of the same eventKey const eventKeys = this.incomingEvents.map( (event: DyFM_SocketEvent) => event.eventKey ).filter( (eventKey: string, index: number, self: string[]) => self.indexOf(eventKey) === index ); const mergedEvents: DyFM_SocketEvent[] = []; eventKeys.forEach((eventKey: string) => { const events = this.incomingEvents.filter( (event: DyFM_SocketEvent) => event.eventKey === eventKey ); mergedEvents.push(new DyFM_SocketEvent({ eventKey: eventKey, preProcesses: events.map((event: DyFM_SocketEvent) => event.preProcesses).flat(), tasks: events.map((event: DyFM_SocketEvent) => event.tasks).flat(), })); }); this.incomingEvents = mergedEvents; if (DyNTS_global_settings.log_settings.setup) { DyFM_Log.log( `Socket Server Service - Event Preparation setup done: ` + `"${this.params?.name}" (${this.params?.port})` + `\nincomingEvents:`, this.incomingEvents.map( (event: DyFM_SocketEvent) => `\n\t"${event.eventKey}": preProcesses: ${event.preProcesses?.length} ` + `tasks: ${event.tasks?.length}` ) ); } // Set incoming events serviceName this.incomingEvents.forEach((event: DyFM_SocketEvent) => { event.serviceName = this.params.name; }); } catch (error) { DyFM_Log.error( `Socket Server Service - Event Preparation setup failed: ` + `"${this.params?.name}" (${this.params?.port})`, error ); throw error; } } // eslint-disable-next-line max-lines-per-function async setupSocketServer( newSocketServer: SocketIO.Server, security: DyNTS_SocketSecurity.open | DyNTS_SocketSecurity.secure, successCallback: () => void, options?: DyNTS_SocketServerSetupOptions ): Promise { try { if (this.highDetailedLogs) console.log('\nfn:. setupSocketServer'); const effectivePort: number | undefined = options?.effectivePort ?? this.params.port; newSocketServer.on(DyFM_SocketEvent_Key.incomingNewConnection, async (socket: SocketIO.Socket) => { try { let issuer: string; /* await this.connectEvent.executeEventTasks(socket); */ await DyFM_Array.asyncForEach( this.incomingEvents.filter( (event: DyFM_SocketEvent) => event.eventKey === DyFM_SocketEvent_Key.incomingNewConnection ), async (event: DyFM_SocketEvent) => { await event.executeEventTasks(socket); } ); if (this.getPresenceFromSubscriptionEventContent) { socket.on( DyFM_SocketEvent_Key.subscribe, async (content: any) => { let presence: T_Presence; try { /** * usually socket logs are in event.executeEventTasks(), * but subscribe event is an exception from this, * to be able to check content before getPresenceFromSubscriptionEventContent */ if (DyNTS_global_settings.log_settings.socket_eventContent) { DyFM_Log.log( `--=> incoming socket (${this.params.name}) ` + `event: "${DyFM_SocketEvent_Key.subscribe}";` + `\ncontent:`, content ); } else { DyFM_Log.log( `--=> incoming socket (${this.params.name}) ` + `event: "${DyFM_SocketEvent_Key.subscribe}"` ); } presence = await this.getPresenceFromSubscriptionEventContent( content, socket ); presence.serviceName = this.params.name; issuer = presence.issuerLocalId; this.addSocketToPresence(presence); /* await this.subscriptionEvent.executeEventTasks(content, issuer); */ if (this.getSubscriptionTasks) { await DyFM_Array.asyncForEach( this.getSubscriptionTasks(), async (task: DyFM_SocketEventTask) => { await task(content, issuer); } ); } socket.emit( DyFM_SocketEvent_Key.subscriptionSuccessful, 'subscribe was successful', error => { DyFM_Log.error( `Emitting subscriptionSuccessful event failed!` + `\nerror:`, error ); } ); DyFM_Log.success( `<-==> socket(${this.params.name}) subscription successful (${issuer})` ); } catch (error) { if (error instanceof DyFM_Error) { error.logShort( `Socket Subscription failed: "${this.params.name}" ` + `(${this.params.port}) will disconnect now...` ) } else if (DyNTS_global_settings.log_settings.socket_eventContent) { DyFM_Log.error( `Socket Subscription failed: "${this.params.name}" ` + `(${this.params.port}) will disconnect now...`, /* `${this.thisLocationStack}`, */ error ); } else { DyFM_Log.error( `Socket Subscription failed: "${this.params.name}" ` , `(${this.params.port}) will disconnect now...`, /* `${this.thisLocationStack}` */ ); } await this.closeSocket(socket, issuer, error); } } ); socket.on( DyFM_SocketEvent_Key.unsubscribe, async (issuer: string) => { try { /** * usually socket logs are in event.executeEventTasks(), * but unsubscribe event is an exception from this, * to be able to check content before getPresenceFromSubscriptionEventContent */ if (DyNTS_global_settings.log_settings.socket_eventContent) { DyFM_Log.log( `--=> incoming socket (${this.params.name}) ` + `event: ${DyFM_SocketEvent_Key.unsubscribe};` + `\ncontent:`, issuer ); } else { DyFM_Log.log( `--=> incoming socket (${this.params.name}) ` + `event: ${DyFM_SocketEvent_Key.unsubscribe}` ); } /* await this.unsubscribeEvent.executeEventTasks(null, issuer); */ await DyFM_Array.asyncForEach( this.incomingEvents.filter( (event: DyFM_SocketEvent) => event.eventKey === DyFM_SocketEvent_Key.unsubscribe ), async (event: DyFM_SocketEvent) => { await event.executeEventTasks(null, issuer); } ); this.removeSubscription(socket, issuer); socket.emit( DyFM_SocketEvent_Key.unsubscribeSuccessful, 'unsubscribe was successful', error => { DyFM_Log.error( `Emitting unsubscribeSuccessful event failed!` + `\nerror:`, error ); } ); DyFM_Log.success( `< x > socket(${this.params.name}) unsubscribe successful (${issuer})` ); } catch (error) { if (error instanceof DyFM_Error) { error.logSimple( `Socket Unsubscription failed: "${this.params.name}" ` + `(${this.params.port}) will disconnect now...` ) } else if (DyNTS_global_settings.log_settings.socket_eventContent) { DyFM_Log.error( `Socket unsubscribe failed: "${this.params.name}" ` , `(${this.params.port}) will disconnect now...`, error ); } else { DyFM_Log.error( `Socket unsubscribe failed: "${this.params.name}" ` , `(${this.params.port}) will disconnect now...` ); } socket.emit(DyFM_SocketEvent_Key.error, error); this.removeSubscription(socket, issuer); } } ); } this.incomingEvents.filter( (event: DyFM_SocketEvent) => event.eventKey !== DyFM_SocketEvent_Key.incomingNewConnection && event.eventKey !== DyFM_SocketEvent_Key.subscribe && event.eventKey !== DyFM_SocketEvent_Key.unsubscribe ).forEach((event: DyFM_SocketEvent) => { socket.on( event.eventKey, async (content: any) => { try { await event.executeEventTasks(content, issuer); } catch (error) { if (error instanceof DyFM_Error) { error.logSimple( `Socket Event failed: "${this.params.name}" (${this.params.port})` ) } else { DyFM_Log.error( `Socket Event failed: "${this.params.name}" (${this.params.port})`, error ); } socket.emit(DyFM_SocketEvent_Key.error, error); } } ); }); if (DyNTS_global_settings.log_settings.socket_mainEvents) { DyFM_Log.success(`< > socket(${this.params.name}): new CONNECTION established`); } } catch (error) { if (error instanceof DyFM_Error) { error.logSimple( `Socket Connection failed: "${this.params.name}" ` + `(${this.params.port}) will disconnect now...` ) } else { DyFM_Log.error( `Socket Connection failed: "${this.params.name}" ` + `(${this.params.port}) will disconnect now...`, error ); } await this.closeSocket(socket, `${this.params.name} on connection error`, error); } }); const isAttach: boolean = options?.attachedToExistingServer === true || (this.params.port === undefined && options?.effectivePort !== undefined); if (isAttach) { DyFM_Log.success( `\nsocket server setup finished: "${this.params.name}"` + (effectivePort !== undefined ? ` (attached to server on port: ${effectivePort})` : ' (attached to existing server)') ); successCallback(); } else { try { const portToListen: number | undefined = this.params.port ?? options?.effectivePort; if (portToListen === undefined) { throw new Error( `PORT NOT SET: "${this.params.name}". ` + `Either set port in getServiceParams() or pass effectivePort in setupSocketServer options.` ); } newSocketServer.listen(portToListen); DyFM_Log.success( `\nsocket server setup finished: "${this.params.name}"` + `\nsocket server listening on port: "${portToListen}"` ); } catch (listenError) { const portForMsg: number | undefined = this.params.port ?? options?.effectivePort; const errorMessage = `Failed to bind socket server "${this.params.name}" to port ${portForMsg}. ` + `This could be due to port already in use or insufficient permissions. ` + `Please ensure the port is available and try again.`; DyFM_Log.error(errorMessage, listenError); throw new Error(errorMessage); } successCallback(); } if (security === DyNTS_SocketSecurity.open) { this.openSocketServer = newSocketServer; return this.openSocketServer; } else { this.secureSocketServer = newSocketServer; return this.secureSocketServer; } } catch (error) { if (error instanceof DyFM_Error) { error.logSimple( `Socket Server Service - Deep Setup failed: "${this.params?.name}" ` + `(${this.params?.port})` ) } else { DyFM_Log.error( `Socket Server Service - Deep Setup failed: "${this.params?.name}" ` + `(${this.params?.port})`, error ); } throw error; } } protected async addSocketToPresence(newPresence: T_Presence): Promise { try { const activePresence: T_Presence = this.presences.find( (pres: T_Presence) => pres.issuerLocalId === newPresence.issuerLocalId ); if (activePresence) { activePresence.sockets.push(...newPresence.sockets); } else { this.presences.push(newPresence); } } catch (error) { if (error instanceof DyFM_Error) { error.logShort( `Socket Subscription failed: "${this.params?.name}" (${this.params?.port})` ) } else if (DyNTS_global_settings.log_settings.socket_eventContent) { DyFM_Log.error( `Socket Subscription failed: "${this.params?.name}" (${this.params?.port})`, /* `${this.thisLocationStack}`, */ error ); } else { DyFM_Log.error( `Socket Subscription failed: "${this.params?.name}" (${this.params?.port})`, /* `${this.thisLocationStack}` */ ); } throw error; } } protected async closeSocket(socket: SocketIO.Socket, issuer: string, withError?: DyFM_AnyError): Promise { try { if (withError) { socket.emit(DyFM_SocketEvent_Key.error, withError); } await DyFM_Array.asyncForEach( this.incomingEvents.filter( (event: DyFM_SocketEvent) => event.eventKey === DyFM_SocketEvent_Key.disconnect ), async (event: DyFM_SocketEvent) => { await event.executeEventTasks(null, issuer); } ); /* if (this.getSubscriptionCloseTasks) { await DyFM_Array.asyncForEach( this.getSubscriptionCloseTasks(), async (task: DyFM_SocketEventTask) => { await task(null, issuer); } ); } */ socket.emit(DyFM_SocketEvent_Key.disconnect); socket.disconnect(); await this.removeSocketFromPresence(socket); } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings('closeSocket', error), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-CS0`, }); } } protected async removeSocketFromPresence(socket: SocketIO.Socket): Promise { try { const presence: T_Presence = this.presences.find( (pres: T_Presence) => pres.sockets.includes(socket) ); if (presence) { const socketIndex: number = presence.sockets.indexOf(socket); if (socketIndex !== -1) { presence.sockets.splice(socketIndex, 1); } if (presence.sockets.length === 0) { const presenceIndex: number = this.presences.indexOf(presence); if (presenceIndex !== -1) { this.presences.splice(presenceIndex, 1); } } } } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings('removeSocketFromPresence', error), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-RSO0`, }); } } private async disconnectBaseTask(socket: SocketIO.Socket, issuer: string): Promise { try { if (!(socket instanceof SocketIO.Socket)) { /* throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'disconnectBaseTask', new Error('Socket is not a SocketIO.Socket, type: ' + typeof socket), ), }); */ DyFM_Log.error( `Socket is not a SocketIO.Socket, type: ${typeof socket}`, socket ); //TODO: remove all presences that have no socket } await this.removeSubscription(socket, issuer).catch(error => { if (error instanceof DyFM_Error) { error.logShort( `Remove Subscription failed: "${this.params.name}" (${this.params.port})` ); } else { DyFM_Log.error(`Remove Subscription failed: "${this.params.name}" (${this.params.port})`, error); } }); DyFM_Log.info(`< x > socket(${this.params.name}) disconnected (${issuer})`); } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'disconnectBaseTask', error, issuer ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-DBT0`, }); } } private async removeSubscription(socket: SocketIO.Socket, issuer?: string): Promise { try { if (!this.presences.length) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'removeSubscription', new Error(`No presences found (${this.params.name})`), issuer ?? 'unknown issuer' ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-RS1`, }); } const activePresenceIndex: number = this.presences.findIndex( (pres: DyNTS_SocketPresence) => pres.sockets.includes(socket) ); if (activePresenceIndex === -1) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'sendEventForId', new Error( `closing socket (${this.params.name}) does not match any in the activePresences` ), issuer ?? 'unknown issuer' ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-RS2`, }); } const activePresence: DyNTS_SocketPresence = this.presences[activePresenceIndex]; if (issuer && activePresence.issuerLocalId !== issuer) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'sendEventForId', new Error( `socket unsubscription for "${issuer}" and ` + `"${activePresence.issuerLocalId}" does not match.` ), issuer ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-RS3`, }); } const socketIndex = activePresence.sockets.findIndex((s: SocketIO.Socket) => s === socket); if (socketIndex === -1) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'sendEventForId', new Error( `closing socket (${this.params.name}) does not match any in the activePresences` ), activePresence.issuerLocalId ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-RS4`, }); } activePresence.sockets.splice(socketIndex); await DyFM_Array.asyncForEach( this.incomingEvents.filter( (event: DyFM_SocketEvent) => event.eventKey === DyFM_SocketEvent_Key.unsubscribeSuccessful ), async (event: DyFM_SocketEvent) => { await event.executeEventTasks(null, issuer); } ); /* if (this.getSubscriptionCloseTasks) { await DyFM_Array.asyncForEach( this.getSubscriptionCloseTasks(), async (task: DyFM_SocketEventTask) => { await task(null, issuer); } ); } */ if (activePresence.sockets.length === 0) { this.presences.splice(activePresenceIndex); } socket.emit(DyFM_SocketEvent_Key.unsubscribeSuccessful, 'unsubscribe was successful'); socket.disconnect(); DyFM_Log.info(` socket (${this.params.name}) unsubscription successful (${issuer})`); } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'sendEventForId', error, issuer ?? 'unknown issuer' ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-RS0`, message: `socket (${this.params.name}) Subscription Removal failed: "${this.params?.name}"`, }); } } /* emitServerEvent(event: string, content: any): void { try { DyFM_Log.log(` <=-- emitting server socket(${this.params.name}) event: ${event}`); this.openSocketServer.emit(event, content, error => { DyFM_Log.error(`Emitting server event '${event}' failed!\nerror:`, error); }); } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'emitEvent', error ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-500', message: `socket(${this.params.name}) Event Emit (${event}) failed: ${this.params?.name}`, }); } } */ idIsSubscribed(id: string): boolean { try { return !!this.presences.find( (presence: DyNTS_SocketPresence) => presence.issuerLocalId === id ); } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'idIsSubscribed', error ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-IIS0`, message: `socket (${this.params.name}) ID Subscription Check (${id}) failed`, }); } } async emitError(presenceIssuerId: string, error: any, issuer: string): Promise { try { await this.sendEventForId(presenceIssuerId, DyFM_SocketEvent_Key.error, error, issuer); } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'emitError', error, presenceIssuerId ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-EE0`, message: `socket (${this.params.name}) Error Emit (id: ${presenceIssuerId}) failed`, }); } } async sendEventForId(id: string, event: string, content: any, issuer: string): Promise { try { if (!event) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'sendEventForId', new Error('No event was provided'), content?.source ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-SEFI1`, }); } if (!id) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'sendEventForId', new Error('No ID was provided'), content?.source ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-SEFI2`, }); } const presence: DyNTS_SocketPresence = this.presences.find( (pres: DyNTS_SocketPresence) => pres.issuerLocalId === id ); if (!presence) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'sendEventForId', new Error(`No active socket with this specific ID: "${id}"`), content?.source ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-SEFI3`, }); } presence.emitEvent(event, content, issuer); if (DyNTS_global_settings.log_settings.socket_eventContent) { DyFM_Log.success( ` <--= emitted socket (${this.params.name}) event: "${event}", ` + `\n presenceId: "${id}", sockets: ${presence.sockets.length}` + `\n content:`, content ); } else { DyFM_Log.success( ` <--= emitted socket (${this.params.name}) event: "${event}", ` + `\n presenceId: ${id}, sockets: ${presence.sockets.length}` ); } } catch (error) { try { if (error?.flag?.includes(DyFM_errorFlag) && error?.additionalInfo?.inactiveSockets) { const sockets: SocketIO.Socket[] = error.additionalInfo.inactiveSockets; await DyFM_Array.asyncForEach(sockets, async (socket: SocketIO.Socket) => { await this.removeSubscription(socket); }); const presence: DyNTS_SocketPresence = this.presences.find( (pres: DyNTS_SocketPresence) => pres.issuerLocalId === id ); if (!presence) { throw new DyFM_Error({ ...this._getDefaultErrorSettings( 'sendEventForId', new Error(`No active socket (${this.params.name}) with this specific ID: "${id}"`), content?.source ), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-SEFI4`, }); } } } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings('sendEventForId', error, content?.source), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-SEFI5`, message: `Error handling of inactive sockets (${this.params.name}) failed!` + `\n(Socket Event Emit for id (${id}, ${event}) failed)`, }); } throw new DyFM_Error({ ...this._getDefaultErrorSettings('sendEventForId', error, content?.source), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-SEFI0`, message: `socket (${this.params.name}) Event Emit for id (${id}, ${event}) failed`, }); } } async broadcastEvent(event: string, content: any, issuer: string): Promise { try { DyFM_Log.log(`<=-== broadcasting socket (${this.params.name}) event: "${event}"`); this.presences.forEach((presence: DyNTS_SocketPresence) => { presence.emitEvent(event, content, issuer); }); } catch (error) { if (DyNTS_global_settings.log_settings.detailedErrors) { if (error instanceof DyFM_Error) { error.logSimple(`socket (${this.params.name}) Event Broadcast (${event}) failed`); } else { DyFM_Log.error(`socket (${this.params.name}) Event Broadcast (${event}) failed`, error); } } throw new DyFM_Error({ ...this._getDefaultErrorSettings('broadcastEvent', error), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-BE0`, message: `socket (${this.params.name}) Event Broadcast (${event}) failed`, }); } } async sendError(error: DyFM_AnyError, issuer: string): Promise { try { await this.sendEventForId(issuer, DyFM_SocketEvent_Key.error, error, issuer); } catch (error) { throw new DyFM_Error({ ...this._getDefaultErrorSettings('sendError', error, issuer), errorCode: `${DyNTS_global_settings.systemShortCodeName}|DyNTS-SSS-SE0`, message: `socket (${this.params.name}) Error Send (${issuer}) failed`, }); } } private _getDefaultErrorSettings( fnName: string, error: DyFM_AnyError, issuer?: string ): DyFM_Error_Settings { return { status: (error as DyFM_Error)?.___status ?? 500, message: (error as Error)?.message ?? (error as DyFM_Error)?._message ?? `${fnName} was UNSUCCESSFUL (NTS)`, addECToUserMsg: !(error as DyFM_Error)?.__userMessage, userMessage: (error as DyFM_Error)?.__userMessage ?? this.defaultErrorUserMsg, issuer: issuer, issuerService: this.constructor?.name, error: error, }; } /** * You must setup params for the service in this function */ protected abstract getServiceParams(): DyNTS_SocketServerService_Params; /** * You must setup events and required services in this function */ protected abstract getIncomingEvents(): DyFM_SocketEvent[]; /** * You can setup tasks for the subscription event in this function */ /* protected getSubscriptionCloseTasks?(): DyFM_SocketEventTask[]; */ /** * You can setup preprocesses for the subscription event in this function */ /* protected getSubscriptionPreProcesses?(): DyFM_SocketEventPreprocessTask[]; */ /** * You can setup tasks for the subscription event in this function * This will happen after the presence is created * If this throws an error, the socket will be closed */ protected getSubscriptionTasks?(): DyFM_SocketEventTask[]; /** * You can setup tasks for the unsubscribe event in this function */ /* protected getUnsubscribeTasks?(): DyFM_SocketEventTask[]; */ /** * You can setup preprocesses for the unsubscribe event in this function */ /* protected getUnsubscribePreProcesses?(): DyFM_SocketEventPreprocessTask[]; */ /** * You can setup tasks for the error event in this function */ protected errorHandling?: DyFM_SocketEventTask; }