This commit is contained in:
Shusui MOYATANI
2023-03-05 09:05:21 +09:00
parent 0327af6ba1
commit 30e6e894ed
13 changed files with 144 additions and 102 deletions

View File

@@ -16,8 +16,7 @@ export type UseBatchedEventProps<TaskArgs> = {
const useBatchedEvent = <TaskArgs>(propsProvider: () => UseBatchedEventProps<TaskArgs>) => {
const props = createMemo(propsProvider);
return useBatch<TaskArgs, NostrEvent>(() => {
return {
return useBatch<TaskArgs, NostrEvent>(() => ({
interval: props().interval,
executor: (tasks) => {
const { generateKey, mergeFilters, extractKey } = props();
@@ -37,19 +36,17 @@ const useBatchedEvent = <TaskArgs>(propsProvider: () => UseBatchedEventProps<Tas
const key = extractKey(event);
if (key == null) return;
const task = keyTaskMap.get(key);
// possibly, the new event received
if (task == null) return;
task.resolve(event);
},
onEOSE: () => {
tasks.forEach((task) => {
task.reject(new Error('NotFound'));
task.reject(new Error(`NotFound: ${JSON.stringify(filters)}`));
});
},
}));
},
};
});
}));
};
export default useBatchedEvent;

View File

@@ -35,8 +35,7 @@ const addEvent =
const useBatchedEvents = <TaskArgs>(propsProvider: () => UseBatchedEventsProps<TaskArgs>) => {
const props = createMemo(propsProvider);
return useBatch<TaskArgs, Accessor<BatchedEvents>>(() => {
return {
return useBatch<TaskArgs, Accessor<BatchedEvents>>(() => ({
interval: props().interval,
executor: (tasks) => {
const { generateKey, mergeFilters, extractKey } = props();
@@ -91,8 +90,7 @@ const useBatchedEvents = <TaskArgs>(propsProvider: () => UseBatchedEventsProps<T
},
}));
},
};
});
}));
};
export default useBatchedEvents;

View File

