@microsoft/omnichannel-chat-widget 1.8.2-main.f638bed → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/lib/cjs/common/Constants.js +1 -0
  2. package/lib/cjs/components/livechatwidget/common/createMarkdown.js +54 -1
  3. package/lib/cjs/components/livechatwidget/common/endChat.js +1 -0
  4. package/lib/cjs/components/livechatwidget/common/initWebChatComposer.js +2 -1
  5. package/lib/cjs/components/livechatwidget/common/renderSurveyHelpers.js +23 -0
  6. package/lib/cjs/components/webchatcontainerstateful/common/DesignerChatAdapter.js +3 -1
  7. package/lib/cjs/components/webchatcontainerstateful/common/utils/chatAdapterUtils.js +27 -2
  8. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.js +42 -0
  9. package/lib/cjs/plugins/createChatTranscript.js +13 -0
  10. package/lib/esm/common/Constants.js +1 -0
  11. package/lib/esm/components/livechatwidget/common/createMarkdown.js +54 -1
  12. package/lib/esm/components/livechatwidget/common/endChat.js +1 -0
  13. package/lib/esm/components/livechatwidget/common/initWebChatComposer.js +2 -1
  14. package/lib/esm/components/livechatwidget/common/renderSurveyHelpers.js +23 -0
  15. package/lib/esm/components/webchatcontainerstateful/common/DesignerChatAdapter.js +4 -2
  16. package/lib/esm/components/webchatcontainerstateful/common/utils/chatAdapterUtils.js +23 -0
  17. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.js +36 -0
  18. package/lib/esm/plugins/createChatTranscript.js +13 -0
  19. package/lib/types/common/Constants.d.ts +1 -0
  20. package/lib/types/components/webchatcontainerstateful/common/utils/chatAdapterUtils.d.ts +2 -0
  21. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.d.ts +8 -0
  22. package/package.json +2 -2
@@ -32,6 +32,7 @@ _defineProperty(Constants, "webchatChannelId", "webchat");
32
32
  _defineProperty(Constants, "markdown", "markdown");
33
33
  _defineProperty(Constants, "actionType", "actionType");
34
34
  _defineProperty(Constants, "markDownSystemMessageClass", "webchat__basic-transcript__activity-markdown-body");
35
+ _defineProperty(Constants, "MARKDOWN_LIST_INDENTATION", " ");
35
36
  _defineProperty(Constants, "String", "string");
36
37
  _defineProperty(Constants, "ChatMessagesJson", "chatMessagesJson");
37
38
  _defineProperty(Constants, "truePascal", "True");
@@ -34,12 +34,65 @@ const createMarkdown = (disableMarkdownMessageFormatting, disableNewLineMarkdown
34
34
  // Rule to process html blocks and paragraphs
35
35
  "html_inline",
36
36
  // Rule to process html tags
37
- "newline" // Rule to proceess '\n'
37
+ "newline",
38
+ // Rule to proceess '\n'
39
+ "list" // Enable list parsing rule
38
40
  ]);
39
41
  }
40
42
 
41
43
  markdown.disable(["strikethrough"]);
42
44
 
45
+ // Custom plugin to fix numbered list continuity
46
+ markdown.use(function (md) {
47
+ const originalRender = md.render.bind(md);
48
+ const originalRenderInline = md.renderInline.bind(md);
49
+ function preprocessText(text) {
50
+ // Handle numbered lists that come with double line breaks (knowledge article format)
51
+ // This ensures proper continuous numbering instead of separate lists
52
+
53
+ let result = text;
54
+
55
+ // Only process if the text contains the double line break pattern
56
+ // But exclude simple numbered lists (where content after \n\n starts with another number)
57
+ if (!/\d+\.\s+.*?\n\n(?!\d+\.\s)[\s\S]*?(?:\n\n\d+\.|\s*$)/.test(text)) {
58
+ return result;
59
+ }
60
+
61
+ // Convert "1. Item\n\nContent\n\n2. Item" to proper markdown list format
62
+ // Use improved pattern with negative lookahead to exclude cases where content starts with numbered list
63
+ const listPattern = /(\d+\.\s+[^\n]+)(\n\n(?!\d+\.\s)[\s\S]*?)(?=\n\n\d+\.|\s*$)/g;
64
+ if (listPattern.test(result)) {
65
+ // Reset regex state for actual replacement
66
+ listPattern.lastIndex = 0;
67
+ result = result.replace(listPattern, (match, listItem, content) => {
68
+ if (!content) {
69
+ return match;
70
+ }
71
+
72
+ // Format content with proper indentation
73
+ const cleanContent = content.substring(2); // Remove leading \n\n
74
+ const lines = cleanContent.split("\n");
75
+ const indentedContent = lines.map(line => line.trim() ? `${_Constants.Constants.MARKDOWN_LIST_INDENTATION}${line}` : "").join("\n");
76
+ const lineBreak = disableNewLineMarkdownSupport ? "\n" : "\n\n";
77
+ return `${listItem}${lineBreak}${indentedContent}`;
78
+ });
79
+ }
80
+ return result;
81
+ }
82
+
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ md.render = function (text, env) {
85
+ const processedText = preprocessText(text);
86
+ return originalRender(processedText, env);
87
+ };
88
+
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ md.renderInline = function (text, env) {
91
+ const processedText = preprocessText(text);
92
+ return originalRenderInline(processedText, env);
93
+ };
94
+ });
95
+
43
96
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
97
  markdown.use(_markdownItForInline.default, "url_new_win", "link_open", function (tokens, idx, env) {
45
98
  const targetAttrIndex = tokens[idx].attrIndex(_Constants.Constants.Target);
@@ -40,6 +40,7 @@ const prepareEndChat = async (props, facadeChatSDK, state, dispatch, setAdapter,
40
40
  Description: _Constants.PrepareEndChatDescriptionConstants.ConversationEndedByCustomerWithoutPostChat
41
41
  });
42
42
  await endChat(props, facadeChatSDK, state, dispatch, setAdapter, setWebChatStyles, adapter, false, false, true);
43
+ return;
43
44
  }
