diff --git a/src/components/common/AddInstanceDialog.vue b/src/components/common/AddInstanceDialog.vue index fd21bf662d..f92b0b0014 100644 --- a/src/components/common/AddInstanceDialog.vue +++ b/src/components/common/AddInstanceDialog.vue @@ -79,6 +79,7 @@ import StateMixin from '@/mixins/state' import { Debounce } from 'vue-debounce-decorator' import { consola } from 'consola' import { httpClientActions } from '@/api/httpClientActions' +import webSocketWrapper from '@/util/web-socket-wrapper' @Component({}) export default class AddInstanceDialog extends Mixins(StateMixin) { @@ -193,21 +194,38 @@ export default class AddInstanceDialog extends Mixins(StateMixin) { this.controller = new AbortController() const { signal } = this.controller - await fetch(url + 'server/info', { signal, mode: 'no-cors', cache: 'no-cache' }) - .then(() => { - // likely a cors issue - this.error = this.$t('app.endpoint.error.cors_error') - this.note = this.$t('app.endpoint.error.cors_note', { - url: Globals.DOCS_MULTIPLE_INSTANCES + if (this.hosted) { + const apiEndpoints = this.$filters.getApiUrls(url.toString()) + + await webSocketWrapper(apiEndpoints.socketUrl, signal) + .then(() => { + // likely a cors issue, but socket worked + this.verified = true + }) + .catch(e => { + // external host not reachable (fetch returns 'failed to fetch') + consola.debug('Network Error', e, request) + this.error = request + this.note = this.$t('app.endpoint.error.cant_connect') + }) + .finally(() => { this.verifying = false }) + } else { + await fetch(url + 'server/info', { signal, mode: 'no-cors', cache: 'no-cache' }) + .then(() => { + // likely a cors issue + this.error = this.$t('app.endpoint.error.cors_error') + this.note = this.$t('app.endpoint.error.cors_note', { + url: Globals.DOCS_MULTIPLE_INSTANCES + }) + }) + .catch(e => { + // external host not reachable (fetch returns 'failed to fetch') + consola.debug('Network Error', e, request) + this.error = request + this.note = this.$t('app.endpoint.error.cant_connect') }) - }) - .catch(e => { - // external host not reachable (fetch returns 'failed to fetch') - consola.debug('Network Error', e, request) - this.error = request - this.note = this.$t('app.endpoint.error.cant_connect') - }) - .finally(() => { this.verifying = false }) + .finally(() => { this.verifying = false }) + } } } } @@ -218,6 +236,10 @@ export default class AddInstanceDialog extends Mixins(StateMixin) { }) } + get hosted () { + return this.$store.state.config.hostConfig.hosted + } + addInstance () { const url = this.buildUrl(this.url) const apiConfig = this.$filters.getApiUrls(url.toString()) diff --git a/src/init.ts b/src/init.ts index e2a2d4136e..fd4fea2ea2 100644 --- a/src/init.ts +++ b/src/init.ts @@ -7,6 +7,7 @@ import axios from 'axios' import router from './router' import { httpClientActions } from '@/api/httpClientActions' import sanitizeEndpoint from '@/util/sanitize-endpoint' +import webSocketWrapper from '@/util/web-socket-wrapper' // Load API configuration /** @@ -75,15 +76,13 @@ const getApiConfig = async (hostConfig: HostConfig): Promise { - return httpClientActions.get(`${endpoint}/server/info?date=${Date.now()}`, { timeout: 1000 }) - .then(() => true) - .catch((error) => error.response?.status === 401) + const i = await Promise.race( + endpoints.map(async (endpoint, index) => { + const apiEndpoints = Vue.$filters.getApiUrls(endpoint) + return await webSocketWrapper(apiEndpoints.socketUrl) ? index : -1 }) ) - const i = results.findIndex(endpoint => endpoint) return (i > -1) ? Vue.$filters.getApiUrls(endpoints[i]) : { apiUrl: '', socketUrl: '' } diff --git a/src/util/web-socket-wrapper.ts b/src/util/web-socket-wrapper.ts new file mode 100644 index 0000000000..a2a92b6166 --- /dev/null +++ b/src/util/web-socket-wrapper.ts @@ -0,0 +1,41 @@ +import { consola } from 'consola' + +const webSocketWrapper = (url: string, signal?: AbortSignal) => { + return new Promise((resolve) => { + consola.debug(`[webSocketWrapper] open ${url}`) + + const connection = new WebSocket(url) + + const dispose = () => { + signal?.removeEventListener('abort', abortHandler) + + connection.close() + } + + const abortHandler = () => dispose() + + signal?.addEventListener('abort', abortHandler) + + connection.onopen = () => { + consola.debug(`[webSocketWrapper] ${url} open`) + + resolve(true) + + dispose() + } + + connection.onerror = () => { + consola.debug(`[webSocketWrapper] ${url} error`) + + resolve(false) + + dispose() + } + + connection.onclose = () => { + consola.debug(`[webSocketWrapper] ${url} close`) + } + }) +} + +export default webSocketWrapper