1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
import config from "$config";
import { db } from "$lib/db";
import { l, warn } from "$lib/logging";
import { getProfile } from "$lib/nostr";
import { Relay } from "nostr-tools/relay";
import type { Event } from "nostr-tools";
const MLS_KEY = "mls:users";
const RELAYS = config.relays.filter((r) => r.startsWith("wss://"));
interface MlsUser {
pubkey: string;
name?: string;
picture?: string;
nip05?: string;
}
const knownPubkeys = new Set<string>();
async function resolveProfile(pubkey: string): Promise<MlsUser> {
try {
const profile = await getProfile(pubkey);
return {
pubkey,
name: profile.display_name || profile.name || undefined,
picture: profile.picture || undefined,
nip05: profile.nip05 || undefined,
};
} catch {
return { pubkey };
}
}
async function addUser(pubkey: string): Promise<void> {
if (knownPubkeys.has(pubkey)) return;
knownPubkeys.add(pubkey);
const user = await resolveProfile(pubkey);
await db.hSet(MLS_KEY, pubkey, JSON.stringify(user));
}
async function backfill(): Promise<void> {
// Paginate back 3 weeks
const threeWeeksAgo = Math.floor(Date.now() / 1000) - 21 * 24 * 60 * 60;
let until: number | undefined;
for (let pass = 0; pass < 50; pass++) {
const filter: any = { kinds: [443], limit: 500 };
if (until) filter.until = until;
let events: Event[] = [];
try {
events = (
await Promise.all(
RELAYS.map(
(url) =>
new Promise<Event[]>((resolve) => {
Relay.connect(url)
.then((r) => {
const found: Event[] = [];
r.subscribe([filter], {
onevent(e) { found.push(e); },
oneose() { r.close(); resolve(found); },
});
})
.catch(() => resolve([]));
}),
),
)
).flat();
} catch {
break;
}
if (events.length === 0) break;
const newPubkeys = [...new Set(events.map((e) => e.pubkey))].filter(
(pk) => !knownPubkeys.has(pk),
);
for (const pk of newPubkeys) {
await addUser(pk);
}
const oldest = Math.min(...events.map((e) => e.created_at));
if (oldest <= threeWeeksAgo) break;
until = oldest;
}
l(`mls backfill complete: ${knownPubkeys.size} users`);
}
function subscribe(): void {
for (const url of RELAYS) {
Relay.connect(url)
.then((r) => {
r.subscribe([{ kinds: [443], since: Math.floor(Date.now() / 1000) }], {
onevent(e) {
addUser(e.pubkey).catch(() => {});
},
});
})
.catch((e) => warn(`mls subscribe failed: ${url}`, e.message));
}
}
export async function initMlsIndex(): Promise<void> {
// Load existing index into memory
try {
const existing = await db.hGetAll(MLS_KEY);
for (const pk of Object.keys(existing)) knownPubkeys.add(pk);
} catch {}
// Subscribe for new key packages
subscribe();
// Backfill in background
backfill().catch((e) => warn("mls backfill error", e.message));
}
export async function getMlsUsers(): Promise<MlsUser[]> {
const data = await db.hGetAll(MLS_KEY);
return Object.values(data).map((v) => JSON.parse(v));
}
|