Skip to content

Commit cd0512a

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 ae2b48d commit cd0512a

File tree

4 files changed

+97
-3
lines changed

4 files changed

+97
-3
lines changed

src/composables/useGetMessages.ts

Lines changed: 82 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,7 @@ type GetMessagesContext = {
4142
}
4243

4344
const GET_MESSAGES_CONTEXT_KEY: InjectionKey<GetMessagesContext> = Symbol.for('GET_MESSAGES_CONTEXT')
45+
const experimentalChatRelay = (getTalkConfig('local', 'experiments', 'enabled') ?? 0) & CONFIG.EXPERIMENTAL.CHAT_RELAY
4446

4547
/**
4648
* Check whether caught error is from OCS API
@@ -54,6 +56,7 @@ function isAxiosErrorResponse(exception: unknown): exception is AxiosError<strin
5456
let pollingTimeout: NodeJS.Timeout | undefined
5557
let expirationInterval: NodeJS.Timeout | undefined
5658
let pollingErrorTimeout = 1_000
59+
let chatRelaySupported = false
5760

5861
/**
5962
* Composable to provide control logic for fetching messages list
@@ -74,6 +77,7 @@ export function useGetMessagesProvider() {
7477
const loadingNewMessages = ref(false)
7578
const isInitialisingMessages = ref(true)
7679
const stopFetchingOldMessages = ref(false)
80+
let chatRelayEnabled = false
7781

7882
/**
7983
* Returns whether the current participant is a participant of current conversation.
@@ -144,12 +148,14 @@ export function useGetMessagesProvider() {
144148
}
145149
if (oldToken && oldToken !== newToken) {
146150
store.dispatch('cancelPollNewMessages', { requestId: oldToken })
151+
stopChatRelay()
147152
}
148153

149154
if (newToken && canGetMessages) {
150155
handleStartGettingMessagesPreconditions(newToken)
151156
} else {
152157
store.dispatch('cancelPollNewMessages', { requestId: newToken })
158+
stopChatRelay()
153159
}
154160

155161
/** Remove expired messages when joining a room */
@@ -162,6 +168,7 @@ export function useGetMessagesProvider() {
162168
subscribe('networkOnline', handleNetworkOnline)
163169
EventBus.on('route-change', onRouteChange)
164170
EventBus.on('set-context-id-to-bottom', setContextIdToBottom)
171+
EventBus.on('signaling-supported-features', checkChatRelaySupport)
165172

166173
/** Every 30 seconds we remove expired messages from the store */
167174
expirationInterval = setInterval(() => {
@@ -173,8 +180,11 @@ export function useGetMessagesProvider() {
173180
unsubscribe('networkOnline', handleNetworkOnline)
174181
EventBus.off('route-change', onRouteChange)
175182
EventBus.off('set-context-id-to-bottom', setContextIdToBottom)
183+
EventBus.off('signaling-message-received', addMessageFromChatRelay)
184+
EventBus.off('signaling-supported-features', checkChatRelaySupport)
176185

177186
store.dispatch('cancelPollNewMessages', { requestId: currentToken.value })
187+
stopChatRelay()
178188
clearInterval(pollingTimeout)
179189
clearInterval(expirationInterval)
180190
})
@@ -195,6 +205,7 @@ export function useGetMessagesProvider() {
195205
if (currentToken.value) {
196206
console.debug('Canceling message request as we are offline')
197207
store.dispatch('cancelPollNewMessages', { requestId: currentToken.value })
208+
stopChatRelay()
198209
}
199210
}
200211

@@ -482,6 +493,10 @@ export function useGetMessagesProvider() {
482493
* @param token token of conversation where a method was called
483494
*/
484495
async function pollNewMessages(token: string) {
496+
if (chatRelayEnabled) {
497+
// Stop polling if chat relay is supported
498+
return
499+
}
485500
// Check that the token has not changed
486501
if (currentToken.value !== token) {
487502
console.debug(`token has changed to ${currentToken.value}, breaking the loop for ${token}`)
@@ -499,6 +514,7 @@ export function useGetMessagesProvider() {
499514
requestId: token,
500515
})
501516
debugTimer.end(`${token} | long polling`, 'status 200')
517+
tryChatRelay()
502518
} catch (exception) {
503519
if (Axios.isCancel(exception)) {
504520
debugTimer.end(`${token} | long polling`, 'cancelled')
@@ -512,6 +528,7 @@ export function useGetMessagesProvider() {
512528
// This is not an error, so reset error timeout and poll again
513529
pollingErrorTimeout = 1_000
514530
clearTimeout(pollingTimeout)
531+
tryChatRelay({ force: true })
515532
pollingTimeout = setTimeout(() => {
516533
pollNewMessages(token)
517534
}, 500)
@@ -539,6 +556,70 @@ export function useGetMessagesProvider() {
539556
}, 500)
540557
}
541558

559+
/**
560+
* Try to start chat relay
561+
*
562+
* @param options
563+
* @param options.force - to skip end reached check when it is guaranteed
564+
*/
565+
function tryChatRelay(options?: { force: boolean }) {
566+
if (chatRelaySupported && (isChatEndReached.value || options?.force)) {
567+
startChatRelay()
568+
}
569+
}
570+
571+
/**
572+
* Check whether chat relay is supported
573+
*
574+
* @param features
575+
*/
576+
function checkChatRelaySupport(features: string[]) {
577+
if (experimentalChatRelay && features.includes('chat-relay')) {
578+
chatRelaySupported = true
579+
}
580+
}
581+
582+
/**
583+
* Initialize chat relay support by stopping polling and listening to chat relay messages
584+
*/
585+
function startChatRelay() {
586+
if (currentToken.value) {
587+
// it might have been set already, ensure we cancel it
588+
store.dispatch('cancelPollNewMessages', { requestId: currentToken.value })
589+
stopChatRelay()
590+
}
591+
chatRelayEnabled = true
592+
EventBus.on('signaling-message-received', addMessageFromChatRelay)
593+
}
594+
595+
/**
596+
*
597+
* Chat relay sends one message at a time, we update our stores directly
598+
*
599+
* @param payload
600+
* @param payload.token
601+
* @param payload.message
602+
*/
603+
function addMessageFromChatRelay(payload: { token: string, message: ChatMessage }) {
604+
const { token, message } = payload
605+
if (token !== currentToken.value) {
606+
// Guard: Message is for another conversation
607+
// e.g., user switched conversation while messages were in-flight
608+
return
609+
}
610+
611+
chatStore.processChatBlocks(token, [message], { mergeBy: chatStore.getLastKnownId(token) })
612+
store.dispatch('processMessage', { token, message })
613+
}
614+
615+
/**
616+
* Stop chat relay and remove listener
617+
*/
618+
function stopChatRelay() {
619+
chatRelayEnabled = false
620+
EventBus.off('signaling-message-received', addMessageFromChatRelay)
621+
}
622+
542623
provide(GET_MESSAGES_CONTEXT_KEY, {
543624
contextMessageId,
544625
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ 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 }
25+
'chat-relay-supported': void
2426
'conversations-received': { singleConversation?: Conversation, fromBrowserStorage?: boolean }
2527
'session-conflict-confirmation': string
2628
'deleted-session-detected': void
@@ -53,6 +55,7 @@ export type Events = {
5355
'signaling-users-joined': [StandaloneSignalingJoinSession[]]
5456
'signaling-users-left': [string[]]
5557
'signaling-all-users-changed-in-call-to-disconnected': void
58+
'signaling-supported-features': string[]
5659
'smart-picker-open': void
5760
'switch-to-conversation': { token: string }
5861
'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', Object.keys(this.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 })
1436+
} else {
1437+
// FIXME this is not listened to
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)