forked from coracle/flotilla
Monitor relay connections for restricted responses and show error to user
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {page} from "$app/stores"
|
||||
import Drawer from "@lib/components/Drawer.svelte"
|
||||
import Dialog from "@lib/components/Dialog.svelte"
|
||||
import {modals, clearModals} from "@app/util/modal"
|
||||
import {modal, clearModals} from "@app/util/modal"
|
||||
|
||||
const onKeyDown = (e: any) => {
|
||||
if (e.code === "Escape" && e.target === document.body) {
|
||||
@@ -10,22 +9,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
const hash = $derived($page.url.hash.slice(1))
|
||||
const modal = $derived($modals[hash])
|
||||
const m = $derived($modal)
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={onKeyDown} />
|
||||
|
||||
{#if modal?.options?.drawer}
|
||||
<Drawer onClose={clearModals} {...modal.options}>
|
||||
{#key modal.id}
|
||||
<modal.component {...modal.props} />
|
||||
{#if m?.options?.drawer}
|
||||
<Drawer onClose={clearModals} {...m.options}>
|
||||
{#key m.id}
|
||||
<m.component {...m.props} />
|
||||
{/key}
|
||||
</Drawer>
|
||||
{:else if modal}
|
||||
<Dialog onClose={clearModals} {...modal.options}>
|
||||
{#key modal.id}
|
||||
<modal.component {...modal.props} />
|
||||
{:else if m}
|
||||
<Dialog onClose={clearModals} {...m.options}>
|
||||
{#key m.id}
|
||||
<m.component {...m.props} />
|
||||
{/key}
|
||||
</Dialog>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {displayUrl} from "@welshman/lib"
|
||||
import {Pool, AuthStatus} from "@welshman/net"
|
||||
import {AuthStatus} from "@welshman/net"
|
||||
import {waitForThunkError} from "@welshman/app"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -11,7 +12,8 @@
|
||||
import SpaceJoinConfirm, {confirmSpaceJoin} from "@app/components/SpaceJoinConfirm.svelte"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {attemptRelayAccess} from "@app/core/commands"
|
||||
import {publishJoinRequest} from "@app/core/commands"
|
||||
import {deriveSocket} from "@app/core/state"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
@@ -21,27 +23,24 @@
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const joinRelay = async () => {
|
||||
const error = await attemptRelayAccess(url, claim)
|
||||
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error, timeout: 30_000})
|
||||
}
|
||||
|
||||
const socket = Pool.get().get(url)
|
||||
|
||||
if (socket.auth.status === AuthStatus.None) {
|
||||
pushModal(SpaceJoinConfirm, {url}, {replaceState: true})
|
||||
} else {
|
||||
await confirmSpaceJoin(url)
|
||||
}
|
||||
}
|
||||
const socket = deriveSocket(url)
|
||||
|
||||
const join = async () => {
|
||||
loading = true
|
||||
|
||||
try {
|
||||
await joinRelay()
|
||||
const thunk = publishJoinRequest({url, claim})
|
||||
const error = await waitForThunkError(thunk)
|
||||
|
||||
if (error) {
|
||||
return pushToast({theme: "error", message: error, timeout: 30_000})
|
||||
}
|
||||
|
||||
if ($socket.auth.status === AuthStatus.None) {
|
||||
pushModal(SpaceJoinConfirm, {url}, {replaceState: true})
|
||||
} else {
|
||||
await confirmSpaceJoin(url)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script module lang="ts">
|
||||
import {goto} from "$app/navigation"
|
||||
import {dissoc} from "@welshman/lib"
|
||||
import {ROOM_META} from "@welshman/util"
|
||||
import {load} from "@welshman/net"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {makeSpacePath} from "@app/util/routes"
|
||||
import {addSpaceMembership, broadcastUserData} from "@app/core/commands"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {relaysMostlyRestricted} from "@app/core/state"
|
||||
|
||||
export const confirmSpaceJoin = async (url: string) => {
|
||||
await addSpaceMembership(url)
|
||||
@@ -19,12 +21,9 @@
|
||||
}
|
||||
|
||||
broadcastUserData([url])
|
||||
|
||||
goto(path, {replaceState: true})
|
||||
|
||||
pushToast({
|
||||
message: "Welcome to the space!",
|
||||
})
|
||||
relaysMostlyRestricted.update(dissoc(url))
|
||||
pushToast({message: "Welcome to the space!"})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -246,11 +246,7 @@ export const checkRelayAccess = async (url: string, claim = "") => {
|
||||
|
||||
await attemptAuth(url)
|
||||
|
||||
const thunk = publishThunk({
|
||||
event: makeEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
||||
relays: [url],
|
||||
})
|
||||
|
||||
const thunk = publishJoinRequest({url, claim})
|
||||
const error = await getThunkError(thunk)
|
||||
|
||||
if (error) {
|
||||
@@ -296,7 +292,7 @@ export const checkRelayConnection = async (url: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const checkRelayAuth = async (url: string, timeout = 3000) => {
|
||||
export const checkRelayAuth = async (url: string) => {
|
||||
const socket = Pool.get().get(url)
|
||||
const okStatuses = [AuthStatus.None, AuthStatus.Ok]
|
||||
|
||||
@@ -325,7 +321,7 @@ export const attemptRelayAccess = async (url: string, claim = "") => {
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
// Deletions
|
||||
|
||||
export type DeleteParams = {
|
||||
protect: boolean
|
||||
@@ -351,6 +347,8 @@ export const makeDelete = ({protect, event, tags = []}: DeleteParams) => {
|
||||
export const publishDelete = ({relays, ...params}: DeleteParams & {relays: string[]}) =>
|
||||
publishThunk({event: makeDelete(params), relays})
|
||||
|
||||
// Reports
|
||||
|
||||
export type ReportParams = {
|
||||
event: TrustedEvent
|
||||
content: string
|
||||
@@ -374,6 +372,8 @@ export const publishReport = ({
|
||||
}: ReportParams & {relays: string[]}) =>
|
||||
publishThunk({event: makeReport({event, reason, content}), relays})
|
||||
|
||||
// Reactions
|
||||
|
||||
export type ReactionParams = {
|
||||
protect: boolean
|
||||
event: TrustedEvent
|
||||
@@ -399,6 +399,8 @@ export const makeReaction = ({protect, content, event, tags: paramTags = []}: Re
|
||||
export const publishReaction = ({relays, ...params}: ReactionParams & {relays: string[]}) =>
|
||||
publishThunk({event: makeReaction(params), relays})
|
||||
|
||||
// Comments
|
||||
|
||||
export type CommentParams = {
|
||||
event: TrustedEvent
|
||||
content: string
|
||||
@@ -411,6 +413,8 @@ export const makeComment = ({event, content, tags = []}: CommentParams) =>
|
||||
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
|
||||
publishThunk({event: makeComment(params), relays})
|
||||
|
||||
// Alerts
|
||||
|
||||
export type AlertParams = {
|
||||
feed: Feed
|
||||
description: string
|
||||
@@ -494,6 +498,19 @@ export const addTrustedRelay = async (url: string) =>
|
||||
export const removeTrustedRelay = async (url: string) =>
|
||||
publishSettings({trusted_relays: remove(url, userSettingsValues.get().trusted_relays)})
|
||||
|
||||
// Join request
|
||||
|
||||
export type JoinRequestParams = {
|
||||
url: string
|
||||
claim: string
|
||||
}
|
||||
|
||||
export const makeJoinRequest = (params: JoinRequestParams) =>
|
||||
makeEvent(AUTH_JOIN, {tags: [["claim", params.claim]]})
|
||||
|
||||
export const publishJoinRequest = (params: JoinRequestParams) =>
|
||||
publishThunk({event: makeJoinRequest(params), relays: [params.url]})
|
||||
|
||||
// Lightning
|
||||
|
||||
export const getWebLn = () => (window as any).webln
|
||||
|
||||
@@ -70,6 +70,7 @@ import {
|
||||
getTagValue,
|
||||
getTagValues,
|
||||
verifyEvent,
|
||||
makeEvent,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, SignedEvent, PublishedList, List, Filter} from "@welshman/util"
|
||||
import {Nip59, decrypt} from "@welshman/signer"
|
||||
@@ -92,6 +93,8 @@ import {
|
||||
signer,
|
||||
makeOutboxLoader,
|
||||
appContext,
|
||||
getThunkError,
|
||||
publishThunk,
|
||||
} from "@welshman/app"
|
||||
import type {Thunk, Relay} from "@welshman/app"
|
||||
|
||||
@@ -368,6 +371,10 @@ export const {
|
||||
|
||||
export const relaysPendingTrust = writable<string[]>([])
|
||||
|
||||
// Relays that mostly send restricted responses to requests and events
|
||||
|
||||
export const relaysMostlyRestricted = writable<Record<string, string>>({})
|
||||
|
||||
// Alerts
|
||||
|
||||
export type Alert = {
|
||||
@@ -738,3 +745,51 @@ export const deriveSocket = (url: string) =>
|
||||
|
||||
return () => subs.forEach(call)
|
||||
})
|
||||
|
||||
export const deriveTimeout = (timeout: number) => {
|
||||
const store = writable<boolean>(false)
|
||||
|
||||
setTimeout(() => store.set(true), timeout)
|
||||
|
||||
return derived(store, identity)
|
||||
}
|
||||
|
||||
export const deriveRelayAuthError = (url: string, claim = "") => {
|
||||
const $signer = signer.get()
|
||||
const socket = Pool.get().get(url)
|
||||
const stripPrefix = (m: string) => m.replace(/^\w+: /, "")
|
||||
|
||||
// Kick off the auth process
|
||||
socket.auth.attemptAuth($signer.sign)
|
||||
|
||||
// Attempt to join the relay
|
||||
const thunk = publishThunk({
|
||||
event: makeEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
||||
relays: [url],
|
||||
})
|
||||
|
||||
return derived(
|
||||
[relaysMostlyRestricted, deriveSocket(url)],
|
||||
([$relaysMostlyRestricted, $socket]) => {
|
||||
if ($socket.auth.details) {
|
||||
return stripPrefix($socket.auth.details)
|
||||
}
|
||||
|
||||
if ($relaysMostlyRestricted[url]) {
|
||||
return stripPrefix($relaysMostlyRestricted[url])
|
||||
}
|
||||
|
||||
const error = getThunkError(thunk)
|
||||
|
||||
if (error) {
|
||||
const isIgnored = error.startsWith("mute: ")
|
||||
const isEmptyInvite = !claim && error.includes("invite code")
|
||||
const isStrictNip29Relay = error.includes("missing group (`h`) tag")
|
||||
|
||||
if (!isStrictNip29Relay && !isIgnored && !isEmptyInvite && !isStrictNip29Relay) {
|
||||
return stripPrefix(error) || "join request rejected"
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type {Component} from "svelte"
|
||||
import {writable} from "svelte/store"
|
||||
import {derived, writable} from "svelte/store"
|
||||
import {randomId, always, assoc, Emitter} from "@welshman/lib"
|
||||
import {goto} from "$app/navigation"
|
||||
import {page} from "$app/stores"
|
||||
|
||||
export type ModalOptions = {
|
||||
drawer?: boolean
|
||||
@@ -21,6 +22,10 @@ export const emitter = new Emitter()
|
||||
|
||||
export const modals = writable<Record<string, Modal>>({})
|
||||
|
||||
export const modal = derived([page, modals], ([$page, $modals]) => {
|
||||
return $modals[$page.url.hash.slice(1)]
|
||||
})
|
||||
|
||||
export const pushModal = (
|
||||
component: Component<any>,
|
||||
props: Record<string, any> = {},
|
||||
|
||||
Reference in New Issue
Block a user