// eslint-disable-next-line @typescript-eslint/triple-slash-reference
///
import { ACSAdapterState, StateKey } from '../models/ACSAdapterState';
import { ChatThreadClient, SendMessageOptions, SendMessageRequest } from '@azure/communication-chat';
import { FileMetadata, IFileManager } from '../types/FileManagerTypes';
import { LogLevel, Logger } from '../log/Logger';
import { ACSDirectLineActivity } from '../models/ACSDirectLineActivity';
import { AcsMessageFormat } from '../models/AcsMessageFormat';
import { ActivityType } from '../types/DirectLineTypes';
import { BotAttachment } from '../models/BotAttachment';
import { EgressMiddleware } from '../libs/applyEgressMiddleware';
import EventManager from '../utils/EventManager';
import { LogEvent } from '../types/LogTypes';
import { setMessageIdToClientId } from '../utils/ClientIdToMessageId';
import packageInfo from '../../package.json';
import { ErrorEventSubscriber } from '../event/ErrorEventNotifier';
import { AdapterErrorEventType } from '../types/ErrorEventTypes';
const TelemetryOptions = {
requestOptions: {
customHeaders: {
'x-ms-useragent': `acs-webchat-adapter-${packageInfo.version} azsdk-js-communication-chat/${packageInfo.dependencies['@azure/communication-chat']}`
}
}
};
export default function createEgressMessageActivityMiddleware(): EgressMiddleware<
ACSDirectLineActivity,
ACSAdapterState
> {
return ({ getState }) =>
(next) =>
async (activity: ACSDirectLineActivity) => {
if (activity.type !== ActivityType.Message) {
return next(activity);
}
const chatThreadClient: ChatThreadClient = getState(StateKey.ChatThreadClient);
const userDisplayName: string = getState(StateKey.UserDisplayName);
const eventManager: EventManager = getState(StateKey.EventManager);
const fileManager: IFileManager = getState(StateKey.FileManager);
if (!chatThreadClient) {
const errorMessage = 'ACS Adapter: Failed to egress message without an active chatThreadClient.';
Logger.logEvent(LogLevel.ERROR, {
Event: LogEvent.ACS_ADAPTER_EGRESS_FAILED,
Description: errorMessage,
ACSRequesterUserId: getState(StateKey.UserId),
TimeStamp: activity.timestamp,
ChatThreadId: getState(StateKey.ThreadId),
ChatMessageId: activity.messageid
});
throw new Error(errorMessage);
}
Logger.logEvent(LogLevel.INFO, {
Event: LogEvent.ACS_ADAPTER_EGRESS_MESSAGE,
Description: 'Convert activity to egress ACS message',
ACSRequesterUserId: getState(StateKey.UserId),
TimeStamp: activity.timestamp,
ChatThreadId: getState(StateKey.ThreadId),
ChatMessageId: activity.messageid
});
const { text, value } = activity;
const uniqueClientMessageId = Date.now().toString();
(activity as any).clientmessageid = uniqueClientMessageId;
let content = text || '';
const complexContent: AcsMessageFormat = {};
// Add file ids if present
const fileIds: string[] = activity.channelData?.uploadedFiles?.map((file) => {
return file?.fileId;
});
const fileIdsProperty: Record = fileIds ? fileManager.createFileIdProperty(fileIds) : undefined;
// Add file metadata if present
const fileMetadata: FileMetadata[] = activity.channelData?.uploadedFiles?.map((file) => {
return file?.metadata;
});
const fileMetadataProperty: Record = fileMetadata
? fileManager.createFileMetadataProperty(fileMetadata)
: undefined;
// Add attachment if present
const attachment: BotAttachment = fileManager.createBotAttachment({
...fileIdsProperty,
...fileMetadataProperty
});
if (attachment && Object.keys(attachment).length !== 0) {
complexContent.attachments = [attachment];
}
// Add tags if present
const tags: string = activity.channelData?.tags;
const tagsProperty: Record = tags ? { tags: tags } : undefined;
let metadata = activity.channelData?.metadata;
// include client message id as a metadata if present
if (activity.channelData?.clientActivityID) {
metadata = { ...metadata, ...{ clientActivityId: activity.channelData.clientActivityID.toString() } };
}
// Add value if present
if (value) {
complexContent.value = value;
}
// Add attachmentLayout if present
if (activity.attachmentLayout) {
complexContent.attachmentLayout = activity.attachmentLayout;
}
if (JSON.stringify(complexContent) !== '{}') {
// Add text if present
if (text) {
complexContent.text = text;
}
content = JSON.stringify(complexContent);
metadata = {
...metadata,
...{ 'microsoft.azure.communication.chat.bot.contenttype': 'azurebotservice.adaptivecard' }
};
}
// Message Options
const sendMessageOptions: SendMessageOptions = {
...TelemetryOptions,
senderDisplayName: userDisplayName ? userDisplayName : undefined
};
// Metadata consists of tags, fileIds and any additional metadata passed in the activity
if (metadata || tags || fileIds) {
sendMessageOptions.metadata = {
...metadata,
...tagsProperty,
...fileIdsProperty,
...fileMetadataProperty
};
}
// Message Request
const sendMessageRequest: SendMessageRequest = {
content: content
};
try {
Logger.logEvent(LogLevel.INFO, {
Event: LogEvent.ACS_ADAPTER_EGRESS_SEND_MESSAGE,
Description: 'Convert activity to egress ACS message',
ACSRequesterUserId: getState(StateKey.UserId),
TimeStamp: activity.timestamp,
ChatThreadId: getState(StateKey.ThreadId),
ChatMessageId: activity.messageid
});
const sentResult = await chatThreadClient.sendMessage(sendMessageRequest, sendMessageOptions);
setMessageIdToClientId(sentResult.id, activity.channelData.clientActivityID as string);
Logger.logEvent(LogLevel.DEBUG, {
Event: LogEvent.ACS_ADAPTER_SEND_MESSAGE_SUCCESS,
Description: `Adapter: Successfully sent a message with messageid ${sentResult.id}.`,
CustomProperties: sendMessageRequest,
ACSRequesterUserId: getState(StateKey.UserId),
TimeStamp: new Date().toISOString(),
ChatThreadId: getState(StateKey.ThreadId),
ChatMessageId: activity.messageid,
ClientActivityId: activity?.channelData.clientActivityID as string
});
} catch (exception) {
Logger.logEvent(LogLevel.ERROR, {
Event: LogEvent.ACS_ADAPTER_SEND_MESSAGE_FAILED,
Description: `Send message failed.`,
CustomProperties: sendMessageRequest,
ACSRequesterUserId: getState(StateKey.UserId),
TimeStamp: new Date().toISOString(),
ChatThreadId: getState(StateKey.ThreadId),
ChatMessageId: activity.messageid,
ClientActivityId: activity?.channelData.clientActivityID as string,
ExceptionDetails: exception
});
ErrorEventSubscriber.notifyErrorEvent({
StatusCode: exception.response?.status,
ErrorType: AdapterErrorEventType.EGRESS_SEND_MESSAGE_FAILED,
ErrorMessage: exception.message,
ErrorStack: exception.stack,
ErrorDetails: (exception as any)?.details,
Timestamp: new Date().toISOString(),
AcsChatDetails: {
ThreadId: getState(StateKey.ThreadId),
RequesterUserId: getState(StateKey.UserId)
},
AdditionalParams: {
activity,
sendMessageRequest,
sendMessageOptions
}
});
eventManager.handleError(exception);
}
};
}