Position-based cleanup (no name collisions) of unused catch bindings and unused single-line pure consts across 12 files. Automated destructure/prop removals were proven unsafe (shadowed names + typed shorthand), so those remain. Warning backlog: 76 -> 60 (39 no-unused-vars, 14 exhaustive-deps, 7 react-refresh). Gate green: 101 tests, 0 errors, boot smoke passing.
123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { useNostr } from '@nostrify/react';
|
|
import { useCurrentUser } from './useCurrentUser';
|
|
import { onlyVerified } from '@/lib/verifyEvents';
|
|
|
|
/**
|
|
* Hook to fetch trust scores for multiple pubkeys in batch.
|
|
* More efficient than calling useTrustScore multiple times.
|
|
*
|
|
* @param pubkeys - Array of pubkeys to calculate trust scores for
|
|
* @returns Map of pubkey to trust score (0-100)
|
|
*/
|
|
export function useBatchTrustScores(pubkeys: string[]) {
|
|
const { nostr } = useNostr();
|
|
const { user } = useCurrentUser();
|
|
|
|
return useQuery({
|
|
queryKey: ['batch-trust-scores', pubkeys.sort().join(','), user?.pubkey],
|
|
queryFn: async (c) => {
|
|
const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]);
|
|
const scores = new Map<string, number>();
|
|
|
|
try {
|
|
// Get user's follow list (kind 3)
|
|
const sourcePubkey = user?.pubkey || '20e7c953439e66f3882ab96fe0faa8ba246d6fb12c03b0cb4e2dc4623b8bcb21';
|
|
|
|
const followListEvents = onlyVerified(await nostr.query(
|
|
[{ kinds: [3], authors: [sourcePubkey], limit: 1 }],
|
|
{ signal }
|
|
));
|
|
|
|
const followList = followListEvents[0];
|
|
if (!followList) {
|
|
// No follow list (e.g. logged-out or new account) — default all to 0.
|
|
pubkeys.forEach((pk) => scores.set(pk, 0));
|
|
return scores;
|
|
}
|
|
|
|
// Extract followed pubkeys
|
|
const following = new Set(
|
|
followList.tags
|
|
.filter(([tag]) => tag === 'p')
|
|
.map(([, pubkey]) => pubkey)
|
|
);
|
|
|
|
// Process each pubkey
|
|
for (const targetPubkey of pubkeys) {
|
|
// Self score = 100
|
|
if (user?.pubkey && targetPubkey === user.pubkey) {
|
|
scores.set(targetPubkey, 100);
|
|
continue;
|
|
}
|
|
|
|
// Direct follow = 90
|
|
if (following.has(targetPubkey)) {
|
|
scores.set(targetPubkey, 90);
|
|
continue;
|
|
}
|
|
|
|
// Default to 0 (will check friend-of-friend and NIP-05 later)
|
|
scores.set(targetPubkey, 0);
|
|
}
|
|
|
|
// Get friend-of-friend relationships (batch query)
|
|
const uncheckedPubkeys = pubkeys.filter((pk) => scores.get(pk) === 0);
|
|
|
|
if (uncheckedPubkeys.length > 0) {
|
|
const friendsFollowLists = onlyVerified(await nostr.query(
|
|
[{ kinds: [3], authors: Array.from(following), limit: 100 }],
|
|
{ signal }
|
|
));
|
|
|
|
// Build a set of all friends-of-friends
|
|
const friendsOfFriends = new Set<string>();
|
|
for (const friendList of friendsFollowLists) {
|
|
friendList.tags
|
|
.filter(([tag]) => tag === 'p')
|
|
.forEach(([, pubkey]) => friendsOfFriends.add(pubkey));
|
|
}
|
|
|
|
// Check friend-of-friend = 60
|
|
for (const pubkey of uncheckedPubkeys) {
|
|
if (friendsOfFriends.has(pubkey)) {
|
|
scores.set(pubkey, 60);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check NIP-05 verification for remaining 0-score pubkeys
|
|
const unverifiedPubkeys = pubkeys.filter((pk) => scores.get(pk) === 0);
|
|
|
|
if (unverifiedPubkeys.length > 0) {
|
|
const profileEvents = onlyVerified(await nostr.query(
|
|
[{ kinds: [0], authors: unverifiedPubkeys }],
|
|
{ signal }
|
|
));
|
|
|
|
for (const profileEvent of profileEvents) {
|
|
try {
|
|
const metadata = JSON.parse(profileEvent.content);
|
|
if (metadata.nip05) {
|
|
scores.set(profileEvent.pubkey, 30);
|
|
}
|
|
} catch {
|
|
// Ignore JSON parse errors
|
|
}
|
|
}
|
|
}
|
|
|
|
return scores;
|
|
} catch (error) {
|
|
console.warn('[useBatchTrustScores] Error calculating batch trust scores:', error);
|
|
// Return default scores of 0
|
|
pubkeys.forEach((pk) => scores.set(pk, 0));
|
|
return scores;
|
|
}
|
|
},
|
|
enabled: pubkeys.length > 0,
|
|
staleTime: 3600000, // Cache for 1 hour
|
|
gcTime: 86400000, // Keep in cache for 24 hours
|
|
retry: 1,
|
|
});
|
|
}
|