Merge pull request #15307 from nextcloud/chore/noid/migrate-actor-store

chore(pinia): migrate actor store to pinia
This commit is contained in:
Dorra 2025-06-11 21:26:20 +02:00 committed by GitHub
commit 2a4341f2b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 755 additions and 631 deletions

View file

@ -47,6 +47,7 @@ import Router from './router/router.ts'
import BrowserStorage from './services/BrowserStorage.js'
import { EventBus } from './services/EventBus.ts'
import { leaveConversationSync } from './services/participantsService.js'
import { useActorStore } from './stores/actor.ts'
import { useCallViewStore } from './stores/callView.ts'
import { useFederationStore } from './stores/federation.ts'
import { useSidebarStore } from './stores/sidebar.ts'
@ -80,6 +81,7 @@ export default {
federationStore: useFederationStore(),
callViewStore: useCallViewStore(),
sidebarStore: useSidebarStore(),
actorStore: useActorStore(),
}
},
@ -122,7 +124,7 @@ export default {
},
getUserId() {
return this.$store.getters.getUserId()
return this.actorStore.userId
},
isSendingMessages() {
@ -323,7 +325,7 @@ export default {
const payload = {
token: params.token,
participantIdentifier: this.$store.getters.getParticipantIdentifier(),
participantIdentifier: this.actorStore.participantIdentifier,
flags,
silent: true,
recordingConsent: this.recordingConsentGiven,
@ -372,7 +374,7 @@ export default {
if (!getCurrentUser()) {
// Set the current actor/participant for guests
const conversation = this.$store.getters.conversation(this.token)
this.$store.dispatch('setCurrentParticipant', conversation)
this.actorStore.setCurrentParticipant(conversation)
}
})
@ -440,14 +442,6 @@ export default {
})
}
})
if (getCurrentUser()) {
console.debug('Setting current user')
this.$store.dispatch('setCurrentUser', getCurrentUser())
this.$store.dispatch('getCurrentUserTeams')
} else {
console.debug('Can not set current user because it\'s a guest')
}
},
async mounted() {

View file

@ -40,6 +40,7 @@ import { getFileConversation } from './services/filesIntegrationServices.js'
import {
leaveConversationSync,
} from './services/participantsService.js'
import { useActorStore } from './stores/actor.ts'
import { checkBrowser } from './utils/browserCheck.ts'
import CancelableRequest from './utils/cancelableRequest.js'
import { signalingKill } from './utils/webrtc/index.js'
@ -62,6 +63,7 @@ export default {
setup() {
return {
isLeavingAfterSessionIssue: useSessionIssueHandler(),
actorStore: useActorStore(),
}
},
@ -142,7 +144,7 @@ export default {
},
beforeMount() {
this.$store.dispatch('setCurrentUser', getCurrentUser())
this.actorStore.setCurrentUser(getCurrentUser())
window.addEventListener('unload', () => {
console.info('Navigating away, leaving conversation')

View file

@ -43,6 +43,7 @@ import {
leaveConversationSync,
setGuestUserName,
} from './services/participantsService.js'
import { useActorStore } from './stores/actor.ts'
import { signalingKill } from './utils/webrtc/index.js'
export default {
@ -65,6 +66,7 @@ export default {
return {
isLeavingAfterSessionIssue: useSessionIssueHandler(),
actorStore: useActorStore(),
}
},
@ -127,9 +129,9 @@ export default {
const guestNickname = getGuestNickname()
if (currentUser) {
this.$store.dispatch('setCurrentUser', currentUser)
this.actorStore.setCurrentUser(currentUser)
} else if (guestNickname) {
this.$store.dispatch('setDisplayName', guestNickname)
this.actorStore.setDisplayName(guestNickname)
} else {
subscribe('talk:guest-name:added', this.showGuestMediaSettings)
}
@ -183,12 +185,12 @@ export default {
// Although the current participant is automatically added to
// the participants store it must be explicitly set in the
// actors store.
if (!this.$store.getters.getUserId()) {
if (!this.actorStore.userId) {
// Set the current actor/participant for guests
const conversation = this.$store.getters.conversation(this.token)
// Setting a guest only uses "sessionId" and "participantType".
this.$store.dispatch('setCurrentParticipant', conversation)
this.actorStore.setCurrentParticipant(conversation)
}
} catch (exception) {
window.clearInterval(this.fetchCurrentConversationIntervalId)

View file

@ -58,6 +58,7 @@ import { getPublicShareConversationData } from './services/filesIntegrationServi
import {
leaveConversationSync,
} from './services/participantsService.js'
import { useActorStore } from './stores/actor.ts'
import { checkBrowser } from './utils/browserCheck.ts'
import { signalingKill } from './utils/webrtc/index.js'
@ -97,6 +98,7 @@ export default {
return {
isInCall: useIsInCall(),
isLeavingAfterSessionIssue: useSessionIssueHandler(),
actorStore: useActorStore(),
}
},
@ -223,7 +225,7 @@ export default {
// signaling server will retry the connection again and again,
// so at some point the anonymous user will have been overriden
// with the current user and the connection will succeed.
this.$store.dispatch('setCurrentUser', {
this.actorStore.setCurrentUser({
uid: data.userId,
displayName: data.displayName,
})
@ -241,12 +243,12 @@ export default {
// Although the current participant is automatically added to
// the participants store it must be explicitly set in the
// actors store.
if (!this.$store.getters.getUserId()) {
if (!this.actorStore.userId) {
// Set the current actor/participant for guests
const conversation = this.$store.getters.conversation(this.token)
// Setting a guest only uses "sessionId" and "participantType".
this.$store.dispatch('setCurrentParticipant', conversation)
this.actorStore.setCurrentParticipant(conversation)
}
} catch (exception) {
window.clearInterval(this.fetchCurrentConversationIntervalId)

View file

@ -164,6 +164,7 @@ import LocalVideo from '../shared/LocalVideo.vue'
import VideoBottomBar from '../shared/VideoBottomBar.vue'
import VideoVue from '../shared/VideoVue.vue'
import { ATTENDEE, PARTICIPANT } from '../../../constants.ts'
import { useActorStore } from '../../../stores/actor.ts'
import { useCallViewStore } from '../../../stores/callView.ts'
import { placeholderImage, placeholderModel, placeholderName, placeholderSharedData } from './gridPlaceholders.ts'
@ -269,6 +270,7 @@ export default {
videosCap,
videosCapEnforced,
callViewStore: useCallViewStore(),
actorStore: useActorStore(),
}
},
@ -502,7 +504,7 @@ export default {
},
isGuestNonModerator() {
return this.$store.getters.getActorType() === ATTENDEE.ACTOR_TYPE.GUESTS
return this.actorStore.isActorGuest
&& this.$store.getters.conversation(this.token).participantType !== PARTICIPANT.TYPE.GUEST_MODERATOR
},

View file

@ -41,7 +41,7 @@
<AvatarWrapper :id="userId"
:token="token"
:name="displayName"
:source="actorType"
:source="actorStore.actorType"
:size="avatarSize"
:loading="isNotConnected"
disable-menu
@ -69,6 +69,7 @@ import AccountOff from 'vue-material-design-icons/AccountOff.vue'
import AvatarWrapper from '../../AvatarWrapper/AvatarWrapper.vue'
import VideoBackground from './VideoBackground.vue'
import { AVATAR } from '../../../constants.ts'
import { useActorStore } from '../../../stores/actor.ts'
import { useCallViewStore } from '../../../stores/callView.ts'
import attachMediaStream from '../../../utils/attachmediastream.js'
import { ConnectionState } from '../../../utils/webrtc/models/CallParticipantModel.js'
@ -158,6 +159,7 @@ export default {
devMode,
screenshotMode,
callViewStore: useCallViewStore(),
actorStore: useActorStore(),
}
},
@ -208,15 +210,11 @@ export default {
},
userId() {
return this.$store.getters.getUserId()
},
actorType() {
return this.$store.getters.getActorType()
return this.actorStore.userId
},
displayName() {
return this.$store.getters.getDisplayName()
return this.actorStore.displayName
},
avatarSize() {

View file

@ -36,6 +36,7 @@ import usernameToColor from '@nextcloud/vue/functions/usernameToColor'
import Hex from 'crypto-js/enc-hex.js'
import SHA1 from 'crypto-js/sha1.js'
import TransitionWrapper from '../../UIShared/TransitionWrapper.vue'
import { useActorStore } from '../../../stores/actor.ts'
import { useGuestNameStore } from '../../../stores/guestName.js'
const reactions = {
@ -86,7 +87,10 @@ export default {
setup() {
const guestNameStore = useGuestNameStore()
return { guestNameStore }
return {
guestNameStore,
actorStore: useActorStore(),
}
},
data() {
@ -159,7 +163,7 @@ export default {
reaction,
reactionURL: this.getReactionURL(reaction),
name: isLocalModel
? this.$store.getters.getDisplayName() || t('spreed', 'Guest')
? this.actorStore.displayName || t('spreed', 'Guest')
: this.getParticipantName(model),
seed: Math.random(),
})

View file

@ -16,6 +16,7 @@ import VideoOff from 'vue-material-design-icons/VideoOff.vue'
import VideoBottomBar from './VideoBottomBar.vue'
import { CONVERSATION, PARTICIPANT } from '../../../constants.ts'
import storeConfig from '../../../store/storeConfig.js'
import { useActorStore } from '../../../stores/actor.ts'
import { useCallViewStore } from '../../../stores/callView.ts'
import { findNcButton } from '../../../test-helpers.js'
import { ConnectionState } from '../../../utils/webrtc/models/CallParticipantModel.js'
@ -36,6 +37,7 @@ describe('VideoBottomBar.vue', () => {
let testStoreConfig
let componentProps
let conversationProps
let actorStore
const audioIndicatorAriaLabels = [t('spreed', 'Mute'), t('spreed', 'Muted')]
const videoIndicatorAriaLabels = [t('spreed', 'Disable video'), t('spreed', 'Enable video')]
@ -47,6 +49,7 @@ describe('VideoBottomBar.vue', () => {
localVue.use(Vuex)
setActivePinia(createPinia())
callViewStore = useCallViewStore()
actorStore = useActorStore()
conversationProps = {
token: TOKEN,
@ -83,7 +86,7 @@ describe('VideoBottomBar.vue', () => {
testStoreConfig = cloneDeep(storeConfig)
testStoreConfig.modules.conversationsStore.getters.conversation = jest.fn().mockReturnValue((token) => conversationProps)
testStoreConfig.modules.actorStore.getters.getUserId = jest.fn().mockReturnValue(() => USER_ID)
actorStore.userId = USER_ID
store = new Store(testStoreConfig)
})

View file

@ -101,6 +101,7 @@ import VideoIcon from 'vue-material-design-icons/Video.vue'
import VideoOff from 'vue-material-design-icons/VideoOff.vue'
import TransitionWrapper from '../../UIShared/TransitionWrapper.vue'
import { PARTICIPANT } from '../../../constants.ts'
import { useActorStore } from '../../../stores/actor.ts'
import { useCallViewStore } from '../../../stores/callView.ts'
import { ConnectionState } from '../../../utils/webrtc/models/CallParticipantModel.js'
@ -186,6 +187,7 @@ export default {
setup() {
return {
callViewStore: useCallViewStore(),
actorStore: useActorStore(),
}
},
@ -265,7 +267,7 @@ export default {
// Moderator rights
participantType() {
return this.$store.getters.conversation(this.token)?.participantType
|| (this.$store.getters.getUserId() !== null
|| (this.actorStore.isLoggedIn
? PARTICIPANT.TYPE.USER
: PARTICIPANT.TYPE.GUEST)
},

View file

@ -71,6 +71,7 @@ import TransitionWrapper from './UIShared/TransitionWrapper.vue'
import { CONVERSATION, PARTICIPANT } from '../constants.ts'
import { getTalkConfig } from '../services/CapabilitiesManager.ts'
import { EventBus } from '../services/EventBus.ts'
import { useActorStore } from '../stores/actor.ts'
import { useChatExtrasStore } from '../stores/chatExtras.js'
export default {
@ -108,6 +109,7 @@ export default {
provide('chatView:isSidebar', props.isSidebar)
return {
chatExtrasStore: useChatExtrasStore(),
actorStore: useActorStore(),
}
},
@ -120,16 +122,15 @@ export default {
computed: {
isGuest() {
return this.$store.getters.isActorGuest()
return this.actorStore.isActorGuest
},
isGuestWithoutDisplayName() {
const userName = this.$store.getters.getDisplayName()
return !userName && this.isGuest
return this.isGuest && !this.actorStore.displayName
},
canUploadFiles() {
return getTalkConfig(this.token, 'attachments', 'allowed') && this.$store.getters.getUserId()
return getTalkConfig(this.token, 'attachments', 'allowed') && this.actorStore.userId
&& this.$store.getters.getAttachmentFolderFreeSpace() !== 0
&& (this.conversation.permissions & PARTICIPANT.PERMISSIONS.CHAT)
&& !this.conversation.remoteServer // no attachments support in federated conversations

View file

@ -132,6 +132,7 @@ import RecordingConsentSettings from './RecordingConsentSettings.vue'
import SipSettings from './SipSettings.vue'
import { CALL, CONFIG, CONVERSATION, PARTICIPANT } from '../../constants.ts'
import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useSettingsStore } from '../../stores/settings.js'
const matterbridgeEnabled = loadState('spreed', 'enable_matterbridge')
@ -174,6 +175,7 @@ export default {
settingsStore,
token,
meetingHeader,
actorStore: useActorStore(),
}
},
@ -195,7 +197,7 @@ export default {
},
isGuest() {
return this.$store.getters.isActorGuest()
return this.actorStore.isActorGuest
},
showSettings() {

View file

@ -31,6 +31,7 @@ import { useStore } from '../../composables/useStore.js'
import { CONVERSATION } from '../../constants.ts'
import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { EventBus } from '../../services/EventBus.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useDashboardStore } from '../../stores/dashboard.ts'
import { hasUnreadMentions } from '../../utils/conversation.ts'
import { copyConversationLinkToClipboard } from '../../utils/handleUrl.ts'
@ -46,6 +47,7 @@ const isDirectionRTL = isRTL()
const store = useStore()
const router = useRouter()
const dashboardStore = useDashboardStore()
const actorStore = useActorStore()
const forwardScrollable = ref(false)
const backwardScrollable = ref(false)
const eventCardsWrapper = ref<HTMLDivElement | null>(null)
@ -166,7 +168,7 @@ function scrollEventCards({ direction }: { direction: 'backward' | 'forward' })
<template>
<div class="talk-dashboard-wrapper">
<h2 class="talk-dashboard__header">
{{ t('spreed', 'Hello, {displayName}', { displayName: store.getters.getDisplayName() }, { escape: false }) }}
{{ t('spreed', 'Hello, {displayName}', { displayName: actorStore.displayName }, { escape: false }) }}
</h2>
<div class="talk-dashboard__actions">
<NcPopover v-if="canStartConversations"

View file

@ -56,6 +56,7 @@ import { callSIPDialOut } from '../../../services/callsService.js'
import { hasTalkFeature } from '../../../services/CapabilitiesManager.ts'
import { createLegacyConversation } from '../../../services/conversationsService.ts'
import { addParticipant } from '../../../services/participantsService.js'
import { useActorStore } from '../../../stores/actor.ts'
export default {
name: 'CallPhoneDialog',
@ -71,6 +72,12 @@ export default {
expose: ['showModal'],
setup() {
return {
actorStore: useActorStore(),
}
},
data() {
return {
modal: false,
@ -156,7 +163,7 @@ export default {
console.info('Joining call')
await this.$store.dispatch('joinCall', {
token,
participantIdentifier: this.$store.getters.getParticipantIdentifier(),
participantIdentifier: this.actorStore.participantIdentifier,
flags,
silent: false,
recordingConsent: true,

View file

@ -16,6 +16,7 @@ import { searchListedConversations } from '../../services/conversationsService.t
import { autocompleteQuery } from '../../services/coreService.ts'
import { EventBus } from '../../services/EventBus.ts'
import storeConfig from '../../store/storeConfig.js'
import { useActorStore } from '../../stores/actor.ts'
import { findNcActionButton, findNcButton } from '../../test-helpers.js'
import { requestTabLeadership } from '../../utils/requestTabLeadership.js'
@ -90,6 +91,7 @@ describe('LeftSidebar.vue', () => {
localVue.use(Vuex)
localVue.use(VueRouter)
setActivePinia(createPinia())
const actorStore = useActorStore()
loadStateSettings = {
circles_enabled: true,
@ -109,8 +111,7 @@ describe('LeftSidebar.vue', () => {
fetchConversationsAction = jest.fn().mockReturnValue({ headers: {} })
addConversationAction = jest.fn()
createOneToOneConversationAction = jest.fn()
const getUserIdMock = jest.fn().mockReturnValue('current-user')
testStoreConfig.modules.actorStore.getters.getUserId = () => getUserIdMock
actorStore.userId = 'current-user'
testStoreConfig.modules.conversationsStore.getters.conversationsList = conversationsListMock
testStoreConfig.modules.conversationsStore.actions.fetchConversations = fetchConversationsAction
testStoreConfig.modules.conversationsStore.actions.addConversation = addConversationAction

View file

@ -302,6 +302,7 @@ import {
import { autocompleteQuery } from '../../services/coreService.ts'
import { EventBus } from '../../services/EventBus.ts'
import { talkBroadcastChannel } from '../../services/talkBroadcastChannel.js'
import { useActorStore } from '../../stores/actor.ts'
import { useFederationStore } from '../../stores/federation.ts'
import { useSettingsStore } from '../../stores/settings.js'
import { useTalkHashStore } from '../../stores/talkHash.js'
@ -393,6 +394,7 @@ export default {
showArchived,
settingsStore,
FILTER_LABELS,
actorStore: useActorStore(),
}
},
@ -680,7 +682,7 @@ export default {
acc.push(result.name)
}
return acc
}, [this.$store.getters.getUserId()])
}, [this.actorStore.userId])
this.searchResults = response?.data?.ocs?.data.filter((match) => {
return !(match.source === ATTENDEE.ACTOR_TYPE.USERS && oneToOneMap.includes(match.id))

View file

@ -42,6 +42,7 @@ import NcRichText from '@nextcloud/vue/components/NcRichText'
import RoomService from 'vue-material-design-icons/RoomService.vue'
import GuestWelcomeWindow from './GuestWelcomeWindow.vue'
import SetGuestUsername from './SetGuestUsername.vue'
import { useActorStore } from '../stores/actor.ts'
import { futureRelativeTime, ONE_DAY_IN_MS } from '../utils/formattedTime.ts'
export default {
@ -55,6 +56,12 @@ export default {
SetGuestUsername,
},
setup() {
return {
actorStore: useActorStore(),
}
},
computed: {
token() {
@ -94,12 +101,11 @@ export default {
// Determines whether the current user is a guest user
currentUserIsGuest() {
return !this.$store.getters.getUserId()
return !this.actorStore.userId
},
isGuestWithoutDisplayName() {
const userName = this.$store.getters.getDisplayName()
return !userName && this.currentUserIsGuest
return !this.actorStore.displayName && this.currentUserIsGuest
},
},

View file

@ -36,7 +36,7 @@
<AvatarWrapper :id="userId"
:token="token"
:name="displayName"
:source="actorType"
:source="actorStore.actorType"
:size="AVATAR.SIZE.EXTRA_LARGE"
disable-menu
disable-tooltip />
@ -231,6 +231,7 @@ import { useIsInCall } from '../../composables/useIsInCall.js'
import { AVATAR, CALL, CONFIG, PARTICIPANT, VIRTUAL_BACKGROUND } from '../../constants.ts'
import BrowserStorage from '../../services/BrowserStorage.js'
import { getTalkConfig } from '../../services/CapabilitiesManager.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useGuestNameStore } from '../../stores/guestName.js'
import { useSettingsStore } from '../../stores/settings.js'
import { localMediaModel } from '../../utils/webrtc/index.js'
@ -337,6 +338,7 @@ export default {
dialogHeaderId,
supportStartWithoutMedia,
supportDefaultBlurVirtualBackground,
actorStore: useActorStore(),
}
},
@ -361,22 +363,18 @@ export default {
computed: {
displayName() {
return this.$store.getters.getDisplayName()
return this.actorStore.displayName
},
guestName() {
return this.guestNameStore.getGuestName(
this.$store.getters.getToken(),
this.$store.getters.getActorId(),
this.actorStore.actorId,
)
},
userId() {
return this.$store.getters.getUserId()
},
actorType() {
return this.$store.getters.getActorType()
return this.actorStore.userId
},
token() {

View file

@ -85,6 +85,7 @@ import { VIRTUAL_BACKGROUND } from '../../constants.ts'
import BrowserStorage from '../../services/BrowserStorage.js'
import { getTalkConfig } from '../../services/CapabilitiesManager.ts'
import { getDavClient } from '../../services/DavClient.js'
import { useActorStore } from '../../stores/actor.ts'
import { useSettingsStore } from '../../stores/settings.js'
import { findUniquePath } from '../../utils/fileUpload.js'
@ -131,6 +132,7 @@ export default {
predefinedBackgrounds: getTalkConfig('local', 'call', 'predefined-backgrounds'),
predefinedBackgroundsV2: getTalkConfig('local', 'call', 'predefined-backgrounds-v2'),
settingsStore: useSettingsStore(),
actorStore: useActorStore(),
}
},
@ -174,12 +176,12 @@ export default {
async mounted() {
this.loadBackground()
if (this.$store.getters.getUserId() === null) {
if (this.actorStore.userId === null) {
console.debug('Skip Talk backgrounds folder check and setup for participants that are not logged in')
return
}
const userRoot = '/files/' + this.$store.getters.getUserId()
const userRoot = '/files/' + this.actorStore.userId
const absoluteBackgroundsFolderPath = userRoot + this.relativeBackgroundsFolderPath
try {
@ -216,7 +218,7 @@ export default {
event.target.value = ''
// userRoot path
const userRoot = '/files/' + this.$store.getters.getUserId()
const userRoot = '/files/' + this.actorStore.userId
const filePath = this.$store.getters.getAttachmentFolder() + '/Backgrounds/' + file.name

View file

@ -23,6 +23,7 @@ import * as useIsInCallModule from '../../../../composables/useIsInCall.js'
import { ATTENDEE, CONVERSATION, MESSAGE, PARTICIPANT } from '../../../../constants.ts'
import { EventBus } from '../../../../services/EventBus.ts'
import storeConfig from '../../../../store/storeConfig.js'
import { useActorStore } from '../../../../stores/actor.ts'
// needed because of https://github.com/vuejs/vue-test-utils/issues/1507
const RichTextStub = {
@ -45,13 +46,13 @@ describe('Message.vue', () => {
let messageProps
let conversationProps
let injected
let getActorTypeMock
const getVisualLastReadMessageIdMock = jest.fn()
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
setActivePinia(createPinia())
const actorStore = useActorStore()
conversationProps = {
token: TOKEN,
@ -65,17 +66,15 @@ describe('Message.vue', () => {
getMessagesListScroller: jest.fn(),
}
actorStore.actorId = 'user-id-1'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
testStoreConfig = cloneDeep(storeConfig)
testStoreConfig.modules.tokenStore.getters.getToken
= jest.fn().mockReturnValue(() => TOKEN)
testStoreConfig.modules.conversationsStore.getters.conversation
= jest.fn().mockReturnValue((token) => conversationProps)
testStoreConfig.modules.actorStore.getters.getActorId
= jest.fn().mockReturnValue(() => 'user-id-1')
testStoreConfig.modules.messagesStore.getters.getVisualLastReadMessageId
= jest.fn().mockReturnValue(getVisualLastReadMessageIdMock)
getActorTypeMock = jest.fn().mockReturnValue(() => ATTENDEE.ACTOR_TYPE.USERS)
testStoreConfig.modules.actorStore.getters.getActorType = getActorTypeMock
messageProps = {
message: {

View file

@ -122,6 +122,7 @@ import Reactions from './MessagePart/Reactions.vue'
import { CONVERSATION, MENTION, MESSAGE, PARTICIPANT } from '../../../../constants.ts'
import { getTalkConfig, hasTalkFeature } from '../../../../services/CapabilitiesManager.ts'
import { EventBus } from '../../../../services/EventBus.ts'
import { useActorStore } from '../../../../stores/actor.ts'
import { useChatExtrasStore } from '../../../../stores/chatExtras.js'
import { getItemTypeFromMessage } from '../../../../utils/getItemTypeFromMessage.ts'
@ -204,6 +205,7 @@ export default {
return {
isTranslationAvailable,
chatExtrasStore: useChatExtrasStore(),
actorStore: useActorStore(),
}
},
@ -279,8 +281,7 @@ export default {
return !this.isSystemMessage
&& !this.isTemporary
&& !this.isDeleting
&& this.message.actorType === this.$store.getters.getActorType()
&& this.message.actorId === this.$store.getters.getActorId()
&& this.actorStore.checkIfSelfIsActor(this.message)
&& !this.isDeletedMessage
},

View file

@ -13,6 +13,7 @@ import MessageButtonsBar from './../MessageButtonsBar/MessageButtonsBar.vue'
import * as useMessageInfoModule from '../../../../../composables/useMessageInfo.js'
import { ATTENDEE, CONVERSATION, MESSAGE, PARTICIPANT } from '../../../../../constants.ts'
import storeConfig from '../../../../../store/storeConfig.js'
import { useActorStore } from '../../../../../stores/actor.ts'
import { useIntegrationsStore } from '../../../../../stores/integrations.js'
import { findNcActionButton, findNcButton } from '../../../../../test-helpers.js'
@ -24,14 +25,13 @@ describe('MessageButtonsBar.vue', () => {
let messageProps
let injected
let conversationProps
let getActorTypeMock
let isActorUserMock
let isActorGuestMock
let actorStore
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
setActivePinia(createPinia())
actorStore = useActorStore()
conversationProps = {
token: TOKEN,
@ -46,14 +46,8 @@ describe('MessageButtonsBar.vue', () => {
= jest.fn().mockReturnValue(() => TOKEN)
testStoreConfig.modules.conversationsStore.getters.conversation
= jest.fn().mockReturnValue((token) => conversationProps)
testStoreConfig.modules.actorStore.getters.getActorId
= jest.fn().mockReturnValue(() => 'user-id-1')
getActorTypeMock = jest.fn().mockReturnValue(() => ATTENDEE.ACTOR_TYPE.USERS)
isActorUserMock = jest.fn().mockReturnValue(() => true)
isActorGuestMock = jest.fn().mockReturnValue(() => false)
testStoreConfig.modules.actorStore.getters.getActorType = getActorTypeMock
testStoreConfig.modules.actorStore.getters.isActorUser = isActorUserMock
testStoreConfig.modules.actorStore.getters.isActorGuest = isActorGuestMock
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
actorStore.actorId = 'user-id-1'
messageProps = {
previousMessageId: 100,
@ -254,9 +248,7 @@ describe('MessageButtonsBar.vue', () => {
test('hides private reply action when current user is a guest', async () => {
messageProps.message.actorId = 'another-user'
getActorTypeMock.mockClear().mockReturnValue(() => ATTENDEE.ACTOR_TYPE.GUESTS)
isActorUserMock.mockClear().mockReturnValue(() => false)
isActorGuestMock.mockClear().mockReturnValue(() => true)
actorStore.actorType = ATTENDEE.ACTOR_TYPE.GUESTS
testPrivateReplyActionVisible(false)
})
})

View file

@ -302,6 +302,7 @@ import { useMessageInfo } from '../../../../../composables/useMessageInfo.js'
import { ATTENDEE, CONVERSATION, MESSAGE, PARTICIPANT } from '../../../../../constants.ts'
import { hasTalkFeature } from '../../../../../services/CapabilitiesManager.ts'
import { getMessageReminder, removeMessageReminder, setMessageReminder } from '../../../../../services/remindersService.js'
import { useActorStore } from '../../../../../stores/actor.ts'
import { useIntegrationsStore } from '../../../../../stores/integrations.js'
import { useReactionsStore } from '../../../../../stores/reactions.js'
import { generatePublicShareDownloadUrl, generateUserFileUrl } from '../../../../../utils/davUtils.ts'
@ -406,6 +407,7 @@ export default {
const { message } = toRefs(props)
const reactionsStore = useReactionsStore()
const { messageActions } = useIntegrationsStore()
const actorStore = useActorStore()
const {
isEditable,
isDeleteable,
@ -430,6 +432,7 @@ export default {
isDeleteable,
isConversationReadOnly,
isConversationModifiable,
actorStore,
}
},
@ -457,7 +460,7 @@ export default {
|| this.conversation.type === CONVERSATION.TYPE.GROUP)
&& !this.isCurrentUserOwnMessage
&& this.message.actorType === ATTENDEE.ACTOR_TYPE.USERS
&& this.$store.getters.isActorUser()
&& !this.isCurrentGuest
},
messageFile() {
@ -472,7 +475,7 @@ export default {
},
isCurrentGuest() {
return this.$store.getters.isActorGuest()
return this.actorStore.isActorGuest
},
isDeletedMessage() {

View file

@ -18,6 +18,7 @@ import { t } from '@nextcloud/l10n'
import { encodePath } from '@nextcloud/paths'
import { generateRemoteUrl } from '@nextcloud/router'
import { EventBus } from '../../../../../services/EventBus.ts'
import { useActorStore } from '../../../../../stores/actor.ts'
export default {
name: 'AudioPlayer',
@ -67,6 +68,12 @@ export default {
},
},
setup() {
return {
actorStore: useActorStore(),
}
},
computed: {
internalAbsolutePath() {
if (this.path.startsWith('/')) {
@ -79,7 +86,7 @@ export default {
if (this.localUrl) {
return this.localUrl
}
const userId = this.$store.getters.getUserId()
const userId = this.actorStore.userId
if (userId === null) {
// guest mode, use public link download URL
return this.link + '/download/' + encodePath(this.name)

View file

@ -12,27 +12,29 @@ import NcButton from '@nextcloud/vue/components/NcButton'
import PlayCircleOutline from 'vue-material-design-icons/PlayCircleOutline.vue'
import FilePreview from './FilePreview.vue'
import storeConfig from '../../../../../store/storeConfig.js'
import { useActorStore } from '../../../../../stores/actor.ts'
describe('FilePreview.vue', () => {
let store
let localVue
let testStoreConfig
let propsData
let getUserIdMock
let oldPixelRatio
let actorStore
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
setActivePinia(createPinia())
actorStore = useActorStore()
oldPixelRatio = window.devicePixelRatio
testStoreConfig = cloneDeep(storeConfig)
getUserIdMock = jest.fn().mockReturnValue('current-user-id')
testStoreConfig.modules.actorStore.getters.getUserId = () => getUserIdMock
store = new Vuex.Store(testStoreConfig)
actorStore.userId = 'current-user-id'
propsData = {
token: 'TOKEN',
file: {
@ -83,7 +85,7 @@ describe('FilePreview.vue', () => {
test('renders file preview for guests', async () => {
propsData.file.link = 'https://localhost/nc-webroot/s/xtokenx'
getUserIdMock.mockClear().mockReturnValue(null)
actorStore.userId = null
const wrapper = shallowMount(FilePreview, {
localVue,
@ -270,7 +272,7 @@ describe('FilePreview.vue', () => {
test('directly renders small GIF files for guests', async () => {
propsData.file.size = '128'
propsData.file.link = 'https://localhost/nc-webroot/s/xtokenx'
getUserIdMock.mockClear().mockReturnValue(null)
actorStore.userId = null
const wrapper = shallowMount(FilePreview, {
localVue,

View file

@ -79,6 +79,7 @@ import AudioPlayer from './AudioPlayer.vue'
import { useViewer } from '../../../../../composables/useViewer.js'
import { SHARED_ITEM } from '../../../../../constants.ts'
import { getTalkConfig } from '../../../../../services/CapabilitiesManager.ts'
import { useActorStore } from '../../../../../stores/actor.ts'
import { useSharedItemsStore } from '../../../../../stores/sharedItems.js'
const PREVIEW_TYPE = {
@ -176,6 +177,7 @@ export default {
return {
openViewer,
sharedItemsStore,
actorStore: useActorStore(),
}
},
@ -324,7 +326,7 @@ export default {
},
previewUrl() {
const userId = this.$store.getters.getUserId()
const userId = this.actorStore.userId
if (this.previewType === PREVIEW_TYPE.TEMPORARY) {
return this.file.localUrl

View file

@ -20,6 +20,7 @@ import { useIsDarkTheme } from '@nextcloud/vue/composables/useIsDarkTheme'
import NcUserBubble from '@nextcloud/vue/components/NcUserBubble'
import { MENTION } from '../../../../../constants.ts'
import { getConversationAvatarOcsUrl, getUserProxyAvatarOcsUrl } from '../../../../../services/avatarService.ts'
import { useActorStore } from '../../../../../stores/actor.ts'
export default {
name: 'Mention',
@ -60,6 +61,7 @@ export default {
return {
isDarkTheme,
actorStore: useActorStore(),
}
},
@ -97,9 +99,9 @@ export default {
// So when comparing a guest we have to prefix "guest/"
// when comparing the id
// However we do not prefix email accounts, so simply compare id
return this.$store.getters.isActorGuest()
&& (this.id === ('guest/' + this.$store.getters.getActorId())
|| this.id === this.$store.getters.getActorId())
return this.actorStore.isActorGuest
&& (this.id === ('guest/' + this.actorStore.actorId)
|| this.id === this.actorStore.actorId)
},
isCurrentUser() {
@ -108,16 +110,16 @@ export default {
return false
}
return this.$store.getters.isActorUser()
&& this.id === this.$store.getters.getUserId()
return this.actorStore.isActorUser
&& this.id === this.actorStore.userId
},
isCurrentUserGroup() {
return this.isGroupMention && this.$store.getters.isActorMemberOfGroup(this.id)
return this.isGroupMention && this.actorStore.isActorMemberOfGroup(this.id)
},
isCurrentUserTeam() {
return this.isTeamMention && this.$store.getters.isActorMemberOfTeam(this.id)
return this.isTeamMention && this.actorStore.isActorMemberOfTeam(this.id)
},
primary() {

View file

@ -19,6 +19,7 @@ import {
} from '../../../../../services/reactionsService.ts'
import vuexStore from '../../../../../store/index.js'
import storeConfig from '../../../../../store/storeConfig.js'
import { useActorStore } from '../../../../../stores/actor.ts'
import { useReactionsStore } from '../../../../../stores/reactions.js'
import { generateOCSResponse } from '../../../../../test-helpers.js'
@ -41,8 +42,6 @@ describe('Reactions.vue', () => {
let store
let localVue
let messageMock
let getActorTypeMock
let getActorIdMock
let reactionsStored
let message
@ -51,6 +50,7 @@ describe('Reactions.vue', () => {
reactionsStore = useReactionsStore()
localVue = createLocalVue()
localVue.use(Vuex)
const actorStore = useActorStore()
testStoreConfig = cloneDeep(storeConfig)
token = 'token1'
@ -69,11 +69,8 @@ describe('Reactions.vue', () => {
messageMock = jest.fn().mockReturnValue(message)
testStoreConfig.modules.messagesStore.getters.message = () => messageMock
getActorTypeMock = jest.fn().mockReturnValue(() => ATTENDEE.ACTOR_TYPE.USERS)
testStoreConfig.modules.actorStore.getters.getActorType = getActorTypeMock
getActorIdMock = jest.fn().mockReturnValue('admin')
testStoreConfig.modules.actorStore.getters.getActorId = () => getActorIdMock
actorStore.actorId = 'admin'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
store = new Vuex.Store(testStoreConfig)

View file

@ -86,6 +86,7 @@ import EmoticonPlusOutline from 'vue-material-design-icons/EmoticonPlusOutline.v
import HeartOutlineIcon from 'vue-material-design-icons/HeartOutline.vue'
import ReactionsList from './ReactionsList.vue'
import { ATTENDEE } from '../../../../../constants.ts'
import { useActorStore } from '../../../../../stores/actor.ts'
import { useGuestNameStore } from '../../../../../stores/guestName.js'
import { useReactionsStore } from '../../../../../stores/reactions.js'
import { getDisplayNameWithFallback } from '../../../../../utils/getDisplayName.ts'
@ -134,11 +135,10 @@ export default {
emits: ['emoji-picker-toggled'],
setup() {
const guestNameStore = useGuestNameStore()
const reactionsStore = useReactionsStore()
return {
guestNameStore,
reactionsStore,
guestNameStore: useGuestNameStore(),
reactionsStore: useReactionsStore(),
actorStore: useActorStore(),
}
},
@ -249,8 +249,7 @@ export default {
const summary = []
for (const item in list) {
if (list[item].actorType === this.$store.getters.getActorType()
&& list[item].actorId === this.$store.getters.getActorId()) {
if (this.actorStore.checkIfSelfIsActor(list[item])) {
summary.unshift(t('spreed', 'You'))
} else {
summary.push(this.getDisplayNameForReaction(list[item]))

View file

@ -9,6 +9,7 @@ import Vuex from 'vuex'
import MessagesGroup from './MessagesGroup.vue'
import { ATTENDEE, MESSAGE } from '../../../constants.ts'
import storeConfig from '../../../store/storeConfig.js'
import { useActorStore } from '../../../stores/actor.ts'
import { useGuestNameStore } from '../../../stores/guestName.js'
describe('MessagesGroup.vue', () => {
@ -23,11 +24,12 @@ describe('MessagesGroup.vue', () => {
localVue.use(Vuex)
setActivePinia(createPinia())
guestNameStore = useGuestNameStore()
const actorStore = useActorStore()
testStoreConfig = cloneDeep(storeConfig)
testStoreConfig.modules.conversationsStore.getters.conversation = () => () => ({})
testStoreConfig.modules.actorStore.getters.getActorId = () => () => 'actor-1'
testStoreConfig.modules.actorStore.getters.getActorType = () => () => ATTENDEE.ACTOR_TYPE.USERS
actorStore.actorId = 'actor-1'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
store = new Vuex.Store(testStoreConfig)
})

View file

@ -34,6 +34,7 @@ import AvatarWrapper from '../../AvatarWrapper/AvatarWrapper.vue'
import Message from './Message/Message.vue'
import { useMessageInfo } from '../../../composables/useMessageInfo.js'
import { ATTENDEE, AVATAR } from '../../../constants.ts'
import { useActorStore } from '../../../stores/actor.ts'
import { useGuestNameStore } from '../../../stores/guestName.js'
export default {
@ -92,6 +93,7 @@ export default {
return {
AVATAR,
guestNameStore: useGuestNameStore(),
actorStore: useActorStore(),
actorDisplayName,
actorInfo,
}
@ -109,7 +111,7 @@ export default {
disableMenu() {
// disable the menu if accessing the conversation as guest
// or the message sender is a bridged user
return this.$store.getters.isActorGuest() || this.actorType === ATTENDEE.ACTOR_TYPE.BRIDGED
return this.actorStore.isActorGuest || this.actorType === ATTENDEE.ACTOR_TYPE.BRIDGED
},
},

View file

@ -200,6 +200,7 @@ import BrowserStorage from '../../services/BrowserStorage.js'
import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { EventBus } from '../../services/EventBus.ts'
import { shareFile } from '../../services/filesSharingServices.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useChatExtrasStore } from '../../stores/chatExtras.js'
import { useGroupwareStore } from '../../stores/groupware.ts'
import { useSettingsStore } from '../../stores/settings.js'
@ -295,6 +296,7 @@ export default {
const { autoComplete, userData } = useChatMentions(token)
const { createTemporaryMessage } = useTemporaryMessage()
return {
actorStore: useActorStore(),
chatExtrasStore: useChatExtrasStore(),
groupwareStore: useGroupwareStore(),
settingsStore: useSettingsStore(),
@ -377,12 +379,8 @@ export default {
return messageToEditId && this.$store.getters.message(this.token, messageToEditId)
},
currentUserIsGuest() {
return this.$store.getters.getUserId() === null
},
canShareFiles() {
return !this.currentUserIsGuest
return !this.actorStore.isActorGuest
&& !this.conversation.remoteServer // no attachments support in federated conversations
},
@ -893,8 +891,7 @@ export default {
// last message within 24 hours
const lastMessageByCurrentUser = this.$store.getters.messagesList(this.token).findLast((message) => {
return message.actorId === this.$store.getters.getUserId()
&& message.actorType === this.$store.getters.getActorType()
return this.actorStore.checkIfSelfIsActor(message)
&& !message.isTemporary && !message.systemMessage
&& (Date.now() - message.timestamp * 1000 < ONE_DAY_IN_MS)
})

View file

@ -30,6 +30,7 @@ import { n, t } from '@nextcloud/l10n'
import escapeHtml from 'escape-html'
import AvatarWrapper from '../AvatarWrapper/AvatarWrapper.vue'
import { AVATAR } from '../../constants.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useGuestNameStore } from '../../stores/guestName.js'
export default {
@ -48,12 +49,16 @@ export default {
setup() {
const guestNameStore = useGuestNameStore()
return { AVATAR, guestNameStore }
return {
AVATAR,
guestNameStore,
actorStore: useActorStore(),
}
},
computed: {
isGuest() {
return this.$store.getters.isActorGuest()
return this.actorStore.isActorGuest
},
externalTypingSignals() {

View file

@ -137,6 +137,7 @@ import { useIsInCall } from '../../composables/useIsInCall.js'
import { POLL } from '../../constants.ts'
import { hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { EventBus } from '../../services/EventBus.ts'
import { useActorStore } from '../../stores/actor.ts'
import { usePollsStore } from '../../stores/polls.ts'
import { calculateVotePercentage } from '../../utils/calculateVotePercentage.ts'
import { convertToJSONDataURI } from '../../utils/fileDownload.ts'
@ -187,6 +188,7 @@ export default {
return {
isInCall: useIsInCall(),
actorStore: useActorStore(),
pollsStore,
voteToSubmit,
modalPage,
@ -243,8 +245,7 @@ export default {
selfIsOwnerOrModerator() {
return this.isModerator
|| (this.poll?.actorType === this.$store.getters.getActorType()
&& this.poll?.actorId === this.$store.getters.getActorId())
|| (this.poll && this.actorStore.checkIfSelfIsActor(this.poll))
},
pollSummaryText() {

View file

@ -67,6 +67,7 @@ import FilePreview from './MessagesList/MessagesGroup/Message/MessagePart/FilePr
import { useMessageInfo } from '../composables/useMessageInfo.js'
import { ATTENDEE, AVATAR } from '../constants.ts'
import { EventBus } from '../services/EventBus.ts'
import { useActorStore } from '../stores/actor.ts'
import { useChatExtrasStore } from '../stores/chatExtras.js'
export default {
@ -125,6 +126,7 @@ export default {
isFileShareWithoutCaption,
actorDisplayName,
actorInfo,
actorStore: useActorStore(),
}
},
@ -136,8 +138,7 @@ export default {
},
isOwnMessageQuoted() {
return this.message.actorId === this.$store.getters.getActorId()
&& this.message.actorType === this.$store.getters.getActorType()
return this.actorStore.checkIfSelfIsActor(this.message)
},
richParameters() {

View file

@ -23,6 +23,7 @@ import AvatarWrapper from '../../AvatarWrapper/AvatarWrapper.vue'
import Participant from './Participant.vue'
import { ATTENDEE, PARTICIPANT, WEBINAR } from '../../../constants.ts'
import storeConfig from '../../../store/storeConfig.js'
import { useActorStore } from '../../../stores/actor.ts'
import { findNcActionButton, findNcButton } from '../../../test-helpers.js'
describe('Participant.vue', () => {
@ -36,6 +37,7 @@ describe('Participant.vue', () => {
localVue = createLocalVue()
localVue.use(Vuex)
setActivePinia(createPinia())
const actorStore = useActorStore()
participant = {
displayName: 'Alice',
@ -61,13 +63,12 @@ describe('Participant.vue', () => {
lobbyState: WEBINAR.LOBBY.NONE,
}
actorStore.actorId = 'user-actor-id'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
const conversationGetterMock = jest.fn().mockReturnValue(conversation)
const actorIdMock = jest.fn().mockReturnValue('user-actor-id')
const actorTypeMock = jest.fn().mockReturnValue(ATTENDEE.ACTOR_TYPE.USERS)
testStoreConfig = cloneDeep(storeConfig)
testStoreConfig.modules.actorStore.getters.getActorId = () => () => actorIdMock()
testStoreConfig.modules.actorStore.getters.getActorType = () => () => actorTypeMock()
testStoreConfig.modules.tokenStore.getters.getToken = () => () => 'current-token'
testStoreConfig.modules.conversationsStore.getters.conversation = () => conversationGetterMock
store = new Vuex.Store(testStoreConfig)

View file

@ -354,6 +354,7 @@ import {
callSIPUnmutePhone,
} from '../../../services/callsService.js'
import { hasTalkFeature } from '../../../services/CapabilitiesManager.ts'
import { useActorStore } from '../../../stores/actor.ts'
import { formattedTime } from '../../../utils/formattedTime.ts'
import { getDisplayNameWithFallback } from '../../../utils/getDisplayName.ts'
import { readableNumber } from '../../../utils/readableNumber.ts'
@ -417,6 +418,7 @@ export default {
setup() {
return {
isInCall: useIsInCall(),
actorStore: useActorStore(),
}
},
@ -609,7 +611,7 @@ export default {
return this.$store.getters.conversation(this.token) || {
sessionId: '0',
participantFlags: 0,
participantType: this.$store.getters.getUserId() !== null ? PARTICIPANT.TYPE.USER : PARTICIPANT.TYPE.GUEST,
participantType: this.actorStore.isLoggedIn ? PARTICIPANT.TYPE.USER : PARTICIPANT.TYPE.GUEST,
}
},
@ -625,7 +627,7 @@ export default {
},
isSelf() {
return this.participant.actorType === this.$store.getters.getActorType() && this.participant.actorId === this.$store.getters.getActorId()
return this.actorStore.checkIfSelfIsActor(this.participant)
},
selfIsModerator() {
@ -904,7 +906,7 @@ export default {
console.info('Joining call')
await this.$store.dispatch('joinCall', {
token: this.token,
participantIdentifier: this.$store.getters.getParticipantIdentifier(),
participantIdentifier: this.actorStore.participantIdentifier,
flags,
silent: false,
recordingConsent: true,

View file

@ -94,6 +94,7 @@ import { getTalkConfig, hasTalkFeature } from '../../../services/CapabilitiesMan
import { autocompleteQuery } from '../../../services/coreService.ts'
import { EventBus } from '../../../services/EventBus.ts'
import { addParticipant } from '../../../services/participantsService.js'
import { useActorStore } from '../../../stores/actor.ts'
import { useSidebarStore } from '../../../stores/sidebar.ts'
import CancelableRequest from '../../../utils/cancelableRequest.js'
@ -155,6 +156,7 @@ export default {
isInCall,
cancelableGetParticipants,
sidebarStore: useSidebarStore(),
actorStore: useActorStore(),
}
},
@ -220,7 +222,7 @@ export default {
},
userId() {
return this.$store.getters.getUserId()
return this.actorStore.userId
},
canAddPhones() {

View file

@ -142,6 +142,7 @@ import SharedItemsTab from './SharedItems/SharedItemsTab.vue'
import SipSettings from './SipSettings.vue'
import { CONVERSATION, PARTICIPANT, WEBINAR } from '../../constants.ts'
import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useSidebarStore } from '../../stores/sidebar.ts'
const canStartConversations = getTalkConfig('local', 'conversations', 'can-create')
@ -234,6 +235,7 @@ export default {
sidebar,
sidebarContent,
sidebarStore: useSidebarStore(),
actorStore: useActorStore(),
}
},
@ -279,7 +281,7 @@ export default {
},
getUserId() {
return this.$store.getters.getUserId()
return this.actorStore.userId
},
canAddParticipants() {

View file

@ -90,6 +90,7 @@ import SharedItemsBrowser from './SharedItemsBrowser.vue'
import { CONVERSATION } from '../../../constants.ts'
import { hasTalkFeature } from '../../../services/CapabilitiesManager.ts'
import { EventBus } from '../../../services/EventBus.ts'
import { useActorStore } from '../../../stores/actor.ts'
import { useSharedItemsStore } from '../../../stores/sharedItems.js'
import { useSidebarStore } from '../../../stores/sidebar.ts'
import {
@ -126,6 +127,7 @@ export default {
setup() {
return {
actorStore: useActorStore(),
sharedItemsStore: useSharedItemsStore(),
sidebarStore: useSidebarStore(),
sharedItemButtonTitle,
@ -146,7 +148,7 @@ export default {
computed: {
getUserId() {
return this.$store.getters.getUserId()
return this.actorStore.userId
},
token() {

View file

@ -46,6 +46,7 @@ import escapeHtml from 'escape-html'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import Pencil from 'vue-material-design-icons/Pencil.vue'
import { useActorStore } from '../stores/actor.ts'
import { useGuestNameStore } from '../stores/guestName.js'
export default {
@ -59,7 +60,10 @@ export default {
setup() {
const guestNameStore = useGuestNameStore()
return { guestNameStore }
return {
guestNameStore,
actorStore: useActorStore(),
}
},
data() {
@ -72,7 +76,7 @@ export default {
computed: {
actorDisplayName() {
return this.$store.getters.getDisplayName() || t('spreed', 'Guest')
return this.actorStore.displayName || t('spreed', 'Guest')
},
displayNameLabel() {
@ -82,7 +86,7 @@ export default {
},
actorId() {
return this.$store.getters.getActorId()
return this.actorStore.actorId
},
token() {

View file

@ -232,6 +232,7 @@ import BrowserStorage from '../../services/BrowserStorage.js'
import { getTalkConfig } from '../../services/CapabilitiesManager.ts'
import { useCustomSettings } from '../../services/SettingsAPI.ts'
import { setUserConfig } from '../../services/settingsService.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useSettingsStore } from '../../stores/settings.js'
import { useSoundsStore } from '../../stores/sounds.js'
import { isMac } from '../../utils/browserCheck.ts'
@ -276,6 +277,7 @@ export default {
customSettingsSections,
supportStartWithoutMedia,
supportConversationsListStyle,
actorStore: useActorStore(),
}
},
@ -305,7 +307,7 @@ export default {
},
isGuest() {
return !this.$store.getters.getUserId()
return !this.actorStore.userId
},
readStatusPrivacyIsPublic() {

View file

@ -106,6 +106,7 @@ import { ATTENDEE, CALL, CONVERSATION, PARTICIPANT } from '../../constants.ts'
import { callSIPDialOut } from '../../services/callsService.js'
import { hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { EventBus } from '../../services/EventBus.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.ts'
import { useCallViewStore } from '../../stores/callView.ts'
import { useSettingsStore } from '../../stores/settings.js'
@ -189,6 +190,7 @@ export default {
setup() {
return {
actorStore: useActorStore(),
isInCall: useIsInCall(),
breakoutRoomsStore: useBreakoutRoomsStore(),
callViewStore: useCallViewStore(),
@ -384,7 +386,7 @@ export default {
})
await this.$store.dispatch('joinCall', {
token: this.token,
participantIdentifier: this.$store.getters.getParticipantIdentifier(),
participantIdentifier: this.actorStore.participantIdentifier,
flags,
silent: this.hasCall ? true : this.silentCall,
recordingConsent: this.recordingConsentGiven,
@ -425,7 +427,7 @@ export default {
})
await this.$store.dispatch('leaveCall', {
token: this.token,
participantIdentifier: this.$store.getters.getParticipantIdentifier(),
participantIdentifier: this.actorStore.participantIdentifier,
all: endMeetingForAll,
})
this.loading = false

View file

@ -132,6 +132,7 @@ import TopBarMenu from './TopBarMenu.vue'
import { useGetParticipants } from '../../composables/useGetParticipants.js'
import { AVATAR, CONVERSATION } from '../../constants.ts'
import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useGroupwareStore } from '../../stores/groupware.ts'
import { useSidebarStore } from '../../stores/sidebar.ts'
import { getStatusMessage } from '../../utils/userStatus.ts'
@ -187,6 +188,7 @@ export default {
localMediaModel,
groupwareStore: useGroupwareStore(),
sidebarStore: useSidebarStore(),
actorStore: useActorStore(),
CONVERSATION,
}
},
@ -229,13 +231,6 @@ export default {
return getStatusMessage(this.conversation)
},
/**
* Current actor id
*/
actorId() {
return this.$store.getters.getActorId()
},
/**
* Online status of the peer in one to one conversation.
*/
@ -249,7 +244,7 @@ export default {
let peer
const participants = this.$store.getters.participantsList(this.token)
for (const participant of participants) {
if (participant.actorId !== this.actorId) {
if (participant.actorId !== this.actorStore.actorId) {
peer = participant
}
}
@ -287,7 +282,7 @@ export default {
},
getUserId() {
return this.$store.getters.getUserId()
return this.actorStore.userId
},
},

View file

@ -184,6 +184,7 @@ import {
import { useIsInCall } from '../../composables/useIsInCall.js'
import { CALL, CONVERSATION, PARTICIPANT } from '../../constants.ts'
import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useBreakoutRoomsStore } from '../../stores/breakoutRooms.ts'
import { useCallViewStore } from '../../stores/callView.ts'
import { generateAbsoluteUrl } from '../../utils/handleUrl.ts'
@ -259,6 +260,7 @@ export default {
isFullscreen: useDocumentFullscreen(),
breakoutRoomsStore: useBreakoutRoomsStore(),
callViewStore: useCallViewStore(),
actorStore: useActorStore(),
}
},
@ -472,7 +474,7 @@ export default {
this.$store.dispatch(
'setParticipantHandRaised',
{
sessionId: this.$store.getters.getSessionId(),
sessionId: this.actorStore.sessionId,
raisedHand: this.model.attributes.raisedHand,
},
)
@ -480,7 +482,7 @@ export default {
// also send request for assistance to the moderators.
if (this.userIsInBreakoutRoomAndInCall && !this.canModerate) {
const hasRaisedHands = Object.keys(this.$store.getters.participantRaisedHandList)
.filter((sessionId) => sessionId !== this.$store.getters.getSessionId())
.filter((sessionId) => sessionId !== this.actorStore.sessionId)
.length !== 0
if (hasRaisedHands) {
return // Assistance is already requested by someone in the room

View file

@ -5,6 +5,7 @@
import { createPinia, setActivePinia } from 'pinia'
import { computed, ref } from 'vue'
import { ATTENDEE, CONVERSATION, MESSAGE } from '../../constants.ts'
import { useActorStore } from '../../stores/actor.ts'
import { useGuestNameStore } from '../../stores/guestName.js'
import { useConversationInfo } from '../useConversationInfo.ts'
import { useMessageInfo } from '../useMessageInfo.js'
@ -26,12 +27,17 @@ describe('message actions', () => {
let message
let conversationProps
let mockConversationInfo
let actorStore
const TOKEN = 'XXTOKENXX'
jest.useFakeTimers().setSystemTime(new Date('2024-05-01 17:00:00'))
beforeEach(() => {
setActivePinia(createPinia())
actorStore = useActorStore()
actorStore.actorId = 'user-id-1'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
message = ref({
message: 'test message',
@ -55,8 +61,6 @@ describe('message actions', () => {
useStore.mockReturnValue({
getters: {
conversation: () => conversationProps,
getActorId: () => 'user-id-1',
getActorType: () => ATTENDEE.ACTOR_TYPE.USERS,
isModerator: false,
},
})
@ -123,11 +127,10 @@ describe('message actions', () => {
useStore.mockReturnValue({
getters: {
conversation: () => conversationProps,
getActorId: () => 'user-id-1',
getActorType: () => ATTENDEE.ACTOR_TYPE.MODERATOR,
isModerator: true,
},
})
actorStore.actorType = ATTENDEE.ACTOR_TYPE.MODERATOR
// Act
const result = useMessageInfo(message)
// Assert
@ -142,8 +145,6 @@ describe('message actions', () => {
useStore.mockReturnValue({
getters: {
conversation: () => conversationProps,
getActorId: () => 'user-id-1',
getActorType: () => ATTENDEE.ACTOR_TYPE.USER,
isModerator: false,
},
})
@ -186,11 +187,10 @@ describe('message actions', () => {
useStore.mockReturnValue({
getters: {
conversation: () => conversationProps,
getActorId: () => 'user-id-1',
getActorType: () => ATTENDEE.ACTOR_TYPE.MODERATOR,
isModerator: true,
},
})
actorStore.actorType = ATTENDEE.ACTOR_TYPE.MODERATOR
// Act
const result = useMessageInfo(message)
// Assert
@ -205,8 +205,6 @@ describe('message actions', () => {
useStore.mockReturnValue({
getters: {
conversation: () => conversationProps,
getActorId: () => 'user-id-1',
getActorType: () => ATTENDEE.ACTOR_TYPE.USERS,
isModerator: false,
},
})
@ -273,8 +271,6 @@ describe('message actions', () => {
useStore.mockReturnValue({
getters: {
conversation: () => null,
getActorId: () => 'user-id-1',
getActorType: () => ATTENDEE.ACTOR_TYPE.USERS,
},
})
// Act

View file

@ -5,6 +5,7 @@
import { n, t } from '@nextcloud/l10n'
import cloneDeep from 'lodash/cloneDeep.js'
import { useActorStore } from '../stores/actor.ts'
import { useStore } from './useStore.js'
/**
@ -13,16 +14,7 @@ import { useStore } from './useStore.js'
*/
export function useCombinedSystemMessage() {
const store = useStore()
/**
*
* @param {object} message message to check for
* @return {boolean}
*/
function checkIfSelfIsActor(message) {
return message.actorId === store.getters.getActorId()
&& message.actorType === store.getters.getActorType()
}
const actorStore = useActorStore()
/**
*
@ -30,8 +22,8 @@ export function useCombinedSystemMessage() {
* @return {boolean}
*/
function checkIfSelfIsOneOfActors(message) {
return message.messageParameters.actor.id === store.getters.getActorId()
&& message.messageParameters.actor.type + 's' === store.getters.getActorType()
return message.messageParameters.actor.id === actorStore.actorId
&& message.messageParameters.actor.type + 's' === actorStore.actorType
}
/**
@ -40,8 +32,8 @@ export function useCombinedSystemMessage() {
* @return {boolean}
*/
function checkIfSelfIsOneOfUsers(message) {
return message.messageParameters.user.id === store.getters.getActorId()
&& message.messageParameters.user.type + 's' === store.getters.getActorType()
return message.messageParameters.user.id === actorStore.actorId
&& message.messageParameters.user.type + 's' === actorStore.actorType
}
/**
@ -90,7 +82,7 @@ export function useCombinedSystemMessage() {
usersCounter++
})
if (checkIfSelfIsActor(combinedMessage)) {
if (actorStore.checkIfSelfIsActor(combinedMessage)) {
if (usersCounter === 2) {
combinedMessage.message = t('spreed', 'You added {user0} and {user1}')
} else {
@ -156,7 +148,7 @@ export function useCombinedSystemMessage() {
usersCounter++
})
if (checkIfSelfIsActor(combinedMessage)) {
if (actorStore.checkIfSelfIsActor(combinedMessage)) {
if (usersCounter === 2) {
combinedMessage.message = t('spreed', 'You removed {user0} and {user1}')
} else {
@ -299,7 +291,7 @@ export function useCombinedSystemMessage() {
usersCounter++
})
if (checkIfSelfIsActor(combinedMessage)) {
if (actorStore.checkIfSelfIsActor(combinedMessage)) {
if (usersCounter === 2) {
combinedMessage.message = t('spreed', 'You promoted {user0} and {user1} to moderators')
} else {
@ -365,7 +357,7 @@ export function useCombinedSystemMessage() {
usersCounter++
})
if (checkIfSelfIsActor(combinedMessage)) {
if (actorStore.checkIfSelfIsActor(combinedMessage)) {
if (usersCounter === 2) {
combinedMessage.message = t('spreed', 'You demoted {user0} and {user1} from moderators')
} else {

View file

@ -10,6 +10,7 @@ import { t } from '@nextcloud/l10n'
import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router/composables'
import { EventBus } from '../services/EventBus.ts'
import { useActorStore } from '../stores/actor.ts'
import { hasCall, hasUnreadMentions } from '../utils/conversation.ts'
import { useDocumentVisibility } from './useDocumentVisibility.ts'
import { useStore } from './useStore.js'
@ -35,8 +36,9 @@ export function useDocumentTitle() {
const savedLastMessageMap = ref<LastMessageMap>({})
const conversationList = computed(() => store.getters.conversationsList)
const actorId = computed(() => store.getters.getActorId())
const actorType = computed(() => store.getters.getActorType())
const actorStore = useActorStore()
const actorId = computed(() => actorStore.actorId)
const actorType = computed(() => actorStore.actorType)
watch(conversationList, (newValue) => {
if (isDocumentVisible.value || document.title.startsWith('* ')

View file

@ -7,6 +7,7 @@ import { t } from '@nextcloud/l10n'
import { computed, ref } from 'vue'
import { ATTENDEE, CONVERSATION, MESSAGE } from '../constants.ts'
import { hasTalkFeature } from '../services/CapabilitiesManager.ts'
import { useActorStore } from '../stores/actor.ts'
import { useGuestNameStore } from '../stores/guestName.js'
import { ONE_DAY_IN_MS, ONE_HOUR_IN_MS } from '../utils/formattedTime.ts'
import { getDisplayNameWithFallback } from '../utils/getDisplayName.ts'
@ -22,9 +23,10 @@ import { useStore } from './useStore.js'
export function useMessageInfo(message = ref({})) {
// Get the conversation
const store = useStore()
const actorStore = useActorStore()
const conversation = computed(() => store.getters.conversation(message.value.token))
const currentActorId = store.getters.getActorId()
const currentActorType = store.getters.getActorType()
const currentActorId = actorStore.actorId
const currentActorType = actorStore.actorType
// If the conversation or message is not available, return false
if (!conversation.value || !message.value.id) {
return {

View file

@ -5,6 +5,7 @@
import type { PrepareTemporaryMessagePayload } from '../utils/prepareTemporaryMessage.ts'
import { useActorStore } from '../stores/actor.ts'
import { useChatExtrasStore } from '../stores/chatExtras.js'
import { prepareTemporaryMessage } from '../utils/prepareTemporaryMessage.ts'
import { useStore } from './useStore.js'
@ -16,6 +17,7 @@ import { useStore } from './useStore.js'
export function useTemporaryMessage(context: unknown) {
const store = context ?? useStore()
const chatExtrasStore = useChatExtrasStore()
const actorStore = useActorStore()
/**
* @param payload payload for generating a temporary message
@ -25,9 +27,9 @@ export function useTemporaryMessage(context: unknown) {
return prepareTemporaryMessage({
...payload,
actorId: store.getters.getActorId(),
actorType: store.getters.getActorType(),
actorDisplayName: store.getters.getDisplayName(),
actorId: actorStore.actorId ?? '',
actorType: actorStore.actorType ?? '',
actorDisplayName: actorStore.displayName,
parent: parentId && store.getters.message(payload.token, parentId),
})
}

View file

@ -1,204 +0,0 @@
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/**
* This store helps to identify a the current actor in all cases.
* In Talk not every user is a local nextcloud user, so identifying
* solely by userId is not enough.
* If an as no userId, they are a guest and identified by actorType + sessionId.
*/
import { loadState } from '@nextcloud/initial-state'
import { ATTENDEE, PARTICIPANT } from '../constants.ts'
import { getTeams } from '../services/teamsService.ts'
const state = {
userId: null,
sessionId: null,
attendeeId: null,
actorId: null,
actorType: null,
displayName: '',
actorGroups: loadState('spreed', 'user_group_ids', []),
actorTeams: [],
}
const getters = {
getUserId: (state) => () => {
return state.userId
},
getSessionId: (state) => () => {
return state.sessionId
},
getAttendeeId: (state) => () => {
return state.attendeeId
},
getActorId: (state) => () => {
return state.actorId
},
getActorType: (state) => () => {
return state.actorType
},
isActorUser: (state) => () => {
return state.actorType === ATTENDEE.ACTOR_TYPE.USERS
},
isActorGuest: (state) => () => {
return state.actorType === ATTENDEE.ACTOR_TYPE.GUESTS
},
isActorMemberOfGroup: (state) => (groupId) => {
return state.actorGroups.includes(groupId)
},
isActorMemberOfTeam: (state) => (teamId) => {
return state.actorTeams.includes(teamId)
},
getDisplayName: (state) => () => {
return state.displayName
},
getParticipantIdentifier: (state) => () => {
return {
attendeeId: state.attendeeId,
actorType: state.actorType,
actorId: state.actorId,
sessionId: state.sessionId,
}
},
}
const mutations = {
/**
* Set the userId
*
* @param {object} state current store state;
* @param {string} userId The user id
*/
setUserId(state, userId) {
state.userId = userId
state.actorId = userId
},
/**
* Set the attendeeId
*
* @param {object} state current store state;
* @param {string} attendeeId The actors attendee id
*/
setAttendeeId(state, attendeeId) {
state.attendeeId = attendeeId
},
/**
* Set the sessionId
*
* @param {object} state current store state;
* @param {string} sessionId The actors session id
*/
setSessionId(state, sessionId) {
state.sessionId = sessionId
},
/**
* Set the actorId
*
* @param {object} state current store state;
* @param {string} actorId The actor id
*/
setActorId(state, actorId) {
state.actorId = actorId
},
/**
* Set the userId
*
* @param {object} state current store state;
* @param {string} displayName The name
*/
setDisplayName(state, displayName) {
state.displayName = displayName
},
/**
* Set the userId
*
* @param {object} state current store state;
* @param {actorType} actorType The actor type of the user
*/
setActorType(state, actorType) {
state.actorType = actorType
},
/**
* Set the user teams ids
*
* @param {object} state current store state;
* @param {Array} teams Teams ids of the current user
*/
setCurrentUserTeams(state, teams) {
state.actorTeams = teams
},
}
const actions = {
/**
* Set the actor from the current user
*
* @param {object} context default store context;
* @param {object} user A NextcloudUser object as returned by @nextcloud/auth
* @param {string} user.uid The user id of the user
* @param {string|null} user.displayName The display name of the user
*/
setCurrentUser(context, user) {
context.commit('setUserId', user.uid)
context.commit('setDisplayName', user.displayName || user.uid)
context.commit('setActorType', ATTENDEE.ACTOR_TYPE.USERS)
context.commit('setActorId', user.uid)
},
/**
* Set the actor from the current participant
*
* @param {object} context default store context;
* @param {object} participant The participant data
* @param {number} participant.attendeeId The attendee id of the participant
* @param {number} participant.participantType The type of the participant
* @param {string} participant.sessionId The session id of the participant
* @param {string} participant.actorId The actor id of the participant
*/
setCurrentParticipant(context, participant) {
context.commit('setSessionId', participant.sessionId)
context.commit('setAttendeeId', participant.attendeeId)
if (participant.participantType === PARTICIPANT.TYPE.GUEST
|| participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR) {
context.commit('setUserId', null)
context.commit('setActorType', ATTENDEE.ACTOR_TYPE.GUESTS)
context.commit('setActorId', participant.actorId)
// FIXME context.commit('setDisplayName', '')
}
},
/**
* Sets displayName only, we currently use this for guests user names.
*
* @param {object} context default store context;
* @param {string} displayName the display name to be set;
*/
setDisplayName(context, displayName) {
context.commit('setDisplayName', displayName)
},
/**
* Sets current user teams, if circles app enabled
*
* @param {object} context default store context;
*/
async getCurrentUserTeams(context) {
if (!loadState('spreed', 'circles_enabled', false)) {
return
}
try {
const response = await getTeams()
const teams = response.data.ocs.data.map((team) => team.id)
context.commit('setCurrentUserTeams', teams)
} catch (error) {
console.error(error)
}
},
}
export default { state, mutations, getters, actions }

View file

@ -1,95 +0,0 @@
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { createLocalVue } from '@vue/test-utils'
import { cloneDeep } from 'lodash'
import Vuex from 'vuex'
import { PARTICIPANT } from '../constants.ts'
import actorStore from './actorStore.js'
describe('actorStore', () => {
let localVue = null
let store = null
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
store = new Vuex.Store(cloneDeep(actorStore))
})
test('setCurrentUser updates all relevant attributes', () => {
store.dispatch('setCurrentUser', {
uid: 'userId',
displayName: 'display-name',
})
expect(store.getters.getUserId()).toBe('userId')
expect(store.getters.getDisplayName()).toBe('display-name')
expect(store.getters.getActorId()).toBe('userId')
expect(store.getters.getActorType()).toBe('users')
})
test('setDisplayName updates all relevant attributes', () => {
store.dispatch('setCurrentUser', {
uid: 'userId',
displayName: 'display-name',
})
store.dispatch('setDisplayName', 'new-display-name')
expect(store.getters.getUserId()).toBe('userId')
expect(store.getters.getDisplayName()).toBe('new-display-name')
})
describe('setCurrentParticipant', () => {
test('setCurrentParticipant with type GUEST clears user id and updates all relevant attributes', () => {
store.dispatch('setCurrentParticipant', {
actorId: 'guestActorId',
sessionId: 'XXSESSIONIDXX',
participantType: PARTICIPANT.TYPE.GUEST,
})
expect(store.getters.getSessionId()).toBe('XXSESSIONIDXX')
expect(store.getters.getUserId()).toBe(null)
expect(store.getters.getDisplayName()).toBe('')
expect(store.getters.getActorId()).toBe('guestActorId')
expect(store.getters.getActorType()).toBe('guests')
})
test('setCurrentParticipant with type GUEST_MODERATOR clears user id and updates all relevant attributes', () => {
store.dispatch('setCurrentParticipant', {
actorId: 'guestActorId',
sessionId: 'XXSESSIONIDXX',
participantType: PARTICIPANT.TYPE.GUEST_MODERATOR,
})
expect(store.getters.getSessionId()).toBe('XXSESSIONIDXX')
expect(store.getters.getUserId()).toBe(null)
expect(store.getters.getDisplayName()).toBe('')
expect(store.getters.getActorId()).toBe('guestActorId')
expect(store.getters.getActorType()).toBe('guests')
})
test('setCurrentParticipant with type USER keeps user id and updates all relevant attributes', () => {
store.dispatch('setCurrentUser', {
uid: 'userId',
displayName: 'display-name',
})
store.dispatch('setCurrentParticipant', {
actorId: 'userActorId',
sessionId: 'XXSESSIONIDXX',
participantType: PARTICIPANT.TYPE.USER,
})
expect(store.getters.getSessionId()).toBe('XXSESSIONIDXX')
// user values unchanged
expect(store.getters.getUserId()).toBe('userId')
expect(store.getters.getDisplayName()).toBe('display-name')
expect(store.getters.getActorId()).toBe('userId')
expect(store.getters.getActorType()).toBe('users')
})
})
})

View file

@ -64,6 +64,7 @@ import {
stopCallRecording,
} from '../services/recordingService.js'
import { talkBroadcastChannel } from '../services/talkBroadcastChannel.js'
import { useActorStore } from '../stores/actor.ts'
import { useBreakoutRoomsStore } from '../stores/breakoutRooms.ts'
import { useChatExtrasStore } from '../stores/chatExtras.js'
import { useFederationStore } from '../stores/federation.ts'
@ -299,10 +300,11 @@ const actions = {
return
}
const actorStore = useActorStore()
// Add current user to a new conversation participants
let currentUser = {
uid: context.getters.getUserId(),
displayName: context.getters.getDisplayName(),
uid: actorStore.userId,
displayName: actorStore.displayName,
}
// Fallback to getCurrentUser() only if it has not been set yet (as
@ -1059,8 +1061,9 @@ const actions = {
*/
async extendOneToOneConversation(context, { token, newParticipants }) {
const conversation = context.getters.conversation(token)
const actorStore = useActorStore()
const participants = [
{ id: conversation.actorId, source: conversation.actorType, label: context.rootGetters.getDisplayName() },
{ id: conversation.actorId, source: conversation.actorType, label: actorStore.displayName },
...newParticipants,
]
const roomName = getDisplayNamesList(participants.map((participant) => participant.label), CONVERSATION.MAX_NAME_LENGTH)

View file

@ -35,6 +35,7 @@ import {
setSIPEnabled,
} from '../services/conversationsService.ts'
import { setConversationUnread, updateLastReadMessage } from '../services/messagesService.ts'
import { useActorStore } from '../stores/actor.ts'
import { useTalkHashStore } from '../stores/talkHash.js'
import { generateOCSErrorResponse, generateOCSResponse } from '../test-helpers.js'
import storeConfig from './storeConfig.js'
@ -87,12 +88,14 @@ describe('conversationsStore', () => {
let localVue = null
let store = null
let addParticipantOnceAction = null
let actorStore
const permissions = PARTICIPANT.PERMISSIONS.MAX_CUSTOM
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
setActivePinia(createPinia())
actorStore = useActorStore()
testConversation = {
token: testToken,
@ -139,7 +142,7 @@ describe('conversationsStore', () => {
})
test('adds conversation to the store, with current user as participant', () => {
store.dispatch('setCurrentUser', {
actorStore.setCurrentUser({
uid: 'current-user',
displayName: 'display-name',
})
@ -169,7 +172,7 @@ describe('conversationsStore', () => {
})
test('adds conversation to the store, with empty user id for guests', () => {
store.dispatch('setCurrentParticipant', {
actorStore.setCurrentParticipant({
actorId: 'guestActorId',
sessionId: 'XXSESSIONIDXX',
participantType: PARTICIPANT.TYPE.GUEST,
@ -201,7 +204,7 @@ describe('conversationsStore', () => {
})
test('deletes messages with conversation', () => {
store.dispatch('setCurrentUser', {
actorStore.setCurrentUser({
uid: 'current-user',
displayName: 'display-name',
})
@ -910,7 +913,7 @@ describe('conversationsStore', () => {
describe('read marker', () => {
beforeEach(() => {
store = new Vuex.Store(testStoreConfig)
store.commit('setUserId', 'current-user')
actorStore.userId = 'current-user'
})
test('marks conversation as read by clearing unread counters', async () => {

View file

@ -18,6 +18,7 @@ import {
shareFile,
} from '../services/filesSharingServices.ts'
import { setAttachmentFolder } from '../services/settingsService.ts'
import { useActorStore } from '../stores/actor.ts'
import { useChatExtrasStore } from '../stores/chatExtras.js'
import {
findUniquePath,
@ -335,7 +336,8 @@ const actions = {
*/
async prepareUploadPaths(context, { token, uploadId }) {
const client = getDavClient()
const userRoot = '/files/' + context.getters.getUserId()
const actorStore = useActorStore()
const userRoot = '/files/' + actorStore.userId
// Store propfind attempts within one action to reduce amount of requests for duplicates
const knownPaths = {}
@ -584,7 +586,8 @@ const actions = {
return
}
if (getters.getUserId() === null) {
const actorStore = useActorStore()
if (actorStore.userId === null) {
console.debug('Skip file templates setup for participants that are not logged in')
commit('markFileTemplatesInitialised')
return

View file

@ -12,9 +12,9 @@ import Vuex from 'vuex'
import { getDavClient } from '../services/DavClient.js'
import { shareFile } from '../services/filesSharingServices.ts'
import { setAttachmentFolder } from '../services/settingsService.ts'
import { useActorStore } from '../stores/actor.ts'
import { findUniquePath } from '../utils/fileUpload.js'
import fileUploadStore from './fileUploadStore.js'
import storeConfig from './storeConfig.js'
jest.mock('../services/DavClient', () => ({
getDavClient: jest.fn(),
@ -38,11 +38,13 @@ describe('fileUploadStore', () => {
let storeConfig = null
let store = null
let mockedActions = null
let actorStore
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
setActivePinia(createPinia())
actorStore = useActorStore()
mockedActions = {
addTemporaryMessage: jest.fn(),
@ -53,10 +55,10 @@ describe('fileUploadStore', () => {
storeConfig = cloneDeep(fileUploadStore)
storeConfig.actions = Object.assign(storeConfig.actions, mockedActions)
storeConfig.getters.getUserId = jest.fn().mockReturnValue(() => 'current-user')
storeConfig.getters.getActorId = jest.fn().mockReturnValue(() => 'current-user')
storeConfig.getters.getActorType = jest.fn().mockReturnValue(() => 'users')
storeConfig.getters.getDisplayName = jest.fn().mockReturnValue(() => 'Current User')
actorStore.userId = 'current-user'
actorStore.actorId = 'current-user'
actorStore.actorType = 'users'
actorStore.displayName = 'Current User'
})
afterEach(() => {

View file

@ -25,6 +25,7 @@ import {
postRichObjectToConversation,
updateLastReadMessage,
} from '../services/messagesService.ts'
import { useActorStore } from '../stores/actor.ts'
import { useCallViewStore } from '../stores/callView.ts'
import { useGuestNameStore } from '../stores/guestName.js'
import { usePollsStore } from '../stores/polls.ts'
@ -44,6 +45,7 @@ import { convertToUnix } from '../utils/formattedTime.ts'
* false otherwise
*/
function hasMentionToSelf(context, message) {
const actorStore = useActorStore()
if (!message.messageParameters) {
return false
}
@ -55,14 +57,14 @@ function hasMentionToSelf(context, message) {
return true
}
if (param.type === 'guest'
&& context.getters.isActorGuest()
&& param.id === ('guest/' + context.getters.getActorId())
&& actorStore.isActorGuest
&& param.id === ('guest/' + actorStore.actorId)
) {
return true
}
if (param.type === 'user'
&& context.getters.isActorUser()
&& param.id === context.getters.getUserId()
&& actorStore.isActorUser
&& param.id === actorStore.userId
) {
return true
}
@ -542,6 +544,7 @@ const actions = {
*/
processMessage(context, { token, message }) {
const sharedItemsStore = useSharedItemsStore()
const actorStore = useActorStore()
if (message.systemMessage === 'message_deleted'
|| message.systemMessage === 'reaction'
@ -588,8 +591,7 @@ const actions = {
if (tempMessages.length > 0) {
// Replacing temporary placeholder message with server response (text message / file share)
const conversation = context.getters.conversation(token)
const isOwnMessage = message.actorId === context.getters.getActorId()
&& message.actorType === context.getters.getActorType()
const isOwnMessage = actorStore.checkIfSelfIsActor(message)
// update lastMessage and lastReadMessage (no await to make it async)
// do it conditionally because there could have been more messages appearing concurrently
@ -856,7 +858,8 @@ const actions = {
context.commit('setVisualLastReadMessageId', { token, id: visualIdToUpdate })
}
if (context.getters.getUserId()) {
const actorStore = useActorStore()
if (actorStore.userId) {
// only update on server side if there's an actual user, not guest
const response = await updateLastReadMessage(token, id)
context.dispatch('addConversation', response.data.ocs.data)
@ -1107,6 +1110,7 @@ const actions = {
* @param {number} data.lastKnownMessageId The id of the last message in the store.
*/
async pollNewMessages(context, { token, lastKnownMessageId, requestId, requestOptions }) {
const actorStore = useActorStore()
context.dispatch('cancelPollNewMessages', { requestId })
if (!lastKnownMessageId) {
@ -1137,8 +1141,8 @@ const actions = {
}
const conversation = context.getters.conversation(token)
const actorId = context.getters.getActorId()
const actorType = context.getters.getActorType()
const actorId = actorStore.actorId
const actorType = actorStore.actorType
let countNewMessages = 0
let hasNewMention = conversation.unreadMention
let lastMessage = null
@ -1179,14 +1183,13 @@ const actions = {
}
if (message.systemMessage === 'call_ended_everyone'
&& conversation.type !== CONVERSATION.TYPE.ONE_TO_ONE
&& !(message.actorId === context.getters.getActorId()
&& message.actorType === context.getters.getActorType())) {
&& !actorStore.checkIfSelfIsActor(message)) {
const callViewStore = useCallViewStore()
callViewStore.setCallHasJustEnded(message.timestamp)
context.dispatch('leaveCall', {
token,
participantIdentifier: context.getters.getParticipantIdentifier(),
participantIdentifier: actorStore.participantIdentifier,
})
}
}

View file

@ -26,6 +26,7 @@ import {
postRichObjectToConversation,
updateLastReadMessage,
} from '../services/messagesService.ts'
import { useActorStore } from '../stores/actor.ts'
import { useGuestNameStore } from '../stores/guestName.js'
import { useReactionsStore } from '../stores/reactions.js'
import { generateOCSErrorResponse, generateOCSResponse } from '../test-helpers.js'
@ -76,37 +77,32 @@ describe('messagesStore', () => {
let localVue = null
let testStoreConfig
let store = null
let getActorIdMock
let getUserIdMock
let getActorTypeMock
let getDisplayNameMock
let conversationMock
let updateConversationLastMessageMock
let updateConversationLastReadMessageMock
let updateConversationLastActiveAction
let reactionsStore
let actorStore
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
setActivePinia(createPinia())
reactionsStore = useReactionsStore()
actorStore = useActorStore()
testStoreConfig = cloneDeep(storeConfig)
getActorIdMock = jest.fn().mockReturnValue(() => 'actor-id-1')
getUserIdMock = jest.fn().mockReturnValue(() => 'actor-id-1')
getActorTypeMock = jest.fn().mockReturnValue(() => ATTENDEE.ACTOR_TYPE.USERS)
getDisplayNameMock = jest.fn().mockReturnValue(() => 'actor-display-name-1')
actorStore.actorId = 'actor-id-1'
actorStore.userId = 'actor-id-1'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
actorStore.displayName = 'actor-display-name-1'
conversationMock = jest.fn().mockReturnValue(conversation)
updateConversationLastMessageMock = jest.fn()
updateConversationLastReadMessageMock = jest.fn()
updateConversationLastActiveAction = jest.fn()
testStoreConfig.modules.actorStore.getters.getActorId = getActorIdMock
testStoreConfig.modules.actorStore.getters.getUserId = getUserIdMock
testStoreConfig.modules.actorStore.getters.getActorType = getActorTypeMock
testStoreConfig.modules.actorStore.getters.getDisplayName = getDisplayNameMock
testStoreConfig.modules.conversationsStore.getters.conversation = jest.fn().mockReturnValue(conversationMock)
testStoreConfig.modules.conversationsStore.actions.updateConversationLastMessage = updateConversationLastMessageMock
testStoreConfig.modules.conversationsStore.actions.updateConversationLastReadMessage = updateConversationLastReadMessageMock
@ -689,6 +685,8 @@ describe('messagesStore', () => {
describe('last read message markers', () => {
beforeEach(() => {
const response = generateOCSResponse({ payload: conversation })
actorStore.userId = 'user-1'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
updateLastReadMessage.mockResolvedValue(response)
})
@ -701,7 +699,7 @@ describe('messagesStore', () => {
})
test('clears last read message', async () => {
getUserIdMock.mockReturnValue(() => 'user-1')
actorStore.userId = 'user-1'
store.dispatch('setVisualLastReadMessageId', { token: TOKEN, id: 100 })
await store.dispatch('clearLastReadMessage', {
@ -710,7 +708,6 @@ describe('messagesStore', () => {
})
expect(conversationMock).toHaveBeenCalled()
expect(getUserIdMock).toHaveBeenCalled()
expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), {
token: TOKEN,
lastReadMessage: 123,
@ -721,7 +718,6 @@ describe('messagesStore', () => {
})
test('clears last read message for federated conversation', async () => {
getUserIdMock.mockReturnValue(() => 'federated-user-1')
conversationMock.mockReturnValue({
lastMessage: {},
remoteServer: 'nextcloud.com',
@ -735,7 +731,6 @@ describe('messagesStore', () => {
})
expect(conversationMock).toHaveBeenCalled()
expect(getUserIdMock).toHaveBeenCalled()
expect(updateConversationLastReadMessageMock).not.toHaveBeenCalled()
expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, null)
@ -743,8 +738,6 @@ describe('messagesStore', () => {
})
test('clears last read message and update visually', async () => {
getUserIdMock.mockReturnValue(() => 'user-1')
store.dispatch('setVisualLastReadMessageId', { token: TOKEN, id: 100 })
await store.dispatch('clearLastReadMessage', {
token: TOKEN,
@ -752,7 +745,6 @@ describe('messagesStore', () => {
})
expect(conversationMock).toHaveBeenCalled()
expect(getUserIdMock).toHaveBeenCalled()
expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), {
token: TOKEN,
lastReadMessage: 123,
@ -763,7 +755,8 @@ describe('messagesStore', () => {
})
test('clears last read message for guests', async () => {
getUserIdMock.mockReturnValue(() => null)
actorStore.userId = null
actorStore.actorType = ATTENDEE.ACTOR_TYPE.GUESTS
store.dispatch('setVisualLastReadMessageId', { token: TOKEN, id: 100 })
await store.dispatch('clearLastReadMessage', {
@ -772,7 +765,7 @@ describe('messagesStore', () => {
})
expect(conversationMock).toHaveBeenCalled()
expect(getUserIdMock).toHaveBeenCalled()
expect(actorStore.isActorGuest).toBe(true)
expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), {
token: TOKEN,
lastReadMessage: 123,
@ -783,7 +776,6 @@ describe('messagesStore', () => {
})
test('updates last read message', async () => {
getUserIdMock.mockReturnValue(() => 'user-1')
const response = generateOCSResponse({
payload: {
unreadMessages: 0,
@ -800,7 +792,6 @@ describe('messagesStore', () => {
})
expect(conversationMock).toHaveBeenCalled()
expect(getUserIdMock).toHaveBeenCalled()
expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), {
token: TOKEN,
lastReadMessage: 200,
@ -811,7 +802,6 @@ describe('messagesStore', () => {
})
test('updates last read message and update visually', async () => {
getUserIdMock.mockReturnValue(() => 'user-1')
const response = generateOCSResponse({
payload: {
unreadMessages: 0,
@ -828,7 +818,6 @@ describe('messagesStore', () => {
})
expect(conversationMock).toHaveBeenCalled()
expect(getUserIdMock).toHaveBeenCalled()
expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), {
token: TOKEN,
lastReadMessage: 200,
@ -839,7 +828,8 @@ describe('messagesStore', () => {
})
test('updates last read message for guests', async () => {
getUserIdMock.mockReturnValue(() => null)
actorStore.userId = null
actorStore.actorType = ATTENDEE.ACTOR_TYPE.GUESTS
store.dispatch('setVisualLastReadMessageId', { token: TOKEN, id: 100 })
await store.dispatch('updateLastReadMessage', {
@ -849,7 +839,7 @@ describe('messagesStore', () => {
})
expect(conversationMock).toHaveBeenCalled()
expect(getUserIdMock).toHaveBeenCalled()
expect(actorStore.isActorGuest).toBe(true)
expect(updateConversationLastReadMessageMock).toHaveBeenCalledWith(expect.anything(), {
token: TOKEN,
lastReadMessage: 200,
@ -1155,28 +1145,13 @@ describe('messagesStore', () => {
let addGuestNameAction
let cancelFunctionMocks
let conversationMock
let getActorIdMock
let getActorTypeMock
let isActorUserMock
let isActorGuestMock
let getUserIdMock
beforeEach(() => {
testStoreConfig = cloneDeep(messagesStore)
const guestNameStore = useGuestNameStore()
conversationMock = jest.fn()
getActorIdMock = jest.fn()
getActorTypeMock = jest.fn()
isActorUserMock = jest.fn()
isActorGuestMock = jest.fn()
getUserIdMock = jest.fn()
testStoreConfig.getters.conversation = jest.fn().mockReturnValue(conversationMock)
testStoreConfig.getters.getActorId = jest.fn().mockReturnValue(getActorIdMock)
testStoreConfig.getters.getActorType = jest.fn().mockReturnValue(getActorTypeMock)
testStoreConfig.getters.isActorUser = jest.fn().mockReturnValue(isActorUserMock)
testStoreConfig.getters.isActorGuest = jest.fn().mockReturnValue(isActorGuestMock)
testStoreConfig.getters.getUserId = jest.fn().mockReturnValue(getUserIdMock)
updateConversationLastMessageAction = jest.fn()
updateLastCommonReadMessageAction = jest.fn()
@ -1527,10 +1502,8 @@ describe('messagesStore', () => {
})
test('updates unread mention flag for guest mention', async () => {
getActorIdMock.mockReturnValue('me_as_guest')
getActorTypeMock.mockReturnValue(ATTENDEE.ACTOR_TYPE.GUESTS)
isActorUserMock.mockReturnValue(false)
isActorGuestMock.mockReturnValue(true)
actorStore.actorId = 'me_as_guest'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.GUESTS
await testMentionFlag({
'mention-0': {
type: 'user',
@ -1544,8 +1517,8 @@ describe('messagesStore', () => {
})
test('does not update unread mention flag for a different guest mention', async () => {
getActorIdMock.mockReturnValue('me_as_guest')
getActorTypeMock.mockReturnValue(ATTENDEE.ACTOR_TYPE.GUESTS)
actorStore.actorId = 'me_as_guest'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.GUESTS
await testMentionFlag({
'mention-1': {
type: 'guest',
@ -1555,11 +1528,9 @@ describe('messagesStore', () => {
})
test('updates unread mention flag for user mention', async () => {
getUserIdMock.mockReturnValue('me_as_user')
getActorIdMock.mockReturnValue('me_as_user')
getActorTypeMock.mockReturnValue(ATTENDEE.ACTOR_TYPE.USERS)
isActorUserMock.mockReturnValue(true)
isActorGuestMock.mockReturnValue(false)
actorStore.actorId = 'me_as_user'
actorStore.userId = 'me_as_user'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
await testMentionFlag({
'mention-0': {
type: 'user',
@ -1573,8 +1544,9 @@ describe('messagesStore', () => {
})
test('does not update unread mention flag for another user mention', async () => {
getActorIdMock.mockReturnValue('me_as_user')
getActorTypeMock.mockReturnValue(ATTENDEE.ACTOR_TYPE.USERS)
actorStore.actorId = 'me_as_user'
actorStore.userId = 'me_as_user'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
await testMentionFlag({
'mention-1': {
type: 'user',
@ -1636,9 +1608,6 @@ describe('messagesStore', () => {
describe('posting new message', () => {
let message1
let conversationMock
let getUserIdMock
let getActorIdMock
let getActorTypeMock
let updateLastCommonReadMessageAction
let updateConversationLastMessageAction
let cancelFunctionMocks
@ -1651,15 +1620,11 @@ describe('messagesStore', () => {
console.error = jest.fn()
conversationMock = jest.fn()
getUserIdMock = jest.fn()
getActorIdMock = jest.fn().mockReturnValue(() => 'actor-id-1')
getActorTypeMock = jest.fn().mockReturnValue(() => ATTENDEE.ACTOR_TYPE.USERS)
actorStore.actorId = 'actor-id-1'
actorStore.actorType = ATTENDEE.ACTOR_TYPE.USERS
updateConversationLastMessageAction = jest.fn()
updateLastCommonReadMessageAction = jest.fn()
testStoreConfig.getters.conversation = jest.fn().mockReturnValue(conversationMock)
testStoreConfig.getters.getUserId = jest.fn().mockReturnValue(getUserIdMock)
testStoreConfig.getters.getActorId = getActorIdMock
testStoreConfig.getters.getActorType = getActorTypeMock
testStoreConfig.actions.updateConversationLastMessage = updateConversationLastMessageAction
testStoreConfig.actions.updateLastCommonReadMessage = updateLastCommonReadMessageAction
// mock this complex local action as we already tested it elsewhere
@ -1697,7 +1662,7 @@ describe('messagesStore', () => {
lastMessage: { id: 100 },
lastReadMessage: 50,
})
getUserIdMock.mockReturnValue(() => 'current-user')
actorStore.userId = 'current-user'
const baseMessage = {
actorId: 'actor-id-1',

View file

@ -33,6 +33,7 @@ import {
} from '../services/participantsService.js'
import SessionStorage from '../services/SessionStorage.js'
import { talkBroadcastChannel } from '../services/talkBroadcastChannel.js'
import { useActorStore } from '../stores/actor.ts'
import { useCallViewStore } from '../stores/callView.ts'
import { useGuestNameStore } from '../stores/guestName.js'
import { useSessionStore } from '../stores/session.ts'
@ -123,17 +124,14 @@ const getters = {
* Gets the array of external session ids.
*
* @param {object} state - the state object.
* @param {object} getters - the getters object.
* @param {object} rootState - the rootState object.
* @param {object} rootGetters - the rootGetters object.
* @return {Array} the typing session IDs array.
*/
externalTypingSignals: (state, getters, rootState, rootGetters) => (token) => {
externalTypingSignals: (state) => (token) => {
if (!state.typing[token]) {
return []
}
return Object.keys(state.typing[token]).filter((sessionId) => rootGetters.getSessionId() !== sessionId)
const actorStore = useActorStore()
return Object.keys(state.typing[token]).filter((sessionId) => actorStore.sessionId !== sessionId)
},
/**
@ -149,8 +147,8 @@ const getters = {
if (!state.typing[rootGetters.getToken()]) {
return false
}
return Object.keys(state.typing[rootGetters.getToken()]).some((sessionId) => rootGetters.getSessionId() === sessionId)
const actorStore = useActorStore()
return Object.keys(state.typing[rootGetters.getToken()]).some((sessionId) => actorStore.sessionId === sessionId)
},
/**
@ -159,20 +157,19 @@ const getters = {
*
* @param {object} state - the state object.
* @param {object} getters - the getters object.
* @param {object} rootState - the rootState object.
* @param {object} rootGetters - the rootGetters object.
* @return {Array} the participants array (for registered users only).
*/
participantsListTyping: (state, getters, rootState, rootGetters) => (token) => {
participantsListTyping: (state, getters) => (token) => {
if (!getters.externalTypingSignals(token).length) {
return []
}
const actorStore = useActorStore()
return getters.participantsList(token).filter((attendee) => {
// Check if participant's sessionId matches with any of sessionIds from signaling...
return getters.externalTypingSignals(token).some((sessionId) => attendee.sessionIds.includes(sessionId))
// ... and it's not the participant with same actorType and actorId as yourself
&& (attendee.actorType !== rootGetters.getActorType() || attendee.actorId !== rootGetters.getActorId())
&& !actorStore.checkIfSelfIsActor(attendee)
})
},
@ -1030,16 +1027,17 @@ const actions = {
*/
async joinConversation(context, { token }) {
const forceJoin = SessionStorage.getItem('joined_conversation') === token
const actorStore = useActorStore()
try {
const response = await joinConversation({ token, forceJoin })
// Update the participant and actor session after a force join
context.dispatch('setCurrentParticipant', response.data.ocs.data)
actorStore.setCurrentParticipant(response.data.ocs.data)
context.dispatch('addConversation', response.data.ocs.data)
context.dispatch('updateSessionId', {
token,
participantIdentifier: context.getters.getParticipantIdentifier(),
participantIdentifier: actorStore.participantIdentifier,
sessionId: response.data.ocs.data.sessionId,
})
@ -1087,10 +1085,11 @@ const actions = {
* @param {string} data.token - conversation token.
*/
async leaveConversation(context, { token }) {
const actorStore = useActorStore()
if (context.getters.isInCall(token)) {
await context.dispatch('leaveCall', {
token,
participantIdentifier: context.getters.getParticipantIdentifier(),
participantIdentifier: actorStore.participantIdentifier,
})
}

View file

@ -29,6 +29,7 @@ import {
removeCurrentUserFromConversation,
resendInvitations,
} from '../services/participantsService.js'
import { useActorStore } from '../stores/actor.ts'
import { useGuestNameStore } from '../stores/guestName.js'
import { useSessionStore } from '../stores/session.ts'
import { generateOCSErrorResponse, generateOCSResponse } from '../test-helpers.js'
@ -73,12 +74,14 @@ describe('participantsStore', () => {
let localVue = null
let store = null
let guestNameStore = null
let actorStore
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
setActivePinia(createPinia())
guestNameStore = useGuestNameStore()
actorStore = useActorStore()
testStoreConfig = cloneDeep(participantsStore)
store = new Vuex.Store(testStoreConfig)
@ -795,7 +798,6 @@ describe('participantsStore', () => {
describe('joining conversation', () => {
let getTokenMock
let getParticipantIdentifierMock
let participantData
let joinedConversationEventMock
@ -804,9 +806,6 @@ describe('participantsStore', () => {
EventBus.once('joined-conversation', joinedConversationEventMock)
getTokenMock = jest.fn().mockReturnValue(TOKEN)
getParticipantIdentifierMock = jest.fn().mockReturnValue({
attendeeId: 1,
})
participantData = {
actorId: 'actor-id',
sessionId: 'session-id-1',
@ -815,9 +814,11 @@ describe('participantsStore', () => {
inCall: PARTICIPANT.CALL_FLAG.DISCONNECTED,
}
actorStore.setCurrentParticipant(Object.assign({}, participantData, {
attendeeId: 1,
}))
testStoreConfig.getters.getToken = () => getTokenMock
testStoreConfig.getters.getParticipantIdentifier = () => getParticipantIdentifierMock
testStoreConfig.actions.setCurrentParticipant = jest.fn()
testStoreConfig.actions.addConversation = jest.fn().mockImplementation((context) => {
// needed for the updateSessionId call which requires this
context.dispatch('addParticipantOnce', {
@ -836,10 +837,9 @@ describe('participantsStore', () => {
expect(joinConversation).toHaveBeenCalledWith({ token: TOKEN, forceJoin: false })
expect(returnedResponse).toBe(response)
expect(testStoreConfig.actions.setCurrentParticipant).toHaveBeenCalledWith(expect.anything(), participantData)
expect(testStoreConfig.actions.addConversation).toHaveBeenCalledWith(expect.anything(), participantData)
expect(getParticipantIdentifierMock).toHaveBeenCalled()
expect(actorStore.participantIdentifier.sessionId).toBe('session-id-1')
expect(store.getters.participantsList(TOKEN)[0])
.toStrictEqual(participantData)
@ -863,10 +863,9 @@ describe('participantsStore', () => {
expect(joinConversation).toHaveBeenCalledWith({ token: TOKEN, forceJoin: true })
expect(testStoreConfig.actions.setCurrentParticipant).toHaveBeenCalledWith(expect.anything(), updatedParticipantData)
expect(testStoreConfig.actions.addConversation).toHaveBeenCalledWith(expect.anything(), updatedParticipantData)
expect(getParticipantIdentifierMock).toHaveBeenCalled()
expect(actorStore.participantIdentifier.sessionId).toBe('another-session-id')
expect(store.getters.participantsList(TOKEN)[0])
.toStrictEqual(updatedParticipantData)
@ -881,9 +880,6 @@ describe('participantsStore', () => {
})
afterEach(() => {
jest.useRealTimers()
expect(testStoreConfig.actions.setCurrentParticipant).not.toHaveBeenCalled()
expect(testStoreConfig.actions.addConversation).not.toHaveBeenCalled()
expect(sessionStorage.setItem).not.toHaveBeenCalled()
expect(joinedConversationEventMock).not.toHaveBeenCalled()
@ -970,7 +966,7 @@ describe('participantsStore', () => {
})
test('leaves conversation while in call', async () => {
testStoreConfig.getters.getParticipantIdentifier = () => jest.fn().mockReturnValue({
actorStore.setCurrentParticipant({
attendeeId: 1,
sessionId: 'session-id-1',
})

View file

@ -3,7 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import actorStore from './actorStore.js'
import conversationsStore from './conversationsStore.js'
import fileUploadStore from './fileUploadStore.js'
import messagesStore from './messagesStore.js'
@ -12,7 +11,6 @@ import tokenStore from './tokenStore.js'
export default {
modules: {
actorStore,
conversationsStore,
fileUploadStore,
messagesStore,

View file

@ -0,0 +1,199 @@
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getCurrentUser } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'
import { createPinia, setActivePinia } from 'pinia'
import { ATTENDEE, PARTICIPANT } from '../../constants.ts'
import { getTeams } from '../../services/teamsService.ts'
import { generateOCSResponse } from '../../test-helpers.js'
import { useActorStore } from '../actor.ts'
jest.mock('@nextcloud/initial-state', () => ({
loadState: jest.fn(() => false),
}))
jest.mock('../../services/teamsService.ts', () => ({
getTeams: jest.fn(() => Promise.resolve({
data: {
ocs: {
data: [],
},
},
})),
}))
jest.mock('@nextcloud/auth', () => ({
getCurrentUser: jest.fn(),
}))
describe('actorStore', () => {
let actorStore
beforeEach(() => {
setActivePinia(createPinia())
actorStore = useActorStore()
})
afterEach(() => {
jest.clearAllMocks()
actorStore.userId = null
actorStore.sessionId = null
actorStore.attendeeId = null
actorStore.actorId = null
actorStore.actorType = null
actorStore.displayName = ''
actorStore.actorGroups = []
actorStore.actorTeams = []
})
describe('initialize', () => {
test('initialize the store', () => {
const user = { uid: 'userId', displayName: 'display-name' }
getCurrentUser.mockReturnValue(user)
expect(actorStore.actorId).toBeNull()
expect(actorStore.displayName).toBe('')
actorStore.initialize()
expect(actorStore.actorId).toBe('userId')
expect(actorStore.displayName).toBe('display-name')
})
})
test('setCurrentUser updates all relevant attributes', () => {
actorStore.setCurrentUser({
uid: 'userId',
displayName: 'display-name',
})
expect(actorStore.userId).toBe('userId')
expect(actorStore.displayName).toBe('display-name')
expect(actorStore.actorId).toBe('userId')
expect(actorStore.actorType).toBe('users')
})
test('setDisplayName updates all relevant attributes', () => {
actorStore.setCurrentUser({
uid: 'userId',
displayName: 'display-name',
})
actorStore.setDisplayName('new-display-name')
expect(actorStore.userId).toBe('userId')
expect(actorStore.displayName).toBe('new-display-name')
})
test('check if the message actor is the current one', () => {
actorStore.setCurrentParticipant({
actorId: 'guestId',
attendeeId: 1,
displayName: 'display-name',
participantType: PARTICIPANT.TYPE.GUEST,
})
const message = {
actorId: 'guestId',
actorType: ATTENDEE.ACTOR_TYPE.GUESTS,
message: 'Hello',
}
expect(actorStore.checkIfSelfIsActor(message)).toBe(true)
})
describe('setCurrentParticipant', () => {
test('setCurrentParticipant with type GUEST clears user id and updates all relevant attributes', () => {
actorStore.setCurrentParticipant({
actorId: 'guestActorId',
sessionId: 'XXSESSIONIDXX',
participantType: PARTICIPANT.TYPE.GUEST,
})
expect(actorStore.userId).toBe(null)
expect(actorStore.actorId).toBe('guestActorId')
expect(actorStore.actorType).toBe('guests')
expect(actorStore.sessionId).toBe('XXSESSIONIDXX')
})
test('setCurrentParticipant with type GUEST_MODERATOR clears user id and updates all relevant attributes', () => {
actorStore.setCurrentParticipant({
actorId: 'guestActorId',
sessionId: 'XXSESSIONIDXX',
participantType: PARTICIPANT.TYPE.GUEST_MODERATOR,
})
expect(actorStore.userId).toBe(null)
expect(actorStore.actorId).toBe('guestActorId')
expect(actorStore.actorType).toBe('guests')
expect(actorStore.sessionId).toBe('XXSESSIONIDXX')
})
test('setCurrentParticipant with type USER keeps user id and updates all relevant attributes', () => {
actorStore.setCurrentUser({
uid: 'userId',
displayName: 'display-name',
})
actorStore.setCurrentParticipant({
actorId: 'userActorId',
sessionId: 'XXSESSIONIDXX',
participantType: PARTICIPANT.TYPE.USER,
})
expect(actorStore.sessionId).toBe('XXSESSIONIDXX')
// user values unchanged
expect(actorStore.userId).toBe('userId')
expect(actorStore.displayName).toBe('display-name')
expect(actorStore.actorId).toBe('userId')
expect(actorStore.actorType).toBe('users')
})
})
describe('Groups and Teams', () => {
test('isActorMemberOfGroup returns true for group member', () => {
actorStore.actorGroups = ['group1', 'group2']
expect(actorStore.isActorMemberOfGroup('group1')).toBe(true)
expect(actorStore.isActorMemberOfGroup('group3')).toBe(false)
})
test('isActorMemberOfTeam returns true for team member', () => {
actorStore.actorTeams = ['team1', 'team2']
expect(actorStore.isActorMemberOfTeam('team1')).toBe(true)
expect(actorStore.isActorMemberOfTeam('team3')).toBe(false)
})
})
describe('getCurrentUserTeams', () => {
test('does nothing if circles are not enabled', async () => {
loadState.mockReturnValue(false)
await actorStore.getCurrentUserTeams()
expect(getTeams).not.toHaveBeenCalled()
expect(actorStore.actorTeams).toEqual([])
})
test('sets actorTeams from API response', async () => {
loadState.mockReturnValue(true)
getTeams.mockResolvedValue(generateOCSResponse({
payload: [
{ id: 'team1' },
{ id: 'team2' },
],
}))
await actorStore.getCurrentUserTeams()
expect(getTeams).toHaveBeenCalled()
expect(actorStore.actorTeams).toEqual(['team1', 'team2'])
})
test('logs error if getTeams throws', async () => {
loadState.mockReturnValue(true)
const error = new Error('fail')
getTeams.mockRejectedValue(error)
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
await actorStore.getCurrentUserTeams()
expect(consoleSpy).toHaveBeenCalledWith(error)
consoleSpy.mockRestore()
})
})
})

View file

@ -6,8 +6,8 @@ import { t } from '@nextcloud/l10n'
*/
import { createPinia, setActivePinia } from 'pinia'
import { setGuestUserName } from '../../services/participantsService.js'
import vuexStore from '../../store/index.js'
import { generateOCSErrorResponse } from '../../test-helpers.js'
import { useActorStore } from '../actor.ts'
import { useGuestNameStore } from '../guestName.js'
jest.mock('../../services/participantsService', () => ({
@ -20,10 +20,12 @@ jest.mock('@nextcloud/auth', () => ({
describe('guestNameStore', () => {
let store
let actorStore
beforeEach(() => {
setActivePinia(createPinia())
store = useGuestNameStore()
actorStore = useActorStore()
})
afterEach(() => {
@ -151,7 +153,7 @@ describe('guestNameStore', () => {
actorDisplayName: t('spreed', 'Guest'),
}
vuexStore.dispatch('setCurrentUser', { uid: 'actor-id1' })
actorStore.setCurrentUser({ uid: 'actor-id1' })
const newName = 'actor 1'
@ -165,7 +167,7 @@ describe('guestNameStore', () => {
expect(setGuestUserName).toHaveBeenCalledWith(actor1.token, newName)
expect(setGuestNickname).toHaveBeenCalledWith(newName)
expect(store.getGuestName('token-1', 'actor-id1')).toBe('actor 1')
expect(vuexStore.getters.getDisplayName()).toBe('actor 1')
expect(actorStore.displayName).toBe('actor 1')
})
test('removes display name from local storage when user sumbits an empty new name', async () => {
@ -177,7 +179,7 @@ describe('guestNameStore', () => {
}
const newName = ''
vuexStore.dispatch('setCurrentUser', { uid: 'actor-id1' })
actorStore.setCurrentUser({ uid: 'actor-id1' })
// Mock implementation of setGuestUserName
setGuestUserName.mockResolvedValue()
@ -199,7 +201,7 @@ describe('guestNameStore', () => {
}
console.error = jest.fn()
vuexStore.dispatch('setCurrentUser', { uid: 'actor-id1' })
actorStore.setCurrentUser({ uid: 'actor-id1' })
store.addGuestName(actor1, { noUpdate: false })
const newName = 'actor 1'
@ -213,6 +215,6 @@ describe('guestNameStore', () => {
// Assert
expect(setGuestUserName).toHaveBeenCalledWith(actor1.token, newName)
expect(vuexStore.getters.getDisplayName()).toBe(actor1.actorDisplayName)
expect(actorStore.displayName).toBe(actor1.actorDisplayName)
})
})

177
src/stores/actor.ts Normal file
View file

@ -0,0 +1,177 @@
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/**
* This store helps to identify a current actor in all cases.
* In Talk not every user is a local nextcloud user, so identifying
* solely by userId is not enough.
* If an as no userId, they are a guest and identified by actorType + sessionId.
*/
import type { NextcloudUser } from '@nextcloud/auth'
import type { Participant } from '../types/index.ts'
import { getCurrentUser } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { ATTENDEE, PARTICIPANT } from '../constants.ts'
import { getTeams } from '../services/teamsService.ts'
export const useActorStore = defineStore('actor', () => {
const userId = ref<string | null>(null)
const sessionId = ref<string | null>(null)
const attendeeId = ref<number | null>(null)
const actorId = ref<string | null>(null)
const actorType = ref<string | null>(null)
const displayName = ref<string>('')
const actorGroups = ref<string[]>(loadState('spreed', 'user_group_ids', []))
const actorTeams = ref<string[]>([])
const isLoggedIn = computed(() => userId.value !== null)
// TODO check usage for computed below, migrate to isLoggedIn where appropriate
const isActorUser = computed(() => actorType.value === ATTENDEE.ACTOR_TYPE.USERS)
const isActorGuest = computed(() => actorType.value === ATTENDEE.ACTOR_TYPE.GUESTS)
const participantIdentifier = computed(() => ({
attendeeId: attendeeId.value,
actorType: actorType.value,
actorId: actorId.value,
sessionId: sessionId.value,
}))
// Initialize the store
initialize()
/**
* Initialize the actor store.
*/
function initialize() {
if (getCurrentUser()) {
console.debug('Setting current user')
setCurrentUser(getCurrentUser())
getCurrentUserTeams()
} else {
console.debug('Can not set current user because it\'s a guest')
}
}
/**
* Check if the actor is a member of a group
*
* @param groupId The group id
*/
function isActorMemberOfGroup(groupId: string) {
return actorGroups.value.includes(groupId)
}
/**
* Check if the actor is a member of a team
*
* @param teamId The team id
*/
function isActorMemberOfTeam(teamId: string) {
return actorTeams.value.includes(teamId)
}
/**
* Check if the message is from the current actor
*
* @param payload object to check for
*/
function checkIfSelfIsActor(payload: { actorId?: string, actorType?: string }) {
return payload.actorId === actorId.value
&& payload.actorType === actorType.value
}
/**
* Set the display name of the actor
*
* @param newDisplayName The name to set
*/
function setDisplayName(newDisplayName: string) {
displayName.value = newDisplayName
}
/**
* Set the actor from the current user
*
* @param user A NextcloudUser object as returned by @nextcloud/auth
* @param user.uid The user id of the user
* @param user.displayName The display name of the user
*/
function setCurrentUser(user: NextcloudUser | null) {
if (!user) {
return
}
userId.value = user.uid
displayName.value = user.displayName || user.uid
actorType.value = ATTENDEE.ACTOR_TYPE.USERS
actorId.value = user.uid
}
/**
* Set the actor from the current participant
*
* @param participant The participant data
* @param participant.attendeeId The attendee id of the participant
* @param participant.participantType The type of the participant
* @param participant.sessionId The session id of the participant
* @param participant.actorId The actor id of the participant
*/
function setCurrentParticipant(participant: Participant & { sessionId: string }) {
sessionId.value = participant.sessionId
attendeeId.value = participant.attendeeId
// FIXME other actor types like EMAILS
if (participant.participantType === PARTICIPANT.TYPE.GUEST
|| participant.participantType === PARTICIPANT.TYPE.GUEST_MODERATOR) {
// FIXME displayName.value = ''
userId.value = null
actorType.value = ATTENDEE.ACTOR_TYPE.GUESTS
actorId.value = participant.actorId
}
}
/**
* Sets current user teams, if circles app enabled
*
*/
async function getCurrentUserTeams() {
if (!loadState('spreed', 'circles_enabled', false)) {
return
}
try {
const response = await getTeams()
const teams = response.data.ocs.data.map((team) => team.id)
actorTeams.value = teams
} catch (error) {
console.error(error)
}
}
return {
userId,
sessionId,
attendeeId,
actorId,
actorType,
displayName,
actorGroups,
actorTeams,
isLoggedIn,
isActorUser,
isActorGuest,
participantIdentifier,
isActorMemberOfGroup,
isActorMemberOfTeam,
checkIfSelfIsActor,
initialize,
setDisplayName,
setCurrentUser,
setCurrentParticipant,
getCurrentUserTeams,
}
})

View file

@ -8,7 +8,7 @@ import { t } from '@nextcloud/l10n'
import { defineStore } from 'pinia'
import Vue from 'vue'
import { setGuestUserName } from '../services/participantsService.js'
import store from '../store/index.js'
import { useActorStore } from './actor.ts'
export const useGuestNameStore = defineStore('guestName', {
state: () => ({
@ -77,11 +77,12 @@ export const useGuestNameStore = defineStore('guestName', {
* @param {string} name the new guest name
*/
async submitGuestUsername(token, name) {
const actorId = store.getters.getActorId()
const actorStore = useActorStore()
const actorId = actorStore.actorId
const previousName = this.getGuestName(token, actorId)
try {
store.dispatch('setDisplayName', name)
actorStore.setDisplayName(name)
this.addGuestName({
token,
actorId,
@ -93,7 +94,7 @@ export const useGuestNameStore = defineStore('guestName', {
setGuestNickname(name || t('spreed', 'Guest'))
emit('talk:guest-name:added')
} catch (error) {
store.dispatch('setDisplayName', previousName)
actorStore.setDisplayName(previousName)
this.addGuestName({
token,
actorId,

View file

@ -3,6 +3,8 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { useActorStore } from '../stores/actor.ts'
import pinia from '../stores/pinia.ts'
import SignalingParticipantList from './SignalingParticipantList.js'
/**
@ -17,6 +19,7 @@ import SignalingParticipantList from './SignalingParticipantList.js'
*/
export default function SignalingTypingHandler(store) {
this._store = store
this._actorStore = useActorStore(pinia)
this._signaling = null
this._signalingParticipantList = new SignalingParticipantList()
@ -74,7 +77,7 @@ SignalingTypingHandler.prototype = {
return
}
const currentNextcloudSessionId = this._store.getters.getSessionId()
const currentNextcloudSessionId = this._actorStore.sessionId
for (const participant of this._signalingParticipantList.getParticipants()) {
if (participant.nextcloudSessionId === currentNextcloudSessionId) {
@ -89,7 +92,7 @@ SignalingTypingHandler.prototype = {
this._store.dispatch('setTyping', {
token: this._store.getters.getToken(),
sessionId: this._store.getters.getSessionId(),
sessionId: this._actorStore.sessionId,
typing,
})
},

View file

@ -6,6 +6,7 @@
import { cloneDeep } from 'lodash'
import Vuex from 'vuex'
import storeConfig from '../store/storeConfig.js'
import { useActorStore } from '../stores/actor.ts'
import SignalingTypingHandler from './SignalingTypingHandler.js'
describe('SignalingTypingHandler', () => {
@ -74,6 +75,7 @@ describe('SignalingTypingHandler', () => {
beforeEach(() => {
const testStoreConfig = cloneDeep(storeConfig)
store = new Vuex.Store(testStoreConfig)
const actorStore = useActorStore()
signaling = new function() {
this._handlers = {}
@ -115,7 +117,7 @@ describe('SignalingTypingHandler', () => {
signalingTypingHandler = new SignalingTypingHandler(store)
signalingTypingHandler._signalingParticipantList.getParticipants = jest.fn()
store.dispatch('setCurrentParticipant', {
actorStore.setCurrentParticipant({
sessionId: 'localNextcloudSessionId',
attendeeId: 'localAttendeeId',
})

View file

@ -19,11 +19,15 @@ import { EventBus } from '../services/EventBus.ts'
import { rejoinConversation } from '../services/participantsService.js'
import { pullSignalingMessages } from '../services/signalingService.js'
import store from '../store/index.js'
import { useActorStore } from '../stores/actor.ts'
import pinia from '../stores/pinia.ts'
import CancelableRequest from './cancelableRequest.js'
import Encryption from './e2ee/encryption.js'
import { convertToUnix } from './formattedTime.ts'
import { messagePleaseTryToReload } from './talkDesktopUtils.ts'
const actorStore = useActorStore(pinia)
const Signaling = {
Base: {},
Internal: {},
@ -898,7 +902,7 @@ Signaling.Standalone.prototype.forceReconnect = function(newSession, flags) {
this.nextcloudSessionId = response.data.ocs.data.sessionId
store.dispatch('setCurrentParticipant', response.data.ocs.data)
actorStore.setCurrentParticipant(response.data.ocs.data)
store.commit('setInCall', {
token: this.currentRoomToken,
sessionId: this.nextcloudSessionId,

View file

@ -10,9 +10,13 @@
* It is expected that the speaking status of participant will be
* modified only when the current conversation is joined and call is started.
*/
import { useActorStore } from '../../stores/actor.ts'
import pinia from '../../stores/pinia.ts'
export default class SpeakingStatusHandler {
// Constants, properties
#store
#actorStore
#localMediaModel
#localCallParticipantModel
#callParticipantCollection
@ -26,6 +30,7 @@ export default class SpeakingStatusHandler {
constructor(store, localMediaModel, localCallParticipantModel, callParticipantCollection) {
this.#store = store
this.#actorStore = useActorStore(pinia)
this.#localMediaModel = localMediaModel
this.#localCallParticipantModel = localCallParticipantModel
this.#callParticipantCollection = callParticipantCollection
@ -95,7 +100,7 @@ export default class SpeakingStatusHandler {
*/
#handleLocalSpeaking(localMediaModel, speaking) {
this.#store.dispatch('setSpeaking', {
attendeeId: this.#store.getters.getAttendeeId(),
attendeeId: this.#actorStore.attendeeId,
speaking,
})
}
@ -106,7 +111,7 @@ export default class SpeakingStatusHandler {
*/
#handleLocalPeerId() {
this.#store.dispatch('setSpeaking', {
attendeeId: this.#store.getters.getAttendeeId(),
attendeeId: this.#actorStore.attendeeId,
speaking: this.#localMediaModel.attributes.speaking,
})
}

View file

@ -3,11 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { reactive } from 'vue'
import store from '../../../store/index.js'
import { reactive, watch } from 'vue'
import { useActorStore } from '../../../stores/actor.ts'
import pinia from '../../../stores/pinia.ts'
import EmitterMixin from '../../EmitterMixin.js'
import { ConnectionState } from './CallParticipantModel.js'
const actorStore = useActorStore(pinia)
/**
*
*/
@ -55,7 +57,10 @@ LocalCallParticipantModel.prototype = {
this.set('guestName', null)
this._webRtc.on('forcedMute', this._handleForcedMuteBound)
this._unwatchDisplayNameChange = store.watch((state) => state.actorStore.displayName, this.setGuestName.bind(this))
this._unwatchDisplayNameChange = watch(
() => actorStore.displayName,
this.setGuestName.bind(this),
)
},
setPeerId(peerId) {

View file

@ -11,6 +11,8 @@ import {
import { t } from '@nextcloud/l10n'
import { PARTICIPANT } from '../../constants.ts'
import store from '../../store/index.js'
import { useActorStore } from '../../stores/actor.ts'
import pinia from '../../stores/pinia.ts'
import { Sounds } from '../sounds.js'
import SimpleWebRTC from './simplewebrtc/simplewebrtc.js'
@ -31,6 +33,7 @@ let callParticipantCollection = null
let localCallParticipantModel = null
let showedTURNWarning = false
let sendCurrentStateWithRepetitionTimeout = null
const actorStore = useActorStore(pinia)
/**
* @param {Array} a Source object
@ -198,7 +201,7 @@ function sendCurrentMediaState() {
*
*/
function sendCurrentNick() {
webrtc.webrtc.emit('nickChanged', store.getters.getDisplayName())
webrtc.webrtc.emit('nickChanged', actorStore.displayName)
}
/**
@ -471,10 +474,9 @@ function usersInCallChanged(signaling, users) {
&& selfInCall === PARTICIPANT.CALL_FLAG.DISCONNECTED
&& localUserInCall) {
console.info('Force leaving the call for current participant')
store.dispatch('leaveCall', {
token: store.getters.getToken(),
participantIdentifier: store.getters.getParticipantIdentifier(),
participantIdentifier: actorStore.participantIdentifier,
})
// Do not return to disconnect already from the other participants
@ -669,7 +671,6 @@ export default function initWebRtc(signaling, _callParticipantCollection, _local
message.broadcaster = message.from
}
})
webrtc = new SimpleWebRTC({
autoRequestMedia: true,
debug: false,
@ -678,7 +679,7 @@ export default function initWebRtc(signaling, _callParticipantCollection, _local
connection: signaling,
enableDataChannels: true,
enableSimulcast: signaling.hasFeature('simulcast'),
nick: store.getters.getDisplayName(),
nick: actorStore.displayName,
})
if (!window.OCA.Talk) {

View file

@ -14,6 +14,7 @@ import PollViewer from '../components/PollViewer/PollViewer.vue'
import TopBar from '../components/TopBar/TopBar.vue'
import { useIsInCall } from '../composables/useIsInCall.js'
import { useStore } from '../composables/useStore.js'
import { useActorStore } from '../stores/actor.ts'
const props = defineProps<{
token: string
@ -23,6 +24,7 @@ const store = useStore()
const isInCall = useIsInCall()
const router = useRouter()
const route = useRoute()
const actorStore = useActorStore()
const isInLobby = computed(() => store.getters.isInLobby)
const connectionFailed = computed(() => store.getters.connectionFailed(props.token))
@ -32,7 +34,7 @@ watch(isInLobby, (isInLobby) => {
if (isInLobby && isInCall.value) {
store.dispatch('leaveCall', {
token: props.token,
participantIdentifier: store.getters.getParticipantIdentifier(),
participantIdentifier: actorStore.participantIdentifier,
})
}
})