Skip to content

Commit

Permalink
Merge pull request #10523 from nextcloud/fix/10497/cancel-second-request
Browse files Browse the repository at this point in the history
fix(participantsStore) - handle participants fetch through the store
  • Loading branch information
Antreesy authored Sep 19, 2023
2 parents b22ce4e + d17d9df commit deb906b
Show file tree
Hide file tree
Showing 6 changed files with 320 additions and 212 deletions.
4 changes: 0 additions & 4 deletions src/components/RightSidebar/Participants/ParticipantsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ import getParticipants from '../../../mixins/getParticipants.js'
import { searchPossibleConversations } from '../../../services/conversationsService.js'
import { EventBus } from '../../../services/EventBus.js'
import { addParticipant } from '../../../services/participantsService.js'
import { useGuestNameStore } from '../../../stores/guestName.js'
import CancelableRequest from '../../../utils/cancelableRequest.js'
export default {
Expand Down Expand Up @@ -99,11 +98,8 @@ export default {
setup() {
const { sortParticipants } = useSortParticipants()
// FIXME move to getParticipants when replace with composable
const guestNameStore = useGuestNameStore()
return {
guestNameStore,
sortParticipants,
}
},
Expand Down
9 changes: 7 additions & 2 deletions src/components/RightSidebar/RightSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,14 @@ export default {
}
},
// Switch tab for guest if he is demoted from moderators
isModeratorOrUser(newValue) {
if (!newValue) {
if (newValue) {
// Fetch participants list if guest was promoted to moderators
this.$nextTick(() => {
emit('guest-promoted', { token: this.token })
})
} else {
// Switch active tab to chat if guest was demoted from moderators
this.activeTab = 'chat'
}
},
Expand Down
28 changes: 2 additions & 26 deletions src/components/TopBar/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,8 @@ import TopBarMediaControls from './TopBarMediaControls.vue'
import TopBarMenu from './TopBarMenu.vue'
import { CONVERSATION } from '../../constants.js'
import getParticipants from '../../mixins/getParticipants.js'
import isInLobby from '../../mixins/isInLobby.js'
import BrowserStorage from '../../services/BrowserStorage.js'
import { useGuestNameStore } from '../../stores/guestName.js'
import { getStatusMessage } from '../../utils/userStatus.js'
import { localCallParticipantModel, localMediaModel } from '../../utils/webrtc/index.js'
Expand Down Expand Up @@ -196,7 +195,7 @@ export default {
mixins: [
richEditor,
getParticipants,
isInLobby,
],
props: {
Expand All @@ -214,15 +213,6 @@ export default {
},
},
setup() {
// FIXME move to getParticipants when replace with composable
const guestNameStore = useGuestNameStore()
return {
guestNameStore,
}
},
data: () => {
return {
unreadNotificationHandle: null,
Expand Down Expand Up @@ -353,18 +343,6 @@ export default {
this.notifyUnreadMessages(null)
}
},
isModeratorOrUser(newValue) {
if (newValue) {
// fetch participants immediately when becomes available
this.cancelableGetParticipants()
}
},
},
beforeMount() {
// Initialises the get participants mixin for participants counter
this.initialiseGetParticipantsMixin()
},
mounted() {
Expand All @@ -382,8 +360,6 @@ export default {
document.removeEventListener('MSFullscreenChange', this.fullScreenChanged, false)
document.removeEventListener('webkitfullscreenchange', this.fullScreenChanged, false)
document.body.classList.remove('has-topbar')
this.stopGetParticipantsMixin()
},
methods: {
Expand Down
67 changes: 10 additions & 57 deletions src/mixins/getParticipants.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import Hex from 'crypto-js/enc-hex.js'
import SHA1 from 'crypto-js/sha1.js'
import debounce from 'debounce'

import Axios from '@nextcloud/axios'
import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'

import { PARTICIPANT } from '../constants.js'
import { EventBus } from '../services/EventBus.js'
import { fetchParticipants } from '../services/participantsService.js'
import CancelableRequest from '../utils/cancelableRequest.js'
import isInLobby from './isInLobby.js'

const getParticipants = {
Expand All @@ -41,10 +34,6 @@ const getParticipants = {
data() {
return {
participantsInitialised: false,
/**
* Stores the cancel function for cancelableGetParticipants
*/
cancelGetParticipants: () => {},
fetchingParticipants: false,
}
},
Expand All @@ -65,12 +54,16 @@ const getParticipants = {
// Then we have to search for another solution. Maybe the room list which we update
// periodically gets a hash of all online sessions?
EventBus.$on('signaling-participant-list-changed', this.debounceUpdateParticipants)

subscribe('guest-promoted', this.onJoinedConversation)
},

stopGetParticipantsMixin() {
EventBus.$off('route-change', this.onRouteChange)
EventBus.$off('joined-conversation', this.onJoinedConversation)
EventBus.$off('signaling-participant-list-changed', this.debounceUpdateParticipants)

unsubscribe('guest-promoted', this.onJoinedConversation)
},

onRouteChange() {
Expand Down Expand Up @@ -116,53 +109,13 @@ const getParticipants = {
return
}

try {
// The token must be stored in a local variable to ensure that
// the same token is used after waiting.
const token = this.token
// Clear previous requests if there's one pending
this.cancelGetParticipants('Cancel get participants')
// Get a new cancelable request function and cancel function pair
this.fetchingParticipants = true
const { request, cancel } = CancelableRequest(fetchParticipants)
this.cancelGetParticipants = cancel
const participants = await request(token)
this.$store.dispatch('purgeParticipantsStore', token)

const hasUserStatuses = !!participants.headers['x-nextcloud-has-user-statuses']
participants.data.ocs.data.forEach(participant => {
this.$store.dispatch('addParticipant', {
token,
participant,
})
if (participant.participantType === PARTICIPANT.TYPE.GUEST
|| participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR) {
// FIXME replace mixin with composable. until then
// guestNameStore should be set up at component level
this.guestNameStore.addGuestName({
token,
actorId: Hex.stringify(SHA1(participant.sessionIds[0])),
actorDisplayName: participant.displayName,
}, { noUpdate: false })
} else if (participant.actorType === 'users' && hasUserStatuses) {
emit('user_status:status.updated', {
status: participant.status,
message: participant.statusMessage,
icon: participant.statusIcon,
clearAt: participant.statusClearAt,
userId: participant.actorId,
})
}
})
this.fetchingParticipants = true

const response = await this.$store.dispatch('fetchParticipants', { token: this.token })
if (response) {
this.participantsInitialised = true
} catch (exception) {
if (!Axios.isCancel(exception)) {
console.error(exception)
showError(t('spreed', 'An error occurred while fetching the participants'))
}
} finally {
this.fetchingParticipants = false
}
this.fetchingParticipants = false
},
},
}
Expand Down
91 changes: 91 additions & 0 deletions src/store/participantsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import Hex from 'crypto-js/enc-hex.js'
import SHA1 from 'crypto-js/sha1.js'
import Vue from 'vue'

import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router'

import { PARTICIPANT } from '../constants.js'
Expand All @@ -43,9 +46,12 @@ import {
removeAllPermissionsFromParticipant,
setPermissions,
setTyping,
fetchParticipants,
} from '../services/participantsService.js'
import SessionStorage from '../services/SessionStorage.js'
import { talkBroadcastChannel } from '../services/talkBroadcastChannel.js'
import { useGuestNameStore } from '../stores/guestName.js'
import CancelableRequest from '../utils/cancelableRequest.js'

const state = {
attendees: {
Expand All @@ -60,6 +66,12 @@ const state = {
},
speaking: {
},
/**
* Stores the cancel function returned by `cancelableFetchParticipants`,
* which allows to cancel the previous request for participants
* when quickly switching to a new conversation.
*/
cancelFetchParticipants: null,
}

const getters = {
Expand Down Expand Up @@ -414,6 +426,10 @@ const mutations = {
Vue.delete(state.peers, token)
}
},

setCancelFetchParticipants(state, cancelFunction) {
state.cancelFetchParticipants = cancelFunction
},
}

const actions = {
Expand Down Expand Up @@ -536,6 +552,81 @@ const actions = {
commit('updateParticipant', { token, attendeeId: attendee.attendeeId, updatedData })
},

/**
* Fetches participants that belong to a particular conversation
* specified with its token.
*
* @param {object} context default store context;
* @param {object} data the wrapping object;
* @param {string} data.token the conversation token;
* @return {object|null}
*/
async fetchParticipants(context, { token }) {
const guestNameStore = useGuestNameStore()
// Cancel a previous request
context.dispatch('cancelFetchParticipants')
// Get a new cancelable request function and cancel function pair
const { request, cancel } = CancelableRequest(fetchParticipants)
// Assign the new cancel function to our data value
context.commit('setCancelFetchParticipants', cancel)

try {
const response = await request(token)
context.dispatch('purgeParticipantsStore', token)

const hasUserStatuses = !!response.headers['x-nextcloud-has-user-statuses']

response.data.ocs.data.forEach(participant => {
context.dispatch('addParticipant', { token, participant })

if (participant.participantType === PARTICIPANT.TYPE.GUEST
|| participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR) {
guestNameStore.addGuestName({
token,
actorId: Hex.stringify(SHA1(participant.sessionIds[0])),
actorDisplayName: participant.displayName,
}, { noUpdate: false })
} else if (participant.actorType === 'users' && hasUserStatuses) {
emit('user_status:status.updated', {
status: participant.status,
message: participant.statusMessage,
icon: participant.statusIcon,
clearAt: participant.statusClearAt,
userId: participant.actorId,
})
}
})

// Discard current cancel function
context.commit('setCancelFetchParticipants', null)

return response
} catch (exception) {
if (exception?.response.status === 403) {
context.dispatch('fetchConversation', { token })
} else if (!CancelableRequest.isCancel(exception)) {
console.error(exception)
showError(t('spreed', 'An error occurred while fetching the participants'))
}
return null
}
},

/**
* Cancels a previously running "fetchParticipants" action if applicable.
*
* @param {object} context default store context;
* @return {boolean} true if a request got cancelled, false otherwise
*/
cancelFetchParticipants(context) {
if (context.state.cancelFetchParticipants) {
context.state.cancelFetchParticipants('canceled')
context.commit('setCancelFetchParticipants', null)
return true
}
return false
},

async joinCall({ commit, getters }, { token, participantIdentifier, flags, silent }) {
if (!participantIdentifier?.sessionId) {
console.error('Trying to join call without sessionId')
Expand Down
Loading

0 comments on commit deb906b

Please sign in to comment.