mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
201 lines
6.7 KiB
TypeScript
201 lines
6.7 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 { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
import { Request, Response } from 'express';
|
|
|
|
import { SubscriberService } from '@/chat/services/subscriber.service';
|
|
import { CONSOLE_CHANNEL_NAME } from '@/extensions/channels/console/settings';
|
|
import { WEB_CHANNEL_NAME } from '@/extensions/channels/web/settings';
|
|
import { LoggerService } from '@/logger/logger.service';
|
|
import { getSessionStore } from '@/utils/constants/session-store';
|
|
import {
|
|
SocketGet,
|
|
SocketPost,
|
|
} from '@/websocket/decorators/socket-method.decorator';
|
|
import { SocketReq } from '@/websocket/decorators/socket-req.decorator';
|
|
import { SocketRes } from '@/websocket/decorators/socket-res.decorator';
|
|
import { SocketRequest } from '@/websocket/utils/socket-request';
|
|
import { SocketResponse } from '@/websocket/utils/socket-response';
|
|
|
|
import ChannelHandler from './lib/Handler';
|
|
import { ChannelName } from './types';
|
|
|
|
@Injectable()
|
|
export class ChannelService {
|
|
private registry: Map<string, ChannelHandler<ChannelName>> = new Map();
|
|
|
|
constructor(
|
|
private readonly logger: LoggerService,
|
|
private readonly subscriberService: SubscriberService,
|
|
) {}
|
|
|
|
/**
|
|
* Registers a channel with a specific handler.
|
|
*
|
|
* @param name - The name of the channel to be registered.
|
|
* @param channel - The channel handler associated with the channel name.
|
|
* @typeParam C The channel handler's type that extends `ChannelHandler`.
|
|
*/
|
|
public setChannel<T extends ChannelName, C extends ChannelHandler<T>>(
|
|
name: T,
|
|
channel: C,
|
|
) {
|
|
this.registry.set(name, channel);
|
|
}
|
|
|
|
/**
|
|
* Retrieves all registered channel handlers.
|
|
*
|
|
* @returns An array of all channel handlers currently registered.
|
|
*/
|
|
public getAll() {
|
|
return Array.from(this.registry.values());
|
|
}
|
|
|
|
/**
|
|
* Finds and returns the channel handler associated with the specified channel name.
|
|
*
|
|
* @param name - The name of the channel to find.
|
|
* @returns The channel handler associated with the specified name, or undefined if the channel is not found.
|
|
*/
|
|
public findChannel(name: ChannelName) {
|
|
return this.getAll().find((c) => {
|
|
return c.getName() === name;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Retrieves the appropriate channel handler based on the channel name.
|
|
*
|
|
* @param channelName - The name of the channel (messenger, web, ...).
|
|
* @returns The handler for the specified channel.
|
|
*/
|
|
public getChannelHandler<T extends ChannelName, C extends ChannelHandler<T>>(
|
|
name: T,
|
|
): C {
|
|
const handler = this.registry.get(name);
|
|
if (!handler) {
|
|
throw new Error(`Channel ${name} not found`);
|
|
}
|
|
return handler as C;
|
|
}
|
|
|
|
/**
|
|
* Handles a request for a specific channel.
|
|
*
|
|
* @param channel - The channel for which the request is being handled.
|
|
* @param req - The HTTP request object.
|
|
* @param res - The HTTP response object.
|
|
* @returns A promise that resolves when the handler has processed the request.
|
|
*/
|
|
async handle(channel: string, req: Request, res: Response): Promise<void> {
|
|
const handler = this.getChannelHandler(`${channel}-channel`);
|
|
handler.handle(req, res);
|
|
}
|
|
|
|
/**
|
|
* Handles a download request for a specific channel.
|
|
*
|
|
* @param channel - The channel for which the request is being handled.
|
|
* @param token - The file JWT token.
|
|
* @param req - The HTTP express request object.
|
|
* @returns A promise that resolves a streamable if the signed url is valid.
|
|
*/
|
|
async download(channel: string, token: string, req: Request) {
|
|
const handler = this.getChannelHandler(`${channel}-channel`);
|
|
return await handler.download(token, req);
|
|
}
|
|
|
|
/**
|
|
* Handles a websocket request for the web channel.
|
|
*
|
|
* @param req - The websocket request object.
|
|
* @param res - The websocket response object.
|
|
*/
|
|
@SocketGet(`/webhook/${WEB_CHANNEL_NAME}/`)
|
|
@SocketPost(`/webhook/${WEB_CHANNEL_NAME}/`)
|
|
async handleWebsocketForWebChannel(
|
|
@SocketReq() req: SocketRequest,
|
|
@SocketRes() res: SocketResponse,
|
|
) {
|
|
this.logger.log('Channel notification (Web Socket) : ', req.method);
|
|
const handler = this.getChannelHandler(WEB_CHANNEL_NAME);
|
|
return await Promise.resolve(handler.handle(req, res));
|
|
}
|
|
|
|
/**
|
|
* Handles a websocket request for the admin chat console channel.
|
|
* It considers the user as a subscriber.
|
|
*
|
|
* @param req - The websocket request object.
|
|
* @param res - The websocket response object.
|
|
*/
|
|
@SocketGet(`/webhook/${CONSOLE_CHANNEL_NAME}/`)
|
|
@SocketPost(`/webhook/${CONSOLE_CHANNEL_NAME}/`)
|
|
async handleWebsocketForAdminChatConsole(
|
|
@SocketReq() req: SocketRequest,
|
|
@SocketRes() res: SocketResponse,
|
|
) {
|
|
this.logger.log(
|
|
'Channel notification (Admin Chat Console Socket) : ',
|
|
req.method,
|
|
);
|
|
|
|
if (!req.session?.passport?.user?.id) {
|
|
setTimeout(() => {
|
|
req.socket.client.conn.close();
|
|
}, 300);
|
|
throw new UnauthorizedException(
|
|
'Only authenticated users are allowed to use this channel',
|
|
);
|
|
}
|
|
|
|
if (!req.session.web?.profile?.id) {
|
|
// Create test subscriber for the current user
|
|
const testSubscriber = await this.subscriberService.findOneOrCreate(
|
|
{
|
|
foreign_id: req.session.passport.user.id,
|
|
},
|
|
{
|
|
foreign_id: req.session.passport.user.id,
|
|
first_name: req.session.passport.user.first_name || 'Anonymous',
|
|
last_name: req.session.passport.user.last_name || 'Anonymous',
|
|
locale: '',
|
|
language: '',
|
|
gender: '',
|
|
country: '',
|
|
labels: [],
|
|
channel: {
|
|
name: CONSOLE_CHANNEL_NAME,
|
|
isSocket: true,
|
|
},
|
|
},
|
|
);
|
|
|
|
// Update session (end user is both a user + subscriber)
|
|
req.session.web = {
|
|
profile: testSubscriber,
|
|
isSocket: true,
|
|
messageQueue: [],
|
|
polling: false,
|
|
};
|
|
|
|
// @TODO: temporary fix until it's fixed properly: https://github.com/Hexastack/Hexabot/issues/578
|
|
getSessionStore().set(req.sessionID, req.session, (err) => {
|
|
if (err) {
|
|
this.logger.warn('Unable to store WS Console session', err);
|
|
}
|
|
});
|
|
}
|
|
|
|
const handler = this.getChannelHandler(CONSOLE_CHANNEL_NAME);
|
|
return await Promise.resolve(handler.handle(req, res));
|
|
}
|
|
}
|