mirror of
https://github.com/mroxso/zelo-news.git
synced 2026-06-06 18:41:14 +02:00
Implement NIP-89 client tag display and improved tag format
Co-authored-by: mroxso <24775431+mroxso@users.noreply.github.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { ZapButton } from '@/components/ZapButton';
|
||||
import { BookmarkButton } from '@/components/BookmarkButton';
|
||||
import { ReadingTime } from '@/components/ReadingTime';
|
||||
import { ArticleProgressBar } from '@/components/ArticleProgressBar';
|
||||
import { ClientTag } from '@/components/ClientTag';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
@@ -291,6 +292,10 @@ export function ArticleView({ post }: ArticleViewProps) {
|
||||
|
||||
<Separator className="my-8" />
|
||||
|
||||
<div className="mb-8">
|
||||
<ClientTag event={post} />
|
||||
</div>
|
||||
|
||||
<CommentsSection root={post} />
|
||||
</article>
|
||||
</div>
|
||||
|
||||
30
src/components/ClientTag.tsx
Normal file
30
src/components/ClientTag.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
import { parseClientTag } from '@/lib/parseClientTag';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Smartphone } from 'lucide-react';
|
||||
|
||||
interface ClientTagProps {
|
||||
event: NostrEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the client tag information from a Nostr event according to NIP-89
|
||||
* Shows the client name that was used to publish the event
|
||||
*/
|
||||
export function ClientTag({ event }: ClientTagProps) {
|
||||
const clientInfo = parseClientTag(event);
|
||||
|
||||
if (!clientInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Smartphone className="h-4 w-4" />
|
||||
<span>Published with</span>
|
||||
<Badge variant="secondary" className="font-normal">
|
||||
{clientInfo.name}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,9 +14,11 @@ export function useNostrPublish(): UseMutationResult<NostrEvent> {
|
||||
if (user) {
|
||||
const tags = t.tags ?? [];
|
||||
|
||||
// Add the client tag if it doesn't exist
|
||||
// Add the client tag if it doesn't exist (NIP-89)
|
||||
// Format: ["client", "Client Name", "31990:pubkey:d-identifier", "wss://relay-hint"]
|
||||
// The address and relay are optional but recommended for better discoverability
|
||||
if (location.protocol === "https:" && !tags.some(([name]) => name === "client")) {
|
||||
tags.push(["client", location.hostname]);
|
||||
tags.push(["client", "zelo.news"]);
|
||||
}
|
||||
|
||||
const event = await user.signer.signEvent({
|
||||
|
||||
75
src/lib/parseClientTag.test.ts
Normal file
75
src/lib/parseClientTag.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseClientTag } from './parseClientTag';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
describe('parseClientTag', () => {
|
||||
it('should return undefined if no client tag exists', () => {
|
||||
const event: NostrEvent = {
|
||||
id: '123',
|
||||
pubkey: 'abc',
|
||||
created_at: 1234567890,
|
||||
kind: 1,
|
||||
tags: [],
|
||||
content: 'test',
|
||||
sig: 'sig',
|
||||
};
|
||||
|
||||
const result = parseClientTag(event);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should parse a client tag with only name', () => {
|
||||
const event: NostrEvent = {
|
||||
id: '123',
|
||||
pubkey: 'abc',
|
||||
created_at: 1234567890,
|
||||
kind: 1,
|
||||
tags: [['client', 'zelo.news']],
|
||||
content: 'test',
|
||||
sig: 'sig',
|
||||
};
|
||||
|
||||
const result = parseClientTag(event);
|
||||
expect(result).toEqual({
|
||||
name: 'zelo.news',
|
||||
address: undefined,
|
||||
relay: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse a full NIP-89 client tag', () => {
|
||||
const event: NostrEvent = {
|
||||
id: '123',
|
||||
pubkey: 'abc',
|
||||
created_at: 1234567890,
|
||||
kind: 1,
|
||||
tags: [
|
||||
['client', 'My Client', '31990:pubkey123:identifier', 'wss://relay.example.com'],
|
||||
],
|
||||
content: 'test',
|
||||
sig: 'sig',
|
||||
};
|
||||
|
||||
const result = parseClientTag(event);
|
||||
expect(result).toEqual({
|
||||
name: 'My Client',
|
||||
address: '31990:pubkey123:identifier',
|
||||
relay: 'wss://relay.example.com',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined for empty client tag', () => {
|
||||
const event: NostrEvent = {
|
||||
id: '123',
|
||||
pubkey: 'abc',
|
||||
created_at: 1234567890,
|
||||
kind: 1,
|
||||
tags: [['client']],
|
||||
content: 'test',
|
||||
sig: 'sig',
|
||||
};
|
||||
|
||||
const result = parseClientTag(event);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
39
src/lib/parseClientTag.ts
Normal file
39
src/lib/parseClientTag.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
export interface ClientTagInfo {
|
||||
name: string;
|
||||
address?: string; // 31990:pubkey:d-identifier
|
||||
relay?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and parses the client tag from a Nostr event according to NIP-89
|
||||
*
|
||||
* Client tag format: ["client", "My Client", "31990:app-pubkey:<d-identifier>", "wss://relay"]
|
||||
* - First element: tag name "client"
|
||||
* - Second element: client name (required)
|
||||
* - Third element: handler address (optional)
|
||||
* - Fourth element: relay hint (optional)
|
||||
*
|
||||
* @param event - The Nostr event to extract the client tag from
|
||||
* @returns ClientTagInfo if a client tag exists, undefined otherwise
|
||||
*/
|
||||
export function parseClientTag(event: NostrEvent): ClientTagInfo | undefined {
|
||||
const clientTag = event.tags.find(([name]) => name === 'client');
|
||||
|
||||
if (!clientTag || clientTag.length < 2) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [, name, address, relay] = clientTag;
|
||||
|
||||
if (!name) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
address: address || undefined,
|
||||
relay: relay || undefined,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user