@microsoft/omnichannel-chat-widget 2.0.0 → 2.0.1-main.6ca1911
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 +5 -2
- package/lib/cjs/common/telemetry/TelemetryConstants.js +6 -0
- package/lib/cjs/components/chatbuttonstateful/ChatButtonStateful.js +42 -25
- package/lib/cjs/components/livechatwidget/common/endChat.js +39 -0
- package/lib/cjs/components/livechatwidget/common/initWebChatComposer.js +11 -6
- package/lib/cjs/components/livechatwidget/common/recoveryEligibilityTelemetry.js +60 -0
- package/lib/cjs/components/livechatwidget/common/startChat.js +107 -33
- package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +8 -6
- package/lib/cjs/components/webchatcontainerstateful/WebChatContainerStateful.js +2 -2
- package/lib/cjs/components/webchatcontainerstateful/common/defaultProps/defaultMiddlewareLocalizedTexts.js +2 -1
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.js +16 -2
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.js +68 -40
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentUploadValidatorMiddleware.js +0 -27
- package/lib/esm/common/telemetry/TelemetryConstants.js +6 -0
- package/lib/esm/components/chatbuttonstateful/ChatButtonStateful.js +42 -25
- package/lib/esm/components/livechatwidget/common/endChat.js +39 -0
- package/lib/esm/components/livechatwidget/common/initWebChatComposer.js +12 -7
- package/lib/esm/components/livechatwidget/common/recoveryEligibilityTelemetry.js +52 -0
- package/lib/esm/components/livechatwidget/common/startChat.js +104 -31
- package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +8 -6
- package/lib/esm/components/webchatcontainerstateful/WebChatContainerStateful.js +2 -2
- package/lib/esm/components/webchatcontainerstateful/common/defaultProps/defaultMiddlewareLocalizedTexts.js +2 -1
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.js +16 -2
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.js +65 -39
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentUploadValidatorMiddleware.js +0 -27
- package/lib/types/common/telemetry/TelemetryConstants.d.ts +3 -0
- package/lib/types/components/livechatwidget/common/recoveryEligibilityTelemetry.d.ts +17 -0
- package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.d.ts +11 -4
- package/lib/types/contexts/common/ILiveChatWidgetLocalizedTexts.d.ts +9 -2
- package/package.json +8 -7
|
@@ -3,31 +3,41 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.default = void 0;
|
|
6
|
+
exports.getFileSentMessage = exports.default = exports.createAttachmentSentAnnouncementMiddleware = exports.ATTACHMENT_ANNOUNCEMENT_REGION_ID = void 0;
|
|
7
7
|
var _IWebChatAction = require("../../../interfaces/IWebChatAction");
|
|
8
8
|
var _WebChatActionType = require("../../enums/WebChatActionType");
|
|
9
9
|
/******
|
|
10
10
|
* AttachmentSentAnnouncementMiddleware
|
|
11
11
|
*
|
|
12
12
|
* When a file attachment is successfully sent, announces the result to
|
|
13
|
-
* screen readers
|
|
14
|
-
*
|
|
13
|
+
* screen readers so that assistive technology users receive immediate
|
|
14
|
+
* feedback ("File report.pdf sent").
|
|
15
|
+
*
|
|
16
|
+
* The announcement region lives inside the chat dialog. While the dialog is
|
|
17
|
+
* open it is aria-modal and its host siblings are aria-hidden, so screen
|
|
18
|
+
* readers (TalkBack, VoiceOver) ignore live regions outside it.
|
|
15
19
|
******/
|
|
16
20
|
|
|
17
|
-
const
|
|
21
|
+
const ATTACHMENT_ANNOUNCEMENT_REGION_ID = exports.ATTACHMENT_ANNOUNCEMENT_REGION_ID = "oc-lcw-attachment-announcement";
|
|
22
|
+
const DEFAULT_FILE_SENT_TEXT = "File {2} sent";
|
|
23
|
+
const DEFAULT_FILES_SENT_TEXT = "{0} files sent";
|
|
24
|
+
|
|
25
|
+
// Let the send-box focus shift settle before speaking, then keep the node
|
|
26
|
+
// long enough for the screen reader to read it.
|
|
27
|
+
const ANNOUNCE_DELAY_MS = 100;
|
|
28
|
+
const ANNOUNCEMENT_LIFETIME_MS = 5000;
|
|
18
29
|
|
|
19
30
|
/**
|
|
20
|
-
* Ensures a visually-hidden aria-live region exists
|
|
21
|
-
* announcing attachment upload results to screen readers.
|
|
31
|
+
* Ensures a visually-hidden aria-live region exists inside the chat dialog
|
|
32
|
+
* for announcing attachment upload results to screen readers.
|
|
22
33
|
*/
|
|
23
|
-
const ensureAriaLiveRegion =
|
|
24
|
-
|
|
34
|
+
const ensureAriaLiveRegion = widgetElementId => {
|
|
35
|
+
const host = document.getElementById(widgetElementId) || document.body;
|
|
36
|
+
let region = document.getElementById(ATTACHMENT_ANNOUNCEMENT_REGION_ID);
|
|
25
37
|
if (!region) {
|
|
26
38
|
region = document.createElement("div");
|
|
27
|
-
region.id =
|
|
39
|
+
region.id = ATTACHMENT_ANNOUNCEMENT_REGION_ID;
|
|
28
40
|
region.setAttribute("aria-live", "polite");
|
|
29
|
-
region.setAttribute("role", "status");
|
|
30
|
-
region.setAttribute("aria-atomic", "true");
|
|
31
41
|
// Visually hidden but available to screen readers
|
|
32
42
|
Object.assign(region.style, {
|
|
33
43
|
position: "absolute",
|
|
@@ -40,44 +50,62 @@ const ensureAriaLiveRegion = () => {
|
|
|
40
50
|
whiteSpace: "nowrap",
|
|
41
51
|
border: "0"
|
|
42
52
|
});
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
}
|
|
54
|
+
if (region.parentElement !== host) {
|
|
55
|
+
host.appendChild(region);
|
|
45
56
|
}
|
|
46
57
|
return region;
|
|
47
58
|
};
|
|
48
59
|
|
|
49
60
|
/**
|
|
50
|
-
* Announces a message
|
|
51
|
-
*
|
|
61
|
+
* Announces a message by appending a fresh node to the live region.
|
|
62
|
+
* TalkBack reliably speaks newly added nodes but often misses text changes
|
|
63
|
+
* on an existing node, so each announcement gets its own element.
|
|
52
64
|
*/
|
|
53
|
-
const announce = message => {
|
|
54
|
-
const region = ensureAriaLiveRegion();
|
|
55
|
-
region.textContent = "";
|
|
56
|
-
// Small delay so the screen reader detects the content change
|
|
65
|
+
const announce = (message, widgetElementId) => {
|
|
66
|
+
const region = ensureAriaLiveRegion(widgetElementId);
|
|
57
67
|
setTimeout(() => {
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
const node = document.createElement("div");
|
|
69
|
+
node.textContent = message;
|
|
70
|
+
region.appendChild(node);
|
|
71
|
+
setTimeout(() => node.remove(), ANNOUNCEMENT_LIFETIME_MS);
|
|
72
|
+
}, ANNOUNCE_DELAY_MS);
|
|
60
73
|
};
|
|
61
74
|
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
|
+
const getFileSentMessage = (attachments, localizedTexts) => {
|
|
77
|
+
var _attachments$;
|
|
78
|
+
if (attachments.length > 1) {
|
|
79
|
+
// Replacer callbacks insert values literally; a string would treat "$&" or "$$" as patterns.
|
|
80
|
+
return ((localizedTexts === null || localizedTexts === void 0 ? void 0 : localizedTexts.MIDDLEWARE_BANNER_FILES_SENT) || DEFAULT_FILES_SENT_TEXT).replace("{0}", () => String(attachments.length));
|
|
81
|
+
}
|
|
82
|
+
const name = typeof ((_attachments$ = attachments[0]) === null || _attachments$ === void 0 ? void 0 : _attachments$.name) === "string" ? attachments[0].name.trim() : "";
|
|
83
|
+
return ((localizedTexts === null || localizedTexts === void 0 ? void 0 : localizedTexts.MIDDLEWARE_BANNER_FILE_SENT) || DEFAULT_FILE_SENT_TEXT).replace("{2}", () => name).replace(/\s{2,}/g, " ").trim();
|
|
84
|
+
};
|
|
85
|
+
exports.getFileSentMessage = getFileSentMessage;
|
|
86
|
+
const createAttachmentSentAnnouncementMiddleware = function (localizedTexts) {
|
|
87
|
+
let widgetElementId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "oc-lcw";
|
|
88
|
+
return (
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
90
|
+
_ref => {
|
|
91
|
+
let dispatch = _ref.dispatch;
|
|
92
|
+
return next => action => {
|
|
93
|
+
if (!(0, _IWebChatAction.isWebChatAction)(action)) {
|
|
94
|
+
return next(action);
|
|
95
|
+
}
|
|
69
96
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
// Intercept successfully posted activities that contain attachments
|
|
98
|
+
if (action.type === _WebChatActionType.WebChatActionType.DIRECT_LINE_POST_ACTIVITY_FULFILLED) {
|
|
99
|
+
var _action$payload;
|
|
100
|
+
const attachments = (_action$payload = action.payload) === null || _action$payload === void 0 || (_action$payload = _action$payload.activity) === null || _action$payload === void 0 ? void 0 : _action$payload.attachments;
|
|
101
|
+
if (attachments && attachments.length > 0) {
|
|
102
|
+
announce(getFileSentMessage(attachments, localizedTexts), widgetElementId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return next(action);
|
|
106
|
+
};
|
|
79
107
|
}
|
|
80
|
-
|
|
81
|
-
};
|
|
108
|
+
);
|
|
82
109
|
};
|
|
83
|
-
|
|
110
|
+
exports.createAttachmentSentAnnouncementMiddleware = createAttachmentSentAnnouncementMiddleware;
|
|
111
|
+
var _default = exports.default = createAttachmentSentAnnouncementMiddleware;
|
|
@@ -18,27 +18,6 @@ var _WebChatActionType = require("../../enums/WebChatActionType");
|
|
|
18
18
|
******/
|
|
19
19
|
|
|
20
20
|
const MBtoBRatio = 1000000;
|
|
21
|
-
const announceFileSent = message => {
|
|
22
|
-
// TalkBack on Android WebView reliably announces newly *appended*
|
|
23
|
-
// role="alert" nodes but often misses text-content updates on existing
|
|
24
|
-
// nodes. Wait 500ms for the send-box focus shift to settle, then inject
|
|
25
|
-
// a fresh alert element appended to document.body and remove it after 3s.
|
|
26
|
-
setTimeout(() => {
|
|
27
|
-
const el = document.createElement("div");
|
|
28
|
-
el.setAttribute("role", "alert");
|
|
29
|
-
el.setAttribute("aria-live", "assertive");
|
|
30
|
-
el.setAttribute("aria-atomic", "true");
|
|
31
|
-
el.style.cssText = "position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;";
|
|
32
|
-
el.textContent = message;
|
|
33
|
-
document.body.appendChild(el);
|
|
34
|
-
// Remove after TalkBack has had time to read it
|
|
35
|
-
setTimeout(() => {
|
|
36
|
-
if (el.parentNode) {
|
|
37
|
-
el.parentNode.removeChild(el);
|
|
38
|
-
}
|
|
39
|
-
}, 3000);
|
|
40
|
-
}, 500);
|
|
41
|
-
};
|
|
42
21
|
|
|
43
22
|
/*
|
|
44
23
|
* If an attachment is invalid, delete this attachment from the attachments list
|
|
@@ -227,13 +206,7 @@ const createAttachmentUploadValidatorMiddleware = (allowedFileExtensions, maxFil
|
|
|
227
206
|
var _payload$activity, _payload$activity2;
|
|
228
207
|
const payload = action.payload;
|
|
229
208
|
if (payload !== null && payload !== void 0 && payload.activity.attachments && payload.activity.attachments.length > 0 && (payload === null || payload === void 0 || (_payload$activity = payload.activity) === null || _payload$activity === void 0 || (_payload$activity = _payload$activity.attachments) === null || _payload$activity === void 0 ? void 0 : _payload$activity.length) === (payload === null || payload === void 0 || (_payload$activity2 = payload.activity) === null || _payload$activity2 === void 0 || (_payload$activity2 = _payload$activity2.channelData) === null || _payload$activity2 === void 0 || (_payload$activity2 = _payload$activity2.attachmentSizes) === null || _payload$activity2 === void 0 ? void 0 : _payload$activity2.length)) {
|
|
230
|
-
var _validatedAction$payl;
|
|
231
209
|
const validatedAction = validateAttachment(action, allowedFileExtensions, maxFileSizeSupportedByDynamics, localizedTexts);
|
|
232
|
-
|
|
233
|
-
// Announce to screen readers that the file was sent when validation passes
|
|
234
|
-
if (((_validatedAction$payl = validatedAction.payload) === null || _validatedAction$payl === void 0 || (_validatedAction$payl = _validatedAction$payl.activity) === null || _validatedAction$payl === void 0 || (_validatedAction$payl = _validatedAction$payl.attachments) === null || _validatedAction$payl === void 0 ? void 0 : _validatedAction$payl.length) > 0) {
|
|
235
|
-
announceFileSent(localizedTexts.MIDDLEWARE_BANNER_FILE_SENT ?? "File sent successfully.");
|
|
236
|
-
}
|
|
237
210
|
return next(validatedAction);
|
|
238
211
|
}
|
|
239
212
|
}
|
|
@@ -109,6 +109,7 @@ export let TelemetryEvent = /*#__PURE__*/function (TelemetryEvent) {
|
|
|
109
109
|
TelemetryEvent["MidConversationAuthSucceeded"] = "MidConversationAuthSucceeded";
|
|
110
110
|
TelemetryEvent["MidConversationAuthFailed"] = "MidConversationAuthFailed";
|
|
111
111
|
TelemetryEvent["MidConversationAuthReset"] = "MidConversationAuthReset";
|
|
112
|
+
TelemetryEvent["RecoveryEligibilityEvaluated"] = "RecoveryEligibilityEvaluated";
|
|
112
113
|
TelemetryEvent["GetConversationDetailsCallStarted"] = "GetConversationDetailsCallStarted";
|
|
113
114
|
TelemetryEvent["GetConversationDetailsCallFailed"] = "GetConversationDetailsCallFailed";
|
|
114
115
|
TelemetryEvent["EndChatSDKCallFailed"] = "EndChatSDKCallFailed";
|
|
@@ -140,6 +141,8 @@ export let TelemetryEvent = /*#__PURE__*/function (TelemetryEvent) {
|
|
|
140
141
|
TelemetryEvent["PrechatSubmitted"] = "PrechatSubmitted";
|
|
141
142
|
TelemetryEvent["StartChatSDKCall"] = "StartChatCall";
|
|
142
143
|
TelemetryEvent["StartChatEventReceived"] = "StartChatEventReceived";
|
|
144
|
+
TelemetryEvent["ConcurrentStartChatRejected"] = "ConcurrentStartChatRejected";
|
|
145
|
+
TelemetryEvent["ChatSDKStateCleanUpDisconnectFailed"] = "ChatSDKStateCleanUpDisconnectFailed";
|
|
143
146
|
TelemetryEvent["EndChatSDKCall"] = "EndChatSDKCall";
|
|
144
147
|
TelemetryEvent["PrepareEndChat"] = "PrepareEndChat";
|
|
145
148
|
TelemetryEvent["EndChatEventReceived"] = "EndChatEventReceived";
|
|
@@ -412,6 +415,9 @@ export let TelemetryConstants = /*#__PURE__*/function () {
|
|
|
412
415
|
case TelemetryEvent.SecureEventBusDispatchError:
|
|
413
416
|
case TelemetryEvent.AgentJoinedConversation:
|
|
414
417
|
case TelemetryEvent.BrowserTabHidden:
|
|
418
|
+
case TelemetryEvent.ConcurrentStartChatRejected:
|
|
419
|
+
case TelemetryEvent.ChatSDKStateCleanUpDisconnectFailed:
|
|
420
|
+
case TelemetryEvent.RecoveryEligibilityEvaluated:
|
|
415
421
|
return ScenarioType.ACTIONS;
|
|
416
422
|
case TelemetryEvent.StartChatSDKCall:
|
|
417
423
|
case TelemetryEvent.StartChatEventReceived:
|
|
@@ -50,37 +50,54 @@ export const ChatButtonStateful = props => {
|
|
|
50
50
|
const ref = useRef(() => {
|
|
51
51
|
return;
|
|
52
52
|
});
|
|
53
|
+
// Optimistic guard against rapid double-clicks. `conversationState` only flips to
|
|
54
|
+
// `Loading` after React re-renders, so the button stays visible and clickable for the
|
|
55
|
+
// whole duration of the first click's async work. Without this ref two concurrent
|
|
56
|
+
// startChat() calls can create two conversations on the backend.
|
|
57
|
+
const startChatInProgressRef = useRef(false);
|
|
53
58
|
ref.current = async () => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
ConversationStage: ConversationStage.Initialization
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
if (state.appStates.isMinimized) {
|
|
62
|
-
dispatch({
|
|
63
|
-
type: LiveChatWidgetActionType.SET_MINIMIZED,
|
|
64
|
-
payload: false
|
|
59
|
+
if (startChatInProgressRef.current) {
|
|
60
|
+
TelemetryHelper.logActionEvent(LogLevel.WARN, {
|
|
61
|
+
Event: TelemetryEvent.ConcurrentStartChatRejected,
|
|
62
|
+
Description: "Chat button click ignored, a start chat operation is already in progress."
|
|
65
63
|
});
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
startChatInProgressRef.current = true;
|
|
67
|
+
try {
|
|
68
|
+
TelemetryHelper.logActionEventToAllTelemetry(LogLevel.INFO, {
|
|
69
|
+
Event: TelemetryEvent.LCWChatButtonClicked,
|
|
70
|
+
Description: "Chat button click action started",
|
|
71
|
+
CustomProperties: {
|
|
72
|
+
ConversationStage: ConversationStage.Initialization
|
|
73
|
+
}
|
|
69
74
|
});
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
if (state.appStates.isMinimized) {
|
|
76
|
+
dispatch({
|
|
77
|
+
type: LiveChatWidgetActionType.SET_MINIMIZED,
|
|
78
|
+
payload: false
|
|
79
|
+
});
|
|
80
|
+
dispatch({
|
|
81
|
+
type: LiveChatWidgetActionType.SET_UNREAD_MESSAGE_COUNT,
|
|
82
|
+
payload: 0
|
|
83
|
+
});
|
|
84
|
+
// If chat is minimized and then unminimized, start a chat if convesation state is closed.
|
|
85
|
+
if (state.appStates.conversationState === ConversationState.Closed) {
|
|
86
|
+
await startChat();
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
72
89
|
await startChat();
|
|
73
90
|
}
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
TelemetryHelper.logActionEventToAllTelemetry(LogLevel.INFO, {
|
|
92
|
+
Event: TelemetryEvent.LCWChatButtonActionCompleted,
|
|
93
|
+
Description: "Chat button action completed",
|
|
94
|
+
CustomProperties: {
|
|
95
|
+
ConversationStage: ConversationStage.Initialization
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
} finally {
|
|
99
|
+
startChatInProgressRef.current = false;
|
|
76
100
|
}
|
|
77
|
-
TelemetryHelper.logActionEventToAllTelemetry(LogLevel.INFO, {
|
|
78
|
-
Event: TelemetryEvent.LCWChatButtonActionCompleted,
|
|
79
|
-
Description: "Chat button action completed",
|
|
80
|
-
CustomProperties: {
|
|
81
|
-
ConversationStage: ConversationStage.Initialization
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
101
|
};
|
|
85
102
|
const outOfOfficeStyleProps = Object.assign({}, defaultOutOfOfficeChatButtonStyleProps, outOfOfficeButtonProps === null || outOfOfficeButtonProps === void 0 ? void 0 : outOfOfficeButtonProps.styleProps);
|
|
86
103
|
const controlProps = {
|
|
@@ -349,14 +349,53 @@ export const closeChatStateCleanUp = dispatch => {
|
|
|
349
349
|
});
|
|
350
350
|
};
|
|
351
351
|
|
|
352
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
353
|
+
const logCleanUpDisconnectFailure = error => {
|
|
354
|
+
TelemetryHelper.logActionEvent(LogLevel.WARN, {
|
|
355
|
+
Event: TelemetryEvent.ChatSDKStateCleanUpDisconnectFailed,
|
|
356
|
+
Description: "Failed to disconnect the previous conversation during chat SDK state clean up.",
|
|
357
|
+
ExceptionDetails: {
|
|
358
|
+
exception: (error === null || error === void 0 ? void 0 : error.message) ?? error
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// Releases the previous session's conversation. Mirrors the cleanup OmnichannelChatSDK.endChat()
|
|
364
|
+
// runs in its finally block (disconnect, then drop the reference). Without it, a conversation
|
|
365
|
+
// left over from a failed or orphaned start chat, or from a chat ended in another tab (which
|
|
366
|
+
// skips the SDK endChat), keeps its messaging session open and leaks into the next chat.
|
|
367
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
368
|
+
const disconnectConversation = chatSDK => {
|
|
369
|
+
const conversation = chatSDK.conversation;
|
|
370
|
+
if (!conversation || typeof conversation.disconnect !== "function") {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
// ACS conversations disconnect asynchronously; legacy IC3 conversations may not return a promise.
|
|
375
|
+
const result = conversation.disconnect();
|
|
376
|
+
if (result && typeof result.catch === "function") {
|
|
377
|
+
result.catch(logCleanUpDisconnectFailure);
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
logCleanUpDisconnectFailure(error);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
352
384
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
353
385
|
export const chatSDKStateCleanUp = chatSDK => {
|
|
386
|
+
if (!chatSDK) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
354
390
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
355
391
|
chatSDK.requestId = uuidv4();
|
|
356
392
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
357
393
|
chatSDK.chatToken = {};
|
|
358
394
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
359
395
|
chatSDK.reconnectId = null;
|
|
396
|
+
disconnectConversation(chatSDK);
|
|
397
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
398
|
+
chatSDK.conversation = null;
|
|
360
399
|
};
|
|
361
400
|
|
|
362
401
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -11,7 +11,7 @@ import { LiveChatWidgetActionType } from "../../../contexts/common/LiveChatWidge
|
|
|
11
11
|
import { TelemetryHelper } from "../../../common/telemetry/TelemetryHelper";
|
|
12
12
|
import { WebChatStoreLoader } from "../../webchatcontainerstateful/webchatcontroller/WebChatStoreLoader";
|
|
13
13
|
import attachmentProcessingMiddleware from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentProcessingMiddleware";
|
|
14
|
-
import
|
|
14
|
+
import createAttachmentSentAnnouncementMiddleware from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware";
|
|
15
15
|
import channelDataMiddleware from "../../webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/channelDataMiddleware";
|
|
16
16
|
import { createActivityMiddleware } from "../../webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware";
|
|
17
17
|
import { createActivityStatusMiddleware } from "../../webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityStatusMiddleware";
|
|
@@ -62,7 +62,7 @@ export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endCh
|
|
|
62
62
|
// Initialize Web Chat's redux store
|
|
63
63
|
let webChatStore = WebChatStoreLoader.store;
|
|
64
64
|
if (!webChatStore) {
|
|
65
|
-
var _state$domainStates$l, _state$domainStates$l2, _state$domainStates$l3, _props$webChatContain7;
|
|
65
|
+
var _state$domainStates$l, _state$domainStates$l2, _props$controlProps, _state$domainStates$l3, _props$webChatContain7;
|
|
66
66
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
67
|
const addConversationalSurveyTagsCallback = action => {
|
|
68
68
|
var _inMemoryState$appSta;
|
|
@@ -121,7 +121,7 @@ export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endCh
|
|
|
121
121
|
};
|
|
122
122
|
webChatStore = createStore({},
|
|
123
123
|
//initial state
|
|
124
|
-
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),
|
|
124
|
+
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), createAttachmentSentAnnouncementMiddleware(localizedTexts, ((_props$controlProps = props.controlProps) === null || _props$controlProps === void 0 ? void 0 : _props$controlProps.id) || "oc-lcw"), 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, createCitationsMiddleware(state, dispatch), gifUploadMiddleware, htmlPlayerMiddleware, htmlTextMiddleware(honorsTargetInHTMLLinks), createMaxMessageSizeValidator(localizedTexts), sanitizationMiddleware, createCallActionMiddleware(),
|
|
125
125
|
// Pass a callback so middleware can push initials into React context for reactivity
|
|
126
126
|
localizedStringsBotInitialsMiddleware(initials => {
|
|
127
127
|
dispatch({
|
|
@@ -251,6 +251,12 @@ export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endCh
|
|
|
251
251
|
// Track what would be removed
|
|
252
252
|
const removedTags = [];
|
|
253
253
|
const removedAttributes = [];
|
|
254
|
+
const trackAttribute = name => {
|
|
255
|
+
const attrName = name.toLowerCase();
|
|
256
|
+
if (!strictConfig.ALLOWED_ATTR.includes(attrName) && attrName !== "class" && attrName !== "id") {
|
|
257
|
+
removedAttributes.push(attrName);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
254
260
|
|
|
255
261
|
// Add hooks to the isolated monitoring instance
|
|
256
262
|
monitorDOMPurify.addHook("uponSanitizeElement", (node, data) => {
|
|
@@ -259,6 +265,8 @@ export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endCh
|
|
|
259
265
|
// Filter out "body" tag which is DOMPurify's internal wrapper
|
|
260
266
|
if (node.nodeType === 1 && !strictConfig.ALLOWED_TAGS.includes(tagName) && tagName !== "body") {
|
|
261
267
|
removedTags.push(tagName);
|
|
268
|
+
// DOMPurify 3.4.16+ skips uponSanitizeAttribute for removed elements
|
|
269
|
+
Array.from(node.attributes ?? []).forEach(attr => trackAttribute(attr.name));
|
|
262
270
|
}
|
|
263
271
|
} catch (hookError) {
|
|
264
272
|
// Silently ignore hook errors
|
|
@@ -266,10 +274,7 @@ export const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endCh
|
|
|
266
274
|
});
|
|
267
275
|
monitorDOMPurify.addHook("uponSanitizeAttribute", (node, data) => {
|
|
268
276
|
try {
|
|
269
|
-
|
|
270
|
-
if (!strictConfig.ALLOWED_ATTR.includes(attrName) && attrName !== "class" && attrName !== "id") {
|
|
271
|
-
removedAttributes.push(attrName);
|
|
272
|
-
}
|
|
277
|
+
trackAttribute(data.attrName);
|
|
273
278
|
} catch (hookError) {
|
|
274
279
|
// Silently ignore hook errors
|
|
275
280
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ConversationState } from "../../../contexts/common/ConversationState";
|
|
2
|
+
import { LogLevel, TelemetryEvent } from "../../../common/telemetry/TelemetryConstants";
|
|
3
|
+
import { TelemetryHelper } from "../../../common/telemetry/TelemetryHelper";
|
|
4
|
+
import { isUndefinedOrEmpty } from "../../../common/utils";
|
|
5
|
+
export let RecoveryEligibilityReason = /*#__PURE__*/function (RecoveryEligibilityReason) {
|
|
6
|
+
RecoveryEligibilityReason["ReconnectIdPresent"] = "ReconnectIdPresent";
|
|
7
|
+
RecoveryEligibilityReason["ActiveContextPresent"] = "ActiveContextPresent";
|
|
8
|
+
RecoveryEligibilityReason["MissingContext"] = "MissingContext";
|
|
9
|
+
RecoveryEligibilityReason["CachedContextNonActive"] = "CachedContextNonActive";
|
|
10
|
+
return RecoveryEligibilityReason;
|
|
11
|
+
}({});
|
|
12
|
+
const getConversationStateName = conversationState => {
|
|
13
|
+
if (typeof conversationState !== "number") {
|
|
14
|
+
return "Unknown";
|
|
15
|
+
}
|
|
16
|
+
const stateName = ConversationState[conversationState];
|
|
17
|
+
return typeof stateName === "string" ? stateName : "Unknown";
|
|
18
|
+
};
|
|
19
|
+
export const getRecoveryEligibilityTelemetryProperties = (state, eligible) => {
|
|
20
|
+
var _state$appStates, _state$domainStates, _state$appStates2, _state$appStates3;
|
|
21
|
+
const reconnectId = (_state$appStates = state.appStates) === null || _state$appStates === void 0 ? void 0 : _state$appStates.reconnectId;
|
|
22
|
+
const liveChatContext = (_state$domainStates = state.domainStates) === null || _state$domainStates === void 0 ? void 0 : _state$domainStates.liveChatContext;
|
|
23
|
+
const hasReconnectId = !isUndefinedOrEmpty(reconnectId);
|
|
24
|
+
const hasLiveChatContext = !isUndefinedOrEmpty(liveChatContext);
|
|
25
|
+
const hasRequestId = !isUndefinedOrEmpty(liveChatContext === null || liveChatContext === void 0 ? void 0 : liveChatContext.requestId);
|
|
26
|
+
let reason = RecoveryEligibilityReason.ActiveContextPresent;
|
|
27
|
+
if (hasReconnectId) {
|
|
28
|
+
reason = RecoveryEligibilityReason.ReconnectIdPresent;
|
|
29
|
+
} else if (!hasLiveChatContext) {
|
|
30
|
+
reason = RecoveryEligibilityReason.MissingContext;
|
|
31
|
+
} else if (((_state$appStates2 = state.appStates) === null || _state$appStates2 === void 0 ? void 0 : _state$appStates2.conversationState) !== ConversationState.Active) {
|
|
32
|
+
reason = RecoveryEligibilityReason.CachedContextNonActive;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
conversationState: getConversationStateName((_state$appStates3 = state.appStates) === null || _state$appStates3 === void 0 ? void 0 : _state$appStates3.conversationState),
|
|
36
|
+
hasReconnectId,
|
|
37
|
+
hasLiveChatContext,
|
|
38
|
+
hasRequestId,
|
|
39
|
+
eligible,
|
|
40
|
+
reason
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export const logRecoveryEligibilityTelemetry = (state, eligible) => {
|
|
44
|
+
try {
|
|
45
|
+
TelemetryHelper.logActionEvent(LogLevel.INFO, {
|
|
46
|
+
Event: TelemetryEvent.RecoveryEligibilityEvaluated,
|
|
47
|
+
CustomProperties: getRecoveryEligibilityTelemetryProperties(state, eligible)
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(error);
|
|
51
|
+
}
|
|
52
|
+
};
|