2023-03-28 14:34:01 +00:00
|
|
|
import { EventKind, HexKey, TaggedRawEvent } from "@snort/nostr";
|
|
|
|
import { ProfileCacheExpire } from "Const";
|
2023-03-29 12:10:22 +00:00
|
|
|
import { mapEventToProfile, MetadataCache } from "Cache";
|
|
|
|
import { UserCache } from "Cache/UserCache";
|
2023-03-28 14:34:01 +00:00
|
|
|
import { PubkeyReplaceableNoteStore, RequestBuilder, System } from "System";
|
|
|
|
import { unixNowMs } from "Util";
|
|
|
|
|
2023-03-29 12:10:22 +00:00
|
|
|
class ProfileLoaderService {
|
2023-03-28 14:34:01 +00:00
|
|
|
/**
|
|
|
|
* List of pubkeys to fetch metadata for
|
|
|
|
*/
|
|
|
|
WantsMetadata: Set<HexKey> = new Set();
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
this.#FetchMetadata();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Request profile metadata for a set of pubkeys
|
|
|
|
*/
|
|
|
|
TrackMetadata(pk: HexKey | Array<HexKey>) {
|
|
|
|
for (const p of Array.isArray(pk) ? pk : [pk]) {
|
|
|
|
if (p.length > 0) {
|
|
|
|
this.WantsMetadata.add(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop tracking metadata for a set of pubkeys
|
|
|
|
*/
|
|
|
|
UntrackMetadata(pk: HexKey | Array<HexKey>) {
|
|
|
|
for (const p of Array.isArray(pk) ? pk : [pk]) {
|
|
|
|
if (p.length > 0) {
|
|
|
|
this.WantsMetadata.delete(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async #FetchMetadata() {
|
|
|
|
const missingFromCache = await UserCache.buffer([...this.WantsMetadata]);
|
|
|
|
|
|
|
|
const expire = unixNowMs() - ProfileCacheExpire;
|
|
|
|
const expired = [...this.WantsMetadata]
|
|
|
|
.filter(a => !missingFromCache.includes(a))
|
2023-03-29 12:10:22 +00:00
|
|
|
.filter(a => (UserCache.getFromCache(a)?.loaded ?? 0) < expire);
|
2023-03-28 14:34:01 +00:00
|
|
|
const missing = new Set([...missingFromCache, ...expired]);
|
|
|
|
if (missing.size > 0) {
|
|
|
|
console.debug(`Wants profiles: ${missingFromCache.length} missing, ${expired.length} expired`);
|
|
|
|
|
|
|
|
const sub = new RequestBuilder(`profiles`);
|
|
|
|
sub
|
2023-04-04 14:27:49 +00:00
|
|
|
.withOptions({
|
|
|
|
skipDiff: true,
|
|
|
|
})
|
2023-03-28 14:34:01 +00:00
|
|
|
.withFilter()
|
|
|
|
.kinds([EventKind.SetMetadata])
|
|
|
|
.authors([...missing]);
|
|
|
|
|
|
|
|
const q = System.Query<PubkeyReplaceableNoteStore>(PubkeyReplaceableNoteStore, sub);
|
|
|
|
// never release this callback, it will stop firing anyway after eose
|
2023-04-04 14:27:49 +00:00
|
|
|
q.onEvent(async ev => {
|
2023-03-28 14:34:01 +00:00
|
|
|
for (const e of ev) {
|
|
|
|
const profile = mapEventToProfile(e);
|
|
|
|
if (profile) {
|
|
|
|
await UserCache.update(profile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const results = await new Promise<Readonly<Array<TaggedRawEvent>>>(resolve => {
|
|
|
|
let timeout: ReturnType<typeof setTimeout> | undefined = undefined;
|
|
|
|
const release = q.hook(() => {
|
|
|
|
if (!q.loading) {
|
|
|
|
clearTimeout(timeout);
|
|
|
|
resolve(q.getSnapshotData() ?? []);
|
2023-04-04 14:27:49 +00:00
|
|
|
release();
|
2023-03-28 14:34:01 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
timeout = setTimeout(() => {
|
|
|
|
release();
|
|
|
|
resolve(q.getSnapshotData() ?? []);
|
|
|
|
}, 5_000);
|
|
|
|
});
|
|
|
|
|
|
|
|
const couldNotFetch = [...missing].filter(a => !results.some(b => b.pubkey === a));
|
|
|
|
if (couldNotFetch.length > 0) {
|
|
|
|
console.debug("No profiles: ", couldNotFetch);
|
|
|
|
const empty = couldNotFetch.map(a =>
|
|
|
|
UserCache.update({
|
|
|
|
pubkey: a,
|
|
|
|
loaded: unixNowMs() - ProfileCacheExpire + 5_000, // expire in 5s
|
|
|
|
created: 69,
|
|
|
|
} as MetadataCache)
|
|
|
|
);
|
|
|
|
await Promise.all(empty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(() => this.#FetchMetadata(), 500);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-29 12:10:22 +00:00
|
|
|
export const ProfileLoader = new ProfileLoaderService();
|