diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 77effcb..9183c2d 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -1,6 +1,7 @@ import { type AppController } from '@/app.ts'; -import { nip05, z } from '@/deps.ts'; +import { z } from '@/deps.ts'; import { getAuthor, getFilter, getFollows } from '@/client.ts'; +import { lookupNip05Cached } from '@/nip05.ts'; import { toAccount, toStatus } from '@/transmute.ts'; import { bech32ToPubkey, eventDateComparator } from '@/utils.ts'; @@ -110,7 +111,7 @@ const accountStatusesController: AppController = async (c) => { async function lookupAccount(value: string): Promise | undefined> { console.log(`Looking up ${value}`); - const pubkey = bech32ToPubkey(value) || (await nip05.queryProfile(value))?.pubkey; + const pubkey = bech32ToPubkey(value) || await lookupNip05Cached(value); if (pubkey) { return getAuthor(pubkey); diff --git a/src/nip05.ts b/src/nip05.ts index 4416c98..6f53225 100644 --- a/src/nip05.ts +++ b/src/nip05.ts @@ -1,4 +1,7 @@ -import { nip19, z } from '@/deps.ts'; +import { TTLCache, z } from '@/deps.ts'; + +const ONE_HOUR = 60 * 60 * 1000; +const nip05Cache = new TTLCache>({ ttl: ONE_HOUR, max: 5000 }); const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/; @@ -6,12 +9,12 @@ interface LookupOpts { timeout?: number; } -/** Adapted from nostr-tools. */ -async function lookup(value: string, opts: LookupOpts = {}): Promise { +/** Get pubkey from NIP-05. */ +async function lookup(value: string, opts: LookupOpts = {}): Promise { const { timeout = 1000 } = opts; const match = value.match(NIP05_REGEX); - if (!match) return; + if (!match) return null; const [_, name = '_', domain] = match; @@ -20,38 +23,42 @@ async function lookup(value: string, opts: LookupOpts = {}): Promise { - try { - const result = await nip05.lookup(value); - return result?.pubkey === pubkey; - } catch (_e) { - return false; +/** + * Lookup the NIP-05 and serve from cache first. + * To prevent race conditions we put the promise in the cache instead of the result. + */ +function lookupNip05Cached(value: string): Promise { + const cached = nip05Cache.get(value); + if (cached !== undefined) { + console.log(`Using cached NIP-05 for ${value}`); + return cached; } + + console.log(`Looking up NIP-05 for ${value}`); + const result = lookup(value); + nip05Cache.set(value, result); + + return result; } -const nip05 = { - lookup, - verify, -}; +/** Verify the NIP-05 matches the pubkey, with cache. */ +async function verifyNip05Cached(value: string, pubkey: string): Promise { + const result = await lookupNip05Cached(value); + return result === pubkey; +} -export { nip05 }; +export { lookupNip05Cached, verifyNip05Cached }; diff --git a/src/transmute.ts b/src/transmute.ts index 54e3a29..bb4a120 100644 --- a/src/transmute.ts +++ b/src/transmute.ts @@ -4,7 +4,7 @@ import { type MetaContent, parseMetaContent } from '@/schema.ts'; import { LOCAL_DOMAIN } from './config.ts'; import { getAuthor } from './client.ts'; -import { nip05 } from './nip05.ts'; +import { verifyNip05Cached } from './nip05.ts'; import { getMediaLinks, type MediaLink, parseNoteContent } from './note.ts'; import { type Nip05, parseNip05 } from './utils.ts'; @@ -66,24 +66,6 @@ async function toAccount(event: Event<0>, opts: ToAccountOpts = {}) { }; } -const ONE_HOUR = 60 * 60 * 1000; - -const nip05Cache = new TTLCache>({ ttl: ONE_HOUR, max: 5000 }); - -function verifyNip05Cached(value: string, pubkey: string): Promise { - const cached = nip05Cache.get(value); - if (cached !== undefined) { - console.log(`Using cached NIP-05 for ${value}`); - return cached; - } - - console.log(`Verifying NIP-05 for ${value}`); - const result = nip05.verify(value, pubkey); - nip05Cache.set(value, result); - - return result; -} - async function toMention(pubkey: string) { const profile = await getAuthor(pubkey); const account = profile ? await toAccount(profile) : undefined;