@seamly/web-ui 20.8.1 → 21.0.0

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 (208) hide show
  1. package/build/dist/lib/deprecated-view.js +1 -1
  2. package/build/dist/lib/index.debug.js +585 -584
  3. package/build/dist/lib/index.debug.min.js +1 -1
  4. package/build/dist/lib/index.debug.min.js.LICENSE.txt +110 -110
  5. package/build/dist/lib/index.js +20269 -26441
  6. package/build/dist/lib/index.min.js +1 -1
  7. package/build/dist/lib/index.min.js.LICENSE.txt +6 -1
  8. package/build/dist/lib/standalone.js +27728 -34583
  9. package/build/dist/lib/standalone.min.js +1 -1
  10. package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
  11. package/build/dist/lib/storage.js +6 -15
  12. package/build/dist/lib/style-guide.js +9660 -8970
  13. package/build/dist/lib/style-guide.min.js +1 -1
  14. package/build/dist/lib/styles-default-implementation.js +1 -1
  15. package/build/dist/lib/styles.js +1 -1
  16. package/build/dist/lib/utils.js +85 -3
  17. package/build/dist/lib/utils.min.js +1 -1
  18. package/package.json +54 -52
  19. package/src/icons/icon_check-16.svg +14 -0
  20. package/src/icons/icon_check-32.svg +14 -0
  21. package/src/javascripts/api/conversation-connector.ts +149 -0
  22. package/src/javascripts/api/errors/seamly-base-error.js +19 -0
  23. package/src/javascripts/api/errors/seamly-unavailable-error.js +5 -7
  24. package/src/javascripts/api/{index.js → index.ts} +163 -116
  25. package/src/javascripts/config.types.ts +5 -4
  26. package/src/javascripts/domains/app/actions.ts +47 -46
  27. package/src/javascripts/domains/app/hooks.js +1 -1
  28. package/src/javascripts/domains/config/actions.ts +2 -8
  29. package/src/javascripts/domains/config/hooks.ts +1 -1
  30. package/src/javascripts/domains/config/selectors.ts +6 -6
  31. package/src/javascripts/domains/config/slice.ts +3 -3
  32. package/src/javascripts/domains/errors/index.ts +66 -0
  33. package/src/javascripts/domains/forms/context.ts +1 -1
  34. package/src/javascripts/domains/forms/forms.types.ts +3 -3
  35. package/src/javascripts/domains/forms/hooks.ts +10 -10
  36. package/src/javascripts/domains/forms/provider.tsx +9 -9
  37. package/src/javascripts/domains/i18n/actions.ts +11 -5
  38. package/src/javascripts/domains/i18n/hooks.ts +11 -8
  39. package/src/javascripts/domains/i18n/selectors.ts +10 -4
  40. package/src/javascripts/domains/i18n/slice.ts +0 -1
  41. package/src/javascripts/domains/interrupt/hooks.ts +1 -1
  42. package/src/javascripts/domains/interrupt/middleware.ts +1 -1
  43. package/src/javascripts/domains/store/index.ts +1 -1
  44. package/src/javascripts/domains/store/selectors.ts +16 -0
  45. package/src/javascripts/domains/store/slice.ts +47 -41
  46. package/src/javascripts/domains/store/store.types.ts +38 -10
  47. package/src/javascripts/domains/translations/components/{options-button.js → options-button.tsx} +30 -20
  48. package/src/javascripts/domains/translations/components/options-dialog/index.tsx +33 -0
  49. package/src/javascripts/domains/translations/components/options-dialog/translation-option.tsx +37 -0
  50. package/src/javascripts/domains/translations/components/options-dialog/translation-options.tsx +85 -0
  51. package/src/javascripts/domains/translations/components/translation-status.tsx +15 -0
  52. package/src/javascripts/domains/translations/hooks.ts +77 -11
  53. package/src/javascripts/domains/translations/slice.ts +20 -9
  54. package/src/javascripts/domains/translations/translations.types.ts +4 -2
  55. package/src/javascripts/domains/visibility/actions.ts +6 -10
  56. package/src/javascripts/domains/visibility/hooks.ts +33 -14
  57. package/src/javascripts/domains/visibility/selectors.ts +3 -2
  58. package/src/javascripts/domains/visibility/slice.ts +2 -6
  59. package/src/javascripts/index.ts +19 -21
  60. package/src/javascripts/lib/engine/{index.js → index.tsx} +25 -7
  61. package/src/javascripts/lib/url-helpers.ts +112 -0
  62. package/src/javascripts/package/utils.js +5 -2
  63. package/src/javascripts/schema.ts +28 -0
  64. package/src/javascripts/style-guide/components/app.js +16 -12
  65. package/src/javascripts/style-guide/components/links.js +6 -6
  66. package/src/javascripts/style-guide/components/static-core.js +6 -3
  67. package/src/javascripts/style-guide/components/view.js +1 -1
  68. package/src/javascripts/style-guide/states.js +129 -31
  69. package/src/javascripts/style-guide/style-guide-engine.js +1 -1
  70. package/src/javascripts/ui/components/app-options/index.js +25 -6
  71. package/src/javascripts/ui/components/chat-app.js +1 -1
  72. package/src/javascripts/ui/components/chat-status/chat-status-action.tsx +30 -0
  73. package/src/javascripts/ui/components/chat-status/index.tsx +61 -0
  74. package/src/javascripts/ui/components/conversation/component-filter.js +9 -9
  75. package/src/javascripts/ui/components/conversation/{conversation.js → conversation.tsx} +32 -41
  76. package/src/javascripts/ui/components/conversation/event/card-component.js +2 -2
  77. package/src/javascripts/ui/components/conversation/event/card-message.js +1 -1
  78. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +2 -2
  79. package/src/javascripts/ui/components/conversation/event/carousel-component/index.js +4 -4
  80. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +2 -2
  81. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +1 -1
  82. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-context.ts +12 -0
  83. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx +46 -0
  84. package/src/javascripts/ui/components/conversation/event/chat-scroll/unread-messages-button.tsx +27 -0
  85. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +12 -8
  86. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +6 -6
  87. package/src/javascripts/ui/components/conversation/event/cta.js +2 -2
  88. package/src/javascripts/ui/components/conversation/event/divider/index.js +0 -1
  89. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  90. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +17 -22
  91. package/src/javascripts/ui/components/conversation/event/divider/variants/time-indicator.js +2 -2
  92. package/src/javascripts/ui/components/conversation/event/event-participant.js +1 -1
  93. package/src/javascripts/ui/components/conversation/event/event.tsx +66 -0
  94. package/src/javascripts/ui/components/conversation/event/hooks/use-event-link-click-handler.js +1 -1
  95. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +1 -1
  96. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  97. package/src/javascripts/ui/components/conversation/event/image.js +2 -2
  98. package/src/javascripts/ui/components/conversation/event/splash.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/translation.js +1 -1
  100. package/src/javascripts/ui/components/conversation/event/upload.js +2 -2
  101. package/src/javascripts/ui/components/conversation/event/video.js +2 -2
  102. package/src/javascripts/ui/components/conversation/event-divider.js +1 -1
  103. package/src/javascripts/ui/components/conversation/message-container.js +1 -1
  104. package/src/javascripts/ui/components/conversation/use-chat-scroll.ts +108 -0
  105. package/src/javascripts/ui/components/core/{seamly-activity-monitor.js → seamly-activity-monitor.tsx} +12 -5
  106. package/src/javascripts/ui/components/core/seamly-api-context.ts +7 -0
  107. package/src/javascripts/ui/components/core/seamly-chat.tsx +8 -0
  108. package/src/javascripts/ui/components/core/{seamly-core.js → seamly-core.tsx} +27 -14
  109. package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +340 -0
  110. package/src/javascripts/ui/components/core/seamly-file-upload.js +2 -2
  111. package/src/javascripts/ui/components/core/seamly-idle-detach-counter.js +1 -1
  112. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +24 -11
  113. package/src/javascripts/ui/components/core/seamly-live-region.js +4 -4
  114. package/src/javascripts/ui/components/core/seamly-new-notifications.js +3 -3
  115. package/src/javascripts/ui/components/core/seamly-read-state.js +2 -33
  116. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +4 -4
  117. package/src/javascripts/ui/components/entry/entry-container.js +8 -8
  118. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  119. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -3
  120. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +4 -4
  121. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +3 -3
  122. package/src/javascripts/ui/components/entry/upload/index.js +5 -5
  123. package/src/javascripts/ui/components/entry/upload-toggle.js +6 -6
  124. package/src/javascripts/ui/components/faq/faq.js +14 -14
  125. package/src/javascripts/ui/components/form-controls/error.js +2 -2
  126. package/src/javascripts/ui/components/form-controls/file-input.js +3 -3
  127. package/src/javascripts/ui/components/layout/agent-info.js +3 -3
  128. package/src/javascripts/ui/components/layout/chat-frame.js +20 -12
  129. package/src/javascripts/ui/components/layout/chat.js +5 -5
  130. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +6 -6
  131. package/src/javascripts/ui/components/layout/deprecated-chat-frame.js +34 -0
  132. package/src/javascripts/ui/components/layout/header.js +2 -2
  133. package/src/javascripts/ui/components/layout/icon.js +11 -9
  134. package/src/javascripts/ui/components/layout/interrupt.js +7 -5
  135. package/src/javascripts/ui/components/layout/pre-chat-messages.js +1 -1
  136. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  137. package/src/javascripts/ui/components/options/options-button.js +5 -5
  138. package/src/javascripts/ui/components/options/{options-frame.js → options-frame.tsx} +52 -18
  139. package/src/javascripts/ui/components/options/transcript/index.js +9 -10
  140. package/src/javascripts/ui/components/options/transcript/transcript-form.js +2 -2
  141. package/src/javascripts/ui/components/suggestions/index.js +8 -8
  142. package/src/javascripts/ui/components/suggestions/suggestions-item.js +1 -1
  143. package/src/javascripts/{domains/translations/components/chat-status.js → ui/components/translation-chat-status/index.tsx} +13 -14
  144. package/src/javascripts/ui/components/translation-proposal/index.tsx +36 -0
  145. package/src/javascripts/ui/components/view/app-view.js +2 -7
  146. package/src/javascripts/ui/components/view/deprecated-view.js +8 -10
  147. package/src/javascripts/ui/components/view/index.js +6 -6
  148. package/src/javascripts/ui/components/view/inline-view.js +4 -8
  149. package/src/javascripts/ui/components/view/window-view/collapse-button.js +2 -2
  150. package/src/javascripts/ui/components/view/window-view/index.js +11 -17
  151. package/src/javascripts/ui/components/view/window-view/window-open-button.js +6 -6
  152. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +3 -3
  153. package/src/javascripts/ui/components/warnings/prompt.js +1 -1
  154. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +4 -4
  155. package/src/javascripts/ui/components/widgets/in-out-transition.js +20 -18
  156. package/src/javascripts/ui/components/widgets/lightbox.js +3 -3
  157. package/src/javascripts/ui/components/widgets/modal.js +2 -2
  158. package/src/javascripts/ui/components/widgets/upload-progress.js +2 -2
  159. package/src/javascripts/ui/hooks/file-upload-hooks.js +1 -1
  160. package/src/javascripts/ui/hooks/focus-helper-hooks.js +1 -1
  161. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +6 -6
  162. package/src/javascripts/ui/hooks/seamly-hooks.js +11 -10
  163. package/src/javascripts/ui/hooks/seamly-option-hooks.js +6 -6
  164. package/src/javascripts/ui/hooks/{seamly-state-hooks.js → seamly-state-hooks.ts} +9 -6
  165. package/src/javascripts/ui/hooks/use-click-outside.ts +29 -0
  166. package/src/javascripts/ui/hooks/use-event-component-mapping.js +11 -10
  167. package/src/javascripts/ui/hooks/use-interval.js +1 -1
  168. package/src/javascripts/ui/hooks/use-seamly-actions.ts +29 -29
  169. package/src/javascripts/ui/hooks/use-seamly-chat.js +13 -23
  170. package/src/javascripts/ui/hooks/use-seamly-commands.js +20 -15
  171. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +8 -8
  172. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +2 -2
  173. package/src/javascripts/ui/hooks/use-single-file-upload.js +1 -1
  174. package/src/javascripts/ui/hooks/utility-hooks.js +1 -1
  175. package/src/javascripts/ui/utils/general-utils.js +0 -23
  176. package/src/javascripts/ui/utils/seamly-utils.ts +10 -1
  177. package/src/javascripts/ui/utils/seamly-utils.types.ts +9 -0
  178. package/src/stylesheets/1-settings/_config.scss +1 -1
  179. package/src/stylesheets/3-chat/_chat.scss +23 -5
  180. package/src/stylesheets/5-components/_chat-status.scss +72 -16
  181. package/src/stylesheets/5-components/_conversation.scss +35 -1
  182. package/src/stylesheets/5-components/_disclaimer.scss +0 -5
  183. package/src/stylesheets/5-components/_options.scss +16 -2
  184. package/src/stylesheets/5-components/_translation-options.scss +39 -0
  185. package/src/stylesheets/6-default-implementation/_scrollbar.scss +1 -1
  186. package/src/stylesheets/7-deprecated/3-app/_app.scss +19 -4
  187. package/src/stylesheets/7-deprecated/5-components/_chat-status.scss +5 -0
  188. package/src/stylesheets/7-deprecated/5-components/_options.scss +1 -0
  189. package/src/stylesheets/7-deprecated/5-components/_translation-options.scss +39 -0
  190. package/src/stylesheets/deprecated-view.scss +1 -0
  191. package/src/stylesheets/styles.scss +1 -0
  192. package/webpack/config.common.js +4 -4
  193. package/webpack/config.package.js +10 -16
  194. package/webpack/config.site.js +4 -1
  195. package/webpack/config.test.js +2 -1
  196. package/build/dist/lib/deprecated-view.css +0 -1
  197. package/build/dist/lib/styles-default-implementation.css +0 -1
  198. package/build/dist/lib/styles.css +0 -1
  199. package/src/.DS_Store +0 -0
  200. package/src/javascripts/api/event-producer.js +0 -20
  201. package/src/javascripts/api/producer.js +0 -136
  202. package/src/javascripts/domains/errors/index.js +0 -37
  203. package/src/javascripts/domains/translations/components/options-dialog/form.js +0 -70
  204. package/src/javascripts/domains/translations/components/options-dialog/index.js +0 -87
  205. package/src/javascripts/ui/components/chat-status/index.js +0 -38
  206. package/src/javascripts/ui/components/conversation/event/event.js +0 -36
  207. package/src/javascripts/ui/components/core/seamly-api-context.js +0 -5
  208. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +0 -279