@@ -49,20 +49,20 @@ const getEvents = async ({
* 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) => {
const useCachedEvents = (propsProvider: () => UseSubscriptionProps | null) => {
const pool = usePool();
return createQuery(
() => {
const { relayUrls, filters, continuous, options } = propsProvider();
return ['useCachedEvents', relayUrls, filters, continuous, options] as const;
const currentProps = propsProvider();
return ['useCachedEvents', currentProps] as const;
},
({ queryKey, signal }) => {
const [, relayUrls, filters, continuous, options] = queryKey;
return getEvents({ pool: pool(), relayUrls, filters, options, continuous, signal });
const [, currentProps] = queryKey;
if (currentProps == null) return [];
return getEvents({ pool: pool(), signal, ...currentProps });
},
{
// 5 minutes
staleTime: 5 * 60 * 1000,
cacheTime: 15 * 60 * 1000,
},

View File

@@ -39,6 +39,7 @@ const useDeprecatedReposts = (
() => queryKey(),
({ queryKey: currentQueryKey, signal }) => {
const [, currentProps] = currentQueryKey;
if (currentProps == null) return () => ({ events: [], completed: false });
return timeout(
15000,
`useDeprecatedReposts: ${currentProps.eventId}`,

View File

@@ -13,7 +13,7 @@ export type UseEventProps = {
export type UseEvent = {
event: Accessor<NostrEvent | undefined>;
query: CreateQueryResult<NostrEvent>;
query: CreateQueryResult<NostrEvent | undefined>;
};
const { exec } = useBatchedEvent<UseEventProps>(() => ({
@@ -25,12 +25,13 @@ const { exec } = useBatchedEvent<UseEventProps>(() => ({
extractKey: (event: NostrEvent) => event.id,
}));
const useEvent = (propsProvider: () => UseEventProps): UseEvent => {
const useEvent = (propsProvider: () => UseEventProps | null): UseEvent => {
const props = createMemo(propsProvider);
const query = createQuery(
() => ['useEvent', props()] as const,
({ queryKey, signal }) => {
const [, currentProps] = queryKey;
if (currentProps == null) return undefined;
return timeout(15000, `useEvent: ${currentProps.eventId}`)(exec(currentProps, signal));
},
{

View File

@@ -1,3 +1,4 @@
import { createMemo } from 'solid-js';
import useCachedEvents from '@/clients/useCachedEvents';
type UseFollowingsProps = {
@@ -12,8 +13,11 @@ type Following = {
};
const useFollowings = (propsProvider: () => UseFollowingsProps) => {
const props = createMemo(propsProvider);
const query = useCachedEvents(() => {
const { relayUrls, pubkey } = propsProvider();
const currentProps = props();
if (currentProps == null) return null;
const { relayUrls, pubkey } = currentProps;
return {
relayUrls,
filters: [

View File

@@ -43,12 +43,14 @@ const { exec } = useBatchedEvent<UseProfileProps>(() => ({
extractKey: (event: NostrEvent): string => event.pubkey,
}));
const useProfile = (propsProvider: () => UseProfileProps): UseProfile => {
const useProfile = (propsProvider: () => UseProfileProps | null): UseProfile => {
const props = createMemo(propsProvider);
const query = createQuery(
() => ['useProfile', props()] as const,
({ queryKey, signal }) => {
const [, currentProps] = queryKey;
if (currentProps == null) return null;
// TODO timeoutと同時にsignalでキャンセルするようにしたい
return timeout(15000, `useProfile: ${currentProps.pubkey}`)(exec(currentProps, signal));
},

View File

@@ -6,6 +6,19 @@ const [pubkey, setPubkey] = createSignal<string | undefined>(undefined);
const usePubkey = (): Accessor<string | undefined> => {
onMount(() => {
let count = 0;
const intervalId = setInterval(() => {
if (count >= 5) {
clearInterval(intervalId);
if (pubkey() == null && !asking) {
if (window.nostr == null) {
throw new Error('Failed to obtain public key: Timeout. window.nostr is not defined.');
}
throw new Error('Failed to obtain public key: Timeout');
}
return;
}
if (window.nostr != null && pubkey() == null && !asking) {
asking = true;
window.nostr
@@ -13,6 +26,8 @@ const usePubkey = (): Accessor<string | undefined> => {
.then((key) => setPubkey(key))
.catch((err) => console.error(`failed to obtain public key: ${err}`));
}
count += 1;
}, 1000);
});
return pubkey;

View File

@@ -29,7 +29,7 @@ const { exec } = useBatchedEvents<UseReactionsProps>(() => ({
},
}));
const useReactions = (propsProvider: () => UseReactionsProps): UseReactions => {
const useReactions = (propsProvider: () => UseReactionsProps | null): UseReactions => {
const queryClient = useQueryClient();
const props = createMemo(propsProvider);
const queryKey = createMemo(() => ['useReactions', props()] as const);
@@ -38,6 +38,7 @@ const useReactions = (propsProvider: () => UseReactionsProps): UseReactions => {
() => queryKey(),
({ queryKey: currentQueryKey, signal }) => {
const [, currentProps] = currentQueryKey;
if (currentProps == null) return () => ({ events: [], completed: false });
return timeout(15000, `useReactions: ${currentProps.eventId}`)(exec(currentProps, signal));
},
{

0
src/components/Hello.tsx Normal file
View File

View File

@@ -13,6 +13,7 @@ import useSubscription from '@/clients/useSubscription';
import useFollowings from '@/clients/useFollowings';
import usePubkey from '@/clients/usePubkey';
import useShortcutKeys from '@/hooks/useShortcutKeys';
import ensureNonNull from '@/hooks/ensureNonNull';
useShortcutKeys({
onShortcut: (s) => console.log(s),
@@ -106,6 +107,7 @@ const Home: Component = () => {
});
};
const japaneseRegex = /[あ-ん]/;
return (
<div class="flex h-screen w-screen flex-row overflow-hidden">
<SideBar postForm={() => <NotePostForm onPost={handlePost} />} />

View File

@@ -0,0 +1,14 @@
export type TupleNonNull<T extends readonly any[]> = {
[P in keyof T]: NonNullable<T[P]>;
};
const ensureNonNull =
<T extends readonly any[], R>(tuple: T) =>
(f: (tupleNonNull: TupleNonNull<T>) => R): R | null => {
if (tuple.some((e) => e == null)) {
return null;
}
return f(tuple as TupleNonNull<T>);
};
export default ensureNonNull;

7
src/utils/sleep.ts Normal file
View File

@@ -0,0 +1,7 @@
const sleep = (ms: number): Promise<void> => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
export default sleep;