Fix NIP-05 cache
This commit is contained in:
parent
af9f376ad0
commit
6e09e6f9e5
@ -1,6 +1,7 @@
|
|||||||
import { type AppController } from '@/app.ts';
|
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 { getAuthor, getFilter, getFollows } from '@/client.ts';
|
||||||
|
import { lookupNip05Cached } from '@/nip05.ts';
|
||||||
import { toAccount, toStatus } from '@/transmute.ts';
|
import { toAccount, toStatus } from '@/transmute.ts';
|
||||||
import { bech32ToPubkey, eventDateComparator } from '@/utils.ts';
|
import { bech32ToPubkey, eventDateComparator } from '@/utils.ts';
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ const accountStatusesController: AppController = async (c) => {
|
|||||||
async function lookupAccount(value: string): Promise<Event<0> | undefined> {
|
async function lookupAccount(value: string): Promise<Event<0> | undefined> {
|
||||||
console.log(`Looking up ${value}`);
|
console.log(`Looking up ${value}`);
|
||||||
|
|
||||||
const pubkey = bech32ToPubkey(value) || (await nip05.queryProfile(value))?.pubkey;
|
const pubkey = bech32ToPubkey(value) || await lookupNip05Cached(value);
|
||||||
|
|
||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
return getAuthor(pubkey);
|
return getAuthor(pubkey);
|
||||||
|
57
src/nip05.ts
57
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<string, Promise<string | null>>({ ttl: ONE_HOUR, max: 5000 });
|
||||||
|
|
||||||
const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/;
|
const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/;
|
||||||
|
|
||||||
@ -6,12 +9,12 @@ interface LookupOpts {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adapted from nostr-tools. */
|
/** Get pubkey from NIP-05. */
|
||||||
async function lookup(value: string, opts: LookupOpts = {}): Promise<nip19.ProfilePointer | undefined> {
|
async function lookup(value: string, opts: LookupOpts = {}): Promise<string | null> {
|
||||||
const { timeout = 1000 } = opts;
|
const { timeout = 1000 } = opts;
|
||||||
|
|
||||||
const match = value.match(NIP05_REGEX);
|
const match = value.match(NIP05_REGEX);
|
||||||
if (!match) return;
|
if (!match) return null;
|
||||||
|
|
||||||
const [_, name = '_', domain] = match;
|
const [_, name = '_', domain] = match;
|
||||||
|
|
||||||
@ -20,38 +23,42 @@ async function lookup(value: string, opts: LookupOpts = {}): Promise<nip19.Profi
|
|||||||
signal: AbortSignal.timeout(timeout),
|
signal: AbortSignal.timeout(timeout),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { names, relays } = nostrJsonSchema.parse(await res.json());
|
const { names } = nostrJsonSchema.parse(await res.json());
|
||||||
|
|
||||||
const pubkey = names[name];
|
return names[name] || null;
|
||||||
|
|
||||||
if (pubkey) {
|
|
||||||
return {
|
|
||||||
pubkey,
|
|
||||||
relays: relays?.[pubkey],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** nostr.json schema. */
|
||||||
const nostrJsonSchema = z.object({
|
const nostrJsonSchema = z.object({
|
||||||
names: z.record(z.string(), z.string()),
|
names: z.record(z.string(), z.string()),
|
||||||
relays: z.record(z.string(), z.array(z.string())).optional().catch(undefined),
|
relays: z.record(z.string(), z.array(z.string())).optional().catch(undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
async function verify(value: string, pubkey: string): Promise<boolean> {
|
/**
|
||||||
try {
|
* Lookup the NIP-05 and serve from cache first.
|
||||||
const result = await nip05.lookup(value);
|
* To prevent race conditions we put the promise in the cache instead of the result.
|
||||||
return result?.pubkey === pubkey;
|
*/
|
||||||
} catch (_e) {
|
function lookupNip05Cached(value: string): Promise<string | null> {
|
||||||
return false;
|
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 = {
|
/** Verify the NIP-05 matches the pubkey, with cache. */
|
||||||
lookup,
|
async function verifyNip05Cached(value: string, pubkey: string): Promise<boolean> {
|
||||||
verify,
|
const result = await lookupNip05Cached(value);
|
||||||
};
|
return result === pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
export { nip05 };
|
export { lookupNip05Cached, verifyNip05Cached };
|
||||||
|
@ -4,7 +4,7 @@ import { type MetaContent, parseMetaContent } from '@/schema.ts';
|
|||||||
|
|
||||||
import { LOCAL_DOMAIN } from './config.ts';
|
import { LOCAL_DOMAIN } from './config.ts';
|
||||||
import { getAuthor } from './client.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 { getMediaLinks, type MediaLink, parseNoteContent } from './note.ts';
|
||||||
import { type Nip05, parseNip05 } from './utils.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<string, Promise<boolean>>({ ttl: ONE_HOUR, max: 5000 });
|
|
||||||
|
|
||||||
function verifyNip05Cached(value: string, pubkey: string): Promise<boolean> {
|
|
||||||
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) {
|
async function toMention(pubkey: string) {
|
||||||
const profile = await getAuthor(pubkey);
|
const profile = await getAuthor(pubkey);
|
||||||
const account = profile ? await toAccount(profile) : undefined;
|
const account = profile ? await toAccount(profile) : undefined;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user