44
45
 
45
46
  // Use Case: If ended by Agent, stay chat in InActive state
@@ -37,6 +37,7 @@ var _htmlPlayerMiddleware = _interopRequireDefault(require("../../webchatcontain
37
37
  var _htmlTextMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/htmlTextMiddleware"));
38
38
  var _preProcessingMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/preProcessingMiddleware"));
39
39
  var _sanitizationMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/sanitizationMiddleware"));
40
+ var _callActionMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware"));
40
41
  var _customEventMiddleware = _interopRequireDefault(require("../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware"));
41
42
  var _ConversationState = require("../../../contexts/common/ConversationState");
42
43
  var _createReducer = require("../../../contexts/createReducer");
@@ -120,7 +121,7 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
120
121
  };
121
122
  webChatStore = (0, _botframeworkWebchat.createStore)({},
122
123
  //initial state
123
- _preProcessingMiddleware.default, _attachmentProcessingMiddleware.default, (0, _attachmentUploadValidatorMiddleware.default)((_state$domainStates$l = state.domainStates.liveChatConfig) === null || _state$domainStates$l === void 0 ? void 0 : _state$domainStates$l.allowedFileExtensions, (_state$domainStates$l2 = state.domainStates.liveChatConfig) === null || _state$domainStates$l2 === void 0 ? void 0 : _state$domainStates$l2.maxUploadFileSize, localizedTexts), (0, _customEventMiddleware.default)(_omnichannelChatComponents.BroadcastService), (0, _queueOverflowHandlerMiddleware.createQueueOverflowMiddleware)(state, dispatch), (0, _channelDataMiddleware.default)(addConversationalSurveyTagsCallback), (0, _conversationEndMiddleware.default)(conversationEndCallback, startConversationalSurveyCallback, endConversationalSurveyCallback), (0, _dataMaskingMiddleware.default)((_state$domainStates$l3 = state.domainStates.liveChatConfig) === null || _state$domainStates$l3 === void 0 ? void 0 : _state$domainStates$l3.DataMaskingInfo), _messageTimestampMiddleware.createMessageTimeStampMiddleware, _messageSequenceIdOverrideMiddleware.createMessageSequenceIdOverrideMiddleware, _gifUploadMiddleware.default, _htmlPlayerMiddleware.default, (0, _htmlTextMiddleware.default)(honorsTargetInHTMLLinks), (0, _maxMessageSizeValidator.default)(localizedTexts), _sanitizationMiddleware.default,
124
+ _preProcessingMiddleware.default, _attachmentProcessingMiddleware.default, (0, _attachmentUploadValidatorMiddleware.default)((_state$domainStates$l = state.domainStates.liveChatConfig) === null || _state$domainStates$l === void 0 ? void 0 : _state$domainStates$l.allowedFileExtensions, (_state$domainStates$l2 = state.domainStates.liveChatConfig) === null || _state$domainStates$l2 === void 0 ? void 0 : _state$domainStates$l2.maxUploadFileSize, localizedTexts), (0, _customEventMiddleware.default)(_omnichannelChatComponents.BroadcastService), (0, _queueOverflowHandlerMiddleware.createQueueOverflowMiddleware)(state, dispatch), (0, _channelDataMiddleware.default)(addConversationalSurveyTagsCallback), (0, _conversationEndMiddleware.default)(conversationEndCallback, startConversationalSurveyCallback, endConversationalSurveyCallback), (0, _dataMaskingMiddleware.default)((_state$domainStates$l3 = state.domainStates.liveChatConfig) === null || _state$domainStates$l3 === void 0 ? void 0 : _state$domainStates$l3.DataMaskingInfo), _messageTimestampMiddleware.createMessageTimeStampMiddleware, _messageSequenceIdOverrideMiddleware.createMessageSequenceIdOverrideMiddleware, _gifUploadMiddleware.default, _htmlPlayerMiddleware.default, (0, _htmlTextMiddleware.default)(honorsTargetInHTMLLinks), (0, _maxMessageSizeValidator.default)(localizedTexts), _sanitizationMiddleware.default, (0, _callActionMiddleware.default)(),
124
125
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
126
  ...(((_props$webChatContain7 = props.webChatContainerProps) === null || _props$webChatContain7 === void 0 ? void 0 : _props$webChatContain7.storeMiddlewares) ?? []));
126
127
  _WebChatStoreLoader.WebChatStoreLoader.store = webChatStore;
@@ -12,6 +12,7 @@ var _PostChatSurveyMode = require("../../postchatsurveypanestateful/enums/PostCh
12
12
  var _TelemetryHelper = require("../../../common/telemetry/TelemetryHelper");
13
13
  var _utils = require("../../../common/utils");
14
14
  var _liveChatConfigUtils = require("./liveChatConfigUtils");
15
+ var _createReducer = require("../../../contexts/createReducer");
15
16
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
17
  let conversationDetails = undefined;
17
18
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -64,6 +65,13 @@ const renderSurvey = async (postChatContext, state, dispatch) => {
64
65
  // Function for embed mode postchat workflow which is essentially same for both customer and agent
65
66
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
67
  const embedModePostChatWorkflow = async (postChatContext, state, dispatch) => {
68
+ const inMemoryState = (0, _createReducer.executeReducer)(state, {
69
+ type: _LiveChatWidgetActionType.LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
70
+ payload: null
71
+ });
72
+ if (inMemoryState.appStates.conversationState === _ConversationState.ConversationState.Closed) {
73
+ return;
74
+ }
67
75
  _TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
68
76
  Event: _TelemetryConstants.TelemetryEvent.EmbedModePostChatWorkflowStarted
69
77
  });
@@ -84,6 +92,13 @@ const embedModePostChatWorkflow = async (postChatContext, state, dispatch) => {
84
92
  payload: _ConversationState.ConversationState.PostchatLoading
85
93
  });
86
94
  await (0, _utils.addDelayInMs)(_Constants.Constants.PostChatLoadingDurationInMs);
95
+ const inMemoryState = (0, _createReducer.executeReducer)(state, {
96
+ type: _LiveChatWidgetActionType.LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
97
+ payload: null
98
+ });
99
+ if (inMemoryState.appStates.conversationState === _ConversationState.ConversationState.Closed) {
100
+ return;
101
+ }
87
102
  dispatch({
88
103
  type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_CONVERSATION_STATE,
89
104
  payload: _ConversationState.ConversationState.Postchat
@@ -102,6 +117,14 @@ const embedModePostChatWorkflow = async (postChatContext, state, dispatch) => {
102
117
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
118
  const initiatePostChat = async (props, conversationDetailsParam, state, dispatch, postchatContext) => {
104
119
  var _conversationDetails;
120
+ const inMemoryState = (0, _createReducer.executeReducer)(state, {
121
+ type: _LiveChatWidgetActionType.LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
122
+ payload: null
123
+ });
124
+ if (inMemoryState.appStates.conversationState === _ConversationState.ConversationState.Closed) {
125
+ // If the conversation is closed, we need to reset the state
126
+ return;
127
+ }
105
128
  conversationDetails = conversationDetailsParam;
106
129
  const participantType = ((_conversationDetails = conversationDetails) === null || _conversationDetails === void 0 ? void 0 : _conversationDetails.participantType) ?? postchatContext.participantType;
107
130
  await setSurveyMode(props, participantType, state, dispatch);
@@ -29,10 +29,12 @@ let DesignerChatAdapter = /*#__PURE__*/function (_MockAdapter) {
29
29
  _this = _super.call(this);
30
30
  setTimeout(() => {
31
31
  (0, _chatAdapterUtils.postBotMessageActivity)(_this.activityObserver, "Thank you for contacting us! How can I help you today?", undefined, 0);
32
+ (0, _chatAdapterUtils.postBotMessageActivity)(_this.activityObserver, "Please accept terms and conditions to proceed. Visit the link for terms and conditions <a href=\"\">here</a>.", undefined, 0);
32
33
  _this.postUserActivity("I need to change my address.", 0);
33
34
  (0, _chatAdapterUtils.postBotMessageActivity)(_this.activityObserver, "Okay, let me connect you with a live agent.", undefined, 100);
34
35
  (0, _chatAdapterUtils.postSystemMessageActivity)(_this.activityObserver, "John has joined the chat", 100);
35
- (0, _chatAdapterUtils.postBotMessageActivity)(_this.activityObserver, "I'd be happy to help you update your account.", undefined, 100);
36
+ (0, _chatAdapterUtils.postAgentMessageActivity)(_this.activityObserver, "I'd be happy to help you update your account.", undefined, 100);
37
+ _this.postUserActivity("I have trouble visiting the signin page <a href=\"\">signin</a>.", 0);
36
38
  }, 1000);
37
39
  return _this;
38
40
  }
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.postSystemMessageActivity = exports.postEchoActivity = exports.postBotTypingActivity = exports.postBotMessageActivity = exports.postBotAttachmentActivity = exports.customerUser = exports.botUser = void 0;
6
+ exports.postSystemMessageActivity = exports.postEchoActivity = exports.postBotTypingActivity = exports.postBotMessageActivity = exports.postBotAttachmentActivity = exports.postAgentMessageActivity = exports.customerUser = exports.botUser = exports.agentUser = void 0;
7
7
  var _omnichannelChatSdk = require("@microsoft/omnichannel-chat-sdk");
8
8
  const customerUser = {
9
9
  id: "usedId",
@@ -16,9 +16,15 @@ const botUser = {
16
16
  name: "Bot",
17
17
  role: "bot"
18
18
  };
19
+ exports.botUser = botUser;
20
+ const agentUser = {
21
+ id: "AgentId",
22
+ name: "John",
23
+ role: "bot"
24
+ };
19
25
 
20
26
  // WebChat expects an "echo" activity to confirm the message has been sent successfully
21
- exports.botUser = botUser;
27
+ exports.agentUser = agentUser;
22
28
  const postEchoActivity = function (activityObserver, activity, user) {
23
29
  let delay = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1000;
24
30
  const echoActivity = {
@@ -54,6 +60,25 @@ const postBotMessageActivity = function (activityObserver, text) {
54
60
  }, delay);
55
61
  };
56
62
  exports.postBotMessageActivity = postBotMessageActivity;
63
+ const postAgentMessageActivity = function (activityObserver, text) {
64
+ let tags = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "";
65
+ let delay = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1000;
66
+ setTimeout(() => {
67
+ activityObserver === null || activityObserver === void 0 ? void 0 : activityObserver.next({
68
+ id: (0, _omnichannelChatSdk.uuidv4)(),
69
+ from: {
70
+ ...agentUser
71
+ },
72
+ text,
73
+ type: "message",
74
+ channelData: {
75
+ tags
76
+ },
77
+ timestamp: new Date().toISOString()
78
+ });
79
+ }, delay);
80
+ };
81
+ exports.postAgentMessageActivity = postAgentMessageActivity;
57
82
  const postSystemMessageActivity = function (activityObserver, text) {
58
83
  let delay = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1000;
59
84
  postBotMessageActivity(activityObserver, text, "system", delay);
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _WebChatActionType = require("../../enums/WebChatActionType");
8
+ /******
9
+ * CallActionMiddleware
10
+ *
11
+ * Intercepts custom call actions and handles tel: URL navigation
12
+ ******/
13
+
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
15
+ const createCallActionMiddleware = () => () => next => action => {
16
+ // Intercept incoming activities to modify suggested actions with call type
17
+ if (action.type === _WebChatActionType.WebChatActionType.DIRECT_LINE_INCOMING_ACTIVITY) {
18
+ var _action$payload, _activity$suggestedAc;
19
+ const activity = (_action$payload = action.payload) === null || _action$payload === void 0 ? void 0 : _action$payload.activity;
20
+
21
+ // Check if activity has suggested actions with call type
22
+ if (activity !== null && activity !== void 0 && (_activity$suggestedAc = activity.suggestedActions) !== null && _activity$suggestedAc !== void 0 && _activity$suggestedAc.actions) {
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ activity.suggestedActions.actions = activity.suggestedActions.actions.map(suggestedAction => {
25
+ if (suggestedAction.type === "call") {
26
+ // Convert call action to openUrl with encoded tel URL scheme
27
+ const telUrl = suggestedAction.value;
28
+ const convertedAction = {
29
+ ...suggestedAction,
30
+ type: "openUrl",
31
+ value: `tel:${telUrl}`
32
+ };
33
+ return convertedAction;
34
+ }
35
+ return suggestedAction;
36
+ });
37
+ }
38
+ }
39
+ return next(action);
40
+ };
41
+ var _default = createCallActionMiddleware;
42
+ exports.default = _default;
@@ -705,10 +705,23 @@ const createChatTranscript = async function (transcript, facadeChatSDK) {
705
705
  reader.readAsDataURL(blob);
706
706
  });
707
707
  };
708
+
709
+ // Configure DOMPurify to remove target attribute from br tags
710
+ const hook = function (node) {
711
+ // eslint-disable-line @typescript-eslint/no-explicit-any
712
+ // Remove target attribute from br tags as it causes them to display as literal text
713
+ if (node.tagName === "BR" && node.hasAttribute("target")) {
714
+ node.removeAttribute("target");
715
+ }
716
+ };
717
+ _dompurify.default.addHook("afterSanitizeAttributes", hook);
708
718
  let messages = transcriptMessages.filter(message => {
709
719
  message.content = _dompurify.default.sanitize(message.content);
710
720
  return message;
711
721
  });
722
+
723
+ // Clean up the hook after processing all messages
724
+ _dompurify.default.removeHook("afterSanitizeAttributes", hook);
712
725
  if (renderAttachments) {
713
726
  messages = await Promise.all(transcriptMessages.map(async message => {
714
727
  // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -25,6 +25,7 @@ _defineProperty(Constants, "webchatChannelId", "webchat");
25
25
  _defineProperty(Constants, "markdown", "markdown");
26
26
  _defineProperty(Constants, "actionType", "actionType");
27
27
  _defineProperty(Constants, "markDownSystemMessageClass", "webchat__basic-transcript__activity-markdown-body");
28
+ _defineProperty(Constants, "MARKDOWN_LIST_INDENTATION", " ");
28
29
  _defineProperty(Constants, "String", "string");
29
30
  _defineProperty(Constants, "ChatMessagesJson", "chatMessagesJson");
30
31
  _defineProperty(Constants, "truePascal", "True");
@@ -28,12 +28,65 @@ export const createMarkdown = (disableMarkdownMessageFormatting, disableNewLineM
28
28
  // Rule to process html blocks and paragraphs
29
29
  "html_inline",
30
30
  // Rule to process html tags
31
- "newline" // Rule to proceess '\n'
31
+ "newline",
32
+ // Rule to proceess '\n'
33
+ "list" // Enable list parsing rule
32
34
  ]);
33
35
  }
34
36
 
35
37
  markdown.disable(["strikethrough"]);
36
38
 
39
+ // Custom plugin to fix numbered list continuity
40
+ markdown.use(function (md) {
41
+ const originalRender = md.render.bind(md);
42
+ const originalRenderInline = md.renderInline.bind(md);
43
+ function preprocessText(text) {
44
+ // Handle numbered lists that come with double line breaks (knowledge article format)
45
+ // This ensures proper continuous numbering instead of separate lists
46
+
47
+ let result = text;
48
+
49
+ // Only process if the text contains the double line break pattern
50
+ // But exclude simple numbered lists (where content after \n\n starts with another number)
51
+ if (!/\d+\.\s+.*?\n\n(?!\d+\.\s)[\s\S]*?(?:\n\n\d+\.|\s*$)/.test(text)) {
52
+ return result;
53
+ }
54
+
55
+ // Convert "1. Item\n\nContent\n\n2. Item" to proper markdown list format
56
+ // Use improved pattern with negative lookahead to exclude cases where content starts with numbered list
57
+ const listPattern = /(\d+\.\s+[^\n]+)(\n\n(?!\d+\.\s)[\s\S]*?)(?=\n\n\d+\.|\s*$)/g;
58
+ if (listPattern.test(result)) {
59
+ // Reset regex state for actual replacement
60
+ listPattern.lastIndex = 0;
61
+ result = result.replace(listPattern, (match, listItem, content) => {
62
+ if (!content) {
63
+ return match;
64
+ }
65
+
66
+ // Format content with proper indentation
67
+ const cleanContent = content.substring(2); // Remove leading \n\n
68
+ const lines = cleanContent.split("\n");
69
+ const indentedContent = lines.map(line => line.trim() ? `${Constants.MARKDOWN_LIST_INDENTATION}${line}` : "").join("\n");
70
+ const lineBreak = disableNewLineMarkdownSupport ? "\n" : "\n\n";
71
+ return `${listItem}${lineBreak}${indentedContent}`;
72
+ });
73
+ }
74
+ return result;
75
+ }
76
+
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ md.render = function (text, env) {
79
+ const processedText = preprocessText(text);
80
+ return originalRender(processedText, env);
81
+ };
82
+
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ md.renderInline = function (text, env) {
85
+ const processedText = preprocessText(text);
86
+ return originalRenderInline(processedText, env);
87
+ };
88
+ });
89
+
37
90
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
91
  markdown.use(MarkdownItForInline, "url_new_win", "link_open", function (tokens, idx, env) {
39
92
  const targetAttrIndex = tokens[idx].attrIndex(Constants.Target);
@@ -35,6 +35,7 @@ const prepareEndChat = async (props, facadeChatSDK, state, dispatch, setAdapter,
35
35
  Description: PrepareEndChatDescriptionConstants.ConversationEndedByCustomerWithoutPostChat
36
36
  });
37
37
  await endChat(props, facadeChatSDK, state, dispatch, setAdapter, setWebChatStyles, adapter, false, false, true);
38
+ return;
38
39
  }
39
40
 
40
41
  // Use Case: If ended by Agent, stay chat in InActive state
@@ -32,6 +32,7 @@ import htmlTextMiddleware from "../../webchatcontainerstateful/webchatcontroller
32
32
  import preProcessingMiddleware from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/preProcessingMiddleware";
33
33
  import sanitizationMiddleware from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/sanitizationMiddleware";
34
34
  import { Constants } from "../../../common/Constants";
35
+ import createCallActionMiddleware from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware";
35
36
  import createCustomEventMiddleware from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware";
36
37
  import { ConversationState } from "../../../contexts/common/ConversationState";
37
38
  import { executeReducer } from "../../../contexts/createReducer";
@@ -115,7 +116,7 @@ export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endCh
115
116
  };
116
117
  webChatStore = createStore({},
117
118
  //initial state
118
- preProcessingMiddleware, attachmentProcessingMiddleware, createAttachmentUploadValidatorMiddleware((_state$domainStates$l = state.domainStates.liveChatConfig) === null || _state$domainStates$l === void 0 ? void 0 : _state$domainStates$l.allowedFileExtensions, (_state$domainStates$l2 = state.domainStates.liveChatConfig) === null || _state$domainStates$l2 === void 0 ? void 0 : _state$domainStates$l2.maxUploadFileSize, localizedTexts), createCustomEventMiddleware(BroadcastService), createQueueOverflowMiddleware(state, dispatch), channelDataMiddleware(addConversationalSurveyTagsCallback), createConversationEndMiddleware(conversationEndCallback, startConversationalSurveyCallback, endConversationalSurveyCallback), createDataMaskingMiddleware((_state$domainStates$l3 = state.domainStates.liveChatConfig) === null || _state$domainStates$l3 === void 0 ? void 0 : _state$domainStates$l3.DataMaskingInfo), createMessageTimeStampMiddleware, createMessageSequenceIdOverrideMiddleware, gifUploadMiddleware, htmlPlayerMiddleware, htmlTextMiddleware(honorsTargetInHTMLLinks), createMaxMessageSizeValidator(localizedTexts), sanitizationMiddleware,
119
+ preProcessingMiddleware, attachmentProcessingMiddleware, createAttachmentUploadValidatorMiddleware((_state$domainStates$l = state.domainStates.liveChatConfig) === null || _state$domainStates$l === void 0 ? void 0 : _state$domainStates$l.allowedFileExtensions, (_state$domainStates$l2 = state.domainStates.liveChatConfig) === null || _state$domainStates$l2 === void 0 ? void 0 : _state$domainStates$l2.maxUploadFileSize, localizedTexts), createCustomEventMiddleware(BroadcastService), createQueueOverflowMiddleware(state, dispatch), channelDataMiddleware(addConversationalSurveyTagsCallback), createConversationEndMiddleware(conversationEndCallback, startConversationalSurveyCallback, endConversationalSurveyCallback), createDataMaskingMiddleware((_state$domainStates$l3 = state.domainStates.liveChatConfig) === null || _state$domainStates$l3 === void 0 ? void 0 : _state$domainStates$l3.DataMaskingInfo), createMessageTimeStampMiddleware, createMessageSequenceIdOverrideMiddleware, gifUploadMiddleware, htmlPlayerMiddleware, htmlTextMiddleware(honorsTargetInHTMLLinks), createMaxMessageSizeValidator(localizedTexts), sanitizationMiddleware, createCallActionMiddleware(),
119
120
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
121
  ...(((_props$webChatContain7 = props.webChatContainerProps) === null || _props$webChatContain7 === void 0 ? void 0 : _props$webChatContain7.storeMiddlewares) ?? []));
121
122
  WebChatStoreLoader.store = webChatStore;
@@ -6,6 +6,7 @@ import { PostChatSurveyMode } from "../../postchatsurveypanestateful/enums/PostC
6
6
  import { TelemetryHelper } from "../../../common/telemetry/TelemetryHelper";
7
7
  import { addDelayInMs } from "../../../common/utils";
8
8
  import { getPostChatSurveyConfig } from "./liveChatConfigUtils";
9
+ import { executeReducer } from "../../../contexts/createReducer";
9
10
 
10
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
12
  let conversationDetails = undefined;
@@ -59,6 +60,13 @@ const renderSurvey = async (postChatContext, state, dispatch) => {
59
60
  // Function for embed mode postchat workflow which is essentially same for both customer and agent
60
61
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
62
  const embedModePostChatWorkflow = async (postChatContext, state, dispatch) => {
63
+ const inMemoryState = executeReducer(state, {
64
+ type: LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
65
+ payload: null
66
+ });
67
+ if (inMemoryState.appStates.conversationState === ConversationState.Closed) {
68
+ return;
69
+ }
62
70
  TelemetryHelper.logActionEvent(LogLevel.INFO, {
63
71
  Event: TelemetryEvent.EmbedModePostChatWorkflowStarted
64
72
  });
@@ -79,6 +87,13 @@ const embedModePostChatWorkflow = async (postChatContext, state, dispatch) => {
79
87
  payload: ConversationState.PostchatLoading
80
88
  });
81
89
  await addDelayInMs(Constants.PostChatLoadingDurationInMs);
90
+ const inMemoryState = executeReducer(state, {
91
+ type: LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
92
+ payload: null
93
+ });
94
+ if (inMemoryState.appStates.conversationState === ConversationState.Closed) {
95
+ return;
96
+ }
82
97
  dispatch({
83
98
  type: LiveChatWidgetActionType.SET_CONVERSATION_STATE,
84
99
  payload: ConversationState.Postchat
@@ -97,6 +112,14 @@ const embedModePostChatWorkflow = async (postChatContext, state, dispatch) => {
97
112
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
113
  const initiatePostChat = async (props, conversationDetailsParam, state, dispatch, postchatContext) => {
99
114
  var _conversationDetails;
115
+ const inMemoryState = executeReducer(state, {
116
+ type: LiveChatWidgetActionType.GET_IN_MEMORY_STATE,
117
+ payload: null
118
+ });
119
+ if (inMemoryState.appStates.conversationState === ConversationState.Closed) {
120
+ // If the conversation is closed, we need to reset the state
121
+ return;
122
+ }
100
123
  conversationDetails = conversationDetailsParam;
101
124
  const participantType = ((_conversationDetails = conversationDetails) === null || _conversationDetails === void 0 ? void 0 : _conversationDetails.participantType) ?? postchatContext.participantType;
102
125
  await setSurveyMode(props, participantType, state, dispatch);
@@ -12,7 +12,7 @@ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Re
12
12
  function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
13
13
  import { Observable } from "rxjs/Observable";
14
14
  import MockAdapter from "./mockadapter";
15
- import { customerUser, postBotMessageActivity, postEchoActivity, postSystemMessageActivity } from "./utils/chatAdapterUtils";
15
+ import { customerUser, postAgentMessageActivity, postBotMessageActivity, postEchoActivity, postSystemMessageActivity } from "./utils/chatAdapterUtils";
16
16
  export let DesignerChatAdapter = /*#__PURE__*/function (_MockAdapter) {
17
17
  _inherits(DesignerChatAdapter, _MockAdapter);
18
18
  var _super = _createSuper(DesignerChatAdapter);
@@ -22,10 +22,12 @@ export let DesignerChatAdapter = /*#__PURE__*/function (_MockAdapter) {
22
22
  _this = _super.call(this);
23
23
  setTimeout(() => {
24
24
  postBotMessageActivity(_this.activityObserver, "Thank you for contacting us! How can I help you today?", undefined, 0);
25
+ postBotMessageActivity(_this.activityObserver, "Please accept terms and conditions to proceed. Visit the link for terms and conditions <a href=\"\">here</a>.", undefined, 0);
25
26
  _this.postUserActivity("I need to change my address.", 0);
26
27
  postBotMessageActivity(_this.activityObserver, "Okay, let me connect you with a live agent.", undefined, 100);
27
28
  postSystemMessageActivity(_this.activityObserver, "John has joined the chat", 100);
28
- postBotMessageActivity(_this.activityObserver, "I'd be happy to help you update your account.", undefined, 100);
29
+ postAgentMessageActivity(_this.activityObserver, "I'd be happy to help you update your account.", undefined, 100);
30
+ _this.postUserActivity("I have trouble visiting the signin page <a href=\"\">signin</a>.", 0);
29
31
  }, 1000);
30
32
  return _this;
31
33
  }
@@ -9,6 +9,11 @@ export const botUser = {
9
9
  name: "Bot",
10
10
  role: "bot"
11
11
  };
12
+ export const agentUser = {
13
+ id: "AgentId",
14
+ name: "John",
15
+ role: "bot"
16
+ };
12
17
 
13
18
  // WebChat expects an "echo" activity to confirm the message has been sent successfully
14
19
  export const postEchoActivity = function (activityObserver, activity, user) {
@@ -44,6 +49,24 @@ export const postBotMessageActivity = function (activityObserver, text) {
44
49
  });
45
50
  }, delay);
46
51
  };
52
+ export const postAgentMessageActivity = function (activityObserver, text) {
53
+ let tags = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "";
54
+ let delay = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1000;
55
+ setTimeout(() => {
56
+ activityObserver === null || activityObserver === void 0 ? void 0 : activityObserver.next({
57
+ id: uuidv4(),
58
+ from: {
59
+ ...agentUser
60
+ },
61
+ text,
62
+ type: "message",
63
+ channelData: {
64
+ tags
65
+ },
66
+ timestamp: new Date().toISOString()
67
+ });
68
+ }, delay);
69
+ };
47
70
  export const postSystemMessageActivity = function (activityObserver, text) {
48
71
  let delay = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1000;
49
72
  postBotMessageActivity(activityObserver, text, "system", delay);
@@ -0,0 +1,36 @@
1
+ /******
2
+ * CallActionMiddleware
3
+ *
4
+ * Intercepts custom call actions and handles tel: URL navigation
5
+ ******/
6
+
7
+ import { WebChatActionType } from "../../enums/WebChatActionType";
8
+
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
10
+ const createCallActionMiddleware = () => () => next => action => {
11
+ // Intercept incoming activities to modify suggested actions with call type
12
+ if (action.type === WebChatActionType.DIRECT_LINE_INCOMING_ACTIVITY) {
13
+ var _action$payload, _activity$suggestedAc;
14
+ const activity = (_action$payload = action.payload) === null || _action$payload === void 0 ? void 0 : _action$payload.activity;
15
+
16
+ // Check if activity has suggested actions with call type
17
+ if (activity !== null && activity !== void 0 && (_activity$suggestedAc = activity.suggestedActions) !== null && _activity$suggestedAc !== void 0 && _activity$suggestedAc.actions) {
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ activity.suggestedActions.actions = activity.suggestedActions.actions.map(suggestedAction => {
20
+ if (suggestedAction.type === "call") {
21
+ // Convert call action to openUrl with encoded tel URL scheme
22
+ const telUrl = suggestedAction.value;
23
+ const convertedAction = {
24
+ ...suggestedAction,
25
+ type: "openUrl",
26
+ value: `tel:${telUrl}`
27
+ };
28
+ return convertedAction;
29
+ }
30
+ return suggestedAction;
31
+ });
32
+ }
33
+ }
34
+ return next(action);
35
+ };
36
+ export default createCallActionMiddleware;
@@ -700,10 +700,23 @@ const createChatTranscript = async function (transcript, facadeChatSDK) {
700
700
  reader.readAsDataURL(blob);
701
701
  });
702
702
  };
703
+
704
+ // Configure DOMPurify to remove target attribute from br tags
705
+ const hook = function (node) {
706
+ // eslint-disable-line @typescript-eslint/no-explicit-any
707
+ // Remove target attribute from br tags as it causes them to display as literal text
708
+ if (node.tagName === "BR" && node.hasAttribute("target")) {
709
+ node.removeAttribute("target");
710
+ }
711
+ };
712
+ DOMPurify.addHook("afterSanitizeAttributes", hook);
703
713
  let messages = transcriptMessages.filter(message => {
704
714
  message.content = DOMPurify.sanitize(message.content);
705
715
  return message;
706
716
  });
717
+
718
+ // Clean up the hook after processing all messages
719
+ DOMPurify.removeHook("afterSanitizeAttributes", hook);
707
720
  if (renderAttachments) {
708
721
  messages = await Promise.all(transcriptMessages.map(async message => {
709
722
  // eslint-disable-line @typescript-eslint/no-explicit-any
@@ -16,6 +16,7 @@ export declare class Constants {
16
16
  static readonly markdown = "markdown";
17
17
  static readonly actionType = "actionType";
18
18
  static readonly markDownSystemMessageClass = "webchat__basic-transcript__activity-markdown-body";
19
+ static readonly MARKDOWN_LIST_INDENTATION = " ";
19
20
  static readonly String = "string";
20
21
  static readonly ChatMessagesJson = "chatMessagesJson";
21
22
  static readonly truePascal = "True";
@@ -2,8 +2,10 @@ import { Activity, Attachment, Message, User } from "botframework-directlinejs";
2
2
  import { Subscriber } from "rxjs/Subscriber";
3
3
  export declare const customerUser: User;
4
4
  export declare const botUser: User;
5
+ export declare const agentUser: User;
5
6
  export declare const postEchoActivity: (activityObserver: Subscriber<Activity> | undefined, activity: Message, user: User, delay?: number) => void;
6
7
  export declare const postBotMessageActivity: (activityObserver: Subscriber<Activity> | undefined, text: string, tags?: string, delay?: number) => void;
8
+ export declare const postAgentMessageActivity: (activityObserver: Subscriber<Activity> | undefined, text: string, tags?: string, delay?: number) => void;
7
9
  export declare const postSystemMessageActivity: (activityObserver: Subscriber<Activity> | undefined, text: string, delay?: number) => void;
8
10
  export declare const postBotTypingActivity: (activityObserver: Subscriber<Activity> | undefined, delay?: number) => void;
9
11
  export declare const postBotAttachmentActivity: (activityObserver: Subscriber<Activity> | undefined, attachments?: Attachment[], delay?: number) => void;
@@ -0,0 +1,8 @@
1
+ /******
2
+ * CallActionMiddleware
3
+ *
4
+ * Intercepts custom call actions and handles tel: URL navigation
5
+ ******/
6
+ import { IWebChatAction } from "../../../interfaces/IWebChatAction";
7
+ declare const createCallActionMiddleware: () => () => (next: any) => (action: IWebChatAction) => any;
8
+ export default createCallActionMiddleware;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/omnichannel-chat-widget",
3
- "version": "1.8.2-main.f638bed",
3
+ "version": "1.8.2",
4
4
  "description": "Microsoft Omnichannel Chat Widget",
5
5
  "main": "lib/cjs/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -87,7 +87,7 @@
87
87
  "@azure/core-tracing": "^1.2.0",
88
88
  "@microsoft/applicationinsights-web": "^3.3.6",
89
89
  "@microsoft/omnichannel-chat-components": "1.1.13",
90
- "@microsoft/omnichannel-chat-sdk": "^1.11.4",
90
+ "@microsoft/omnichannel-chat-sdk": "^1.11.6",
91
91
  "@opentelemetry/api": "^1.9.0",
92
92
  "abort-controller": "^3",
93
93
  "abort-controller-es5": "^2.0.1",