Address PR comments on RoomForm
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {get} from "svelte/store"
|
||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
|
import {assoc, append, nth, type Maybe, uniqBy} from "@welshman/lib"
|
||||||
import type {RoomMeta} from "@welshman/util"
|
import type {RoomMeta} from "@welshman/util"
|
||||||
import {makeRoomMeta} from "@welshman/util"
|
import {getTag, makeRoomMeta} from "@welshman/util"
|
||||||
import {waitForThunkError, createRoom, editRoom, joinRoom} from "@welshman/app"
|
import {waitForThunkError, createRoom, editRoom, joinRoom} from "@welshman/app"
|
||||||
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
|
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
|
||||||
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
|
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
|
||||||
@@ -15,14 +17,14 @@
|
|||||||
import ModalBody from "@lib/components/ModalBody.svelte"
|
import ModalBody from "@lib/components/ModalBody.svelte"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {uploadFile} from "@app/core/commands"
|
import {uploadFile} from "@app/core/commands"
|
||||||
import {checkRelayHasLivekit} from "@app/voice"
|
import {deriveHasLivekit} from "@app/core/state"
|
||||||
|
|
||||||
type RoomMode = "text" | "voice" | "both"
|
type RoomMode = "text" | "voice" | "both"
|
||||||
|
|
||||||
const getRoomModeFromEvent = (event?: {tags?: string[][]}): RoomMode => {
|
const getRoomModeFromEvent = (event: Maybe<{tags: Maybe<string[][]>}>): RoomMode => {
|
||||||
const tags = event?.tags ?? []
|
const tags = event?.tags ?? []
|
||||||
const hasLivekit = tags.some(t => t[0] === "livekit")
|
const hasLivekit = !!getTag("livekit", tags)
|
||||||
const hasNoText = tags.some(t => t[0] === "no-text")
|
const hasNoText = !!getTag("no-text", tags)
|
||||||
if (hasLivekit && hasNoText) return "voice"
|
if (hasLivekit && hasNoText) return "voice"
|
||||||
if (hasLivekit) return "both"
|
if (hasLivekit) return "both"
|
||||||
return "text"
|
return "text"
|
||||||
@@ -30,8 +32,9 @@
|
|||||||
|
|
||||||
const buildTagsWithRoomMode = (existingTags: string[][], roomMode: RoomMode): string[][] => {
|
const buildTagsWithRoomMode = (existingTags: string[][], roomMode: RoomMode): string[][] => {
|
||||||
const filtered = existingTags.filter(t => t[0] !== "livekit" && t[0] !== "no-text")
|
const filtered = existingTags.filter(t => t[0] !== "livekit" && t[0] !== "no-text")
|
||||||
if (roomMode === "both") return [...filtered, ["livekit"]]
|
if (roomMode === "both") return uniqBy(nth(0), append(["livekit"], filtered))
|
||||||
if (roomMode === "voice") return [...filtered, ["livekit"], ["no-text"]]
|
if (roomMode === "voice")
|
||||||
|
return uniqBy(nth(0), append(["no-text"], append(["livekit"], filtered)))
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,23 +50,12 @@
|
|||||||
|
|
||||||
const values = $state(initialValues)
|
const values = $state(initialValues)
|
||||||
let roomMode = $state<RoomMode>(getRoomModeFromEvent(initialValues.event))
|
let roomMode = $state<RoomMode>(getRoomModeFromEvent(initialValues.event))
|
||||||
let relayHasLivekit = $state<boolean | undefined>(undefined)
|
const relayHasLivekit = deriveHasLivekit(url)
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
const u = url
|
|
||||||
let cancelled = false
|
|
||||||
checkRelayHasLivekit(u).then(has => {
|
|
||||||
if (!cancelled) relayHasLivekit = has
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
const room = $state.snapshot(values)
|
const room = $state.snapshot(values)
|
||||||
|
|
||||||
if ((roomMode === "voice" || roomMode === "both") && !relayHasLivekit) {
|
if ((roomMode === "voice" || roomMode === "both") && !get(relayHasLivekit)) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
theme: "error",
|
theme: "error",
|
||||||
message: "This relay does not support voice rooms.",
|
message: "This relay does not support voice rooms.",
|
||||||
@@ -84,16 +76,17 @@
|
|||||||
room.pictureMeta = result.tags
|
room.pictureMeta = result.tags
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingTags = room.event?.tags ?? []
|
|
||||||
const tags = buildTagsWithRoomMode(existingTags, roomMode)
|
|
||||||
room.event = room.event ? {...room.event, tags} : ({tags} as RoomMeta["event"])
|
|
||||||
|
|
||||||
const createMessage = await waitForThunkError(createRoom(url, room))
|
const createMessage = await waitForThunkError(createRoom(url, room))
|
||||||
|
|
||||||
if (createMessage && !createMessage.includes("already")) {
|
if (createMessage && !createMessage.includes("already")) {
|
||||||
return pushToast({theme: "error", message: createMessage})
|
return pushToast({theme: "error", message: createMessage})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (room.event && get(relayHasLivekit)) {
|
||||||
|
const existingTags = room.event.tags ?? []
|
||||||
|
const tags = buildTagsWithRoomMode(existingTags, roomMode)
|
||||||
|
room.event = assoc("tags", tags)(room.event) as RoomMeta["event"]
|
||||||
|
}
|
||||||
const editMessage = await waitForThunkError(editRoom(url, room))
|
const editMessage = await waitForThunkError(editRoom(url, room))
|
||||||
|
|
||||||
if (editMessage) {
|
if (editMessage) {
|
||||||
@@ -207,22 +200,23 @@
|
|||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
<FieldInline>
|
{#if $relayHasLivekit}
|
||||||
{#snippet label()}
|
<FieldInline>
|
||||||
<p>Room type</p>
|
{#snippet label()}
|
||||||
{/snippet}
|
<p>Room type</p>
|
||||||
{#snippet input()}
|
{/snippet}
|
||||||
<select class="select select-bordered w-full" bind:value={roomMode} aria-label="Room type">
|
{#snippet input()}
|
||||||
<option value="text">Text only</option>
|
<select
|
||||||
<option value="both" disabled={relayHasLivekit === false}>
|
class="select select-bordered w-full"
|
||||||
Text and voice{relayHasLivekit === false ? " (not setup)" : ""}
|
bind:value={roomMode}
|
||||||
</option>
|
aria-label="Room type">
|
||||||
<option value="voice" disabled={relayHasLivekit === false}>
|
<option value="text">Text only</option>
|
||||||
Voice only{relayHasLivekit === false ? " (not setup)" : ""}
|
<option value="both">Text and voice</option>
|
||||||
</option>
|
<option value="voice">Voice only</option>
|
||||||
</select>
|
</select>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
|
{/if}
|
||||||
<strong class="md:hidden">Permissions</strong>
|
<strong class="md:hidden">Permissions</strong>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<input type="checkbox" class="checkbox" bind:checked={values.isRestricted} />
|
<input type="checkbox" class="checkbox" bind:checked={values.isRestricted} />
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ import {
|
|||||||
displayProfileByPubkey,
|
displayProfileByPubkey,
|
||||||
getProfile,
|
getProfile,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
|
import {checkRelayHasLivekit} from "$lib/livekit"
|
||||||
import {readFeed} from "@lib/feeds"
|
import {readFeed} from "@lib/feeds"
|
||||||
|
|
||||||
export const fromCsv = (s: string) => (s || "").split(",").filter(identity)
|
export const fromCsv = (s: string) => (s || "").split(",").filter(identity)
|
||||||
@@ -1197,6 +1198,12 @@ export const deriveSupportedMethods = simpleCache(([url]: [string]) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const deriveHasLivekit = simpleCache(([url]: [string]) =>
|
||||||
|
readable<boolean | undefined>(undefined, set => {
|
||||||
|
checkRelayHasLivekit(url).then(has => set(has))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
export const deriveTimeout = (timeout: number) => {
|
export const deriveTimeout = (timeout: number) => {
|
||||||
const store = writable<boolean>(false)
|
const store = writable<boolean>(false)
|
||||||
|
|
||||||
|
|||||||
+3
-21
@@ -8,31 +8,13 @@ import {derived, get, writable} from "svelte/store"
|
|||||||
import {now} from "@welshman/lib"
|
import {now} from "@welshman/lib"
|
||||||
import {makeEvent, getTagValue} from "@welshman/util"
|
import {makeEvent, getTagValue} from "@welshman/util"
|
||||||
import {signer, publishThunk} from "@welshman/app"
|
import {signer, publishThunk} from "@welshman/app"
|
||||||
|
import {getLivekitEndpoint} from "$lib/livekit"
|
||||||
import {deriveEventsForUrl} from "@app/core/state"
|
import {deriveEventsForUrl} from "@app/core/state"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
export const ROOM_PRESENCE = 10312
|
export const ROOM_PRESENCE = 10312
|
||||||
|
|
||||||
const livekitEndpoint = (url: string, groupId: string) => {
|
export {checkRelayHasLivekit} from "$lib/livekit"
|
||||||
const httpUrl = url
|
|
||||||
.replace(/^wss:\/\//, "https://")
|
|
||||||
.replace(/^ws:\/\//, "http://")
|
|
||||||
.replace(/\/$/, "")
|
|
||||||
return `${httpUrl}/.well-known/nip29/livekit/${groupId}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const checkRelayHasLivekit = async (url: string): Promise<boolean> => {
|
|
||||||
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_INTERVAL_MS = 60_000
|
||||||
const PRESENCE_EXPIRY_S = 300
|
const PRESENCE_EXPIRY_S = 300
|
||||||
@@ -53,7 +35,7 @@ const fetchLivekitToken = async (
|
|||||||
groupId: string,
|
groupId: string,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<{server_url: string; participant_token: string}> => {
|
): Promise<{server_url: string; participant_token: string}> => {
|
||||||
const endpoint = livekitEndpoint(url, groupId)
|
const endpoint = getLivekitEndpoint(url, groupId)
|
||||||
|
|
||||||
const $signer = signer.get()
|
const $signer = signer.get()
|
||||||
if (!$signer) throw new Error("No signer available")
|
if (!$signer) throw new Error("No signer available")
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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<boolean> => {
|
||||||
|
const endpoint = livekitEndpoint(url, "nop")
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Zooid returns 401 when livekit is configured and 404 if it is not.
|
||||||
|
const response = await fetch(endpoint)
|
||||||
|
return response.status === 401
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLivekitEndpoint = (url: string, groupId: string) => livekitEndpoint(url, groupId)
|
||||||
Reference in New Issue
Block a user