diff --git a/src/components/RightSidebar/Participants/ParticipantsTab.vue b/src/components/RightSidebar/Participants/ParticipantsTab.vue index 8dea1339b5..6b649250fb 100644 --- a/src/components/RightSidebar/Participants/ParticipantsTab.vue +++ b/src/components/RightSidebar/Participants/ParticipantsTab.vue @@ -85,7 +85,7 @@ import ParticipantsList from './ParticipantsList.vue' import ParticipantsListVirtual from './ParticipantsListVirtual.vue' import ParticipantsSearchResults from './ParticipantsSearchResults.vue' import { useArrowNavigation } from '../../../composables/useArrowNavigation.js' -import { useGetParticipants } from '../../../composables/useGetParticipants.js' +import { useGetParticipants } from '../../../composables/useGetParticipants.ts' import { useGetToken } from '../../../composables/useGetToken.ts' import { useId } from '../../../composables/useId.ts' import { useIsInCall } from '../../../composables/useIsInCall.js' diff --git a/src/components/RightSidebar/RightSidebar.vue b/src/components/RightSidebar/RightSidebar.vue index 834c88d252..89a471e456 100644 --- a/src/components/RightSidebar/RightSidebar.vue +++ b/src/components/RightSidebar/RightSidebar.vue @@ -140,7 +140,7 @@ import RightSidebarContent from './RightSidebarContent.vue' import SearchMessagesTab from './SearchMessages/SearchMessagesTab.vue' import SharedItemsTab from './SharedItems/SharedItemsTab.vue' import SipSettings from './SipSettings.vue' -import { useGetParticipants } from '../../composables/useGetParticipants.js' +import { useGetParticipants } from '../../composables/useGetParticipants.ts' import { useGetToken } from '../../composables/useGetToken.ts' import { CONVERSATION, PARTICIPANT, WEBINAR } from '../../constants.ts' import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts' diff --git a/src/composables/useGetParticipants.js b/src/composables/useGetParticipants.ts similarity index 75% rename from src/composables/useGetParticipants.js rename to src/composables/useGetParticipants.ts index fd9cf7aa9f..3625d7b743 100644 --- a/src/composables/useGetParticipants.js +++ b/src/composables/useGetParticipants.ts @@ -3,6 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import type { + Conversation, + Participant, + SignalingSessionPayload, + StandaloneSignalingLeaveSession, + StandaloneSignalingUpdateSession, +} from '../types/index.ts' + import { createSharedComposable } from '@vueuse/core' import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue' import { CONFIG, CONVERSATION } from '../constants.ts' @@ -17,6 +25,12 @@ import { useStore } from './useStore.js' const experimentalUpdateParticipants = (getTalkConfig('local', 'experiments', 'enabled') ?? 0) & CONFIG.EXPERIMENTAL.UPDATE_PARTICIPANTS +let fetchingParticipants = false +let pendingChanges = true +let throttleFastUpdateTimeout: NodeJS.Timeout | undefined +let throttleSlowUpdateTimeout: NodeJS.Timeout | undefined +let throttleLongUpdateTimeout: NodeJS.Timeout | undefined + /** * Composable to control logic for fetching participants list * @@ -31,16 +45,11 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { const isActive = computed(() => activeTab.value === 'participants') const token = useGetToken() - const conversation = computed(() => store.getters.conversation(token.value)) - const isInLobby = computed(() => store.getters.isInLobby) - const isModeratorOrUser = computed(() => store.getters.isModeratorOrUser) + const conversation = computed(() => store.getters.conversation(token.value)) + const isInLobby = computed(() => store.getters.isInLobby) + const isModeratorOrUser = computed(() => store.getters.isModeratorOrUser) const isOneToOneConversation = computed(() => conversation.value?.type === CONVERSATION.TYPE.ONE_TO_ONE || conversation.value?.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER) - let fetchingParticipants = false - let pendingChanges = true - let throttleFastUpdateTimeout = null - let throttleSlowUpdateTimeout = null - let throttleLongUpdateTimeout = null /** * Initialise the get participants listeners @@ -64,7 +73,10 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { EventBus.on('signaling-users-changed', checkCurrentUserPermissions) } - const handleUsersUpdated = async ([users]) => { + /** + * Patch participants list from signaling messages (joined/changed) + */ + function handleUsersUpdated([users]: [SignalingSessionPayload[]]) { if (sessionStore.updateSessions(token.value, users)) { throttleUpdateParticipants() } else { @@ -72,7 +84,10 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { } } - const checkCurrentUserPermissions = async ([users]) => { + /** + * Check if current user permission has been changed from signaling messages + */ + async function checkCurrentUserPermissions([users]: [StandaloneSignalingUpdateSession[]]) { // TODO: move logic to sessionStore once experimental flag is dropped const currentUser = users.find((user) => { return user.userId ? user.userId === actorStore.userId : user.actorId === actorStore.actorId @@ -91,14 +106,22 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { } } - const handleUsersLeft = ([sessionIds]) => { + /** + * Patch participants list from signaling messages (left) + */ + function handleUsersLeft([sessionIds]: [StandaloneSignalingLeaveSession[]]) { sessionStore.updateSessionsLeft(token.value, sessionIds) throttleLongUpdate() } - const handleUsersDisconnected = () => { + + /** + * Patch participants list from signaling messages (end call for everyone) + */ + function handleUsersDisconnected() { sessionStore.updateParticipantsDisconnectedFromStandaloneSignaling(token.value) throttleLongUpdate() } + /** * Stop the get participants listeners * @@ -115,7 +138,10 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { EventBus.off('signaling-users-changed', checkCurrentUserPermissions) } - const onJoinedConversation = () => { + /** + * Trigger participants list update upon joining + */ + async function onJoinedConversation() { if (isOneToOneConversation.value || experimentalUpdateParticipants) { cancelableGetParticipants() } else { @@ -123,7 +149,12 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { } } - const throttleUpdateParticipants = () => { + /** + * Schedule a participants list update depending on conditions: + * - participant list is visible - fast update + * - chat is open, tab is in background, user is not in the current call - slow update + */ + function throttleUpdateParticipants() { if (!isActive.value && !isInCall.value) { // Update is ignored but there is a flag to force the participants update pendingChanges = true @@ -138,7 +169,10 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { pendingChanges = false } - const cancelableGetParticipants = async () => { + /** + * Update participants list + */ + async function cancelableGetParticipants() { if (fetchingParticipants || token.value === '' || isInLobby.value || !isModeratorOrUser.value) { return } @@ -150,17 +184,28 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { fetchingParticipants = false } - const throttleFastUpdate = () => { + /** + * Schedule a participants list update (in 3 seconds) + */ + function throttleFastUpdate() { if (!fetchingParticipants && !throttleFastUpdateTimeout) { throttleFastUpdateTimeout = setTimeout(cancelableGetParticipants, 3_000) } } - const throttleSlowUpdate = () => { + + /** + * Schedule a participants list update (in 15 seconds) + */ + function throttleSlowUpdate() { if (!fetchingParticipants && !throttleSlowUpdateTimeout) { throttleSlowUpdateTimeout = setTimeout(cancelableGetParticipants, 15_000) } } - const throttleLongUpdate = () => { + + /** + * Schedule a participants list update (in 60 seconds) + */ + function throttleLongUpdate() { if (!fetchingParticipants && !throttleLongUpdateTimeout) { throttleLongUpdateTimeout = setTimeout(cancelableGetParticipants, 60_000) } @@ -196,11 +241,11 @@ function useGetParticipantsComposable(activeTab = ref('participants')) { */ function cancelPendingUpdates() { clearTimeout(throttleFastUpdateTimeout) - throttleFastUpdateTimeout = null + throttleFastUpdateTimeout = undefined clearTimeout(throttleSlowUpdateTimeout) - throttleSlowUpdateTimeout = null + throttleSlowUpdateTimeout = undefined clearTimeout(throttleLongUpdateTimeout) - throttleLongUpdateTimeout = null + throttleLongUpdateTimeout = undefined } return { diff --git a/src/stores/session.ts b/src/stores/session.ts index 8e2ca82e1f..98a17b763c 100644 --- a/src/stores/session.ts +++ b/src/stores/session.ts @@ -6,6 +6,7 @@ import type { InternalSignalingSession, Participant, + SignalingSessionPayload, StandaloneSignalingJoinSession, StandaloneSignalingLeaveSession, StandaloneSignalingUpdateSession, @@ -27,11 +28,6 @@ type Session = { inCall: number | undefined } -type SignalingSessionPayload = - | InternalSignalingSession - | StandaloneSignalingJoinSession - | StandaloneSignalingUpdateSession - type ParticipantUpdatePayload = { displayName?: string inCall: number diff --git a/src/types/index.ts b/src/types/index.ts index 74c56b4dde..1a6d62d8d8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -142,6 +142,11 @@ export type StandaloneSignalingUpdateSession = { virtual?: boolean } +export type SignalingSessionPayload = + | InternalSignalingSession + | StandaloneSignalingJoinSession + | StandaloneSignalingUpdateSession + // Conversations export type Conversation = components['schemas']['Room'] & { // internal parameter up to mock a conversation object