ditto/src/nip05.ts

62 lines
1.7 KiB
TypeScript
Raw Normal View History

2023-05-07 16:44:22 +00:00
import { TTLCache, z } from '@/deps.ts';
2023-07-09 01:01:10 +00:00
import { Time } from '@/utils/time.ts';
2023-05-07 16:44:22 +00:00
2023-07-09 00:54:27 +00:00
const nip05Cache = new TTLCache<string, Promise<string | null>>({ ttl: Time.hours(1), max: 5000 });
2023-05-07 03:29:41 +00:00
const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/;
interface LookupOpts {
timeout?: number;
}
2023-05-07 16:44:22 +00:00
/** Get pubkey from NIP-05. */
async function lookup(value: string, opts: LookupOpts = {}): Promise<string | null> {
2023-05-07 03:29:41 +00:00
const { timeout = 1000 } = opts;
const match = value.match(NIP05_REGEX);
2023-05-07 16:44:22 +00:00
if (!match) return null;
2023-05-07 03:29:41 +00:00
const [_, name = '_', domain] = match;
try {
const res = await fetch(`https://${domain}/.well-known/nostr.json?name=${name}`, {
signal: AbortSignal.timeout(timeout),
});
2023-05-07 16:44:22 +00:00
const { names } = nostrJsonSchema.parse(await res.json());
2023-05-07 03:29:41 +00:00
2023-05-07 16:44:22 +00:00
return names[name] || null;
2023-05-07 03:29:41 +00:00
} catch (_e) {
2023-05-07 16:44:22 +00:00
return null;
2023-05-07 03:29:41 +00:00
}
}
2023-05-07 16:44:22 +00:00
/** nostr.json schema. */
2023-05-07 03:29:41 +00:00
const nostrJsonSchema = z.object({
names: z.record(z.string(), z.string()),
relays: z.record(z.string(), z.array(z.string())).optional().catch(undefined),
});
2023-05-07 16:44:22 +00:00
/**
* 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<string | null> {
const cached = nip05Cache.get(value);
2023-05-07 21:59:36 +00:00
if (cached !== undefined) return cached;
2023-05-07 16:44:22 +00:00
console.log(`Looking up NIP-05 for ${value}`);
const result = lookup(value);
nip05Cache.set(value, result);
return result;
2023-05-07 03:29:41 +00:00
}
2023-05-07 16:44:22 +00:00
/** Verify the NIP-05 matches the pubkey, with cache. */
async function verifyNip05Cached(value: string, pubkey: string): Promise<boolean> {
const result = await lookupNip05Cached(value);
return result === pubkey;
}
2023-05-07 03:29:41 +00:00
2023-05-07 16:44:22 +00:00
export { lookupNip05Cached, verifyNip05Cached };