Compare commits

...

6 Commits

Author SHA1 Message Date
nayan9617 d0e23b605c Stop tracking .next artifacts 2026-04-08 21:55:57 +05:30
nayan9617 3929ca5eb5 Keep follow sync trickling 2026-04-08 16:28:14 +05:30
nayan9617 5cce03d2ec Align sync as per the review 2026-04-08 16:20:35 +05:30
nayan9617 1f893c890d Guard fallback pulls after abort 2026-04-07 23:02:58 +05:30
nayan9617 5aec6bfa5d refactor: revert socket reset workaround for adapter layer investigation 2026-04-07 00:26:30 +05:30
nayan9617 cd40768f1c fix: reset relay sockets on visibility change to prevent auth hangs on sleep/wake
When browser tab is hidden (sleep), tear down all relay socket connections
completely. This forces fresh socket creation and clean auth handshake on wake,
preventing stuck AuthStatus.PendingResponse states.

- Add documentVisibility store tracking document.visibilityState
- Call Pool.get().remove(url) for relay sockets during hidden teardown
- Integrate socket reset in syncUserData, syncSpaces, syncDMs
- Add explanatory comments for visibility guards and race conditions

Fixes: App stuck in "Authenticating" after Mac sleep/wake cycle
Tested with: wss://news.utxo.one/
2026-04-07 00:15:39 +05:30
2 changed files with 92 additions and 47 deletions
+1
View File
@@ -28,6 +28,7 @@ node_modules/
.pnpm-store/ .pnpm-store/
build/ build/
.svelte-kit/ .svelte-kit/
.next/
# Rust/Tauri # Rust/Tauri
*target/ *target/
+91 -47
View File
@@ -1,8 +1,9 @@
import {page} from "$app/stores" import {page} from "$app/stores"
import type {Unsubscriber} from "svelte/store" import type {Unsubscriber} from "svelte/store"
import {derived, get} from "svelte/store" import {get} from "svelte/store"
import {last, call, ifLet, assoc, chunk, sleep, identity, WEEK, ago} from "@welshman/lib" import {last, call, ifLet, assoc, chunk, sleep, WEEK, ago} from "@welshman/lib"
import {PollResponse} from "nostr-tools/kinds" import {PollResponse} from "nostr-tools/kinds"
import {merged} from "@welshman/store"
import { import {
getListTags, getListTags,
getRelayTagValues, getRelayTagValues,
@@ -21,7 +22,7 @@ import {
unionFilters, unionFilters,
getTagValue, getTagValue,
} from "@welshman/util" } from "@welshman/util"
import type {Filter, TrustedEvent} from "@welshman/util" import type {Filter, List, PublishedList, TrustedEvent} from "@welshman/util"
import {request, requestOne, Difference, DifferenceEvent} from "@welshman/net" import {request, requestOne, Difference, DifferenceEvent} from "@welshman/net"
import { import {
pubkey, pubkey,
@@ -74,6 +75,8 @@ const pullOneWithFallback = async (
signal: AbortSignal, signal: AbortSignal,
onEvent?: (event: TrustedEvent) => void, onEvent?: (event: TrustedEvent) => void,
) => { ) => {
if (signal.aborted) return
const cachedEvents = repository.query([filter]).filter(isSignedEvent) const cachedEvents = repository.query([filter]).filter(isSignedEvent)
const since = last(cachedEvents.slice(10))?.created_at || 0 const since = last(cachedEvents.slice(10))?.created_at || 0
@@ -86,6 +89,12 @@ const pullOneWithFallback = async (
const shouldFallback = const shouldFallback =
!hasNegentropy(url) || !hasNegentropy(url) ||
(await new Promise(resolve => { (await new Promise(resolve => {
if (signal.aborted) {
resolve(false)
return
}
// If teardown wins while the diff is opening, skip the fallback path and let cleanup stay in control.
const diff = new Difference({relay: url, filter, events: cachedEvents, signal}) const diff = new Difference({relay: url, filter, events: cachedEvents, signal})
diff.on(DifferenceEvent.Error, () => { diff.on(DifferenceEvent.Error, () => {
@@ -111,9 +120,7 @@ export const pullWithFallback = async ({url, signal, filters, onEvent}: SyncOpts
if (signal.aborted) return if (signal.aborted) return
for (const filter of filters) { await Promise.all(filters.map(filter => pullOneWithFallback(url, filter, signal, onEvent)))
pullOneWithFallback(url, filter, signal, onEvent)
}
} }
const listen = ({url, signal, filters, onEvent}: SyncOpts) => { const listen = ({url, signal, filters, onEvent}: SyncOpts) => {
@@ -123,6 +130,8 @@ const listen = ({url, signal, filters, onEvent}: SyncOpts) => {
} }
const pullAndListen = (options: SyncOpts) => { const pullAndListen = (options: SyncOpts) => {
if (options.signal.aborted) return
pullWithFallback(options) pullWithFallback(options)
listen(options) listen(options)
} }
@@ -197,7 +206,7 @@ const syncUserRoomMembership = (url: string, h: string) => {
const syncUserData = () => { const syncUserData = () => {
const unsubscribersByKey = new Map<string, Unsubscriber>() const unsubscribersByKey = new Map<string, Unsubscriber>()
const unsubscribeGroupList = userGroupList.subscribe($userGroupList => { const syncGroupList = ($userGroupList: List | undefined) => {
if ($userGroupList) { if ($userGroupList) {
const keys = new Set<string>() const keys = new Set<string>()
@@ -226,26 +235,32 @@ const syncUserData = () => {
} }
} }
} }
}) }
const unsubscribeRelayList = userRelayList.subscribe($userRelayList => { const syncRelayList = ($userRelayList: PublishedList | undefined) => {
if ($userRelayList) { const pubkey = $userRelayList?.event?.pubkey
loadBlossomServerList($userRelayList.event.pubkey)
loadBlockedRelayList($userRelayList.event.pubkey)
loadFollowList($userRelayList.event.pubkey)
loadGroupList($userRelayList.event.pubkey)
loadMuteList($userRelayList.event.pubkey)
loadProfile($userRelayList.event.pubkey)
loadSettings($userRelayList.event.pubkey)
loadFeedsForPubkey($userRelayList.event.pubkey)
}
})
const unsubscribeFollows = userFollowList.subscribe(async $userFollowList => { if (!pubkey) return
loadBlossomServerList(pubkey)
loadBlockedRelayList(pubkey)
loadFollowList(pubkey)
loadGroupList(pubkey)
loadMuteList(pubkey)
loadProfile(pubkey)
loadSettings(pubkey)
loadFeedsForPubkey(pubkey)
}
const syncFollowList = async (signal: AbortSignal) => {
for (const pubkeys of chunk(10, get(bootstrapPubkeys))) { for (const pubkeys of chunk(10, get(bootstrapPubkeys))) {
if (signal.aborted) return
// This isn't urgent, avoid clogging other stuff up // This isn't urgent, avoid clogging other stuff up
await sleep(1000) await sleep(1000)
if (signal.aborted) return
await Promise.all( await Promise.all(
pubkeys.flatMap(pk => [ pubkeys.flatMap(pk => [
loadRelayList(pk), loadRelayList(pk),
@@ -256,9 +271,26 @@ const syncUserData = () => {
]), ]),
) )
} }
}
let bootstrapFollowController = new AbortController()
const unsubscribeGroupList = merged([userGroupList]).subscribe(([$userGroupList]) => {
syncGroupList($userGroupList)
})
const unsubscribeRelayList = merged([userRelayList]).subscribe(([$userRelayList]) => {
syncRelayList($userRelayList)
})
const unsubscribeFollows = merged([userFollowList]).subscribe(() => {
bootstrapFollowController.abort()
bootstrapFollowController = new AbortController()
void syncFollowList(bootstrapFollowController.signal)
}) })
return () => { return () => {
bootstrapFollowController.abort()
unsubscribersByKey.forEach(call) unsubscribersByKey.forEach(call)
unsubscribeGroupList() unsubscribeGroupList()
unsubscribeRelayList() unsubscribeRelayList()
@@ -321,7 +353,7 @@ const syncSpace = (url: string, rooms: string[]) => {
} }
const syncSpaces = () => { const syncSpaces = () => {
const store = derived([userGroupList, page], identity) const store = merged([userGroupList, page])
const unsubscribersByUrl = new Map<string, Unsubscriber>() const unsubscribersByUrl = new Map<string, Unsubscriber>()
const roomsByUrl = new Map<string, string>() const roomsByUrl = new Map<string, string>()
@@ -383,6 +415,7 @@ const syncDMs = () => {
const unsubscribersByUrl = new Map<string, Unsubscriber>() const unsubscribersByUrl = new Map<string, Unsubscriber>()
let currentPubkey: string | undefined let currentPubkey: string | undefined
let currentShouldUnwrap = false
const unsubscribeAll = () => { const unsubscribeAll = () => {
for (const [url, unsubscribe] of unsubscribersByUrl.entries()) { for (const [url, unsubscribe] of unsubscribersByUrl.entries()) {
@@ -391,6 +424,34 @@ const syncDMs = () => {
} }
} }
const syncPubkey = ($pubkey: string | undefined, $shouldUnwrap: boolean) => {
if ($pubkey !== currentPubkey) {
unsubscribeAll()
}
if ($pubkey && $shouldUnwrap) {
loadRelayList($pubkey)
.then(() => loadMessagingRelayList($pubkey))
.then($l => {
if ($l && currentPubkey === $pubkey && currentShouldUnwrap === $shouldUnwrap) {
subscribeAll($pubkey, getRelayTagValues(getListTags($l)))
}
})
}
currentPubkey = $pubkey
currentShouldUnwrap = $shouldUnwrap
}
const syncList = ($userMessagingRelayList: List | undefined) => {
const $pubkey = pubkey.get()
const $shouldUnwrap = shouldUnwrap.get()
if ($pubkey && $shouldUnwrap) {
subscribeAll($pubkey, getRelayTagValues(getListTags($userMessagingRelayList)))
}
}
const subscribeAll = (pubkey: string, urls: string[]) => { const subscribeAll = (pubkey: string, urls: string[]) => {
// Start syncing newly added relays // Start syncing newly added relays
for (const url of urls) { for (const url of urls) {
@@ -408,33 +469,16 @@ const syncDMs = () => {
} }
} }
// When pubkey changes, re-sync const unsubscribePubkey = merged([pubkey, shouldUnwrap]).subscribe(([$pubkey, $shouldUnwrap]) => {
const unsubscribePubkey = derived([pubkey, shouldUnwrap], identity).subscribe( syncPubkey($pubkey, $shouldUnwrap)
([$pubkey, $shouldUnwrap]) => { })
if ($pubkey !== currentPubkey) {
unsubscribeAll()
}
// If we have a pubkey, refresh our user's relay list then sync our subscriptions
if ($pubkey && $shouldUnwrap) {
loadRelayList($pubkey)
.then(() => loadMessagingRelayList($pubkey))
.then($l => subscribeAll($pubkey, getRelayTagValues(getListTags($l))))
}
currentPubkey = $pubkey
},
)
// When user messaging relays change, update synchronization // When user messaging relays change, update synchronization
const unsubscribeList = userMessagingRelayList.subscribe($userMessagingRelayList => { const unsubscribeList = merged([userMessagingRelayList]).subscribe(
const $pubkey = pubkey.get() ([$userMessagingRelayList]) => {
const $shouldUnwrap = shouldUnwrap.get() syncList($userMessagingRelayList)
},
if ($pubkey && $shouldUnwrap) { )
subscribeAll($pubkey, getRelayTagValues(getListTags($userMessagingRelayList)))
}
})
return () => { return () => {
unsubscribeAll() unsubscribeAll()