mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 10:57:04 +00:00
Monitor relay connections for restricted responses and show error to user
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {page} from "$app/stores"
|
|
||||||
import Drawer from "@lib/components/Drawer.svelte"
|
import Drawer from "@lib/components/Drawer.svelte"
|
||||||
import Dialog from "@lib/components/Dialog.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) => {
|
const onKeyDown = (e: any) => {
|
||||||
if (e.code === "Escape" && e.target === document.body) {
|
if (e.code === "Escape" && e.target === document.body) {
|
||||||
@@ -10,22 +9,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = $derived($page.url.hash.slice(1))
|
const m = $derived($modal)
|
||||||
const modal = $derived($modals[hash])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window onkeydown={onKeyDown} />
|
<svelte:window onkeydown={onKeyDown} />
|
||||||
|
|
||||||
{#if modal?.options?.drawer}
|
{#if m?.options?.drawer}
|
||||||
<Drawer onClose={clearModals} {...modal.options}>
|
<Drawer onClose={clearModals} {...m.options}>
|
||||||
{#key modal.id}
|
{#key m.id}
|
||||||
<modal.component {...modal.props} />
|
<m.component {...m.props} />
|
||||||
{/key}
|
{/key}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
{:else if modal}
|
{:else if m}
|
||||||
<Dialog onClose={clearModals} {...modal.options}>
|
<Dialog onClose={clearModals} {...m.options}>
|
||||||
{#key modal.id}
|
{#key m.id}
|
||||||
<modal.component {...modal.props} />
|
<m.component {...m.props} />
|
||||||
{/key}
|
{/key}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {displayUrl} from "@welshman/lib"
|
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 {preventDefault} from "@lib/html"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -11,7 +12,8 @@
|
|||||||
import SpaceJoinConfirm, {confirmSpaceJoin} from "@app/components/SpaceJoinConfirm.svelte"
|
import SpaceJoinConfirm, {confirmSpaceJoin} from "@app/components/SpaceJoinConfirm.svelte"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {pushModal} from "@app/util/modal"
|
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 = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -21,27 +23,24 @@
|
|||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const joinRelay = async () => {
|
const socket = deriveSocket(url)
|
||||||
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 join = async () => {
|
const join = async () => {
|
||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
try {
|
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 {
|
} finally {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<script module lang="ts">
|
<script module lang="ts">
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
|
import {dissoc} from "@welshman/lib"
|
||||||
import {ROOM_META} from "@welshman/util"
|
import {ROOM_META} from "@welshman/util"
|
||||||
import {load} from "@welshman/net"
|
import {load} from "@welshman/net"
|
||||||
|
import {pushToast} from "@app/util/toast"
|
||||||
import {makeSpacePath} from "@app/util/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {addSpaceMembership, broadcastUserData} from "@app/core/commands"
|
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) => {
|
export const confirmSpaceJoin = async (url: string) => {
|
||||||
await addSpaceMembership(url)
|
await addSpaceMembership(url)
|
||||||
@@ -19,12 +21,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
broadcastUserData([url])
|
broadcastUserData([url])
|
||||||
|
|
||||||
goto(path, {replaceState: true})
|
goto(path, {replaceState: true})
|
||||||
|
relaysMostlyRestricted.update(dissoc(url))
|
||||||
pushToast({
|
pushToast({message: "Welcome to the space!"})
|
||||||
message: "Welcome to the space!",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -246,11 +246,7 @@ export const checkRelayAccess = async (url: string, claim = "") => {
|
|||||||
|
|
||||||
await attemptAuth(url)
|
await attemptAuth(url)
|
||||||
|
|
||||||
const thunk = publishThunk({
|
const thunk = publishJoinRequest({url, claim})
|
||||||
event: makeEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
|
||||||
relays: [url],
|
|
||||||
})
|
|
||||||
|
|
||||||
const error = await getThunkError(thunk)
|
const error = await getThunkError(thunk)
|
||||||
|
|
||||||
if (error) {
|
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 socket = Pool.get().get(url)
|
||||||
const okStatuses = [AuthStatus.None, AuthStatus.Ok]
|
const okStatuses = [AuthStatus.None, AuthStatus.Ok]
|
||||||
|
|
||||||
@@ -325,7 +321,7 @@ export const attemptRelayAccess = async (url: string, claim = "") => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Deletions
|
||||||
|
|
||||||
export type DeleteParams = {
|
export type DeleteParams = {
|
||||||
protect: boolean
|
protect: boolean
|
||||||
@@ -351,6 +347,8 @@ export const makeDelete = ({protect, event, tags = []}: DeleteParams) => {
|
|||||||
export const publishDelete = ({relays, ...params}: DeleteParams & {relays: string[]}) =>
|
export const publishDelete = ({relays, ...params}: DeleteParams & {relays: string[]}) =>
|
||||||
publishThunk({event: makeDelete(params), relays})
|
publishThunk({event: makeDelete(params), relays})
|
||||||
|
|
||||||
|
// Reports
|
||||||
|
|
||||||
export type ReportParams = {
|
export type ReportParams = {
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
content: string
|
content: string
|
||||||
@@ -374,6 +372,8 @@ export const publishReport = ({
|
|||||||
}: ReportParams & {relays: string[]}) =>
|
}: ReportParams & {relays: string[]}) =>
|
||||||
publishThunk({event: makeReport({event, reason, content}), relays})
|
publishThunk({event: makeReport({event, reason, content}), relays})
|
||||||
|
|
||||||
|
// Reactions
|
||||||
|
|
||||||
export type ReactionParams = {
|
export type ReactionParams = {
|
||||||
protect: boolean
|
protect: boolean
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
@@ -399,6 +399,8 @@ export const makeReaction = ({protect, content, event, tags: paramTags = []}: Re
|
|||||||
export const publishReaction = ({relays, ...params}: ReactionParams & {relays: string[]}) =>
|
export const publishReaction = ({relays, ...params}: ReactionParams & {relays: string[]}) =>
|
||||||
publishThunk({event: makeReaction(params), relays})
|
publishThunk({event: makeReaction(params), relays})
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
|
||||||
export type CommentParams = {
|
export type CommentParams = {
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
content: string
|
content: string
|
||||||
@@ -411,6 +413,8 @@ export const makeComment = ({event, content, tags = []}: CommentParams) =>
|
|||||||
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
|
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
|
||||||
publishThunk({event: makeComment(params), relays})
|
publishThunk({event: makeComment(params), relays})
|
||||||
|
|
||||||
|
// Alerts
|
||||||
|
|
||||||
export type AlertParams = {
|
export type AlertParams = {
|
||||||
feed: Feed
|
feed: Feed
|
||||||
description: string
|
description: string
|
||||||
@@ -494,6 +498,19 @@ export const addTrustedRelay = async (url: string) =>
|
|||||||
export const removeTrustedRelay = async (url: string) =>
|
export const removeTrustedRelay = async (url: string) =>
|
||||||
publishSettings({trusted_relays: remove(url, userSettingsValues.get().trusted_relays)})
|
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
|
// Lightning
|
||||||
|
|
||||||
export const getWebLn = () => (window as any).webln
|
export const getWebLn = () => (window as any).webln
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import {
|
|||||||
getTagValue,
|
getTagValue,
|
||||||
getTagValues,
|
getTagValues,
|
||||||
verifyEvent,
|
verifyEvent,
|
||||||
|
makeEvent,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent, SignedEvent, PublishedList, List, Filter} from "@welshman/util"
|
import type {TrustedEvent, SignedEvent, PublishedList, List, Filter} from "@welshman/util"
|
||||||
import {Nip59, decrypt} from "@welshman/signer"
|
import {Nip59, decrypt} from "@welshman/signer"
|
||||||
@@ -92,6 +93,8 @@ import {
|
|||||||
signer,
|
signer,
|
||||||
makeOutboxLoader,
|
makeOutboxLoader,
|
||||||
appContext,
|
appContext,
|
||||||
|
getThunkError,
|
||||||
|
publishThunk,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import type {Thunk, Relay} from "@welshman/app"
|
import type {Thunk, Relay} from "@welshman/app"
|
||||||
|
|
||||||
@@ -368,6 +371,10 @@ export const {
|
|||||||
|
|
||||||
export const relaysPendingTrust = writable<string[]>([])
|
export const relaysPendingTrust = writable<string[]>([])
|
||||||
|
|
||||||
|
// Relays that mostly send restricted responses to requests and events
|
||||||
|
|
||||||
|
export const relaysMostlyRestricted = writable<Record<string, string>>({})
|
||||||
|
|
||||||
// Alerts
|
// Alerts
|
||||||
|
|
||||||
export type Alert = {
|
export type Alert = {
|
||||||
@@ -738,3 +745,51 @@ export const deriveSocket = (url: string) =>
|
|||||||
|
|
||||||
return () => subs.forEach(call)
|
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 type {Component} from "svelte"
|
||||||
import {writable} from "svelte/store"
|
import {derived, writable} from "svelte/store"
|
||||||
import {randomId, always, assoc, Emitter} from "@welshman/lib"
|
import {randomId, always, assoc, Emitter} from "@welshman/lib"
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
|
import {page} from "$app/stores"
|
||||||
|
|
||||||
export type ModalOptions = {
|
export type ModalOptions = {
|
||||||
drawer?: boolean
|
drawer?: boolean
|
||||||
@@ -21,6 +22,10 @@ export const emitter = new Emitter()
|
|||||||
|
|
||||||
export const modals = writable<Record<string, Modal>>({})
|
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 = (
|
export const pushModal = (
|
||||||
component: Component<any>,
|
component: Component<any>,
|
||||||
props: Record<string, any> = {},
|
props: Record<string, any> = {},
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {throttled} from "@welshman/store"
|
||||||
import {AuthStatus, SocketStatus} from "@welshman/net"
|
import {AuthStatus, SocketStatus} from "@welshman/net"
|
||||||
import {deriveSocket} from "@app/core/state"
|
|
||||||
import StatusIndicator from "@lib/components/StatusIndicator.svelte"
|
import StatusIndicator from "@lib/components/StatusIndicator.svelte"
|
||||||
|
import {deriveSocket, relaysMostlyRestricted} from "@app/core/state"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url}: Props = $props()
|
const {url}: Props = $props()
|
||||||
const socket = deriveSocket(url)
|
const socket = throttled(800, deriveSocket(url))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $socket.status === SocketStatus.Open}
|
{#if $socket.status === SocketStatus.Open}
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
<StatusIndicator class="bg-red-500">Failed to Authenticate</StatusIndicator>
|
<StatusIndicator class="bg-red-500">Failed to Authenticate</StatusIndicator>
|
||||||
{:else if $socket.auth.status === AuthStatus.PendingResponse}
|
{:else if $socket.auth.status === AuthStatus.PendingResponse}
|
||||||
<StatusIndicator class="bg-yellow-500">Authenticating</StatusIndicator>
|
<StatusIndicator class="bg-yellow-500">Authenticating</StatusIndicator>
|
||||||
{:else if $socket.auth.status === AuthStatus.Forbidden}
|
{:else if $socket.auth.status === AuthStatus.Forbidden || $relaysMostlyRestricted[url]}
|
||||||
<StatusIndicator class="bg-red-500">Access Denied</StatusIndicator>
|
<StatusIndicator class="bg-red-500">Access Denied</StatusIndicator>
|
||||||
{:else if $socket.auth.status === AuthStatus.Ok}
|
{:else if $socket.auth.status === AuthStatus.Ok}
|
||||||
<StatusIndicator class="bg-green-500">Connected</StatusIndicator>
|
<StatusIndicator class="bg-green-500">Connected</StatusIndicator>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
ago,
|
ago,
|
||||||
WEEK,
|
WEEK,
|
||||||
TaskQueue,
|
TaskQueue,
|
||||||
|
assoc,
|
||||||
|
dissoc,
|
||||||
} from "@welshman/lib"
|
} from "@welshman/lib"
|
||||||
import type {TrustedEvent, StampedEvent} from "@welshman/util"
|
import type {TrustedEvent, StampedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
@@ -40,13 +42,18 @@
|
|||||||
getRelaysFromList,
|
getRelaysFromList,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||||
import type {Socket, RelayMessage} from "@welshman/net"
|
import type {Socket, RelayMessage, ClientMessage} from "@welshman/net"
|
||||||
import {
|
import {
|
||||||
request,
|
request,
|
||||||
defaultSocketPolicies,
|
defaultSocketPolicies,
|
||||||
makeSocketPolicyAuth,
|
makeSocketPolicyAuth,
|
||||||
SocketEvent,
|
SocketEvent,
|
||||||
isRelayEvent,
|
isRelayEvent,
|
||||||
|
isRelayOk,
|
||||||
|
isRelayClosed,
|
||||||
|
isClientReq,
|
||||||
|
isClientEvent,
|
||||||
|
isClientClose,
|
||||||
} from "@welshman/net"
|
} from "@welshman/net"
|
||||||
import {
|
import {
|
||||||
loadRelay,
|
loadRelay,
|
||||||
@@ -87,6 +94,7 @@
|
|||||||
ensureUnwrapped,
|
ensureUnwrapped,
|
||||||
canDecrypt,
|
canDecrypt,
|
||||||
getSetting,
|
getSetting,
|
||||||
|
relaysMostlyRestricted,
|
||||||
} from "@app/core/state"
|
} from "@app/core/state"
|
||||||
import {loadUserData, listenForNotifications} from "@app/core/requests"
|
import {loadUserData, listenForNotifications} from "@app/core/requests"
|
||||||
import {theme} from "@app/util/theme"
|
import {theme} from "@app/util/theme"
|
||||||
@@ -296,6 +304,71 @@
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribers.forEach(call)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function monitorRestrictedResponses(socket: Socket) {
|
||||||
|
let total = 0
|
||||||
|
let restricted = 0
|
||||||
|
let error = ""
|
||||||
|
|
||||||
|
const pending = new Set<string>()
|
||||||
|
|
||||||
|
const updateStatus = () =>
|
||||||
|
relaysMostlyRestricted.update(
|
||||||
|
restricted > total / 2 ? assoc(socket.url, error) : dissoc(socket.url),
|
||||||
|
)
|
||||||
|
|
||||||
|
const unsubscribers = [
|
||||||
|
on(socket, SocketEvent.Receive, (message: RelayMessage) => {
|
||||||
|
if (isRelayOk(message)) {
|
||||||
|
const [_, id, ok, details = ""] = message
|
||||||
|
|
||||||
|
if (pending.has(id)) {
|
||||||
|
pending.delete(id)
|
||||||
|
|
||||||
|
if (!ok && details.startsWith("restricted: ")) {
|
||||||
|
restricted++
|
||||||
|
error = details
|
||||||
|
updateStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRelayClosed(message)) {
|
||||||
|
const [_, id, details = ""] = message
|
||||||
|
|
||||||
|
if (pending.has(id)) {
|
||||||
|
pending.delete(id)
|
||||||
|
|
||||||
|
if (details.startsWith("restricted: ")) {
|
||||||
|
restricted++
|
||||||
|
error = details
|
||||||
|
updateStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
on(socket, SocketEvent.Send, (message: ClientMessage) => {
|
||||||
|
if (isClientReq(message)) {
|
||||||
|
total++
|
||||||
|
pending.add(message[1])
|
||||||
|
updateStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isClientEvent(message)) {
|
||||||
|
total++
|
||||||
|
pending.add(message[1].id)
|
||||||
|
updateStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isClientClose(message)) {
|
||||||
|
pending.delete(message[1])
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribers.forEach(call)
|
unsubscribers.forEach(call)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {ago, MONTH} from "@welshman/lib"
|
import {ago, sleep, once, MONTH} from "@welshman/lib"
|
||||||
import {ROOM_META, EVENT_TIME, THREAD, COMMENT, MESSAGE} from "@welshman/util"
|
import {ROOM_META, EVENT_TIME, THREAD, COMMENT, MESSAGE, displayRelayUrl} from "@welshman/util"
|
||||||
|
import {SocketStatus} from "@welshman/net"
|
||||||
import Page from "@lib/components/Page.svelte"
|
import Page from "@lib/components/Page.svelte"
|
||||||
import Dialog from "@lib/components/Dialog.svelte"
|
import Dialog from "@lib/components/Dialog.svelte"
|
||||||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||||
@@ -13,8 +14,13 @@
|
|||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {setChecked} from "@app/util/notifications"
|
import {setChecked} from "@app/util/notifications"
|
||||||
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/core/commands"
|
import {
|
||||||
import {decodeRelay, userRoomsByUrl, relaysPendingTrust} from "@app/core/state"
|
decodeRelay,
|
||||||
|
deriveRelayAuthError,
|
||||||
|
relaysPendingTrust,
|
||||||
|
deriveSocket,
|
||||||
|
userRoomsByUrl,
|
||||||
|
} from "@app/core/state"
|
||||||
import {pullConservatively} from "@app/core/requests"
|
import {pullConservatively} from "@app/core/requests"
|
||||||
import {notifications} from "@app/util/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
|
||||||
@@ -28,21 +34,11 @@
|
|||||||
|
|
||||||
const rooms = Array.from($userRoomsByUrl.get(url) || [])
|
const rooms = Array.from($userRoomsByUrl.get(url) || [])
|
||||||
|
|
||||||
const checkConnection = async (signal: AbortSignal) => {
|
const socket = deriveSocket(url)
|
||||||
const connectionError = await checkRelayConnection(url)
|
|
||||||
|
|
||||||
if (connectionError) {
|
const authError = deriveRelayAuthError(url)
|
||||||
return pushToast({theme: "error", message: connectionError})
|
|
||||||
}
|
|
||||||
|
|
||||||
const [authError, accessError] = await Promise.all([checkRelayAuth(url), checkRelayAccess(url)])
|
const showAuthError = once(() => pushModal(SpaceAuthError, {url, error: $authError}))
|
||||||
|
|
||||||
const error = authError || accessError
|
|
||||||
|
|
||||||
if (error && !signal.aborted) {
|
|
||||||
pushModal(SpaceAuthError, {url, error})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to watch this one, since on mobile the badge will be visible when active
|
// We have to watch this one, since on mobile the badge will be visible when active
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -51,17 +47,29 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
// Watch for relay errors and notify the user
|
||||||
const relays = [url]
|
$effect(() => {
|
||||||
const since = ago(MONTH)
|
if ($authError) {
|
||||||
const controller = new AbortController()
|
showAuthError()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
checkConnection(controller.signal)
|
onMount(() => {
|
||||||
|
const since = ago(MONTH)
|
||||||
|
|
||||||
|
sleep(2000).then(() => {
|
||||||
|
if ($socket.status !== SocketStatus.Open) {
|
||||||
|
pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: `Failed to connect to ${displayRelayUrl(url)}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Load group meta, threads, calendar events, comments, and recent messages
|
// Load group meta, threads, calendar events, comments, and recent messages
|
||||||
// for user rooms to help with a quick page transition
|
// for user rooms to help with a quick page transition
|
||||||
pullConservatively({
|
pullConservatively({
|
||||||
relays,
|
relays: [url],
|
||||||
filters: [
|
filters: [
|
||||||
{kinds: [ROOM_META]},
|
{kinds: [ROOM_META]},
|
||||||
{kinds: [THREAD, EVENT_TIME, MESSAGE], since},
|
{kinds: [THREAD, EVENT_TIME, MESSAGE], since},
|
||||||
@@ -69,10 +77,6 @@
|
|||||||
...rooms.map(room => ({kinds: [MESSAGE], "#h": [room], since})),
|
...rooms.map(room => ({kinds: [MESSAGE], "#h": [room], since})),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
|
||||||
controller.abort()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user