@seamly/web-ui 20.7.0 → 20.8.0-beta.1

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 (226) hide show
  1. package/build/dist/lib/hooks.js +1 -1
  2. package/build/dist/lib/hooks.min.js +1 -1
  3. package/build/dist/lib/index.debug.js +945 -790
  4. package/build/dist/lib/index.debug.min.js +1 -1
  5. package/build/dist/lib/index.debug.min.js.LICENSE.txt +187 -131
  6. package/build/dist/lib/index.js +24800 -19606
  7. package/build/dist/lib/index.min.js +1 -1
  8. package/build/dist/lib/index.min.js.LICENSE.txt +38 -4
  9. package/build/dist/lib/standalone.js +32920 -26742
  10. package/build/dist/lib/standalone.min.js +1 -1
  11. package/build/dist/lib/standalone.min.js.LICENSE.txt +39 -0
  12. package/build/dist/lib/storage.js +2 -2
  13. package/build/dist/lib/storage.min.js +1 -1
  14. package/build/dist/lib/style-guide.js +8780 -7907
  15. package/build/dist/lib/style-guide.min.js +2 -1
  16. package/build/dist/lib/style-guide.min.js.LICENSE.txt +38 -0
  17. package/build/dist/lib/styles.css +1 -1
  18. package/build/dist/lib/utils.js +1 -2
  19. package/build/dist/lib/utils.min.js +1 -1
  20. package/package.json +19 -9
  21. package/src/icons/avatar_agent-32.svg +7 -0
  22. package/src/icons/avatar_bot-32.svg +6 -1
  23. package/src/javascripts/api/index.js +1 -1
  24. package/src/javascripts/{config.js → config.ts} +3 -1
  25. package/src/javascripts/config.types.ts +96 -0
  26. package/src/javascripts/domains/app/actions.ts +83 -0
  27. package/src/javascripts/domains/app/app.types.ts +3 -0
  28. package/src/javascripts/domains/app/hooks.js +3 -5
  29. package/src/javascripts/domains/app/selectors.ts +6 -0
  30. package/src/javascripts/domains/app/slice.ts +30 -0
  31. package/src/javascripts/domains/config/actions.ts +45 -0
  32. package/src/javascripts/domains/config/hooks.ts +19 -0
  33. package/src/javascripts/domains/config/selectors.ts +24 -0
  34. package/src/javascripts/domains/config/slice.ts +113 -0
  35. package/src/javascripts/domains/errors/index.js +13 -9
  36. package/src/javascripts/domains/forms/context.ts +14 -0
  37. package/src/javascripts/domains/forms/forms.types.ts +24 -0
  38. package/src/javascripts/domains/forms/{hooks.js → hooks.ts} +23 -26
  39. package/src/javascripts/domains/forms/{provider.js → provider.tsx} +20 -14
  40. package/src/javascripts/domains/forms/{selectors.js → selectors.ts} +7 -8
  41. package/src/javascripts/domains/forms/slice.ts +84 -0
  42. package/src/javascripts/domains/forms/utils.ts +15 -0
  43. package/src/javascripts/domains/i18n/actions.ts +24 -0
  44. package/src/javascripts/domains/i18n/{hooks.js → hooks.ts} +2 -2
  45. package/src/javascripts/domains/i18n/i18n.types.ts +6 -0
  46. package/src/javascripts/domains/i18n/selectors.ts +16 -0
  47. package/src/javascripts/domains/i18n/{reducer.js → slice.ts} +40 -37
  48. package/src/javascripts/domains/interrupt/{hooks.js → hooks.ts} +2 -2
  49. package/src/javascripts/domains/interrupt/{middleware.js → middleware.ts} +11 -8
  50. package/src/javascripts/domains/interrupt/selectors.ts +6 -0
  51. package/src/javascripts/domains/interrupt/slice.ts +40 -0
  52. package/src/javascripts/domains/options/middleware.js +9 -6
  53. package/src/javascripts/domains/redux/redux.types.ts +11 -0
  54. package/src/javascripts/domains/store/index.ts +53 -0
  55. package/src/javascripts/domains/store/slice.ts +642 -0
  56. package/src/javascripts/domains/store/store.types.ts +146 -0
  57. package/src/javascripts/domains/translations/components/chat-status.js +2 -2
  58. package/src/javascripts/domains/translations/components/options-button.js +1 -1
  59. package/src/javascripts/domains/translations/components/options-dialog/form.js +5 -5
  60. package/src/javascripts/domains/translations/components/options-dialog/index.js +2 -2
  61. package/src/javascripts/domains/translations/hooks.ts +114 -0
  62. package/src/javascripts/domains/translations/middleware.js +29 -27
  63. package/src/javascripts/domains/translations/selectors.ts +12 -0
  64. package/src/javascripts/domains/translations/slice.ts +120 -0
  65. package/src/javascripts/domains/translations/translations.types.ts +19 -0
  66. package/src/javascripts/domains/visibility/{actions.js → actions.ts} +25 -19
  67. package/src/javascripts/domains/visibility/{hooks.js → hooks.ts} +13 -10
  68. package/src/javascripts/domains/visibility/{selectors.js → selectors.ts} +3 -6
  69. package/src/javascripts/domains/visibility/slice.ts +38 -0
  70. package/src/javascripts/domains/visibility/utils.js +0 -9
  71. package/src/javascripts/domains/visibility/visibility.types.ts +6 -0
  72. package/src/javascripts/index.ts +92 -0
  73. package/src/javascripts/lib/engine/index.js +15 -11
  74. package/src/javascripts/lib/external-api/initialize-api.js +1 -1
  75. package/src/javascripts/lib/id.js +5 -8
  76. package/src/javascripts/lib/mutex.js +3 -1
  77. package/src/javascripts/lib/store/providers/cookie-storage.js +1 -1
  78. package/src/javascripts/lib/store/providers/session-storage.js +1 -1
  79. package/src/javascripts/package/hooks.js +2 -2
  80. package/src/javascripts/package/utils.js +0 -1
  81. package/src/javascripts/schema.ts +1448 -0
  82. package/src/javascripts/style-guide/components/app.js +6 -6
  83. package/src/javascripts/style-guide/components/static-core.js +87 -65
  84. package/src/javascripts/style-guide/components/view.js +4 -4
  85. package/src/javascripts/style-guide/state-helpers/index.js +5 -5
  86. package/src/javascripts/style-guide/states.js +67 -7
  87. package/src/javascripts/style-guide.ts +5 -0
  88. package/src/javascripts/ui/components/app-options/index.js +2 -4
  89. package/src/javascripts/ui/components/conversation/component-filter.js +1 -1
  90. package/src/javascripts/ui/components/conversation/conversation.js +11 -7
  91. package/src/javascripts/ui/components/conversation/event/card-message.js +2 -2
  92. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +1 -1
  93. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +1 -1
  94. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +2 -2
  95. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +3 -3
  96. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +19 -15
  97. package/src/javascripts/ui/components/conversation/event/cta.js +2 -2
  98. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +44 -5
  100. package/src/javascripts/ui/components/conversation/event/event-participant.js +2 -2
  101. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +2 -2
  102. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  103. package/src/javascripts/ui/components/conversation/event/image.js +6 -8
  104. package/src/javascripts/ui/components/conversation/event/participant.js +2 -2
  105. package/src/javascripts/ui/components/conversation/event/splash.js +4 -4
  106. package/src/javascripts/ui/components/conversation/event/text.js +2 -2
  107. package/src/javascripts/ui/components/conversation/event/translation.js +3 -3
  108. package/src/javascripts/ui/components/conversation/event/upload.js +3 -3
  109. package/src/javascripts/ui/components/conversation/event/video.js +2 -2
  110. package/src/javascripts/ui/components/conversation/message-container.js +4 -26
  111. package/src/javascripts/ui/components/core/seamly-api-context.js +1 -1
  112. package/src/javascripts/ui/components/core/seamly-core.js +15 -14
  113. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +98 -92
  114. package/src/javascripts/ui/components/core/seamly-file-upload.js +20 -24
  115. package/src/javascripts/ui/components/core/seamly-initializer.js +1 -1
  116. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +5 -4
  117. package/src/javascripts/ui/components/core/seamly-new-notifications.js +2 -2
  118. package/src/javascripts/ui/components/core/seamly-read-state.js +10 -17
  119. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +3 -3
  120. package/src/javascripts/ui/components/entry/entry-container.js +4 -6
  121. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  122. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -2
  123. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +6 -10
  124. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +2 -2
  125. package/src/javascripts/ui/components/entry/upload/index.js +10 -9
  126. package/src/javascripts/ui/components/entry/upload-toggle.js +2 -2
  127. package/src/javascripts/ui/components/faq/faq.js +9 -7
  128. package/src/javascripts/ui/components/form-controls/file-input.js +1 -1
  129. package/src/javascripts/ui/components/form-controls/form.js +1 -1
  130. package/src/javascripts/ui/components/form-controls/input.js +1 -1
  131. package/src/javascripts/ui/components/form-controls/select.js +1 -1
  132. package/src/javascripts/ui/components/layout/agent-info.js +4 -4
  133. package/src/javascripts/ui/components/layout/chat-frame.js +3 -3
  134. package/src/javascripts/ui/components/layout/chat.js +11 -12
  135. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +10 -9
  136. package/src/javascripts/ui/components/layout/header.js +1 -1
  137. package/src/javascripts/ui/components/layout/interrupt.js +23 -24
  138. package/src/javascripts/ui/components/layout/pre-chat-messages.js +11 -11
  139. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  140. package/src/javascripts/ui/components/options/options-button.js +14 -10
  141. package/src/javascripts/ui/components/options/transcript/index.js +2 -2
  142. package/src/javascripts/ui/components/options/transcript/transcript-form.js +1 -1
  143. package/src/javascripts/ui/components/suggestions/index.js +14 -10
  144. package/src/javascripts/ui/components/view/deprecated-view.js +19 -16
  145. package/src/javascripts/ui/components/view/index.js +12 -12
  146. package/src/javascripts/ui/components/view/inline-view.js +2 -2
  147. package/src/javascripts/ui/components/view/window-view/collapse-button.js +3 -3
  148. package/src/javascripts/ui/components/view/window-view/index.js +13 -13
  149. package/src/javascripts/ui/components/view/window-view/window-open-button.js +13 -13
  150. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +1 -1
  151. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +1 -1
  152. package/src/javascripts/ui/components/widgets/lightbox.js +2 -2
  153. package/src/javascripts/ui/components/widgets/upload-progress.js +1 -1
  154. package/src/javascripts/ui/hooks/component-helper-hooks.js +1 -1
  155. package/src/javascripts/ui/hooks/file-upload-hooks.js +4 -6
  156. package/src/javascripts/ui/hooks/focus-helper-hooks.js +14 -12
  157. package/src/javascripts/ui/hooks/live-region-hooks.js +2 -0
  158. package/src/javascripts/ui/hooks/seamly-api-hooks.js +8 -3
  159. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +28 -25
  160. package/src/javascripts/ui/hooks/seamly-hooks.js +25 -25
  161. package/src/javascripts/ui/hooks/seamly-option-hooks.js +17 -19
  162. package/src/javascripts/ui/hooks/seamly-state-hooks.js +20 -13
  163. package/src/javascripts/ui/hooks/use-seamly-chat.js +15 -25
  164. package/src/javascripts/ui/hooks/use-seamly-commands.js +46 -46
  165. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +22 -24
  166. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +8 -9
  167. package/src/javascripts/ui/hooks/use-single-file-upload.js +4 -6
  168. package/src/javascripts/ui/hooks/utility-hooks.js +4 -4
  169. package/src/javascripts/ui/utils/form-utils.js +0 -145
  170. package/src/javascripts/ui/utils/general-utils.js +3 -4
  171. package/src/javascripts/ui/utils/seamly-utils.ts +73 -0
  172. package/src/stylesheets/5-components/_message-carousel.scss +10 -8
  173. package/webpack/config.common.js +16 -0
  174. package/webpack/config.dev.js +1 -0
  175. package/webpack/config.package.js +26 -5
  176. package/webpack/defaults.js +7 -2
  177. package/webpack/parts/babel-loader-browser-plugins.js +1 -0
  178. package/webpack/parts/dev-server.js +4 -3
  179. package/CHANGELOG.md +0 -791
  180. package/src/javascripts/domains/app/actions.js +0 -112
  181. package/src/javascripts/domains/app/index.js +0 -7
  182. package/src/javascripts/domains/app/reducer.js +0 -16
  183. package/src/javascripts/domains/app/selectors.js +0 -8
  184. package/src/javascripts/domains/app/utils.js +0 -4
  185. package/src/javascripts/domains/config/actions.js +0 -7
  186. package/src/javascripts/domains/config/hooks.js +0 -23
  187. package/src/javascripts/domains/config/index.js +0 -7
  188. package/src/javascripts/domains/config/reducer.js +0 -79
  189. package/src/javascripts/domains/config/selectors.js +0 -23
  190. package/src/javascripts/domains/config/utils.js +0 -4
  191. package/src/javascripts/domains/forms/actions.js +0 -21
  192. package/src/javascripts/domains/forms/context.js +0 -6
  193. package/src/javascripts/domains/forms/index.js +0 -8
  194. package/src/javascripts/domains/forms/reducer.js +0 -84
  195. package/src/javascripts/domains/forms/utils.js +0 -20
  196. package/src/javascripts/domains/i18n/actions.js +0 -20
  197. package/src/javascripts/domains/i18n/index.js +0 -7
  198. package/src/javascripts/domains/i18n/selectors.js +0 -15
  199. package/src/javascripts/domains/i18n/utils.js +0 -4
  200. package/src/javascripts/domains/interrupt/actions.js +0 -4
  201. package/src/javascripts/domains/interrupt/index.js +0 -9
  202. package/src/javascripts/domains/interrupt/reducer.js +0 -22
  203. package/src/javascripts/domains/interrupt/selectors.js +0 -6
  204. package/src/javascripts/domains/interrupt/utils.js +0 -4
  205. package/src/javascripts/domains/options/index.js +0 -1
  206. package/src/javascripts/domains/redux/context.js +0 -6
  207. package/src/javascripts/domains/redux/create-redux-store.js +0 -21
  208. package/src/javascripts/domains/redux/hooks.js +0 -80
  209. package/src/javascripts/domains/redux/index.js +0 -19
  210. package/src/javascripts/domains/redux/provider.js +0 -5
  211. package/src/javascripts/domains/redux/utils.js +0 -12
  212. package/src/javascripts/domains/store/index.js +0 -46
  213. package/src/javascripts/domains/store/state-reducer.js +0 -56
  214. package/src/javascripts/domains/translations/actions.js +0 -11
  215. package/src/javascripts/domains/translations/hooks.js +0 -103
  216. package/src/javascripts/domains/translations/index.js +0 -10
  217. package/src/javascripts/domains/translations/reducer.js +0 -69
  218. package/src/javascripts/domains/translations/selectors.js +0 -16
  219. package/src/javascripts/domains/translations/utils.js +0 -4
  220. package/src/javascripts/domains/visibility/index.js +0 -8
  221. package/src/javascripts/domains/visibility/reducer.js +0 -24
  222. package/src/javascripts/index.js +0 -153
  223. package/src/javascripts/lib/redux-helpers/index.js +0 -99
  224. package/src/javascripts/style-guide.js +0 -5
  225. package/src/javascripts/ui/hooks/use-seamly-dispatch.js +0 -3
  226. package/src/javascripts/ui/utils/seamly-utils.js +0 -832
