diff --git a/api/src/channel/lib/EventWrapper.ts b/api/src/channel/lib/EventWrapper.ts index 9456e200..af45c566 100644 --- a/api/src/channel/lib/EventWrapper.ts +++ b/api/src/channel/lib/EventWrapper.ts @@ -11,6 +11,7 @@ import { AttachmentForeignKey, AttachmentPayload, } from '@/chat/schemas/types/attachment'; +import { SubscriberChannelData } from '@/chat/schemas/types/channel'; import { IncomingMessageType, StdEventType, @@ -19,10 +20,11 @@ import { import { Payload } from '@/chat/schemas/types/quick-reply'; import { Nlp } from '@/helper/types'; -import ChannelHandler from './Handler'; +import ChannelHandler, { ChannelNameOf } from './Handler'; export interface ChannelEvent {} +// eslint-disable-next-line prettier/prettier export default abstract class EventWrapper< A, E, @@ -32,6 +34,8 @@ export default abstract class EventWrapper< _handler: C; + channelAttrs: SubscriberChannelDict[ChannelNameOf]; + _profile!: Subscriber; _nlp!: Nlp.ParseEntities; @@ -42,14 +46,18 @@ export default abstract class EventWrapper< * * Any method declared in this class should be extended and overridden in any given channel's * event wrapper if needed. - * @param handler - The channel's handler - * @param event - The message event received - * @param channelData - Channel's specific data + * @param handler - The channel's handler + * @param event - The message event received + * @param channelAttrs - Channel's specific data */ - constructor(handler: C, event: E, channelData: any = {}) { + constructor( + handler: C, + event: E, + channelAttrs: SubscriberChannelDict[ChannelNameOf] = {}, + ) { this._handler = handler; this._init(event); - this.set('channelData', channelData); + this.channelAttrs = channelAttrs; } toString() { @@ -98,8 +106,11 @@ export default abstract class EventWrapper< * * @returns Returns any channel related data. */ - getChannelData(): any { - return this.get('channelData', {}); + getChannelData(): SubscriberChannelData> { + return { + name: this._handler.getName(), + ...this.channelAttrs, + } as SubscriberChannelData>; } /** @@ -285,15 +296,6 @@ export class GenericEventWrapper extends EventWrapper< this._adapter.raw = event; } - /** - * Returns channel related data - * - * @returns An object representing the channel specific data - */ - getChannelData(): any { - return this.get('channelData', {}); - } - /** * Returns the message id * diff --git a/api/src/channel/lib/Handler.ts b/api/src/channel/lib/Handler.ts index 08eb6d3f..c23e58d9 100644 --- a/api/src/channel/lib/Handler.ts +++ b/api/src/channel/lib/Handler.ts @@ -28,6 +28,8 @@ import { ChannelName, ChannelSetting } from '../types'; import EventWrapper from './EventWrapper'; +export type ChannelNameOf = C extends ChannelHandler ? N : never; + @Injectable() export default abstract class ChannelHandler< N extends ChannelName = ChannelName, @@ -48,10 +50,14 @@ export default abstract class ChannelHandler< this.settings = require(path.join(this.getPath(), 'settings')).default; } + getName() { + return this.name as N; + } + async onModuleInit() { await super.onModuleInit(); this.channelService.setChannel( - this.getName() as ChannelName, + this.getName(), this as unknown as ChannelHandler, ); this.setup(); diff --git a/api/src/chat/dto/subscriber.dto.ts b/api/src/chat/dto/subscriber.dto.ts index 8f9d8a9b..82148f96 100644 --- a/api/src/chat/dto/subscriber.dto.ts +++ b/api/src/chat/dto/subscriber.dto.ts @@ -19,7 +19,7 @@ import { import { ChannelName } from '@/channel/types'; import { IsObjectId } from '@/utils/validation-rules/is-object-id'; -import { SubscriberChannel } from '../schemas/types/channel'; +import { SubscriberChannelData } from '../schemas/types/channel'; import { IsChannelData } from '../validation-rules/is-channel-data'; export class SubscriberCreateDto { @@ -110,7 +110,7 @@ export class SubscriberCreateDto { }) @IsNotEmpty() @IsChannelData() - channel: SubscriberChannel; + channel: SubscriberChannelData; } export class SubscriberUpdateDto extends PartialType(SubscriberCreateDto) {} diff --git a/api/src/chat/index.d.ts b/api/src/chat/index.d.ts index 5e574593..9bd66441 100644 --- a/api/src/chat/index.d.ts +++ b/api/src/chat/index.d.ts @@ -9,5 +9,6 @@ import { ChannelName } from '@/channel/types'; declare global { - interface SubscriberChannelDict extends Record {} + interface SubscriberChannelDict + extends Record> {} } diff --git a/api/src/chat/schemas/subscriber.schema.ts b/api/src/chat/schemas/subscriber.schema.ts index e96c238f..5302df41 100644 --- a/api/src/chat/schemas/subscriber.schema.ts +++ b/api/src/chat/schemas/subscriber.schema.ts @@ -21,7 +21,7 @@ import { } from '@/utils/types/filter.types'; import { Label } from './label.schema'; -import { SubscriberChannel } from './types/channel'; +import { SubscriberChannelData } from './types/channel'; import { SubscriberContext } from './types/subscriberContext'; @Schema({ timestamps: true }) @@ -103,7 +103,7 @@ export class SubscriberStub extends BaseSchema { @Prop({ type: Object, }) - channel: SubscriberChannel; + channel: SubscriberChannelData; @Prop({ type: MongooseSchema.Types.ObjectId, @@ -122,7 +122,7 @@ export class SubscriberStub extends BaseSchema { C extends ChannelName, S extends SubscriberStub = Subscriber, >(subscriber: S) { - return subscriber.channel as unknown as SubscriberChannel; + return subscriber.channel as SubscriberChannelData; } } diff --git a/api/src/chat/schemas/types/channel.ts b/api/src/chat/schemas/types/channel.ts index 048a9503..4cab1fef 100644 --- a/api/src/chat/schemas/types/channel.ts +++ b/api/src/chat/schemas/types/channel.ts @@ -8,13 +8,14 @@ import { ChannelName } from '@/channel/types'; -export type SubscriberChannel< +export type SubscriberChannelData< C extends ChannelName = null, K extends keyof SubscriberChannelDict[C] = keyof SubscriberChannelDict[C], > = C extends null ? { name: ChannelName } : { - [P in keyof SubscriberChannelDict[C]]: SubscriberChannelDict[C][K]; - } & { name: C; + } & { + // Channel's specific attributes + [P in keyof SubscriberChannelDict[C]]: SubscriberChannelDict[C][K]; }; diff --git a/api/src/chat/services/block.service.spec.ts b/api/src/chat/services/block.service.spec.ts index ac4e7b02..e15cbc75 100644 --- a/api/src/chat/services/block.service.spec.ts +++ b/api/src/chat/services/block.service.spec.ts @@ -236,7 +236,11 @@ describe('BlockService', () => { text: 'Hello', }, }, - {}, + { + isSocket: true, + ipAddress: '1.1.1.1', + agent: 'Chromium', + }, ); const webEventGetStarted = new WebEventWrapper( handlerMock, @@ -247,7 +251,11 @@ describe('BlockService', () => { payload: 'GET_STARTED', }, }, - {}, + { + isSocket: true, + ipAddress: '1.1.1.1', + agent: 'Chromium', + }, ); it('should return undefined when no blocks are provided', async () => { diff --git a/api/src/chat/services/bot.service.spec.ts b/api/src/chat/services/bot.service.spec.ts index d2acfc85..b9615a0f 100644 --- a/api/src/chat/services/bot.service.spec.ts +++ b/api/src/chat/services/bot.service.spec.ts @@ -186,6 +186,7 @@ describe('BlockService', () => { const event = new WebEventWrapper(handler, webEventText, { isSocket: false, ipAddress: '1.1.1.1', + agent: 'Chromium', }); const [block] = await blockService.findAndPopulate({ patterns: ['Hi'] }); @@ -254,6 +255,7 @@ describe('BlockService', () => { const event = new WebEventWrapper(handler, webEventText, { isSocket: false, ipAddress: '1.1.1.1', + agent: 'Chromium', }); const webSubscriber = await subscriberService.findOne({ foreign_id: 'foreign-id-web-1', @@ -307,6 +309,7 @@ describe('BlockService', () => { const event = new WebEventWrapper(handler, webEventText, { isSocket: false, ipAddress: '1.1.1.1', + agent: 'Chromium', }); const webSubscriber = await subscriberService.findOne({ foreign_id: 'foreign-id-web-2', diff --git a/api/src/chat/services/chat.service.ts b/api/src/chat/services/chat.service.ts index 7e0a556a..5ed33bf1 100644 --- a/api/src/chat/services/chat.service.ts +++ b/api/src/chat/services/chat.service.ts @@ -244,10 +244,7 @@ export class ChatService { if (!subscriber) { const subscriberData = await handler.getUserData(event); this.eventEmitter.emit('hook:stats:entry', 'new_users', 'New users'); - subscriberData.channel = { - ...event.getChannelData(), - name: handler.getName(), - }; + subscriberData.channel = event.getChannelData(); subscriber = await this.subscriberService.create(subscriberData); } else { // Already existing user profile diff --git a/api/src/chat/services/conversation.service.ts b/api/src/chat/services/conversation.service.ts index 1172c21a..6a7e5b31 100644 --- a/api/src/chat/services/conversation.service.ts +++ b/api/src/chat/services/conversation.service.ts @@ -9,7 +9,6 @@ import { Injectable, Logger } from '@nestjs/common'; import EventWrapper from '@/channel/lib/EventWrapper'; -import { ChannelName } from '@/channel/types'; import { LoggerService } from '@/logger/logger.service'; import { BaseService } from '@/utils/generics/base-service'; @@ -70,7 +69,7 @@ export class ConversationService extends BaseService< const msgType = event.getMessageType(); const profile = event.getSender(); // Capture channel specific context data - convo.context.channel = event.getHandler().getName() as ChannelName; + convo.context.channel = event.getHandler().getName(); convo.context.text = event.getText(); convo.context.payload = event.getPayload(); convo.context.nlp = event.getNLP(); diff --git a/api/src/extensions/channels/console/index.d.ts b/api/src/extensions/channels/console/index.d.ts index 59fa7bff..ec17d286 100644 --- a/api/src/extensions/channels/console/index.d.ts +++ b/api/src/extensions/channels/console/index.d.ts @@ -14,7 +14,6 @@ declare global { interface Settings extends SettingTree {} interface SubscriberChannelDict { [CONSOLE_CHANNEL_NAME]: { - name: typeof CONSOLE_CHANNEL_NAME; isSocket: boolean; ipAddress: string; agent: string; diff --git a/api/src/extensions/channels/hexabot-channel-messenger b/api/src/extensions/channels/hexabot-channel-messenger new file mode 160000 index 00000000..cf7004ef --- /dev/null +++ b/api/src/extensions/channels/hexabot-channel-messenger @@ -0,0 +1 @@ +Subproject commit cf7004ef6adac1b5e033d06987f493a9b00e01d2 diff --git a/api/src/extensions/channels/web/__test__/wrapper.spec.ts b/api/src/extensions/channels/web/__test__/wrapper.spec.ts index c206a6c0..66fae47e 100644 --- a/api/src/extensions/channels/web/__test__/wrapper.spec.ts +++ b/api/src/extensions/channels/web/__test__/wrapper.spec.ts @@ -37,6 +37,7 @@ import { SocketEventDispatcherService } from '@/websocket/services/socket-event- import { WebsocketGateway } from '@/websocket/websocket.gateway'; import WebChannelHandler from '../index.channel'; +import { WEB_CHANNEL_NAME } from '../settings'; import WebEventWrapper from '../wrapper'; import { webEvents } from './events.mock'; @@ -119,7 +120,10 @@ describe(`Web event wrapper`, () => { e, expected.channelData, ); - expect(event.getChannelData()).toEqual(expected.channelData); + expect(event.getChannelData()).toEqual({ + ...expected.channelData, + name: WEB_CHANNEL_NAME, + }); expect(event.getId()).toEqual(expected.id); expect(event.getEventType()).toEqual(expected.eventType); expect(event.getMessageType()).toEqual(expected.messageType); diff --git a/api/src/extensions/channels/web/base-web-channel.ts b/api/src/extensions/channels/web/base-web-channel.ts index 1672e1be..eb428c20 100644 --- a/api/src/extensions/channels/web/base-web-channel.ts +++ b/api/src/extensions/channels/web/base-web-channel.ts @@ -433,7 +433,6 @@ export default abstract class BaseWebChannelHandler< return subscriber; } - const channelData = this.getChannelData(req); const newProfile: SubscriberCreateDto = { foreign_id: this.generateId(), first_name: data.first_name ? data.first_name.toString() : 'Anon.', @@ -443,8 +442,8 @@ export default abstract class BaseWebChannelHandler< lastvisit: new Date(), retainedFrom: new Date(), channel: { - ...channelData, - name: this.getName() as ChannelName, + name: this.getName(), + ...this.getChannelAttributes(req), }, language: '', locale: '', @@ -736,13 +735,15 @@ export default abstract class BaseWebChannelHandler< } /** - * Handle channel event (probably a message) + * Return subscriber channel specific attributes * * @param req * - * @returns The channel's data + * @returns The subscriber channel's attributes */ - protected getChannelData(req: Request | SocketRequest): Web.ChannelData { + getChannelAttributes( + req: Request | SocketRequest, + ): SubscriberChannelDict[typeof WEB_CHANNEL_NAME] { return { isSocket: 'isSocket' in req && !!req.isSocket, ipAddress: this.getIpAddress(req), @@ -780,11 +781,11 @@ export default abstract class BaseWebChannelHandler< if (upload) { data.data = upload; } - const channelData = this.getChannelData(req); + const channelAttrs = this.getChannelAttributes(req); const event: WebEventWrapper = new WebEventWrapper( this, data, - channelData, + channelAttrs, ); if (event.getEventType() === 'message') { // Handler sync message sent by chabbot diff --git a/api/src/extensions/channels/web/index.d.ts b/api/src/extensions/channels/web/index.d.ts index 0615ff39..a1677fa2 100644 --- a/api/src/extensions/channels/web/index.d.ts +++ b/api/src/extensions/channels/web/index.d.ts @@ -16,7 +16,6 @@ declare global { interface SubscriberChannelDict { [WEB_CHANNEL_NAME]: { - name: typeof WEB_CHANNEL_NAME; isSocket: boolean; ipAddress: string; agent: string; diff --git a/api/src/extensions/channels/web/types.ts b/api/src/extensions/channels/web/types.ts index 5d475728..ec263bf8 100644 --- a/api/src/extensions/channels/web/types.ts +++ b/api/src/extensions/channels/web/types.ts @@ -30,12 +30,6 @@ export namespace Web { export type Settings = Record; - export type ChannelData = { - isSocket: boolean; - ipAddress: string; - agent: string; - }; - export type RequestSession = { web?: { profile: SubscriberFull; diff --git a/api/src/extensions/channels/web/wrapper.ts b/api/src/extensions/channels/web/wrapper.ts index e36ff5d9..b1650be7 100644 --- a/api/src/extensions/channels/web/wrapper.ts +++ b/api/src/extensions/channels/web/wrapper.ts @@ -21,6 +21,7 @@ import { import { Payload } from '@/chat/schemas/types/quick-reply'; import BaseWebChannelHandler from './base-web-channel'; +import { WEB_CHANNEL_NAME } from './settings'; import { Web } from './types'; type WebEventAdapter = @@ -76,10 +77,14 @@ export default class WebEventWrapper< * * @param handler - The channel's handler * @param event - The message event received - * @param channelData - Channel's specific extra data {isSocket, ipAddress} + * @param channelAttrs - Channel's specific extra attributes {isSocket, ipAddress} */ - constructor(handler: T, event: Web.Event, channelData: any) { - super(handler, event, channelData); + constructor( + handler: T, + event: Web.Event, + channelAttrs: SubscriberChannelDict[typeof WEB_CHANNEL_NAME], + ) { + super(handler, event, channelAttrs); } /** @@ -129,20 +134,6 @@ export default class WebEventWrapper< this._adapter.raw = event; } - /** - * Returns channel related data - * - * @returns Channel's data - */ - getChannelData(): any { - return this.get('channelData', { - isSocket: true, - ipAddress: '0.0.0.0', - agent: - 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36', - }); - } - /** * Returns the message id *