149 lines
3.5 KiB
TypeScript
149 lines
3.5 KiB
TypeScript
import { db, MetadataCache } from ".";
|
|
import { fetchNip05Pubkey, FeedCache, LNURL } from "@snort/shared";
|
|
|
|
export class UserProfileCache extends FeedCache<MetadataCache> {
|
|
#zapperQueue: Array<{ pubkey: string; lnurl: string }> = [];
|
|
#nip5Queue: Array<{ pubkey: string; nip05: string }> = [];
|
|
|
|
constructor() {
|
|
super("UserCache", db.users);
|
|
this.#processZapperQueue();
|
|
this.#processNip5Queue();
|
|
}
|
|
|
|
key(of: MetadataCache): string {
|
|
return of.pubkey;
|
|
}
|
|
|
|
override async preload(follows?: Array<string>): Promise<void> {
|
|
await super.preload();
|
|
// load follows profiles
|
|
if (follows) {
|
|
await this.buffer(follows);
|
|
}
|
|
}
|
|
|
|
async search(q: string): Promise<Array<MetadataCache>> {
|
|
if (db.ready) {
|
|
// on-disk cache will always have more data
|
|
return (
|
|
await db.users
|
|
.where("npub")
|
|
.startsWithIgnoreCase(q)
|
|
.or("name")
|
|
.startsWithIgnoreCase(q)
|
|
.or("display_name")
|
|
.startsWithIgnoreCase(q)
|
|
.or("nip05")
|
|
.startsWithIgnoreCase(q)
|
|
.toArray()
|
|
).slice(0, 5);
|
|
} else {
|
|
return [...this.cache.values()]
|
|
.filter(user => {
|
|
const profile = user as MetadataCache;
|
|
return (
|
|
profile.name?.includes(q) ||
|
|
profile.npub?.includes(q) ||
|
|
profile.display_name?.includes(q) ||
|
|
profile.nip05?.includes(q)
|
|
);
|
|
})
|
|
.slice(0, 5);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try to update the profile metadata cache with a new version
|
|
* @param m Profile metadata
|
|
* @returns
|
|
*/
|
|
override async update(m: MetadataCache) {
|
|
const updateType = await super.update(m);
|
|
if (updateType !== "refresh") {
|
|
const lnurl = m.lud16 ?? m.lud06;
|
|
if (lnurl) {
|
|
this.#zapperQueue.push({
|
|
pubkey: m.pubkey,
|
|
lnurl,
|
|
});
|
|
}
|
|
if (m.nip05) {
|
|
this.#nip5Queue.push({
|
|
pubkey: m.pubkey,
|
|
nip05: m.nip05,
|
|
});
|
|
}
|
|
}
|
|
return updateType;
|
|
}
|
|
|
|
takeSnapshot(): MetadataCache[] {
|
|
return [];
|
|
}
|
|
|
|
async #processZapperQueue() {
|
|
await this.#batchQueue(
|
|
this.#zapperQueue,
|
|
async i => {
|
|
const svc = new LNURL(i.lnurl);
|
|
await svc.load();
|
|
const p = this.getFromCache(i.pubkey);
|
|
if (p) {
|
|
await this.set({
|
|
...p,
|
|
zapService: svc.zapperPubkey,
|
|
});
|
|
}
|
|
},
|
|
5,
|
|
);
|
|
|
|
setTimeout(() => this.#processZapperQueue(), 1_000);
|
|
}
|
|
|
|
async #processNip5Queue() {
|
|
await this.#batchQueue(
|
|
this.#nip5Queue,
|
|
async i => {
|
|
const [name, domain] = i.nip05.split("@");
|
|
const nip5pk = await fetchNip05Pubkey(name, domain);
|
|
const p = this.getFromCache(i.pubkey);
|
|
if (p) {
|
|
await this.set({
|
|
...p,
|
|
isNostrAddressValid: i.pubkey === nip5pk,
|
|
});
|
|
}
|
|
},
|
|
5,
|
|
);
|
|
|
|
setTimeout(() => this.#processNip5Queue(), 1_000);
|
|
}
|
|
|
|
async #batchQueue<T>(queue: Array<T>, proc: (v: T) => Promise<void>, batchSize = 3) {
|
|
const batch = [];
|
|
while (queue.length > 0) {
|
|
const i = queue.shift();
|
|
if (i) {
|
|
batch.push(
|
|
(async () => {
|
|
try {
|
|
await proc(i);
|
|
} catch {
|
|
console.warn("Failed to process item", i);
|
|
}
|
|
batch.pop(); // pop any
|
|
})(),
|
|
);
|
|
if (batch.length === batchSize) {
|
|
await Promise.all(batch);
|
|
}
|
|
} else {
|
|
await Promise.all(batch);
|
|
}
|
|
}
|
|
}
|
|
}
|