diff --git a/src/app/components/RoomForm.svelte b/src/app/components/RoomForm.svelte index 615dd4cc..07ea28b8 100644 --- a/src/app/components/RoomForm.svelte +++ b/src/app/components/RoomForm.svelte @@ -15,6 +15,7 @@ import ModalBody from "@lib/components/ModalBody.svelte" import {pushToast} from "@app/util/toast" import {uploadFile} from "@app/core/commands" + import {checkRelayHasLivekit} from "@app/voice" type RoomMode = "text" | "voice" | "both" @@ -46,10 +47,29 @@ const values = $state(initialValues) let roomMode = $state(getRoomModeFromEvent(initialValues.event)) + let relayHasLivekit = $state(undefined) + + $effect(() => { + const u = url + let cancelled = false + checkRelayHasLivekit(u).then(has => { + if (!cancelled) relayHasLivekit = has + }) + return () => { + cancelled = true + } + }) const submit = async () => { const room = $state.snapshot(values) + if ((roomMode === "voice" || roomMode === "both") && !relayHasLivekit) { + return pushToast({ + theme: "error", + message: "This relay does not support voice rooms.", + }) + } + if (imageFile) { const {error, result} = await uploadFile(imageFile, { maxWidth: 256, @@ -211,8 +231,12 @@ {#snippet input()} {/snippet} diff --git a/src/app/voice.ts b/src/app/voice.ts index b3db81c7..ff7b7f74 100644 --- a/src/app/voice.ts +++ b/src/app/voice.ts @@ -1,3 +1,7 @@ +/** + * Voice rooms via LiveKit. Note: Voice does not work on localhost in Firefox + * (ICE candidate gathering fails). Use Chrome or test from deployed HTTPS. + */ import {DisconnectReason, Room, RoomEvent, Track} from "livekit-client" import {getToken} from "nostr-tools/nip98" import {derived, get, writable} from "svelte/store" @@ -9,6 +13,27 @@ import {pushToast} from "@app/util/toast" export const ROOM_PRESENCE = 10312 +const livekitEndpoint = (url: string, groupId: string) => { + const httpUrl = url + .replace(/^wss:\/\//, "https://") + .replace(/^ws:\/\//, "http://") + .replace(/\/$/, "") + return `${httpUrl}/.well-known/nip29/livekit/${groupId}` +} + +export const checkRelayHasLivekit = async (url: string): Promise => { + const endpoint = livekitEndpoint(url, "nop") + + try { + // Currently we are hitting the API with no auth because zooid returns a 401 livekit + // is configured and 404 if it is not. But we need a standardized solution in the NIP. + const response = await fetch(endpoint) + return response.status === 401 + } catch { + return false + } +} + const PRESENCE_INTERVAL_MS = 60_000 const PRESENCE_EXPIRY_S = 300 @@ -26,11 +51,7 @@ const fetchLivekitToken = async ( groupId: string, signal?: AbortSignal, ): Promise<{server_url: string; participant_token: string}> => { - const httpUrl = url - .replace(/^wss:\/\//, "https://") - .replace(/^ws:\/\//, "http://") - .replace(/\/$/, "") - const endpoint = `${httpUrl}/.well-known/nip29/livekit/${groupId}` + const endpoint = livekitEndpoint(url, groupId) const $signer = signer.get() if (!$signer) throw new Error("No signer available")