Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 70 additions & 13 deletions src/components/AdminSettings/SignalingServer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,30 @@
<IconAlertCircleOutline v-else-if="warningMessage" :size="20" fill-color="var(--color-warning)" />
<IconCheck v-else :size="20" fill-color="var(--color-success)" />
{{ connectionState }}

<NcButton v-if="server && checked"
type="tertiary"
:title="t('spreed', 'Test this server')"
:aria-label="t('spreed', 'Test this server')"
@click="checkServerVersion">
<template #icon>
<IconReload :size="20" />
</template>
</NcButton>
</span>

<NcButton v-if="server && checked"
type="tertiary"
:title="t('spreed', 'Test this server')"
:aria-label="t('spreed', 'Test this server')"
@click="checkServerVersion">
<template #icon>
<IconReload :size="20" />
</template>
</NcButton>
<ul v-if="signalingTestInfo.length" class="test-connection-data">
<li v-for="(row, idx) in signalingTestInfo"
:key="idx"
class="test-connection-data__item">
<span class="test-connection-data__caption">
{{ row.caption }}
</span>
<span>
{{ row.description }}
</span>
</li>
</ul>
</li>
</template>

Expand All @@ -58,13 +71,15 @@ 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'
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',
Expand Down Expand Up @@ -111,6 +126,7 @@ export default {
errorMessage: '',
warningMessage: '',
versionFound: '',
signalingTestInfo: [],
}
},

Expand Down Expand Up @@ -159,14 +175,14 @@ export default {

async checkServerVersion() {
this.checked = false
this.signalingTestInfo = []

this.errorMessage = ''
this.warningMessage = ''
this.versionFound = ''

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') {
Expand All @@ -175,8 +191,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

Expand All @@ -198,6 +215,26 @@ 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()
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')
}
},
},
Expand All @@ -207,7 +244,10 @@ export default {
<style lang="scss" scoped>
.signaling-server {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--default-grid-baseline);
margin-bottom: 10px;

& &__textfield {
width: 300px;
Expand All @@ -220,8 +260,25 @@ export default {
}

.test-connection {
flex-basis: fit-content;
display: inline-flex;
align-items: center;
gap: 8px;
gap: var(--default-grid-baseline);
}

.test-connection-data {
flex-basis: 100%;
display: inline-grid;
grid-template-columns: auto auto;
gap: var(--default-grid-baseline);

&__item {
display: contents;
}

&__caption {
font-weight: bold;
margin-inline-end: var(--default-grid-baseline);
}
}
</style>
174 changes: 174 additions & 0 deletions src/utils/SignalingStandaloneTest.js
Original file line number Diff line number Diff line change
@@ -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 }
Loading