botframework-webchat-core 4.15.1 → 4.15.2-main.20220413.af6e8a3

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 (200) hide show
  1. package/babel.config.json +1 -1
  2. package/lib/actions/clearSuggestedActions.js +1 -1
  3. package/lib/actions/connect.js +1 -1
  4. package/lib/actions/connectionStatusUpdate.js +1 -1
  5. package/lib/actions/deleteActivity.d.ts +13 -0
  6. package/lib/actions/deleteActivity.d.ts.map +1 -0
  7. package/lib/actions/deleteActivity.js +5 -3
  8. package/lib/actions/disconnect.js +1 -1
  9. package/lib/actions/dismissNotification.js +1 -1
  10. package/lib/actions/emitTypingIndicator.js +1 -1
  11. package/lib/actions/incomingActivity.d.ts +14 -0
  12. package/lib/actions/incomingActivity.d.ts.map +1 -0
  13. package/lib/actions/incomingActivity.js +5 -3
  14. package/lib/actions/markActivity.d.ts +17 -0
  15. package/lib/actions/markActivity.d.ts.map +1 -0
  16. package/lib/actions/markActivity.js +5 -3
  17. package/lib/actions/postActivity.d.ts +52 -0
  18. package/lib/actions/postActivity.d.ts.map +1 -0
  19. package/lib/actions/postActivity.js +5 -3
  20. package/lib/actions/queueIncomingActivity.js +1 -1
  21. package/lib/actions/sagaError.js +1 -1
  22. package/lib/actions/sendEvent.js +1 -1
  23. package/lib/actions/sendFiles.js +1 -1
  24. package/lib/actions/sendMessage.js +1 -1
  25. package/lib/actions/sendMessageBack.js +1 -1
  26. package/lib/actions/sendPostBack.js +1 -1
  27. package/lib/actions/setDictateInterims.js +1 -1
  28. package/lib/actions/setDictateState.js +1 -1
  29. package/lib/actions/setLanguage.js +1 -1
  30. package/lib/actions/setNotification.js +1 -1
  31. package/lib/actions/setReferenceGrammarID.js +1 -1
  32. package/lib/actions/setSendBox.js +1 -1
  33. package/lib/actions/setSendTimeout.js +1 -1
  34. package/lib/actions/setSendTypingIndicator.js +1 -1
  35. package/lib/actions/setSuggestedActions.js +1 -1
  36. package/lib/actions/startDictate.js +1 -1
  37. package/lib/actions/startSpeakingActivity.js +1 -1
  38. package/lib/actions/stopDictate.js +1 -1
  39. package/lib/actions/stopSpeakingActivity.js +1 -1
  40. package/lib/actions/submitSendBox.js +1 -1
  41. package/lib/actions/updateConnectionStatus.js +1 -1
  42. package/lib/constants/ActivityClientState.js +1 -1
  43. package/lib/definitions/speakingActivity.js +4 -2
  44. package/lib/index.d.ts +15 -14
  45. package/lib/index.d.ts.map +1 -1
  46. package/lib/index.js +4 -3
  47. package/lib/reducer.d.ts +1 -2
  48. package/lib/reducer.d.ts.map +1 -1
  49. package/lib/reducer.js +1 -4
  50. package/lib/reducers/activities.d.ts +10 -0
  51. package/lib/reducers/activities.d.ts.map +1 -0
  52. package/lib/reducers/activities.js +65 -45
  53. package/lib/reducers/typing.js +13 -7
  54. package/lib/sagas/effects/observeOnce.js +1 -1
  55. package/lib/sagas/effects/whileConnected.js +1 -1
  56. package/lib/sagas/observeActivitySaga.js +21 -3
  57. package/lib/sagas/postActivitySaga.js +51 -72
  58. package/lib/sagas/sendFilesToPostActivitySaga.js +4 -4
  59. package/lib/selectors/activities.js +1 -1
  60. package/lib/selectors/combineSelectors.js +3 -3
  61. package/lib/selectors/dictateState.js +1 -1
  62. package/lib/selectors/language.js +1 -1
  63. package/lib/selectors/notifications.js +1 -1
  64. package/lib/selectors/sendBoxValue.js +1 -1
  65. package/lib/selectors/sendTimeout.js +1 -1
  66. package/lib/selectors/sendTypingIndicator.js +1 -1
  67. package/lib/selectors/shouldSpeakIncomingActivity.js +1 -1
  68. package/lib/types/AnyAnd.d.ts +2 -0
  69. package/lib/types/AnyAnd.d.ts.map +1 -0
  70. package/lib/types/AnyAnd.js +6 -0
  71. package/lib/types/OneOrMany.js +4 -0
  72. package/lib/types/WebChatActivity.d.ts +81 -0
  73. package/lib/types/WebChatActivity.d.ts.map +1 -0
  74. package/lib/types/WebChatActivity.js +6 -0
  75. package/lib/types/external/DirectLineActivity.d.ts +1 -1
  76. package/lib/types/external/DirectLineActivity.d.ts.map +1 -1
  77. package/lib/types/external/DirectLineActivity.js +4 -0
  78. package/lib/types/external/DirectLineAnimationCard.d.ts +5 -2
  79. package/lib/types/external/DirectLineAnimationCard.d.ts.map +1 -1
  80. package/lib/types/external/DirectLineAnimationCard.js +4 -0
  81. package/lib/types/external/DirectLineAttachment.d.ts +8 -2
  82. package/lib/types/external/DirectLineAttachment.d.ts.map +1 -1
  83. package/lib/types/external/DirectLineAttachment.js +4 -0
  84. package/lib/types/external/DirectLineAudioCard.d.ts +5 -2
  85. package/lib/types/external/DirectLineAudioCard.d.ts.map +1 -1
  86. package/lib/types/external/DirectLineAudioCard.js +4 -0
  87. package/lib/types/external/DirectLineBasicCardEssence.d.ts +12 -0
  88. package/lib/types/external/DirectLineBasicCardEssence.d.ts.map +1 -0
  89. package/lib/types/external/DirectLineBasicCardEssence.js +6 -0
  90. package/lib/types/external/DirectLineCardAction.d.ts +1 -1
  91. package/lib/types/external/DirectLineCardAction.d.ts.map +1 -1
  92. package/lib/types/external/DirectLineCardAction.js +4 -0
  93. package/lib/types/external/DirectLineCardImage.d.ts +8 -0
  94. package/lib/types/external/DirectLineCardImage.d.ts.map +1 -0
  95. package/lib/types/external/DirectLineCardImage.js +6 -0
  96. package/lib/types/external/DirectLineHeroCard.d.ts +5 -2
  97. package/lib/types/external/DirectLineHeroCard.d.ts.map +1 -1
  98. package/lib/types/external/DirectLineHeroCard.js +4 -0
  99. package/lib/types/external/DirectLineJSBotConnection.d.ts +1 -1
  100. package/lib/types/external/DirectLineJSBotConnection.d.ts.map +1 -1
  101. package/lib/types/external/DirectLineJSBotConnection.js +4 -0
  102. package/lib/types/external/DirectLineMediaCardEssence.d.ts +21 -0
  103. package/lib/types/external/DirectLineMediaCardEssence.d.ts.map +1 -0
  104. package/lib/types/external/DirectLineMediaCardEssence.js +6 -0
  105. package/lib/types/external/DirectLineOAuthCard.d.ts +7 -2
  106. package/lib/types/external/DirectLineOAuthCard.d.ts.map +1 -1
  107. package/lib/types/external/DirectLineOAuthCard.js +4 -0
  108. package/lib/types/external/DirectLineReceiptCard.d.ts +27 -2
  109. package/lib/types/external/DirectLineReceiptCard.d.ts.map +1 -1
  110. package/lib/types/external/DirectLineReceiptCard.js +4 -0
  111. package/lib/types/external/DirectLineSignInCard.d.ts +7 -2
  112. package/lib/types/external/DirectLineSignInCard.d.ts.map +1 -1
  113. package/lib/types/external/DirectLineSignInCard.js +4 -0
  114. package/lib/types/external/DirectLineSuggestedAction.d.ts +6 -2
  115. package/lib/types/external/DirectLineSuggestedAction.d.ts.map +1 -1
  116. package/lib/types/external/DirectLineSuggestedAction.js +4 -0
  117. package/lib/types/external/DirectLineThumbnailCard.d.ts +5 -2
  118. package/lib/types/external/DirectLineThumbnailCard.d.ts.map +1 -1
  119. package/lib/types/external/DirectLineThumbnailCard.js +4 -0
  120. package/lib/types/external/DirectLineVideoCard.d.ts +5 -2
  121. package/lib/types/external/DirectLineVideoCard.d.ts.map +1 -1
  122. package/lib/types/external/DirectLineVideoCard.js +4 -0
  123. package/lib/types/external/Observable.js +6 -0
  124. package/lib/types/internal/Notification.js +6 -0
  125. package/lib/types/internal/ReduxState.js +6 -0
  126. package/lib/utils/dateToLocaleISOString.js +5 -5
  127. package/lib/utils/deleteKey.js +1 -1
  128. package/lib/utils/sleep.js +1 -1
  129. package/lib/utils/uniqueID.js +1 -1
  130. package/package.json +18 -14
  131. package/src/actions/deleteActivity.ts +19 -0
  132. package/src/actions/incomingActivity.ts +21 -0
  133. package/src/actions/markActivity.ts +23 -0
  134. package/src/actions/postActivity.ts +48 -0
  135. package/src/definitions/speakingActivity.js +1 -1
  136. package/src/index.ts +17 -14
  137. package/src/reducer.ts +0 -2
  138. package/src/reducers/activities.ts +172 -0
  139. package/src/reducers/typing.js +6 -5
  140. package/src/sagas/effects/{observeOnce.js → observeOnce.ts} +6 -4
  141. package/src/sagas/effects/{whileConnected.js → whileConnected.ts} +20 -1
  142. package/src/sagas/{observeActivitySaga.js → observeActivitySaga.ts} +25 -6
  143. package/src/sagas/{postActivitySaga.js → postActivitySaga.ts} +57 -48
  144. package/src/sagas/sendFilesToPostActivitySaga.js +1 -1
  145. package/src/selectors/activities.ts +12 -0
  146. package/src/selectors/{combineSelectors.js → combineSelectors.ts} +7 -2
  147. package/src/selectors/dictateState.ts +3 -0
  148. package/src/selectors/language.ts +3 -0
  149. package/src/selectors/notifications.ts +6 -0
  150. package/src/selectors/sendBoxValue.ts +3 -0
  151. package/src/selectors/sendTimeout.ts +3 -0
  152. package/src/selectors/sendTypingIndicator.ts +3 -0
  153. package/src/selectors/shouldSpeakIncomingActivity.ts +3 -0
  154. package/src/types/AnyAnd.ts +1 -0
  155. package/src/types/WebChatActivity.ts +154 -0
  156. package/src/types/external/DirectLineActivity.ts +1 -1
  157. package/src/types/external/DirectLineAnimationCard.ts +6 -3
  158. package/src/types/external/DirectLineAttachment.ts +10 -4
  159. package/src/types/external/DirectLineAudioCard.ts +6 -3
  160. package/src/types/external/DirectLineBasicCardEssence.ts +14 -0
  161. package/src/types/external/DirectLineCardAction.ts +1 -1
  162. package/src/types/external/DirectLineCardImage.ts +9 -0
  163. package/src/types/external/DirectLineHeroCard.ts +6 -3
  164. package/src/types/external/DirectLineJSBotConnection.ts +1 -1
  165. package/src/types/external/DirectLineMediaCardEssence.ts +19 -0
  166. package/src/types/external/DirectLineOAuthCard.ts +7 -3
  167. package/src/types/external/DirectLineReceiptCard.ts +30 -3
  168. package/src/types/external/DirectLineSignInCard.ts +8 -3
  169. package/src/types/external/DirectLineSuggestedAction.ts +6 -3
  170. package/src/types/external/DirectLineThumbnailCard.ts +6 -3
  171. package/src/types/external/DirectLineVideoCard.ts +6 -3
  172. package/src/types/external/Observable.ts +69 -0
  173. package/src/types/internal/Notification.ts +10 -0
  174. package/src/types/internal/ReduxState.ts +16 -0
  175. package/src/utils/{dateToLocaleISOString.js → dateToLocaleISOString.ts} +6 -6
  176. package/src/utils/deleteKey.ts +9 -0
  177. package/src/utils/{sleep.js → sleep.ts} +1 -1
  178. package/src/utils/{uniqueID.js → uniqueID.ts} +1 -1
  179. package/lib/reducers/clockSkewAdjustment.js +0 -44
  180. package/lib/sagas/effects/callUntil.js +0 -48
  181. package/lib/selectors/clockSkewAdjustment.js +0 -14
  182. package/lib/utils/mime-wrapper.js +0 -47
  183. package/src/actions/deleteActivity.js +0 -8
  184. package/src/actions/incomingActivity.js +0 -10
  185. package/src/actions/markActivity.js +0 -14
  186. package/src/actions/postActivity.js +0 -14
  187. package/src/reducers/activities.js +0 -116
  188. package/src/reducers/clockSkewAdjustment.js +0 -29
  189. package/src/sagas/effects/callUntil.js +0 -13
  190. package/src/selectors/activities.js +0 -8
  191. package/src/selectors/clockSkewAdjustment.js +0 -1
  192. package/src/selectors/dictateState.js +0 -1
  193. package/src/selectors/language.js +0 -1
  194. package/src/selectors/notifications.js +0 -3
  195. package/src/selectors/sendBoxValue.js +0 -1
  196. package/src/selectors/sendTimeout.js +0 -1
  197. package/src/selectors/sendTypingIndicator.js +0 -1
  198. package/src/selectors/shouldSpeakIncomingActivity.js +0 -1
  199. package/src/utils/deleteKey.js +0 -9
  200. package/src/utils/mime-wrapper.js +0 -39
