Channel data egress middleware (#118)

* Add util to create channel data egress middleware

* Use channel data egress middleware

* Rename optional parameters to default

* Add tests for createChannelDataEgressMiddleware

* Extract tags to constants

* Add tests

* Refactor

* Refactor

* Add test

* Remove new line

* Update CHANGELOG.md
This commit is contained in:
xTEddie 2022-03-22 16:26:34 -07:00 коммит произвёл GitHub
Родитель 6219e16286
Коммит 474110f229
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 165 добавлений и 6 удалений

Просмотреть файл

@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file.
- Add `runtimeId` attribute in `OmnichannelChatSDK` & `ChatSDKRuntimeId` field in telemetry
- Add ability to automatically pass locale from chat config on calling `ChatSDK.emailLiveChatTranscript()`
- Bubble up `WidgetUseOutsideOperatingHour` exception
- Add `acs_webchat-chat-adapter` middleware to add default `channelData.tags` & `channelData.metadata`
### Fix
- Add `acs_webchat-chat-adapter` middlewares to format `channelData.tags`

Просмотреть файл

@ -0,0 +1,106 @@
import { channelIdTag, customerMessageTag } from "../../../src/core/messaging/MessageTags";
import { DeliveryMode } from "../../../src/core/messaging/OmnichannelMessage";
import createChannelDataEgressMiddleware from "../../../src/external/ACSAdapter/createChannelDataEgressMiddleware";
describe('createChannelDataEgressMiddleware', () => {
it('createChannelDataEgressMiddleware should call next if there\'s no channel data', () => {
const channelData = {
widgetId: 'widgetId'
};
const next = jest.fn();
const activity = {};
createChannelDataEgressMiddleware(channelData)()(next)(activity);
expect(next).toHaveBeenCalledWith(activity);
});
it('createChannelDataEgressMiddleware should add basic tags if not set', () => {
const channelData = {
widgetId: 'widgetId'
};
const next = jest.fn();
const activity = {
channelData: {} as any // eslint-disable-line @typescript-eslint/no-explicit-any
};
createChannelDataEgressMiddleware(channelData)()(next)(activity);
expect(activity.channelData).toBeDefined();
expect(activity.channelData.tags.includes(channelIdTag)).toBe(true);
expect(activity.channelData.tags.includes(customerMessageTag)).toBe(true);
expect(next).toHaveBeenCalledWith(activity);
});
it('createChannelDataEgressMiddleware should add \'channelIdTag\' if not set', () => {
const channelData = {
widgetId: 'widgetId'
};
const next = jest.fn();
const activity = {
channelData: {
tags: [customerMessageTag]
} as any // eslint-disable-line @typescript-eslint/no-explicit-any
};
createChannelDataEgressMiddleware(channelData)()(next)(activity);
expect(activity.channelData).toBeDefined();
expect(activity.channelData.tags.includes(channelIdTag)).toBe(true);
expect(next).toHaveBeenCalledWith(activity);
});
it('createChannelDataEgressMiddleware should add \'customerMessageTag\' if not set', () => {
const channelData = {
widgetId: 'widgetId'
};
const next = jest.fn();
const activity = {
channelData: {
tags: [channelIdTag]
} as any // eslint-disable-line @typescript-eslint/no-explicit-any
};
createChannelDataEgressMiddleware(channelData)()(next)(activity);
expect(activity.channelData).toBeDefined();
expect(activity.channelData.tags.includes(customerMessageTag)).toBe(true);
expect(next).toHaveBeenCalledWith(activity);
});
it('createChannelDataEgressMiddleware should have deliveryMode as metadata if not set', () => {
const channelData = {
widgetId: 'widgetId'
};
const next = jest.fn();
const activity = {
channelData: {} as any // eslint-disable-line @typescript-eslint/no-explicit-any
};
createChannelDataEgressMiddleware(channelData)()(next)(activity);
expect(activity.channelData).toBeDefined();
expect(activity.channelData.metadata).toBeDefined();
expect(activity.channelData.metadata.deliveryMode).toBeDefined();
expect(activity.channelData.metadata.deliveryMode).toBe(DeliveryMode.Bridged);
expect(next).toHaveBeenCalledWith(activity);
});
it('createChannelDataEgressMiddleware MUST have widgetId as metadata', () => {
const channelData = {
widgetId: 'widgetId'
};
const next = jest.fn();
const activity = {
channelData: {} as any // eslint-disable-line @typescript-eslint/no-explicit-any
};
createChannelDataEgressMiddleware(channelData)()(next)(activity);
expect(activity.channelData).toBeDefined();
expect(activity.channelData.metadata).toBeDefined();
expect(activity.channelData.metadata.widgetId).toBeDefined();
expect(activity.channelData.metadata.widgetId).toBe(channelData.widgetId);
expect(next).toHaveBeenCalledWith(activity);
});
});

Просмотреть файл

@ -21,6 +21,7 @@ import ChatSDKConfig from "./core/ChatSDKConfig";
import ChatSDKMessage from "./core/messaging/ChatSDKMessage";
import ChatTranscriptBody from "./core/ChatTranscriptBody";
import ConversationMode from "./core/ConversationMode";
import createChannelDataEgressMiddleware from "./external/ACSAdapter/createChannelDataEgressMiddleware";
import createFormatEgressTagsMiddleware from "./external/ACSAdapter/createFormatEgressTagsMiddleware";
import createFormatIngressTagsMiddleware from "./external/ACSAdapter/createFormatIngressTagsMiddleware";
import DeliveryMode from "@microsoft/omnichannel-ic3core/lib/model/DeliveryMode";
@ -1445,15 +1446,19 @@ class OmnichannelChatSDK {
if (protocol === ChatAdapterProtocols.ACS || this.liveChatVersion === LiveChatVersion.V2) {
return new Promise (async (resolve, reject) => { // eslint-disable-line no-async-promise-executor
const options = optionalParams.ACSAdapter? optionalParams.ACSAdapter.options: {};
const egressMiddlewares = [createFormatEgressTagsMiddleware()];
const ingressMiddlewares = [createFormatIngressTagsMiddleware()];
// Tags formatting middlewares are required to be the last in the pipeline to ensure tags are converted to the right format
const defaultEgressMiddlewares = [createChannelDataEgressMiddleware({widgetId: this.omnichannelConfig.widgetId}), createFormatEgressTagsMiddleware()];
const defaultIngressMiddlewares = [createFormatIngressTagsMiddleware()];
const egressMiddleware = options?.egressMiddleware? [...options.egressMiddleware, ...defaultEgressMiddlewares]: [...defaultEgressMiddlewares];
const ingressMiddleware = options?.ingressMiddleware? [...options.egressMiddleware, ...defaultIngressMiddlewares]: [...defaultIngressMiddlewares];
const featuresOption = {
enableAdaptiveCards: true, // Whether to enable adaptive card payload in adapter (payload in JSON string)
enableThreadMemberUpdateNotification: true, // Whether to enable chat thread member join/leave notification
enableLeaveThreadOnWindowClosed: false, // Whether to remove user on browser close event
egressMiddleware: egressMiddlewares,
ingressMiddleware: ingressMiddlewares,
...options // overrides
...options, // overrides
ingressMiddleware,
egressMiddleware
};
const acsAdapterCDNUrl = this.resolveChatAdapterUrl(protocol || ChatAdapterProtocols.ACS);

Просмотреть файл

@ -1,2 +1,4 @@
export const defaultMessageTags = ['ChannelId-lcw', 'FromCustomer'];
export const channelIdTag = 'ChannelId-lcw'; // Tag for PVA bot to return proper response for live chat channel
export const customerMessageTag = 'FromCustomer'; // Tag to support transcript analytics feature
export const defaultMessageTags = [channelIdTag, customerMessageTag];

45
src/external/ACSAdapter/createChannelDataEgressMiddleware.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,45 @@
import { channelIdTag, customerMessageTag } from "../../core/messaging/MessageTags";
import { DeliveryMode } from "../../core/messaging/OmnichannelMessage";
interface ChannelData {
widgetId: string;
}
const createChannelDataEgressMiddleware = (channelData: ChannelData): CallableFunction => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const channelDataMiddleware = () => (next: any) => (activity: any) => {
const applicable = activity && activity.channelData;
if (applicable) {
if (!activity.channelData.tags) {
activity.channelData.tags = [];
}
if (!activity.channelData.tags.includes(channelIdTag)) {
activity.channelData.tags.push(channelIdTag);
}
if (!activity.channelData.tags.includes(customerMessageTag)) {
activity.channelData.tags.push(customerMessageTag);
}
if (!activity.channelData.metadata) {
activity.channelData.metadata = {};
}
if (!activity.channelData.metadata.deliveryMode) {
activity.channelData.metadata.deliveryMode = DeliveryMode.Bridged;
}
if (channelData.widgetId) {
activity.channelData.metadata.widgetId = channelData.widgetId;
}
}
return next(activity);
}
return channelDataMiddleware;
}
export default createChannelDataEgressMiddleware;