@microsoft/omnichannel-chat-widget 1.8.2-main.5195aba → 1.8.2-main.5a42a08
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.
- package/README.md +46 -1
- package/lib/cjs/common/Constants.js +6 -0
- package/lib/cjs/common/telemetry/TelemetryConstants.js +1 -0
- package/lib/cjs/common/utils.js +21 -2
- package/lib/cjs/components/chatbuttonstateful/ChatButtonStateful.js +4 -4
- package/lib/cjs/components/livechatwidget/common/createMarkdown.js +54 -1
- package/lib/cjs/components/livechatwidget/common/customEventHandler.js +53 -0
- package/lib/cjs/components/livechatwidget/common/initWebChatComposer.js +4 -1
- package/lib/cjs/components/livechatwidget/common/startChat.js +1 -1
- package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +5 -1
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.js +42 -0
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.js +41 -0
- package/lib/cjs/contexts/common/CustomEventType.js +1 -0
- package/lib/cjs/firstresponselatency/FirstMessageTrackerFromBot.js +101 -36
- package/lib/cjs/firstresponselatency/FirstResponseLatencyTracker.js +39 -21
- package/lib/cjs/firstresponselatency/util.js +12 -8
- package/lib/esm/common/Constants.js +6 -0
- package/lib/esm/common/telemetry/TelemetryConstants.js +1 -0
- package/lib/esm/common/utils.js +17 -0
- package/lib/esm/components/chatbuttonstateful/ChatButtonStateful.js +4 -4
- package/lib/esm/components/livechatwidget/common/createMarkdown.js +54 -1
- package/lib/esm/components/livechatwidget/common/customEventHandler.js +45 -0
- package/lib/esm/components/livechatwidget/common/initWebChatComposer.js +4 -1
- package/lib/esm/components/livechatwidget/common/startChat.js +1 -1
- package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +5 -1
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.js +36 -0
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.js +33 -0
- package/lib/esm/contexts/common/CustomEventType.js +1 -0
- package/lib/esm/firstresponselatency/FirstMessageTrackerFromBot.js +101 -36
- package/lib/esm/firstresponselatency/FirstResponseLatencyTracker.js +39 -21
- package/lib/esm/firstresponselatency/util.js +12 -8
- package/lib/types/common/Constants.d.ts +6 -0
- package/lib/types/common/telemetry/TelemetryConstants.d.ts +1 -0
- package/lib/types/common/utils.d.ts +3 -0
- package/lib/types/components/livechatwidget/common/customEventHandler.d.ts +4 -0
- package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/callActionMiddleware.d.ts +8 -0
- package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware.d.ts +22 -0
- package/lib/types/contexts/common/CustomEventType.d.ts +6 -0
- package/lib/types/firstresponselatency/FirstResponseLatencyTracker.d.ts +2 -2
- package/package.json +2 -2
|
@@ -17,14 +17,13 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
|
|
|
17
17
|
function FirstResponseLatencyTracker() {
|
|
18
18
|
_classCallCheck(this, FirstResponseLatencyTracker);
|
|
19
19
|
_defineProperty(this, "isABotConversation", false);
|
|
20
|
-
_defineProperty(this, "
|
|
21
|
-
_defineProperty(this, "isEnded", false);
|
|
20
|
+
_defineProperty(this, "isTracking", false);
|
|
22
21
|
_defineProperty(this, "startTrackingMessage", void 0);
|
|
23
22
|
_defineProperty(this, "stopTrackingMessage", void 0);
|
|
24
23
|
_defineProperty(this, "isReady", false);
|
|
24
|
+
_defineProperty(this, "trackingTimeoutId", void 0);
|
|
25
25
|
_defineProperty(this, "offlineNetworkListener", _omnichannelChatComponents.BroadcastService.getMessageByEventName(_TelemetryConstants.TelemetryEvent.NetworkDisconnected).subscribe(() => {
|
|
26
|
-
this.
|
|
27
|
-
this.isEnded = false;
|
|
26
|
+
this.isTracking = false;
|
|
28
27
|
_TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
|
|
29
28
|
Event: _TelemetryConstants.TelemetryEvent.MessageStopLapTrackError,
|
|
30
29
|
Description: "Tracker Stopped due to network disconnection"
|
|
@@ -72,7 +71,7 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
|
|
|
72
71
|
value: function startTracking(payload) {
|
|
73
72
|
if (!this.isReady) return;
|
|
74
73
|
// this prevents to initiate tracking for multiple incoming messages
|
|
75
|
-
if (this.
|
|
74
|
+
if (this.isTracking) {
|
|
76
75
|
return;
|
|
77
76
|
}
|
|
78
77
|
// this is to ensure we track only messages where bot is engaged
|
|
@@ -80,10 +79,24 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
|
|
|
80
79
|
return;
|
|
81
80
|
}
|
|
82
81
|
// control of states to prevent clashing of messages
|
|
83
|
-
this.
|
|
84
|
-
this.isEnded = false;
|
|
82
|
+
this.isTracking = true;
|
|
85
83
|
// The idea of using types is to enrich telemetry data
|
|
86
84
|
this.startTrackingMessage = this.createTrackingMessage(payload, "userMessage");
|
|
85
|
+
|
|
86
|
+
// Start a 5-second timeout to auto-stop tracking if not stopped
|
|
87
|
+
if (this.trackingTimeoutId) {
|
|
88
|
+
clearTimeout(this.trackingTimeoutId);
|
|
89
|
+
}
|
|
90
|
+
this.trackingTimeoutId = setTimeout(() => {
|
|
91
|
+
// this means the start process is in progress, but the end wasn't called within the time limit
|
|
92
|
+
if (this.isTracking) {
|
|
93
|
+
// Reset state variables and skip stopTracking
|
|
94
|
+
this.isTracking = false;
|
|
95
|
+
this.startTrackingMessage = undefined;
|
|
96
|
+
this.stopTrackingMessage = undefined;
|
|
97
|
+
this.trackingTimeoutId = undefined;
|
|
98
|
+
}
|
|
99
|
+
}, 5000);
|
|
87
100
|
}
|
|
88
101
|
}, {
|
|
89
102
|
key: "handleAgentMessage",
|
|
@@ -99,13 +112,16 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
|
|
|
99
112
|
value: function stopTracking(payload) {
|
|
100
113
|
var _this$stopTrackingMes, _this$startTrackingMe;
|
|
101
114
|
// this prevents execution for multiple incoming messages from the bot.
|
|
102
|
-
if (
|
|
115
|
+
if (!this.isTracking) {
|
|
103
116
|
return;
|
|
104
117
|
}
|
|
105
|
-
|
|
118
|
+
// Clear the timeout if it exists
|
|
119
|
+
if (this.trackingTimeoutId) {
|
|
120
|
+
clearTimeout(this.trackingTimeoutId);
|
|
121
|
+
this.trackingTimeoutId = undefined;
|
|
122
|
+
}
|
|
106
123
|
// control of states to prevent clashing of messages
|
|
107
|
-
this.
|
|
108
|
-
this.isStarted = false;
|
|
124
|
+
this.isTracking = false;
|
|
109
125
|
|
|
110
126
|
// The idea of using types is to enrich telemetry data
|
|
111
127
|
this.stopTrackingMessage = this.createTrackingMessage(payload, "botMessage");
|
|
@@ -160,12 +176,16 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
|
|
|
160
176
|
if (!payload || !payload.Id) {
|
|
161
177
|
throw new Error("Invalid payload");
|
|
162
178
|
}
|
|
163
|
-
|
|
164
|
-
if
|
|
179
|
+
|
|
180
|
+
// Only allow stopTracking if sender is valid and tracking is active
|
|
181
|
+
if (!this.isMessageFromValidSender(payload)) {
|
|
182
|
+
// Do not change isTracking or stopTrackingMessage
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (this.isABotConversation && this.isTracking) {
|
|
165
186
|
this.stopTracking(payload);
|
|
166
187
|
}
|
|
167
188
|
} catch (e) {
|
|
168
|
-
console.error("FRL : error while trying to stop the tracker", e);
|
|
169
189
|
_TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.ERROR, {
|
|
170
190
|
Event: _TelemetryConstants.TelemetryEvent.MessageStopLapTrackError,
|
|
171
191
|
Description: "Error while stopping the clock",
|
|
@@ -174,11 +194,6 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
|
|
|
174
194
|
payload: payload
|
|
175
195
|
}
|
|
176
196
|
});
|
|
177
|
-
//reset state
|
|
178
|
-
this.startTrackingMessage = undefined;
|
|
179
|
-
this.stopTrackingMessage = undefined;
|
|
180
|
-
this.isStarted = false;
|
|
181
|
-
this.isEnded = false;
|
|
182
197
|
}
|
|
183
198
|
}
|
|
184
199
|
}, {
|
|
@@ -186,10 +201,13 @@ let FirstResponseLatencyTracker = /*#__PURE__*/function () {
|
|
|
186
201
|
value: function deregister() {
|
|
187
202
|
// Reset State
|
|
188
203
|
this.isABotConversation = false;
|
|
189
|
-
this.
|
|
190
|
-
this.isEnded = false;
|
|
204
|
+
this.isTracking = false;
|
|
191
205
|
this.startTrackingMessage = undefined;
|
|
192
206
|
this.stopTrackingMessage = undefined;
|
|
207
|
+
if (this.trackingTimeoutId) {
|
|
208
|
+
clearTimeout(this.trackingTimeoutId);
|
|
209
|
+
this.trackingTimeoutId = undefined;
|
|
210
|
+
}
|
|
193
211
|
this.offlineNetworkListener.unsubscribe();
|
|
194
212
|
this.fmltrackingListener.unsubscribe();
|
|
195
213
|
this.rehydrateListener.unsubscribe();
|
|
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.polyfillMessagePayloadForEvent = exports.isHistoryMessage = exports.getScenarioType = exports.extractTimestampFromId = exports.createTrackingMessage = exports.buildMessagePayload = void 0;
|
|
7
7
|
var _Constants = require("./Constants");
|
|
8
8
|
var _Constants2 = require("../common/Constants");
|
|
9
|
+
const DELTA_WITHIN_LIMITS_IN_MS = 250;
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Determines whether a given activity is a historical message.
|
|
11
13
|
*
|
|
@@ -23,19 +25,21 @@ var _Constants2 = require("../common/Constants");
|
|
|
23
25
|
* - If the ID is valid and the timestamp is older than the start time, the message is historical.
|
|
24
26
|
*/
|
|
25
27
|
const isHistoryMessage = (activity, startTime) => {
|
|
26
|
-
var _activity$channelData
|
|
28
|
+
var _activity$channelData;
|
|
27
29
|
// Only process message activities
|
|
28
30
|
if ((activity === null || activity === void 0 ? void 0 : activity.type) !== _Constants2.Constants.message) {
|
|
29
31
|
return false;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
//
|
|
33
|
-
if (activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 &&
|
|
34
|
+
// Prioritize legacy history tag
|
|
35
|
+
if (activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && _activity$channelData.tags && activity.channelData.tags.includes(_Constants2.Constants.historyMessageTag)) {
|
|
34
36
|
return true;
|
|
35
37
|
}
|
|
36
38
|
const activityId = extractTimestampFromId(activity);
|
|
37
39
|
const isValidId = !isNaN(activityId) && activityId > 0;
|
|
38
|
-
const
|
|
40
|
+
const difference = startTime - activityId;
|
|
41
|
+
// Only consider historical if activityId < startTime and difference >= DELTA_WITHIN_LIMITS_IN_MS
|
|
42
|
+
const isOlderThanStartTime = activityId < startTime && difference >= DELTA_WITHIN_LIMITS_IN_MS;
|
|
39
43
|
const isHistoryById = isValidId && isOlderThanStartTime;
|
|
40
44
|
return isHistoryById;
|
|
41
45
|
};
|
|
@@ -64,7 +68,7 @@ const extractTimestampFromId = activity => {
|
|
|
64
68
|
};
|
|
65
69
|
exports.extractTimestampFromId = extractTimestampFromId;
|
|
66
70
|
const buildMessagePayload = (activity, userId) => {
|
|
67
|
-
var _text, _text2, _activity$
|
|
71
|
+
var _text, _text2, _activity$channelData2, _activity$from;
|
|
68
72
|
return {
|
|
69
73
|
// To identify hidden contents vs empty content
|
|
70
74
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -72,7 +76,7 @@ const buildMessagePayload = (activity, userId) => {
|
|
|
72
76
|
type: activity === null || activity === void 0 ? void 0 : activity.type,
|
|
73
77
|
timestamp: activity === null || activity === void 0 ? void 0 : activity.timestamp,
|
|
74
78
|
userId: userId,
|
|
75
|
-
tags: activity === null || activity === void 0 ? void 0 : (_activity$
|
|
79
|
+
tags: (activity === null || activity === void 0 ? void 0 : (_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : _activity$channelData2.tags) || [],
|
|
76
80
|
messageType: "",
|
|
77
81
|
Id: activity === null || activity === void 0 ? void 0 : activity.id,
|
|
78
82
|
role: activity === null || activity === void 0 ? void 0 : (_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role,
|
|
@@ -98,9 +102,9 @@ const polyfillMessagePayloadForEvent = (activity, payload, conversationId) => {
|
|
|
98
102
|
};
|
|
99
103
|
exports.polyfillMessagePayloadForEvent = polyfillMessagePayloadForEvent;
|
|
100
104
|
const getScenarioType = activity => {
|
|
101
|
-
var _activity$from3, _activity$
|
|
105
|
+
var _activity$from3, _activity$channelData3;
|
|
102
106
|
const role = activity === null || activity === void 0 ? void 0 : (_activity$from3 = activity.from) === null || _activity$from3 === void 0 ? void 0 : _activity$from3.role;
|
|
103
|
-
const tags = activity === null || activity === void 0 ? void 0 : (_activity$
|
|
107
|
+
const tags = activity === null || activity === void 0 ? void 0 : (_activity$channelData3 = activity.channelData) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.tags;
|
|
104
108
|
if (role === _Constants2.Constants.userMessageTag) {
|
|
105
109
|
return _Constants.ScenarioType.UserSendMessageStrategy;
|
|
106
110
|
}
|
|
@@ -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");
|
|
@@ -124,6 +125,11 @@ _defineProperty(Constants, "InitContextParamsResponse", "initContextParamsRespon
|
|
|
124
125
|
_defineProperty(Constants, "OCOriginalMessageId", "OriginalMessageId");
|
|
125
126
|
_defineProperty(Constants, "WebchatSequenceIdAttribute", "webchat:sequence-id");
|
|
126
127
|
_defineProperty(Constants, "MessageSequenceIdOverride", "MessageSequenceIdOverride");
|
|
128
|
+
_defineProperty(Constants, "sendCustomEvent", "sendCustomEvent");
|
|
129
|
+
_defineProperty(Constants, "onCustomEvent", "onCustomEvent");
|
|
130
|
+
_defineProperty(Constants, "customEventName", "customEventName");
|
|
131
|
+
_defineProperty(Constants, "customEventValue", "customEventValue");
|
|
132
|
+
_defineProperty(Constants, "Hidden", "Hidden");
|
|
127
133
|
_defineProperty(Constants, "EndConversationDueToOverflow", "endconversationduetooverflow");
|
|
128
134
|
export const Regex = (_class = /*#__PURE__*/_createClass(function Regex() {
|
|
129
135
|
_classCallCheck(this, Regex);
|
|
@@ -223,6 +223,7 @@ export let TelemetryEvent;
|
|
|
223
223
|
TelemetryEvent["SystemMessageReceived"] = "SystemMessageReceived";
|
|
224
224
|
TelemetryEvent["RehydrateMessageReceived"] = "RehydrateMessageReceived";
|
|
225
225
|
TelemetryEvent["CustomContextReceived"] = "CustomContextReceived";
|
|
226
|
+
TelemetryEvent["CustomEventAction"] = "CustomEventAction";
|
|
226
227
|
TelemetryEvent["NetworkDisconnected"] = "NetworkDisconnected";
|
|
227
228
|
TelemetryEvent["NetworkReconnected"] = "NetworkReconnected";
|
|
228
229
|
TelemetryEvent["LinkModePostChatWorkflowStarted"] = "LinkModePostChatWorkflowStarted";
|
package/lib/esm/common/utils.js
CHANGED
|
@@ -454,6 +454,23 @@ export function getDeviceType() {
|
|
|
454
454
|
return "standard";
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
|
+
|
|
458
|
+
//Bots expect a payload containing:
|
|
459
|
+
//1. customEventName: this should be string describe the event name
|
|
460
|
+
//2. customEventValue: given the value is from customer with unknown type, it is required to stringify the payload later
|
|
461
|
+
export const isValidCustomEvent = payload => {
|
|
462
|
+
if (Constants.customEventName in payload && payload.customEventName && typeof payload.customEventName === Constants.String && Constants.customEventValue in payload && payload.customEventValue) return true;
|
|
463
|
+
return false;
|
|
464
|
+
};
|
|
465
|
+
export const getCustomEventValue = customEventPayload => {
|
|
466
|
+
let returnVal = "";
|
|
467
|
+
try {
|
|
468
|
+
returnVal = typeof customEventPayload.customEventValue === Constants.String ? customEventPayload.customEventValue : JSON.stringify(customEventPayload.customEventValue);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
console.error(error);
|
|
471
|
+
}
|
|
472
|
+
return returnVal;
|
|
473
|
+
};
|
|
457
474
|
export function isEndConversationDueToOverflowActivity(activity) {
|
|
458
475
|
var _activity$channelData, _activity$channelData2;
|
|
459
476
|
return (activity === null || activity === void 0 ? void 0 : (_activity$channelData = activity.channelData) === null || _activity$channelData === void 0 ? void 0 : _activity$channelData.tags) && Array.isArray(activity === null || activity === void 0 ? void 0 : (_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : _activity$channelData2.tags) && activity.channelData.tags.includes(Constants.EndConversationDueToOverflow);
|
|
@@ -65,7 +65,6 @@ export const ChatButtonStateful = props => {
|
|
|
65
65
|
};
|
|
66
66
|
const outOfOfficeStyleProps = Object.assign({}, defaultOutOfOfficeChatButtonStyleProps, outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.styleProps);
|
|
67
67
|
const controlProps = {
|
|
68
|
-
...(buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.controlProps),
|
|
69
68
|
id: "oc-lcw-chat-button",
|
|
70
69
|
dir: state.domainStates.globalDir,
|
|
71
70
|
titleText: "Let's Chat!",
|
|
@@ -74,7 +73,8 @@ export const ChatButtonStateful = props => {
|
|
|
74
73
|
unreadMessageCount: state.appStates.unreadMessageCount ? state.appStates.unreadMessageCount > Constants.maximumUnreadMessageCount ? (_props$buttonProps = props.buttonProps) === null || _props$buttonProps === void 0 ? void 0 : (_props$buttonProps$co = _props$buttonProps.controlProps) === null || _props$buttonProps$co === void 0 ? void 0 : _props$buttonProps$co.largeUnreadMessageString : state.appStates.unreadMessageCount.toString() : "0",
|
|
75
74
|
unreadMessageString: (_props$buttonProps2 = props.buttonProps) === null || _props$buttonProps2 === void 0 ? void 0 : (_props$buttonProps2$c = _props$buttonProps2.controlProps) === null || _props$buttonProps2$c === void 0 ? void 0 : _props$buttonProps2$c.unreadMessageString,
|
|
76
75
|
// Regular chat button onClick - this will always take precedence
|
|
77
|
-
onClick: () => ref.current()
|
|
76
|
+
onClick: () => ref.current(),
|
|
77
|
+
...(buttonProps === null || buttonProps === void 0 ? void 0 : buttonProps.controlProps)
|
|
78
78
|
};
|
|
79
79
|
const outOfOfficeControlProps = {
|
|
80
80
|
// Only take specific properties from outOfOfficeButtonProps, never onClick
|
|
@@ -83,7 +83,6 @@ export const ChatButtonStateful = props => {
|
|
|
83
83
|
titleText: (outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : (_outOfOfficeButtonPro = outOfOfficeButtonProps.controlProps) === null || _outOfOfficeButtonPro === void 0 ? void 0 : _outOfOfficeButtonPro.titleText) || "We're Offline",
|
|
84
84
|
subtitleText: (outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : (_outOfOfficeButtonPro2 = outOfOfficeButtonProps.controlProps) === null || _outOfOfficeButtonPro2 === void 0 ? void 0 : _outOfOfficeButtonPro2.subtitleText) || "No agents available",
|
|
85
85
|
unreadMessageString: (_props$buttonProps3 = props.buttonProps) === null || _props$buttonProps3 === void 0 ? void 0 : (_props$buttonProps3$c = _props$buttonProps3.controlProps) === null || _props$buttonProps3$c === void 0 ? void 0 : _props$buttonProps3$c.unreadMessageString,
|
|
86
|
-
...(outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.controlProps),
|
|
87
86
|
// Out-of-office specific onClick - this will ALWAYS take precedence
|
|
88
87
|
onClick: () => {
|
|
89
88
|
if (state.appStates.isMinimized) {
|
|
@@ -96,7 +95,8 @@ export const ChatButtonStateful = props => {
|
|
|
96
95
|
type: LiveChatWidgetActionType.SET_CONVERSATION_STATE,
|
|
97
96
|
payload: ConversationState.OutOfOffice
|
|
98
97
|
});
|
|
99
|
-
}
|
|
98
|
+
},
|
|
99
|
+
...(outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.controlProps)
|
|
100
100
|
};
|
|
101
101
|
useEffect(() => {
|
|
102
102
|
TelemetryHelper.logLoadingEvent(LogLevel.INFO, {
|
|
@@ -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"
|
|
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);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Constants } from "../../../common/Constants";
|
|
2
|
+
import { TelemetryHelper } from "../../../common/telemetry/TelemetryHelper";
|
|
3
|
+
import { LogLevel, TelemetryEvent } from "../../../common/telemetry/TelemetryConstants";
|
|
4
|
+
import { getCustomEventValue, isValidCustomEvent } from "../../../common/utils";
|
|
5
|
+
export const customEventCallback = facadeChatSDK => event => {
|
|
6
|
+
if (!(Constants.payload in event)) return;
|
|
7
|
+
if (isValidCustomEvent(event.payload)) {
|
|
8
|
+
const customEventPayload = event.payload;
|
|
9
|
+
try {
|
|
10
|
+
const customEventValueStr = getCustomEventValue(customEventPayload);
|
|
11
|
+
const customEventName = customEventPayload.customEventName;
|
|
12
|
+
const messageMeta = {
|
|
13
|
+
customEvent: Constants.true,
|
|
14
|
+
customEventName: customEventName,
|
|
15
|
+
customEventValue: customEventValueStr
|
|
16
|
+
};
|
|
17
|
+
const messagePayload = {
|
|
18
|
+
content: "",
|
|
19
|
+
tags: [Constants.Hidden],
|
|
20
|
+
metadata: messageMeta,
|
|
21
|
+
timestamp: new Date()
|
|
22
|
+
};
|
|
23
|
+
facadeChatSDK.sendMessage(messagePayload);
|
|
24
|
+
TelemetryHelper.logActionEventToAllTelemetry(LogLevel.DEBUG, {
|
|
25
|
+
Event: TelemetryEvent.CustomEventAction,
|
|
26
|
+
Description: "Sent customEvent.",
|
|
27
|
+
CustomProperties: {
|
|
28
|
+
customEventName,
|
|
29
|
+
lengthCustomEventValue: customEventValueStr.length
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
} catch (error) {
|
|
33
|
+
TelemetryHelper.logActionEventToAllTelemetry(LogLevel.ERROR, {
|
|
34
|
+
Event: TelemetryEvent.CustomEventAction,
|
|
35
|
+
Description: "Failed to process CustomEvent.",
|
|
36
|
+
ExceptionDetails: {
|
|
37
|
+
error
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export const subscribeToSendCustomEvent = (broadcastService, facadeChatSDK, customEventCallback) => {
|
|
44
|
+
broadcastService.getMessageByEventName(Constants.sendCustomEvent).subscribe(customEventCallback(facadeChatSDK));
|
|
45
|
+
};
|
|
@@ -32,9 +32,12 @@ 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";
|
|
36
|
+
import createCustomEventMiddleware from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/customEventMiddleware";
|
|
35
37
|
import { ConversationState } from "../../../contexts/common/ConversationState";
|
|
36
38
|
import { executeReducer } from "../../../contexts/createReducer";
|
|
37
39
|
import { createQueueOverflowMiddleware } from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/queueOverflowHandlerMiddleware";
|
|
40
|
+
import { BroadcastService } from "@microsoft/omnichannel-chat-components";
|
|
38
41
|
|
|
39
42
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
43
|
export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) => {
|
|
@@ -113,7 +116,7 @@ export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endCh
|
|
|
113
116
|
};
|
|
114
117
|
webChatStore = createStore({},
|
|
115
118
|
//initial state
|
|
116
|
-
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), 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(),
|
|
117
120
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
121
|
...(((_props$webChatContain7 = props.webChatContainerProps) === null || _props$webChatContain7 === void 0 ? void 0 : _props$webChatContain7.storeMiddlewares) ?? []));
|
|
119
122
|
WebChatStoreLoader.store = webChatStore;
|
|
@@ -148,7 +148,6 @@ const setPreChatAndInitiateChat = async (facadeChatSDK, dispatch, setAdapter, is
|
|
|
148
148
|
const optionalParams = {
|
|
149
149
|
isProactiveChat
|
|
150
150
|
};
|
|
151
|
-
createTrackingForFirstMessage();
|
|
152
151
|
await initStartChat(facadeChatSDK, dispatch, setAdapter, state, props, optionalParams);
|
|
153
152
|
};
|
|
154
153
|
|
|
@@ -199,6 +198,7 @@ const initStartChat = async (facadeChatSDK, dispatch, setAdapter, state, props,
|
|
|
199
198
|
const startChatOptionalParams = Object.assign({}, params, optionalParams, defaultOptionalParams);
|
|
200
199
|
// startTime is used to determine if a message is history or new, better to be set before creating the adapter to get bandwidth
|
|
201
200
|
const startTime = new Date().getTime();
|
|
201
|
+
createTrackingForFirstMessage();
|
|
202
202
|
await facadeChatSDK.startChat(startChatOptionalParams);
|
|
203
203
|
isStartChatSuccessful = true;
|
|
204
204
|
await createAdapterAndSubscribe(facadeChatSDK, dispatch, setAdapter, startTime, props);
|
|
@@ -53,6 +53,7 @@ import { startProactiveChat } from "../common/startProactiveChat";
|
|
|
53
53
|
import useChatAdapterStore from "../../../hooks/useChatAdapterStore";
|
|
54
54
|
import useChatContextStore from "../../../hooks/useChatContextStore";
|
|
55
55
|
import useFacadeSDKStore from "../../../hooks/useFacadeChatSDKStore";
|
|
56
|
+
import { customEventCallback, subscribeToSendCustomEvent } from "../common/customEventHandler";
|
|
56
57
|
let uiTimer;
|
|
57
58
|
export const LiveChatWidgetStateful = props => {
|
|
58
59
|
var _props$webChatContain, _props$webChatContain2, _props$webChatContain3, _props$webChatContain4, _props$webChatContain5, _props$webChatContain6, _props$webChatContain7, _props$webChatContain8, _props$webChatContain9, _props$styleProps, _props$webChatContain10, _props$webChatContain11, _props$controlProps, _props$controlProps3, _state$appStates7, _props$webChatContain15, _state$appStates14, _props$webChatContain17, _props$webChatContain18, _props$controlProps12, _props$draggableChatW, _props$draggableChatW2, _props$draggableChatW3, _props$draggableChatW4, _props$draggableChatW5, _livechatProps$webCha, _livechatProps$styleP, _livechatProps$contro, _livechatProps$contro2, _livechatProps$compon, _livechatProps$contro3, _livechatProps$compon2, _livechatProps$contro4, _livechatProps$compon3, _livechatProps$contro5, _livechatProps$compon4, _livechatProps$contro6, _livechatProps$compon5, _livechatProps$contro7, _livechatProps$compon6, _livechatProps$contro8, _livechatProps$compon7, _livechatProps$contro9, _livechatProps$compon8, _livechatProps$contro10, _livechatProps$contro11, _livechatProps$compon9, _livechatProps$contro12, _livechatProps$compon10, _livechatProps$contro13, _livechatProps$compon11, _livechatProps$compon12, _livechatProps$compon13;
|
|
@@ -413,7 +414,7 @@ export const LiveChatWidgetStateful = props => {
|
|
|
413
414
|
BroadcastService.getMessageByEventName(BroadcastEvent.StartChat).subscribe(msg => {
|
|
414
415
|
var _msg$payload5, _msg$payload6, _msg$payload7, _msg$payload9, _inMemoryState$appSta2, _inMemoryState$appSta3, _inMemoryState$appSta4;
|
|
415
416
|
// If chat is out of operating hours chat widget sets the conversation state to OutOfOffice.
|
|
416
|
-
if (state.appStates.outsideOperatingHours
|
|
417
|
+
if (state.appStates.outsideOperatingHours && state.appStates.conversationState !== ConversationState.Active) {
|
|
417
418
|
dispatch({
|
|
418
419
|
type: LiveChatWidgetActionType.SET_MINIMIZED,
|
|
419
420
|
payload: false
|
|
@@ -580,6 +581,9 @@ export const LiveChatWidgetStateful = props => {
|
|
|
580
581
|
}
|
|
581
582
|
});
|
|
582
583
|
|
|
584
|
+
// subscribe custom event
|
|
585
|
+
subscribeToSendCustomEvent(BroadcastService, facadeChatSDK, customEventCallback);
|
|
586
|
+
|
|
583
587
|
// Check for TPC and log in telemetry if blocked
|
|
584
588
|
isCookieAllowed();
|
|
585
589
|
return () => {
|
|
@@ -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;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/******
|
|
2
|
+
* CustomEventMiddleware
|
|
3
|
+
*
|
|
4
|
+
* This middleware is invoked when a custom event is received.
|
|
5
|
+
* The callback is then invoked to handle the custom event.
|
|
6
|
+
******/
|
|
7
|
+
|
|
8
|
+
import { Constants } from "../../../../../common/Constants";
|
|
9
|
+
import { WebChatActionType } from "../../enums/WebChatActionType";
|
|
10
|
+
export const isValidCustomEvent = activity => {
|
|
11
|
+
var _activity$channelData, _activity$channelData2, _activity$channelData3, _activity$channelData4, _activity$channelData5, _activity$channelData6, _activity$channelData7, _activity$from, _activity$channelData8, _activity$channelData9, _activity$channelData10, _activity$channelData11;
|
|
12
|
+
return !!(activity !== null && activity !== void 0 && (_activity$channelData = activity.channelData) !== null && _activity$channelData !== void 0 && (_activity$channelData2 = _activity$channelData.metadata) !== null && _activity$channelData2 !== void 0 && _activity$channelData2.customEvent && typeof (activity === null || activity === void 0 ? void 0 : (_activity$channelData3 = activity.channelData) === null || _activity$channelData3 === void 0 ? void 0 : (_activity$channelData4 = _activity$channelData3.metadata) === null || _activity$channelData4 === void 0 ? void 0 : _activity$channelData4.customEvent) === Constants.String && (activity === null || activity === void 0 ? void 0 : (_activity$channelData5 = activity.channelData) === null || _activity$channelData5 === void 0 ? void 0 : (_activity$channelData6 = _activity$channelData5.metadata) === null || _activity$channelData6 === void 0 ? void 0 : (_activity$channelData7 = _activity$channelData6.customEvent) === null || _activity$channelData7 === void 0 ? void 0 : _activity$channelData7.toLowerCase()) === Constants.true && (activity === null || activity === void 0 ? void 0 : (_activity$from = activity.from) === null || _activity$from === void 0 ? void 0 : _activity$from.role) !== Constants.userMessageTag && typeof (activity === null || activity === void 0 ? void 0 : (_activity$channelData8 = activity.channelData) === null || _activity$channelData8 === void 0 ? void 0 : (_activity$channelData9 = _activity$channelData8.metadata) === null || _activity$channelData9 === void 0 ? void 0 : _activity$channelData9.customEventName) === Constants.String && activity !== null && activity !== void 0 && (_activity$channelData10 = activity.channelData) !== null && _activity$channelData10 !== void 0 && (_activity$channelData11 = _activity$channelData10.metadata) !== null && _activity$channelData11 !== void 0 && _activity$channelData11.customEventValue);
|
|
13
|
+
};
|
|
14
|
+
const createCustomEventMiddleware = broadcastservice => () => next => action => {
|
|
15
|
+
var _action$payload;
|
|
16
|
+
if ((action === null || action === void 0 ? void 0 : action.type) == WebChatActionType.DIRECT_LINE_INCOMING_ACTIVITY && (_action$payload = action.payload) !== null && _action$payload !== void 0 && _action$payload.activity) {
|
|
17
|
+
const activity = action.payload.activity;
|
|
18
|
+
if (isValidCustomEvent(activity)) {
|
|
19
|
+
const customEvent = {
|
|
20
|
+
eventName: Constants.onCustomEvent,
|
|
21
|
+
payload: {
|
|
22
|
+
messageId: activity.messageid ?? activity.id,
|
|
23
|
+
customEventName: activity.channelData.metadata.customEventName,
|
|
24
|
+
customEventValue: activity.channelData.metadata.customEventValue
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
broadcastservice.postMessage(customEvent);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return next(action);
|
|
32
|
+
};
|
|
33
|
+
export default createCustomEventMiddleware;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|