mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
feat: rename offline to web
This commit is contained in:
parent
1fc58f4496
commit
a3d7b83511
@ -11,7 +11,7 @@ import { Request, Response } from 'express';
|
||||
|
||||
import { SubscriberService } from '@/chat/services/subscriber.service';
|
||||
import { LIVE_CHAT_TEST_CHANNEL_NAME } from '@/extensions/channels/live-chat-tester/settings';
|
||||
import { OFFLINE_CHANNEL_NAME } from '@/extensions/channels/offline/settings';
|
||||
import { WEB_CHANNEL_NAME } from '@/extensions/channels/web/settings';
|
||||
import { LoggerService } from '@/logger/logger.service';
|
||||
import {
|
||||
SocketGet,
|
||||
@ -72,7 +72,7 @@ export class ChannelService {
|
||||
/**
|
||||
* Retrieves the appropriate channel handler based on the channel name.
|
||||
*
|
||||
* @param channelName - The name of the channel (messenger, offline, ...).
|
||||
* @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>>(
|
||||
@ -99,19 +99,19 @@ export class ChannelService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a websocket request for the offline channel.
|
||||
* Handles a websocket request for the web channel.
|
||||
*
|
||||
* @param req - The websocket request object.
|
||||
* @param res - The websocket response object.
|
||||
*/
|
||||
@SocketGet(`/webhook/${OFFLINE_CHANNEL_NAME}/`)
|
||||
@SocketPost(`/webhook/${OFFLINE_CHANNEL_NAME}/`)
|
||||
handleWebsocketForOffline(
|
||||
@SocketGet(`/webhook/${WEB_CHANNEL_NAME}/`)
|
||||
@SocketPost(`/webhook/${WEB_CHANNEL_NAME}/`)
|
||||
handleWebsocketForWebChannel(
|
||||
@SocketReq() req: SocketRequest,
|
||||
@SocketRes() res: SocketResponse,
|
||||
) {
|
||||
this.logger.log('Channel notification (Offline Socket) : ', req.method);
|
||||
const handler = this.getChannelHandler(OFFLINE_CHANNEL_NAME);
|
||||
this.logger.log('Channel notification (Web Socket) : ', req.method);
|
||||
const handler = this.getChannelHandler(WEB_CHANNEL_NAME);
|
||||
return handler.handle(req, res);
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ export class ChannelService {
|
||||
);
|
||||
|
||||
// Update session (end user is both a user + subscriber)
|
||||
req.session.offline = {
|
||||
req.session.web = {
|
||||
profile: testSubscriber,
|
||||
isSocket: true,
|
||||
messageQueue: [],
|
||||
|
@ -16,7 +16,7 @@ const baseLabel: Label = {
|
||||
name: '',
|
||||
label_id: {
|
||||
messenger: '',
|
||||
offline: '',
|
||||
web: '',
|
||||
dimelo: '',
|
||||
twitter: '',
|
||||
},
|
||||
@ -30,7 +30,7 @@ export const labelMock: Label = {
|
||||
name: 'label',
|
||||
label_id: {
|
||||
messenger: 'none',
|
||||
offline: 'none',
|
||||
web: 'none',
|
||||
dimelo: 'none',
|
||||
twitter: 'none',
|
||||
},
|
||||
@ -43,7 +43,7 @@ export const customerLabelsMock: Label[] = [
|
||||
name: 'client',
|
||||
label_id: {
|
||||
messenger: 'none',
|
||||
offline: 'none',
|
||||
web: 'none',
|
||||
dimelo: 'none',
|
||||
twitter: 'none',
|
||||
},
|
||||
@ -54,7 +54,7 @@ export const customerLabelsMock: Label[] = [
|
||||
name: 'profressional',
|
||||
label_id: {
|
||||
messenger: 'none',
|
||||
offline: 'none',
|
||||
web: 'none',
|
||||
dimelo: 'none',
|
||||
twitter: 'none',
|
||||
},
|
||||
|
@ -25,7 +25,7 @@ export const subscriberInstance: Subscriber = {
|
||||
lastvisit: new Date(),
|
||||
retainedFrom: new Date(),
|
||||
channel: {
|
||||
name: 'offline-channel',
|
||||
name: 'web-channel',
|
||||
},
|
||||
labels: [],
|
||||
...modelInstance,
|
||||
|
@ -173,7 +173,7 @@ describe('LabelController', () => {
|
||||
name: 'LABEL_2',
|
||||
label_id: {
|
||||
messenger: 'messenger',
|
||||
offline: 'offline',
|
||||
web: 'web',
|
||||
twitter: 'twitter',
|
||||
dimelo: 'dimelo',
|
||||
},
|
||||
|
@ -24,10 +24,10 @@ import { ContentTypeModel } from '@/cms/schemas/content-type.schema';
|
||||
import { Content, ContentModel } from '@/cms/schemas/content.schema';
|
||||
import { ContentTypeService } from '@/cms/services/content-type.service';
|
||||
import { ContentService } from '@/cms/services/content.service';
|
||||
import OfflineHandler from '@/extensions/channels/offline/index.channel';
|
||||
import { OFFLINE_CHANNEL_NAME } from '@/extensions/channels/offline/settings';
|
||||
import { Offline } from '@/extensions/channels/offline/types';
|
||||
import OfflineEventWrapper from '@/extensions/channels/offline/wrapper';
|
||||
import WebChannelHandler from '@/extensions/channels/web/index.channel';
|
||||
import { WEB_CHANNEL_NAME } from '@/extensions/channels/web/settings';
|
||||
import { Web } from '@/extensions/channels/web/types';
|
||||
import WebEventWrapper from '@/extensions/channels/web/wrapper';
|
||||
import { LanguageRepository } from '@/i18n/repositories/language.repository';
|
||||
import { LanguageModel } from '@/i18n/schemas/language.schema';
|
||||
import { I18nService } from '@/i18n/services/i18n.service';
|
||||
@ -222,22 +222,22 @@ describe('BlockService', () => {
|
||||
|
||||
describe('match', () => {
|
||||
const handlerMock = {
|
||||
getName: jest.fn(() => OFFLINE_CHANNEL_NAME),
|
||||
} as any as OfflineHandler;
|
||||
const offlineEventGreeting = new OfflineEventWrapper(
|
||||
getName: jest.fn(() => WEB_CHANNEL_NAME),
|
||||
} as any as WebChannelHandler;
|
||||
const webEventGreeting = new WebEventWrapper(
|
||||
handlerMock,
|
||||
{
|
||||
type: Offline.IncomingMessageType.text,
|
||||
type: Web.IncomingMessageType.text,
|
||||
data: {
|
||||
text: 'Hello',
|
||||
},
|
||||
},
|
||||
{},
|
||||
);
|
||||
const offlineEventGetStarted = new OfflineEventWrapper(
|
||||
const webEventGetStarted = new WebEventWrapper(
|
||||
handlerMock,
|
||||
{
|
||||
type: Offline.IncomingMessageType.postback,
|
||||
type: Web.IncomingMessageType.postback,
|
||||
data: {
|
||||
text: 'Get Started',
|
||||
payload: 'GET_STARTED',
|
||||
@ -247,40 +247,37 @@ describe('BlockService', () => {
|
||||
);
|
||||
|
||||
it('should return undefined when no blocks are provided', async () => {
|
||||
const result = await blockService.match([], offlineEventGreeting);
|
||||
const result = await blockService.match([], webEventGreeting);
|
||||
expect(result).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined for empty blocks', async () => {
|
||||
const result = await blockService.match(
|
||||
[blockEmpty],
|
||||
offlineEventGreeting,
|
||||
);
|
||||
const result = await blockService.match([blockEmpty], webEventGreeting);
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should return undefined for no matching labels', async () => {
|
||||
offlineEventGreeting.setSender(subscriberWithoutLabels);
|
||||
const result = await blockService.match(blocks, offlineEventGreeting);
|
||||
webEventGreeting.setSender(subscriberWithoutLabels);
|
||||
const result = await blockService.match(blocks, webEventGreeting);
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should match block text and labels', async () => {
|
||||
offlineEventGreeting.setSender(subscriberWithLabels);
|
||||
const result = await blockService.match(blocks, offlineEventGreeting);
|
||||
webEventGreeting.setSender(subscriberWithLabels);
|
||||
const result = await blockService.match(blocks, webEventGreeting);
|
||||
expect(result).toEqual(blockGetStarted);
|
||||
});
|
||||
|
||||
it('should match block with payload', async () => {
|
||||
offlineEventGetStarted.setSender(subscriberWithLabels);
|
||||
const result = await blockService.match(blocks, offlineEventGetStarted);
|
||||
webEventGetStarted.setSender(subscriberWithLabels);
|
||||
const result = await blockService.match(blocks, webEventGetStarted);
|
||||
expect(result).toEqual(blockGetStarted);
|
||||
});
|
||||
|
||||
it('should match block with nlp', async () => {
|
||||
offlineEventGreeting.setSender(subscriberWithLabels);
|
||||
offlineEventGreeting.setNLP(nlpEntitiesGreeting);
|
||||
const result = await blockService.match(blocks, offlineEventGreeting);
|
||||
webEventGreeting.setSender(subscriberWithLabels);
|
||||
webEventGreeting.setNLP(nlpEntitiesGreeting);
|
||||
const result = await blockService.match(blocks, webEventGreeting);
|
||||
expect(result).toEqual(blockGetStarted);
|
||||
});
|
||||
});
|
||||
@ -502,7 +499,7 @@ describe('BlockService', () => {
|
||||
describe('processText', () => {
|
||||
const context: Context = {
|
||||
...contextGetStartedInstance,
|
||||
channel: 'offline-channel',
|
||||
channel: 'web-channel',
|
||||
text: '',
|
||||
payload: undefined,
|
||||
nlp: { entities: [] },
|
||||
|
@ -24,9 +24,9 @@ import { MenuModel } from '@/cms/schemas/menu.schema';
|
||||
import { ContentTypeService } from '@/cms/services/content-type.service';
|
||||
import { ContentService } from '@/cms/services/content.service';
|
||||
import { MenuService } from '@/cms/services/menu.service';
|
||||
import { offlineEventText } from '@/extensions/channels/offline/__test__/events.mock';
|
||||
import OfflineHandler from '@/extensions/channels/offline/index.channel';
|
||||
import OfflineEventWrapper from '@/extensions/channels/offline/wrapper';
|
||||
import { webEventText } from '@/extensions/channels/web/__test__/events.mock';
|
||||
import WebChannelHandler from '@/extensions/channels/web/index.channel';
|
||||
import WebEventWrapper from '@/extensions/channels/web/wrapper';
|
||||
import { HelperService } from '@/helper/helper.service';
|
||||
import { LanguageRepository } from '@/i18n/repositories/language.repository';
|
||||
import { LanguageModel } from '@/i18n/schemas/language.schema';
|
||||
@ -75,7 +75,7 @@ describe('BlockService', () => {
|
||||
let blockService: BlockService;
|
||||
let subscriberService: SubscriberService;
|
||||
let botService: BotService;
|
||||
let handler: OfflineHandler;
|
||||
let handler: WebChannelHandler;
|
||||
let eventEmitter: EventEmitter2;
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -126,7 +126,7 @@ describe('BlockService', () => {
|
||||
ChannelService,
|
||||
MessageService,
|
||||
MenuService,
|
||||
OfflineHandler,
|
||||
WebChannelHandler,
|
||||
ContextVarService,
|
||||
ContextVarRepository,
|
||||
LanguageService,
|
||||
@ -170,7 +170,7 @@ describe('BlockService', () => {
|
||||
botService = module.get<BotService>(BotService);
|
||||
blockService = module.get<BlockService>(BlockService);
|
||||
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
|
||||
handler = module.get<OfflineHandler>(OfflineHandler);
|
||||
handler = module.get<WebChannelHandler>(WebChannelHandler);
|
||||
});
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
@ -183,38 +183,38 @@ describe('BlockService', () => {
|
||||
triggeredEvents.push(args);
|
||||
});
|
||||
|
||||
const event = new OfflineEventWrapper(handler, offlineEventText, {
|
||||
const event = new WebEventWrapper(handler, webEventText, {
|
||||
isSocket: false,
|
||||
ipAddress: '1.1.1.1',
|
||||
});
|
||||
|
||||
const [block] = await blockService.findAndPopulate({ patterns: ['Hi'] });
|
||||
const offlineSubscriber = await subscriberService.findOne({
|
||||
foreign_id: 'foreign-id-offline-1',
|
||||
const webSubscriber = await subscriberService.findOne({
|
||||
foreign_id: 'foreign-id-web-1',
|
||||
});
|
||||
|
||||
event.setSender(offlineSubscriber);
|
||||
event.setSender(webSubscriber);
|
||||
|
||||
let hasBotSpoken = false;
|
||||
const clearMock = jest
|
||||
.spyOn(botService, 'findBlockAndSendReply')
|
||||
.mockImplementation(
|
||||
(
|
||||
actualEvent: OfflineEventWrapper,
|
||||
actualEvent: WebEventWrapper,
|
||||
actualConversation: Conversation,
|
||||
actualBlock: BlockFull,
|
||||
isFallback: boolean,
|
||||
) => {
|
||||
expect(actualConversation).toEqualPayload({
|
||||
sender: offlineSubscriber.id,
|
||||
sender: webSubscriber.id,
|
||||
active: true,
|
||||
next: [],
|
||||
context: {
|
||||
user: {
|
||||
first_name: offlineSubscriber.first_name,
|
||||
last_name: offlineSubscriber.last_name,
|
||||
first_name: webSubscriber.first_name,
|
||||
last_name: webSubscriber.last_name,
|
||||
language: 'en',
|
||||
id: offlineSubscriber.id,
|
||||
id: webSubscriber.id,
|
||||
},
|
||||
user_location: {
|
||||
lat: 0,
|
||||
@ -224,8 +224,8 @@ describe('BlockService', () => {
|
||||
nlp: null,
|
||||
payload: null,
|
||||
attempt: 0,
|
||||
channel: 'offline-channel',
|
||||
text: offlineEventText.data.text,
|
||||
channel: 'web-channel',
|
||||
text: webEventText.data.text,
|
||||
},
|
||||
});
|
||||
expect(actualEvent).toEqual(event);
|
||||
@ -251,40 +251,40 @@ describe('BlockService', () => {
|
||||
triggeredEvents.push(args);
|
||||
});
|
||||
|
||||
const event = new OfflineEventWrapper(handler, offlineEventText, {
|
||||
const event = new WebEventWrapper(handler, webEventText, {
|
||||
isSocket: false,
|
||||
ipAddress: '1.1.1.1',
|
||||
});
|
||||
const offlineSubscriber = await subscriberService.findOne({
|
||||
foreign_id: 'foreign-id-offline-1',
|
||||
const webSubscriber = await subscriberService.findOne({
|
||||
foreign_id: 'foreign-id-web-1',
|
||||
});
|
||||
event.setSender(offlineSubscriber);
|
||||
event.setSender(webSubscriber);
|
||||
|
||||
const clearMock = jest
|
||||
.spyOn(botService, 'handleIncomingMessage')
|
||||
.mockImplementation(
|
||||
async (
|
||||
actualConversation: ConversationFull,
|
||||
event: OfflineEventWrapper,
|
||||
event: WebEventWrapper,
|
||||
) => {
|
||||
expect(actualConversation).toEqualPayload({
|
||||
next: [],
|
||||
sender: offlineSubscriber,
|
||||
sender: webSubscriber,
|
||||
active: true,
|
||||
context: {
|
||||
user: {
|
||||
first_name: offlineSubscriber.first_name,
|
||||
last_name: offlineSubscriber.last_name,
|
||||
first_name: webSubscriber.first_name,
|
||||
last_name: webSubscriber.last_name,
|
||||
language: 'en',
|
||||
id: offlineSubscriber.id,
|
||||
id: webSubscriber.id,
|
||||
},
|
||||
user_location: { lat: 0, lon: 0 },
|
||||
vars: {},
|
||||
nlp: null,
|
||||
payload: null,
|
||||
attempt: 0,
|
||||
channel: 'offline-channel',
|
||||
text: offlineEventText.data.text,
|
||||
channel: 'web-channel',
|
||||
text: webEventText.data.text,
|
||||
},
|
||||
});
|
||||
expect(event).toEqual(event);
|
||||
@ -304,14 +304,14 @@ describe('BlockService', () => {
|
||||
eventEmitter.on('hook:stats:entry', (...args) => {
|
||||
triggeredEvents.push(args);
|
||||
});
|
||||
const event = new OfflineEventWrapper(handler, offlineEventText, {
|
||||
const event = new WebEventWrapper(handler, webEventText, {
|
||||
isSocket: false,
|
||||
ipAddress: '1.1.1.1',
|
||||
});
|
||||
const offlineSubscriber = await subscriberService.findOne({
|
||||
foreign_id: 'foreign-id-offline-2',
|
||||
const webSubscriber = await subscriberService.findOne({
|
||||
foreign_id: 'foreign-id-web-2',
|
||||
});
|
||||
event.setSender(offlineSubscriber);
|
||||
event.setSender(webSubscriber);
|
||||
const captured = await botService.processConversationMessage(event);
|
||||
|
||||
expect(captured).toBe(false);
|
||||
|
@ -19,7 +19,7 @@ import { LoggerService } from '@/logger/logger.service';
|
||||
import { SettingService } from '@/setting/services/setting.service';
|
||||
import { WebsocketGateway } from '@/websocket/websocket.gateway';
|
||||
|
||||
import BaseWebChannelHandler from '../offline/base-web-channel';
|
||||
import BaseWebChannelHandler from '../web/base-web-channel';
|
||||
|
||||
import { LIVE_CHAT_TEST_CHANNEL_NAME } from './settings';
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { ChannelSetting } from '@/channel/types';
|
||||
import { config } from '@/config';
|
||||
import { SettingType } from '@/setting/schemas/types';
|
||||
|
||||
import { Offline } from '../offline/types';
|
||||
import { Web } from '../web/types';
|
||||
|
||||
export const LIVE_CHAT_TEST_CHANNEL_NAME = 'live-chat-tester-channel';
|
||||
|
||||
@ -19,74 +19,74 @@ export const LIVE_CHAT_TEST_CHANNEL_NAMESPACE = 'live_chat_tester_channel';
|
||||
export default [
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.verification_token,
|
||||
label: Web.SettingLabel.verification_token,
|
||||
value: 'test',
|
||||
type: SettingType.text,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.allowed_domains,
|
||||
label: Web.SettingLabel.allowed_domains,
|
||||
value: config.frontendPath,
|
||||
type: SettingType.text,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.start_button,
|
||||
label: Web.SettingLabel.start_button,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.input_disabled,
|
||||
label: Web.SettingLabel.input_disabled,
|
||||
value: false,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.persistent_menu,
|
||||
label: Web.SettingLabel.persistent_menu,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.greeting_message,
|
||||
label: Web.SettingLabel.greeting_message,
|
||||
value: 'Welcome! Ready to start a conversation with our chatbot?',
|
||||
type: SettingType.textarea,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.theme_color,
|
||||
label: Web.SettingLabel.theme_color,
|
||||
value: 'teal',
|
||||
type: SettingType.select,
|
||||
options: ['teal', 'orange', 'red', 'green', 'blue', 'dark'],
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.show_emoji,
|
||||
label: Web.SettingLabel.show_emoji,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.show_file,
|
||||
label: Web.SettingLabel.show_file,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.show_location,
|
||||
label: Web.SettingLabel.show_location,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.allowed_upload_size,
|
||||
label: Web.SettingLabel.allowed_upload_size,
|
||||
value: 2500000,
|
||||
type: SettingType.number,
|
||||
},
|
||||
{
|
||||
group: LIVE_CHAT_TEST_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.allowed_upload_types,
|
||||
label: Web.SettingLabel.allowed_upload_types,
|
||||
value:
|
||||
'audio/mpeg,audio/x-ms-wma,audio/vnd.rn-realaudio,audio/x-wav,image/gif,image/jpeg,image/png,image/tiff,image/vnd.microsoft.icon,image/vnd.djvu,image/svg+xml,text/css,text/csv,text/html,text/plain,text/xml,video/mpeg,video/mp4,video/quicktime,video/x-ms-wmv,video/x-msvideo,video/x-flv,video/web,application/msword,application/vnd.ms-powerpoint,application/pdf,application/vnd.ms-excel,application/vnd.oasis.opendocument.presentation,application/vnd.oasis.opendocument.tex,application/vnd.oasis.opendocument.spreadsheet,application/vnd.oasis.opendocument.graphics,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
type: SettingType.textarea,
|
||||
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"offline_channel": "Web Channel"
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"offline_channel": "Canal Web"
|
||||
}
|
16
api/src/extensions/channels/offline/index.d.ts
vendored
16
api/src/extensions/channels/offline/index.d.ts
vendored
@ -1,16 +0,0 @@
|
||||
import DEFAULT_OFFLINE_SETTINGS, {
|
||||
OFFLINE_CHANNEL_NAMESPACE,
|
||||
} from './settings';
|
||||
|
||||
declare global {
|
||||
interface Settings extends SettingTree<typeof DEFAULT_OFFLINE_SETTINGS> {}
|
||||
}
|
||||
|
||||
declare module '@nestjs/event-emitter' {
|
||||
interface IHookExtensionsOperationMap {
|
||||
[OFFLINE_CHANNEL_NAMESPACE]: TDefinition<
|
||||
object,
|
||||
SettingMapByType<typeof DEFAULT_OFFLINE_SETTINGS>
|
||||
>;
|
||||
}
|
||||
}
|
@ -12,14 +12,14 @@ import { ButtonType } from '@/chat/schemas/types/button';
|
||||
import { FileType } from '@/chat/schemas/types/message';
|
||||
import { QuickReplyType } from '@/chat/schemas/types/quick-reply';
|
||||
|
||||
import { Offline } from '../types';
|
||||
import { Web } from '../types';
|
||||
|
||||
export const offlineText: Offline.OutgoingMessageBase = {
|
||||
type: Offline.OutgoingMessageType.text,
|
||||
export const webText: Web.OutgoingMessageBase = {
|
||||
type: Web.OutgoingMessageType.text,
|
||||
data: textMessage,
|
||||
};
|
||||
|
||||
export const offlineQuickReplies: Offline.OutgoingMessageBase = {
|
||||
export const webQuickReplies: Web.OutgoingMessageBase = {
|
||||
data: {
|
||||
quick_replies: [
|
||||
{
|
||||
@ -35,10 +35,10 @@ export const offlineQuickReplies: Offline.OutgoingMessageBase = {
|
||||
],
|
||||
text: 'Choose one option',
|
||||
},
|
||||
type: Offline.OutgoingMessageType.quick_replies,
|
||||
type: Web.OutgoingMessageType.quick_replies,
|
||||
};
|
||||
|
||||
export const offlineButtons: Offline.OutgoingMessageBase = {
|
||||
export const webButtons: Web.OutgoingMessageBase = {
|
||||
data: {
|
||||
buttons: [
|
||||
{
|
||||
@ -56,10 +56,10 @@ export const offlineButtons: Offline.OutgoingMessageBase = {
|
||||
],
|
||||
text: 'Hit one of these buttons :',
|
||||
},
|
||||
type: Offline.OutgoingMessageType.buttons,
|
||||
type: Web.OutgoingMessageType.buttons,
|
||||
};
|
||||
|
||||
export const offlineList: Offline.OutgoingMessageBase = {
|
||||
export const webList: Web.OutgoingMessageBase = {
|
||||
data: {
|
||||
buttons: [
|
||||
{
|
||||
@ -95,10 +95,10 @@ export const offlineList: Offline.OutgoingMessageBase = {
|
||||
},
|
||||
],
|
||||
},
|
||||
type: Offline.OutgoingMessageType.list,
|
||||
type: Web.OutgoingMessageType.list,
|
||||
};
|
||||
|
||||
export const offlineCarousel: Offline.OutgoingMessageBase = {
|
||||
export const webCarousel: Web.OutgoingMessageBase = {
|
||||
data: {
|
||||
elements: [
|
||||
{
|
||||
@ -127,10 +127,10 @@ export const offlineCarousel: Offline.OutgoingMessageBase = {
|
||||
},
|
||||
],
|
||||
},
|
||||
type: Offline.OutgoingMessageType.carousel,
|
||||
type: Web.OutgoingMessageType.carousel,
|
||||
};
|
||||
|
||||
export const offlineAttachment: Offline.OutgoingMessageBase = {
|
||||
export const webAttachment: Web.OutgoingMessageBase = {
|
||||
data: {
|
||||
quick_replies: [
|
||||
{
|
||||
@ -142,5 +142,5 @@ export const offlineAttachment: Offline.OutgoingMessageBase = {
|
||||
type: FileType.image,
|
||||
url: 'http://localhost:4000/attachment/download/1/attachment.jpg',
|
||||
},
|
||||
type: Offline.OutgoingMessageType.file,
|
||||
type: Web.OutgoingMessageType.file,
|
||||
};
|
@ -12,56 +12,55 @@ import {
|
||||
StdEventType,
|
||||
} from '@/chat/schemas/types/message';
|
||||
|
||||
import { Offline } from '../types';
|
||||
import { Web } from '../types';
|
||||
|
||||
const img_url =
|
||||
'http://demo.hexabot.ai/attachment/download/5c334078e2c41d11206bd152/myimage.png';
|
||||
|
||||
// Offline events
|
||||
const offlineEventPayload: Offline.Event = {
|
||||
type: Offline.IncomingMessageType.postback,
|
||||
// Web events
|
||||
const webEventPayload: Web.Event = {
|
||||
type: Web.IncomingMessageType.postback,
|
||||
data: {
|
||||
text: 'Get Started',
|
||||
payload: 'GET_STARTED',
|
||||
},
|
||||
author: 'offline-9be7aq09-b45a-452q-bcs0-f145b9qce1cad',
|
||||
mid: 'offline-event-payload',
|
||||
author: 'web-9be7aq09-b45a-452q-bcs0-f145b9qce1cad',
|
||||
mid: 'web-event-payload',
|
||||
read: true,
|
||||
};
|
||||
|
||||
export const offlineEventText: Offline.IncomingMessage<Offline.IncomingTextMessage> =
|
||||
{
|
||||
type: Offline.IncomingMessageType.text,
|
||||
data: {
|
||||
text: 'Hello',
|
||||
},
|
||||
author: 'offline-9qsdfgqxac09-f83a-452d-bca0-f1qsdqg457c1ad',
|
||||
mid: 'offline-event-text',
|
||||
read: true,
|
||||
};
|
||||
export const webEventText: Web.IncomingMessage<Web.IncomingTextMessage> = {
|
||||
type: Web.IncomingMessageType.text,
|
||||
data: {
|
||||
text: 'Hello',
|
||||
},
|
||||
author: 'web-9qsdfgqxac09-f83a-452d-bca0-f1qsdqg457c1ad',
|
||||
mid: 'web-event-text',
|
||||
read: true,
|
||||
};
|
||||
|
||||
const offlineEventLocation: Offline.IncomingMessage = {
|
||||
type: Offline.IncomingMessageType.location,
|
||||
const webEventLocation: Web.IncomingMessage = {
|
||||
type: Web.IncomingMessageType.location,
|
||||
data: {
|
||||
coordinates: {
|
||||
lat: 2.0545,
|
||||
lng: 12.2558,
|
||||
},
|
||||
},
|
||||
author: 'offline-9beqsdqa09-b489a-438c-bqd0-f11buykkhl851ad',
|
||||
mid: 'offline-event-location',
|
||||
author: 'web-9beqsdqa09-b489a-438c-bqd0-f11buykkhl851ad',
|
||||
mid: 'web-event-location',
|
||||
read: true,
|
||||
};
|
||||
|
||||
const offlineEventFile: Offline.Event = {
|
||||
type: Offline.IncomingMessageType.file,
|
||||
const webEventFile: Web.Event = {
|
||||
type: Web.IncomingMessageType.file,
|
||||
data: {
|
||||
type: FileType.image,
|
||||
url: img_url,
|
||||
size: 500,
|
||||
},
|
||||
author: 'offline-9be8ac09-b43a-432d-bca0-f11b98cec1ad',
|
||||
mid: 'offline-event-file',
|
||||
author: 'web-9be8ac09-b43a-432d-bca0-f11b98cec1ad',
|
||||
mid: 'web-event-file',
|
||||
read: true,
|
||||
};
|
||||
|
||||
@ -85,66 +84,66 @@ const fileChannelData = {
|
||||
ipAddress: '3.3.3.3',
|
||||
};
|
||||
|
||||
export const offlineEvents: [string, Offline.IncomingMessage, any][] = [
|
||||
export const webEvents: [string, Web.IncomingMessage, any][] = [
|
||||
[
|
||||
'Payload Event',
|
||||
offlineEventPayload,
|
||||
webEventPayload,
|
||||
{
|
||||
channelData: payloadChannelData,
|
||||
id: offlineEventPayload.mid,
|
||||
id: webEventPayload.mid,
|
||||
eventType: StdEventType.message,
|
||||
messageType: IncomingMessageType.postback,
|
||||
payload: offlineEventPayload.data.payload,
|
||||
payload: webEventPayload.data.payload,
|
||||
message: {
|
||||
postback: offlineEventPayload.data.payload,
|
||||
text: offlineEventPayload.data.text,
|
||||
postback: webEventPayload.data.payload,
|
||||
text: webEventPayload.data.text,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'Text Event',
|
||||
offlineEventText,
|
||||
webEventText,
|
||||
{
|
||||
channelData: textChannelData,
|
||||
id: offlineEventText.mid,
|
||||
id: webEventText.mid,
|
||||
eventType: StdEventType.message,
|
||||
messageType: IncomingMessageType.message,
|
||||
payload: undefined,
|
||||
message: {
|
||||
text: offlineEventText.data.text,
|
||||
text: webEventText.data.text,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'Location Event',
|
||||
offlineEventLocation,
|
||||
webEventLocation,
|
||||
{
|
||||
channelData: locationChannelData,
|
||||
id: offlineEventLocation.mid,
|
||||
id: webEventLocation.mid,
|
||||
eventType: StdEventType.message,
|
||||
messageType: IncomingMessageType.location,
|
||||
payload: {
|
||||
type: Offline.IncomingMessageType.location,
|
||||
type: Web.IncomingMessageType.location,
|
||||
coordinates: {
|
||||
lat: offlineEventLocation.data.coordinates.lat,
|
||||
lon: offlineEventLocation.data.coordinates.lng,
|
||||
lat: webEventLocation.data.coordinates.lat,
|
||||
lon: webEventLocation.data.coordinates.lng,
|
||||
},
|
||||
},
|
||||
message: {
|
||||
type: Offline.IncomingMessageType.location,
|
||||
type: Web.IncomingMessageType.location,
|
||||
coordinates: {
|
||||
lat: offlineEventLocation.data.coordinates.lat,
|
||||
lon: offlineEventLocation.data.coordinates.lng,
|
||||
lat: webEventLocation.data.coordinates.lat,
|
||||
lon: webEventLocation.data.coordinates.lng,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'File Event',
|
||||
offlineEventFile,
|
||||
webEventFile,
|
||||
{
|
||||
channelData: fileChannelData,
|
||||
id: offlineEventFile.mid,
|
||||
id: webEventFile.mid,
|
||||
eventType: StdEventType.message,
|
||||
messageType: IncomingMessageType.attachments,
|
||||
payload: {
|
@ -48,21 +48,21 @@ import { SocketRequest } from '@/websocket/utils/socket-request';
|
||||
import { SocketResponse } from '@/websocket/utils/socket-response';
|
||||
import { WebsocketGateway } from '@/websocket/websocket.gateway';
|
||||
|
||||
import OfflineHandler from '../index.channel';
|
||||
import WebChannelHandler from '../index.channel';
|
||||
|
||||
import {
|
||||
offlineAttachment,
|
||||
offlineButtons,
|
||||
offlineCarousel,
|
||||
offlineList,
|
||||
offlineQuickReplies,
|
||||
offlineText,
|
||||
webAttachment,
|
||||
webButtons,
|
||||
webCarousel,
|
||||
webList,
|
||||
webQuickReplies,
|
||||
webText,
|
||||
} from './data.mock';
|
||||
|
||||
describe('Offline Handler', () => {
|
||||
describe('WebChannelHandler', () => {
|
||||
let subscriberService: SubscriberService;
|
||||
let handler: OfflineHandler;
|
||||
const offlineSettings = {};
|
||||
let handler: WebChannelHandler;
|
||||
const webSettings = {};
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -87,7 +87,7 @@ describe('Offline Handler', () => {
|
||||
chatbot: { lang: { default: 'fr' } },
|
||||
})),
|
||||
getSettings: jest.fn(() => ({
|
||||
offline: offlineSettings,
|
||||
web: webSettings,
|
||||
})),
|
||||
},
|
||||
},
|
||||
@ -102,7 +102,7 @@ describe('Offline Handler', () => {
|
||||
MessageRepository,
|
||||
MenuService,
|
||||
MenuRepository,
|
||||
OfflineHandler,
|
||||
WebChannelHandler,
|
||||
EventEmitter2,
|
||||
LoggerService,
|
||||
{
|
||||
@ -122,7 +122,7 @@ describe('Offline Handler', () => {
|
||||
],
|
||||
}).compile();
|
||||
subscriberService = module.get<SubscriberService>(SubscriberService);
|
||||
handler = module.get<OfflineHandler>(OfflineHandler);
|
||||
handler = module.get<WebChannelHandler>(WebChannelHandler);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -131,29 +131,29 @@ describe('Offline Handler', () => {
|
||||
|
||||
it('should have correct name', () => {
|
||||
expect(handler).toBeDefined();
|
||||
expect(handler.getName()).toEqual('offline-channel');
|
||||
expect(handler.getName()).toEqual('web-channel');
|
||||
});
|
||||
|
||||
it('should format text properly', () => {
|
||||
const formatted = handler._textFormat(textMessage, {});
|
||||
expect(formatted).toEqual(offlineText);
|
||||
expect(formatted).toEqual(webText);
|
||||
});
|
||||
|
||||
it('should format quick replies properly', () => {
|
||||
const formatted = handler._quickRepliesFormat(quickRepliesMessage, {});
|
||||
expect(formatted).toEqual(offlineQuickReplies);
|
||||
expect(formatted).toEqual(webQuickReplies);
|
||||
});
|
||||
|
||||
it('should format buttons properly', () => {
|
||||
const formatted = handler._buttonsFormat(buttonsMessage, {});
|
||||
expect(formatted).toEqual(offlineButtons);
|
||||
expect(formatted).toEqual(webButtons);
|
||||
});
|
||||
|
||||
it('should format list properly', () => {
|
||||
const formatted = handler._listFormat(contentMessage, {
|
||||
content: contentMessage.options,
|
||||
});
|
||||
expect(formatted).toEqual(offlineList);
|
||||
expect(formatted).toEqual(webList);
|
||||
});
|
||||
|
||||
it('should format carousel properly', () => {
|
||||
@ -163,12 +163,12 @@ describe('Offline Handler', () => {
|
||||
display: OutgoingMessageFormat.carousel,
|
||||
},
|
||||
});
|
||||
expect(formatted).toEqual(offlineCarousel);
|
||||
expect(formatted).toEqual(webCarousel);
|
||||
});
|
||||
|
||||
it('should format attachment properly', () => {
|
||||
const formatted = handler._attachmentFormat(attachmentMessage, {});
|
||||
expect(formatted).toEqual(offlineAttachment);
|
||||
expect(formatted).toEqual(webAttachment);
|
||||
});
|
||||
|
||||
it('creates a new subscriber if needed + set a new session', async () => {
|
||||
@ -180,7 +180,7 @@ describe('Offline Handler', () => {
|
||||
user: {},
|
||||
} as any as Request;
|
||||
|
||||
const generatedId = 'offline-test';
|
||||
const generatedId = 'web-test';
|
||||
const clearMock = jest
|
||||
.spyOn(handler, 'generateId')
|
||||
.mockImplementation(() => generatedId);
|
||||
@ -192,7 +192,7 @@ describe('Offline Handler', () => {
|
||||
agent: req.headers['user-agent'],
|
||||
ipAddress: '0.0.0.0',
|
||||
isSocket: false,
|
||||
name: 'offline-channel',
|
||||
name: 'web-channel',
|
||||
},
|
||||
country: '',
|
||||
first_name: req.query.first_name,
|
||||
@ -209,7 +209,7 @@ describe('Offline Handler', () => {
|
||||
}, {});
|
||||
expect(subscriberAttrs).toEqual(expectedAttrs);
|
||||
expect(req.session).toEqual({
|
||||
offline: {
|
||||
web: {
|
||||
isSocket: false,
|
||||
messageQueue: [],
|
||||
polling: false,
|
||||
@ -222,7 +222,7 @@ describe('Offline Handler', () => {
|
||||
const subscriber2nd = await handler['getOrCreateSession'](req);
|
||||
expect(subscriber2nd.id).toBe(subscriber.id);
|
||||
expect(req.session).toEqual({
|
||||
offline: {
|
||||
web: {
|
||||
isSocket: false,
|
||||
messageQueue: [],
|
||||
polling: false,
|
||||
@ -232,9 +232,8 @@ describe('Offline Handler', () => {
|
||||
});
|
||||
|
||||
it('subscribes and returns the message history', async () => {
|
||||
const subscriber = await subscriberService.findOneByForeignIdAndPopulate(
|
||||
'foreign-id-offline-1',
|
||||
);
|
||||
const subscriber =
|
||||
await subscriberService.findOneByForeignIdAndPopulate('foreign-id-web-1');
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const req = {
|
||||
@ -257,7 +256,7 @@ describe('Offline Handler', () => {
|
||||
} as any as SocketResponse;
|
||||
req.session = {
|
||||
cookie: { originalMaxAge: 0 },
|
||||
offline: {
|
||||
web: {
|
||||
isSocket: true,
|
||||
messageQueue: [],
|
||||
polling: false,
|
@ -36,14 +36,14 @@ import {
|
||||
import { SocketEventDispatcherService } from '@/websocket/services/socket-event-dispatcher.service';
|
||||
import { WebsocketGateway } from '@/websocket/websocket.gateway';
|
||||
|
||||
import OfflineHandler from '../index.channel';
|
||||
import OfflineEventWrapper from '../wrapper';
|
||||
import WebChannelHandler from '../index.channel';
|
||||
import WebEventWrapper from '../wrapper';
|
||||
|
||||
import { offlineEvents } from './events.mock';
|
||||
import { webEvents } from './events.mock';
|
||||
|
||||
describe(`Offline event wrapper`, () => {
|
||||
let handler: OfflineHandler;
|
||||
const offlineSettings = {};
|
||||
describe(`Web event wrapper`, () => {
|
||||
let handler: WebChannelHandler;
|
||||
const webSettings = {};
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
@ -65,7 +65,7 @@ describe(`Offline event wrapper`, () => {
|
||||
chatbot: { lang: { default: 'fr' } },
|
||||
})),
|
||||
getSettings: jest.fn(() => ({
|
||||
offline: offlineSettings,
|
||||
web: webSettings,
|
||||
})),
|
||||
},
|
||||
},
|
||||
@ -86,7 +86,7 @@ describe(`Offline event wrapper`, () => {
|
||||
MessageRepository,
|
||||
MenuService,
|
||||
MenuRepository,
|
||||
OfflineHandler,
|
||||
WebChannelHandler,
|
||||
EventEmitter2,
|
||||
LoggerService,
|
||||
{
|
||||
@ -105,7 +105,7 @@ describe(`Offline event wrapper`, () => {
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
handler = module.get<OfflineHandler>(OfflineHandler);
|
||||
handler = module.get<WebChannelHandler>(WebChannelHandler);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -113,21 +113,18 @@ describe(`Offline event wrapper`, () => {
|
||||
await closeInMongodConnection();
|
||||
});
|
||||
|
||||
test.each(offlineEvents)(
|
||||
'should wrap event : %s',
|
||||
(_testCase, e, expected) => {
|
||||
const event = new OfflineEventWrapper(
|
||||
handler as unknown as OfflineHandler,
|
||||
e,
|
||||
expected.channelData,
|
||||
);
|
||||
expect(event.getChannelData()).toEqual(expected.channelData);
|
||||
expect(event.getId()).toEqual(expected.id);
|
||||
expect(event.getEventType()).toEqual(expected.eventType);
|
||||
expect(event.getMessageType()).toEqual(expected.messageType);
|
||||
expect(event.getPayload()).toEqual(expected.payload);
|
||||
expect(event.getMessage()).toEqual(expected.message);
|
||||
expect(event.getDeliveredMessages()).toEqual([]);
|
||||
},
|
||||
);
|
||||
test.each(webEvents)('should wrap event : %s', (_testCase, e, expected) => {
|
||||
const event = new WebEventWrapper(
|
||||
handler as unknown as WebChannelHandler,
|
||||
e,
|
||||
expected.channelData,
|
||||
);
|
||||
expect(event.getChannelData()).toEqual(expected.channelData);
|
||||
expect(event.getId()).toEqual(expected.id);
|
||||
expect(event.getEventType()).toEqual(expected.eventType);
|
||||
expect(event.getMessageType()).toEqual(expected.messageType);
|
||||
expect(event.getPayload()).toEqual(expected.payload);
|
||||
expect(event.getMessage()).toEqual(expected.message);
|
||||
expect(event.getDeliveredMessages()).toEqual([]);
|
||||
});
|
||||
});
|
@ -58,9 +58,9 @@ import { SocketRequest } from '@/websocket/utils/socket-request';
|
||||
import { SocketResponse } from '@/websocket/utils/socket-response';
|
||||
import { WebsocketGateway } from '@/websocket/websocket.gateway';
|
||||
|
||||
import { OFFLINE_CHANNEL_NAMESPACE } from './settings';
|
||||
import { Offline } from './types';
|
||||
import OfflineEventWrapper from './wrapper';
|
||||
import { WEB_CHANNEL_NAMESPACE } from './settings';
|
||||
import { Web } from './types';
|
||||
import WebEventWrapper from './wrapper';
|
||||
|
||||
@Injectable()
|
||||
export default abstract class BaseWebChannelHandler<
|
||||
@ -92,7 +92,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify offline websocket connection and return settings
|
||||
* Verify web websocket connection and return settings
|
||||
*
|
||||
* @param client - The socket client
|
||||
*/
|
||||
@ -138,20 +138,20 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt incoming message structure for offline channel
|
||||
* Adapt incoming message structure for web channel
|
||||
*
|
||||
* @param incoming - Incoming message
|
||||
* @returns Formatted offline message
|
||||
* @returns Formatted web message
|
||||
*/
|
||||
private formatIncomingHistoryMessage(
|
||||
incoming: IncomingMessage,
|
||||
): Offline.IncomingMessageBase {
|
||||
): Web.IncomingMessageBase {
|
||||
// Format incoming message
|
||||
if ('type' in incoming.message) {
|
||||
if (incoming.message.type === PayloadType.location) {
|
||||
const coordinates = incoming.message.coordinates;
|
||||
return {
|
||||
type: Offline.IncomingMessageType.location,
|
||||
type: Web.IncomingMessageType.location,
|
||||
data: {
|
||||
coordinates: {
|
||||
lat: coordinates.lat,
|
||||
@ -165,7 +165,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
? incoming.message.attachment[0]
|
||||
: incoming.message.attachment;
|
||||
return {
|
||||
type: Offline.IncomingMessageType.file,
|
||||
type: Web.IncomingMessageType.file,
|
||||
data: {
|
||||
type: attachment.type,
|
||||
url: attachment.payload.url,
|
||||
@ -174,21 +174,21 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: Offline.IncomingMessageType.text,
|
||||
type: Web.IncomingMessageType.text,
|
||||
data: incoming.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt the outgoing message structure for offline channel
|
||||
* Adapt the outgoing message structure for web channel
|
||||
*
|
||||
* @param outgoing - The outgoing message
|
||||
* @returns Formatted offline message
|
||||
* @returns Formatted web message
|
||||
*/
|
||||
private formatOutgoingHistoryMessage(
|
||||
outgoing: OutgoingMessage,
|
||||
): Offline.OutgoingMessageBase {
|
||||
): Web.OutgoingMessageBase {
|
||||
// Format outgoing message
|
||||
if ('buttons' in outgoing.message) {
|
||||
return this._buttonsFormat(outgoing.message);
|
||||
@ -212,13 +212,13 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt the message structure for offline channel
|
||||
* Adapt the message structure for web channel
|
||||
*
|
||||
* @param messages - The messages to be formatted
|
||||
*
|
||||
* @returns Formatted message
|
||||
*/
|
||||
private formatHistoryMessages(messages: AnyMessage[]): Offline.Message[] {
|
||||
private formatHistoryMessages(messages: AnyMessage[]): Web.Message[] {
|
||||
return messages.map((anyMessage: AnyMessage) => {
|
||||
if ('sender' in anyMessage && anyMessage.sender) {
|
||||
return {
|
||||
@ -227,7 +227,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
read: true, // Temporary fix as read is false in the bd
|
||||
mid: anyMessage.mid,
|
||||
createdAt: anyMessage.createdAt,
|
||||
} as Offline.IncomingMessage;
|
||||
} as Web.IncomingMessage;
|
||||
} else {
|
||||
const outgoingMessage = anyMessage as OutgoingMessage;
|
||||
return {
|
||||
@ -237,7 +237,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
mid: outgoingMessage.mid,
|
||||
handover: !!outgoingMessage.handover,
|
||||
createdAt: outgoingMessage.createdAt,
|
||||
} as Offline.OutgoingMessage;
|
||||
} as Web.OutgoingMessage;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -254,8 +254,8 @@ export default abstract class BaseWebChannelHandler<
|
||||
req: Request | SocketRequest,
|
||||
until: Date = new Date(),
|
||||
n: number = 30,
|
||||
): Promise<Offline.Message[]> {
|
||||
const profile = req.session?.offline?.profile;
|
||||
): Promise<Web.Message[]> {
|
||||
const profile = req.session?.web?.profile;
|
||||
if (profile) {
|
||||
const messages = await this.messageService.findHistoryUntilDate(
|
||||
profile,
|
||||
@ -279,8 +279,8 @@ export default abstract class BaseWebChannelHandler<
|
||||
req: Request,
|
||||
since: Date = new Date(10e14),
|
||||
n: number = 30,
|
||||
): Promise<Offline.Message[]> {
|
||||
const profile = req.session?.offline?.profile;
|
||||
): Promise<Web.Message[]> {
|
||||
const profile = req.session?.web?.profile;
|
||||
if (profile) {
|
||||
const messages = await this.messageService.findHistorySinceDate(
|
||||
profile,
|
||||
@ -299,7 +299,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
*/
|
||||
private async verifyToken(verificationToken: string) {
|
||||
const settings =
|
||||
(await this.getSettings()) as Settings[typeof OFFLINE_CHANNEL_NAMESPACE];
|
||||
(await this.getSettings()) as Settings[typeof WEB_CHANNEL_NAMESPACE];
|
||||
const verifyToken = settings.verification_token;
|
||||
|
||||
if (!verifyToken) {
|
||||
@ -326,7 +326,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
req: Request | SocketRequest,
|
||||
res: Response | SocketResponse,
|
||||
) {
|
||||
const settings = await this.getSettings<typeof OFFLINE_CHANNEL_NAMESPACE>();
|
||||
const settings = await this.getSettings<typeof WEB_CHANNEL_NAMESPACE>();
|
||||
// If we have an origin header...
|
||||
if (req.headers && req.headers.origin) {
|
||||
// Get the allowed origins
|
||||
@ -376,7 +376,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
res: Response | SocketResponse,
|
||||
next: (profile: Subscriber) => void,
|
||||
) {
|
||||
if (!req.session?.offline?.profile?.id) {
|
||||
if (!req.session?.web?.profile?.id) {
|
||||
this.logger.warn(
|
||||
'Web Channel Handler : No session ID to be found!',
|
||||
req.session,
|
||||
@ -385,8 +385,8 @@ export default abstract class BaseWebChannelHandler<
|
||||
.status(403)
|
||||
.json({ err: 'Web Channel Handler : Unauthorized!' });
|
||||
} else if (
|
||||
('isSocket' in req && !!req.isSocket !== req.session.offline.isSocket) ||
|
||||
!Array.isArray(req.session.offline.messageQueue)
|
||||
('isSocket' in req && !!req.isSocket !== req.session.web.isSocket) ||
|
||||
!Array.isArray(req.session.web.messageQueue)
|
||||
) {
|
||||
this.logger.warn(
|
||||
'Web Channel Handler : Mixed channel request or invalid session data!',
|
||||
@ -396,7 +396,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
.status(403)
|
||||
.json({ err: 'Web Channel Handler : Unauthorized!' });
|
||||
}
|
||||
next(req.session?.offline?.profile);
|
||||
next(req.session?.web?.profile);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -440,15 +440,15 @@ export default abstract class BaseWebChannelHandler<
|
||||
): Promise<SubscriberFull> {
|
||||
const data = req.query;
|
||||
// Subscriber has already a session
|
||||
const sessionProfile = req.session?.offline?.profile;
|
||||
const sessionProfile = req.session?.web?.profile;
|
||||
if (sessionProfile) {
|
||||
const subscriber = await this.subscriberService.findOneAndPopulate(
|
||||
sessionProfile.id,
|
||||
);
|
||||
if (!subscriber || !req.session.offline) {
|
||||
if (!subscriber || !req.session.web) {
|
||||
throw new Error('Subscriber session was not persisted in DB');
|
||||
}
|
||||
req.session.offline.profile = subscriber;
|
||||
req.session.web.profile = subscriber;
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
@ -456,7 +456,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
const newProfile: SubscriberCreateDto = {
|
||||
foreign_id: this.generateId(),
|
||||
first_name: data.first_name ? data.first_name.toString() : 'Anon.',
|
||||
last_name: data.last_name ? data.last_name.toString() : 'Offline User',
|
||||
last_name: data.last_name ? data.last_name.toString() : 'Web User',
|
||||
assignedTo: null,
|
||||
assignedAt: null,
|
||||
lastvisit: new Date(),
|
||||
@ -481,7 +481,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
avatar: null,
|
||||
};
|
||||
|
||||
req.session.offline = {
|
||||
req.session.web = {
|
||||
profile,
|
||||
isSocket: 'isSocket' in req && !!req.isSocket,
|
||||
messageQueue: [],
|
||||
@ -507,9 +507,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
.json({ err: 'Polling not authorized when using websockets' });
|
||||
}
|
||||
// Session must be active
|
||||
if (
|
||||
!(req.session && req.session.offline && req.session.offline.profile.id)
|
||||
) {
|
||||
if (!(req.session && req.session.web && req.session.web.profile.id)) {
|
||||
this.logger.warn(
|
||||
'Web Channel Handler : Must be connected to poll messages',
|
||||
);
|
||||
@ -519,7 +517,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
|
||||
// Can only request polling once at a time
|
||||
if (req.session && req.session.offline && req.session.offline.polling) {
|
||||
if (req.session && req.session.web && req.session.web.polling) {
|
||||
this.logger.warn(
|
||||
'Web Channel Handler : Poll rejected ... already requested',
|
||||
);
|
||||
@ -528,7 +526,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
.json({ err: 'Poll rejected ... already requested' });
|
||||
}
|
||||
|
||||
req.session.offline.polling = true;
|
||||
req.session.web.polling = true;
|
||||
|
||||
const fetchMessages = async (req: Request, res: Response, retrials = 1) => {
|
||||
try {
|
||||
@ -539,8 +537,8 @@ export default abstract class BaseWebChannelHandler<
|
||||
setTimeout(async () => {
|
||||
await fetchMessages(req, res, retrials * 2);
|
||||
}, retrials * 1000);
|
||||
} else if (req.session.offline) {
|
||||
req.session.offline.polling = false;
|
||||
} else if (req.session.web) {
|
||||
req.session.web.polling = false;
|
||||
return res.status(200).json(messages.map((msg) => ['message', msg]));
|
||||
} else {
|
||||
this.logger.error(
|
||||
@ -549,8 +547,8 @@ export default abstract class BaseWebChannelHandler<
|
||||
return res.status(500).json({ err: 'No session data' });
|
||||
}
|
||||
} catch (err) {
|
||||
if (req.session.offline) {
|
||||
req.session.offline.polling = false;
|
||||
if (req.session.web) {
|
||||
req.session.web.polling = false;
|
||||
}
|
||||
this.logger.error('Web Channel Handler : Polling failed', err);
|
||||
return res.status(500).json({ err: 'Polling failed' });
|
||||
@ -560,7 +558,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the subscription to a offline's webhook after verification
|
||||
* Allow the subscription to a web's webhook after verification
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
@ -608,7 +606,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
* @param filename
|
||||
*/
|
||||
private async storeAttachment(
|
||||
upload: Omit<Offline.IncomingAttachmentMessageData, 'url' | 'file'>,
|
||||
upload: Omit<Web.IncomingAttachmentMessageData, 'url' | 'file'>,
|
||||
filename: string,
|
||||
next: (
|
||||
err: Error | null,
|
||||
@ -623,7 +621,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
type: upload.type || 'text/txt',
|
||||
size: upload.size || 0,
|
||||
location: filename,
|
||||
channel: { offline: {} },
|
||||
channel: { web: {} },
|
||||
});
|
||||
|
||||
this.logger.debug(
|
||||
@ -657,9 +655,9 @@ export default abstract class BaseWebChannelHandler<
|
||||
result: { type: string; url: string } | false,
|
||||
) => void,
|
||||
): Promise<void> {
|
||||
const data: Offline.IncomingMessage = req.body;
|
||||
const data: Web.IncomingMessage = req.body;
|
||||
// Check if any file is provided
|
||||
if (!req.session.offline) {
|
||||
if (!req.session.web) {
|
||||
this.logger.debug('Web Channel Handler : No session provided');
|
||||
return next(null, false);
|
||||
}
|
||||
@ -682,7 +680,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
// Store file as attachment
|
||||
const dirPath = path.join(config.parameters.uploadDir);
|
||||
const sanitizedFilename = sanitize(
|
||||
`${req.session.offline.profile.id}_${+new Date()}_${upload.name}`,
|
||||
`${req.session.web.profile.id}_${+new Date()}_${upload.name}`,
|
||||
);
|
||||
const filePath = path.resolve(dirPath, sanitizedFilename);
|
||||
|
||||
@ -763,7 +761,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
*
|
||||
* @returns The channel's data
|
||||
*/
|
||||
protected getChannelData(req: Request | SocketRequest): Offline.ChannelData {
|
||||
protected getChannelData(req: Request | SocketRequest): Web.ChannelData {
|
||||
return {
|
||||
isSocket: 'isSocket' in req && !!req.isSocket,
|
||||
ipAddress: this.getIpAddress(req),
|
||||
@ -781,13 +779,13 @@ export default abstract class BaseWebChannelHandler<
|
||||
req: Request | SocketRequest,
|
||||
res: Response | SocketResponse,
|
||||
): void {
|
||||
const data: Offline.IncomingMessage = req.body;
|
||||
const data: Web.IncomingMessage = req.body;
|
||||
this.validateSession(req, res, (profile) => {
|
||||
this.handleFilesUpload(
|
||||
req,
|
||||
res,
|
||||
// @ts-expect-error @TODO : This needs to be fixed at a later point @TODO
|
||||
(err: Error, upload: Offline.IncomingMessageData) => {
|
||||
(err: Error, upload: Web.IncomingMessageData) => {
|
||||
if (err) {
|
||||
this.logger.warn(
|
||||
'Web Channel Handler : Unable to upload file ',
|
||||
@ -802,7 +800,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
data.data = upload;
|
||||
}
|
||||
const channelData = this.getChannelData(req);
|
||||
const event: OfflineEventWrapper = new OfflineEventWrapper(
|
||||
const event: WebEventWrapper = new WebEventWrapper(
|
||||
this,
|
||||
data,
|
||||
channelData,
|
||||
@ -844,14 +842,14 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
|
||||
/**
|
||||
* Process incoming Offline data (finding out its type and assigning it to its proper handler)
|
||||
* Process incoming Web Channel data (finding out its type and assigning it to its proper handler)
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
async handle(req: Request | SocketRequest, res: Response | SocketResponse) {
|
||||
const settings = await this.getSettings();
|
||||
// Offline messaging can be done through websockets or long-polling
|
||||
// Web Channel messaging can be done through websockets or long-polling
|
||||
try {
|
||||
await this.checkRequest(req, res);
|
||||
if (req.method === 'GET') {
|
||||
@ -887,7 +885,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
.json({ err: 'Webhook received unknown command' });
|
||||
}
|
||||
} else if (req.query._disconnect) {
|
||||
req.session.offline = undefined;
|
||||
req.session.web = undefined;
|
||||
return res.status(200).json({ _disconnect: true });
|
||||
} else {
|
||||
// Handle webhook subscribe requests
|
||||
@ -911,7 +909,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
* @returns UUID
|
||||
*/
|
||||
generateId(): string {
|
||||
return 'offline-' + uuidv4();
|
||||
return 'web-' + uuidv4();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -925,9 +923,9 @@ export default abstract class BaseWebChannelHandler<
|
||||
_textFormat(
|
||||
message: StdOutgoingTextMessage,
|
||||
_options?: BlockOptions,
|
||||
): Offline.OutgoingMessageBase {
|
||||
): Web.OutgoingMessageBase {
|
||||
return {
|
||||
type: Offline.OutgoingMessageType.text,
|
||||
type: Web.OutgoingMessageType.text,
|
||||
data: message,
|
||||
};
|
||||
}
|
||||
@ -943,9 +941,9 @@ export default abstract class BaseWebChannelHandler<
|
||||
_quickRepliesFormat(
|
||||
message: StdOutgoingQuickRepliesMessage,
|
||||
_options?: BlockOptions,
|
||||
): Offline.OutgoingMessageBase {
|
||||
): Web.OutgoingMessageBase {
|
||||
return {
|
||||
type: Offline.OutgoingMessageType.quick_replies,
|
||||
type: Web.OutgoingMessageType.quick_replies,
|
||||
data: {
|
||||
text: message.text,
|
||||
quick_replies: message.quickReplies,
|
||||
@ -964,9 +962,9 @@ export default abstract class BaseWebChannelHandler<
|
||||
_buttonsFormat(
|
||||
message: StdOutgoingButtonsMessage,
|
||||
_options?: BlockOptions,
|
||||
): Offline.OutgoingMessageBase {
|
||||
): Web.OutgoingMessageBase {
|
||||
return {
|
||||
type: Offline.OutgoingMessageType.buttons,
|
||||
type: Web.OutgoingMessageType.buttons,
|
||||
data: {
|
||||
text: message.text,
|
||||
buttons: message.buttons,
|
||||
@ -985,9 +983,9 @@ export default abstract class BaseWebChannelHandler<
|
||||
_attachmentFormat(
|
||||
message: StdOutgoingAttachmentMessage<WithUrl<Attachment>>,
|
||||
_options?: BlockOptions,
|
||||
): Offline.OutgoingMessageBase {
|
||||
const payload: Offline.OutgoingMessageBase = {
|
||||
type: Offline.OutgoingMessageType.file,
|
||||
): Web.OutgoingMessageBase {
|
||||
const payload: Web.OutgoingMessageBase = {
|
||||
type: Web.OutgoingMessageType.file,
|
||||
data: {
|
||||
type: message.attachment.type,
|
||||
url: message.attachment.payload.url,
|
||||
@ -1007,10 +1005,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
*
|
||||
* @returns An array of elements object
|
||||
*/
|
||||
_formatElements(
|
||||
data: any[],
|
||||
options: BlockOptions,
|
||||
): Offline.MessageElement[] {
|
||||
_formatElements(data: any[], options: BlockOptions): Web.MessageElement[] {
|
||||
if (!options.content || !options.content.fields) {
|
||||
throw new Error('Content options are missing the fields');
|
||||
}
|
||||
@ -1018,7 +1013,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
const fields = options.content.fields;
|
||||
const buttons: Button[] = options.content.buttons;
|
||||
return data.map((item) => {
|
||||
const element: Offline.MessageElement = {
|
||||
const element: Web.MessageElement = {
|
||||
title: item[fields.title],
|
||||
buttons: item.buttons || [],
|
||||
};
|
||||
@ -1031,7 +1026,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
if (!attachmentPayload.id) {
|
||||
// @deprecated
|
||||
this.logger.warn(
|
||||
'Offline Channel Handler: Attachment remote url has been deprecated',
|
||||
'Web Channel Handler: Attachment remote url has been deprecated',
|
||||
item,
|
||||
);
|
||||
}
|
||||
@ -1090,11 +1085,11 @@ export default abstract class BaseWebChannelHandler<
|
||||
_listFormat(
|
||||
message: StdOutgoingListMessage,
|
||||
options: BlockOptions,
|
||||
): Offline.OutgoingMessageBase {
|
||||
): Web.OutgoingMessageBase {
|
||||
const data = message.elements || [];
|
||||
const pagination = message.pagination;
|
||||
let buttons: Button[] = [],
|
||||
elements: Offline.MessageElement[] = [];
|
||||
elements: Web.MessageElement[] = [];
|
||||
|
||||
// Items count min check
|
||||
if (!data.length) {
|
||||
@ -1123,7 +1118,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
: {};
|
||||
return {
|
||||
type: Offline.OutgoingMessageType.list,
|
||||
type: Web.OutgoingMessageType.list,
|
||||
data: {
|
||||
elements,
|
||||
buttons,
|
||||
@ -1143,7 +1138,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
_carouselFormat(
|
||||
message: StdOutgoingListMessage,
|
||||
options: BlockOptions,
|
||||
): Offline.OutgoingMessageBase {
|
||||
): Web.OutgoingMessageBase {
|
||||
const data = message.elements || [];
|
||||
// Items count min check
|
||||
if (data.length === 0) {
|
||||
@ -1156,7 +1151,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
// Populate items (elements/cards) with content
|
||||
const elements = this._formatElements(data, options);
|
||||
return {
|
||||
type: Offline.OutgoingMessageType.carousel,
|
||||
type: Web.OutgoingMessageType.carousel,
|
||||
data: {
|
||||
elements,
|
||||
},
|
||||
@ -1174,7 +1169,7 @@ export default abstract class BaseWebChannelHandler<
|
||||
_formatMessage(
|
||||
envelope: StdOutgoingEnvelope,
|
||||
options: BlockOptions,
|
||||
): Offline.OutgoingMessageBase {
|
||||
): Web.OutgoingMessageBase {
|
||||
switch (envelope.format) {
|
||||
case OutgoingMessageFormat.attachment:
|
||||
return this._attachmentFormat(envelope.message, options);
|
||||
@ -1214,14 +1209,14 @@ export default abstract class BaseWebChannelHandler<
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Offline Message to the end-user
|
||||
* Send a Web Channel Message to the end-user
|
||||
*
|
||||
* @param event - Incoming event/message being responded to
|
||||
* @param envelope - The message to be sent {format, message}
|
||||
* @param options - Might contain additional settings
|
||||
* @param _context - Contextual data
|
||||
*
|
||||
* @returns The offline's response, otherwise an error
|
||||
* @returns The web's response, otherwise an error
|
||||
*/
|
||||
async sendMessage(
|
||||
event: EventWrapper<any, any>,
|
||||
@ -1229,13 +1224,13 @@ export default abstract class BaseWebChannelHandler<
|
||||
options: BlockOptions,
|
||||
_context?: any,
|
||||
): Promise<{ mid: string }> {
|
||||
const messageBase: Offline.OutgoingMessageBase = this._formatMessage(
|
||||
const messageBase: Web.OutgoingMessageBase = this._formatMessage(
|
||||
envelope,
|
||||
options,
|
||||
);
|
||||
const subscriber = event.getSender();
|
||||
|
||||
const message: Offline.OutgoingMessage = {
|
||||
const message: Web.OutgoingMessage = {
|
||||
...messageBase,
|
||||
mid: this.generateId(),
|
||||
author: 'chatbot',
|
||||
@ -1296,9 +1291,9 @@ export default abstract class BaseWebChannelHandler<
|
||||
*
|
||||
* @param event - The message event received
|
||||
*
|
||||
* @returns The offline's response, otherwise an error
|
||||
* @returns The web's response, otherwise an error
|
||||
*/
|
||||
async getUserData(event: OfflineEventWrapper): Promise<SubscriberCreateDto> {
|
||||
async getUserData(event: WebEventWrapper): Promise<SubscriberCreateDto> {
|
||||
return event.getSender() as SubscriberCreateDto;
|
||||
}
|
||||
}
|
3
api/src/extensions/channels/web/i18n/en/title.json
Normal file
3
api/src/extensions/channels/web/i18n/en/title.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"web_channel": "Web Channel"
|
||||
}
|
3
api/src/extensions/channels/web/i18n/fr/title.json
Normal file
3
api/src/extensions/channels/web/i18n/fr/title.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"web_channel": "Canal Web"
|
||||
}
|
@ -20,11 +20,11 @@ import { SettingService } from '@/setting/services/setting.service';
|
||||
import { WebsocketGateway } from '@/websocket/websocket.gateway';
|
||||
|
||||
import BaseWebChannelHandler from './base-web-channel';
|
||||
import { OFFLINE_CHANNEL_NAME } from './settings';
|
||||
import { WEB_CHANNEL_NAME } from './settings';
|
||||
|
||||
@Injectable()
|
||||
export default class OfflineHandler extends BaseWebChannelHandler<
|
||||
typeof OFFLINE_CHANNEL_NAME
|
||||
export default class WebChannelHandler extends BaseWebChannelHandler<
|
||||
typeof WEB_CHANNEL_NAME
|
||||
> {
|
||||
constructor(
|
||||
settingService: SettingService,
|
||||
@ -39,7 +39,7 @@ export default class OfflineHandler extends BaseWebChannelHandler<
|
||||
websocketGateway: WebsocketGateway,
|
||||
) {
|
||||
super(
|
||||
OFFLINE_CHANNEL_NAME,
|
||||
WEB_CHANNEL_NAME,
|
||||
settingService,
|
||||
channelService,
|
||||
logger,
|
16
api/src/extensions/channels/web/index.d.ts
vendored
Normal file
16
api/src/extensions/channels/web/index.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
import DEFAULT_WEB_CHANNEL_SETTINGS, {
|
||||
WEB_CHANNEL_NAMESPACE,
|
||||
} from './settings';
|
||||
|
||||
declare global {
|
||||
interface Settings extends SettingTree<typeof DEFAULT_WEB_CHANNEL_SETTINGS> {}
|
||||
}
|
||||
|
||||
declare module '@nestjs/event-emitter' {
|
||||
interface IHookExtensionsOperationMap {
|
||||
[WEB_CHANNEL_NAMESPACE]: TDefinition<
|
||||
object,
|
||||
SettingMapByType<typeof DEFAULT_WEB_CHANNEL_SETTINGS>
|
||||
>;
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "hexabot-channel-offline",
|
||||
"name": "hexabot-channel-web",
|
||||
"version": "2.0.0",
|
||||
"description": "The Web Channel Extension for Hexabot Chatbot / Agent Builder for website integration",
|
||||
"author": "Hexastack",
|
@ -9,97 +9,97 @@
|
||||
import { ChannelSetting } from '@/channel/types';
|
||||
import { SettingType } from '@/setting/schemas/types';
|
||||
|
||||
import { Offline } from './types';
|
||||
import { Web } from './types';
|
||||
|
||||
export const OFFLINE_CHANNEL_NAME = 'offline-channel' as const;
|
||||
export const WEB_CHANNEL_NAME = 'web-channel' as const;
|
||||
|
||||
export const OFFLINE_CHANNEL_NAMESPACE = 'offline_channel';
|
||||
export const WEB_CHANNEL_NAMESPACE = 'web_channel';
|
||||
|
||||
export default [
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.verification_token,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.verification_token,
|
||||
value: 'token123',
|
||||
type: SettingType.secret,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.allowed_domains,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.allowed_domains,
|
||||
value: 'http://localhost:8080,http://localhost:4000',
|
||||
type: SettingType.text,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.start_button,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.start_button,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.input_disabled,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.input_disabled,
|
||||
value: false,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.persistent_menu,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.persistent_menu,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.greeting_message,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.greeting_message,
|
||||
value: 'Welcome! Ready to start a conversation with our chatbot?',
|
||||
type: SettingType.textarea,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.theme_color,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.theme_color,
|
||||
value: 'teal',
|
||||
type: SettingType.select,
|
||||
options: ['teal', 'orange', 'red', 'green', 'blue', 'dark'],
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.window_title,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.window_title,
|
||||
value: 'Widget Title',
|
||||
type: SettingType.text,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.avatar_url,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.avatar_url,
|
||||
value: 'https://eu.ui-avatars.com/api/?name=Hexa+Bot&size=64',
|
||||
type: SettingType.text,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.show_emoji,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.show_emoji,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.show_file,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.show_file,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.show_location,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.show_location,
|
||||
value: true,
|
||||
type: SettingType.checkbox,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.allowed_upload_size,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.allowed_upload_size,
|
||||
value: 2500000,
|
||||
type: SettingType.number,
|
||||
},
|
||||
{
|
||||
group: OFFLINE_CHANNEL_NAMESPACE,
|
||||
label: Offline.SettingLabel.allowed_upload_types,
|
||||
group: WEB_CHANNEL_NAMESPACE,
|
||||
label: Web.SettingLabel.allowed_upload_types,
|
||||
value:
|
||||
'audio/mpeg,audio/x-ms-wma,audio/vnd.rn-realaudio,audio/x-wav,image/gif,image/jpeg,image/png,image/tiff,image/vnd.microsoft.icon,image/vnd.djvu,image/svg+xml,text/css,text/csv,text/html,text/plain,text/xml,video/mpeg,video/mp4,video/quicktime,video/x-ms-wmv,video/x-msvideo,video/x-flv,video/web,application/msword,application/vnd.ms-powerpoint,application/pdf,application/vnd.ms-excel,application/vnd.oasis.opendocument.presentation,application/vnd.oasis.opendocument.tex,application/vnd.oasis.opendocument.spreadsheet,application/vnd.oasis.opendocument.graphics,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
type: SettingType.textarea,
|
||||
},
|
||||
] as const satisfies ChannelSetting<typeof OFFLINE_CHANNEL_NAME>[];
|
||||
] as const satisfies ChannelSetting<typeof WEB_CHANNEL_NAME>[];
|
@ -11,7 +11,7 @@ import { Button, WebUrlButton } from '@/chat/schemas/types/button';
|
||||
import { FileType } from '@/chat/schemas/types/message';
|
||||
import { StdQuickReply } from '@/chat/schemas/types/quick-reply';
|
||||
|
||||
export namespace Offline {
|
||||
export namespace Web {
|
||||
export enum SettingLabel {
|
||||
secret = 'secret',
|
||||
verification_token = 'verification_token',
|
||||
@ -39,7 +39,7 @@ export namespace Offline {
|
||||
};
|
||||
|
||||
export type RequestSession = {
|
||||
offline?: {
|
||||
web?: {
|
||||
profile: SubscriberFull;
|
||||
isSocket: boolean;
|
||||
messageQueue: any[];
|
||||
@ -61,7 +61,7 @@ export namespace Offline {
|
||||
file = 'file',
|
||||
}
|
||||
|
||||
export type EventType = Offline.StatusEventType | Offline.IncomingMessageType;
|
||||
export type EventType = Web.StatusEventType | Web.IncomingMessageType;
|
||||
|
||||
export enum OutgoingMessageType {
|
||||
text = 'text',
|
@ -21,56 +21,56 @@ import {
|
||||
import { Payload } from '@/chat/schemas/types/quick-reply';
|
||||
|
||||
import BaseWebChannelHandler from './base-web-channel';
|
||||
import { Offline } from './types';
|
||||
import { Web } from './types';
|
||||
|
||||
type OfflineEventAdapter =
|
||||
type WebEventAdapter =
|
||||
| {
|
||||
eventType: StdEventType.unknown;
|
||||
messageType: never;
|
||||
raw: Offline.Event;
|
||||
raw: Web.Event;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.read;
|
||||
messageType: never;
|
||||
raw: Offline.StatusReadEvent;
|
||||
raw: Web.StatusReadEvent;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.delivery;
|
||||
messageType: never;
|
||||
raw: Offline.StatusDeliveryEvent;
|
||||
raw: Web.StatusDeliveryEvent;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.typing;
|
||||
messageType: never;
|
||||
raw: Offline.StatusTypingEvent;
|
||||
raw: Web.StatusTypingEvent;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.message;
|
||||
messageType: IncomingMessageType.message;
|
||||
raw: Offline.IncomingMessage<Offline.IncomingTextMessage>;
|
||||
raw: Web.IncomingMessage<Web.IncomingTextMessage>;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.message;
|
||||
messageType:
|
||||
| IncomingMessageType.postback
|
||||
| IncomingMessageType.quick_reply;
|
||||
raw: Offline.IncomingMessage<Offline.IncomingPayloadMessage>;
|
||||
raw: Web.IncomingMessage<Web.IncomingPayloadMessage>;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.message;
|
||||
messageType: IncomingMessageType.location;
|
||||
raw: Offline.IncomingMessage<Offline.IncomingLocationMessage>;
|
||||
raw: Web.IncomingMessage<Web.IncomingLocationMessage>;
|
||||
}
|
||||
| {
|
||||
eventType: StdEventType.message;
|
||||
messageType: IncomingMessageType.attachments;
|
||||
raw: Offline.IncomingMessage<Offline.IncomingAttachmentMessage>;
|
||||
raw: Web.IncomingMessage<Web.IncomingAttachmentMessage>;
|
||||
};
|
||||
|
||||
export default class OfflineEventWrapper<
|
||||
export default class WebEventWrapper<
|
||||
T extends
|
||||
BaseWebChannelHandler<ChannelName> = BaseWebChannelHandler<ChannelName>,
|
||||
> extends EventWrapper<OfflineEventAdapter, Offline.Event> {
|
||||
> extends EventWrapper<WebEventAdapter, Web.Event> {
|
||||
/**
|
||||
* Constructor : channel's event wrapper
|
||||
*
|
||||
@ -78,7 +78,7 @@ export default class OfflineEventWrapper<
|
||||
* @param event - The message event received
|
||||
* @param channelData - Channel's specific extra data {isSocket, ipAddress}
|
||||
*/
|
||||
constructor(handler: T, event: Offline.Event, channelData: any) {
|
||||
constructor(handler: T, event: Web.Event, channelData: any) {
|
||||
super(handler, event, channelData);
|
||||
}
|
||||
|
||||
@ -90,34 +90,34 @@ export default class OfflineEventWrapper<
|
||||
*
|
||||
* @param event - The message event received
|
||||
*/
|
||||
_init(event: Offline.Event) {
|
||||
_init(event: Web.Event) {
|
||||
switch (event.type) {
|
||||
case Offline.StatusEventType.delivery:
|
||||
case Web.StatusEventType.delivery:
|
||||
this._adapter.eventType = StdEventType.delivery;
|
||||
break;
|
||||
case Offline.StatusEventType.read:
|
||||
case Web.StatusEventType.read:
|
||||
this._adapter.eventType = StdEventType.read;
|
||||
break;
|
||||
case Offline.StatusEventType.typing:
|
||||
case Web.StatusEventType.typing:
|
||||
this._adapter.eventType = StdEventType.typing;
|
||||
break;
|
||||
case Offline.IncomingMessageType.text:
|
||||
case Web.IncomingMessageType.text:
|
||||
this._adapter.eventType = StdEventType.message;
|
||||
this._adapter.messageType = IncomingMessageType.message;
|
||||
break;
|
||||
case Offline.IncomingMessageType.quick_reply:
|
||||
case Web.IncomingMessageType.quick_reply:
|
||||
this._adapter.eventType = StdEventType.message;
|
||||
this._adapter.messageType = IncomingMessageType.quick_reply;
|
||||
break;
|
||||
case Offline.IncomingMessageType.postback:
|
||||
case Web.IncomingMessageType.postback:
|
||||
this._adapter.eventType = StdEventType.message;
|
||||
this._adapter.messageType = IncomingMessageType.postback;
|
||||
break;
|
||||
case Offline.IncomingMessageType.location:
|
||||
case Web.IncomingMessageType.location:
|
||||
this._adapter.eventType = StdEventType.message;
|
||||
this._adapter.messageType = IncomingMessageType.location;
|
||||
break;
|
||||
case Offline.IncomingMessageType.file:
|
||||
case Web.IncomingMessageType.file:
|
||||
this._adapter.eventType = StdEventType.message;
|
||||
this._adapter.messageType = IncomingMessageType.attachments;
|
||||
break;
|
4
api/src/index.d.ts
vendored
4
api/src/index.d.ts
vendored
@ -27,7 +27,7 @@ declare module 'express-session' {
|
||||
passport?: {
|
||||
user?: SessionUser;
|
||||
};
|
||||
offline?: {
|
||||
web?: {
|
||||
profile?: T;
|
||||
isSocket: boolean;
|
||||
messageQueue: any[];
|
||||
@ -40,7 +40,7 @@ declare module 'express-session' {
|
||||
passport?: {
|
||||
user?: SessionUser;
|
||||
};
|
||||
offline?: {
|
||||
web?: {
|
||||
profile?: SubscriberStub;
|
||||
isSocket: boolean;
|
||||
messageQueue: any[];
|
||||
|
4
api/src/utils/test/fixtures/conversation.ts
vendored
4
api/src/utils/test/fixtures/conversation.ts
vendored
@ -71,7 +71,7 @@ const conversations: ConversationCreateDto[] = [
|
||||
{
|
||||
sender: '1',
|
||||
context: {
|
||||
channel: 'offline-channel',
|
||||
channel: 'web-channel',
|
||||
text: 'Hello',
|
||||
payload: '',
|
||||
nlp: {
|
||||
@ -106,7 +106,7 @@ const conversations: ConversationCreateDto[] = [
|
||||
foreign_id: '',
|
||||
labels: [],
|
||||
assignedTo: null,
|
||||
channel: { name: 'offline-channel' },
|
||||
channel: { name: 'web-channel' },
|
||||
},
|
||||
skip: {},
|
||||
attempt: 0,
|
||||
|
6
api/src/utils/test/fixtures/label.ts
vendored
6
api/src/utils/test/fixtures/label.ts
vendored
@ -9,7 +9,7 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
import { LabelCreateDto } from '@/chat/dto/label.dto';
|
||||
import { LabelModel, Label } from '@/chat/schemas/label.schema';
|
||||
import { Label, LabelModel } from '@/chat/schemas/label.schema';
|
||||
|
||||
import { getFixturesWithDefaultValues } from '../defaultValues';
|
||||
import { TFixturesDefaultValues } from '../types';
|
||||
@ -19,7 +19,7 @@ export const labels: LabelCreateDto[] = [
|
||||
description: 'test description 1',
|
||||
label_id: {
|
||||
messenger: 'messenger',
|
||||
offline: 'offline',
|
||||
web: 'web',
|
||||
twitter: 'twitter',
|
||||
dimelo: 'dimelo',
|
||||
},
|
||||
@ -30,7 +30,7 @@ export const labels: LabelCreateDto[] = [
|
||||
description: 'test description 2',
|
||||
label_id: {
|
||||
messenger: 'messenger',
|
||||
offline: 'offline',
|
||||
web: 'web',
|
||||
twitter: 'twitter',
|
||||
dimelo: 'dimelo',
|
||||
},
|
||||
|
10
api/src/utils/test/fixtures/subscriber.ts
vendored
10
api/src/utils/test/fixtures/subscriber.ts
vendored
@ -35,7 +35,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
retainedFrom: new Date('2020-01-01T20:40:03.249Z'),
|
||||
},
|
||||
{
|
||||
foreign_id: 'foreign-id-offline-1',
|
||||
foreign_id: 'foreign-id-web-1',
|
||||
first_name: 'Maynard',
|
||||
last_name: 'James Keenan',
|
||||
language: 'en',
|
||||
@ -43,7 +43,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
gender: 'male',
|
||||
country: 'US',
|
||||
channel: {
|
||||
name: 'offline-channel',
|
||||
name: 'web-channel',
|
||||
},
|
||||
labels: [],
|
||||
assignedAt: null,
|
||||
@ -51,7 +51,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
retainedFrom: new Date('2021-01-02T20:40:03.249Z'),
|
||||
},
|
||||
{
|
||||
foreign_id: 'foreign-id-offline-2',
|
||||
foreign_id: 'foreign-id-web-2',
|
||||
first_name: 'Queen',
|
||||
last_name: 'Elisabeth',
|
||||
language: 'en',
|
||||
@ -59,7 +59,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
gender: 'male',
|
||||
country: 'US',
|
||||
channel: {
|
||||
name: 'offline-channel',
|
||||
name: 'web-channel',
|
||||
},
|
||||
labels: [],
|
||||
assignedAt: null,
|
||||
@ -75,7 +75,7 @@ const subscribers: SubscriberCreateDto[] = [
|
||||
gender: 'male',
|
||||
country: 'US',
|
||||
channel: {
|
||||
name: 'offline-channel',
|
||||
name: 'web-channel',
|
||||
},
|
||||
labels: [],
|
||||
assignedAt: null,
|
||||
|
@ -16,7 +16,7 @@ import { modelInstance } from './misc';
|
||||
import { subscriberInstance } from './subscriber';
|
||||
|
||||
export const contextBlankInstance: Context = {
|
||||
channel: 'offline-channel',
|
||||
channel: 'web-channel',
|
||||
text: '',
|
||||
payload: undefined,
|
||||
nlp: { entities: [] },
|
||||
@ -42,7 +42,7 @@ export const contextEmailVarInstance: Context = {
|
||||
};
|
||||
|
||||
export const contextGetStartedInstance: Context = {
|
||||
channel: 'offline-channel',
|
||||
channel: 'web-channel',
|
||||
text: 'Get Started',
|
||||
payload: 'GET_STARTED',
|
||||
nlp: { entities: [] },
|
||||
|
@ -25,7 +25,7 @@ export const subscriberInstance: Subscriber = {
|
||||
lastvisit: new Date(),
|
||||
retainedFrom: new Date(),
|
||||
channel: {
|
||||
name: 'offline-channel',
|
||||
name: 'web-channel',
|
||||
},
|
||||
labels: [],
|
||||
...modelInstance,
|
||||
|
@ -49,8 +49,8 @@ describe('WebsocketGateway', () => {
|
||||
ioClient = io('http://localhost:3000', {
|
||||
autoConnect: false,
|
||||
transports: ['websocket', 'polling'],
|
||||
// path: '/socket.io/?EIO=4&transport=websocket&channel=offline',
|
||||
query: { EIO: '4', transport: 'websocket', channel: 'offline' },
|
||||
// path: '/socket.io/?EIO=4&transport=websocket&channel=web',
|
||||
query: { EIO: '4', transport: 'websocket', channel: 'web' },
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
|
@ -223,7 +223,7 @@ export class WebsocketGateway
|
||||
'Unable to load session, creating a new one ...',
|
||||
err,
|
||||
);
|
||||
if (searchParams.get('channel') === 'offline') {
|
||||
if (searchParams.get('channel') === 'web') {
|
||||
return this.createAndStoreSession(client, next);
|
||||
} else {
|
||||
return next(new Error('Unauthorized: Unknown session ID'));
|
||||
@ -237,7 +237,7 @@ export class WebsocketGateway
|
||||
return next(new Error('Unable to parse session ID from cookie'));
|
||||
}
|
||||
}
|
||||
} else if (searchParams.get('channel') === 'offline') {
|
||||
} else if (searchParams.get('channel') === 'web') {
|
||||
return this.createAndStoreSession(client, next);
|
||||
} else {
|
||||
return next(new Error('Unauthorized to connect to WS'));
|
||||
|
@ -57,5 +57,5 @@ NEXT_PUBLIC_SSO_ENABLED=false
|
||||
# Widget
|
||||
APP_WIDGET_PORT=5173
|
||||
REACT_APP_WIDGET_API_URL=http://${APP_DOMAIN}:${API_PORT}
|
||||
REACT_APP_WIDGET_CHANNEL=offline
|
||||
REACT_APP_WIDGET_CHANNEL=web
|
||||
REACT_APP_WIDGET_TOKEN=token123
|
||||
|
@ -216,12 +216,6 @@
|
||||
"handled_by_me": "Assigned to me",
|
||||
"handled_by_chatbot": "Others",
|
||||
"settings": "Settings",
|
||||
"facebook_settings": "Facebook",
|
||||
"messenger": "Facebook Messenger",
|
||||
"msbot": "Microsoft Bot Connector",
|
||||
"offline": "Web Channel",
|
||||
"twitter": "Twitter",
|
||||
"dimelo": "Dimelo",
|
||||
"event_log": "Events Log",
|
||||
"log_entry": "Log entry",
|
||||
"dashboard": "Dashboard",
|
||||
@ -304,11 +298,6 @@
|
||||
"user_location_zipcode": "Zipcode",
|
||||
"user_location_streetName": "Street Name",
|
||||
"from_channels": "Target channels",
|
||||
"messenger": "Facebook/Messenger",
|
||||
"msbot": "Microsoft Bot Connector",
|
||||
"offline": "Canal Web",
|
||||
"twitter": "Twitter",
|
||||
"dimelo": "Dimelo",
|
||||
"simple_text": "Simple Text",
|
||||
"quick_replies": "Quick Replies",
|
||||
"buttons": "Buttons",
|
||||
|
@ -216,12 +216,6 @@
|
||||
"handled_by_me": "Assignés à moi",
|
||||
"handled_by_chatbot": "Autres",
|
||||
"settings": "Paramètres",
|
||||
"facebook_settings": "Paramètres Facebook",
|
||||
"messenger": "Facebook Messenger",
|
||||
"msbot": "Microsoft Bot Connector",
|
||||
"offline": "Canal Web",
|
||||
"twitter": "Twitter",
|
||||
"dimelo": "Dimelo",
|
||||
"event_log": "Journal des événements",
|
||||
"log_entry": "Journal des entrées",
|
||||
"dashboard": "Tableau de bord",
|
||||
@ -304,11 +298,6 @@
|
||||
"user_location_zipcode": "Code postal",
|
||||
"user_location_streetName": "Adresse",
|
||||
"from_channels": "Cibler les canaux",
|
||||
"messenger": "Facebook/Messenger",
|
||||
"msbot": "Microsoft Bot Connector",
|
||||
"offline": "Canal Web",
|
||||
"twitter": "Twitter",
|
||||
"dimelo": "Dimelo",
|
||||
"simple_text": "Texte simple",
|
||||
"quick_replies": "Réponses rapides",
|
||||
"buttons": "Boutons",
|
||||
|
@ -61,7 +61,7 @@ Once the widget is built, you can easily embed it into any webpage. Here's an ex
|
||||
ReactDOM.render(
|
||||
el(HexabotWidget, {
|
||||
apiUrl: 'https://api.yourdomain.com',
|
||||
channel: 'offline-channel',
|
||||
channel: 'web-channel',
|
||||
token: 'token123',
|
||||
}),
|
||||
domContainer,
|
||||
@ -96,7 +96,7 @@ To prevent the website css from conflicting with the chat widget css, we can lev
|
||||
ReactDOM.render(
|
||||
React.createElement(HexabotWidget, {
|
||||
apiUrl: 'https://api.yourdomain.com',
|
||||
channel: 'offline-channel',
|
||||
channel: 'web-channel',
|
||||
token: 'token123',
|
||||
}),
|
||||
shadowContainer,
|
||||
|
@ -34,7 +34,7 @@
|
||||
ReactDOM.render(
|
||||
React.createElement(HexabotWidget, {
|
||||
apiUrl: 'http://localhost:4000',
|
||||
channel: 'offline-channel',
|
||||
channel: 'web-channel',
|
||||
token: 'token123',
|
||||
}),
|
||||
shadowContainer,
|
||||
|
@ -18,7 +18,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<ChatWidget
|
||||
{...{
|
||||
apiUrl: process.env.REACT_APP_WIDGET_API_URL || 'http://localhost:4000',
|
||||
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'offline-channel',
|
||||
channel: process.env.REACT_APP_WIDGET_CHANNEL || 'web-channel',
|
||||
token: process.env.REACT_APP_WIDGET_TOKEN || 'token123',
|
||||
language: 'en',
|
||||
}}
|
||||
|
@ -82,7 +82,7 @@ export type TChannelData = {
|
||||
};
|
||||
|
||||
export type TRequestSession = {
|
||||
offline?: {
|
||||
web?: {
|
||||
profile: ISubscriber;
|
||||
isSocket: boolean;
|
||||
// @TODO : not sure why we added messageQuery (long pooling ?)
|
||||
|
Loading…
Reference in New Issue
Block a user