From 957461bba1ec8d5cef23da29a14740584dd17880 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Mon, 24 Jun 2024 15:32:57 +0200 Subject: [PATCH] feat(participants): update participants changed data from standalone signaling Signed-off-by: Maksim Sukharev --- src/composables/useGetParticipants.js | 12 ++++ src/stores/__tests__/signaling.spec.js | 46 +++++++++++++ src/stores/signaling.ts | 89 ++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) diff --git a/src/composables/useGetParticipants.js b/src/composables/useGetParticipants.js index 4e03d1c53ef..83a64ca81fd 100644 --- a/src/composables/useGetParticipants.js +++ b/src/composables/useGetParticipants.js @@ -38,6 +38,8 @@ export function useGetParticipants(isActive = ref(true), isTopBar = true) { EventBus.on('signaling-users-in-room', updateUsersFromInternalSignaling) EventBus.on('signaling-users-joined', updateUsersJoinedFromStandaloneSignaling) EventBus.on('signaling-users-left', updateUsersLeftFromStandaloneSignaling) + EventBus.on('signaling-users-changed', updateUsersChangedFromStandaloneSignaling) + EventBus.on('signaling-all-users-changed-in-call-to-disconnected', updateUsersCallDisconnectedFromStandaloneSignaling) // FIXME this works only temporary until signaling is fixed to be only on the calls // Then we have to search for another solution. Maybe the room list which we update // periodically gets a hash of all online sessions? @@ -63,6 +65,14 @@ export function useGetParticipants(isActive = ref(true), isTopBar = true) { const signalingStore = useSignalingStore() signalingStore.updateParticipantsLeftFromStandaloneSignaling(signalingSessionIds) } + const updateUsersChangedFromStandaloneSignaling = ([participants]) => { + const signalingStore = useSignalingStore() + signalingStore.updateParticipantsChangedFromStandaloneSignaling(token.value, participants) + } + const updateUsersCallDisconnectedFromStandaloneSignaling = () => { + const signalingStore = useSignalingStore() + signalingStore.updateParticipantsCallDisconnectedFromStandaloneSignaling(token.value) + } /** * Stop the get participants listeners * @@ -72,6 +82,8 @@ export function useGetParticipants(isActive = ref(true), isTopBar = true) { EventBus.off('signaling-users-in-room', updateUsersFromInternalSignaling) EventBus.off('signaling-users-joined', updateUsersJoinedFromStandaloneSignaling) EventBus.off('signaling-users-left', updateUsersLeftFromStandaloneSignaling) + EventBus.off('signaling-users-changed', updateUsersChangedFromStandaloneSignaling) + EventBus.off('signaling-all-users-changed-in-call-to-disconnected', updateUsersCallDisconnectedFromStandaloneSignaling) EventBus.off('signaling-participant-list-changed', debounceUpdateParticipants) unsubscribe('guest-promoted', onJoinedConversation) } diff --git a/src/stores/__tests__/signaling.spec.js b/src/stores/__tests__/signaling.spec.js index c201e96daf0..1022b5568c1 100644 --- a/src/stores/__tests__/signaling.spec.js +++ b/src/stores/__tests__/signaling.spec.js @@ -135,6 +135,14 @@ describe('signalingStore', () => { { userid: '', user: { displayname: 'Guest' }, sessionid: 'signaling-id-4', roomsessionid: 'session-id-4' }, ] + const participantsChangedPayload = [ + { userId: 'user1', sessionId: 'signaling-id-1', inCall: 7, participantType: 1, lastPing: 1717192800, participantPermissions: 254 }, + { userId: 'user2', sessionId: 'signaling-id-2', inCall: 7, participantType: 3, lastPing: 1717192800, participantPermissions: 254 }, + { userId: 'user2', sessionId: 'signaling-id-3', inCall: 0, participantType: 3, lastPing: 1717192800, participantPermissions: 254 }, + { userId: '', displayName: 'Guest New', sessionId: 'signaling-id-4', inCall: 7, participantType: 6, lastPing: 1717192800, participantPermissions: 254 }, + { userId: '', sessionId: 'signaling-id-unknown', inCall: 7, participantType: 3, lastPing: 1717192800, participantPermissions: 254 }, + ] + it('should return a mapped object for a known session', () => { // Arrange populateParticipantsStore() @@ -216,5 +224,43 @@ describe('signalingStore', () => { { token: TOKEN, attendeeId: 3, updatedData: { inCall: 0, sessionIds: [] } }) }) + + it('should update participant objects for a known session on change', () => { + // Arrange + jest.spyOn(vuexStore, 'commit') + populateParticipantsStore() + signalingStore.updateParticipantsJoinedFromStandaloneSignaling(TOKEN, participantsJoinedPayload) + + // Act + signalingStore.updateParticipantsChangedFromStandaloneSignaling(TOKEN, participantsChangedPayload) + + // Assert + expect(vuexStore.commit).toHaveBeenCalledTimes(6) + expect(vuexStore.commit).toHaveBeenNthCalledWith(4, 'updateParticipant', + { token: TOKEN, attendeeId: 1, updatedData: { inCall: 7, participantType: 1, lastPing: 1717192800, permissions: 254 } }) + expect(vuexStore.commit).toHaveBeenNthCalledWith(5, 'updateParticipant', + { token: TOKEN, attendeeId: 2, updatedData: { inCall: 7, participantType: 3, lastPing: 1717192800, permissions: 254 } }) + expect(vuexStore.commit).toHaveBeenNthCalledWith(6, 'updateParticipant', + { token: TOKEN, attendeeId: 3, updatedData: { displayName: 'Guest New', inCall: 7, participantType: 6, lastPing: 1717192800, permissions: 254 } }) + }) + + it('should update participant objects for a known session on call disconnect', () => { + // Arrange + jest.spyOn(vuexStore, 'commit') + populateParticipantsStore() + signalingStore.updateParticipantsJoinedFromStandaloneSignaling(TOKEN, participantsJoinedPayload) + + // Act + signalingStore.updateParticipantsCallDisconnectedFromStandaloneSignaling(TOKEN) + + // Assert + expect(vuexStore.commit).toHaveBeenCalledTimes(6) + expect(vuexStore.commit).toHaveBeenNthCalledWith(4, 'updateParticipant', + { token: TOKEN, attendeeId: 1, updatedData: { inCall: 0 } }) + expect(vuexStore.commit).toHaveBeenNthCalledWith(5, 'updateParticipant', + { token: TOKEN, attendeeId: 2, updatedData: { inCall: 0 } }) + expect(vuexStore.commit).toHaveBeenNthCalledWith(6, 'updateParticipant', + { token: TOKEN, attendeeId: 3, updatedData: { inCall: 0 } }) + }) }) }) diff --git a/src/stores/signaling.ts b/src/stores/signaling.ts index 2c5dd28a2ff..11913875183 100644 --- a/src/stores/signaling.ts +++ b/src/stores/signaling.ts @@ -3,9 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import Hex from 'crypto-js/enc-hex.js' +import SHA1 from 'crypto-js/sha1.js' import { defineStore } from 'pinia' import Vue from 'vue' +import { useGuestNameStore } from './guestName.js' import { ATTENDEE, PARTICIPANT } from '../constants.js' import store from '../store/index.js' import type { Participant } from '../types' @@ -36,6 +39,23 @@ type StandaloneSignalingJoinPayload = { sessionid: string, // Standalone signaling id roomsessionid: string, // Nextcloud id } +type StandaloneSignalingChangePayload = { + sessionId: string, // Standalone signaling id + participantType: number, + participantPermissions: number, + inCall: number, + lastPing: number, + nextcloudSessionId?: string, // Nextcloud id + userId?: string, // For registered users only + displayName?: string, +} +type StandaloneUpdatePayload = Record type State = { sessions: Record, @@ -223,5 +243,74 @@ export const useSignalingStore = defineStore('signaling', { } }, + /** + * Update participants changed in store according to data from standalone signaling server + * + * @param token the conversation token; + * @param participants the changed participant objects; + */ + updateParticipantsChangedFromStandaloneSignaling(token: string, participants: StandaloneSignalingChangePayload[]) { + const guestNameStore = useGuestNameStore() + const attendeeUsersToUpdate: StandaloneUpdatePayload = {} + + for (const participant of participants) { + const session = this.getSignalingSession(participant.sessionId) + if (!session?.attendeeId) { + continue + } + const { token, attendeeId } = session + + if (!attendeeUsersToUpdate[attendeeId]) { + attendeeUsersToUpdate[attendeeId] = { + participantType: participant.participantType, + permissions: participant.participantPermissions, + inCall: participant.inCall, + lastPing: participant.lastPing, + } + } else { + // Participant might join from several devices + attendeeUsersToUpdate[attendeeId].inCall + = Math.max(attendeeUsersToUpdate[attendeeId].inCall, participant.inCall) + } + if (participant.displayName) { + attendeeUsersToUpdate[attendeeId].displayName = participant.displayName + const attendee = store.getters.getParticipant(token, attendeeId) as Participant + + if (attendee.displayName !== participant.displayName + && (participant.participantType === PARTICIPANT.TYPE.GUEST + || participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR)) { + guestNameStore.addGuestName({ + token, + actorId: Hex.stringify(SHA1(attendee.sessionIds[0])), + actorDisplayName: participant.displayName, + }, { noUpdate: false }) + } + } + } + + for (const [attendeeId, updatedData] of Object.entries(attendeeUsersToUpdate)) { + store.commit('updateParticipant', { + token, + attendeeId: +attendeeId, + updatedData, + }) + } + }, + + /** + * Update participants (end call for everyone) in store according to data from standalone signaling server + * + * @param token conversation token; + */ + updateParticipantsCallDisconnectedFromStandaloneSignaling(token: string) { + const attendeeUsers = store.getters.participantsList(token) as Participant[] + for (const attendee of attendeeUsers) { + store.commit('updateParticipant', { + token, + attendeeId: attendee.attendeeId, + updatedData: { inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED } + }) + } + }, }, })