diff --git a/src/components/Column.tsx b/src/components/Column.tsx index a88b100..940723b 100644 --- a/src/components/Column.tsx +++ b/src/components/Column.tsx @@ -25,7 +25,7 @@ const Column: Component = (props) => { useHandleCommand(() => ({ commandType: 'moveToLastColumn', - handler: (command) => { + handler: () => { if (props.lastColumn) { columnDivRef?.scrollIntoView({ behavior: 'smooth' }); } diff --git a/src/components/Config.tsx b/src/components/Config.tsx index 8d1355a..3142e7f 100644 --- a/src/components/Config.tsx +++ b/src/components/Config.tsx @@ -125,7 +125,7 @@ const ToggleButton = (props: { 'justify-end': props.value, }} area-label={props.value} - onClick={props.onClick} + onClick={(event) => props.onClick(event)} > diff --git a/src/components/DeprecatedRepost.tsx b/src/components/DeprecatedRepost.tsx index 335f417..d9a9dac 100644 --- a/src/components/DeprecatedRepost.tsx +++ b/src/components/DeprecatedRepost.tsx @@ -1,15 +1,10 @@ // NIP-18 (DEPRECATED) -import { Show, Switch, Match, type Component, createMemo } from 'solid-js'; +import { type Component, createMemo } from 'solid-js'; import { Event as NostrEvent } from 'nostr-tools'; import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-square.svg'; -import useConfig from '@/nostr/useConfig'; -import useEvent from '@/nostr/useEvent'; -import useProfile from '@/nostr/useProfile'; - import ColumnItem from '@/components/ColumnItem'; import UserDisplayName from '@/components/UserDisplayName'; -import TextNote from '@/components/TextNote'; import eventWrapper from '@/core/event'; import useFormatDate from '@/hooks/useFormatDate'; import TextNoteDisplayById from './textNote/TextNoteDisplayById'; diff --git a/src/components/textNote/TextNoteDisplay.tsx b/src/components/textNote/TextNoteDisplay.tsx index 0c2c4da..4ef6c39 100644 --- a/src/components/textNote/TextNoteDisplay.tsx +++ b/src/components/textNote/TextNoteDisplay.tsx @@ -8,7 +8,6 @@ import ArrowPathRoundedSquare from 'heroicons/24/outline/arrow-path-rounded-squa import ChatBubbleLeft from 'heroicons/24/outline/chat-bubble-left.svg'; import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg'; -import ColumnItem from '@/components/ColumnItem'; import GeneralUserMentionDisplay from '@/components/textNote/GeneralUserMentionDisplay'; import ContentWarningDisplay from '@/components/textNote/ContentWarningDisplay'; import TextNoteContentDisplay from '@/components/textNote/TextNoteContentDisplay'; @@ -107,7 +106,7 @@ const TextNoteDisplay: Component = (props) => { const createdAt = () => formatDate(event().createdAtAsDate()); - const handleRepost: JSX.EventHandler = (ev) => { + const handleRepost: JSX.EventHandler = () => { if (isRepostedByMe()) { // TODO remove reaction return; @@ -123,7 +122,7 @@ const TextNoteDisplay: Component = (props) => { }); }; - const handleReaction: JSX.EventHandler = (ev) => { + const handleReaction: JSX.EventHandler = () => { if (isReactedByMe()) { // TODO remove reaction return; diff --git a/src/core/column.ts b/src/core/column.ts index 0e13d87..329d37b 100644 --- a/src/core/column.ts +++ b/src/core/column.ts @@ -1,18 +1,16 @@ // import { z } from 'zod'; -import { Event as NostrEvent } from 'nostr-tools'; +import { type Event as NostrEvent, type Filter } from 'nostr-tools'; +import ColumnComponent from '@/components/Column'; -export type NotificationTypes = +export type NotificationType = // The event which includes ["p", ...] tags. | 'PubkeyMention' // The event which includes ["e", ...] tags. | 'EventMention' // The event which has the deprecated kind 6. | 'DeprecatedRepost' - // The event which has - | 'Followed' - | 'PlusReaction' - | 'MinusReaction' - | 'EmojiReaction'; + // The event which has the deprecated kind 7. + | 'Reaction'; export type GenericFilterOptions = { matching: string; @@ -21,24 +19,73 @@ export type GenericFilterOptions = { }; export type NotificationFilterOptions = { - allowedTypes: NotificationTypes[]; + allowedTypes: NotificationType[]; }; type BulidOptions = { supportedNips: string[]; }; -const notificationFilter = - (filterOption: NotificationFilterOptions) => - (event: NostrEvent): boolean => {}; +// const notificationFilter = +// (filterOption: NotificationFilterOptions) => +// (event: NostrEvent): boolean => {}; +// +// const genericFilter = +// (filterOption: GenericFilterOptions) => +// (event: NostrEvent): boolean => { +// event.content; +// }; +// +// /** +// * build a filter for the subscription ("REQ") +// */ +// export const buildFilter = (options: BuildOptions) => {}; -const genericFilter = - (filterOption: GenericFilterOptions) => - (event: NostrEvent): boolean => { - event.content; - }; +export type BaseColumn = { + columnWidth: (typeof ColumnComponent)['width']; +}; -/** - * build a filter for the subscription ("REQ") - */ -export const buildFilter = (options: BuildOptions) => {}; +/** A column which shows posts by following users */ +export type FollowingColumn = { + columnType: 'Following'; + pubkey: string; +}; + +/** A column which shows replies, reactions, reposts to the specific user */ +export type NotificationColumn = { + columnType: 'Notification'; + notificationTypes: NotificationType[]; + pubkey: string; +}; + +/** A column which shows posts from the specific user */ +export type PostsColumn = { + columnType: 'Posts'; + pubkey: string; +}; + +/** A column which shows reactions published by the specific user */ +export type ReactionsColumn = { + columnType: 'Reactions'; + pubkey: string; +}; + +/** A column which shows text notes and reposts posted to the specific relays */ +export type GlobalColumn = { + columnType: 'Global'; + relayUrls: string[]; +}; + +/** A column which shows text notes and reposts posted to the specific relays */ +export type CustomFilterColumn = { + columnType: 'CustomFilter'; + filters: Filter[]; +}; + +export type ColumnConfig = + | FollowingColumn + | NotificationColumn + | GlobalColumn + | PostsColumn + | ReactionsColumn + | CustomFilterColumn; diff --git a/src/core/parseTextNote.ts b/src/core/parseTextNote.ts index 87d480f..31b533f 100644 --- a/src/core/parseTextNote.ts +++ b/src/core/parseTextNote.ts @@ -30,7 +30,6 @@ export type Bech32Entity = { | { type: 'nprofile'; data: ProfilePointer } | { type: 'nevent'; data: EventPointer }; }; -// | { type: 'naddr'; data: AddressPointer }; export type HashTag = { type: 'HashTag'; @@ -53,13 +52,12 @@ export type ParsedTextNoteNode = export type ParsedTextNote = ParsedTextNoteNode[]; -export const parseTextNote = (event: NostrEvent): ParsedTextNote => { +const parseTextNote = (event: NostrEvent): ParsedTextNote => { const matches = [ ...event.content.matchAll(/(?:#\[(?\d+)\])/g), ...event.content.matchAll(/#(?[^[-^`:-@!-/{-~\d\s][^[-^`:-@!-/{-~\s]+)/g), - ...event.content.matchAll( - /(?(npub|note|nprofile|nevent|nrelay|naddr)1[ac-hj-np-z02-9]+)/gi, - ), + // nrelay and naddr is not supported by nostr-tools + ...event.content.matchAll(/(?(npub|note|nprofile|nevent)1[ac-hj-np-z02-9]+)/gi), ...event.content.matchAll( /(?(https?|wss?):\/\/[-a-zA-Z0-9.]+(?:\/[-\w.@%:]+|\/)*(?:\?[-\w=.@%:&]*)?(?:#[-\w=.%:&]*)?)/g, ), diff --git a/src/nostr/useBatchedEvents.ts b/src/nostr/useBatchedEvents.ts index 5fa3a03..e5cc305 100644 --- a/src/nostr/useBatchedEvents.ts +++ b/src/nostr/useBatchedEvents.ts @@ -20,7 +20,8 @@ type TaskArg = | { type: 'Profile'; pubkey: string } | { type: 'TextNote'; eventId: string } | { type: 'Reactions'; mentionedEventId: string } - | { type: 'DeprecatedReposts'; mentionedEventId: string }; + | { type: 'DeprecatedReposts'; mentionedEventId: string } + | { type: 'Followings'; pubkey: string }; type BatchedEvents = { completed: boolean; events: NostrEvent[] }; @@ -92,12 +93,30 @@ export type UseDeprecatedReposts = { query: CreateQueryResult; }; +// Followings +type UseFollowingsProps = { + pubkey: string; +}; + +type Following = { + pubkey: string; + mainRelayUrl?: string; + petname?: string; +}; + +export type UseFollowings = { + followings: Accessor; + followingPubkeys: Accessor; + query: CreateQueryResult; +}; + const { exec } = useBatch(() => ({ executor: (tasks) => { const profileTasks = new Map[]>(); const textNoteTasks = new Map[]>(); const reactionsTasks = new Map[]>(); const repostsTasks = new Map[]>(); + const followingsTasks = new Map[]>(); tasks.forEach((task) => { if (task.args.type === 'Profile') { @@ -112,6 +131,9 @@ const { exec } = useBatch(() => ({ } else if (task.args.type === 'DeprecatedReposts') { const current = repostsTasks.get(task.args.mentionedEventId) ?? []; repostsTasks.set(task.args.mentionedEventId, [...current, task]); + } else if (task.args.type === 'Followings') { + const current = followingsTasks.get(task.args.pubkey) ?? []; + followingsTasks.set(task.args.pubkey, [...current, task]); } }); @@ -119,6 +141,7 @@ const { exec } = useBatch(() => ({ const textNoteIds = [...textNoteTasks.keys()]; const reactionsIds = [...reactionsTasks.keys()]; const repostsIds = [...repostsTasks.keys()]; + const followingsIds = [...followingsTasks.keys()]; const filters: Filter[] = []; @@ -134,6 +157,9 @@ const { exec } = useBatch(() => ({ if (repostsIds.length > 0) { filters.push({ kinds: [6], '#e': repostsIds }); } + if (followingsIds.length > 0) { + filters.push({ kinds: [Kind.Contacts], authors: followingsIds }); + } if (filters.length === 0) return; @@ -194,6 +220,9 @@ const { exec } = useBatch(() => ({ const registeredTasks = repostsTasks.get(taggedEventId) ?? []; resolveTasks(registeredTasks, event); }); + } else if (event.kind === Kind.Contacts) { + const registeredTasks = followingsTasks.get(event.pubkey) ?? []; + resolveTasks(registeredTasks, event); } }, onEOSE: () => { @@ -203,6 +232,11 @@ const { exec } = useBatch(() => ({ }, })); +const pickLatestEvent = (events: NostrEvent[]): NostrEvent | undefined => { + if (events.length === 0) return undefined; + return events.reduce((a, b) => (a.created_at > b.created_at ? a : b)); +}; + export const useProfile = (propsProvider: () => UseProfileProps | null): UseProfile => { const props = createMemo(propsProvider); const queryClient = useQueryClient(); @@ -215,9 +249,8 @@ export const useProfile = (propsProvider: () => UseProfileProps | null): UseProf const { pubkey } = currentProps; const promise = exec({ type: 'Profile', pubkey }, signal).then((batchedEvents) => { const latestEvent = () => { - const { events } = batchedEvents(); - if (events.length === 0) throw new Error(`profile not found: ${pubkey}`); - const latest = events.reduce((a, b) => (a.created_at > b.created_at ? a : b)); + const latest = pickLatestEvent(batchedEvents().events); + if (latest == null) throw new Error(`profile not found: ${pubkey}`); return latest; }; observable(batchedEvents).subscribe(() => { @@ -233,9 +266,10 @@ export const useProfile = (propsProvider: () => UseProfileProps | null): UseProf return timeout(15000, `useProfile: ${pubkey}`)(promise); }, { - // profile is updated occasionally - staleTime: 5 * 60 * 1000, // 5min - cacheTime: 24 * 60 * 60 * 1000, // 1day + // Profiles are updated occasionally, so a short staleTime is used here. + // cacheTime is long so that the user see profiles instantly. + staleTime: 5 * 60 * 1000, // 5 min + cacheTime: 24 * 60 * 60 * 1000, // 1 day }, ); @@ -273,9 +307,9 @@ export const useTextNote = (propsProvider: () => UseTextNoteProps | null): UseTe return timeout(15000, `useTextNote: ${eventId}`)(promise); }, { - // text note cannot be updated. - staleTime: 24 * 60 * 60 * 1000, // 1 day - cacheTime: 24 * 60 * 60 * 1000, // 1 day + // Text notes never change, so they can be stored for a long time. + staleTime: 4 * 60 * 60 * 1000, // 4 hour + cacheTime: 4 * 60 * 60 * 1000, // 4 hour }, ); @@ -299,10 +333,8 @@ export const useReactions = (propsProvider: () => UseReactionsProps | null): Use const promise = exec({ type: 'Reactions', mentionedEventId }, signal).then( (batchedEvents) => { const events = () => batchedEvents().events; - setTimeout(() => { - observable(batchedEvents).subscribe(() => { - queryClient.setQueryData(queryKey, events()); - }); + observable(batchedEvents).subscribe(() => { + queryClient.setQueryData(queryKey, events()); }); return events(); }, @@ -311,7 +343,7 @@ export const useReactions = (propsProvider: () => UseReactionsProps | null): Use }, { staleTime: 1 * 60 * 1000, // 1 min - cacheTime: 5 * 60 * 1000, // 5 min + cacheTime: 4 * 60 * 60 * 1000, // 4 hour }, ); @@ -351,10 +383,8 @@ export const useDeprecatedReposts = ( const promise = exec({ type: 'DeprecatedReposts', mentionedEventId }, signal).then( (batchedEvents) => { const events = () => batchedEvents().events; - setTimeout(() => { - observable(batchedEvents).subscribe(() => { - queryClient.setQueryData(queryKey, events()); - }); + observable(batchedEvents).subscribe(() => { + queryClient.setQueryData(queryKey, events()); }); return events(); }, @@ -363,7 +393,7 @@ export const useDeprecatedReposts = ( }, { staleTime: 1 * 60 * 1000, // 1 min - cacheTime: 5 * 60 * 1000, // 5 min + cacheTime: 4 * 60 * 60 * 1000, // 4 hour }, ); @@ -377,3 +407,70 @@ export const useDeprecatedReposts = ( return { reposts, isRepostedBy, invalidateDeprecatedReposts, query }; }; + +export const useFollowings = (propsProvider: () => UseFollowingsProps | null): UseFollowings => { + const queryClient = useQueryClient(); + const props = createMemo(propsProvider); + const genQueryKey = () => ['useFollowings', props()] as const; + + const query = createQuery( + genQueryKey, + ({ queryKey, signal }) => { + const [, currentProps] = queryKey; + if (currentProps == null) return undefined; + const { pubkey } = currentProps; + const promise = exec({ type: 'Followings', pubkey }, signal).then((batchedEvents) => { + const latestEvent = () => { + const latest = pickLatestEvent(batchedEvents().events); + if (latest == null) throw new Error(`followings not found: ${pubkey}`); + return latest; + }; + observable(batchedEvents).subscribe(() => { + try { + queryClient.setQueryData(queryKey, latestEvent()); + } catch (err) { + console.error(err); + } + }); + return latestEvent(); + }); + return timeout(15000, `useFollowings: ${pubkey}`)(promise); + }, + { + staleTime: 5 * 60 * 1000, // 5 min + cacheTime: 4 * 60 * 60 * 1000, // 4 hour + refetchOnWindowFocus: false, + }, + ); + + const followings = () => { + if (query.data == null) return []; + + const event = query.data; + + const result: Following[] = []; + event.tags.forEach((tag) => { + // TODO zodにする + const [tagName, followingPubkey, mainRelayUrl, petname] = tag; + if (!tag.every((e) => typeof e === 'string')) return; + if (tagName !== 'p') return; + + const following: Following = { pubkey: followingPubkey, petname }; + if (mainRelayUrl != null && mainRelayUrl.length > 0) { + following.mainRelayUrl = mainRelayUrl; + } + + result.push(following); + }); + + return result; + }; + + const followingPubkeys = (): string[] => { + return followings().map((follow) => follow.pubkey); + }; + + return { followings, followingPubkeys, query }; +}; + +export default useFollowings; diff --git a/src/nostr/useCachedEvents.ts b/src/nostr/useCachedEvents.ts deleted file mode 100644 index 260df12..0000000 --- a/src/nostr/useCachedEvents.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { createQuery } from '@tanstack/solid-query'; -import { type UseSubscriptionProps } from '@/nostr/useSubscription'; -import type { Event as NostrEvent, Filter, SimplePool, SubscriptionOptions } from 'nostr-tools'; -import usePool from './usePool'; - -type GetEventsArgs = { - pool: SimplePool; - relayUrls: string[]; - filters: Filter[]; - // TODO 継続的に取得する場合、Promiseでは無理なので、無理やりキャッシュにストアする仕組みを使う - continuous?: boolean; - options?: SubscriptionOptions; - signal?: AbortSignal; -}; - -const getEvents = async ({ - pool, - relayUrls, - filters, - options, - signal, -}: GetEventsArgs): Promise => { - const result: NostrEvent[] = []; - - const sub = pool.sub(relayUrls, filters, options); - sub.on('event', (event: NostrEvent) => result.push(event)); - - return new Promise((resolve, reject) => { - sub.on('eose', () => { - sub.unsub(); - resolve(result); - }); - - if (signal != null) { - signal.addEventListener('abort', () => { - sub.unsub(); - reject(signal.reason); - }); - } - }); -}; - -/** - * This aims to fetch stored data, and doesn't support fetching streaming data continuously. - * - * This is useful when you want to fetch some data which change occasionally: - * profile or following list, reactions, and something like that. - */ -const useCachedEvents = (propsProvider: () => UseSubscriptionProps | null) => { - const pool = usePool(); - - return createQuery( - () => { - const currentProps = propsProvider(); - return ['useCachedEvents', currentProps] as const; - }, - ({ queryKey, signal }) => { - const [, currentProps] = queryKey; - if (currentProps == null) return []; - return getEvents({ pool: pool(), signal, ...currentProps }); - }, - { - staleTime: 5 * 60 * 1000, - cacheTime: 15 * 60 * 1000, - }, - ); -}; - -export default useCachedEvents; diff --git a/src/nostr/useCommands.ts b/src/nostr/useCommands.ts index bcb954d..3f5cf78 100644 --- a/src/nostr/useCommands.ts +++ b/src/nostr/useCommands.ts @@ -6,7 +6,7 @@ import usePool from '@/nostr/usePool'; const currentDate = (): number => Math.floor(Date.now() / 1000); // NIP-20: Command Result -const waitCommandResult = (pub: Pub): Promise => { +const waitCommandResult = (pub: Pub, relayUrl: string): Promise => { return new Promise((resolve, reject) => { pub.on('ok', () => { console.log(`${relayUrl} has accepted our event`); @@ -34,7 +34,7 @@ const useCommands = () => { return relayUrls.map(async (relayUrl) => { const relay = await pool().ensureRelay(relayUrl); const pub = relay.publish(signedEvent); - return waitCommandResult(pub); + return waitCommandResult(pub, relayUrl); }); }; diff --git a/src/nostr/useConfig.ts b/src/nostr/useConfig.ts index 8e2ac48..434ace8 100644 --- a/src/nostr/useConfig.ts +++ b/src/nostr/useConfig.ts @@ -17,28 +17,40 @@ type UseConfig = { removeRelay: (url: string) => void; }; -const InitialConfig: Config = { - relayUrls: [ - 'wss://relay-jp.nostr.wirednet.jp', - 'wss://nostr.h3z.jp', +const InitialConfig = (): Config => { + const relayUrls = [ 'wss://relay.damus.io', 'wss://nos.lol', 'wss://relay.snort.social', 'wss://relay.current.fyi', - 'wss://relay.nostr.wirednet.jp', - 'wss://nostr-relay.nokotaro.com', - 'wss://nostr.holybea.com', - ], - dateFormat: 'relative', - keepOpenPostForm: false, + ]; + if (navigator.language === 'ja') { + relayUrls.push( + 'wss://nostr.h3z.jp', + 'wss://relay.nostr.wirednet.jp', + 'wss://relay-jp.nostr.wirednet.jp', + 'wss://nostr.holybea.com', + 'wss://nostr-relay.nokotaro.com', + ); + } + + return { + relayUrls, + dateFormat: 'relative', + keepOpenPostForm: false, + }; }; const serializer = (config: Config): string => JSON.stringify(config); // TODO zod使う -const deserializer = (json: string): Config => JSON.parse(json) as Config; +const deserializer = (json: string): Config => + ({ + ...InitialConfig(), + ...JSON.parse(json), + } as Config); const storage = createStorageWithSerializer(() => window.localStorage, serializer, deserializer); -const [config, setConfig] = createSignalWithStorage('RabbitConfig', InitialConfig, storage); +const [config, setConfig] = createSignalWithStorage('RabbitConfig', InitialConfig(), storage); const useConfig = (): UseConfig => { const addRelay = (relayUrl: string) => { @@ -56,7 +68,7 @@ const useConfig = (): UseConfig => { }; return { - config: () => ({ ...InitialConfig, ...config() }), + config, setConfig, addRelay, removeRelay, diff --git a/src/nostr/useFollowings.ts b/src/nostr/useFollowings.ts index 1079b8b..4495073 100644 --- a/src/nostr/useFollowings.ts +++ b/src/nostr/useFollowings.ts @@ -1,65 +1,3 @@ -import { createMemo } from 'solid-js'; -import useCachedEvents from '@/nostr/useCachedEvents'; - -type UseFollowingsProps = { - relayUrls: string[]; - pubkey: string; -}; - -type Following = { - pubkey: string; - mainRelayUrl?: string; - petname?: string; -}; - -const useFollowings = (propsProvider: () => UseFollowingsProps | null) => { - const props = createMemo(propsProvider); - const query = useCachedEvents(() => { - const currentProps = props(); - if (currentProps == null) return null; - const { relayUrls, pubkey } = currentProps; - return { - relayUrls, - filters: [ - { - kinds: [3], - authors: [pubkey], - limit: 1, - }, - ], - }; - }); - - const followings = () => { - if (query.data != null && query.data.length === 0) return []; - - const event = query.data?.reduce((a, b) => (a.created_at > b.created_at ? a : b)); - - if (event == null) return []; - - const result: Following[] = []; - event.tags.forEach((tag) => { - // TODO zodにする - const [tagName, followingPubkey, mainRelayUrl, petname] = tag; - if (!tag.every((e) => typeof e === 'string')) return; - if (tagName !== 'p') return; - - const following: Following = { pubkey: followingPubkey, petname }; - if (mainRelayUrl != null && mainRelayUrl.length > 0) { - following.mainRelayUrl = mainRelayUrl; - } - - result.push(following); - }); - - return result; - }; - - const followingPubkeys = (): string[] => { - return followings().map((follow) => follow.pubkey); - }; - - return { followings, followingPubkeys }; -}; +import { useFollowings } from '@/nostr/useBatchedEvents'; export default useFollowings; diff --git a/src/nostr/usePubkey.ts b/src/nostr/usePubkey.ts index 186a810..e8e69fd 100644 --- a/src/nostr/usePubkey.ts +++ b/src/nostr/usePubkey.ts @@ -25,7 +25,7 @@ const usePubkey = (): Accessor => { window.nostr .getPublicKey() .then((key) => setPubkey(key)) - .catch((err) => console.error(`failed to obtain public key: ${err}`)); + .catch((err) => console.error('failed to obtain public key: ', err)); } count += 1; }, 1000); diff --git a/src/pages/Hello.tsx b/src/pages/Hello.tsx index c5497dd..23376dc 100644 --- a/src/pages/Hello.tsx +++ b/src/pages/Hello.tsx @@ -1,7 +1,6 @@ import { createSignal, onMount, Switch, Match, type Component } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import usePersistStatus from '@/hooks/usePersistStatus'; -import { persistQueryClient } from '@tanstack/react-query-persist-client'; type SignerStatus = 'checking' | 'available' | 'unavailable'; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 5f7210c..a4a853e 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -30,8 +30,7 @@ const Home: Component = () => { createEffect(() => { config().relayUrls.map(async (relayUrl) => { const relay = await pool().ensureRelay(relayUrl); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - relay.on('notice', (msg: any) => { + relay.on('notice', (msg: string) => { console.error(`NOTICE: ${relayUrl}: ${msg}`); }); }); @@ -44,6 +43,10 @@ const Home: Component = () => { })), ); + createEffect(() => { + console.log(followingPubkeys()); + }); + const { events: followingsPosts } = useSubscription(() => ensureNonNull([pubkey()] as const)(([pubkeyNonNull]) => ({ relayUrls: config().relayUrls, diff --git a/tsconfig.json b/tsconfig.json index 1836ce4..f19e126 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "esModuleInterop": true, "jsx": "preserve", "jsxImportSource": "solid-js", - "types": ["vite/client", "mocha", "node"], + "types": ["vite/client", "node"], "noEmit": true, "isolatedModules": true, "baseUrl": ".",