From 8c6eabccd06e614a08f8d0b2b9fb885f69eb374c Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Tue, 7 Jul 2026 14:39:02 +0200 Subject: [PATCH] feat(sip): treat direct-dial-in and dial-out similarly - When calling back from direct-dial-in conversation, it's essentially the same dial-out request. So rejecting / ending the call should work the same way, as if for dial-out room type - Extract helper util to reuse it across project Signed-off-by: Maksim Sukharev --- .../CallView/shared/EmptyCallView.vue | 6 ++---- src/components/TopBar/CallButton.vue | 6 ++---- src/composables/useJoinCall.ts | 21 +++---------------- src/store/participantsStore.js | 15 +++++-------- src/utils/conversation.ts | 19 +++++++++++++++++ src/views/MainView.vue | 9 ++------ 6 files changed, 33 insertions(+), 43 deletions(-) diff --git a/src/components/CallView/shared/EmptyCallView.vue b/src/components/CallView/shared/EmptyCallView.vue index bdcf8cf14c6..da6dac4c1cf 100644 --- a/src/components/CallView/shared/EmptyCallView.vue +++ b/src/components/CallView/shared/EmptyCallView.vue @@ -36,6 +36,7 @@ import IconLink from 'vue-material-design-icons/Link.vue' import IconPhoneOutline from 'vue-material-design-icons/PhoneOutline.vue' import { useGetToken } from '../../../composables/useGetToken.ts' import { CONVERSATION, PARTICIPANT } from '../../../constants.ts' +import { isConversationPhoneRoom } from '../../../utils/conversation.ts' import { copyConversationLinkToClipboard } from '../../../utils/handleUrl.ts' export default { @@ -104,10 +105,7 @@ export default { }, isPhoneConversation() { - return this.conversation - && (this.conversation.objectType === CONVERSATION.OBJECT_TYPE.PHONE_LEGACY - || this.conversation.objectType === CONVERSATION.OBJECT_TYPE.PHONE_PERSISTENT - || this.conversation.objectType === CONVERSATION.OBJECT_TYPE.PHONE_TEMPORARY) + return this.conversation && isConversationPhoneRoom(this.conversation) }, canInviteOthers() { diff --git a/src/components/TopBar/CallButton.vue b/src/components/TopBar/CallButton.vue index 0fdebc417f2..953a935b419 100644 --- a/src/components/TopBar/CallButton.vue +++ b/src/components/TopBar/CallButton.vue @@ -129,6 +129,7 @@ import { useSoundsStore } from '../../stores/sounds.js' import { useTalkHashStore } from '../../stores/talkHash.js' import { useTokenStore } from '../../stores/token.ts' import { blockCalls, unsupportedWarning } from '../../utils/browserCheck.ts' +import { isConversationPhoneRoom } from '../../utils/conversation.ts' import { messagePleaseReload } from '../../utils/talkDesktopUtils.ts' export default { @@ -354,10 +355,7 @@ export default { }, isPhoneRoom() { - return this.conversation.objectId === CONVERSATION.OBJECT_ID.PHONE_OUTGOING - && (this.conversation.objectType === CONVERSATION.OBJECT_TYPE.PHONE_LEGACY - || this.conversation.objectType === CONVERSATION.OBJECT_TYPE.PHONE_PERSISTENT - || this.conversation.objectType === CONVERSATION.OBJECT_TYPE.PHONE_TEMPORARY) + return isConversationPhoneRoom(this.conversation) }, isInLobby() { diff --git a/src/composables/useJoinCall.ts b/src/composables/useJoinCall.ts index 632bf835fe5..091214eef8c 100644 --- a/src/composables/useJoinCall.ts +++ b/src/composables/useJoinCall.ts @@ -3,18 +3,19 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type { Conversation, Participant } from '../types/index.ts' +import type { Participant } from '../types/index.ts' import { showError } from '@nextcloud/dialogs' import { emit } from '@nextcloud/event-bus' import { t } from '@nextcloud/l10n' import { useStore } from 'vuex' -import { ATTENDEE, CALL, CONVERSATION, PARTICIPANT } from '../constants.ts' +import { ATTENDEE, CALL, PARTICIPANT } from '../constants.ts' import { callSIPDialOut } from '../services/callsService.ts' import { getTalkConfig } from '../services/CapabilitiesManager.ts' import { useActorStore } from '../stores/actor.ts' import { useSettingsStore } from '../stores/settings.ts' import { isAxiosErrorResponse } from '../types/guards.ts' +import { isConversationPhoneRoom } from '../utils/conversation.ts' /** * Handler function to join a call and manage side effects @@ -24,22 +25,6 @@ export function useJoinCall() { const settingsStore = useSettingsStore() const vuexStore = useStore() - /** - * Returns whether the conversation is a phone room (with a single SIP phone participant) - * - * @param conversation - conversation object - * @param conversation.objectId - conversation objectId - * @param conversation.objectType - conversation objectType - */ - function isConversationPhoneRoom({ objectId, objectType }: Conversation) { - return objectId === CONVERSATION.OBJECT_ID.PHONE_OUTGOING - && [ - CONVERSATION.OBJECT_TYPE.PHONE_LEGACY, - CONVERSATION.OBJECT_TYPE.PHONE_PERSISTENT, - CONVERSATION.OBJECT_TYPE.PHONE_TEMPORARY, - ].includes(objectType) - } - /** * Tries to call the given SIP phone participant * diff --git a/src/store/participantsStore.js b/src/store/participantsStore.js index 6150100368a..f0fd1ad90e9 100644 --- a/src/store/participantsStore.js +++ b/src/store/participantsStore.js @@ -9,7 +9,7 @@ import { emit } from '@nextcloud/event-bus' import { t } from '@nextcloud/l10n' import Hex from 'crypto-js/enc-hex.js' import SHA1 from 'crypto-js/sha1.js' -import { ATTENDEE, CONVERSATION, PARTICIPANT } from '../constants.ts' +import { ATTENDEE, PARTICIPANT } from '../constants.ts' import { banActor } from '../services/banService.ts' import { joinCall, @@ -41,6 +41,7 @@ import pinia from '../stores/pinia.ts' import { useSessionStore } from '../stores/session.ts' import { useTokenStore } from '../stores/token.ts' import CancelableRequest from '../utils/CancelableRequest.ts' +import { isConversationPhoneRoom } from '../utils/conversation.ts' import { convertToUnix } from '../utils/formattedTime.ts' import { messagePleaseTryToReload } from '../utils/talkDesktopUtils.ts' @@ -1286,17 +1287,11 @@ const actions = { }, 5000) } - // Special handling for dial-out rooms, if a call was rejected + // Special handling for phone rooms, if a call was rejected if (value.status === 'rejected') { const conversation = context.rootGetters.conversation(tokenStore.token) - const isConversationPhoneRoom = [ - CONVERSATION.OBJECT_TYPE.PHONE_LEGACY, - CONVERSATION.OBJECT_TYPE.PHONE_PERSISTENT, - CONVERSATION.OBJECT_TYPE.PHONE_TEMPORARY, - ].includes(conversation.objectType) - && conversation.objectId === CONVERSATION.OBJECT_ID.PHONE_OUTGOING - - if (isConversationPhoneRoom) { + + if (isConversationPhoneRoom(conversation)) { const actorStore = useActorStore() await context.dispatch('leaveCall', { token: tokenStore.token, diff --git a/src/utils/conversation.ts b/src/utils/conversation.ts index 87c8ed967a0..eb84fe4bebb 100644 --- a/src/utils/conversation.ts +++ b/src/utils/conversation.ts @@ -55,6 +55,25 @@ export function isEvent(conversation: Conversation): boolean { return conversation.objectType === CONVERSATION.OBJECT_TYPE.EVENT } +/** + * Returns whether the conversation is a phone room (with a single SIP phone participant). + * Covers both dial-out and direct-dial-in rooms. + * + * @param conversation - conversation object + * @param conversation.objectId - conversation objectId + * @param conversation.objectType - conversation objectType + */ +export function isConversationPhoneRoom({ objectId, objectType }: Conversation) { + return [ + CONVERSATION.OBJECT_ID.PHONE_OUTGOING, + CONVERSATION.OBJECT_ID.PHONE_INCOMING, + ].includes(objectId) && [ + CONVERSATION.OBJECT_TYPE.PHONE_LEGACY, + CONVERSATION.OBJECT_TYPE.PHONE_PERSISTENT, + CONVERSATION.OBJECT_TYPE.PHONE_TEMPORARY, + ].includes(objectType) +} + /** * check if the conversation is archived * diff --git a/src/views/MainView.vue b/src/views/MainView.vue index 83f2ca37ad9..70906bce8c1 100644 --- a/src/views/MainView.vue +++ b/src/views/MainView.vue @@ -24,6 +24,7 @@ import { CALL, CONVERSATION } from '../constants.ts' import { getTalkConfig } from '../services/CapabilitiesManager.ts' import { useActorStore } from '../stores/actor.ts' import { useSettingsStore } from '../stores/settings.ts' +import { isConversationPhoneRoom } from '../utils/conversation.ts' const props = defineProps<{ token: string @@ -116,15 +117,9 @@ function handleDirectCall(routeToken: string) { CALL.RECORDING.AUDIO, ].includes(conversation.callRecording) || conversation.recordingConsent === CALL.RECORDING_CONSENT.ENABLED - const isConversationPhoneRoom = [ - CONVERSATION.OBJECT_TYPE.PHONE_LEGACY, - CONVERSATION.OBJECT_TYPE.PHONE_PERSISTENT, - CONVERSATION.OBJECT_TYPE.PHONE_TEMPORARY, - ].includes(conversation.objectType) - && conversation.objectId === CONVERSATION.OBJECT_ID.PHONE_OUTGOING // Verify conditions for showing MediaSettings (required or user opted out) - if (showRecordingWarning || settingsStore.showMediaSettings || isConversationPhoneRoom) { + if (showRecordingWarning || settingsStore.showMediaSettings || isConversationPhoneRoom(conversation)) { emit('talk:media-settings:show') return }