mirror of
https://github.com/layer-systems/website.git
synced 2026-09-13 14:14:07 +02:00
fix: dedupe multiple revisions of the same web bookmark
Per an earlier "previously missed" finding: useMyWebBookmarks() returned every kind-39701 event a relay handed back, but for an addressable event the pool can return more than one revision of the same d tag (an edit history, or relays disagreeing on what's current), which showed up as duplicate rows for the same URL. Extracted dedupeLatestByDTag() (keeps the newest per d, newest-first) and covered it with regression tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { bookmarkDTag, bookmarkUrl } from './useWebBookmarks';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
import { bookmarkDTag, bookmarkUrl, dedupeLatestByDTag } from './useWebBookmarks';
|
||||
|
||||
function bookmarkEvent(dTag: string, createdAt: number, id = `${dTag}-${createdAt}`): NostrEvent {
|
||||
return { id, pubkey: 'author', created_at: createdAt, kind: 39701, tags: [['d', dTag]], content: '', sig: '' };
|
||||
}
|
||||
|
||||
describe('bookmarkDTag / bookmarkUrl', () => {
|
||||
it('strips the https scheme per NIP-B0', () => {
|
||||
@@ -41,3 +46,30 @@ describe('bookmarkDTag / bookmarkUrl', () => {
|
||||
expect(bookmarkUrl('alice.blog:8080/post')).toBe('https://alice.blog:8080/post');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dedupeLatestByDTag', () => {
|
||||
it('keeps only the newest event for each d tag', () => {
|
||||
const older = bookmarkEvent('alice.blog/post', 100);
|
||||
const newer = bookmarkEvent('alice.blog/post', 200);
|
||||
const result = dedupeLatestByDTag([older, newer]);
|
||||
expect(result).toEqual([newer]);
|
||||
});
|
||||
|
||||
it('is order-independent', () => {
|
||||
const older = bookmarkEvent('alice.blog/post', 100);
|
||||
const newer = bookmarkEvent('alice.blog/post', 200);
|
||||
expect(dedupeLatestByDTag([newer, older])).toEqual([newer]);
|
||||
});
|
||||
|
||||
it('keeps distinct d tags separately, sorted newest first', () => {
|
||||
const a = bookmarkEvent('a.com', 100);
|
||||
const b = bookmarkEvent('b.com', 300);
|
||||
const c = bookmarkEvent('c.com', 200);
|
||||
expect(dedupeLatestByDTag([a, b, c])).toEqual([b, c, a]);
|
||||
});
|
||||
|
||||
it('drops events with no d tag', () => {
|
||||
const noD: NostrEvent = { id: 'x', pubkey: 'author', created_at: 0, kind: 39701, tags: [], content: '', sig: '' };
|
||||
expect(dedupeLatestByDTag([noD])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,6 +44,22 @@ export interface WebBookmarkInput {
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Addressable events: relays across the pool can hand back more than one
|
||||
* revision of the same `d` tag (an edit history, or just multiple relays
|
||||
* disagreeing on what's current). Keeps only the newest per `d`, newest first.
|
||||
*/
|
||||
export function dedupeLatestByDTag(events: NostrEvent[]): NostrEvent[] {
|
||||
const latest = new Map<string, NostrEvent>();
|
||||
for (const event of events) {
|
||||
const dTag = tagValue(event, 'd');
|
||||
if (!dTag) continue;
|
||||
const current = latest.get(dTag);
|
||||
if (!current || event.created_at > current.created_at) latest.set(dTag, event);
|
||||
}
|
||||
return [...latest.values()].sort((a, b) => b.created_at - a.created_at);
|
||||
}
|
||||
|
||||
export function useMyWebBookmarks() {
|
||||
const { nostr } = useNostr();
|
||||
const { user } = useCurrentUser();
|
||||
@@ -56,9 +72,7 @@ export function useMyWebBookmarks() {
|
||||
[{ kinds: [WEB_BOOKMARK_KIND], authors: [user!.pubkey], limit: 200 }],
|
||||
{ signal: AbortSignal.any([signal, AbortSignal.timeout(6000)]) },
|
||||
);
|
||||
return events
|
||||
.filter((event) => Boolean(tagValue(event, 'd')))
|
||||
.sort((a, b) => b.created_at - a.created_at);
|
||||
return dedupeLatestByDTag(events);
|
||||
},
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user