mirror of
https://github.com/aljazceru/ditto.git
synced 2025-12-30 11:44:25 +01:00
Merge remote-tracking branch 'origin/main' into zaps
This commit is contained in:
37
src/utils/SimpleLRU.ts
Normal file
37
src/utils/SimpleLRU.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user