From ca6320a02a3b7466be79b6a88fcc972697b108df Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Tue, 10 Dec 2024 14:16:47 +0100 Subject: [PATCH 1/2] fix(AdminSettings): add test of websocket connection with HPB Signed-off-by: Maksim Sukharev --- .../AdminSettings/SignalingServer.vue | 21 ++- src/utils/SignalingStandaloneTest.js | 174 ++++++++++++++++++ 2 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 src/utils/SignalingStandaloneTest.js diff --git a/src/components/AdminSettings/SignalingServer.vue b/src/components/AdminSettings/SignalingServer.vue index 43b6e23f1c6..058b0523e7b 100644 --- a/src/components/AdminSettings/SignalingServer.vue +++ b/src/components/AdminSettings/SignalingServer.vue @@ -64,7 +64,8 @@ import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadi import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' -import { getWelcomeMessage } from '../../services/signalingService.js' +import { fetchSignalingSettings, getWelcomeMessage } from '../../services/signalingService.js' +import { createConnection } from '../../utils/SignalingStandaloneTest.js' export default { name: 'SignalingServer', @@ -166,7 +167,6 @@ export default { try { const response = await getWelcomeMessage(this.index) - this.checked = true const data = response.data.ocs.data this.versionFound = data.version if (data.warning === 'UPDATE_OPTIONAL') { @@ -175,8 +175,9 @@ export default { features: data.features.join(', '), }) } + + await this.testWebSocketConnection(this.server) } catch (exception) { - this.checked = true const data = exception.response.data.ocs.data const error = data.error @@ -198,6 +199,20 @@ export default { } else { this.errorMessage = t('spreed', 'Error: Unknown error occurred') } + } finally { + this.checked = true + } + }, + + async testWebSocketConnection(url) { + try { + const response = await fetchSignalingSettings({ token: '' }, {}) + const settings = response.data.ocs.data + const signalingTest = createConnection(settings, url) + await signalingTest.connect() + } catch (exception) { + console.error(exception) + this.errorMessage = t('spreed', 'Error: Websocket connection failed. Check browser console') } }, }, diff --git a/src/utils/SignalingStandaloneTest.js b/src/utils/SignalingStandaloneTest.js new file mode 100644 index 00000000000..72855a9a64a --- /dev/null +++ b/src/utils/SignalingStandaloneTest.js @@ -0,0 +1,174 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { generateOcsUrl } from '@nextcloud/router' + +/** + * This is a simplified version of Signaling prototype (see signaling.js) + * to be used for connection testing purposes (welcome, hello): + * - no internal signaling supported + * - no room events (join, leave, update) supported + */ + +class StandaloneTest { + + constructor(settings, url) { + this.settings = settings + this.features = null + this.version = null + + this.socket = null + this.connected = false + this.url = this.getWebSocketUrl(url) + + this.waitForWelcomeTimeout = null + this.welcomeTimeoutMs = 3000 + } + + hasFeature(feature) { + return this.features && this.features.includes(feature) + } + + getWebSocketUrl(url) { + return url + .replace(/^http/, 'ws') // FIXME: not needed? request should be automatically upgraded to wss + .replace(/\/$/, '') + '/spreed' + } + + getBackendUrl(baseURL = undefined) { + return generateOcsUrl('apps/spreed/api/v3/signaling/backend', {}, { baseURL }) + } + + connect() { + console.debug('Connecting to ' + this.url + ' with ' + this.settings) + + return new Promise((resolve, reject) => { + this.socket = new WebSocket(this.url) + + this.socket.onopen = (event) => { + console.debug('Connected to websocket', event) + if (this.settings.helloAuthParams['2.0']) { + this.waitForWelcomeTimeout = setTimeout(this.welcomeResponseTimeout.bind(this), this.welcomeTimeoutMs) + } else { + this.sendHello() + } + } + + this.socket.onerror = (event) => { + console.error('Error on websocket', event) + this.disconnect() + } + + this.socket.onclose = (event) => { + if (event.wasClean) { + console.info('Connection closed cleanly:', event) + resolve(true) + } else { + console.warn(`Closing code ${event.code}. See https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4`) + reject(event) + } + this.socket = null + } + + this.socket.onmessage = (event) => { + let data = event.data + if (typeof (data) === 'string') { + data = JSON.parse(data) + } + if (OC.debug) { + console.debug('Received', data) + } + + switch (data.type) { + case 'welcome': + this.welcomeResponseReceived(data) + break + case 'hello': + this.helloResponseReceived(data) + break + case 'error': + console.error('Received error', data) + break + default: + console.debug('Ignore unexpected event', data) + break + } + } + }) + } + + disconnect() { + if (this.socket) { + this.sendBye() + this.socket.close() + this.socket = null + } + } + + welcomeResponseReceived(data) { + console.debug('Welcome received', data) + if (this.waitForWelcomeTimeout !== null) { + clearTimeout(this.waitForWelcomeTimeout) + this.waitForWelcomeTimeout = null + } + + if (data.welcome && data.welcome.features) { + this.features = data.welcome.features + this.version = data.welcome.version + } + + this.sendHello() + } + + welcomeResponseTimeout() { + console.warn('No welcome received, assuming old-style signaling server') + this.sendHello() + } + + sendHello() { + const version = this.hasFeature('hello-v2') ? '2.0' : '1.0' + + const msg = { + type: 'hello', + hello: { + version, + auth: { + url: this.getBackendUrl(), + params: this.settings.helloAuthParams[version], + }, + }, + } + + this.socket.send(JSON.stringify(msg)) + } + + helloResponseReceived(data) { + console.debug('Hello response received', data) + this.connected = true + this.disconnect() + } + + sendBye() { + if (this.connected) { + this.socket.send(JSON.stringify({ type: 'bye', bye: {} })) + } + } + +} + +/** + * Returns test instance + * @param {object} settings signaling settings + * @param {string} url HPB server URL + */ +function createConnection(settings, url) { + if (!settings) { + console.error('Signaling settings are not given') + } + + return new StandaloneTest(settings, url) +} + +export { createConnection } From 4ddb6a484752491dffc032b12ebd708cd52a2ece Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Wed, 18 Dec 2024 14:51:21 +0100 Subject: [PATCH 2/2] fix(AdminSettings): add test information Signed-off-by: Maksim Sukharev --- .../AdminSettings/SignalingServer.vue | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/src/components/AdminSettings/SignalingServer.vue b/src/components/AdminSettings/SignalingServer.vue index 058b0523e7b..c24db26a601 100644 --- a/src/components/AdminSettings/SignalingServer.vue +++ b/src/components/AdminSettings/SignalingServer.vue @@ -36,17 +36,30 @@ {{ connectionState }} + + + + - - - +
    +
  • + + {{ row.caption }} + + + {{ row.description }} + +
  • +
