mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-09 20:29:17 +02:00
handle delete events in timeline loader
properly cleanup timelines and set max 10 in cache
This commit is contained in:
parent
7c797ff05b
commit
11f98c8967
@ -16,8 +16,9 @@ export default class EventStore {
|
||||
return Array.from(this.events.values()).sort((a, b) => b.created_at - a.created_at);
|
||||
}
|
||||
|
||||
onEvent = new Subject<NostrEvent>();
|
||||
onClear = new Subject();
|
||||
onEvent = new Subject<NostrEvent>(undefined, false);
|
||||
onDelete = new Subject<string>(undefined, false);
|
||||
onClear = new Subject(undefined, false);
|
||||
|
||||
addEvent(event: NostrEvent) {
|
||||
const id = getEventUID(event);
|
||||
@ -30,17 +31,25 @@ export default class EventStore {
|
||||
getEvent(id: string) {
|
||||
return this.events.get(id);
|
||||
}
|
||||
deleteEvent(id: string) {
|
||||
if (this.events.has(id)) {
|
||||
this.events.delete(id);
|
||||
this.onDelete.next(id);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.events.clear();
|
||||
this.onClear.next(null);
|
||||
this.onClear.next(undefined);
|
||||
}
|
||||
|
||||
connect(other: EventStore) {
|
||||
other.onEvent.subscribe(this.addEvent, this);
|
||||
other.onDelete.subscribe(this.deleteEvent, this);
|
||||
}
|
||||
disconnect(other: EventStore) {
|
||||
other.onEvent.unsubscribe(this.addEvent, this);
|
||||
other.onDelete.unsubscribe(this.deleteEvent, this);
|
||||
}
|
||||
|
||||
getFirstEvent(nth = 0, filter?: EventFilter) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import dayjs from "dayjs";
|
||||
import { Debugger } from "debug";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { NostrEvent, isATag, isETag } from "../types/nostr-event";
|
||||
import { NostrQuery, NostrRequestFilter } from "../types/nostr-query";
|
||||
import { NostrRequest } from "./nostr-request";
|
||||
import { NostrMultiSubscription } from "./nostr-multi-subscription";
|
||||
@ -9,6 +9,7 @@ import { logger } from "../helpers/debug";
|
||||
import EventStore from "./event-store";
|
||||
import { isReplaceable } from "../helpers/nostr/events";
|
||||
import replaceableEventLoaderService from "../services/replaceable-event-requester";
|
||||
import deleteEventService from "../services/delete-events";
|
||||
|
||||
function addToQuery(filter: NostrRequestFilter, query: NostrQuery) {
|
||||
if (Array.isArray(filter)) {
|
||||
@ -40,6 +41,8 @@ export class RelayTimelineLoader {
|
||||
|
||||
this.log = log || logger.extend(relay);
|
||||
this.events = new EventStore(relay);
|
||||
|
||||
deleteEventService.stream.subscribe(this.handleDeleteEvent, this);
|
||||
}
|
||||
|
||||
loadNextBlock() {
|
||||
@ -70,10 +73,22 @@ export class RelayTimelineLoader {
|
||||
request.start(query);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
return this.events.addEvent(event);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
deleteEventService.stream.unsubscribe(this.handleDeleteEvent, this);
|
||||
}
|
||||
|
||||
getFirstEvent(nth = 0, filter?: EventFilter) {
|
||||
return this.events.getFirstEvent(nth, filter);
|
||||
}
|
||||
@ -111,7 +126,10 @@ export class TimelineLoader {
|
||||
|
||||
// update the timeline when there are new events
|
||||
this.events.onEvent.subscribe(this.updateTimeline, this);
|
||||
this.events.onDelete.subscribe(this.updateTimeline, this);
|
||||
this.events.onClear.subscribe(this.updateTimeline, this);
|
||||
|
||||
deleteEventService.stream.subscribe(this.handleDeleteEvent, this);
|
||||
}
|
||||
|
||||
private updateTimeline() {
|
||||
@ -126,6 +144,13 @@ export class TimelineLoader {
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
private createLoaders() {
|
||||
if (!this.query) return;
|
||||
@ -143,6 +168,7 @@ export class TimelineLoader {
|
||||
private removeLoaders(filter?: (loader: RelayTimelineLoader) => boolean) {
|
||||
for (const [relay, loader] of this.relayTimelineLoaders) {
|
||||
if (!filter || filter(loader)) {
|
||||
loader.cleanup();
|
||||
this.events.disconnect(loader.events);
|
||||
loader.onBlockFinish.unsubscribe(this.updateLoading, this);
|
||||
loader.onBlockFinish.unsubscribe(this.updateComplete, this);
|
||||
@ -166,18 +192,19 @@ export class TimelineLoader {
|
||||
setQuery(query: NostrRequestFilter) {
|
||||
if (JSON.stringify(this.query) === JSON.stringify(query)) return;
|
||||
|
||||
// remove all loaders
|
||||
this.removeLoaders();
|
||||
|
||||
this.log("set query", query);
|
||||
this.query = query;
|
||||
this.events.clear();
|
||||
this.timeline.next([]);
|
||||
|
||||
// forget all events
|
||||
this.forgetEvents();
|
||||
// create any missing loaders
|
||||
this.createLoaders();
|
||||
// update the complete flag
|
||||
this.updateComplete();
|
||||
|
||||
// update the subscription
|
||||
this.subscription.forgetEvents();
|
||||
// update the subscription with the new query
|
||||
this.subscription.setQuery(addToQuery(query, { limit: BLOCK_SIZE / 2 }));
|
||||
}
|
||||
setFilter(filter?: (event: NostrEvent) => boolean) {
|
||||
@ -242,10 +269,17 @@ export class TimelineLoader {
|
||||
|
||||
reset() {
|
||||
this.cursor = dayjs().unix();
|
||||
this.relayTimelineLoaders.clear();
|
||||
this.removeLoaders();
|
||||
this.forgetEvents();
|
||||
}
|
||||
|
||||
/** close the subscription and remove any event listeners for this timeline */
|
||||
cleanup() {
|
||||
this.close();
|
||||
this.removeLoaders();
|
||||
deleteEventService.stream.unsubscribe(this.handleDeleteEvent, this);
|
||||
}
|
||||
|
||||
// TODO: this is only needed because the current logic dose not remove events when the relay they where fetched from is removed
|
||||
/** @deprecated */
|
||||
forgetEvents() {
|
||||
|
@ -35,6 +35,7 @@ import { ExternalLinkIcon } from "../components/icons";
|
||||
import { getEventCoordinate, getEventUID, isReplaceable } from "../helpers/nostr/events";
|
||||
import NostrPublishAction from "../classes/nostr-publish-action";
|
||||
import { Tag } from "../types/nostr-event";
|
||||
import deleteEventService from "../services/delete-events";
|
||||
|
||||
type DeleteEventContextType = {
|
||||
isLoading: boolean;
|
||||
@ -94,6 +95,7 @@ export default function DeleteEventProvider({ children }: PropsWithChildren) {
|
||||
};
|
||||
const signed = await signingService.requestSignature(draft, account);
|
||||
const pub = new NostrPublishAction("Delete", writeRelays, signed);
|
||||
deleteEventService.handleEvent(signed);
|
||||
defer?.resolve();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||
|
24
src/services/delete-events.ts
Normal file
24
src/services/delete-events.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Kind } from "nostr-tools";
|
||||
import Subject from "../classes/subject";
|
||||
import { getEventUID } from "../helpers/nostr/events";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
|
||||
const deleteEventStream = new Subject<NostrEvent>();
|
||||
|
||||
function handleEvent(deleteEvent: NostrEvent) {
|
||||
if (deleteEvent.kind !== Kind.EventDeletion) return;
|
||||
deleteEventStream.next(deleteEvent);
|
||||
}
|
||||
|
||||
function doseMatch(deleteEvent: NostrEvent, event: NostrEvent) {
|
||||
const id = getEventUID(event);
|
||||
return deleteEvent.tags.some((t) => (t[0] === "a" || t[0] === "e") && t[1] === id);
|
||||
}
|
||||
|
||||
const deleteEventService = {
|
||||
stream: deleteEventStream,
|
||||
handleEvent,
|
||||
doseMatch,
|
||||
};
|
||||
|
||||
export default deleteEventService;
|
@ -13,7 +13,7 @@ import { getEventCoordinate } from "../helpers/nostr/events";
|
||||
type Pubkey = string;
|
||||
type Relay = string;
|
||||
|
||||
export function getReadableCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
export function getHumanReadableCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
return `${kind}:${nameOrPubkey(pubkey)}${d ? ":" + d : ""}`;
|
||||
}
|
||||
export function createCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { TimelineLoader } from "../classes/timeline-loader";
|
||||
|
||||
const MAX_CACHE = 4;
|
||||
const MAX_CACHE = 10;
|
||||
|
||||
class TimelineCacheService {
|
||||
private timelines = new Map<string, TimelineLoader>();
|
||||
@ -13,9 +13,18 @@ class TimelineCacheService {
|
||||
this.timelines.set(key, timeline);
|
||||
}
|
||||
|
||||
// add or move the timelines key to the top of the queue
|
||||
this.cacheQueue = this.cacheQueue.filter((p) => p !== key).concat(key);
|
||||
|
||||
// remove any timelines at the end of the queue
|
||||
while (this.cacheQueue.length > MAX_CACHE) {
|
||||
this.cacheQueue.shift();
|
||||
const deleteKey = this.cacheQueue.shift();
|
||||
if (!deleteKey) break;
|
||||
const deadTimeline = this.timelines.get(deleteKey);
|
||||
if (deadTimeline) {
|
||||
this.timelines.delete(deleteKey);
|
||||
deadTimeline.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
return timeline;
|
||||
|
Loading…
x
Reference in New Issue
Block a user