Skip to content

Commit adb1652

Browse files
committed
feat: stop polling when chat relat is present. Process incoming messages from chat relay
Signed-off-by: Dorra Jaouad <[email protected]>
1 parent a3d644d commit adb1652

File tree

4 files changed

+112
-3
lines changed

4 files changed

+112
-3
lines changed

src/composables/useGetMessages.ts

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import { subscribe, unsubscribe } from '@nextcloud/event-bus'
2020
import { computed, inject, onBeforeUnmount, provide, ref, watch } from 'vue'
2121
import { START_LOCATION, useRoute } from 'vue-router'
2222
import { useStore } from 'vuex'
23-
import { CHAT, MESSAGE } from '../constants.ts'
23+
import { CHAT, CONFIG, MESSAGE } from '../constants.ts'
24+
import { getTalkConfig } from '../services/CapabilitiesManager.ts'
2425
import { EventBus } from '../services/EventBus.ts'
2526
import { useChatStore } from '../stores/chat.ts'
2627
import { useChatExtrasStore } from '../stores/chatExtras.ts'
@@ -41,6 +42,8 @@ type GetMessagesContext = {
4142
}
4243

4344
const GET_MESSAGES_CONTEXT_KEY: InjectionKey<GetMessagesContext> = Symbol.for('GET_MESSAGES_CONTEXT')
45+
// TOREMOVE in main branch
46+
const experimentalChatRelay = (getTalkConfig('local', 'experiments', 'enabled') ?? 0) & CONFIG.EXPERIMENTAL.CHAT_RELAY
4447

