mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
127 lines
4.1 KiB
TypeScript
127 lines
4.1 KiB
TypeScript
/*
|
|
* Copyright © 2025 Hexastack. All rights reserved.
|
|
*
|
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
|
*/
|
|
|
|
import {
|
|
HttpException,
|
|
HttpStatus,
|
|
Injectable,
|
|
OnModuleInit,
|
|
} from '@nestjs/common';
|
|
import { ModulesContainer } from '@nestjs/core';
|
|
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
|
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
|
|
import { Mutex } from 'async-mutex';
|
|
import { Socket } from 'socket.io';
|
|
|
|
import { SocketEventMetadataStorage } from '../storage/socket-event-metadata.storage';
|
|
import { SocketRequest } from '../utils/socket-request';
|
|
import { SocketResponse } from '../utils/socket-response';
|
|
|
|
type Handler = (req: any, res: SocketResponse) => Promise<any>;
|
|
|
|
export type SocketMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | string;
|
|
|
|
@Injectable()
|
|
export class SocketEventDispatcherService implements OnModuleInit {
|
|
private routeHandlers: { [key: SocketMethod]: Map<string, Handler> } = {
|
|
get: new Map(),
|
|
post: new Map(),
|
|
put: new Map(),
|
|
patch: new Map(),
|
|
delete: new Map(),
|
|
};
|
|
|
|
constructor(
|
|
private readonly eventEmitter: EventEmitter2,
|
|
private readonly modulesContainer: ModulesContainer,
|
|
) {}
|
|
|
|
@OnEvent('hook:websocket:connection')
|
|
handleConnection(client: Socket) {
|
|
client.data.mutex = new Mutex();
|
|
}
|
|
|
|
async handleEvent(
|
|
socketMethod: SocketMethod,
|
|
path: string,
|
|
req: SocketRequest,
|
|
res: SocketResponse,
|
|
) {
|
|
// Prevent racing conditions from the same socket
|
|
const socketData = req.socket.data;
|
|
const release = await socketData.mutex.acquire();
|
|
|
|
try {
|
|
const handlers = this.routeHandlers[socketMethod];
|
|
const foundHandler = Array.from(handlers.entries()).find(([key, _]) => {
|
|
const urlPathname = new URL(req.url, 'http://localhost').pathname;
|
|
const keyUrlPathName = new URL(key, 'http://localhost').pathname;
|
|
|
|
return urlPathname === keyUrlPathName;
|
|
});
|
|
|
|
if (!foundHandler) {
|
|
return res.status(HttpStatus.NOT_FOUND).send({ message: 'Not Found' });
|
|
}
|
|
|
|
const [_, handler] = foundHandler;
|
|
return await handler(req, res);
|
|
} catch (error) {
|
|
return this.handleException(error, res);
|
|
} finally {
|
|
release();
|
|
}
|
|
}
|
|
|
|
onModuleInit() {
|
|
const allProviders = Array.from(this.modulesContainer.values())
|
|
.map((module) => module.providers.values())
|
|
.reduce(
|
|
(prev, curr) => prev.concat(Array.from(curr)),
|
|
[] as InstanceWrapper<unknown>[],
|
|
)
|
|
.filter((provider) => !!provider.instance);
|
|
|
|
for (const provider of allProviders) {
|
|
const instance = provider.instance;
|
|
const events = SocketEventMetadataStorage.getMetadataFor(instance);
|
|
events.forEach((event) => {
|
|
// Error Handling
|
|
if (this.routeHandlers[event.socketMethod] === undefined) {
|
|
throw new Error(`Invalid event type: ${event.socketMethod}`);
|
|
}
|
|
|
|
if (this.routeHandlers[event.socketMethod].has(event.path)) {
|
|
throw new Error(
|
|
`Duplicate event: ${event.socketMethod} ${event.path}`,
|
|
);
|
|
}
|
|
|
|
// add event handler
|
|
this.routeHandlers[event.socketMethod].set(
|
|
event.path,
|
|
// bind the method to the instance
|
|
event.method.bind(instance),
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
private handleException(error: Error, res: SocketResponse) {
|
|
if (error instanceof HttpException) {
|
|
// Handle known HTTP exceptions
|
|
return res.status(error.getStatus()).send(error.getResponse());
|
|
} else {
|
|
// Handle generic errors
|
|
return res
|
|
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
.send({ message: 'Internal Server Error' });
|
|
}
|
|
}
|
|
}
|