feat: automate social graph

This commit is contained in:
2024-02-22 11:12:26 +00:00
parent 3f0bd88db8
commit 7558e91d28
16 changed files with 285 additions and 336 deletions

View File

@ -0,0 +1,117 @@
import { CachedTable, CacheEvents, removeUndefined, unixNowMs, unwrap } from "@snort/shared";
import { EventKind, NostrEvent, UsersFollows } from "@snort/system";
import { WorkerRelayInterface } from "@snort/worker-relay";
import debug from "debug";
import EventEmitter from "eventemitter3";
export class UserFollowsWorker extends EventEmitter<CacheEvents> implements CachedTable<UsersFollows> {
#relay: WorkerRelayInterface;
#keys = new Set<string>();
#cache = new Map<string, UsersFollows>();
#log = debug("UserFollowsWorker");
constructor(relay: WorkerRelayInterface) {
super();
this.#relay = relay;
}
async preload() {
const start = unixNowMs();
const profiles = await this.#relay.query([
"REQ",
"profiles-preload",
{
kinds: [3],
},
]);
this.#cache = new Map<string, UsersFollows>(profiles.map(a => [a.pubkey, unwrap(mapEventToUserFollows(a))]));
this.#keys = new Set<string>(this.#cache.keys());
this.#log(`Loaded %d/%d in %d ms`, this.#cache.size, this.#keys.size, (unixNowMs() - start).toLocaleString());
}
keysOnTable(): string[] {
return [...this.#keys];
}
getFromCache(key?: string | undefined): UsersFollows | undefined {
if (key) {
return this.#cache.get(key);
}
}
discover(ev: NostrEvent) {
this.#keys.add(ev.pubkey);
}
async get(key?: string | undefined): Promise<UsersFollows | undefined> {
if (key) {
const res = await this.bulkGet([key]);
if (res.length > 0) {
return res[0];
}
}
}
async bulkGet(keys: string[]) {
if (keys.length === 0) return [];
const results = await this.#relay.query([
"REQ",
"UserFollowsWorker.bulkGet",
{
authors: keys,
kinds: [3],
},
]);
const mapped = removeUndefined(results.map(a => mapEventToUserFollows(a)));
for (const pf of mapped) {
this.#cache.set(this.key(pf), pf);
}
this.emit(
"change",
mapped.map(a => this.key(a)),
);
return mapped;
}
async set(obj: UsersFollows) {
this.#keys.add(this.key(obj));
}
async bulkSet(obj: UsersFollows[] | readonly UsersFollows[]) {
const mapped = obj.map(a => this.key(a));
mapped.forEach(a => this.#keys.add(a));
// todo: store in cache
this.emit("change", mapped);
}
async update(): Promise<"new" | "refresh" | "updated" | "no_change"> {
// do nothing
return "refresh";
}
async buffer(keys: string[]): Promise<string[]> {
const missing = keys.filter(a => !this.#keys.has(a));
const res = await this.bulkGet(missing);
return missing.filter(a => !res.some(b => this.key(b) === a));
}
key(of: UsersFollows): string {
return of.pubkey;
}
snapshot(): UsersFollows[] {
return [...this.#cache.values()];
}
}
export function mapEventToUserFollows(ev: NostrEvent): UsersFollows | undefined {
if (ev.kind !== EventKind.ContactList) return;
return {
pubkey: ev.pubkey,
loaded: unixNowMs(),
created: ev.created_at,
follows: ev.tags,
};
}

View File

@ -6,6 +6,7 @@ import WorkerRelayPath from "@snort/worker-relay/dist/worker?worker&url";
import { EventCacheWorker } from "./EventCacheWorker";
import { GiftWrapCache } from "./GiftWrapCache";
import { ProfileCacheRelayWorker } from "./ProfileWorkerCache";
import { UserFollowsWorker } from "./UserFollowsWorker";
export const Relay = new WorkerRelayInterface(WorkerRelayPath);
export async function initRelayWorker() {
@ -20,6 +21,7 @@ export const SystemDb = new SnortSystemDb();
export const UserRelays = new UserRelaysCache(SystemDb.userRelays);
export const RelayMetrics = new RelayMetricCache(SystemDb.relayMetrics);
export const UserFollows = new UserFollowsWorker(Relay);
export const UserCache = new ProfileCacheRelayWorker(Relay);
export const EventsCache = new EventCacheWorker(Relay);
@ -32,6 +34,7 @@ export async function preload(follows?: Array<string>) {
GiftsCache.preload(),
UserRelays.preload(follows),
EventsCache.preload(),
UserFollows.preload(),
];
await Promise.all(preloads);
}

View File

@ -2,13 +2,12 @@ import "./index.css";
import "@szhsin/react-menu/dist/index.css";
import "@/assets/fonts/inter.css";
import { socialGraphInstance } from "@snort/system";
import { SnortContext } from "@snort/system-react";
import { StrictMode } from "react";
import * as ReactDOM from "react-dom/client";
import { createBrowserRouter, RouteObject, RouterProvider } from "react-router-dom";
import { initRelayWorker, preload, Relay, UserCache } from "@/Cache";
import { initRelayWorker, preload, UserCache } from "@/Cache";
import { ThreadRoute } from "@/Components/Event/Thread";
import { IntlProvider } from "@/Components/IntlProvider/IntlProvider";
import { db } from "@/Db";
@ -55,25 +54,10 @@ async function initSite() {
updateRelayConnections(System, login.relays.item).catch(console.error);
setupWebLNWalletConfig(Wallets);
Relay.query([
"REQ",
"preload-social-graph",
{
kinds: [3],
},
]).then(res => {
for (const ev of res) {
try {
socialGraphInstance.handleEvent(ev);
} catch (e) {
console.error("Failed to handle contact list event from sql db", e);
}
}
});
db.ready = await db.isAvailable();
if (db.ready) {
await preload(login.follows.item);
await System.PreloadSocialGraph();
}
queueMicrotask(() => {

View File

@ -1,7 +1,7 @@
import { removeUndefined, throwIfOffline } from "@snort/shared";
import { mapEventToProfile, NostrEvent, NostrSystem, socialGraphInstance } from "@snort/system";
import { mapEventToProfile, NostrEvent, NostrSystem } from "@snort/system";
import { EventsCache, Relay, RelayMetrics, SystemDb, UserCache, UserRelays } from "@/Cache";
import { EventsCache, Relay, RelayMetrics, SystemDb, UserCache, UserFollows, UserRelays } from "@/Cache";
import { addEventToFuzzySearch } from "@/Db/FuzzySearch";
import { LoginStore } from "@/Utils/Login";
import { hasWasm, WasmOptimizer } from "@/Utils/wasm";
@ -10,13 +10,15 @@ import { hasWasm, WasmOptimizer } from "@/Utils/wasm";
* Singleton nostr system
*/
export const System = new NostrSystem({
relayCache: UserRelays,
eventsCache: EventsCache,
profileCache: UserCache,
relays: UserRelays,
events: EventsCache,
profiles: UserCache,
relayMetrics: RelayMetrics,
cacheRelay: Relay,
cachingRelay: Relay,
contactLists: UserFollows,
optimizer: hasWasm ? WasmOptimizer : undefined,
db: SystemDb,
buildFollowGraph: true,
});
System.on("auth", async (c, r, cb) => {
@ -31,7 +33,6 @@ System.on("event", (_, ev) => {
Relay.event(ev);
EventsCache.discover(ev);
UserCache.discover(ev);
socialGraphInstance.handleEvent(ev);
addEventToFuzzySearch(ev);
});