UNPKG

nostr-relaypool

Version:

A Nostr RelayPool implementation in TypeScript using only nostr-tools library as a dependency.

221 lines (208 loc) 6.2 kB
import {type Filter, Kind, type Event} from "nostr-tools"; export class EventCache { eventsById: Map<string, Event> = new Map(); metadataByPubKey: Map<string, Event> = new Map(); contactsByPubKey: Map<string, Event> = new Map(); authorsKindsByPubKey: Map<string, Map<number, Event[]>> = new Map(); eventsByTags: Map<string, Event[]> = new Map(); #addEventToAuthorKindsByPubKey(event: Event) { const kindsByPubKey = this.authorsKindsByPubKey.get(event.pubkey); if (!kindsByPubKey) { this.authorsKindsByPubKey.set( event.pubkey, new Map([[event.kind, [event]]]) ); } else { const events = kindsByPubKey.get(event.kind); if (!events) { kindsByPubKey.set(event.kind, [event]); } else { if (event.kind === Kind.Metadata || event.kind === Kind.Contacts) { if (event.created_at > events[0].created_at) { events[0] = event; } } else { events.push(event); } } } } #addEventToEventsByTags(event: Event) { for (const tag of event.tags) { let tag2 = tag[0] + ":" + tag[1]; const events = this.eventsByTags.get(tag2); if (events) { events.push(event); } else { this.eventsByTags.set(tag2, [event]); } } } addEvent(event: Event) { if (this.getEventById(event.id)) { return; } this.eventsById.set(event.id, event); if (event.kind === Kind.Metadata) { this.metadataByPubKey.set(event.pubkey, event); } if (event.kind === Kind.Contacts) { this.contactsByPubKey.set(event.pubkey, event); } this.#addEventToAuthorKindsByPubKey(event); this.#addEventToEventsByTags(event); } getEventById(id: string): Event | undefined { return this.eventsById.get(id); } hasEventById(id: string): boolean { return this.eventsById.has(id); } #getCachedEventsByPubKeyWithUpdatedFilter( filter: Filter & { relay?: string; noCache?: boolean; } ): {filter: Filter & {relay?: string}; events: Set<Event>} | undefined { if ( filter.noCache || !filter.authors || !filter.kinds || filter.kinds.find( (kind) => kind !== Kind.Contacts && kind !== Kind.Metadata ) !== undefined ) { return undefined; } const authors: string[] = []; const events = new Set<Event>(); for (const author of filter.authors) { let contactEvent; if (filter.kinds.includes(Kind.Contacts)) { contactEvent = this.contactsByPubKey.get(author); if (!contactEvent) { authors.push(author); continue; } } let metadataEvent; if (filter.kinds.includes(Kind.Metadata)) { metadataEvent = this.metadataByPubKey.get(author); if (!metadataEvent) { authors.push(author); continue; } } if (contactEvent) { events.add(contactEvent); } if (metadataEvent) { events.add(metadataEvent); } } return {filter: {...filter, authors}, events}; } #getCachedEventsByPubKeyWithUpdatedFilter2( filter: Filter & { relay?: string; noCache?: boolean; } ): {filter: Filter & {relay?: string}; events: Set<Event>} | undefined { if (filter.noCache || !filter.authors) { return undefined; } const events = new Set<Event>(); for (const author of filter.authors) { if (filter.kinds) { const kindsByPubKey = this.authorsKindsByPubKey.get(author); if (kindsByPubKey) { for (const kind of filter.kinds) { const events2 = kindsByPubKey.get(kind); if (events2) { for (const event of events2) { events.add(event); } } } } } else { const kindsByPubKey = this.authorsKindsByPubKey.get(author); if (kindsByPubKey) { for (const events2 of kindsByPubKey.values()) { for (const event3 of events2) { events.add(event3); } } } } } return {filter, events}; } #getCachedEventsByTagsWithUpdatedFilter( filter: Filter & { relay?: string; noCache?: boolean; } ): {filter: Filter & {relay?: string}; events: Set<Event>} | undefined { if (filter.noCache) { return undefined; } const events = new Set<Event>(); for (const tag in filter) { if (tag[0] !== "#") { continue; } // @ts-ignore let tag2 = tag.slice(1) + ":" + filter[tag][0]; const events2 = this.eventsByTags.get(tag2); if (events2) { for (const event of events2) { events.add(event); } } } return {filter, events}; } #getCachedEventsByIdWithUpdatedFilter( filter: Filter & {relay?: string; noCache?: boolean} ): {filter: Filter & {relay?: string}; events: Set<Event>} | undefined { if (!filter.ids) { return undefined; } const events = new Set<Event>(); const ids: string[] = []; for (const id of filter.ids) { const event = this.getEventById(id); if (event) { events.add(event); } else { ids.push(id); } } return {filter: {...filter, ids}, events}; } getCachedEventsWithUpdatedFilters( filters: (Filter & {relay?: string; noCache?: boolean})[], relays: string[] ): { filters: (Filter & {relay?: string})[]; events: Event[]; } { const events: Set<Event> = new Set(); const new_filters: (Filter & {relay?: string})[] = []; for (const filter of filters) { const new_data = this.#getCachedEventsByIdWithUpdatedFilter(filter) || // this.#getCachedEventsByPubKeyWithUpdatedFilter(filter) || this.#getCachedEventsByPubKeyWithUpdatedFilter2(filter) || this.#getCachedEventsByTagsWithUpdatedFilter(filter) || { filter, events: [], }; for (const event of new_data.events) { events.add(event); } new_filters.push(new_data.filter); } return {filters: new_filters, events: [...events]}; } }