@@ -0,0 +1,23 @@
1
+ type MarkActivityActionType = 'WEB_CHAT/MARK_ACTIVITY';
2
+
3
+ type MarkActivityAction = {
4
+ payload: { activityID: string; name: string; value: any };
5
+ type: MarkActivityActionType;
6
+ };
7
+
8
+ const MARK_ACTIVITY: MarkActivityActionType = 'WEB_CHAT/MARK_ACTIVITY';
9
+
10
+ function markActivity({ id: activityID }: { id: string }, name: string, value: any): MarkActivityAction {
11
+ return {
12
+ type: MARK_ACTIVITY,
13
+ payload: {
14
+ activityID,
15
+ name,
16
+ value
17
+ }
18
+ };
19
+ }
20
+
21
+ export default markActivity;
22
+ export { MARK_ACTIVITY };
23
+ export type { MarkActivityAction };
@@ -0,0 +1,48 @@
1
+ import type { WebChatActivity } from '../types/WebChatActivity';
2
+
3
+ type PostActivityActionType = 'DIRECT_LINE/POST_ACTIVITY';
4
+ type PostActivityFulfilledActionType = 'DIRECT_LINE/POST_ACTIVITY_FULFILLED';
5
+ type PostActivityPendingActionType = 'DIRECT_LINE/POST_ACTIVITY_PENDING';
6
+ type PostActivityRejectedActionType = 'DIRECT_LINE/POST_ACTIVITY_REJECTED';
7
+
8
+ type PostActivityAction = {
9
+ meta: { method: string };
10
+ payload: { activity: WebChatActivity };
11
+ type: PostActivityActionType;
12
+ };
13
+
14
+ type PostActivityFulfilledAction = {
15
+ meta: { clientActivityID: string; method: string };
16
+ payload: { activity: WebChatActivity };
17
+ type: PostActivityFulfilledActionType;
18
+ };
19
+
20
+ type PostActivityPendingAction = {
21
+ meta: { clientActivityID: string; method: string };
22
+ payload: { activity: WebChatActivity };
23
+ type: PostActivityPendingActionType;
24
+ };
25
+
26
+ type PostActivityRejectedAction = {
27
+ error: true;
28
+ meta: { clientActivityID: string; method: string };
29
+ payload: Error;
30
+ type: PostActivityRejectedActionType;
31
+ };
32
+
33
+ const POST_ACTIVITY: PostActivityActionType = 'DIRECT_LINE/POST_ACTIVITY';
34
+ const POST_ACTIVITY_FULFILLED: PostActivityFulfilledActionType = `${POST_ACTIVITY}_FULFILLED`;
35
+ const POST_ACTIVITY_PENDING: PostActivityPendingActionType = `${POST_ACTIVITY}_PENDING`;
36
+ const POST_ACTIVITY_REJECTED: PostActivityRejectedActionType = `${POST_ACTIVITY}_REJECTED`;
37
+
38
+ function postActivity(activity: WebChatActivity, method = 'keyboard'): PostActivityAction {
39
+ return {
40
+ type: POST_ACTIVITY,
41
+ meta: { method },
42
+ payload: { activity }
43
+ };
44
+ }
45
+
46
+ export default postActivity;
47
+ export { POST_ACTIVITY, POST_ACTIVITY_FULFILLED, POST_ACTIVITY_PENDING, POST_ACTIVITY_REJECTED };
48
+ export type { PostActivityAction, PostActivityFulfilledAction, PostActivityPendingAction, PostActivityRejectedAction };
@@ -1,5 +1,5 @@
1
1
  // If true, the activity is in the queue and needs to be spoken.
