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 props = createMemo(propsProvider);
|
||||
|
||||
return useBatch<TaskArgs, NostrEvent>(() => {
|
||||
return {
|
||||
interval: props().interval,
|
||||
executor: (tasks) => {
|
||||
const { generateKey, mergeFilters, extractKey } = props();
|
||||
// TODO relayUrlsを考慮する
|
||||
const [config] = useConfig();
|
||||
return useBatch<TaskArgs, NostrEvent>(() => ({
|
||||
interval: props().interval,
|
||||
executor: (tasks) => {
|
||||
const { generateKey, mergeFilters, extractKey } = props();
|
||||
// TODO relayUrlsを考慮する
|
||||
const [config] = useConfig();
|
||||
|
||||
const keyTaskMap = new Map<string | number, Task<TaskArgs, NostrEvent>>(
|
||||
tasks.map((task) => [generateKey(task.args), task]),
|
||||
);
|
||||
const filters = mergeFilters(tasks.map((task) => task.args));
|
||||
const keyTaskMap = new Map<string | number, Task<TaskArgs, NostrEvent>>(
|
||||
tasks.map((task) => [generateKey(task.args), task]),
|
||||
);
|
||||
const filters = mergeFilters(tasks.map((task) => task.args));
|
||||
|
||||
useSubscription(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters,
|
||||
continuous: false,
|
||||
onEvent: (event: NostrEvent) => {
|
||||
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'));
|
||||
});
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
});
|
||||
useSubscription(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters,
|
||||
continuous: false,
|
||||
onEvent: (event: NostrEvent) => {
|
||||
const key = extractKey(event);
|
||||
if (key == null) return;
|
||||
const task = keyTaskMap.get(key);
|
||||
if (task == null) return;
|
||||
task.resolve(event);
|
||||
},
|
||||
onEOSE: () => {
|
||||
tasks.forEach((task) => {
|
||||
task.reject(new Error(`NotFound: ${JSON.stringify(filters)}`));
|
||||
});
|
||||
},
|
||||
}));
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
export default useBatchedEvent;
|
||||
|
||||
@@ -35,64 +35,62 @@ const addEvent =
|
||||
const useBatchedEvents = <TaskArgs>(propsProvider: () => UseBatchedEventsProps<TaskArgs>) => {
|
||||
const props = createMemo(propsProvider);
|
||||
|
||||
return useBatch<TaskArgs, Accessor<BatchedEvents>>(() => {
|
||||
return {
|
||||
interval: props().interval,
|
||||
executor: (tasks) => {
|
||||
const { generateKey, mergeFilters, extractKey } = props();
|
||||
// TODO relayUrlsを考慮する
|
||||
const [config] = useConfig();
|
||||
return useBatch<TaskArgs, Accessor<BatchedEvents>>(() => ({
|
||||
interval: props().interval,
|
||||
executor: (tasks) => {
|
||||
const { generateKey, mergeFilters, extractKey } = props();
|
||||
// TODO relayUrlsを考慮する
|
||||
const [config] = useConfig();
|
||||
|
||||
const keyTaskMap = new Map<string | number, Task<TaskArgs, Accessor<BatchedEvents>>>(
|
||||
tasks.map((task) => [generateKey(task.args), task]),
|
||||
);
|
||||
const filters = mergeFilters(tasks.map((task) => task.args));
|
||||
const keyEventSignalsMap = new Map<string | number, Signal<BatchedEvents>>();
|
||||
const keyTaskMap = new Map<string | number, Task<TaskArgs, Accessor<BatchedEvents>>>(
|
||||
tasks.map((task) => [generateKey(task.args), task]),
|
||||
);
|
||||
const filters = mergeFilters(tasks.map((task) => task.args));
|
||||
const keyEventSignalsMap = new Map<string | number, Signal<BatchedEvents>>();
|
||||
|
||||
const getSignalForKey = (key: string | number): Signal<BatchedEvents> => {
|
||||
const eventsSignal =
|
||||
keyEventSignalsMap.get(key) ??
|
||||
createSignal<BatchedEvents>({
|
||||
events: [],
|
||||
completed: false,
|
||||
});
|
||||
keyEventSignalsMap.set(key, eventsSignal);
|
||||
return eventsSignal;
|
||||
};
|
||||
const didReceivedEventsForKey = (key: string | number): boolean =>
|
||||
keyEventSignalsMap.has(key);
|
||||
const getSignalForKey = (key: string | number): Signal<BatchedEvents> => {
|
||||
const eventsSignal =
|
||||
keyEventSignalsMap.get(key) ??
|
||||
createSignal<BatchedEvents>({
|
||||
events: [],
|
||||
completed: false,
|
||||
});
|
||||
keyEventSignalsMap.set(key, eventsSignal);
|
||||
return eventsSignal;
|
||||
};
|
||||
const didReceivedEventsForKey = (key: string | number): boolean =>
|
||||
keyEventSignalsMap.has(key);
|
||||
|
||||
useSubscription(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters,
|
||||
continuous: false,
|
||||
onEvent: (event: NostrEvent) => {
|
||||
const key = extractKey(event);
|
||||
if (key == null) return;
|
||||
const task = keyTaskMap.get(key);
|
||||
if (task == null) return;
|
||||
useSubscription(() => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters,
|
||||
continuous: false,
|
||||
onEvent: (event: NostrEvent) => {
|
||||
const key = extractKey(event);
|
||||
if (key == null) return;
|
||||
const task = keyTaskMap.get(key);
|
||||
if (task == null) return;
|
||||
|
||||
const [events, setEvents] = getSignalForKey(key);
|
||||
const [events, setEvents] = getSignalForKey(key);
|
||||
|
||||
setEvents(addEvent(event));
|
||||
setEvents(addEvent(event));
|
||||
|
||||
task.resolve(events);
|
||||
},
|
||||
onEOSE: () => {
|
||||
tasks.forEach((task) => {
|
||||
const key = generateKey(task.args);
|
||||
if (didReceivedEventsForKey(key)) {
|
||||
const [, setEvents] = getSignalForKey(key);
|
||||
setEvents(completeBatchedEvents);
|
||||
} else {
|
||||
task.resolve(emptyBatchedEvents);
|
||||
}
|
||||
});
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
});
|
||||
task.resolve(events);
|
||||
},
|
||||
onEOSE: () => {
|
||||
tasks.forEach((task) => {
|
||||
const key = generateKey(task.args);
|
||||
if (didReceivedEventsForKey(key)) {
|
||||
const [, setEvents] = getSignalForKey(key);
|
||||
setEvents(completeBatchedEvents);
|
||||
} else {
|
||||
task.resolve(emptyBatchedEvents);
|
||||
}
|
||||
});
|
||||
},
|
||||
}));
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
export default useBatchedEvents;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -6,13 +6,28 @@ const [pubkey, setPubkey] = createSignal<string | undefined>(undefined);
|
||||
|
||||
const usePubkey = (): Accessor<string | undefined> => {
|
||||
onMount(() => {
|
||||
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}`));
|
||||
}
|
||||
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
|
||||
.getPublicKey()
|
||||
.then((key) => setPubkey(key))
|
||||
.catch((err) => console.error(`failed to obtain public key: ${err}`));
|
||||
}
|
||||
count += 1;
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
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 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
0
src/components/Hello.tsx
Normal 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} />} />
|
||||
|
||||
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