@seamly/web-ui 22.3.4 → 22.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/dist/lib/components.js +1484 -14
- package/build/dist/lib/components.js.map +1 -1
- package/build/dist/lib/components.min.js +1 -1
- package/build/dist/lib/components.min.js.map +1 -1
- package/build/dist/lib/hooks.js +1519 -9
- package/build/dist/lib/hooks.js.map +1 -1
- package/build/dist/lib/hooks.min.js +1 -1
- package/build/dist/lib/hooks.min.js.map +1 -1
- package/build/dist/lib/index.debug.js +15 -4
- package/build/dist/lib/index.debug.js.map +1 -1
- package/build/dist/lib/index.debug.min.js +1 -1
- package/build/dist/lib/index.debug.min.js.LICENSE.txt +4 -0
- package/build/dist/lib/index.debug.min.js.map +1 -1
- package/build/dist/lib/index.js +258 -244
- package/build/dist/lib/index.js.map +1 -1
- package/build/dist/lib/index.min.js +1 -1
- package/build/dist/lib/index.min.js.map +1 -1
- package/build/dist/lib/standalone.js +536 -245
- package/build/dist/lib/standalone.js.map +1 -1
- package/build/dist/lib/standalone.min.js +1 -1
- package/build/dist/lib/standalone.min.js.map +1 -1
- package/build/dist/lib/style-guide.js +265 -251
- package/build/dist/lib/style-guide.js.map +1 -1
- package/build/dist/lib/style-guide.min.js +1 -1
- package/build/dist/lib/style-guide.min.js.map +1 -1
- package/build/dist/lib/utils.js +8497 -8427
- package/build/dist/lib/utils.js.map +1 -1
- package/build/dist/lib/utils.min.js +1 -1
- package/build/dist/lib/utils.min.js.map +1 -1
- package/package.json +1 -1
- package/src/javascripts/api/conversation-connector.ts +48 -32
- package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +197 -218
- package/src/javascripts/ui/hooks/seamly-hooks.js +5 -5
- package/src/javascripts/ui/hooks/use-seamly-conversation.ts +13 -0
package/package.json
CHANGED
|
@@ -18,6 +18,17 @@ type ConnectionState = {
|
|
|
18
18
|
ready: boolean
|
|
19
19
|
currentState: CurrentConnectionState
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
// Subscribers set which are used to subscribe to changes in the conversation.
|
|
23
|
+
// Needs to be outside of the ConversationConnector class so its not recreated for each instance.
|
|
24
|
+
const subscribers = new Set<Function>()
|
|
25
|
+
|
|
26
|
+
// Syncs the lifecycle of the conversation with Preact. Each subscriber will fetch the latest value from the conversation if needed.
|
|
27
|
+
const emitChange = () => {
|
|
28
|
+
// Call the callback function for each subscriber
|
|
29
|
+
subscribers.forEach((callback) => callback())
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export default class ConversationConnector {
|
|
22
33
|
#connectionListeners: ((_payload: ConnectionState) => boolean | void)[] = []
|
|
23
34
|
|
|
@@ -46,32 +57,17 @@ export default class ConversationConnector {
|
|
|
46
57
|
this.channelTopic = channelTopic
|
|
47
58
|
const { url: splittedUrl, params } = splitUrlParams(this.url)
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const backoff = 2 ** tries * 250
|
|
55
|
-
|
|
56
|
-
// Limit the backoff time to 10 seconds.
|
|
57
|
-
return Math.min(backoff, 10000)
|
|
58
|
-
},
|
|
59
|
-
})
|
|
60
|
-
}
|
|
60
|
+
this.socket = new Socket(splittedUrl, {
|
|
61
|
+
params: { ...params, v: apiVersion },
|
|
62
|
+
reconnectAfterMs: (tries) => {
|
|
63
|
+
// Calculate the backoff time based on the number of tries.
|
|
64
|
+
const backoff = 2 ** tries * 250
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.socket.connect()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
this.channel = this.socket.channel(this.channelTopic, {
|
|
69
|
-
authorization: `Bearer ${this.accessToken}`,
|
|
70
|
-
channelName: this.channelName,
|
|
66
|
+
// Limit the backoff time to 10 seconds.
|
|
67
|
+
return Math.min(backoff, 10000)
|
|
68
|
+
},
|
|
71
69
|
})
|
|
72
70
|
|
|
73
|
-
this.start()
|
|
74
|
-
|
|
75
71
|
this.socket.onError((err) => {
|
|
76
72
|
log('[SOCKET][ERROR]', err)
|
|
77
73
|
})
|
|
@@ -80,6 +76,24 @@ export default class ConversationConnector {
|
|
|
80
76
|
log('[SOCKET]OPEN')
|
|
81
77
|
})
|
|
82
78
|
|
|
79
|
+
this.socket.onClose(() => {
|
|
80
|
+
log('[SOCKET]CLOSE')
|
|
81
|
+
this.#emitConnectionState({
|
|
82
|
+
connected: false,
|
|
83
|
+
ready: false,
|
|
84
|
+
currentState: 'socket_closed',
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
this.socket.connect()
|
|
89
|
+
|
|
90
|
+
this.channel = this.socket.channel(this.channelTopic, {
|
|
91
|
+
authorization: `Bearer ${this.accessToken}`,
|
|
92
|
+
channelName: this.channelName,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
this.start()
|
|
96
|
+
|
|
83
97
|
this.channel.on('system', (msg) => {
|
|
84
98
|
switch (msg.type) {
|
|
85
99
|
case 'attach_channel_succeeded':
|
|
@@ -92,15 +106,6 @@ export default class ConversationConnector {
|
|
|
92
106
|
}
|
|
93
107
|
})
|
|
94
108
|
|
|
95
|
-
this.socket.onClose(() => {
|
|
96
|
-
log('[SOCKET]CLOSE')
|
|
97
|
-
this.#emitConnectionState({
|
|
98
|
-
connected: false,
|
|
99
|
-
ready: false,
|
|
100
|
-
currentState: 'socket_closed',
|
|
101
|
-
})
|
|
102
|
-
})
|
|
103
|
-
|
|
104
109
|
this.channel.onClose(() => {
|
|
105
110
|
log('[CHANNEL]CLOSE')
|
|
106
111
|
this.#emitConnectionState({
|
|
@@ -184,6 +189,8 @@ export default class ConversationConnector {
|
|
|
184
189
|
// If we only want to execute the callback once, remove it from the listener
|
|
185
190
|
return !complete
|
|
186
191
|
})
|
|
192
|
+
|
|
193
|
+
emitChange()
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
pushToChannel(
|
|
@@ -193,4 +200,13 @@ export default class ConversationConnector {
|
|
|
193
200
|
) {
|
|
194
201
|
this.channel.push(command, payload, timeout)
|
|
195
202
|
}
|
|
203
|
+
|
|
204
|
+
// Adds a callback function to the subscribers set and returns a method to unsubscribe from the store.
|
|
205
|
+
// This method is used to sync the state with the lifecycle of Preact.
|
|
206
|
+
static subscribe(callback: Function) {
|
|
207
|
+
subscribers.add(callback)
|
|
208
|
+
|
|
209
|
+
// Returns a function that removes the callback from the subscribers set.
|
|
210
|
+
return () => subscribers.delete(callback)
|
|
211
|
+
}
|
|
196
212
|
}
|
|
@@ -4,10 +4,10 @@ import SeamlyOfflineError from 'api/errors/seamly-offline-error'
|
|
|
4
4
|
import SeamlySessionExpiredError from 'api/errors/seamly-session-expired-error'
|
|
5
5
|
import { SeamlyEventBusContext } from 'ui/components/core/seamly-api-context'
|
|
6
6
|
import {
|
|
7
|
-
useSeamlyApiContext,
|
|
8
7
|
useSeamlyCommands,
|
|
9
8
|
useSeamlyIdleDetachCountdown,
|
|
10
9
|
} from 'ui/hooks/seamly-hooks'
|
|
10
|
+
import useSeamlyConversation from 'ui/hooks/use-seamly-conversation'
|
|
11
11
|
import { featureKeys } from 'ui/utils/seamly-utils'
|
|
12
12
|
import { setHasResponded } from 'domains/app/slice'
|
|
13
13
|
import { clearInterrupt, setInterrupt } from 'domains/interrupt/slice'
|
|
@@ -38,7 +38,7 @@ import { setTranslationProposalPrompt } from 'domains/translations/slice'
|
|
|
38
38
|
const EMITTABLE_MESSAGE_TYPES = ['text', 'choice_prompt', 'image', 'video']
|
|
39
39
|
|
|
40
40
|
const SeamlyEventSubscriber = () => {
|
|
41
|
-
const
|
|
41
|
+
const conversation = useSeamlyConversation()
|
|
42
42
|
const syncChannelRef = useRef<number>()
|
|
43
43
|
const messageChannelRef = useRef<number>()
|
|
44
44
|
const dispatch = useAppDispatch()
|
|
@@ -48,8 +48,8 @@ const SeamlyEventSubscriber = () => {
|
|
|
48
48
|
const { emitEvent } = useSeamlyCommands()
|
|
49
49
|
|
|
50
50
|
useEffect(() => {
|
|
51
|
-
if (
|
|
52
|
-
const { socket } =
|
|
51
|
+
if (conversation.socket) {
|
|
52
|
+
const { socket } = conversation
|
|
53
53
|
socket.onError((err) => {
|
|
54
54
|
const seamlyOfflineError = new SeamlyOfflineError(err)
|
|
55
55
|
dispatch(
|
|
@@ -61,6 +61,7 @@ const SeamlyEventSubscriber = () => {
|
|
|
61
61
|
originalError: seamlyOfflineError.originalError,
|
|
62
62
|
}),
|
|
63
63
|
)
|
|
64
|
+
|
|
64
65
|
dispatch(clearEvents())
|
|
65
66
|
})
|
|
66
67
|
|
|
@@ -68,11 +69,11 @@ const SeamlyEventSubscriber = () => {
|
|
|
68
69
|
dispatch(clearInterrupt())
|
|
69
70
|
})
|
|
70
71
|
}
|
|
71
|
-
}, [
|
|
72
|
+
}, [conversation, dispatch])
|
|
72
73
|
|
|
73
74
|
useEffect(() => {
|
|
74
|
-
if (
|
|
75
|
-
|
|
75
|
+
if (conversation.socket) {
|
|
76
|
+
conversation.onConnection(({ currentState }) => {
|
|
76
77
|
if (currentState === 'join_channel_erred') {
|
|
77
78
|
const seamlyGeneralError = new SeamlyGeneralError()
|
|
78
79
|
dispatch(
|
|
@@ -91,251 +92,229 @@ const SeamlyEventSubscriber = () => {
|
|
|
91
92
|
return false
|
|
92
93
|
})
|
|
93
94
|
}
|
|
94
|
-
}, [
|
|
95
|
+
}, [conversation, dispatch])
|
|
95
96
|
|
|
96
97
|
useEffect(() => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const { payload } = event
|
|
98
|
+
const updateParticipant = (event: ParticipantEvent) => {
|
|
99
|
+
if (event.type !== 'participant') {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const { payload } = event
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if (!payload || !payload.participant) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
const { fromClient, participant } = payload
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
participant?.name
|
|
114
|
-
) {
|
|
115
|
-
dispatch(setHeaderSubTitle(participant.name))
|
|
116
|
-
}
|
|
110
|
+
if (!fromClient && typeof participant !== 'string' && participant?.name) {
|
|
111
|
+
dispatch(setHeaderSubTitle(participant.name))
|
|
112
|
+
}
|
|
117
113
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
dispatch(
|
|
115
|
+
setParticipant({
|
|
116
|
+
participant,
|
|
117
|
+
fromClient,
|
|
118
|
+
}),
|
|
119
|
+
)
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
121
|
+
if (typeof participant !== 'string' && participant.introduction) {
|
|
122
|
+
dispatch(addEvent(event))
|
|
128
123
|
}
|
|
124
|
+
}
|
|
129
125
|
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
conversation.onConnection(({ connected }) => {
|
|
127
|
+
if (!connected) return false
|
|
132
128
|
|
|
133
|
-
|
|
129
|
+
const { channel } = conversation
|
|
134
130
|
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
channel.onMessage = (type, payload) => {
|
|
132
|
+
const event = { type, payload }
|
|
137
133
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
134
|
+
switch (type) {
|
|
135
|
+
case 'ui':
|
|
136
|
+
if (payload.state && payload.state.hasOwnProperty('loading')) {
|
|
137
|
+
dispatch(setIsLoading(payload.state.loading))
|
|
138
|
+
}
|
|
139
|
+
switch (payload.type) {
|
|
140
|
+
case 'idle_detach_countdown':
|
|
141
|
+
initCountdown(payload.body.duration)
|
|
142
|
+
break
|
|
143
|
+
case 'idle_detach_countdown_elapsed':
|
|
144
|
+
endCountdown(undefined, true)
|
|
145
|
+
break
|
|
146
|
+
case 'resume_conversation_prompt':
|
|
147
|
+
dispatch(initResumeConversationPrompt())
|
|
148
|
+
break
|
|
149
|
+
case 'translation_proposal':
|
|
150
|
+
dispatch(setTranslationProposalPrompt(payload.body))
|
|
151
|
+
break
|
|
152
|
+
case 'user_first_response':
|
|
153
|
+
dispatch(setHasResponded(true))
|
|
154
|
+
// @ts-ignore
|
|
155
|
+
eventBus.emit('system.userFirstResponse', payload.body)
|
|
156
|
+
break
|
|
157
|
+
}
|
|
158
|
+
break
|
|
159
|
+
case 'message':
|
|
160
|
+
updateParticipant(payload)
|
|
161
|
+
switch (payload.type) {
|
|
162
|
+
case 'text':
|
|
163
|
+
case 'choice_prompt':
|
|
164
|
+
case 'splash':
|
|
165
|
+
case 'image':
|
|
166
|
+
case 'upload':
|
|
167
|
+
case 'video':
|
|
168
|
+
case 'cta':
|
|
169
|
+
case 'custom':
|
|
170
|
+
case 'carousel':
|
|
171
|
+
case 'card':
|
|
172
|
+
if (payload.service && payload.service.serviceSessionId) {
|
|
173
|
+
dispatch(setActiveService(payload.service.serviceSessionId))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
dispatch(addEvent(event as ChannelEvent))
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
break
|
|
180
|
+
case 'participant':
|
|
181
|
+
updateParticipant(event as ParticipantEvent)
|
|
182
|
+
break
|
|
183
|
+
case 'service_data':
|
|
184
|
+
if (payload.persist) {
|
|
185
|
+
dispatch(setServiceDataItem(payload))
|
|
186
|
+
}
|
|
187
|
+
break
|
|
188
|
+
case 'ack':
|
|
189
|
+
dispatch(ackEvent(event as AckEvent))
|
|
190
|
+
break
|
|
191
|
+
case 'system':
|
|
192
|
+
if (payload.type === 'service_changed') {
|
|
193
|
+
const { serviceSettings, ...eventPayload } = payload
|
|
194
|
+
const { entry, proactiveMessages } = serviceSettings
|
|
195
|
+
const { upload } = entry.options
|
|
196
|
+
|
|
197
|
+
dispatch(
|
|
198
|
+
setFeatureEnabledState({
|
|
199
|
+
key: featureKeys.uploads,
|
|
200
|
+
enabled: !!(upload && upload.enabled),
|
|
201
|
+
}),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
dispatch(setProactiveMessages(proactiveMessages.enabled || false))
|
|
205
|
+
|
|
206
|
+
dispatch(setServiceEntryMetadata(entry))
|
|
207
|
+
if (payload.serviceSessionId) {
|
|
208
|
+
dispatch(setActiveService(payload.serviceSessionId))
|
|
190
209
|
}
|
|
191
|
-
break
|
|
192
|
-
case 'ack':
|
|
193
|
-
dispatch(ackEvent(event as AckEvent))
|
|
194
|
-
break
|
|
195
|
-
case 'system':
|
|
196
|
-
if (payload.type === 'service_changed') {
|
|
197
|
-
const { serviceSettings, ...eventPayload } = payload
|
|
198
|
-
const { entry, proactiveMessages } = serviceSettings
|
|
199
|
-
const { upload } = entry.options
|
|
200
210
|
|
|
211
|
+
emitEvent('system.serviceChanged', eventPayload)
|
|
212
|
+
}
|
|
213
|
+
break
|
|
214
|
+
case 'info':
|
|
215
|
+
if (
|
|
216
|
+
payload.type === 'divider' ||
|
|
217
|
+
payload.type === 'text' ||
|
|
218
|
+
payload.type === 'translation'
|
|
219
|
+
) {
|
|
220
|
+
dispatch(addEvent(event as ChannelEvent))
|
|
221
|
+
}
|
|
222
|
+
break
|
|
223
|
+
case 'error':
|
|
224
|
+
switch (payload.type) {
|
|
225
|
+
case 'find_conversation_erred':
|
|
226
|
+
const seamlySessionExpiredError = new SeamlySessionExpiredError(
|
|
227
|
+
event,
|
|
228
|
+
)
|
|
201
229
|
dispatch(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
230
|
+
setInterrupt({
|
|
231
|
+
name: seamlySessionExpiredError.name,
|
|
232
|
+
action: seamlySessionExpiredError.action,
|
|
233
|
+
message: seamlySessionExpiredError.message,
|
|
234
|
+
originalEvent: seamlySessionExpiredError.originalEvent,
|
|
235
|
+
originalError: seamlySessionExpiredError.originalError,
|
|
205
236
|
}),
|
|
206
237
|
)
|
|
207
|
-
|
|
238
|
+
break
|
|
239
|
+
case 'conversation_erred':
|
|
240
|
+
case 'attach_channel_erred':
|
|
241
|
+
const seamlyGeneralError = new SeamlyGeneralError(event)
|
|
208
242
|
dispatch(
|
|
209
|
-
|
|
243
|
+
setInterrupt({
|
|
244
|
+
name: seamlyGeneralError.name,
|
|
245
|
+
message: seamlyGeneralError.message,
|
|
246
|
+
langKey: seamlyGeneralError.langKey,
|
|
247
|
+
originalEvent: seamlyGeneralError.originalEvent,
|
|
248
|
+
originalError: seamlyGeneralError.originalError,
|
|
249
|
+
action: seamlyGeneralError.action,
|
|
250
|
+
}),
|
|
210
251
|
)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (payload.serviceSessionId) {
|
|
214
|
-
dispatch(setActiveService(payload.serviceSessionId))
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
emitEvent('system.serviceChanged', eventPayload)
|
|
218
|
-
}
|
|
219
|
-
break
|
|
220
|
-
case 'info':
|
|
221
|
-
if (
|
|
222
|
-
payload.type === 'divider' ||
|
|
223
|
-
payload.type === 'text' ||
|
|
224
|
-
payload.type === 'translation'
|
|
225
|
-
) {
|
|
226
|
-
dispatch(addEvent(event as ChannelEvent))
|
|
227
|
-
}
|
|
228
|
-
break
|
|
229
|
-
case 'error':
|
|
230
|
-
switch (payload.type) {
|
|
231
|
-
case 'find_conversation_erred':
|
|
232
|
-
const seamlySessionExpiredError =
|
|
233
|
-
new SeamlySessionExpiredError(event)
|
|
234
|
-
dispatch(
|
|
235
|
-
setInterrupt({
|
|
236
|
-
name: seamlySessionExpiredError.name,
|
|
237
|
-
action: seamlySessionExpiredError.action,
|
|
238
|
-
message: seamlySessionExpiredError.message,
|
|
239
|
-
originalEvent: seamlySessionExpiredError.originalEvent,
|
|
240
|
-
originalError: seamlySessionExpiredError.originalError,
|
|
241
|
-
}),
|
|
242
|
-
)
|
|
243
|
-
break
|
|
244
|
-
case 'conversation_erred':
|
|
245
|
-
case 'attach_channel_erred':
|
|
246
|
-
const seamlyGeneralError = new SeamlyGeneralError(event)
|
|
247
|
-
dispatch(
|
|
248
|
-
setInterrupt({
|
|
249
|
-
name: seamlyGeneralError.name,
|
|
250
|
-
message: seamlyGeneralError.message,
|
|
251
|
-
langKey: seamlyGeneralError.langKey,
|
|
252
|
-
originalEvent: seamlyGeneralError.originalEvent,
|
|
253
|
-
originalError: seamlyGeneralError.originalError,
|
|
254
|
-
action: seamlyGeneralError.action,
|
|
255
|
-
}),
|
|
256
|
-
)
|
|
257
|
-
break
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return payload
|
|
252
|
+
break
|
|
253
|
+
}
|
|
262
254
|
}
|
|
263
255
|
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}, [
|
|
268
|
-
api,
|
|
269
|
-
api.connectionInfo,
|
|
270
|
-
api.conversation.channel,
|
|
271
|
-
dispatch,
|
|
272
|
-
emitEvent,
|
|
273
|
-
endCountdown,
|
|
274
|
-
eventBus,
|
|
275
|
-
initCountdown,
|
|
276
|
-
])
|
|
256
|
+
return payload
|
|
257
|
+
}
|
|
277
258
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (!connected) return false
|
|
259
|
+
return false
|
|
260
|
+
})
|
|
261
|
+
}, [conversation, dispatch, emitEvent, endCountdown, eventBus, initCountdown])
|
|
282
262
|
|
|
283
|
-
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
conversation.onConnection(({ connected }) => {
|
|
265
|
+
if (!connected) return false
|
|
284
266
|
|
|
285
|
-
|
|
286
|
-
channel?.off('message', messageChannelRef.current)
|
|
287
|
-
}
|
|
267
|
+
const { channel } = conversation
|
|
288
268
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
269
|
+
if (messageChannelRef.current) {
|
|
270
|
+
channel.off('message', messageChannelRef.current)
|
|
271
|
+
}
|
|
293
272
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
// error situation.
|
|
297
|
-
if (payload.id !== prevEmittedEventId.current) {
|
|
298
|
-
// @ts-ignore
|
|
299
|
-
eventBus.emit('message', payload)
|
|
300
|
-
}
|
|
301
|
-
prevEmittedEventId.current = payload.id
|
|
273
|
+
messageChannelRef.current = channel.on('message', (payload) => {
|
|
274
|
+
if (!EMITTABLE_MESSAGE_TYPES.includes(payload.type)) {
|
|
302
275
|
return payload
|
|
303
|
-
}
|
|
276
|
+
}
|
|
304
277
|
|
|
305
|
-
|
|
278
|
+
// This check dedupes the sending of messages via
|
|
279
|
+
// the bus if a duplicate connection exists in an
|
|
280
|
+
// error situation.
|
|
281
|
+
if (payload.id !== prevEmittedEventId.current) {
|
|
282
|
+
// @ts-ignore
|
|
283
|
+
eventBus.emit('message', payload)
|
|
284
|
+
}
|
|
285
|
+
prevEmittedEventId.current = payload.id
|
|
286
|
+
return payload
|
|
306
287
|
})
|
|
307
|
-
|
|
308
|
-
|
|
288
|
+
|
|
289
|
+
return true
|
|
290
|
+
})
|
|
291
|
+
}, [conversation, eventBus])
|
|
309
292
|
|
|
310
293
|
useEffect(() => {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (!connected) return false
|
|
294
|
+
conversation.onConnection(({ connected }) => {
|
|
295
|
+
if (!connected) return false
|
|
314
296
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
297
|
+
if (syncChannelRef.current) {
|
|
298
|
+
conversation.channel.off('sync', syncChannelRef.current)
|
|
299
|
+
}
|
|
318
300
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const history = await dispatch(getConversation(payload)).unwrap()
|
|
326
|
-
if (!history) return
|
|
327
|
-
|
|
328
|
-
dispatch(setHistory(history))
|
|
329
|
-
} catch (_e) {
|
|
330
|
-
// nothing to do, the error is handled in the thunk
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
)
|
|
301
|
+
syncChannelRef.current = conversation.channel.on(
|
|
302
|
+
'sync',
|
|
303
|
+
async (payload: { lastEvent: { id: string; occurredAt: number } }) => {
|
|
304
|
+
try {
|
|
305
|
+
const history = await dispatch(getConversation(payload)).unwrap()
|
|
306
|
+
if (!history) return
|
|
334
307
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
308
|
+
dispatch(setHistory(history))
|
|
309
|
+
} catch (_e) {
|
|
310
|
+
// nothing to do, the error is handled in the thunk
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
return true
|
|
316
|
+
})
|
|
317
|
+
}, [conversation, dispatch])
|
|
339
318
|
|
|
340
319
|
return null
|
|
341
320
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect } from 'preact/hooks'
|
|
2
2
|
// Import extracted hooks here for use inside this file
|
|
3
|
-
import
|
|
3
|
+
import useSeamlyConversation from './use-seamly-conversation'
|
|
4
4
|
|
|
5
5
|
// Export extracted hooks here,
|
|
6
6
|
// although this is a redundant, it prevents a bazillion code changes for now.
|
|
@@ -54,11 +54,11 @@ export {
|
|
|
54
54
|
// and imported directly from this file
|
|
55
55
|
// Please do not remove
|
|
56
56
|
export const useSeamlyEventStream = (nextFn, filterFn) => {
|
|
57
|
-
const
|
|
57
|
+
const conversation = useSeamlyConversation()
|
|
58
58
|
|
|
59
59
|
useEffect(() => {
|
|
60
|
-
if (
|
|
61
|
-
const { channel } =
|
|
60
|
+
if (conversation.channel) {
|
|
61
|
+
const { channel } = conversation
|
|
62
62
|
|
|
63
63
|
channel.onMessage = (type, payload) => {
|
|
64
64
|
if (!filterFn || filterFn({ type, payload })) {
|
|
@@ -68,5 +68,5 @@ export const useSeamlyEventStream = (nextFn, filterFn) => {
|
|
|
68
68
|
return payload
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
}, [nextFn, filterFn,
|
|
71
|
+
}, [nextFn, filterFn, conversation])
|
|
72
72
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'preact/compat'
|
|
2
|
+
import ConversationConnector from 'api/conversation-connector'
|
|
3
|
+
import { useSeamlyApiContext } from './seamly-api-hooks'
|
|
4
|
+
|
|
5
|
+
const useSeamlyConversation = () => {
|
|
6
|
+
const api = useSeamlyApiContext()
|
|
7
|
+
|
|
8
|
+
const getSnapshot = () => api.conversation
|
|
9
|
+
|
|
10
|
+
return useSyncExternalStore(ConversationConnector.subscribe, getSnapshot)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default useSeamlyConversation
|