UNPKG

@nostr-relay/wot-guard

Version:
166 lines 6.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WotGuard = void 0; const common_1 = require("@nostr-relay/common"); const rxjs_1 = require("rxjs"); const pool_1 = require("./pool"); class WotGuard { constructor({ trustAnchorPubkey, trustDepth, enabled, logger, eventRepository, relayUrls, skipFilters, agent, }) { this.intervalId = null; this.trustedPubkeySet = new Set(); this.lastRefreshedAt = 0; this.enabled = enabled ?? true; if (this.enabled && !trustAnchorPubkey) { throw new Error('trustAnchorPubkey is required to enable WotGuard'); } this.trustAnchorPubkey = trustAnchorPubkey; this.trustDepth = trustDepth ? Math.min(trustDepth, 2) : 1; // maximum trust depth is 2 now this.logger = logger ?? new common_1.ConsoleLoggerService(); this.eventRepository = eventRepository; this.relayUrls = relayUrls ?? []; this.skipFilters = skipFilters ?? []; this.agent = agent; } async init() { await this.refreshTrustedPubkeySet(); } setEnabled(enabled) { if (!this.trustAnchorPubkey) { throw new Error('trustAnchorPubkey is required to enable WotGuard'); } this.enabled = enabled; } getEnabled() { return this.enabled; } setTrustAnchorPubkey(pubkey) { this.trustAnchorPubkey = pubkey; } setTrustDepth(depth) { this.trustDepth = Math.min(depth, 2); } setAgent(agent) { this.agent = agent; } getLastRefreshedAt() { return this.lastRefreshedAt; } getTrustedPubkeyCount() { return this.trustedPubkeySet.size; } checkPubkey(pubkey) { return this.trustedPubkeySet.has(pubkey); } destroy() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } this.trustedPubkeySet.clear(); } beforeHandleEvent(event) { if (!this.enabled || this.skipFilters.some(filter => common_1.EventUtils.isMatchingFilter(event, filter)) || this.trustedPubkeySet.has(event.pubkey)) { return { canHandle: true }; } return { canHandle: false, message: 'block: you are not in the trusted public keys list', }; } async refreshTrustedPubkeySet() { if (!this.enabled || !this.trustAnchorPubkey) { return; } const start = Date.now(); this.logger.info('Refreshing trusted pubkey set...'); // Initialize a set to store pubkeys within the specified trust depth const newTrustedPubkeySet = new Set([this.trustAnchorPubkey]); // Start with the trust anchor as the current depth users let currentDepthPubkeySet = newTrustedPubkeySet; // Initialize the pool of relays const pool = new pool_1.Pool(this.relayUrls, { agent: this.agent }); const connectedRelayUrls = await pool.init(); this.logger.info(`Connected to relays: ${connectedRelayUrls.length ? connectedRelayUrls.join(', ') : 'none'}`); // Iterate through each level of depth up to the specified trust depth for (let depth = 0; depth < this.trustDepth; depth++) { const nextDepthPubkeySet = await this.fetchNextDepthPubkeySet(pool, currentDepthPubkeySet); // Update the set of pubkeys within the specified depth nextDepthPubkeySet.forEach(user => newTrustedPubkeySet.add(user)); // TODO: if the depth more than 2, we need to filter out the duplicated pubkeys currentDepthPubkeySet = nextDepthPubkeySet; // Move to the next depth } pool.destroy(); // Update the trusted pubkey set const oldTrustedPubkeySet = this.trustedPubkeySet; this.trustedPubkeySet = newTrustedPubkeySet; oldTrustedPubkeySet.clear(); this.logger.info(`Trusted pubkey set updated: ${newTrustedPubkeySet.size} pubkeys, took ${Date.now() - start}ms`); this.lastRefreshedAt = Date.now(); } async fetchNextDepthPubkeySet(pool, currentDepthPubkeySet) { const nextDepthPubkeySet = new Set(); const chunks = []; let chunk = []; for (const pubkey of currentDepthPubkeySet) { chunk.push(pubkey); if (chunk.length === 100) { chunks.push(chunk); chunk = []; } } if (chunk.length > 0) { chunks.push(chunk); chunk = []; } await Promise.allSettled(chunks.map(async (authors) => { const map = new Map(); const filter = { kinds: [3], authors, }; const [eventsFromLocal, events] = await Promise.all([ this.fetchEventFromLocal(filter), pool.fetchEvents(filter), ]); eventsFromLocal.concat(events).forEach(event => { const author = event.pubkey; const following = event.tags .map(([tagName, tagValue]) => { if (tagName !== 'p') return null; return /^[0-9a-f]{64}$/.test(tagValue) ? tagValue : null; }) .filter(Boolean); const exists = map.get(author); if (exists && exists.created_at > event.created_at) return; map.set(author, { following, created_at: event.created_at }); }); for (const [, { following }] of map) { following.forEach(pubkey => nextDepthPubkeySet.add(pubkey)); } })); return nextDepthPubkeySet; } async fetchEventFromLocal(filter) { if (!this.eventRepository) { return []; } const events = await this.eventRepository.find(filter); if (events instanceof rxjs_1.Observable) { return await new Promise((resolve, reject) => { const result = []; events.subscribe({ next: event => result.push(event), error: reject, complete: () => resolve(result), }); }); } return events; } } exports.WotGuard = WotGuard; //# sourceMappingURL=index.js.map