2
2
 
3
3
  export default function speakingActivity(activity) {
4
- return activity.channelData && activity.channelData.speak;
4
+ return activity.channelData?.speak;
5
5
  }
package/src/index.ts CHANGED
@@ -3,19 +3,6 @@ import * as DictateState from './constants/DictateState';
3
3
  import clearSuggestedActions from './actions/clearSuggestedActions';
4
4
  import connect from './actions/connect';
5
5
  import createStore, { withDevTools as createStoreWithDevTools } from './createStore';
6
- import DirectLineActivity from './types/external/DirectLineActivity';
7
- import DirectLineAnimationCard from './types/external/DirectLineAnimationCard';
8
- import DirectLineAttachment from './types/external/DirectLineAttachment';
9
- import DirectLineAudioCard from './types/external/DirectLineAudioCard';
10
- import DirectLineCardAction from './types/external/DirectLineCardAction';
11
- import DirectLineHeroCard from './types/external/DirectLineHeroCard';
12
- import DirectLineJSBotConnection from './types/external/DirectLineJSBotConnection';
13
- import DirectLineOAuthCard from './types/external/DirectLineOAuthCard';
14
- import DirectLineReceiptCard from './types/external/DirectLineReceiptCard';
15
- import DirectLineSignInCard from './types/external/DirectLineSignInCard';
16
- import DirectLineSuggestedAction from './types/external/DirectLineSuggestedAction';
17
- import DirectLineThumbnailCard from './types/external/DirectLineThumbnailCard';
18
- import DirectLineVideoCard from './types/external/DirectLineVideoCard';
19
6
  import disconnect from './actions/disconnect';