@@ -58,6 +71,7 @@ import IconDelete from 'vue-material-design-icons/Delete.vue' import IconReload from 'vue-material-design-icons/Reload.vue' import { t } from '@nextcloud/l10n' +import { getBaseUrl } from '@nextcloud/router' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' @@ -112,6 +126,7 @@ export default { errorMessage: '', warningMessage: '', versionFound: '', + signalingTestInfo: [], } }, @@ -160,6 +175,7 @@ export default { async checkServerVersion() { this.checked = false + this.signalingTestInfo = [] this.errorMessage = '' this.warningMessage = '' @@ -210,6 +226,12 @@ export default { const settings = response.data.ocs.data const signalingTest = createConnection(settings, url) await signalingTest.connect() + this.signalingTestInfo = [ + { caption: t('spreed', 'Nextcloud base URL'), description: getBaseUrl() }, + { caption: t('spreed', 'Talk Backend URL'), description: signalingTest.getBackendUrl() }, + { caption: t('spreed', 'WebSocket URL'), description: signalingTest.url }, + { caption: t('spreed', 'Available features'), description: signalingTest.features.join(', ') }, + ] } catch (exception) { console.error(exception) this.errorMessage = t('spreed', 'Error: Websocket connection failed. Check browser console') @@ -222,7 +244,10 @@ export default {