@@ -1,15 +1,13 @@
1
+ import SeamlyBaseError from './seamly-base-error'
2
+
1
3
  /**
2
4
  * This error is used to alert the user that there's a problem with the connection
3
5
  * when initialising the application because of a connection issue on either the server
4
6
  * or the client side.
5
7
  */
6
- export default class SeamlyUnavailableError extends Error {
7
- constructor(params) {
8
- super(params)
9
-
10
- if (Error.captureStackTrace) {
11
- Error.captureStackTrace(this, SeamlyUnavailableError)
12
- }
8
+ export default class SeamlyUnavailableError extends SeamlyBaseError {
9
+ constructor(originalError, ...params) {
10
+ super(originalError, ...params)
13
11
 
14
12
  this.name = 'SeamlyUnavailableError'
15
13
  this.langKey = 'errors.seamlyUnavailable'
@@ -1,22 +1,26 @@
1
- import xs from 'xstream'
1
+ import type { ApiConfig, Config, Context, LayoutMode } from 'config.types'
2
+ import { components, operations } from 'schema'
2
3
  import superagent from 'superagent'
3
4
  import { apiVersion } from 'config'
4
- import { objectStore } from 'lib/store/index'
5
+ import SeamlyConfigurationError from 'api/errors/seamly-configuration-error'
6
+ import SeamlyGeneralError from 'api/errors/seamly-general-error'
7
+ import SeamlySessionExpiredError from 'api/errors/seamly-session-expired-error'
8
+ import SeamlyUnauthorizedError from 'api/errors/seamly-unauthorized-error'
9
+ import { omit } from 'ui/utils/general-utils'
10
+ import type { ChannelEvent } from 'domains/store/store.types'
11
+ import debug from 'lib/debug'
5
12
  import { randomId } from 'lib/id'
13
+ import { objectStore } from 'lib/store/index'
6
14
  import sessionStorageProvider from 'lib/store/providers/session-storage'
7
- import debug from 'lib/debug'
8
- import { omit } from 'ui/utils/general-utils'
9
- import ConversationProducer from './producer'
10
- import EventProducer from './event-producer'
11
- import SeamlySessionExpiredError from './errors/seamly-session-expired-error'
12
- import SeamlyConfigurationError from './errors/seamly-configuration-error'
13
- import SeamlyGeneralError from './errors/seamly-general-error'
14
- import SeamlyUnauthorizedError from './errors/seamly-unauthorized-error'
15
+ import ConversationConnector from './conversation-connector'
16
+
17
+ declare let PACKAGE_NAME: string
18
+ declare let PACKAGE_VERSION: string
15
19
 
16
20
  const log = debug('seamly')
17
21
 
18
22
  const DOMAIN = 'api.seamly-app.com'
19
- const TRANSLATIONS_VERSION = 1
23
+ const TRANSLATIONS_VERSION = 2
20
24
 
21
25
  function buildPayload(command, payload) {
22
26
  if (command !== 'message') {
@@ -67,38 +71,76 @@ function getTimeZone() {
67
71
  }
68
72
  }
69
73
 
74
+ type UrlsType = components['schemas']['ConversationResponse']['_links'] &
75
+ operations['getAccountConfig']['responses']['201']['content']['application/json']['_links']
76
+
77
+ type InitialConversation = Omit<
78
+ components['schemas']['ConversationResponse']['conversation'],
79
+ 'accessToken' | 'channelTopic'
80
+ >
81
+ export type ConversationHistoryResponse = Omit<
82
+ components['schemas']['ConversationHistoryResponse']['history'],
83
+ 'messages' | 'ui.resumeConversationPrompt'
84
+ > & {
85
+ resumeConversationPrompt: components['schemas']['ConversationHistoryResponse']['history']['ui']['resumeConversationPrompt']
86
+ translationProposal: components['schemas']['ConversationHistoryResponse']['history']['ui']['translationProposal']
87
+ events: ChannelEvent[]
88
+ }
89
+
70
90
  export class API {
71
- /**
72
- * Creates an instance of API.
73
- * @param {Object} [config={}]
74
- * @param {string} config.key Api key
75
- * @param {string} config.domain Domain to connect to
76
- * @param {string} config.secure Connect securely
77
- * @param {string} config.externalId Unique visitor identifier (optional)
78
- * @param {boolean} config.sendEnvironment
79
- * @param {string} layoutMode
80
- * @param {string} namespace
81
- * @param {Object} [context={ channelName: undefined, variables: undefined, locale: undefined, topic: undefined, translationLocale: undefined }]
82
- * @param {string} context.channelName
83
- * @param {object} context.variables
84
- * @param {string} context.locale
85
- * @param {string} context.topic
86
- * @param {string} context.translationLocale
87
- * @memberof API
88
- */
89
- constructor({ layoutMode, namespace, config = {}, context = {} }) {
91
+ #ready: boolean
92
+
93
+ connected: boolean
94
+
95
+ #externalId: string
96
+
97
+ #layoutMode: LayoutMode
98
+
99
+ #config: ApiConfig & {
100
+ context: Context
101
+ }
102
+
103
+ store: {
104
+ get(_key: string): string
105
+ set(_key: string, _value: unknown): string
106
+ delete(_key: string): string
107
+ }
108
+
109
+ connectionInfo: { apiKey: string; domain: string; secure: boolean }
110
+
111
+ configReady: boolean
112
+
113
+ userResponded: boolean
114
+
115
+ URLS: UrlsType
116
+
117
+ locale: string
118
+
119
+ conversation?: ConversationConnector = new ConversationConnector()
120
+
121
+ constructor({
122
+ layoutMode,
123
+ namespace,
124
+ config,
125
+ context,
126
+ }: {
127
+ layoutMode: LayoutMode
128
+ namespace: string
129
+ config: ApiConfig
130
+ context: Context
131
+ }) {
90
132
  this.store = objectStore(
91
133
  `${namespace}.connection${context.locale ? `.${context.locale}` : ''}`,
92
134
  config.storageProvider || sessionStorageProvider,
93
135
  )
94
-
95
136
  this.connectionInfo = {
96
137
  apiKey: config.key,
97
138
  domain: config.domain || DOMAIN,
98
139
  secure: config.secure !== false ? config.secure || true : false,
99
140
  }
100
141
 
101
- this.config = {
142
+ this.#config = {
143
+ ...config,
102
144
  sendEnvironment: config.sendEnvironment ?? true,
103
145
  context: {
104
146
  ...context,
@@ -106,25 +148,13 @@ export class API {
106
148
  },
107
149
  }
108
150
 
151
+ this.#ready = false
109
152
  this.connected = false
110
153
  this.configReady = false
111
- this.externalId = config.externalId
112
- this.layoutMode = layoutMode
154
+ this.#externalId = config.externalId
155
+ this.#layoutMode = layoutMode
113
156
  this.userResponded = false
114
157
 
115
- this.internalProducer = new EventProducer('API')
116
- this.internal$ = xs.create(this.internalProducer).flatten()
117
- this.connection$ = this.internal$.filter(
118
- (event) => event.type === 'connection',
119
- )
120
-
121
- this.connection$.subscribe({
122
- next: ({ connected, ready }) => {
123
- this.connected = connected
124
- this.ready = ready
125
- },
126
- })
127
-
128
158
  this.URLS = {}
129
159
 
130
160
  // We want to reconnect whenever the page is loaded from cache (bfcache).
@@ -136,37 +166,37 @@ export class API {
136
166
  })
137
167
  }
138
168
 
139
- getAccessToken() {
169
+ #getAccessToken(): string {
140
170
  return this.store.get('accessToken')
141
171
  }
142
172
 
143
- setAccessToken(accessToken) {
173
+ #setAccessToken(accessToken: string) {
144
174
  this.store.set('accessToken', accessToken)
145
175
  }
146
176
 
147
- getConversationUrl() {
177
+ getConversationUrl(): string {
148
178
  return this.store.get('conversationUrl')
149
179
  }
150
180
 
151
- setConversationUrl(url) {
181
+ #setConversationUrl(url: { href?: string }) {
152
182
  this.store.set('conversationUrl', url)
153
183
  }
154
184
 
155
- hasConversation() {
185
+ hasConversation(): boolean {
156
186
  return !!this.getConversationUrl()
157
187
  }
158
188
 
159
- getChannelTopic() {
189
+ #getChannelTopic() {
160
190
  // The `channelName` fallback is needed for seamless client upgrades.
161
191
  // TODO: Remove when all clients have been upgraded past v20.
162
192
  return this.store.get('channelTopic') || this.store.get('channelName')
163
193
  }
164
194
 
165
- setChannelTopic(topic) {
195
+ #setChannelTopic(topic: string) {
166
196
  this.store.set('channelTopic', topic)
167
197
  }
168
198
 
169
- getLocale = (locale) => locale || this.locale
199
+ #getLocale = (locale: string): string => locale || this.locale
170
200
 
171
201
  clearStore() {
172
202
  this.store.delete('accessToken')
@@ -176,12 +206,12 @@ export class API {
176
206
  this.store.delete('channelTopic')
177
207
  }
178
208
 
179
- getUrlPrefix(protocol) {
209
+ #getUrlPrefix(protocol: string) {
180
210
  const realProtocol = this.connectionInfo.secure ? `${protocol}s` : protocol
181
211
  return `${realProtocol}://${this.connectionInfo.domain}`
182
212
  }
183
213
 
184
- updateUrls({ _links: responseLinks }) {
214
+ #updateUrls({ _links: responseLinks }: { _links: UrlsType }) {
185
215
  this.URLS = Object.entries(responseLinks)
186
216
  .filter(([key]) => key !== 'self')
187
217
  .reduce((urls, [key, { href }]) => {
@@ -199,24 +229,24 @@ export class API {
199
229
  }
200
230
 
201
231
  async disconnect() {
202
- if (this.conversationProducer) {
203
- await this.conversationProducer.disconnect()
232
+ if (this.conversation?.disconnect) {
233
+ this.conversation.disconnect()
204
234
  }
205
235
  this.connected = false
206
236
  this.configReady = false
207
237
  }
208
238
 
209
- async createConversation() {
239
+ async #createConversation(): Promise<InitialConversation> {
210
240
  try {
211
241
  const request = superagent
212
- .post(`${this.getUrlPrefix('http')}${this.URLS.conversations}`)
242
+ .post(`${this.#getUrlPrefix('http')}${this.URLS.conversations}`)
213
243
  .set('Content-Type', 'application/json')
214
244
  .query({ v: apiVersion })
215
245
  // withCredentials() is necessary to allow browsers to save received
216
246
  // cookies in CORS requests.
217
247
  .withCredentials()
218
248
  .send({
219
- externalId: this.externalId || undefined,
249
+ externalId: this.#externalId || undefined,
220
250
  })
221
251
 
222
252
  const { body } = await request
@@ -225,10 +255,10 @@ export class API {
225
255
  const initialState = { ...conversation }
226
256
  delete initialState.accessToken
227
257
  delete initialState.channelTopic
228
- this.setAccessToken(conversation.accessToken)
229
- this.setChannelTopic(conversation.channelTopic)
230
- this.updateUrls(body)
231
- this.setConversationUrl(this.URLS.conversation)
258
+ this.#setAccessToken(conversation.accessToken)
259
+ this.#setChannelTopic(conversation.channelTopic)
260
+ this.#updateUrls(body)
261
+ this.#setConversationUrl(this.URLS.conversation)
232
262
  this.locale = conversation.translation?.locale
233
263
  this.userResponded = conversation.userResponded
234
264
 
@@ -237,14 +267,15 @@ export class API {
237
267
  if (error.status >= 500) {
238
268
  throw new SeamlyGeneralError(error)
239
269
  }
270
+
240
271
  throw error
241
272
  }
242
273
  }
243
274
 
244
- getConfig() {
275
+ getConfig(): Promise<Config> {
245
276
  return superagent
246
277
  .post(
247
- `${this.getUrlPrefix('http')}/client/${
278
+ `${this.#getUrlPrefix('http')}/client/${
248
279
  this.connectionInfo.apiKey
249
280
  }/configs`,
250
281
  )
@@ -252,41 +283,43 @@ export class API {
252
283
  .query({ v: apiVersion })
253
284
  .send({
254
285
  context: {
255
- ...this.config.context,
286
+ ...this.#config.context,
256
287
  environment:
257
- this.config.sendEnvironment === true
258
- ? this.getEnvironment()
259
- : this.config.sendEnvironment,
288
+ this.#config.sendEnvironment === true
289
+ ? this.#getEnvironment()
290
+ : this.#config.sendEnvironment,
260
291
  },
261
292
  })
262
293
  .then(({ body }) => {
263
- this.updateUrls(body)
294
+ this.#updateUrls(body)
264
295
  this.configReady = true
265
296
  return body.config
266
297
  })
267
298
  .catch((error) => {
268
299
  if (error.status === 404) {
269
- throw new SeamlyConfigurationError()
300
+ throw new SeamlyConfigurationError(error)
270
301
  }
302
+
271
303
  if (error.status >= 500) {
272
304
  throw new SeamlyGeneralError(error)
273
305
  }
306
+
274
307
  throw error
275
308
  })
276
309
  }
277
310
 
278
- async getConversation() {
311
+ async getConversation(): Promise<ConversationHistoryResponse> {
279
312
  if (!this.hasConversation()) {
280
313
  return null
281
314
  }
282
315
 
283
316
  try {
284
317
  const { body } = await superagent
285
- .get(`${this.getUrlPrefix('http')}${this.URLS.history}`)
286
- .set('Authorization', `Bearer ${this.getAccessToken()}`)
318
+ .get(`${this.#getUrlPrefix('http')}${this.URLS.history}`)
319
+ .set('Authorization', `Bearer ${this.#getAccessToken()}`)
287
320
  .query({ v: apiVersion })
288
321
 
289
- this.updateUrls(body)
322
+ this.#updateUrls(body)
290
323
 
291
324
  const {
292
325
  messages,
@@ -314,48 +347,58 @@ export class API {
314
347
  resumeConversationPrompt: ui
315
348
  ? Boolean(ui.resumeConversationPrompt)
316
349
  : false,
350
+ translationProposal: ui.translationProposal,
317
351
  translation,
318
352
  }
319
353
  } catch (error) {
320
354
  if (error.status === 401) {
321
355
  throw new SeamlyUnauthorizedError(error)
322
356
  }
357
+
323
358
  if (error.status === 404) {
324
359
  throw new SeamlySessionExpiredError(error)
325
360
  }
361
+
326
362
  if (error.status >= 500) {
327
363
  throw new SeamlyGeneralError(error)
328
364
  }
365
+
329
366
  throw error
330
367
  }
331
368
  }
332
369
 
333
- async connect() {
370
+ async connect(): Promise<InitialConversation> {
334
371
  this.connected = false
335
- let conversationInitialState = null
372
+ let conversationInitialState: InitialConversation = {}
336
373
 
337
374
  if (!this.hasConversation()) {
338
- conversationInitialState = await this.createConversation()
375
+ conversationInitialState = await this.#createConversation()
339
376
  }
340
377
 
341
- this.conversationProducer = new ConversationProducer(
342
- `${this.getUrlPrefix('ws')}${this.URLS.socket}`,
343
- this.config.context.channelName,
344
- this.getChannelTopic(),
345
- this.getAccessToken(),
378
+ this.conversation.connect(
379
+ `${this.#getUrlPrefix('ws')}${this.URLS.socket}`,
380
+ this.#config.context.channelName,
381
+ this.#getChannelTopic(),
382
+ this.#getAccessToken(),
346
383
  )
347
384
 
348
- this.internalProducer.emit(xs.create(this.conversationProducer))
385
+ this.conversation.onConnection((connectionState) => {
386
+ this.connected = [
387
+ 'attach_channel_succeeded',
388
+ 'join_channel_succeeded',
389
+ ].includes(connectionState)
390
+ this.#ready = connectionState === 'attach_channel_succeeded'
391
+ })
349
392
 
350
393
  // Send environment
351
- if (this.config.sendEnvironment) {
394
+ if (this.#config.sendEnvironment) {
352
395
  this.send(
353
396
  'context',
354
397
  {
355
398
  environment:
356
- this.config.sendEnvironment === true
357
- ? this.getEnvironment()
358
- : this.config.sendEnvironment,
399
+ this.#config.sendEnvironment === true
400
+ ? this.#getEnvironment()
401
+ : this.#config.sendEnvironment,
359
402
  },
360
403
  false,
361
404
  )
@@ -369,8 +412,8 @@ export class API {
369
412
  formData.append('upload', file)
370
413
 
371
414
  const req = superagent
372
- .post(`${this.getUrlPrefix('http')}${this.URLS.uploads}`)
373
- .set('Authorization', `Bearer ${this.getAccessToken()}`)
415
+ .post(`${this.#getUrlPrefix('http')}${this.URLS.uploads}`)
416
+ .set('Authorization', `Bearer ${this.#getAccessToken()}`)
374
417
  .send(formData)
375
418
 
376
419
  req.on('progress', function (e) {
@@ -399,11 +442,11 @@ export class API {
399
442
 
400
443
  getConversationIntitialState() {
401
444
  return superagent
402
- .get(`${this.getUrlPrefix('http')}${this.getConversationUrl()}`)
403
- .set('Authorization', `Bearer ${this.getAccessToken()}`)
445
+ .get(`${this.#getUrlPrefix('http')}${this.getConversationUrl()}`)
446
+ .set('Authorization', `Bearer ${this.#getAccessToken()}`)
404
447
  .query({ v: apiVersion })
405
448
  .then(({ body }) => {
406
- this.updateUrls(body)
449
+ this.#updateUrls(body)
407
450
  this.userResponded = body.conversation.userResponded
408
451
  return omit(body.conversation, ['accessToken', 'channelTopic'])
409
452
  })
@@ -417,15 +460,20 @@ export class API {
417
460
  if (error.status >= 500) {
418
461
  throw new SeamlyGeneralError(error)
419
462
  }
463
+
420
464
  throw error
421
465
  })
422
466
  }
423
467
 
424
- async getTranslations(locale) {
468
+ async getTranslations(locale: string): Promise<Record<string, string>> {
425
469
  try {
426
- const url = `${this.getUrlPrefix('http')}${this.URLS.translations}`
470
+ if (!this.URLS.translations) {
471
+ throw new SeamlyConfigurationError()
472
+ }
473
+
474
+ const url = `${this.#getUrlPrefix('http')}${this.URLS.translations}`
427
475
  .replace('{version}', String(TRANSLATIONS_VERSION))
428
- .replace('{locale}', this.getLocale(locale))
476
+ .replace('{locale}', this.#getLocale(locale))
429
477
  const request = superagent.get(url)
430
478
 
431
479
  const { body } = await request
@@ -434,34 +482,36 @@ export class API {
434
482
  if (error.status >= 500) {
435
483
  throw new SeamlyGeneralError(error)
436
484
  }
485
+
437
486
  throw error
438
487
  }
439
488
  }
440
489
 
441
490
  send(command, payload, waitForReady = true) {
442
- if (!this.connected || (waitForReady && !this.ready)) {
443
- // Wait for connection
444
- this.connection$
445
- .filter((e) => (waitForReady ? e.connected && e.ready : e.connected))
446
- .take(1)
447
- .subscribe({
448
- next: () => this.send(command, payload, waitForReady),
449
- })
491
+ if (!this.connected || (waitForReady && !this.#ready)) {
492
+ // Wait for connection to be made
493
+ this.conversation?.onConnection((connectionState) => {
494
+ const connected = connectionState === 'join_channel_succeeded'
495
+ const ready = connectionState === 'attach_channel_succeeded'
496
+ if (waitForReady ? ready : connected || ready) {
497
+ this.send(command, payload, waitForReady)
498
+ }
499
+ })
450
500
  return
451
501
  }
452
502
 
453
503
  log('[SEND]', command, payload)
454
504
 
455
- this.conversationProducer.push(
505
+ this.conversation.pushToChannel(
456
506
  command,
457
507
  buildPayload(command, payload),
458
508
  10000,
459
509
  )
460
510
  }
461
511
 
462
- sendContext(context = {}) {
512
+ sendContext(context: Context) {
463
513
  const { locale, variables } = context
464
- const payload = {}
514
+ const payload: { locale?: string; variables?: any } = {}
465
515
 
466
516
  if (locale) {
467
517
  if (typeof locale !== 'string') {
@@ -485,19 +535,16 @@ export class API {
485
535
  this.send('context', payload, false)
486
536
  }
487
537
 
488
- stream() {
489
- return this.internal$.filter((event) => event.type !== 'connection')
490
- }
491
-
492
- getEnvironment() {
538
+ #getEnvironment() {
493
539
  return {
494
540
  clientName: PACKAGE_NAME,
495
- clientVariant: this.layoutMode,
541
+ clientVariant: this.#layoutMode,
496
542
  clientVersion: PACKAGE_VERSION,
497
543
  currentUrl: window.location.toString(),
498
544
  screenResolution: `${window.screen.width}x${window.screen.height}`,
499
545
  timezone: getTimeZone(),
500
546
  userAgent: navigator.userAgent,
547
+ preferredLocale: navigator.language,
501
548
  }
502
549
  }
503
550
  }
@@ -1,5 +1,5 @@
1
- import type { ChannelEvent } from 'domains/store/store.types'
2
1
  import { FunctionComponent } from 'preact'
2
+ import type { ChannelEvent } from 'domains/store/store.types'
3
3
 
4
4
  export type ApiConfig = {
5
5
  key: string
@@ -19,8 +19,9 @@ export type ErrorCallbackMeta = {
19
19
  api: ApiConfig
20
20
  layoutMode: LayoutMode
21
21
  conversationUrl: string
22
+ action?: string
22
23
  }
23
- export type ErrorCallback = (error: Error, meta: ErrorCallbackMeta) => void
24
+ export type ErrorCallback = (_error: Error, _meta: ErrorCallbackMeta) => void
24
25
 
25
26
  export type Message = {
26
27
  showName?: boolean
@@ -62,7 +63,7 @@ export type VisibilityCallbackOptions = {
62
63
  }
63
64
 
64
65
  export type VisibilityCallback = (
65
- options: VisibilityCallbackOptions,
66
+ _options: VisibilityCallbackOptions,
66
67
  ) => VisibilityOptions
67
68
 
68
69
  export type Config = {
@@ -79,7 +80,7 @@ export type Config = {
79
80
  visibilityCallback?: VisibilityCallback
80
81
  errorCallback?: ErrorCallback
81
82
  api: ApiConfig
82
- appContainerClassNames?: string[] | ((config: Config) => string[])
83
+ appContainerClassNames?: string[] | ((_config: Config) => string[])
83
84
  zIndex?: number
84
85
  showFaq?: boolean
85
86
  showSuggestions?: boolean