4548
/**
4649
* Check whether caught error is from OCS API
@@ -54,6 +57,7 @@ function isAxiosErrorResponse(exception: unknown): exception is AxiosError<strin
5457
let pollingTimeout: NodeJS.Timeout | undefined
5558
let expirationInterval: NodeJS.Timeout | undefined
5659
let pollingErrorTimeout = 1_000
60+
let chatRelaySupported = false
5761

5862
/**
5963
* Composable to provide control logic for fetching messages list
@@ -74,6 +78,7 @@ export function useGetMessagesProvider() {
7478
const loadingNewMessages = ref(false)
7579
const isInitialisingMessages = ref(true)
7680
const stopFetchingOldMessages = ref(false)
81+
let chatRelayEnabled = false
7782

7883
/**
7984
* Returns whether the current participant is a participant of current conversation.
@@ -144,12 +149,14 @@ export function useGetMessagesProvider() {
144149
}
145150
if (oldToken && oldToken !== newToken) {
146151
store.dispatch('cancelPollNewMessages', { requestId: oldToken })
152+
stopChatRelay()
147153
}
148154

149155
if (newToken && canGetMessages) {
150156
handleStartGettingMessagesPreconditions(newToken)
151157
} else {
152158
store.dispatch('cancelPollNewMessages', { requestId: newToken })
159+
stopChatRelay()
153160
}
154161

155162
/** Remove expired messages when joining a room */
@@ -162,6 +169,8 @@ export function useGetMessagesProvider() {
162169
subscribe('networkOnline', handleNetworkOnline)
163170
EventBus.on('route-change', onRouteChange)
164171
EventBus.on('set-context-id-to-bottom', setContextIdToBottom)
172+
EventBus.on('signaling-supported-features', checkChatRelaySupport)
173+
EventBus.on('should-refresh-chat-messages', tryAbortChatRelay)
165174

166175
/** Every 30 seconds we remove expired messages from the store */
167176
expirationInterval = setInterval(() => {
@@ -173,8 +182,12 @@ export function useGetMessagesProvider() {
173182
unsubscribe('networkOnline', handleNetworkOnline)
174183
EventBus.off('route-change', onRouteChange)
175184
EventBus.off('set-context-id-to-bottom', setContextIdToBottom)
185+
EventBus.off('signaling-message-received', addMessageFromChatRelay)
186+
EventBus.off('signaling-supported-features', checkChatRelaySupport)
187+
EventBus.off('should-refresh-chat-messages', tryAbortChatRelay)
176188

177189
store.dispatch('cancelPollNewMessages', { requestId: currentToken.value })
190+
stopChatRelay()
178191
clearInterval(pollingTimeout)
179192
clearInterval(expirationInterval)
180193
})
@@ -195,6 +208,7 @@ export function useGetMessagesProvider() {
195208
if (currentToken.value) {
196209
console.debug('Canceling message request as we are offline')
197210
store.dispatch('cancelPollNewMessages', { requestId: currentToken.value })
211+
stopChatRelay()
198212
}
199213
}
200214

@@ -482,6 +496,10 @@ export function useGetMessagesProvider() {
482496
* @param token token of conversation where a method was called
483497
*/
484498
async function pollNewMessages(token: string) {
499+
if (chatRelayEnabled) {
500+
// Stop polling if chat relay is supported
501+
return
502+
}
485503
// Check that the token has not changed
486504
if (currentToken.value !== token) {
487505
console.debug(`token has changed to ${currentToken.value}, breaking the loop for ${token}`)
@@ -499,6 +517,7 @@ export function useGetMessagesProvider() {
499517
requestId: token,
500518
})
501519
debugTimer.end(`${token} | long polling`, 'status 200')
520+
tryChatRelay()
502521
} catch (exception) {
503522
if (Axios.isCancel(exception)) {
504523
debugTimer.end(`${token} | long polling`, 'cancelled')
@@ -512,6 +531,7 @@ export function useGetMessagesProvider() {
512531
// This is not an error, so reset error timeout and poll again
513532
pollingErrorTimeout = 1_000
514533
clearTimeout(pollingTimeout)
534+
tryChatRelay({ force: true })
515535
pollingTimeout = setTimeout(() => {
516536
pollNewMessages(token)
517537
}, 500)
@@ -539,6 +559,83 @@ export function useGetMessagesProvider() {
539559
}, 500)
540560
}
541561

562+
/**
563+
* Try to start chat relay
564+
*
565+
* @param options
566+
* @param options.force - to skip end reached check when it is guaranteed
567+
*/
568+
function tryChatRelay(options?: { force: boolean }) {
569+
if (chatRelaySupported && (isChatEndReached.value || options?.force)) {
570+
startChatRelay()
571+
}
572+
}
573+
574+
/**
575+
* Check whether chat relay is supported
576+
*
577+
* @param features
578+
*/
579+
function checkChatRelaySupport(features: string[]) {
580+
if (experimentalChatRelay && features.includes('chat-relay')) {
581+
chatRelaySupported = true
582+
tryChatRelay()
583+
} else {
584+
chatRelaySupported = false
585+
}
586+
}
587+
588+
/**
589+
* Initialize chat relay support by stopping polling and listening to chat relay messages
590+
*/
591+
function startChatRelay() {
592+
if (currentToken.value) {
593+
// it might have been set already, ensure we cancel it
594+
store.dispatch('cancelPollNewMessages', { requestId: currentToken.value })
595+
}
596+
chatRelayEnabled = true
597+
EventBus.on('signaling-message-received', addMessageFromChatRelay)
598+
}
599+
600+
/**
601+
* Chat relay sends one message at a time, we update our stores directly
602+
*
603+
* @param payload
604+
* @param payload.token
605+
* @param payload.message
606+
*/
607+
function addMessageFromChatRelay(payload: { token: string, message: ChatMessage }) {
608+
const { token, message } = payload
609+
if (token !== currentToken.value) {
610+
// Guard: Message is for another conversation
611+
// e.g., user switched conversation while messages were in-flight
612+
return
613+
}
614+
615+
chatStore.processChatBlocks(token, [message], { mergeBy: chatStore.getLastKnownId(token) })
616+
store.dispatch('processMessage', { token, message })
617+
}
618+
619+
/**
620+
* Stop chat relay and remove listener
621+
*/
622+
function stopChatRelay() {
623+
chatRelayEnabled = false
624+
EventBus.off('signaling-message-received', addMessageFromChatRelay)
625+
}
626+
627+
/**
628+
* This is needed when something went wrong after starting chat relay
629+
* and the server is no longer sending us messages events
630+
* so we need to abort it to continue getting messages via polling
631+
*/
632+
function tryAbortChatRelay() {
633+
if (chatRelayEnabled && chatRelaySupported) {
634+
stopChatRelay()
635+
pollNewMessages(currentToken.value)
636+
}
637+
}
638+
542639
provide(GET_MESSAGES_CONTEXT_KEY, {
543640
contextMessageId,
544641
loadingOldMessages,

src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export const CONFIG = {
2222
* to allow join the call without page reload
2323
*/
2424
RECOVER_SESSION: 2,
25+
/**
26+
* Since 22.0.3
27+
* Send chat messages via the High performance-backend / websocket
28+
*/
29+
CHAT_RELAY: 4,
2530
},
2631
} as const
2732

src/services/EventBus.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import mitt from 'mitt'
2121
export type Events = {
2222
[key: EventType]: unknown
2323
'audio-player-ended': number
24+
'signaling-message-received': { token: string, message: ChatMessage }
2425
'conversations-received': { singleConversation?: Conversation, fromBrowserStorage?: boolean }
2526
'session-conflict-confirmation': string
2627
'deleted-session-detected': void
@@ -53,6 +54,7 @@ export type Events = {
5354
'signaling-users-joined': [StandaloneSignalingJoinSession[]]
5455
'signaling-users-left': [string[]]
5556
'signaling-all-users-changed-in-call-to-disconnected': void
57+
'signaling-supported-features': string[]
5658
'smart-picker-open': void
5759
'switch-to-conversation': { token: string }
5860
'talk:poll-added': { token: string, message: ChatMessage }

src/utils/signaling.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,7 @@ Signaling.Standalone.prototype.helloResponseReceived = function(data) {
11131113
for (i = 0; i < features.length; i++) {
11141114
this.features[features[i]] = true
11151115
}
1116+
this._trigger('supportedFeatures', features)
11161117
}
11171118

11181119
if (!this.settings.helloAuthParams.internal
@@ -1430,8 +1431,12 @@ Signaling.Standalone.prototype.processRoomEvent = function(data) {
14301431
Signaling.Standalone.prototype.processRoomMessageEvent = function(token, data) {
14311432
switch (data.type) {
14321433
case 'chat':
1433-
// FIXME this is not listened to
1434-
EventBus.emit('should-refresh-chat-messages')
1434+
if ('comment' in data.chat) {
1435+
EventBus.emit('signaling-message-received', { token, message: { ...data.chat.comment, token } })
1436+
} else {
1437+
// TOREMOVE after HPB next release
1438+
EventBus.emit('should-refresh-chat-messages')
1439+
}
14351440
break
14361441
case 'recording':
14371442
EventBus.emit('signaling-recording-status-changed', [token, data.recording.status])

0 commit comments

Comments
 (0)