diff --git a/src/App.vue b/src/App.vue index 1d88ccd5d3e..b3cbaee17e9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -387,6 +387,7 @@ export default { if (from.name === 'conversation' && from.params.token !== to.params.token) { this.$store.dispatch('leaveConversation', { token: from.params.token }) + this.tokenStore.setLastJoinConversationFailed(false) } /** diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index 3438b5223ea..c804bad506f 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -544,7 +544,8 @@ export default { }, disabled() { - return this.isReadOnly || this.noChatPermission || !this.currentConversationIsJoined || this.isRecordingAudio + return this.isReadOnly || this.noChatPermission || this.isRecordingAudio + || (!this.currentConversationIsJoined && !this.currentConversationIsJoinedWithoutHPB) }, scheduleMessageTime() { @@ -571,7 +572,7 @@ export default { return t('spreed', 'This conversation has been locked') } else if (this.noChatPermission) { return t('spreed', 'No permission to post messages in this conversation') - } else if (!this.currentConversationIsJoined) { + } else if (!this.currentConversationIsJoined && !this.currentConversationIsJoinedWithoutHPB) { return t('spreed', 'Joining conversation …') } else if (this.silentChat) { return t('spreed', 'Write a message without notification') @@ -626,6 +627,10 @@ export default { return this.tokenStore.currentConversationIsJoined }, + currentConversationIsJoinedWithoutHPB() { + return this.tokenStore.currentConversationIsJoinedWithoutHPB + }, + currentUploadId() { return this.uploadStore.currentUploadId }, @@ -767,6 +772,10 @@ export default { this.focusInput() }, + currentConversationIsJoinedWithoutHPB() { + this.focusInput() + }, + currentUploadId(value) { if (value && !this.upload) { this.text = '' diff --git a/src/init.js b/src/init.js index 91d41c4042f..c3c6717e0c0 100644 --- a/src/init.js +++ b/src/init.js @@ -59,6 +59,9 @@ window.OCA.Talk.registerParticipantSearchAction = ({ label, callback, show, icon EventBus.on('signaling-join-room', ([token]) => { tokenStore.updateLastJoinedConversationToken(token) }) +EventBus.on('signaling-join-room-failed', ([token]) => { + tokenStore.setLastJoinConversationFailed(true) +}) EventBus.on('signaling-recording-status-changed', ([token, status]) => { store.dispatch('setConversationProperties', { token, properties: { callRecording: status } }) diff --git a/src/services/EventBus.ts b/src/services/EventBus.ts index 64376ee8544..c1e423d60c4 100644 --- a/src/services/EventBus.ts +++ b/src/services/EventBus.ts @@ -45,6 +45,7 @@ export type Events = { 'signaling-join-call': [string, number] 'signaling-join-call-failed': [string, { meta: components['schemas']['OCSMeta'], data: { error: string } }] 'signaling-join-room': [string] + 'signaling-join-room-failed': [string] 'signaling-participant-list-changed': void 'signaling-participant-list-updated': void 'signaling-recording-status-changed': [string, number] diff --git a/src/stores/token.ts b/src/stores/token.ts index bba33c712df..6805f0f2c59 100644 --- a/src/stores/token.ts +++ b/src/stores/token.ts @@ -17,8 +17,15 @@ export const useTokenStore = defineStore('token', () => { * A in the signaling server. */ const lastJoinedConversationToken = ref<'' | (string & {})>('') + /** + * The joining of a room with the signaling server might fail + * for various reasons. We still want to allow basic functionality + * (e.g. chatting and file sharing) which does not depend on it. + */ + const lastJoinConversationFailed = ref(false) const currentConversationIsJoined = computed(() => token.value !== '' && lastJoinedConversationToken.value === token.value) + const currentConversationIsJoinedWithoutHPB = computed(() => token.value !== '' && lastJoinConversationFailed.value) /** * @param newToken token of active conversation @@ -43,16 +50,28 @@ export const useTokenStore = defineStore('token', () => { */ function updateLastJoinedConversationToken(newToken: string) { lastJoinedConversationToken.value = newToken + // Reset last failed conversation attempt on successful join + lastJoinConversationFailed.value = false + } + + /** + * @param newValue value of the flag for last joined conversation + */ + function setLastJoinConversationFailed(newValue: boolean) { + lastJoinConversationFailed.value = newValue } return { token, fileIdForToken, lastJoinedConversationToken, + lastJoinConversationFailed, currentConversationIsJoined, + currentConversationIsJoinedWithoutHPB, updateToken, updateTokenAndFileIdForToken, updateLastJoinedConversationToken, + setLastJoinConversationFailed, } }) diff --git a/src/utils/signaling.js b/src/utils/signaling.js index 31526c985d8..2509c9739c7 100644 --- a/src/utils/signaling.js +++ b/src/utils/signaling.js @@ -637,6 +637,7 @@ function Standalone(settings, urls) { this.maxReconnectIntervalMs = 16000 this.reconnectIntervalMs = this.initialReconnectIntervalMs this.helloResponseErrorCount = 0 + this.socketErrorCount = 0 this.ownSessionJoined = false this.joinedUsers = {} this.rooms = [] @@ -705,6 +706,7 @@ Signaling.Standalone.prototype.connect = function() { window.signalingSocket = this.socket this.socket.onopen = function(event) { console.debug('Connected', event) + this.socketErrorCount = 0 if (this.signalingConnectionTimeout !== null) { clearTimeout(this.signalingConnectionTimeout) this.signalingConnectionTimeout = null @@ -713,6 +715,10 @@ Signaling.Standalone.prototype.connect = function() { this.signalingConnectionWarning.hideToast() this.signalingConnectionWarning = null } + if (this.signalingConnectionError !== null) { + this.signalingConnectionError.hideToast() + this.signalingConnectionError = null + } this.reconnectIntervalMs = this.initialReconnectIntervalMs if (this.settings.helloAuthParams['2.0']) { this.waitForWelcomeTimeout = setTimeout(this.welcomeTimeout.bind(this), this.welcomeTimeoutMs) @@ -730,11 +736,8 @@ Signaling.Standalone.prototype.connect = function() { this.signalingConnectionWarning.hideToast() this.signalingConnectionWarning = null } - if (this.signalingConnectionError === null) { - this.signalingConnectionError = showError(t('spreed', 'Failed to connect. Retrying …'), { - timeout: TOAST_PERMANENT_TIMEOUT, - }) - } + + this._handleConnectionError('socket') this.reconnect() }.bind(this) this.socket.onclose = function(event) { @@ -836,6 +839,37 @@ Signaling.Standalone.prototype.connect = function() { }.bind(this) } +Signaling.Standalone.prototype._handleConnectionError = function(type) { + let errorCount + if (type === 'socket') { + this.socketErrorCount++ + errorCount = this.socketErrorCount + } else if (type === 'hello') { + this.helloResponseErrorCount++ + errorCount = this.helloResponseErrorCount + } else { + // Type is not provided, do not process the error + return + } + + if (this.signalingConnectionError === null && errorCount < 5) { + this.signalingConnectionError = showError(t('spreed', 'Failed to connect. Retrying …'), { + timeout: TOAST_PERMANENT_TIMEOUT, + }) + } else if (errorCount === 5) { + // Switch to a different message as several errors in a row in hello + // responses indicate that the signaling server might be unable to + // connect to Nextcloud. + if (this.signalingConnectionError) { + this.signalingConnectionError.hideToast() + } + this.signalingConnectionError = showError(t('spreed', 'Failed to connect. The signaling server may be set up incorrectly'), { + timeout: TOAST_PERMANENT_TIMEOUT, + }) + this._trigger('joinRoomFailed', [this.settings.token]) + } +} + Signaling.Standalone.prototype.welcomeReceived = function(data) { console.debug('Welcome received', data) if (this.waitForWelcomeTimeout !== null) { @@ -1066,23 +1100,7 @@ Signaling.Standalone.prototype.helloResponseReceived = function(data) { return } - this.helloResponseErrorCount++ - - if (this.signalingConnectionError === null && this.helloResponseErrorCount < 5) { - this.signalingConnectionError = showError(t('spreed', 'Failed to connect. Retrying …'), { - timeout: TOAST_PERMANENT_TIMEOUT, - }) - } else if (this.helloResponseErrorCount === 5) { - // Switch to a different message as several errors in a row in hello - // responses indicate that the signaling server might be unable to - // connect to Nextcloud. - if (this.signalingConnectionError) { - this.signalingConnectionError.hideToast() - } - this.signalingConnectionError = showError(t('spreed', 'Failed to connect. The signaling server may be set up incorrectly'), { - timeout: TOAST_PERMANENT_TIMEOUT, - }) - } + this._handleConnectionError('hello') // TODO(fancycode): How should this be handled better? const url = this._getBackendUrl() diff --git a/src/utils/webrtc/index.js b/src/utils/webrtc/index.js index 93454a1ae36..4ac1dc37d68 100644 --- a/src/utils/webrtc/index.js +++ b/src/utils/webrtc/index.js @@ -7,6 +7,7 @@ import { isCancel } from '@nextcloud/axios' import { PARTICIPANT, PRIVACY, VIRTUAL_BACKGROUND } from '../../constants.ts' import BrowserStorage from '../../services/BrowserStorage.js' import { getTalkConfig } from '../../services/CapabilitiesManager.ts' +import { EventBus } from '../../services/EventBus.ts' import { fetchSignalingSettings } from '../../services/signalingService.js' import store from '../../store/index.js' import { isSafari } from '../browserCheck.ts' @@ -220,7 +221,14 @@ function setupWebRtc() { async function signalingJoinConversation(token, sessionId) { await connectSignaling(token) if (tokensInSignaling[token]) { - await signaling.joinRoom(token, sessionId) + try { + await signaling.joinRoom(token, sessionId) + } catch (error) { + // Initial 'signaling.joinRoom' might be in reconnection state + // so it returns a previous token after switch + EventBus.emit('signaling-join-room-failed', []) + throw error + } } }