@@ -0,0 +1,642 @@
1
+ import { createSlice, PayloadAction } from '@reduxjs/toolkit'
2
+ import { initializeApp, resetApp } from 'domains/app/actions'
3
+ import { initializeConfig } from 'domains/config/actions'
4
+ import {
5
+ ChannelEvent,
6
+ EntryMeta,
7
+ HistoryEvents,
8
+ HistoryResponse,
9
+ MessageParticipant,
10
+ ParticipantInfo,
11
+ ServiceInfo,
12
+ StoreState,
13
+ } from 'domains/store/store.types'
14
+ import { randomId } from 'lib/id'
15
+ import { getTimeFromSeconds } from 'ui/utils/general-utils'
16
+ import {
17
+ entryTypes,
18
+ eventTypes,
19
+ featureKeys,
20
+ payloadTypes,
21
+ readStates,
22
+ } from 'ui/utils/seamly-utils'
23
+
24
+ export const isUnreadMessage = ({ type, payload }: ChannelEvent) =>
25
+ (type === eventTypes.message && !payload.fromClient) ||
26
+ (type === eventTypes.info && payload.type === payloadTypes.text)
27
+
28
+ export const orderHistory = (events: ChannelEvent[]) => {
29
+ return events.sort(
30
+ (
31
+ { payload: { occurredAt: occurredAtA } },
32
+ { payload: { occurredAt: occurredAtB } },
33
+ ) => occurredAtA - occurredAtB,
34
+ )
35
+ }
36
+
37
+ export const mergeHistory = (
38
+ stateEvents: ChannelEvent[],
39
+ historyEvents: HistoryEvents,
40
+ ) => {
41
+ const newStateEvents = stateEvents.filter(
42
+ (stateEvent) =>
43
+ // Deduplicate the event streams, giving events in historyEvents
44
+ // precedence so the server is able to push changes to events.
45
+ !historyEvents.some(
46
+ (historyEvent) => historyEvent.payload.id === stateEvent.payload.id,
47
+ ),
48
+ )
49
+
50
+ const newHistoryEvents = historyEvents
51
+ .filter(
52
+ (historyEvent) =>
53
+ // Remove all non displayable participant messages
54
+ !(
55
+ historyEvent.type === 'participant' &&
56
+ !historyEvent.payload.participant.introduction
57
+ ),
58
+ )
59
+ // Reverse is done here because the server sends the history in the order
60
+ // newest to oldest. In the case of exactly the same occurredAt timestamps
61
+ // these messages will be shown in the wrong order if not reversed. For
62
+ // the normal merging logic there is no added effect.
63
+ .reverse()
64
+
65
+ return orderHistory([...newHistoryEvents, ...newStateEvents])
66
+ }
67
+
68
+ const participantReducer = (participantInfo, action) => {
69
+ // TODO: a) Styleguide only! b) Should be removed after styleguide overhaul.
70
+ if (!participantInfo) {
71
+ return {
72
+ participants: {},
73
+ currentAgent: '',
74
+ }
75
+ }
76
+
77
+ const { participants } = participantInfo || { participants: {} }
78
+ const { id, avatar, name, introduction } = action.participant
79
+ const oldParticipant = participants[id]
80
+
81
+ const newParticipants = {
82
+ ...participants,
83
+ [id]: oldParticipant
84
+ ? {
85
+ ...oldParticipant,
86
+ ...(avatar ? { avatar } : {}),
87
+ ...(name ? { name } : {}),
88
+ ...(introduction ? { introduction } : {}),
89
+ }
90
+ : action.participant,
91
+ }
92
+
93
+ return {
94
+ ...participantInfo,
95
+ participants: newParticipants,
96
+ currentAgent:
97
+ participantInfo.currentAgent !== id && !action.fromClient
98
+ ? id
99
+ : participantInfo.currentAgent,
100
+ }
101
+ }
102
+
103
+ const calculateNewEntryMeta = (
104
+ entryMeta: EntryMeta,
105
+ channelEvent: ChannelEvent,
106
+ ) => {
107
+ const entry =
108
+ channelEvent.type === 'message' ? channelEvent?.payload.entry : {}
109
+
110
+ const { blockAutoEntrySwitch } = entryMeta
111
+
112
+ if (!entry) {
113
+ return { ...entryMeta, optionsOverride: {} }
114
+ }
115
+
116
+ const { type, options } = entry
117
+
118
+ let newActive = entryMeta.active
119
+
120
+ if (!blockAutoEntrySwitch && type !== entryMeta.userSelected) {
121
+ newActive = type
122
+ }
123
+
124
+ return {
125
+ ...entryMeta,
126
+ active: newActive,
127
+ optionsOverride: {
128
+ ...entryMeta.optionsOverride,
129
+ [type]: options || {},
130
+ },
131
+ }
132
+ }
133
+
134
+ export const initialStoreState: StoreState = {
135
+ events: [],
136
+ initialState: {
137
+ userResponded: false,
138
+ },
139
+ unreadEvents: 0,
140
+ userHasResponded: false,
141
+ loadedImageEventIds: [],
142
+ isLoading: false,
143
+ idleDetachCountdown: {
144
+ hasCountdown: false,
145
+ isActive: false,
146
+ remaining: undefined,
147
+ wasStopped: undefined,
148
+ count: undefined,
149
+ timer: undefined,
150
+ },
151
+ resumeConversationPrompt: false,
152
+ serviceInfo: {
153
+ activeServiceSessionId: '',
154
+ },
155
+ participantInfo: {
156
+ participants: {},
157
+ currentAgent: '',
158
+ },
159
+ headerTitles: {
160
+ title: null,
161
+ subTitle: '',
162
+ },
163
+ historyLoaded: false,
164
+ skiplinkTargetId: randomId(),
165
+ optionsButtonId: randomId(),
166
+ headerCollapseButtonId: randomId(),
167
+ serviceData: {},
168
+ options: {
169
+ features: {},
170
+ panelActive: false,
171
+ optionActive: '',
172
+ userSelectedOptions: {},
173
+ },
174
+ showFileUpload: false,
175
+ currentUploads: [],
176
+ entryMeta: {
177
+ default: entryTypes.text,
178
+ active: entryTypes.text,
179
+ userSelected: null,
180
+ blockAutoEntrySwitch: false,
181
+ options: {},
182
+ optionsOverride: {},
183
+ },
184
+ seamlyContainerElement: null,
185
+ }
186
+
187
+ export const storeSlice = createSlice({
188
+ name: 'store',
189
+ initialState: initialStoreState,
190
+ reducers: {
191
+ addEvent: (state, action: PayloadAction<ChannelEvent>) => {
192
+ const { type: eventType, payload } = action.payload
193
+
194
+ const accountHasUploads = state.options.features.hasOwnProperty(
195
+ featureKeys.uploads,
196
+ )
197
+
198
+ let newOptions = { ...state.options }
199
+
200
+ // This enabled override of the service enabled value for uploads.
201
+ // If a message is sent with entry of type upload it will temporarily
202
+ // override service value and enable uploads.
203
+ if (
204
+ accountHasUploads &&
205
+ (eventType === eventTypes.message ||
206
+ eventType === eventTypes.participant) &&
207
+ !payload.fromClient
208
+ ) {
209
+ const entryType = eventType === 'message' ? payload.entry?.type : {}
210
+ newOptions = {
211
+ ...newOptions,
212
+ features: {
213
+ ...newOptions.features,
214
+ uploads: {
215
+ ...newOptions.features?.uploads,
216
+ enabledFromEntry: entryType === entryTypes.upload,
217
+ },
218
+ },
219
+ }
220
+ }
221
+
222
+ const incrementUnread = isUnreadMessage(action.payload)
223
+
224
+ // We check for duplicated and ignore them as in some error of the websocket
225
+ // a duplicate join can be active for a while until the server connection
226
+ // times out.
227
+ const eventExists = state.events.find((e) => e.payload.id === payload.id)
228
+ if (eventExists) {
229
+ return
230
+ }
231
+
232
+ const newEntryMeta = calculateNewEntryMeta(
233
+ state.entryMeta,
234
+ action.payload,
235
+ )
236
+
237
+ state.entryMeta =
238
+ !accountHasUploads && newEntryMeta.active === entryTypes.upload
239
+ ? { ...state.entryMeta }
240
+ : newEntryMeta
241
+
242
+ state.options = newOptions
243
+
244
+ if (incrementUnread) {
245
+ state.unreadEvents += 1
246
+ action.payload.payload.messageStatus = payload.fromClient
247
+ ? readStates.read
248
+ : readStates.received
249
+ }
250
+
251
+ action.payload.payload.key = randomId()
252
+ state.events.push(action.payload)
253
+ },
254
+ ackEvent: (state, { payload }) => {
255
+ // If any ACKs are sent without transactionID the conversation crashes.
256
+ // Ensure that this edge case is handled gracefully.
257
+ if (!payload.transactionId) {
258
+ console.warn('ACK received without transaction ID.')
259
+ return
260
+ }
261
+
262
+ const matchedEvent = state.events.find(
263
+ (m) => m.payload.transactionId === payload.transactionId,
264
+ )
265
+
266
+ const { id, occurredAt } = payload
267
+ if (matchedEvent) {
268
+ state.events = orderHistory(
269
+ //@ts-ignore
270
+ state.events.map((m) =>
271
+ m.payload.id === matchedEvent.payload.id
272
+ ? {
273
+ ...m,
274
+ payload: { ...m.payload, id, occurredAt },
275
+ }
276
+ : m,
277
+ ),
278
+ )
279
+ }
280
+ },
281
+ clearEvents: (state) => {
282
+ state.unreadEvents = 0
283
+ state.loadedImageEventIds = []
284
+ state.events = []
285
+ },
286
+ setEventsRead: (state, { payload }) => {
287
+ state.unreadEvents = 0
288
+ state.events.forEach((event) => {
289
+ if (payload.indexOf(event.payload.id) !== -1) {
290
+ event.payload = {
291
+ ...event.payload,
292
+ ...(event.payload.messageStatus === readStates.received && {
293
+ messageStatus: readStates.read,
294
+ }),
295
+ }
296
+ }
297
+ return event
298
+ })
299
+ },
300
+ setLoadedImageEventIds: (state, { payload }) => {
301
+ state.loadedImageEventIds.push(payload)
302
+ },
303
+ setHistory: (
304
+ state,
305
+ {
306
+ payload: {
307
+ events: history,
308
+ participants,
309
+ activeServiceSessionId,
310
+ activeServiceSettings = {},
311
+ serviceData,
312
+ resumeConversationPrompt,
313
+ },
314
+ }: PayloadAction<HistoryResponse>,
315
+ ) => {
316
+ const events = mergeHistory(state.events, history)
317
+
318
+ const mergedParticipants = {
319
+ ...state.participantInfo.participants,
320
+ ...participants,
321
+ }
322
+
323
+ const lastParticipantEvent = events
324
+ .slice()
325
+ .reverse()
326
+ .find(
327
+ (m) =>
328
+ (m.type === 'message' || m.type === 'participant') &&
329
+ !m.payload.fromClient,
330
+ )
331
+
332
+ let lastParticipantId = null
333
+
334
+ if (lastParticipantEvent) {
335
+ if (lastParticipantEvent.type === 'message') {
336
+ lastParticipantId = lastParticipantEvent.payload.participant
337
+ }
338
+ if (lastParticipantEvent.type === 'participant') {
339
+ lastParticipantId = lastParticipantEvent.payload.participant.id
340
+ }
341
+ }
342
+
343
+ const { entry } = activeServiceSettings
344
+ const { upload } = entry.options
345
+
346
+ const historyNewEntryMeta = calculateNewEntryMeta(
347
+ {
348
+ ...state.entryMeta,
349
+ ...entry,
350
+ active: entry.default || payloadTypes.text,
351
+ options: { ...(entry && entry.options ? entry.options : {}) },
352
+ },
353
+ events[events.length - 1],
354
+ )
355
+
356
+ let newFeatures = { ...state.options.features }
357
+
358
+ const newFeaturesHasUpload = newFeatures.hasOwnProperty(
359
+ featureKeys.uploads,
360
+ )
361
+
362
+ // Only set uploads if it was initialised by the account config.
363
+ if (newFeaturesHasUpload && lastParticipantEvent.type === 'message') {
364
+ const { payload: lastParticipantEventPayload } = lastParticipantEvent
365
+ const entryType = lastParticipantEventPayload?.entry?.type || {}
366
+ newFeatures = {
367
+ ...newFeatures,
368
+ uploads: {
369
+ enabled: !!(upload && upload.enabled),
370
+ enabledFromEntry: entryType === entryTypes.upload,
371
+ },
372
+ }
373
+ }
374
+
375
+ state.unreadEvents = events.filter(
376
+ (event) =>
377
+ event.type === 'message' &&
378
+ event.payload.messageStatus === readStates.received,
379
+ ).length
380
+ state.events = events.filter(
381
+ (e) => e.type !== 'participant' || !!e.payload.participant.introduction,
382
+ )
383
+ state.participantInfo = {
384
+ ...state.participantInfo,
385
+ ...(lastParticipantId
386
+ ? participantReducer(state.participantInfo, {
387
+ participant: mergedParticipants[lastParticipantId],
388
+ })
389
+ : {}),
390
+ participants: mergedParticipants,
391
+ }
392
+ state.historyLoaded = true
393
+ state.serviceInfo = {
394
+ ...state.serviceInfo,
395
+ activeServiceSessionId,
396
+ }
397
+ state.serviceData = serviceData || {}
398
+ state.options = {
399
+ ...state.options,
400
+ features: newFeatures || {},
401
+ }
402
+ state.entryMeta =
403
+ !newFeaturesHasUpload &&
404
+ historyNewEntryMeta.active === entryTypes.upload
405
+ ? { ...state.entryMeta }
406
+ : historyNewEntryMeta
407
+ state.resumeConversationPrompt = resumeConversationPrompt || false
408
+
409
+ if (lastParticipantId) {
410
+ state.headerTitles.subTitle = mergedParticipants[lastParticipantId].name
411
+ }
412
+ },
413
+ resetHistoryLoadedFlag: (state) => {
414
+ state.historyLoaded = false
415
+ },
416
+ setIsLoading: (state, { payload }) => {
417
+ state.isLoading = payload
418
+ },
419
+ initIdleDetachCountdown: (state, { payload }) => {
420
+ const { delaySeconds, delayTime } = payload
421
+
422
+ state.idleDetachCountdown = {
423
+ hasCountdown: true,
424
+ isActive: true,
425
+ wasStopped: false,
426
+ count: delaySeconds,
427
+ remaining: delaySeconds,
428
+ timer: delayTime,
429
+ }
430
+ },
431
+ decrementIdleDetachCountdownCounter: (state) => {
432
+ const { idleDetachCountdown } = state
433
+
434
+ const { remaining: prevRemaining } = idleDetachCountdown
435
+
436
+ const remaining = prevRemaining - 1
437
+
438
+ state.idleDetachCountdown.remaining = remaining
439
+ state.idleDetachCountdown.timer = getTimeFromSeconds(remaining)
440
+ },
441
+ stopIdleDetachCountdownCounter: (state) => {
442
+ state.idleDetachCountdown.isActive = false
443
+ state.idleDetachCountdown.wasStopped = true
444
+ },
445
+ clearIdleDetachCountdown: (state) => {
446
+ state.idleDetachCountdown.hasCountdown = false
447
+ state.idleDetachCountdown.isActive = false
448
+ },
449
+ initResumeConversationPrompt: (state) => {
450
+ state.resumeConversationPrompt = true
451
+ },
452
+ clearResumeConversationPrompt: (state) => {
453
+ state.resumeConversationPrompt = false
454
+ },
455
+ setParticipant: (state, { payload }: PayloadAction<MessageParticipant>) => {
456
+ state.participantInfo = participantReducer(state.participantInfo, {
457
+ participant: payload.participant,
458
+ fromClient: payload.fromClient,
459
+ })
460
+ },
461
+ setActiveService: (
462
+ state,
463
+ { payload }: PayloadAction<ServiceInfo['activeServiceSessionId']>,
464
+ ) => {
465
+ if (state.serviceInfo.activeServiceSessionId !== payload) {
466
+ state.serviceInfo.activeServiceSessionId = payload
467
+ }
468
+ },
469
+ setHeaderTitle: (state, { payload }) => {
470
+ state.headerTitles.title = payload
471
+ },
472
+ setHeaderSubTitle: (state, { payload }) => {
473
+ state.headerTitles.subTitle = payload
474
+ },
475
+ setInitialState: (state, { payload }) => {
476
+ state.initialState = payload.initialState
477
+ state.unreadEvents = initialStoreState.unreadEvents
478
+ },
479
+ setServiceDataItem: (state, { payload }) => {
480
+ state.serviceData[payload.type] = payload
481
+ },
482
+ setFeatures: (state, { payload }) => {
483
+ if (!payload.features) {
484
+ return
485
+ }
486
+ state.options.features = payload.features
487
+ },
488
+ setFeatureEnabledState: (state, { payload }) => {
489
+ if (!state.options.features.hasOwnProperty(payload.key)) {
490
+ return
491
+ }
492
+ state.options.features[payload.key].enabled = payload.enabled
493
+ },
494
+ clearFeatures: (state) => {
495
+ // case CLEAR_FEATURES:
496
+ state.options.features = {}
497
+ },
498
+ showOption: (state, { payload }) => {
499
+ state.options.panelActive = true
500
+ state.options.optionActive = payload
501
+ },
502
+ hideOption: (state) => {
503
+ // case HIDE_OPTION:
504
+ state.options.panelActive = false
505
+ state.options.optionActive = ''
506
+ },
507
+ setUserSelectedOptions: (state, { payload }) => {
508
+ state.options.userSelectedOptions = payload
509
+ },
510
+ setUserSelectedOption: (state, { payload }) => {
511
+ const { option, value } = payload
512
+ state.options.userSelectedOptions[option] = value
513
+ },
514
+ setBlockAutoEntrySwitch: (state, { payload }) => {
515
+ state.entryMeta.blockAutoEntrySwitch = payload
516
+ },
517
+ setServiceEntryMetadata: (state, { payload }) => {
518
+ state.entryMeta.active = payload.default
519
+ state.entryMeta.options = payload.options || {}
520
+ state.entryMeta.optionsOverride = {}
521
+ },
522
+ setActiveEntryType: (state, { payload }) => {
523
+ state.entryMeta.active = payload
524
+ },
525
+ setUserEntryType: (state, { payload }) => {
526
+ state.entryMeta.userSelected = payload
527
+ },
528
+ registerUpload: (state, { payload }) => {
529
+ state.currentUploads.push({
530
+ id: payload.fileId,
531
+ name: payload.fileName,
532
+ progress: 1,
533
+ uploading: true,
534
+ complete: false,
535
+ error: '',
536
+ uploadHandle: payload.uploadHandle,
537
+ })
538
+ },
539
+ setUploadProgress: (state, { payload }) => {
540
+ state.currentUploads = state.currentUploads.map((fileUpload) => {
541
+ if (fileUpload.id === payload.fileId) {
542
+ return {
543
+ ...fileUpload,
544
+ progress: payload.progress,
545
+ uploading: payload.progress !== 100,
546
+ uploadHandle:
547
+ payload.progress === 100 ? null : fileUpload.uploadHandle,
548
+ }
549
+ }
550
+ return fileUpload
551
+ })
552
+ },
553
+ setUploadError: (state, { payload }) => {
554
+ state.currentUploads = state.currentUploads.map((fileUpload) => {
555
+ if (fileUpload.id === payload.fileId) {
556
+ return {
557
+ ...fileUpload,
558
+ error: payload.errorText,
559
+ progress: 0,
560
+ uploading: false,
561
+ uploadHandle: null,
562
+ }
563
+ }
564
+ return fileUpload
565
+ })
566
+ },
567
+ setUploadComplete: (state, { payload }) => {
568
+ state.currentUploads = state.currentUploads.map((fileUpload) => {
569
+ if (fileUpload.id === payload) {
570
+ return {
571
+ ...fileUpload,
572
+ complete: true,
573
+ }
574
+ }
575
+ return fileUpload
576
+ })
577
+ },
578
+ clearAllUploads: (state) => {
579
+ state.currentUploads = []
580
+ },
581
+ setSeamlyContainerElement: (state, { payload }) => {
582
+ state.seamlyContainerElement = payload
583
+ },
584
+ },
585
+ extraReducers: (builder) => {
586
+ builder
587
+ .addCase(resetApp.pending, () => initialStoreState)
588
+ .addCase(initializeConfig.fulfilled, (state, { payload }) => {
589
+ state.headerTitles.subTitle = payload.agentParticipant?.name
590
+
591
+ if (!payload.features) return
592
+
593
+ state.options.features = payload.features
594
+ })
595
+ .addCase(initializeApp.fulfilled, (state, { payload }) => {
596
+ if (!payload.initialState) return
597
+ state.initialState = payload.initialState
598
+ })
599
+ },
600
+ })
601
+
602
+ export const {
603
+ ackEvent,
604
+ addEvent,
605
+ clearAllUploads,
606
+ clearEvents,
607
+ clearFeatures,
608
+ clearIdleDetachCountdown,
609
+ clearResumeConversationPrompt,
610
+ decrementIdleDetachCountdownCounter,
611
+ hideOption,
612
+ initIdleDetachCountdown,
613
+ initResumeConversationPrompt,
614
+ registerUpload,
615
+ resetHistoryLoadedFlag,
616
+ setActiveEntryType,
617
+ setActiveService,
618
+ setBlockAutoEntrySwitch,
619
+ setEventsRead,
620
+ setFeatureEnabledState,
621
+ setFeatures,
622
+ setHeaderSubTitle,
623
+ setHeaderTitle,
624
+ setHistory,
625
+ setInitialState,
626
+ setIsLoading,
627
+ setLoadedImageEventIds,
628
+ setParticipant,
629
+ setSeamlyContainerElement,
630
+ setServiceDataItem,
631
+ setServiceEntryMetadata,
632
+ setUploadComplete,
633
+ setUploadError,
634
+ setUploadProgress,
635
+ setUserEntryType,
636
+ setUserSelectedOption,
637
+ setUserSelectedOptions,
638
+ showOption,
639
+ stopIdleDetachCountdownCounter,
640
+ } = storeSlice.actions
641
+
642
+ export default storeSlice.reducer