mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-08 14:37:04 +02:00
fix(nip-29): fetch admin and member lists in parallel and correctly tag admins (#169)
* fix(nip-29): fetch admin and member lists in parallel and correctly tag admins This commit improves NIP-29 group member fetching with two key changes: 1. **Parallel fetching**: Admins (kind 39001) and members (kind 39002) are now fetched in parallel using separate subscriptions, improving performance 2. **Correct admin role tagging**: Users in kind 39001 events now correctly default to "admin" role instead of "member" when no explicit role tag is provided, as per NIP-29 spec Changes: - Split participantsFilter into separate adminsFilter and membersFilter - Use Promise.all to fetch both kinds in parallel - Updated normalizeRole helper to accept a defaultRole parameter - Process kind 39001 with "admin" default, kind 39002 with "member" default - Added clearer logging for admin/member event counts Related: src/lib/chat/adapters/nip-29-adapter.ts:192-320 * refactor(nip-29): simplify parallel fetch using pool.request Use pool.request() with both filters in a single call instead of manual subscription management. This is cleaner and more idiomatic: - Auto-closes on EOSE (no manual unsubscribe needed) - Fetches both kinds (39001 and 39002) in parallel with one request - Reduces code complexity significantly * fix(nip-29): use limit 1 for replaceable participant events Kinds 39001 and 39002 are replaceable events with d-tag, so there should only be one valid event of each kind per group. Changed limit from 5 to 1. * fix(chat): change "Sign in to send messages" to "Sign in to post" Simplified the login prompt text in chat interface for clarity. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1126,7 +1126,7 @@ export function ChatViewer({
|
||||
>
|
||||
Sign in
|
||||
</button>{" "}
|
||||
to send messages
|
||||
to post
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -189,83 +189,65 @@ export class Nip29Adapter extends ChatProtocolAdapter {
|
||||
|
||||
console.log(`[NIP-29] Group title: ${title}`);
|
||||
|
||||
// Fetch admins (kind 39001) and members (kind 39002)
|
||||
// Fetch admins (kind 39001) and members (kind 39002) in parallel
|
||||
// Both use d tag (addressable events signed by relay)
|
||||
const participantsFilter: Filter = {
|
||||
kinds: [39001, 39002],
|
||||
const adminsFilter: Filter = {
|
||||
kinds: [39001],
|
||||
"#d": [groupId],
|
||||
limit: 10, // Should be 1 of each kind, but allow for duplicates
|
||||
limit: 1,
|
||||
};
|
||||
|
||||
const participantEvents: NostrEvent[] = [];
|
||||
const participantsObs = pool.subscription(
|
||||
[relayUrl],
|
||||
[participantsFilter],
|
||||
{
|
||||
eventStore,
|
||||
},
|
||||
const membersFilter: Filter = {
|
||||
kinds: [39002],
|
||||
"#d": [groupId],
|
||||
limit: 1,
|
||||
};
|
||||
|
||||
// Use pool.request with both filters to fetch and auto-close on EOSE
|
||||
const participantEvents = await firstValueFrom(
|
||||
pool
|
||||
.request([relayUrl], [adminsFilter, membersFilter], { eventStore })
|
||||
.pipe(toArray()),
|
||||
);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
console.log("[NIP-29] Participants fetch timeout");
|
||||
resolve();
|
||||
}, 5000);
|
||||
console.log(`[NIP-29] Got ${participantEvents.length} participant events`);
|
||||
|
||||
const sub = participantsObs.subscribe({
|
||||
next: (response) => {
|
||||
if (typeof response === "string") {
|
||||
// EOSE received
|
||||
clearTimeout(timeout);
|
||||
console.log(
|
||||
`[NIP-29] Got ${participantEvents.length} participant events`,
|
||||
);
|
||||
sub.unsubscribe();
|
||||
resolve();
|
||||
} else {
|
||||
// Event received
|
||||
participantEvents.push(response);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
clearTimeout(timeout);
|
||||
console.error("[NIP-29] Participants fetch error:", err);
|
||||
sub.unsubscribe();
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
const adminEvents = participantEvents.filter((e) => e.kind === 39001);
|
||||
const memberEvents = participantEvents.filter((e) => e.kind === 39002);
|
||||
|
||||
// Helper to validate and normalize role names
|
||||
const normalizeRole = (role: string | undefined): ParticipantRole => {
|
||||
if (!role) return "member";
|
||||
const normalizeRole = (
|
||||
role: string | undefined,
|
||||
defaultRole: ParticipantRole,
|
||||
): ParticipantRole => {
|
||||
if (!role) return defaultRole;
|
||||
const lower = role.toLowerCase();
|
||||
if (lower === "admin") return "admin";
|
||||
if (lower === "moderator") return "moderator";
|
||||
if (lower === "host") return "host";
|
||||
// Default to member for unknown roles
|
||||
return "member";
|
||||
// Default to provided default for unknown roles
|
||||
return defaultRole;
|
||||
};
|
||||
|
||||
// Extract participants from both admins and members events
|
||||
const participantsMap = new Map<string, Participant>();
|
||||
|
||||
// Process kind:39001 (admins with roles)
|
||||
const adminEvents = participantEvents.filter((e) => e.kind === 39001);
|
||||
// Users in kind 39001 are admins by default
|
||||
for (const event of adminEvents) {
|
||||
// Each p tag: ["p", "<pubkey>", "<role1>", "<role2>", ...]
|
||||
for (const tag of event.tags) {
|
||||
if (tag[0] === "p" && tag[1]) {
|
||||
const pubkey = tag[1];
|
||||
const roles = tag.slice(2).filter((r) => r); // Get all roles after pubkey
|
||||
const primaryRole = normalizeRole(roles[0]); // Use first role as primary
|
||||
const primaryRole = normalizeRole(roles[0], "admin"); // Default to "admin" for kind 39001
|
||||
participantsMap.set(pubkey, { pubkey, role: primaryRole });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process kind:39002 (members without roles)
|
||||
const memberEvents = participantEvents.filter((e) => e.kind === 39002);
|
||||
// Users in kind 39002 are regular members
|
||||
for (const event of memberEvents) {
|
||||
// Each p tag: ["p", "<pubkey>"]
|
||||
for (const tag of event.tags) {
|
||||
|
||||
Reference in New Issue
Block a user