@nostr-relay/wot-guard
Version:
166 lines • 6.47 kB
JavaScript
"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