Merge remote-tracking branch 'origin/main' into zaps

This commit is contained in:
Alex Gleason
2024-01-22 12:00:55 -06:00
9 changed files with 104 additions and 98 deletions

37
src/utils/SimpleLRU.ts Normal file
View File

@@ -0,0 +1,37 @@
// deno-lint-ignore-file ban-types
import { LRUCache, type MapCache } from '@/deps.ts';
type FetchFn<K extends {}, V extends {}, O extends {}> = (key: K, opts: O) => Promise<V>;
interface FetchFnOpts {
signal?: AbortSignal | null;
}
export class SimpleLRU<
K extends {},
V extends {},
O extends {} = FetchFnOpts,
> implements MapCache<K, V, O> {
protected cache: LRUCache<K, V, void>;
constructor(fetchFn: FetchFn<K, V, { signal: AbortSignal }>, opts: LRUCache.Options<K, V, void>) {
this.cache = new LRUCache({
fetchMethod: (key, _staleValue, { signal }) => fetchFn(key, { signal: signal as AbortSignal }),
...opts,
});
}
async fetch(key: K, opts?: O): Promise<V> {
const result = await this.cache.fetch(key, opts);
if (result === undefined) {
throw new Error('SimpleLRU: fetch failed');
}
return result;
}
put(key: K, value: V): Promise<void> {
this.cache.set(key, value);
return Promise.resolve();
}
}

View File

@@ -1,73 +1,22 @@
import { Debug, TTLCache, z } from '@/deps.ts';
import { Debug, NIP05, nip19 } from '@/deps.ts';
import { SimpleLRU } from '@/utils/SimpleLRU.ts';
import { Time } from '@/utils/time.ts';
import { fetchWorker } from '@/workers/fetch.ts';
const debug = Debug('ditto:nip05');
const nip05Cache = new TTLCache<string, Promise<string | null>>({ ttl: Time.hours(1), max: 5000 });
const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/;
interface LookupOpts {
signal?: AbortSignal;
}
/** Get pubkey from NIP-05. */
async function lookup(value: string, opts: LookupOpts = {}): Promise<string | null> {
const { signal = AbortSignal.timeout(2000) } = opts;
const match = value.match(NIP05_REGEX);
if (!match) return null;
const [_, name = '_', domain] = match;
try {
const res = await fetchWorker(`https://${domain}/.well-known/nostr.json?name=${name}`, {
signal,
});
const { names } = nostrJsonSchema.parse(await res.json());
return names[name] || null;
} catch (e) {
debug(e);
return null;
}
}
/** nostr.json schema. */
const nostrJsonSchema = z.object({
names: z.record(z.string(), z.string()),
relays: z.record(z.string(), z.array(z.string())).optional().catch(undefined),
});
/**
* 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);
if (cached !== undefined) return cached;
debug(`Lookup ${value}`);
const result = lookup(value);
nip05Cache.set(value, result);
result.then((result) => {
if (result) {
debug(`Found: ${value} is ${result}`);
} else {
debug(`Not found: ${value} is ${result}`);
const nip05Cache = new SimpleLRU<string, nip19.ProfilePointer>(
async (key, { signal }) => {
debug(`Lookup ${key}`);
try {
const result = await NIP05.lookup(key, { fetch, signal });
debug(`Found: ${key} is ${result.pubkey}`);
return result;
} catch (e) {
debug(`Not found: ${key}`);
throw e;
}
});
},
{ max: 5000, ttl: Time.hours(1) },
);
return result;
}
/** 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;
}
export { lookupNip05Cached, verifyNip05Cached };
export { nip05Cache };