mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-18 14:34:25 +01:00
update
This commit is contained in:
@@ -16,40 +16,37 @@ export type UseBatchedEventProps<TaskArgs> = {
|
|||||||
const useBatchedEvent = <TaskArgs>(propsProvider: () => UseBatchedEventProps<TaskArgs>) => {
|
const useBatchedEvent = <TaskArgs>(propsProvider: () => UseBatchedEventProps<TaskArgs>) => {
|
||||||
const props = createMemo(propsProvider);
|
const props = createMemo(propsProvider);
|
||||||
|
|
||||||
return useBatch<TaskArgs, NostrEvent>(() => {
|
return useBatch<TaskArgs, NostrEvent>(() => ({
|
||||||
return {
|
interval: props().interval,
|
||||||
interval: props().interval,
|
executor: (tasks) => {
|
||||||
executor: (tasks) => {
|
const { generateKey, mergeFilters, extractKey } = props();
|
||||||
const { generateKey, mergeFilters, extractKey } = props();
|
// TODO relayUrlsを考慮する
|
||||||
// TODO relayUrlsを考慮する
|
const [config] = useConfig();
|
||||||
const [config] = useConfig();
|
|
||||||
|
|
||||||
const keyTaskMap = new Map<string | number, Task<TaskArgs, NostrEvent>>(
|
const keyTaskMap = new Map<string | number, Task<TaskArgs, NostrEvent>>(
|
||||||
tasks.map((task) => [generateKey(task.args), task]),
|
tasks.map((task) => [generateKey(task.args), task]),
|
||||||
);
|
);
|
||||||
const filters = mergeFilters(tasks.map((task) => task.args));
|
const filters = mergeFilters(tasks.map((task) => task.args));
|
||||||
|
|
||||||
useSubscription(() => ({
|
useSubscription(() => ({
|
||||||
relayUrls: config().relayUrls,
|
relayUrls: config().relayUrls,
|
||||||
filters,
|
filters,
|
||||||
continuous: false,
|
continuous: false,
|
||||||
onEvent: (event: NostrEvent) => {
|
onEvent: (event: NostrEvent) => {
|
||||||
const key = extractKey(event);
|
const key = extractKey(event);
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
const task = keyTaskMap.get(key);
|
const task = keyTaskMap.get(key);
|
||||||
// possibly, the new event received
|
if (task == null) return;
|
||||||
if (task == null) return;
|
task.resolve(event);
|
||||||
task.resolve(event);
|
},
|
||||||
},
|
onEOSE: () => {
|
||||||
onEOSE: () => {
|
tasks.forEach((task) => {
|
||||||
tasks.forEach((task) => {
|
task.reject(new Error(`NotFound: ${JSON.stringify(filters)}`));
|
||||||
task.reject(new Error('NotFound'));
|
});
|
||||||
});
|
},
|
||||||
},
|
}));
|
||||||
}));
|
},
|
||||||
},
|
}));
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useBatchedEvent;
|
export default useBatchedEvent;
|
||||||
|
|||||||
@@ -35,64 +35,62 @@ const addEvent =
|
|||||||
const useBatchedEvents = <TaskArgs>(propsProvider: () => UseBatchedEventsProps<TaskArgs>) => {
|
const useBatchedEvents = <TaskArgs>(propsProvider: () => UseBatchedEventsProps<TaskArgs>) => {
|
||||||
const props = createMemo(propsProvider);
|
const props = createMemo(propsProvider);
|
||||||
|
|
||||||
return useBatch<TaskArgs, Accessor<BatchedEvents>>(() => {
|
return useBatch<TaskArgs, Accessor<BatchedEvents>>(() => ({
|
||||||
return {
|
interval: props().interval,
|
||||||
interval: props().interval,
|
executor: (tasks) => {
|
||||||
executor: (tasks) => {
|
const { generateKey, mergeFilters, extractKey } = props();
|
||||||
const { generateKey, mergeFilters, extractKey } = props();
|
// TODO relayUrlsを考慮する
|
||||||
// TODO relayUrlsを考慮する
|
const [config] = useConfig();
|
||||||
const [config] = useConfig();
|
|
||||||
|
|
||||||
const keyTaskMap = new Map<string | number, Task<TaskArgs, Accessor<BatchedEvents>>>(
|
const keyTaskMap = new Map<string | number, Task<TaskArgs, Accessor<BatchedEvents>>>(
|
||||||
tasks.map((task) => [generateKey(task.args), task]),
|
tasks.map((task) => [generateKey(task.args), task]),
|
||||||
);
|
);
|
||||||
const filters = mergeFilters(tasks.map((task) => task.args));
|
const filters = mergeFilters(tasks.map((task) => task.args));
|
||||||
const keyEventSignalsMap = new Map<string | number, Signal<BatchedEvents>>();
|
const keyEventSignalsMap = new Map<string | number, Signal<BatchedEvents>>();
|
||||||
|
|
||||||
const getSignalForKey = (key: string | number): Signal<BatchedEvents> => {
|
const getSignalForKey = (key: string | number): Signal<BatchedEvents> => {
|
||||||
const eventsSignal =
|
const eventsSignal =
|
||||||
keyEventSignalsMap.get(key) ??
|
keyEventSignalsMap.get(key) ??
|
||||||
createSignal<BatchedEvents>({
|
createSignal<BatchedEvents>({
|
||||||
events: [],
|
events: [],
|
||||||
completed: false,
|
completed: false,
|
||||||
});
|
});
|
||||||
keyEventSignalsMap.set(key, eventsSignal);
|
keyEventSignalsMap.set(key, eventsSignal);
|
||||||
return eventsSignal;
|
return eventsSignal;
|
||||||
};
|
};
|
||||||
const didReceivedEventsForKey = (key: string | number): boolean =>
|
const didReceivedEventsForKey = (key: string | number): boolean =>
|
||||||
keyEventSignalsMap.has(key);
|
keyEventSignalsMap.has(key);
|
||||||
|
|
||||||
useSubscription(() => ({
|
useSubscription(() => ({
|
||||||
relayUrls: config().relayUrls,
|
relayUrls: config().relayUrls,
|
||||||
filters,
|
filters,
|
||||||
continuous: false,
|
continuous: false,
|
||||||
onEvent: (event: NostrEvent) => {
|
onEvent: (event: NostrEvent) => {
|
||||||
const key = extractKey(event);
|
const key = extractKey(event);
|
||||||
if (key == null) return;
|
if (key == null) return;
|
||||||
const task = keyTaskMap.get(key);
|
const task = keyTaskMap.get(key);
|
||||||
if (task == null) return;
|
if (task == null) return;
|
||||||
|
|
||||||
const [events, setEvents] = getSignalForKey(key);
|
const [events, setEvents] = getSignalForKey(key);
|
||||||
|
|
||||||
setEvents(addEvent(event));
|
setEvents(addEvent(event));
|
||||||
|
|
||||||
task.resolve(events);
|
task.resolve(events);
|
||||||
},
|
},
|
||||||
onEOSE: () => {
|
onEOSE: () => {
|
||||||
tasks.forEach((task) => {
|
tasks.forEach((task) => {
|
||||||
const key = generateKey(task.args);
|
const key = generateKey(task.args);
|
||||||
if (didReceivedEventsForKey(key)) {
|
if (didReceivedEventsForKey(key)) {
|
||||||
const [, setEvents] = getSignalForKey(key);
|
const [, setEvents] = getSignalForKey(key);
|
||||||
setEvents(completeBatchedEvents);
|
setEvents(completeBatchedEvents);
|
||||||
} else {
|
} else {
|
||||||
task.resolve(emptyBatchedEvents);
|
task.resolve(emptyBatchedEvents);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useBatchedEvents;
|
export default useBatchedEvents;
|
||||||
|
|||||||
@@ -49,20 +49,20 @@ const getEvents = async ({
|
|||||||
* This is useful when you want to fetch some data which change occasionally:
|
* This is useful when you want to fetch some data which change occasionally:
|
||||||
* profile or following list, reactions, and something like that.
|
* profile or following list, reactions, and something like that.
|
||||||
*/
|
*/
|
||||||
const useCachedEvents = (propsProvider: () => UseSubscriptionProps) => {
|
const useCachedEvents = (propsProvider: () => UseSubscriptionProps | null) => {
|
||||||
const pool = usePool();
|
const pool = usePool();
|
||||||
|
|
||||||
return createQuery(
|
return createQuery(
|
||||||
() => {
|
() => {
|
||||||
const { relayUrls, filters, continuous, options } = propsProvider();
|
const currentProps = propsProvider();
|
||||||
return ['useCachedEvents', relayUrls, filters, continuous, options] as const;
|
return ['useCachedEvents', currentProps] as const;
|
||||||
},
|
},
|
||||||
({ queryKey, signal }) => {
|
({ queryKey, signal }) => {
|
||||||
const [, relayUrls, filters, continuous, options] = queryKey;
|
const [, currentProps] = queryKey;
|
||||||
return getEvents({ pool: pool(), relayUrls, filters, options, continuous, signal });
|
if (currentProps == null) return [];
|
||||||
|
return getEvents({ pool: pool(), signal, ...currentProps });
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// 5 minutes
|
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
cacheTime: 15 * 60 * 1000,
|
cacheTime: 15 * 60 * 1000,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const useDeprecatedReposts = (
|
|||||||
() => queryKey(),
|
() => queryKey(),
|
||||||
({ queryKey: currentQueryKey, signal }) => {
|
({ queryKey: currentQueryKey, signal }) => {
|
||||||
const [, currentProps] = currentQueryKey;
|
const [, currentProps] = currentQueryKey;
|
||||||
|
if (currentProps == null) return () => ({ events: [], completed: false });
|
||||||
return timeout(
|
return timeout(
|
||||||
15000,
|
15000,
|
||||||
`useDeprecatedReposts: ${currentProps.eventId}`,
|
`useDeprecatedReposts: ${currentProps.eventId}`,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type UseEventProps = {
|
|||||||
|
|
||||||
export type UseEvent = {
|
export type UseEvent = {
|
||||||
event: Accessor<NostrEvent | undefined>;
|
event: Accessor<NostrEvent | undefined>;
|
||||||
query: CreateQueryResult<NostrEvent>;
|
query: CreateQueryResult<NostrEvent | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { exec } = useBatchedEvent<UseEventProps>(() => ({
|
const { exec } = useBatchedEvent<UseEventProps>(() => ({
|
||||||
@@ -25,12 +25,13 @@ const { exec } = useBatchedEvent<UseEventProps>(() => ({
|
|||||||
extractKey: (event: NostrEvent) => event.id,
|
extractKey: (event: NostrEvent) => event.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useEvent = (propsProvider: () => UseEventProps): UseEvent => {
|
const useEvent = (propsProvider: () => UseEventProps | null): UseEvent => {
|
||||||
const props = createMemo(propsProvider);
|
const props = createMemo(propsProvider);
|
||||||
const query = createQuery(
|
const query = createQuery(
|
||||||
() => ['useEvent', props()] as const,
|
() => ['useEvent', props()] as const,
|
||||||
({ queryKey, signal }) => {
|
({ queryKey, signal }) => {
|
||||||
const [, currentProps] = queryKey;
|
const [, currentProps] = queryKey;
|
||||||
|
if (currentProps == null) return undefined;
|
||||||
return timeout(15000, `useEvent: ${currentProps.eventId}`)(exec(currentProps, signal));
|
return timeout(15000, `useEvent: ${currentProps.eventId}`)(exec(currentProps, signal));
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { createMemo } from 'solid-js';
|
||||||
import useCachedEvents from '@/clients/useCachedEvents';
|
import useCachedEvents from '@/clients/useCachedEvents';
|
||||||
|
|
||||||
type UseFollowingsProps = {
|
type UseFollowingsProps = {
|
||||||
@@ -12,8 +13,11 @@ type Following = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useFollowings = (propsProvider: () => UseFollowingsProps) => {
|
const useFollowings = (propsProvider: () => UseFollowingsProps) => {
|
||||||
|
const props = createMemo(propsProvider);
|
||||||
const query = useCachedEvents(() => {
|
const query = useCachedEvents(() => {
|
||||||
const { relayUrls, pubkey } = propsProvider();
|
const currentProps = props();
|
||||||
|
if (currentProps == null) return null;
|
||||||
|
const { relayUrls, pubkey } = currentProps;
|
||||||
return {
|
return {
|
||||||
relayUrls,
|
relayUrls,
|
||||||
filters: [
|
filters: [
|
||||||
|
|||||||
@@ -43,12 +43,14 @@ const { exec } = useBatchedEvent<UseProfileProps>(() => ({
|
|||||||
extractKey: (event: NostrEvent): string => event.pubkey,
|
extractKey: (event: NostrEvent): string => event.pubkey,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useProfile = (propsProvider: () => UseProfileProps): UseProfile => {
|
const useProfile = (propsProvider: () => UseProfileProps | null): UseProfile => {
|
||||||
const props = createMemo(propsProvider);
|
const props = createMemo(propsProvider);
|
||||||
|
|
||||||
const query = createQuery(
|
const query = createQuery(
|
||||||
() => ['useProfile', props()] as const,
|
() => ['useProfile', props()] as const,
|
||||||
({ queryKey, signal }) => {
|
({ queryKey, signal }) => {
|
||||||
const [, currentProps] = queryKey;
|
const [, currentProps] = queryKey;
|
||||||
|
if (currentProps == null) return null;
|
||||||
// TODO timeoutと同時にsignalでキャンセルするようにしたい
|
// TODO timeoutと同時にsignalでキャンセルするようにしたい
|
||||||
return timeout(15000, `useProfile: ${currentProps.pubkey}`)(exec(currentProps, signal));
|
return timeout(15000, `useProfile: ${currentProps.pubkey}`)(exec(currentProps, signal));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,13 +6,28 @@ const [pubkey, setPubkey] = createSignal<string | undefined>(undefined);
|
|||||||
|
|
||||||
const usePubkey = (): Accessor<string | undefined> => {
|
const usePubkey = (): Accessor<string | undefined> => {
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (window.nostr != null && pubkey() == null && !asking) {
|
let count = 0;
|
||||||
asking = true;
|
const intervalId = setInterval(() => {
|
||||||
window.nostr
|
if (count >= 5) {
|
||||||
.getPublicKey()
|
clearInterval(intervalId);
|
||||||
.then((key) => setPubkey(key))
|
if (pubkey() == null && !asking) {
|
||||||
.catch((err) => console.error(`failed to obtain public key: ${err}`));
|
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
|
||||||
|
.getPublicKey()
|
||||||
|
.then((key) => setPubkey(key))
|
||||||
|
.catch((err) => console.error(`failed to obtain public key: ${err}`));
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
return pubkey;
|
return pubkey;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const { exec } = useBatchedEvents<UseReactionsProps>(() => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const useReactions = (propsProvider: () => UseReactionsProps): UseReactions => {
|
const useReactions = (propsProvider: () => UseReactionsProps | null): UseReactions => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const props = createMemo(propsProvider);
|
const props = createMemo(propsProvider);
|
||||||
const queryKey = createMemo(() => ['useReactions', props()] as const);
|
const queryKey = createMemo(() => ['useReactions', props()] as const);
|
||||||
@@ -38,6 +38,7 @@ const useReactions = (propsProvider: () => UseReactionsProps): UseReactions => {
|
|||||||
() => queryKey(),
|
() => queryKey(),
|
||||||
({ queryKey: currentQueryKey, signal }) => {
|
({ queryKey: currentQueryKey, signal }) => {
|
||||||
const [, currentProps] = currentQueryKey;
|
const [, currentProps] = currentQueryKey;
|
||||||
|
if (currentProps == null) return () => ({ events: [], completed: false });
|
||||||
return timeout(15000, `useReactions: ${currentProps.eventId}`)(exec(currentProps, signal));
|
return timeout(15000, `useReactions: ${currentProps.eventId}`)(exec(currentProps, signal));
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
0
src/components/Hello.tsx
Normal file
0
src/components/Hello.tsx
Normal file
@@ -13,6 +13,7 @@ import useSubscription from '@/clients/useSubscription';
|
|||||||
import useFollowings from '@/clients/useFollowings';
|
import useFollowings from '@/clients/useFollowings';
|
||||||
import usePubkey from '@/clients/usePubkey';
|
import usePubkey from '@/clients/usePubkey';
|
||||||
import useShortcutKeys from '@/hooks/useShortcutKeys';
|
import useShortcutKeys from '@/hooks/useShortcutKeys';
|
||||||
|
import ensureNonNull from '@/hooks/ensureNonNull';
|
||||||
|
|
||||||
useShortcutKeys({
|
useShortcutKeys({
|
||||||
onShortcut: (s) => console.log(s),
|
onShortcut: (s) => console.log(s),
|
||||||
@@ -106,6 +107,7 @@ const Home: Component = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const japaneseRegex = /[あ-ん]/;
|
||||||
return (
|
return (
|
||||||
<div class="flex h-screen w-screen flex-row overflow-hidden">
|
<div class="flex h-screen w-screen flex-row overflow-hidden">
|
||||||
<SideBar postForm={() => <NotePostForm onPost={handlePost} />} />
|
<SideBar postForm={() => <NotePostForm onPost={handlePost} />} />
|
||||||
|
|||||||
14
src/utils/ensureNonNull.ts
Normal file
14
src/utils/ensureNonNull.ts
Normal 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
7
src/utils/sleep.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const sleep = (ms: number): Promise<void> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, ms);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default sleep;
|
||||||
Reference in New Issue
Block a user