20
7
  import dismissNotification from './actions/dismissNotification';
21
8
  import emitTypingIndicator from './actions/emitTypingIndicator';
@@ -43,6 +30,21 @@ import stopSpeakingActivity from './actions/stopSpeakingActivity';
43
30
  import submitSendBox from './actions/submitSendBox';
44
31
  import warnOnce from './utils/warnOnce';
45
32
 
33
+ import type { DirectLineActivity } from './types/external/DirectLineActivity';
34
+ import type { DirectLineAnimationCard } from './types/external/DirectLineAnimationCard';
35
+ import type { DirectLineAttachment } from './types/external/DirectLineAttachment';
36
+ import type { DirectLineAudioCard } from './types/external/DirectLineAudioCard';
37
+ import type { DirectLineCardAction } from './types/external/DirectLineCardAction';
38
+ import type { DirectLineHeroCard } from './types/external/DirectLineHeroCard';
39
+ import type { DirectLineJSBotConnection } from './types/external/DirectLineJSBotConnection';
40
+ import type { DirectLineOAuthCard } from './types/external/DirectLineOAuthCard';
41
+ import type { DirectLineReceiptCard } from './types/external/DirectLineReceiptCard';
42
+ import type { DirectLineSignInCard } from './types/external/DirectLineSignInCard';
43
+ import type { DirectLineSuggestedAction } from './types/external/DirectLineSuggestedAction';
44
+ import type { DirectLineThumbnailCard } from './types/external/DirectLineThumbnailCard';
45
+ import type { DirectLineVideoCard } from './types/external/DirectLineVideoCard';
46
+ import type { WebChatActivity } from './types/WebChatActivity';
47
+
46
48
  const Constants = { ActivityClientState, DictateState };
47
49
  const version = process.env.npm_package_version;
48
50
 
@@ -94,5 +96,6 @@ export type {
94
96
  DirectLineSuggestedAction,
95
97
  DirectLineThumbnailCard,
96
98
  DirectLineVideoCard,
97
- OneOrMany
99
+ OneOrMany,
100
+ WebChatActivity
98
101
  };
package/src/reducer.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { combineReducers } from 'redux';
2
2
 
3
3
  import activities from './reducers/activities';
4
- import clockSkewAdjustment from './reducers/clockSkewAdjustment';
5
4
  import connectivityStatus from './reducers/connectivityStatus';
6
5
  import dictateInterims from './reducers/dictateInterims';
7
6
  import dictateState from './reducers/dictateState';
@@ -19,7 +18,6 @@ import typing from './reducers/typing';
19
18
 
