@@ -20,7 +20,8 @@ import { subscribe, unsubscribe } from '@nextcloud/event-bus'
2020import { computed , inject , onBeforeUnmount , provide , ref , watch } from 'vue'
2121import { START_LOCATION , useRoute } from 'vue-router'
2222import { 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'
2425import { EventBus } from '../services/EventBus.ts'
2526import { useChatStore } from '../stores/chat.ts'
2627import { useChatExtrasStore } from '../stores/chatExtras.ts'
@@ -41,6 +42,8 @@ type GetMessagesContext = {
4142}
4243
4344const 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
5457let pollingTimeout : NodeJS . Timeout | undefined
5558let expirationInterval : NodeJS . Timeout | undefined
5659let 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,
0 commit comments