diff --git a/src/components/modal/ProfileDisplay.tsx b/src/components/modal/ProfileDisplay.tsx index 9c1f0c0..fe752fa 100644 --- a/src/components/modal/ProfileDisplay.tsx +++ b/src/components/modal/ProfileDisplay.tsx @@ -1,4 +1,13 @@ -import { Component, createSignal, createMemo, Show, Switch, Match, createEffect } from 'solid-js'; +import { + Component, + createSignal, + createMemo, + Show, + Switch, + Match, + createEffect, + onMount, +} from 'solid-js'; import { createMutation } from '@tanstack/solid-query'; import ArrowPath from 'heroicons/24/outline/arrow-path.svg'; @@ -18,7 +27,7 @@ import useModalState from '@/hooks/useModalState'; import { useTranslation } from '@/i18n/useTranslation'; import useCommands from '@/nostr/useCommands'; import useFollowers from '@/nostr/useFollowers'; -import useFollowings from '@/nostr/useFollowings'; +import useFollowings, { fetchLatestFollowings } from '@/nostr/useFollowings'; import useProfile from '@/nostr/useProfile'; import usePubkey from '@/nostr/usePubkey'; import useSubscription from '@/nostr/useSubscription'; @@ -27,6 +36,7 @@ import ensureNonNull from '@/utils/ensureNonNull'; import epoch from '@/utils/epoch'; import npubEncodeFallback from '@/utils/npubEncodeFallback'; import sleep from '@/utils/sleep'; +import stripMargin from '@/utils/stripMargin'; import timeout from '@/utils/timeout'; export type ProfileDisplayProps = { @@ -130,18 +140,36 @@ const ProfileDisplay: Component = (props) => { if (p == null) return; setUpdatingContacts(true); - await refetchMyFollowing(); - await sleep(3000); - const current = myFollowingPubkeys(); - console.debug('current pubkeys', current); + const latest = await fetchLatestFollowings({ pubkey: p }); + + const msg = stripMargin` + フォローリストが空のようです。初めてのフォローであれば問題ありません。 + そうでなければ、リレーとの接続がうまくいっていない可能性があります。ページを再読み込みしてリレーと再接続してください。 + また、他のクライアントと同じリレーを設定できているどうかご確認ください。 + + 続行しますか? + `; + + if ((latest.data() == null || latest.followingPubkeys().length === 0) && !window.confirm(msg)) + return; + + if ((latest?.data()?.created_at ?? 0) < (myFollowingQuery.data?.created_at ?? 0)) { + window.alert( + '最新のフォローリストを取得できませんでした。リレーの接続状況が悪い可能性があります。', + ); + return; + } await updateContactsMutation.mutateAsync({ relayUrls: config().relayUrls, pubkey: p, - content: myFollowingQuery.data?.content ?? '', - followingPubkeys: uniq(update(current)), + content: latest.data()?.content ?? '', + followingPubkeys: uniq(update(latest.followingPubkeys())), }); + } catch (err) { + console.error('failed to update contact list', err); + window.alert('フォローリストの更新に失敗しました。'); } finally { setUpdatingContacts(false); } diff --git a/src/nostr/useFollowings.ts b/src/nostr/useFollowings.ts index afc9b35..0381414 100644 --- a/src/nostr/useFollowings.ts +++ b/src/nostr/useFollowings.ts @@ -24,13 +24,43 @@ export type UseFollowings = { query: CreateQueryResult; }; -export const fetchLatestFollowings = ( +const buildMethods = (dataProvider: () => NostrEvent | undefined | null) => { + const followings = () => { + const data = dataProvider(); + if (data == null) return []; + + const result: Following[] = []; + + // TODO zodにする + const event = genericEvent(data); + event.pTags().forEach((tag) => { + const [, followingPubkey, mainRelayUrl, petname] = tag; + + const following: Following = { pubkey: followingPubkey, petname }; + if (mainRelayUrl != null && mainRelayUrl.length > 0) { + following.mainRelayUrl = mainRelayUrl; + } + + result.push(following); + }); + + return result; + }; + + const followingPubkeys = (): string[] => followings().map((follow) => follow.pubkey); + + return { followings, followingPubkeys, data: dataProvider }; +}; + +export const fetchLatestFollowings = async ( { pubkey }: UseFollowingsProps, signal?: AbortSignal, -): Promise => { +) => { const task = new BatchedEventsTask({ type: 'Followings', pubkey }); registerTask({ task, signal }); - return task.latestEventPromise(); + + const latestFollowings = await task.latestEventPromise(); + return buildMethods(() => latestFollowings); }; const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollowings => { @@ -51,39 +81,16 @@ const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollo { staleTime: 5 * 60 * 1000, // 5 min cacheTime: 24 * 60 * 60 * 1000, // 24 hour - refetchOnMount: false, + refetchOnMount: true, refetchOnWindowFocus: false, refetchOnReconnect: false, refetchInterval: 0, }, ); - const followings = () => { - if (query.data == null) return []; - - const result: Following[] = []; - - // TODO zodにする - const event = genericEvent(query.data); - event.pTags().forEach((tag) => { - const [, followingPubkey, mainRelayUrl, petname] = tag; - - const following: Following = { pubkey: followingPubkey, petname }; - if (mainRelayUrl != null && mainRelayUrl.length > 0) { - following.mainRelayUrl = mainRelayUrl; - } - - result.push(following); - }); - - return result; - }; - - const followingPubkeys = (): string[] => followings().map((follow) => follow.pubkey); - const invalidateFollowings = (): Promise => queryClient.invalidateQueries(genQueryKey()); - return { followings, followingPubkeys, invalidateFollowings, query }; + return { ...buildMethods(() => query.data), invalidateFollowings, query }; }; export default useFollowings; diff --git a/src/nostr/usePool.ts b/src/nostr/usePool.ts index 069f9b1..db3b593 100644 --- a/src/nostr/usePool.ts +++ b/src/nostr/usePool.ts @@ -2,10 +2,8 @@ import { createSignal } from 'solid-js'; import { SimplePool } from 'nostr-tools'; -const [pool] = createSignal(new SimplePool()); +const [pool] = createSignal(new SimplePool({ eoseSubTimeout: 7500 })); -const usePool = () => { - return pool; -}; +const usePool = () => pool; export default usePool; diff --git a/src/utils/stripMargin.ts b/src/utils/stripMargin.ts new file mode 100644 index 0000000..5f54dd9 --- /dev/null +++ b/src/utils/stripMargin.ts @@ -0,0 +1,24 @@ +const stripMargin = (strings: TemplateStringsArray, ...values: any[]) => { + const s = String.raw(strings, values); + + const result = []; + const lines = s.split('\n'); + + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + const marginRemoved = line.trimStart(); + if (result.length > 0 || marginRemoved.length > 0) { + result.push(marginRemoved); + } + } + + while (result.length > 0) { + const lastLine = result[result.length - 1]; + if (lastLine.length > 0) break; + result.pop(); + } + + return result.join('\n'); +}; + +export default stripMargin;