You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
87 lines
2.7 KiB
87 lines
2.7 KiB
import type { TSubRequestFilter } from '@/types' |
|
import type { Event, Filter } from 'nostr-tools' |
|
import type { FeedRuntimeLoader, FeedRuntimeLoadResult } from './runtime' |
|
|
|
export type FeedLoaderSubRequest = { |
|
urls: string[] |
|
filter: Filter |
|
} |
|
|
|
export type FeedEventsClient = { |
|
fetchEvents: ( |
|
urls: string[], |
|
filter: Filter | Filter[], |
|
options?: { |
|
cache?: boolean |
|
globalTimeout?: number |
|
eoseTimeout?: number |
|
firstRelayResultGraceMs?: number | false |
|
} |
|
) => Promise<Event[]> |
|
getTimelineDiskSnapshotEvents?: (subRequests: { urls: string[]; filter: TSubRequestFilter }[]) => Promise<Event[]> |
|
} |
|
|
|
export type FetchEventsFeedLoaderOptions = { |
|
subRequests: readonly FeedLoaderSubRequest[] |
|
cache?: boolean |
|
hydrateFromDisk?: boolean |
|
globalTimeout?: number |
|
eoseTimeout?: number |
|
firstRelayResultGraceMs?: number | false |
|
} |
|
|
|
function filterWithCursor(filter: Filter, cursor: number | undefined): Filter { |
|
if (cursor === undefined) return filter |
|
const currentUntil = typeof filter.until === 'number' ? filter.until : cursor |
|
return { |
|
...filter, |
|
until: Math.min(currentUntil, cursor) |
|
} |
|
} |
|
|
|
export function applyFeedCursorToRequests( |
|
subRequests: readonly FeedLoaderSubRequest[], |
|
cursor: number | undefined |
|
): FeedLoaderSubRequest[] { |
|
return subRequests.map((request) => ({ |
|
...request, |
|
filter: filterWithCursor(request.filter, cursor) |
|
})) |
|
} |
|
|
|
export function createFetchEventsFeedRuntimeLoader( |
|
client: FeedEventsClient, |
|
options: FetchEventsFeedLoaderOptions |
|
): FeedRuntimeLoader { |
|
return async ({ page, cursor, signal }): Promise<FeedRuntimeLoadResult> => { |
|
const requests = applyFeedCursorToRequests(options.subRequests, page === 'load-more' ? cursor : undefined) |
|
const cacheEvents = |
|
page !== 'load-more' && options.hydrateFromDisk && client.getTimelineDiskSnapshotEvents |
|
? await client.getTimelineDiskSnapshotEvents( |
|
requests as Array<{ urls: string[]; filter: TSubRequestFilter }> |
|
) |
|
: undefined |
|
|
|
if (signal.aborted) return { cacheEvents, cacheStale: true, relayEvents: [] } |
|
|
|
const batches = await Promise.all( |
|
requests.map(({ urls, filter }) => |
|
client.fetchEvents(urls, filter as Filter, { |
|
cache: options.cache, |
|
globalTimeout: options.globalTimeout, |
|
eoseTimeout: options.eoseTimeout, |
|
firstRelayResultGraceMs: options.firstRelayResultGraceMs |
|
}) |
|
) |
|
) |
|
const byId = new Map<string, Event>() |
|
for (const event of batches.flat()) byId.set(event.id, event) |
|
const relayEvents = [...byId.values()] |
|
return { |
|
cacheEvents, |
|
cacheStale: cacheEvents ? true : undefined, |
|
relayEvents, |
|
hasMore: relayEvents.length > 0 |
|
} |
|
} |
|
}
|
|
|