mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-27 03:56:44 +02:00
cleanup core classes
This commit is contained in:
93
src/classes/chunked-request.ts
Normal file
93
src/classes/chunked-request.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { Debugger } from "debug";
|
||||||
|
import { Filter, NostrEvent, matchFilters } from "nostr-tools";
|
||||||
|
import _throttle from "lodash.throttle";
|
||||||
|
|
||||||
|
import NostrRequest from "./nostr-request";
|
||||||
|
import Subject from "./subject";
|
||||||
|
import { logger } from "../helpers/debug";
|
||||||
|
import EventStore from "./event-store";
|
||||||
|
import deleteEventService from "../services/delete-events";
|
||||||
|
import { mergeFilter } from "../helpers/nostr/filter";
|
||||||
|
import { isATag, isETag } from "../types/nostr-event";
|
||||||
|
|
||||||
|
const DEFAULT_CHUNK_SIZE = 100;
|
||||||
|
|
||||||
|
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
||||||
|
|
||||||
|
export default class ChunkedRequest {
|
||||||
|
relay: string;
|
||||||
|
filters: Filter[];
|
||||||
|
chunkSize = DEFAULT_CHUNK_SIZE;
|
||||||
|
private log: Debugger;
|
||||||
|
private subs: ZenObservable.Subscription[] = [];
|
||||||
|
|
||||||
|
loading = false;
|
||||||
|
events: EventStore;
|
||||||
|
/** set to true when the next chunk produces 0 events */
|
||||||
|
complete = false;
|
||||||
|
|
||||||
|
onChunkFinish = new Subject<number>();
|
||||||
|
|
||||||
|
constructor(relay: string, filters: Filter[], log?: Debugger) {
|
||||||
|
this.relay = relay;
|
||||||
|
this.filters = filters;
|
||||||
|
|
||||||
|
this.log = log || logger.extend(relay);
|
||||||
|
this.events = new EventStore(relay);
|
||||||
|
|
||||||
|
// TODO: find a better place for this
|
||||||
|
this.subs.push(deleteEventService.stream.subscribe((e) => this.handleDeleteEvent(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNextChunk() {
|
||||||
|
this.loading = true;
|
||||||
|
let filters: Filter[] = mergeFilter(this.filters, { limit: this.chunkSize });
|
||||||
|
let oldestEvent = this.getLastEvent();
|
||||||
|
if (oldestEvent) {
|
||||||
|
filters = mergeFilter(filters, { until: oldestEvent.created_at - 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = new NostrRequest([this.relay]);
|
||||||
|
|
||||||
|
let gotEvents = 0;
|
||||||
|
request.onEvent.subscribe((e) => {
|
||||||
|
this.handleEvent(e);
|
||||||
|
gotEvents++;
|
||||||
|
});
|
||||||
|
request.onComplete.then(() => {
|
||||||
|
this.loading = false;
|
||||||
|
if (gotEvents === 0) {
|
||||||
|
this.complete = true;
|
||||||
|
this.log("Complete");
|
||||||
|
} else this.log(`Got ${gotEvents} events`);
|
||||||
|
this.onChunkFinish.next(gotEvents);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.start(filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleEvent(event: NostrEvent) {
|
||||||
|
if (!matchFilters(this.filters, event)) return;
|
||||||
|
return this.events.addEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleDeleteEvent(deleteEvent: NostrEvent) {
|
||||||
|
const cord = deleteEvent.tags.find(isATag)?.[1];
|
||||||
|
const eventId = deleteEvent.tags.find(isETag)?.[1];
|
||||||
|
|
||||||
|
if (cord) this.events.deleteEvent(cord);
|
||||||
|
if (eventId) this.events.deleteEvent(eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
for (const sub of this.subs) sub.unsubscribe();
|
||||||
|
this.subs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getFirstEvent(nth = 0, eventFilter?: EventFilter) {
|
||||||
|
return this.events.getFirstEvent(nth, eventFilter);
|
||||||
|
}
|
||||||
|
getLastEvent(nth = 0, eventFilter?: EventFilter) {
|
||||||
|
return this.events.getLastEvent(nth, eventFilter);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import { Filter, NostrEvent } from "nostr-tools";
|
||||||
|
|
||||||
import { NostrEvent } from "../types/nostr-event";
|
|
||||||
import { NostrRequestFilter } from "../types/nostr-relay";
|
|
||||||
import relayPoolService from "../services/relay-pool";
|
import relayPoolService from "../services/relay-pool";
|
||||||
import Relay, { CountResponse, IncomingCount, IncomingEOSE, IncomingEvent } from "./relay";
|
import Relay, { CountResponse, IncomingCount, IncomingEOSE, IncomingEvent } from "./relay";
|
||||||
import createDefer from "./deferred";
|
import createDefer from "./deferred";
|
||||||
@@ -64,7 +63,7 @@ export default class NostrRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start(filter: NostrRequestFilter, type: "REQ" | "COUNT" = "REQ") {
|
start(filter: Filter | Filter[], type: "REQ" | "COUNT" = "REQ") {
|
||||||
if (this.state !== NostrRequest.IDLE) {
|
if (this.state !== NostrRequest.IDLE) {
|
||||||
throw new Error("cant restart a nostr request");
|
throw new Error("cant restart a nostr request");
|
||||||
}
|
}
|
||||||
|
@@ -1,110 +1,25 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { Debugger } from "debug";
|
import { Debugger } from "debug";
|
||||||
import { Filter, matchFilters } from "nostr-tools";
|
import { Filter, NostrEvent } from "nostr-tools";
|
||||||
import _throttle from "lodash.throttle";
|
import _throttle from "lodash.throttle";
|
||||||
|
|
||||||
import { NostrEvent, isATag, isETag } from "../types/nostr-event";
|
import { RelayQueryMap } from "../types/nostr-relay";
|
||||||
import { NostrRequestFilter, RelayQueryMap } from "../types/nostr-relay";
|
|
||||||
import NostrRequest from "./nostr-request";
|
|
||||||
import NostrMultiSubscription from "./nostr-multi-subscription";
|
import NostrMultiSubscription from "./nostr-multi-subscription";
|
||||||
import Subject, { PersistentSubject } from "./subject";
|
import { PersistentSubject } from "./subject";
|
||||||
import { logger } from "../helpers/debug";
|
import { logger } from "../helpers/debug";
|
||||||
import EventStore from "./event-store";
|
import EventStore from "./event-store";
|
||||||
import { isReplaceable } from "../helpers/nostr/event";
|
import { isReplaceable } from "../helpers/nostr/event";
|
||||||
import replaceableEventsService from "../services/replaceable-events";
|
import replaceableEventsService from "../services/replaceable-events";
|
||||||
import deleteEventService from "../services/delete-events";
|
import { mergeFilter, isFilterEqual, isQueryMapEqual, mapQueryMap, stringifyFilter } from "../helpers/nostr/filter";
|
||||||
import {
|
|
||||||
addQueryToFilter,
|
|
||||||
isFilterEqual,
|
|
||||||
isQueryMapEqual,
|
|
||||||
mapQueryMap,
|
|
||||||
stringifyFilter,
|
|
||||||
} from "../helpers/nostr/filter";
|
|
||||||
import { localRelay } from "../services/local-relay";
|
import { localRelay } from "../services/local-relay";
|
||||||
import { relayRequest } from "../helpers/relay";
|
import { relayRequest } from "../helpers/relay";
|
||||||
import SuperMap from "./super-map";
|
import SuperMap from "./super-map";
|
||||||
|
import ChunkedRequest from "./chunked-request";
|
||||||
|
|
||||||
const BLOCK_SIZE = 100;
|
const BLOCK_SIZE = 100;
|
||||||
|
|
||||||
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
||||||
|
|
||||||
export class RelayBlockLoader {
|
|
||||||
relay: string;
|
|
||||||
filter: NostrRequestFilter;
|
|
||||||
blockSize = BLOCK_SIZE;
|
|
||||||
private log: Debugger;
|
|
||||||
private subs: ZenObservable.Subscription[] = [];
|
|
||||||
|
|
||||||
loading = false;
|
|
||||||
events: EventStore;
|
|
||||||
/** set to true when the next block produces 0 events */
|
|
||||||
complete = false;
|
|
||||||
|
|
||||||
onBlockFinish = new Subject<number>();
|
|
||||||
|
|
||||||
constructor(relay: string, filter: NostrRequestFilter, log?: Debugger) {
|
|
||||||
this.relay = relay;
|
|
||||||
this.filter = filter;
|
|
||||||
|
|
||||||
this.log = log || logger.extend(relay);
|
|
||||||
this.events = new EventStore(relay);
|
|
||||||
|
|
||||||
this.subs.push(deleteEventService.stream.subscribe((e) => this.handleDeleteEvent(e)));
|
|
||||||
}
|
|
||||||
|
|
||||||
loadNextBlock() {
|
|
||||||
this.loading = true;
|
|
||||||
let filter: NostrRequestFilter = addQueryToFilter(this.filter, { limit: this.blockSize });
|
|
||||||
let oldestEvent = this.getLastEvent();
|
|
||||||
if (oldestEvent) {
|
|
||||||
filter = addQueryToFilter(filter, { until: oldestEvent.created_at - 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = new NostrRequest([this.relay]);
|
|
||||||
|
|
||||||
let gotEvents = 0;
|
|
||||||
request.onEvent.subscribe((e) => {
|
|
||||||
this.handleEvent(e);
|
|
||||||
gotEvents++;
|
|
||||||
});
|
|
||||||
request.onComplete.then(() => {
|
|
||||||
this.loading = false;
|
|
||||||
if (gotEvents === 0) {
|
|
||||||
this.complete = true;
|
|
||||||
this.log("Complete");
|
|
||||||
} else this.log(`Got ${gotEvents} events`);
|
|
||||||
this.onBlockFinish.next(gotEvents);
|
|
||||||
});
|
|
||||||
|
|
||||||
request.start(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleEvent(event: NostrEvent) {
|
|
||||||
if (!matchFilters(Array.isArray(this.filter) ? this.filter : [this.filter], event)) return;
|
|
||||||
return this.events.addEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleDeleteEvent(deleteEvent: NostrEvent) {
|
|
||||||
const cord = deleteEvent.tags.find(isATag)?.[1];
|
|
||||||
const eventId = deleteEvent.tags.find(isETag)?.[1];
|
|
||||||
|
|
||||||
if (cord) this.events.deleteEvent(cord);
|
|
||||||
if (eventId) this.events.deleteEvent(eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
for (const sub of this.subs) sub.unsubscribe();
|
|
||||||
this.subs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
getFirstEvent(nth = 0, eventFilter?: EventFilter) {
|
|
||||||
return this.events.getFirstEvent(nth, eventFilter);
|
|
||||||
}
|
|
||||||
getLastEvent(nth = 0, eventFilter?: EventFilter) {
|
|
||||||
return this.events.getLastEvent(nth, eventFilter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TimelineLoader {
|
export default class TimelineLoader {
|
||||||
cursor = dayjs().unix();
|
cursor = dayjs().unix();
|
||||||
queryMap: RelayQueryMap = {};
|
queryMap: RelayQueryMap = {};
|
||||||
@@ -121,7 +36,7 @@ export default class TimelineLoader {
|
|||||||
private log: Debugger;
|
private log: Debugger;
|
||||||
private subscription: NostrMultiSubscription;
|
private subscription: NostrMultiSubscription;
|
||||||
|
|
||||||
private blockLoaders = new Map<string, RelayBlockLoader>();
|
private chunkLoaders = new Map<string, ChunkedRequest>();
|
||||||
|
|
||||||
constructor(name: string) {
|
constructor(name: string) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@@ -152,20 +67,23 @@ export default class TimelineLoader {
|
|||||||
this.events.addEvent(event);
|
this.events.addEvent(event);
|
||||||
if (cache) localRelay.publish(event);
|
if (cache) localRelay.publish(event);
|
||||||
}
|
}
|
||||||
|
private handleChunkFinished() {
|
||||||
private blockLoaderSubs = new SuperMap<RelayBlockLoader, ZenObservable.Subscription[]>(() => []);
|
this.updateLoading();
|
||||||
private connectToBlockLoader(loader: RelayBlockLoader) {
|
this.updateComplete();
|
||||||
this.events.connect(loader.events);
|
|
||||||
const subs = this.blockLoaderSubs.get(loader);
|
|
||||||
subs.push(loader.onBlockFinish.subscribe(this.updateLoading.bind(this)));
|
|
||||||
subs.push(loader.onBlockFinish.subscribe(this.updateComplete.bind(this)));
|
|
||||||
}
|
}
|
||||||
private disconnectToBlockLoader(loader: RelayBlockLoader) {
|
|
||||||
|
private chunkLoaderSubs = new SuperMap<ChunkedRequest, ZenObservable.Subscription[]>(() => []);
|
||||||
|
private connectToChunkLoader(loader: ChunkedRequest) {
|
||||||
|
this.events.connect(loader.events);
|
||||||
|
const subs = this.chunkLoaderSubs.get(loader);
|
||||||
|
subs.push(loader.onChunkFinish.subscribe(this.handleChunkFinished.bind(this)));
|
||||||
|
}
|
||||||
|
private disconnectToChunkLoader(loader: ChunkedRequest) {
|
||||||
loader.cleanup();
|
loader.cleanup();
|
||||||
this.events.disconnect(loader.events);
|
this.events.disconnect(loader.events);
|
||||||
const subs = this.blockLoaderSubs.get(loader);
|
const subs = this.chunkLoaderSubs.get(loader);
|
||||||
for (const sub of subs) sub.unsubscribe();
|
for (const sub of subs) sub.unsubscribe();
|
||||||
this.blockLoaderSubs.delete(loader);
|
this.chunkLoaderSubs.delete(loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadQueriesFromCache(queryMap: RelayQueryMap) {
|
private loadQueriesFromCache(queryMap: RelayQueryMap) {
|
||||||
@@ -189,26 +107,26 @@ export default class TimelineLoader {
|
|||||||
|
|
||||||
// remove relays
|
// remove relays
|
||||||
for (const relay of Object.keys(this.queryMap)) {
|
for (const relay of Object.keys(this.queryMap)) {
|
||||||
const loader = this.blockLoaders.get(relay);
|
const loader = this.chunkLoaders.get(relay);
|
||||||
if (!loader) continue;
|
if (!loader) continue;
|
||||||
if (!queryMap[relay]) {
|
if (!queryMap[relay]) {
|
||||||
this.disconnectToBlockLoader(loader);
|
this.disconnectToChunkLoader(loader);
|
||||||
this.blockLoaders.delete(relay);
|
this.chunkLoaders.delete(relay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [relay, filter] of Object.entries(queryMap)) {
|
for (const [relay, filter] of Object.entries(queryMap)) {
|
||||||
// remove outdated loaders
|
// remove outdated loaders
|
||||||
if (this.queryMap[relay] && !isFilterEqual(this.queryMap[relay], filter)) {
|
if (this.queryMap[relay] && !isFilterEqual(this.queryMap[relay], filter)) {
|
||||||
const old = this.blockLoaders.get(relay)!;
|
const old = this.chunkLoaders.get(relay)!;
|
||||||
this.disconnectToBlockLoader(old);
|
this.disconnectToChunkLoader(old);
|
||||||
this.blockLoaders.delete(relay);
|
this.chunkLoaders.delete(relay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.blockLoaders.has(relay)) {
|
if (!this.chunkLoaders.has(relay)) {
|
||||||
const loader = new RelayBlockLoader(relay, filter, this.log.extend(relay));
|
const loader = new ChunkedRequest(relay, Array.isArray(filter) ? filter : [filter], this.log.extend(relay));
|
||||||
this.blockLoaders.set(relay, loader);
|
this.chunkLoaders.set(relay, loader);
|
||||||
this.connectToBlockLoader(loader);
|
this.connectToChunkLoader(loader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +137,10 @@ export default class TimelineLoader {
|
|||||||
|
|
||||||
// update the subscription query map and add limit
|
// update the subscription query map and add limit
|
||||||
this.subscription.setQueryMap(
|
this.subscription.setQueryMap(
|
||||||
mapQueryMap(this.queryMap, (filter) => addQueryToFilter(filter, { limit: BLOCK_SIZE / 2 })),
|
mapQueryMap(this.queryMap, (filter) => mergeFilter(filter, { limit: BLOCK_SIZE / 2 })),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.triggerBlockLoads();
|
this.triggerChunkLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
setEventFilter(filter?: EventFilter) {
|
setEventFilter(filter?: EventFilter) {
|
||||||
@@ -231,33 +149,33 @@ export default class TimelineLoader {
|
|||||||
}
|
}
|
||||||
setCursor(cursor: number) {
|
setCursor(cursor: number) {
|
||||||
this.cursor = cursor;
|
this.cursor = cursor;
|
||||||
this.triggerBlockLoads();
|
this.triggerChunkLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerBlockLoads() {
|
triggerChunkLoad() {
|
||||||
let triggeredLoad = false;
|
let triggeredLoad = false;
|
||||||
for (const [relay, loader] of this.blockLoaders) {
|
for (const [relay, loader] of this.chunkLoaders) {
|
||||||
if (loader.complete || loader.loading) continue;
|
if (loader.complete || loader.loading) continue;
|
||||||
const event = loader.getLastEvent(this.loadNextBlockBuffer, this.eventFilter);
|
const event = loader.getLastEvent(this.loadNextBlockBuffer, this.eventFilter);
|
||||||
if (!event || event.created_at >= this.cursor) {
|
if (!event || event.created_at >= this.cursor) {
|
||||||
loader.loadNextBlock();
|
loader.loadNextChunk();
|
||||||
triggeredLoad = true;
|
triggeredLoad = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (triggeredLoad) this.updateLoading();
|
if (triggeredLoad) this.updateLoading();
|
||||||
}
|
}
|
||||||
loadNextBlock() {
|
loadAllNextChunks() {
|
||||||
let triggeredLoad = false;
|
let triggeredLoad = false;
|
||||||
for (const [relay, loader] of this.blockLoaders) {
|
for (const [relay, loader] of this.chunkLoaders) {
|
||||||
if (loader.complete || loader.loading) continue;
|
if (loader.complete || loader.loading) continue;
|
||||||
loader.loadNextBlock();
|
loader.loadNextChunk();
|
||||||
triggeredLoad = true;
|
triggeredLoad = true;
|
||||||
}
|
}
|
||||||
if (triggeredLoad) this.updateLoading();
|
if (triggeredLoad) this.updateLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateLoading() {
|
private updateLoading() {
|
||||||
for (const [relay, loader] of this.blockLoaders) {
|
for (const [relay, loader] of this.chunkLoaders) {
|
||||||
if (loader.loading) {
|
if (loader.loading) {
|
||||||
if (!this.loading.value) {
|
if (!this.loading.value) {
|
||||||
this.loading.next(true);
|
this.loading.next(true);
|
||||||
@@ -268,7 +186,7 @@ export default class TimelineLoader {
|
|||||||
if (this.loading.value) this.loading.next(false);
|
if (this.loading.value) this.loading.next(false);
|
||||||
}
|
}
|
||||||
private updateComplete() {
|
private updateComplete() {
|
||||||
for (const [relay, loader] of this.blockLoaders) {
|
for (const [relay, loader] of this.chunkLoaders) {
|
||||||
if (!loader.complete) {
|
if (!loader.complete) {
|
||||||
this.complete.next(false);
|
this.complete.next(false);
|
||||||
return;
|
return;
|
||||||
@@ -290,8 +208,8 @@ export default class TimelineLoader {
|
|||||||
}
|
}
|
||||||
reset() {
|
reset() {
|
||||||
this.cursor = dayjs().unix();
|
this.cursor = dayjs().unix();
|
||||||
for (const [_, loader] of this.blockLoaders) this.disconnectToBlockLoader(loader);
|
for (const [_, loader] of this.chunkLoaders) this.disconnectToChunkLoader(loader);
|
||||||
this.blockLoaders.clear();
|
this.chunkLoaders.clear();
|
||||||
this.forgetEvents();
|
this.forgetEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,8 +217,8 @@ export default class TimelineLoader {
|
|||||||
cleanup() {
|
cleanup() {
|
||||||
this.close();
|
this.close();
|
||||||
|
|
||||||
for (const [_, loader] of this.blockLoaders) this.disconnectToBlockLoader(loader);
|
for (const [_, loader] of this.chunkLoaders) this.disconnectToChunkLoader(loader);
|
||||||
this.blockLoaders.clear();
|
this.chunkLoaders.clear();
|
||||||
|
|
||||||
this.events.cleanup();
|
this.events.cleanup();
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,14 @@ export default function TimelineActionAndStatus({ timeline }: { timeline: Timeli
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={() => timeline.loadNextBlock()} flexShrink={0} size="lg" mx="auto" colorScheme="primary" my="4">
|
<Button
|
||||||
|
onClick={() => timeline.loadAllNextChunks()}
|
||||||
|
flexShrink={0}
|
||||||
|
size="lg"
|
||||||
|
mx="auto"
|
||||||
|
colorScheme="primary"
|
||||||
|
my="4"
|
||||||
|
>
|
||||||
Load More
|
Load More
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
@@ -4,7 +4,7 @@ import { useDnsIdentity } from "../../hooks/use-dns-identity";
|
|||||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||||
import { VerificationFailed, VerificationMissing, VerifiedIcon } from "../icons";
|
import { VerificationFailed, VerificationMissing, VerifiedIcon } from "../icons";
|
||||||
|
|
||||||
export const UserDnsIdentityIcon = ({ pubkey, onlyIcon }: { pubkey: string; onlyIcon?: boolean }) => {
|
export function UserDnsIdentityIcon({ pubkey, onlyIcon }: { pubkey: string; onlyIcon?: boolean }) {
|
||||||
const metadata = useUserMetadata(pubkey);
|
const metadata = useUserMetadata(pubkey);
|
||||||
const identity = useDnsIdentity(metadata?.nip05);
|
const identity = useDnsIdentity(metadata?.nip05);
|
||||||
|
|
||||||
@@ -32,4 +32,6 @@ export const UserDnsIdentityIcon = ({ pubkey, onlyIcon }: { pubkey: string; only
|
|||||||
{metadata.nip05.startsWith("_@") ? metadata.nip05.substr(2) : metadata.nip05} {renderIcon()}
|
{metadata.nip05.startsWith("_@") ? metadata.nip05.substr(2) : metadata.nip05} {renderIcon()}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export default UserDnsIdentityIcon;
|
||||||
|
@@ -1,19 +1,21 @@
|
|||||||
import stringify from "json-stringify-deterministic";
|
import stringify from "json-stringify-deterministic";
|
||||||
import { NostrRequestFilter, RelayQueryMap } from "../../types/nostr-relay";
|
import { RelayQueryMap } from "../../types/nostr-relay";
|
||||||
import { Filter } from "nostr-tools";
|
import { Filter } from "nostr-tools";
|
||||||
import { safeRelayUrls } from "../relay";
|
import { safeRelayUrls } from "../relay";
|
||||||
|
|
||||||
export function addQueryToFilter(filter: NostrRequestFilter, query: Filter) {
|
export function mergeFilter(filter: Filter, query: Filter): Filter;
|
||||||
|
export function mergeFilter(filter: Filter[], query: Filter): Filter[];
|
||||||
|
export function mergeFilter(filter: Filter | Filter[], query: Filter) {
|
||||||
if (Array.isArray(filter)) {
|
if (Array.isArray(filter)) {
|
||||||
return filter.map((f) => ({ ...f, ...query }));
|
return filter.map((f) => ({ ...f, ...query }));
|
||||||
}
|
}
|
||||||
return { ...filter, ...query };
|
return { ...filter, ...query };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringifyFilter(filter: NostrRequestFilter) {
|
export function stringifyFilter(filter: Filter | Filter[]) {
|
||||||
return stringify(filter);
|
return stringify(filter);
|
||||||
}
|
}
|
||||||
export function isFilterEqual(a: NostrRequestFilter, b: NostrRequestFilter) {
|
export function isFilterEqual(a: Filter | Filter[], b: Filter | Filter[]) {
|
||||||
return stringifyFilter(a) === stringifyFilter(b);
|
return stringifyFilter(a) === stringifyFilter(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,14 +23,14 @@ export function isQueryMapEqual(a: RelayQueryMap, b: RelayQueryMap) {
|
|||||||
return stringify(a) === stringify(b);
|
return stringify(a) === stringify(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapQueryMap(queryMap: RelayQueryMap, fn: (filter: NostrRequestFilter) => NostrRequestFilter) {
|
export function mapQueryMap(queryMap: RelayQueryMap, fn: (filters: Filter[]) => Filter[]) {
|
||||||
const newMap: RelayQueryMap = {};
|
const newMap: RelayQueryMap = {};
|
||||||
for (const [relay, filter] of Object.entries(queryMap)) newMap[relay] = fn(filter);
|
for (const [relay, filters] of Object.entries(queryMap)) newMap[relay] = fn(filters);
|
||||||
return newMap;
|
return newMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSimpleQueryMap(relays: Iterable<string>, filter: NostrRequestFilter) {
|
export function createSimpleQueryMap(relays: Iterable<string>, filters: Filter | Filter[]) {
|
||||||
const map: RelayQueryMap = {};
|
const map: RelayQueryMap = {};
|
||||||
for (const relay of safeRelayUrls(relays)) map[relay] = filter;
|
for (const relay of safeRelayUrls(relays)) map[relay] = Array.isArray(filters) ? filters : [filters];
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ export function useTimelineCurserIntersectionCallback(timeline: TimelineLoader)
|
|||||||
// if the cursor is set too far ahead and the last block did not overlap with the cursor
|
// if the cursor is set too far ahead and the last block did not overlap with the cursor
|
||||||
// we need to keep loading blocks until the timeline is complete or the blocks pass the cursor
|
// we need to keep loading blocks until the timeline is complete or the blocks pass the cursor
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
timeline.triggerBlockLoads();
|
timeline.triggerChunkLoad();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return useIntersectionMapCallback(
|
return useIntersectionMapCallback(
|
||||||
@@ -25,7 +25,7 @@ export function useTimelineCurserIntersectionCallback(timeline: TimelineLoader)
|
|||||||
|
|
||||||
if (oldestEvent) {
|
if (oldestEvent) {
|
||||||
timeline.setCursor(oldestEvent.created_at);
|
timeline.setCursor(oldestEvent.created_at);
|
||||||
timeline.triggerBlockLoads();
|
timeline.triggerChunkLoad();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[timeline],
|
[timeline],
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useUnmount } from "react-use";
|
import { useUnmount } from "react-use";
|
||||||
|
import { NostrEvent } from "nostr-tools";
|
||||||
|
|
||||||
import { NostrRequestFilter } from "../types/nostr-relay";
|
import { NostrRequestFilter } from "../types/nostr-relay";
|
||||||
import timelineCacheService from "../services/timeline-cache";
|
import timelineCacheService from "../services/timeline-cache";
|
||||||
import { EventFilter } from "../classes/timeline-loader";
|
import { EventFilter } from "../classes/timeline-loader";
|
||||||
import { NostrEvent } from "../types/nostr-event";
|
|
||||||
import { createSimpleQueryMap } from "../helpers/nostr/filter";
|
import { createSimpleQueryMap } from "../helpers/nostr/filter";
|
||||||
|
|
||||||
type Options = {
|
type Options = {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import _throttle from "lodash/throttle";
|
|
||||||
import { Filter, NostrEvent } from "nostr-tools";
|
import { Filter, NostrEvent } from "nostr-tools";
|
||||||
|
import _throttle from "lodash.throttle";
|
||||||
|
|
||||||
import SuperMap from "../classes/super-map";
|
import SuperMap from "../classes/super-map";
|
||||||
import { logger } from "../helpers/debug";
|
import { logger } from "../helpers/debug";
|
||||||
|
@@ -5,4 +5,4 @@ export type NostrQuery = Filter;
|
|||||||
|
|
||||||
export type NostrRequestFilter = Filter | Filter[];
|
export type NostrRequestFilter = Filter | Filter[];
|
||||||
|
|
||||||
export type RelayQueryMap = Record<string, NostrRequestFilter>;
|
export type RelayQueryMap = Record<string, Filter[]>;
|
||||||
|
Reference in New Issue
Block a user