20
19
  export default combineReducers({
21
20
  activities,
22
- clockSkewAdjustment,
23
21
  connectivityStatus,
24
22
  dictateInterims,
25
23
  dictateState,
@@ -0,0 +1,172 @@
1
+ /* eslint no-magic-numbers: ["error", { "ignore": [0, 1, -1] }] */
2
+
3
+ import updateIn from 'simple-update-in';
4
+
5
+ import { DELETE_ACTIVITY } from '../actions/deleteActivity';
6
+ import { INCOMING_ACTIVITY } from '../actions/incomingActivity';
7
+ import { MARK_ACTIVITY } from '../actions/markActivity';
8
+ import { POST_ACTIVITY_FULFILLED, POST_ACTIVITY_PENDING, POST_ACTIVITY_REJECTED } from '../actions/postActivity';
9
+ import { SEND_FAILED, SENDING, SENT } from '../constants/ActivityClientState';
10
+ import type { DeleteActivityAction } from '../actions/deleteActivity';
11
+ import type { IncomingActivityAction } from '../actions/incomingActivity';
12
+ import type { MarkActivityAction } from '../actions/markActivity';
13
+ import type {
14
+ PostActivityFulfilledAction,
15
+ PostActivityPendingAction,
16
+ PostActivityRejectedAction
17
+ } from '../actions/postActivity';
18
+ import type { WebChatActivity } from '../types/WebChatActivity';
19
+
20
+ type ActivitiesAction =
21
+ | DeleteActivityAction
22
+ | IncomingActivityAction
23
+ | MarkActivityAction
24
+ | PostActivityFulfilledAction
25
+ | PostActivityPendingAction
26
+ | PostActivityRejectedAction;
27
+
28
+ type ActivitiesStateType = WebChatActivity[];
29
+
30
+ const DEFAULT_STATE: ActivitiesStateType = [];
31
+ const DIRECT_LINE_PLACEHOLDER_URL =
32
+ 'https://docs.botframework.com/static/devportal/client/images/bot-framework-default-placeholder.png';
33
+
34
+ function getClientActivityID(activity: WebChatActivity): string | undefined {
35
+ return activity.channelData?.clientActivityID;
36
+ }
37
+
38
+ function findByClientActivityID(clientActivityID: string): (activity: WebChatActivity) => boolean {
39
+ return (activity: WebChatActivity) => getClientActivityID(activity) === clientActivityID;
40
+ }
41
+
42
+ function patchActivity(activity: WebChatActivity, lastActivity: WebChatActivity): WebChatActivity {
43
+ // Direct Line channel will return a placeholder image for the user-uploaded image.
44
+ // As observed, the URL for the placeholder image is https://docs.botframework.com/static/devportal/client/images/bot-framework-default-placeholder.png.
45
+ // To make our code simpler, we are removing the value if "contentUrl" is pointing to a placeholder image.
46
+
47
+ // TODO: [P2] #2869 This "contentURL" removal code should be moved to DirectLineJS adapter.
48
+
49
+ // Also, if the "contentURL" starts with "blob:", this means the user is uploading a file (the URL is constructed by URL.createObjectURL)
50
+ // Although the copy/reference of the file is temporary in-memory, to make the UX consistent across page refresh, we do not allow the user to re-download the file either.
51
+
52
+ activity = updateIn(activity, ['attachments', () => true, 'contentUrl'], (contentUrl: string) => {
53
+ if (contentUrl !== DIRECT_LINE_PLACEHOLDER_URL && !/^blob:/iu.test(contentUrl)) {
54
+ return contentUrl;
55
+ }
56
+ });
57
+
58
+ // If the message does not have sequence ID, use these fallback values:
59
+ // 1. "timestamp" field
60
+ // - outgoing activity will not have "timestamp" field
61
+ // 2. last activity sequence ID (or 0) + 0.001
62
+ // - best effort to put this message the last one in the chat history
63
+ activity = updateIn(activity, ['channelData', 'webchat:sequence-id'], (sequenceId?: number) =>
64
+ typeof sequenceId === 'number'
65
+ ? sequenceId
66
+ : typeof activity.timestamp !== 'undefined'
67
+ ? +new Date(activity.timestamp)
68
+ : // We assume there will be no more than 1,000 messages sent before receiving server response.
69
+ // If there are more than 1,000 messages, some messages will get reordered and appear jumpy after receiving server response.
70
+ // eslint-disable-next-line no-magic-numbers
71
+ (lastActivity?.channelData?.['webchat:sequence-id'] || 0) + 0.001
72
+ );
73
+
74
+ // TODO: [P1] #3953 We should move this patching logic to a DLJS wrapper for simplicity.
75
+ activity = updateIn(activity, ['channelData', 'webchat:sequence-id'], (sequenceId: number) =>
76
+ typeof sequenceId === 'number' ? sequenceId : +new Date(activity.timestamp || 0) || 0
77
+ );
78
+
79
+ return activity;
80
+ }
81
+
82
+ function upsertActivityWithSort(activities: WebChatActivity[], nextActivity: WebChatActivity): WebChatActivity[] {
83
+ nextActivity = patchActivity(nextActivity, activities[activities.length - 1]);
84
+
85
+ const { channelData: { clientActivityID: nextClientActivityID, 'webchat:sequence-id': nextSequenceId } = {} } =
86
+ nextActivity;
87
+
88
+ const nextActivities = activities.filter(
89
+ ({ channelData: { clientActivityID } = {}, id }) =>
90
+ // We will remove all "sending messages" activities and activities with same ID
91
+ // "clientActivityID" is unique and used to track if the message has been sent and echoed back from the server
92
+ !(nextClientActivityID && clientActivityID === nextClientActivityID) && !(id && id === nextActivity.id)
93
+ );
94
+
95
+ // Then, find the right (sorted) place to insert the new activity at, based on timestamp
96
+ // Since clockskew might happen, we will ignore timestamp on messages that are sending
97
+
98
+ const indexToInsert = nextActivities.findIndex(
99
+ ({ channelData: { state, 'webchat:sequence-id': sequenceId } = {} }) =>
100
+ (sequenceId || 0) > (nextSequenceId || 0) && state !== SENDING && state !== SEND_FAILED
101
+ );
102
+
103
+ // If no right place are found, append it
104
+ nextActivities.splice(~indexToInsert ? indexToInsert : nextActivities.length, 0, nextActivity);
105
+
106
+ return nextActivities;
107
+ }
108
+
109
+ export default function activities(
110
+ state: ActivitiesStateType = DEFAULT_STATE,
111
+ action: ActivitiesAction
112
+ ): ActivitiesStateType {
113
+ switch (action.type) {
114
+ case DELETE_ACTIVITY:
115
+ state = updateIn(state, [({ id }: WebChatActivity) => id === action.payload.activityID]);
116
+ break;
117
+
118
+ case MARK_ACTIVITY:
119
+ {
120
+ const { payload } = action;
121
+
122
+ state = updateIn(
123
+ state,
124
+ [({ id }: WebChatActivity) => id === payload.activityID, 'channelData', payload.name],
125
+ () => payload.value
126
+ );
127
+ }
128
+
129
+ break;
130
+
131
+ case POST_ACTIVITY_PENDING:
132
+ {
133
+ let {
134
+ payload: { activity }
135
+ } = action;
136
+
137
+ activity = updateIn(activity, ['channelData', 'state'], () => SENDING);
138
+
139
+ state = upsertActivityWithSort(state, activity);
140
+ }
141
+
142
+ break;
143
+
144
+ case POST_ACTIVITY_REJECTED:
145
+ state = updateIn(
146
+ state,
147
+ [findByClientActivityID(action.meta.clientActivityID), 'channelData', 'state'],
148
+ () => SEND_FAILED
149
+ );
150
+
151
+ break;
152
+
153
+ case POST_ACTIVITY_FULFILLED:
154
+ state = updateIn(state, [findByClientActivityID(action.meta.clientActivityID)], () =>
155
+ // We will replace the activity with the version from the server
156
+ updateIn(patchActivity(action.payload.activity, state[state.length - 1]), ['channelData', 'state'], () => SENT)
157
+ );
158
+
159
+ break;
160
+
161
+ case INCOMING_ACTIVITY:
162
+ // TODO: [P4] #2100 Move "typing" into Constants.ActivityType
163
+ state = upsertActivityWithSort(state, action.payload.activity);
164
+
165
+ break;
166
+
167
+ default:
168
+ break;
169
+ }
170
+
171
+ return state;
172
+ }
@@ -18,11 +18,12 @@ export default function lastTyping(state = DEFAULT_STATE, { payload, type }) {
18
18
  } = payload;
19
19
 
20
20
  if (activityType === 'typing') {
21
- state = updateIn(state, [id], () => ({
22
- at: Date.now(),
23
- name,
24
- role
25
- }));
21
+ const now = Date.now();
22
+
23
+ state = updateIn(state, [id, 'at'], at => at || now);
24
+ state = updateIn(state, [id, 'last'], () => now);
25
+ state = updateIn(state, [id, 'name'], () => name);
26
+ state = updateIn(state, [id, 'role'], () => role);
26
27
  } else if (activityType === 'message') {
27
28
  state = updateIn(state, [id]);
28
29
  }
@@ -1,18 +1,20 @@
1
1
  import { call } from 'redux-saga/effects';
2
2
 
3
- export default function observeOnceEffect(observable) {
3
+ import { Observable, Observer, Subscription } from '../../types/external/Observable';
4
+
5
+ export default function observeOnceEffect<T>(observable: Observable<T>) {
4
6
  return call(function* observeOnce() {
5
- let subscription;
7
+ let subscription: Subscription;
6
8
 
7
9
  try {
8
10
  return yield call(
9
11
  () =>
10
- new Promise((resolve, reject) => {
12
+ new Promise<T>((resolve, reject) => {
11
13
  subscription = observable.subscribe({
12
14
  complete: resolve,
13
15
  error: reject,
14
16
  next: resolve
15
- });
17
+ } as Observer<T>);
16
18
  })
17
19
  );
18
20
  } finally {
@@ -4,13 +4,32 @@ import { CONNECT_FULFILLING } from '../../actions/connect';
4
4
  import { DISCONNECT_PENDING } from '../../actions/disconnect';
5
5
  import { RECONNECT_PENDING, RECONNECT_FULFILLING } from '../../actions/reconnect';
6
6
 
7
- export default function whileConnectedEffect(fn) {
7
+ import type { DirectLineJSBotConnection } from '../../types/external/DirectLineJSBotConnection';
8
+
9
+ export default function whileConnectedEffect(
10
+ fn: ({
11
+ directLine,
12
+ userID,
13
+ username
14
+ }: {
15
+ directLine: DirectLineJSBotConnection;
16
+ userID: string;
17
+ username: string;
18
+ }) => void
19
+ ) {
8
20
  return call(function* whileConnected() {
9
21
  for (;;) {
10
22
  const {
11
23
  meta: { userID, username },
12
24
  payload: { directLine }
25
+ }: {
26
+ meta: {
27
+ userID: string;
28
+ username: string;
29
+ };
30
+ payload: { directLine: DirectLineJSBotConnection };
13
31
  } = yield take([CONNECT_FULFILLING, RECONNECT_FULFILLING]);
32
+
14
33
  const task = yield fork(fn, { directLine, userID, username });
15
34
 
16
35
  // When we receive DISCONNECT_PENDING or RECONNECT_PENDING, the Direct Line connection is currently busy and should not be used.
@@ -4,10 +4,13 @@ import updateIn from 'simple-update-in';
4
4
  import observeEach from './effects/observeEach';
5
5
  import queueIncomingActivity from '../actions/queueIncomingActivity';
6
6
  import whileConnected from './effects/whileConnected';
7
+ import type { DirectLineActivity } from '../types/external/DirectLineActivity';
8
+ import type { DirectLineJSBotConnection } from '../types/external/DirectLineJSBotConnection';
9
+ import type { WebChatActivity } from '../types/WebChatActivity';
7
10
 
8
- const PASSTHRU_FN = value => value;
11
+ const PASSTHRU_FN = (value: unknown) => value;
9
12
 
10
- function patchActivityWithFromRole(activity, userID) {
13
+ function patchActivityWithFromRole(activity: DirectLineActivity, userID?: string): DirectLineActivity {
11
14
  // Some activities, such as "ConversationUpdate", does not have "from" defined.
12
15
  // And although "role" is defined in Direct Line spec, it was not sent over the wire.
13
16
  // We normalize the activity here to simplify null-check and logic later.
@@ -28,7 +31,7 @@ function patchActivityWithFromRole(activity, userID) {
28
31
  return activity;
29
32
  }
30
33
 
31
- function patchNullAsUndefined(activity) {
34
+ function patchNullAsUndefined(activity: DirectLineActivity): DirectLineActivity {
32
35
  // These fields are known used in Web Chat and in any cases, they should not be null, but undefined.
33
36
  // The only field omitted is "value", as it could be null purposefully.
34
37
 
@@ -56,12 +59,28 @@ function patchNullAsUndefined(activity) {
56
59
  }, activity);
57
60
  }
58
61
 
59
- function* observeActivity({ directLine, userID }) {
60
- yield observeEach(directLine.activity$, function* observeActivity(activity) {
62
+ // Patching the `from.name` to be a human readable name.
63
+ // We use the `from.name` for typing indicator, such that it read "John is typing...".
64
+ function patchFromName(activity: DirectLineActivity) {
65
+ return updateIn(activity, ['from', 'name'], (name: string | undefined): string => {
66
+ const { channelId, from = {} } = activity;
67
+
68
+ if ((channelId === 'directline' || channelId === 'webchat') && from.id === from.name && from.role === 'bot') {
69
+ return 'Bot';
70
+ }
71
+
72
+ return name;
73
+ });
74
+ }
75
+
76
+ function* observeActivity({ directLine, userID }: { directLine: DirectLineJSBotConnection; userID?: string }) {
77
+ yield observeEach(directLine.activity$, function* observeActivity(activity: DirectLineActivity) {
78
+ // TODO: [P2] #3953 Move the patching logic to a DirectLineJS wrapper, instead of too close to inners of Web Chat.
61
79
  activity = patchNullAsUndefined(activity);
62
80
  activity = patchActivityWithFromRole(activity, userID);
81
+ activity = patchFromName(activity);
63
82
 
64
- yield put(queueIncomingActivity(activity));
83
+ yield put(queueIncomingActivity(activity as WebChatActivity));
65
84
  });
66
85
  }
67
86
 
@@ -1,41 +1,44 @@
1
1
  import { all, call, cancelled, put, race, select, take, takeEvery } from 'redux-saga/effects';
2
2
 
3
- import observeOnce from './effects/observeOnce';
4
- import whileConnected from './effects/whileConnected';
5
-
6
- import clockSkewAdjustmentSelector from '../selectors/clockSkewAdjustment';
7
- import combineSelectors from '../selectors/combineSelectors';
8
- import dateToLocaleISOString from '../utils/dateToLocaleISOString';
9
- import languageSelector from '../selectors/language';
10
- import sendTimeoutSelector from '../selectors/sendTimeout';
11
-
12
- import deleteKey from '../utils/deleteKey';
13
- import sleep from '../utils/sleep';
14
- import uniqueID from '../utils/uniqueID';
15
-
3
+ import { INCOMING_ACTIVITY } from '../actions/incomingActivity';
16
4
  import {
17
5
  POST_ACTIVITY,
18
6
  POST_ACTIVITY_FULFILLED,
19
7
  POST_ACTIVITY_PENDING,
20
8
  POST_ACTIVITY_REJECTED
21
9
  } from '../actions/postActivity';
22
-
23
- import { INCOMING_ACTIVITY } from '../actions/incomingActivity';
24
-
25
- function getTimestamp(date, clockSkewAdjustment = 0) {
26
- // "+date" will return epoch time in milliseconds, same as Date.getTime().
27
- return new Date(+date + clockSkewAdjustment).toISOString();
28
- }
29
-
30
- function* postActivity(directLine, userID, username, numActivitiesPosted, { meta: { method }, payload: { activity } }) {
31
- const { clockSkewAdjustment, locale } = yield select(
32
- combineSelectors({ clockSkewAdjustment: clockSkewAdjustmentSelector, locale: languageSelector })
33
- );
34
- const { attachments } = activity;
10
+ import dateToLocaleISOString from '../utils/dateToLocaleISOString';
11
+ import deleteKey from '../utils/deleteKey';
12
+ import languageSelector from '../selectors/language';
13
+ import observeOnce from './effects/observeOnce';
14
+ import sendTimeoutSelector from '../selectors/sendTimeout';
15
+ import sleep from '../utils/sleep';
16
+ import uniqueID from '../utils/uniqueID';
17
+ import whileConnected from './effects/whileConnected';
18
+ import type { DirectLineActivity } from '../types/external/DirectLineActivity';
19
+ import type { DirectLineJSBotConnection } from '../types/external/DirectLineJSBotConnection';
20
+ import type { IncomingActivityAction } from '../actions/incomingActivity';
21
+ import type {
22
+ PostActivityAction,
23
+ PostActivityFulfilledAction,
24
+ PostActivityPendingAction,
25
+ PostActivityRejectedAction
26
+ } from '../actions/postActivity';
27
+ import type { WebChatActivity } from '../types/WebChatActivity';
28
+
29
+ function* postActivity(
30
+ directLine: DirectLineJSBotConnection,
31
+ userID: string,
32
+ username: string,
33
+ numActivitiesPosted: number,
34
+ { meta: { method }, payload: { activity } }: PostActivityAction
35
+ ) {
36
+ const attachments = (activity.type === 'message' && activity.attachments) || [];
35
37
  const clientActivityID = uniqueID();
36
- const now = new Date();
38
+ const locale = yield select(languageSelector);
37
39
  const localTimeZone =
38
40
  typeof window.Intl === 'undefined' ? undefined : new Intl.DateTimeFormat().resolvedOptions().timeZone;
41
+ const now = new Date();
39
42
 
40
43
  activity = {
41
44
  ...deleteKey(activity, 'id'),
@@ -49,9 +52,7 @@ function* postActivity(directLine, userID, username, numActivitiesPosted, { meta
49
52
  })),
50
53
  channelData: {
51
54
  ...deleteKey(activity.channelData, 'state'),
52
- clientActivityID,
53
- // This is unskewed local timestamp for estimating clock skew.
54
- clientTimestamp: getTimestamp(now)
55
+ clientActivityID
55
56
  },
56
57
  channelId: 'webchat',
57
58
  from: {
@@ -61,10 +62,7 @@ function* postActivity(directLine, userID, username, numActivitiesPosted, { meta
61
62
  },
62
63
  locale,
63
64
  localTimestamp: dateToLocaleISOString(now),
64
- localTimezone: localTimeZone,
65
- // This timestamp will be replaced by Direct Line Channel in echoback.
66
- // We are temporarily adding this timestamp for sorting.
67
- timestamp: getTimestamp(now, clockSkewAdjustment)
65
+ localTimezone: localTimeZone
68
66
  };
69
67
 
70
68
  if (!numActivitiesPosted) {
@@ -81,9 +79,9 @@ function* postActivity(directLine, userID, username, numActivitiesPosted, { meta
81
79
  ];
82
80
  }
83
81
 
84
- const meta = { clientActivityID, method };
82
+ const meta: { clientActivityID: string; method: string } = { clientActivityID, method };
85
83
 
86
- yield put({ type: POST_ACTIVITY_PENDING, meta, payload: { activity } });
84
+ yield put({ type: POST_ACTIVITY_PENDING, meta, payload: { activity } } as PostActivityPendingAction);
87
85
 
88
86
  try {
89
87
  // Quirks: We might receive INCOMING_ACTIVITY before the postActivity call completed
@@ -93,10 +91,8 @@ function* postActivity(directLine, userID, username, numActivitiesPosted, { meta
93
91
  for (;;) {
94
92
  const {
95
93
  payload: { activity }
96
- } = yield take(INCOMING_ACTIVITY);
97
- const { channelData = {}, id } = activity;
98
-
99
- if (channelData.clientActivityID === clientActivityID && id) {
94
+ }: IncomingActivityAction = yield take(INCOMING_ACTIVITY);
95
+ if (activity.channelData?.clientActivityID === clientActivityID && activity.id) {
100
96
  return activity;
101
97
  }
102
98
  }
@@ -107,35 +103,48 @@ function* postActivity(directLine, userID, username, numActivitiesPosted, { meta
107
103
  // - Direct Line service only respond on HTTP after bot respond to Direct Line
108
104
  // - Activity may take too long time to echo back
109
105
 
110
- const sendTimeout = yield select(sendTimeoutSelector);
106
+ const sendTimeout: number = yield select(sendTimeoutSelector);
111
107
 
112
108
  const {
113
109
  send: { echoBack }
114
- } = yield race({
110
+ }: { send: { echoBack: WebChatActivity } } = yield race({
115
111
  send: all({
116
112
  echoBack: echoBackCall,
117
- postActivity: observeOnce(directLine.postActivity(activity))
113
+ postActivity: observeOnce(directLine.postActivity(activity as DirectLineActivity))
118
114
  }),
119
115
  timeout: call(() => sleep(sendTimeout).then(() => Promise.reject(new Error('timeout'))))
120
116
  });
121
117
 
122
- yield put({ type: POST_ACTIVITY_FULFILLED, meta, payload: { activity: echoBack } });
118
+ yield put({ type: POST_ACTIVITY_FULFILLED, meta, payload: { activity: echoBack } } as PostActivityFulfilledAction);
123
119
  } catch (err) {
124
120
  console.error('botframework-webchat: Failed to post activity to chat adapter.', err);
125
121
 
126
- yield put({ type: POST_ACTIVITY_REJECTED, error: true, meta, payload: err });
122
+ yield put({ type: POST_ACTIVITY_REJECTED, error: true, meta, payload: err } as PostActivityRejectedAction);
127
123
  } finally {
128
124
  if (yield cancelled()) {
129
- yield put({ type: POST_ACTIVITY_REJECTED, error: true, meta, payload: new Error('cancelled') });
125
+ yield put({
126
+ type: POST_ACTIVITY_REJECTED,
127
+ error: true,
128
+ meta,
129
+ payload: new Error('cancelled')
130
+ } as PostActivityRejectedAction);
130
131
  }
131
132
  }
132
133
  }
133
134
 
134
135
  export default function* postActivitySaga() {
135
- yield whileConnected(function* postActivityWhileConnected({ directLine, userID, username }) {
136
+ yield whileConnected(function* postActivityWhileConnected({
137
+ directLine,
138
+ userID,
139
+ username
140
+ }: {
141
+ directLine: DirectLineJSBotConnection;
142
+ userID: string;
143
+ username: string;
144
+ }) {
136
145
  let numActivitiesPosted = 0;
137
146
 
138
- yield takeEvery(POST_ACTIVITY, function* postActivityWrapper(action) {
147
+ yield takeEvery(POST_ACTIVITY, function* postActivityWrapper(action: PostActivityAction) {
139
148
  yield* postActivity(directLine, userID, username, numActivitiesPosted++, action);
140
149
  });
141
150
  });
@@ -1,7 +1,7 @@
1
1
  import { put, takeEvery } from 'redux-saga/effects';
2
+ import mime from 'mime';
2
3
 
3
4
  import { SEND_FILES } from '../actions/sendFiles';
4
- import mime from '../utils/mime-wrapper';
5
5
  import postActivity from '../actions/postActivity';
6
6
  import whileConnected from './effects/whileConnected';
7
7
 
@@ -0,0 +1,12 @@
1
+ import type { ReduxState } from '../types/internal/ReduxState';
2
+ import type { WebChatActivity } from '../types/WebChatActivity';
3
+
4
+ const activities = ({ activities }: ReduxState) => activities;
5
+
6
+ const of = (predicate: (activity: WebChatActivity) => boolean) => (state: ReduxState) =>
7
+ activities(state).filter(predicate);
8
+ const ofID = (targetID: string) => of(({ id }) => id === targetID);
9
+ const ofType = (targetType: string) => of(({ type }) => type === targetType);
10
+
11
+ export default activities;
12
+ export { of, ofID, ofType };