fully support nip05

This commit is contained in:
Ren Amamiya 2023-09-01 08:58:33 +07:00
parent 0d207d471c
commit cc315a190a
18 changed files with 158 additions and 44 deletions

View File

@ -36,7 +36,6 @@
"@tauri-apps/plugin-clipboard-manager": "github:tauri-apps/tauri-plugin-clipboard-manager#v2",
"@tauri-apps/plugin-dialog": "github:tauri-apps/tauri-plugin-dialog#v2",
"@tauri-apps/plugin-fs": "github:tauri-apps/tauri-plugin-fs#v2",
"@tauri-apps/plugin-http": "github:tauri-apps/tauri-plugin-http#v2",
"@tauri-apps/plugin-notification": "github:tauri-apps/tauri-plugin-notification#v2",
"@tauri-apps/plugin-os": "github:tauri-apps/tauri-plugin-os#v2",
"@tauri-apps/plugin-process": "github:tauri-apps/tauri-plugin-process#v2",

View File

@ -55,9 +55,6 @@ dependencies:
'@tauri-apps/plugin-fs':
specifier: github:tauri-apps/tauri-plugin-fs#v2
version: github.com/tauri-apps/tauri-plugin-fs/c90fd8bcad3be92f34a0642d67ab1a6ad9f73dee
'@tauri-apps/plugin-http':
specifier: github:tauri-apps/tauri-plugin-http#v2
version: github.com/tauri-apps/tauri-plugin-http/50bc7956907eef54a20255b62ea57ff4c2e8585a
'@tauri-apps/plugin-notification':
specifier: github:tauri-apps/tauri-plugin-notification#v2
version: github.com/tauri-apps/tauri-plugin-notification/66c0779854575ec7860eadcd98673c39627e81a6
@ -7159,14 +7156,6 @@ packages:
'@tauri-apps/api': 2.0.0-alpha.6
dev: false
github.com/tauri-apps/tauri-plugin-http/50bc7956907eef54a20255b62ea57ff4c2e8585a:
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-http/tar.gz/50bc7956907eef54a20255b62ea57ff4c2e8585a}
name: '@tauri-apps/plugin-http'
version: 2.0.0-alpha.1
dependencies:
'@tauri-apps/api': 2.0.0-alpha.6
dev: false
github.com/tauri-apps/tauri-plugin-notification/66c0779854575ec7860eadcd98673c39627e81a6:
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-notification/tar.gz/66c0779854575ec7860eadcd98673c39627e81a6}
name: '@tauri-apps/plugin-notification'

View File

@ -27,7 +27,7 @@ export function User({ pubkey, fallback }: { pubkey: string; fallback?: string }
/>
<div className="flex w-full flex-1 flex-col items-start text-start">
<p className="max-w-[15rem] truncate font-medium leading-tight text-white">
{user?.name || user?.display_name || user?.nip05}
{user?.name || user?.display_name}
</p>
<span className="max-w-[15rem] truncate leading-tight text-white/50">
{displayNpub(pubkey, 16)}

View File

@ -28,7 +28,7 @@ export function UserRelay({ pubkey }: { pubkey: string }) {
className="h-5 w-5 shrink-0 rounded object-cover"
/>
<span className="truncate text-sm font-medium leading-none text-white">
{user?.name || user?.display_name || user?.nip05 || displayNpub(pubkey, 16)}
{user?.name || user?.display_name || displayNpub(pubkey, 16)}
</span>
</div>
</div>

View File

@ -38,7 +38,7 @@ export function ChatsListItem({ pubkey }: { pubkey: string }) {
/>
<div className="inline-flex w-full flex-1 items-center justify-between">
<h5 className="max-w-[10rem] truncate">
{user?.nip05 || user?.name || user?.display_name || displayNpub(pubkey, 16)}
{user?.name || user?.display_name || displayNpub(pubkey, 16)}
</h5>
</div>
</NavLink>

View File

@ -1,6 +1,7 @@
import { Link } from 'react-router-dom';
import { Image } from '@shared/image';
import { NIP05 } from '@shared/nip05';
import { useProfile } from '@utils/hooks/useProfile';
import { displayNpub } from '@utils/shortenKey';
@ -23,9 +24,17 @@ export function ChatSidebar({ pubkey }: { pubkey: string }) {
<h3 className="text-lg font-semibold leading-none">
{user?.display_name || user?.name}
</h3>
<h5 className="leading-none text-white/50">
{user?.nip05 || displayNpub(pubkey, 16)}
</h5>
{user?.nip05 ? (
<NIP05
pubkey={pubkey}
nip05={user?.nip05}
className="leading-none text-white/50"
/>
) : (
<span className="leading-none text-white/50">
{displayNpub(pubkey, 16)}
</span>
)}
</div>
<div>
<p className="leading-tight">{user?.bio || user?.about}</p>

View File

@ -25,7 +25,7 @@ export function NotiUser({ pubkey }: { pubkey: string }) {
className="h-11 w-11 shrink-0 rounded-lg object-cover"
/>
<span className="max-w-[10rem] flex-1 truncate font-medium leading-none text-white">
{user?.nip05 || user?.name || user?.display_name || displayNpub(pubkey, 16)}
{user?.name || user?.display_name || displayNpub(pubkey, 16)}
</span>
</div>
);

View File

@ -7,10 +7,11 @@ import { UserStats } from '@app/users/components/stats';
import { useStorage } from '@libs/storage/provider';
import { Image } from '@shared/image';
import { NIP05 } from '@shared/nip05';
import { useNostr } from '@utils/hooks/useNostr';
import { useProfile } from '@utils/hooks/useProfile';
import { shortenKey } from '@utils/shortenKey';
import { displayNpub } from '@utils/shortenKey';
export function UserProfile({ pubkey }: { pubkey: string }) {
const { db } = useStorage();
@ -68,13 +69,21 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
/>
<div className="mt-2 flex flex-1 flex-col gap-6">
<div className="flex flex-col items-center gap-1">
<div className="inline-flex flex-col gap-1.5">
<div className="inline-flex flex-col items-center gap-1.5">
<h5 className="text-center text-xl font-semibold leading-none">
{user.display_name || user.displayName || user.name || 'No name'}
</h5>
<span className="max-w-[15rem] truncate text-center leading-none text-white/50">
{user.nip05 || user.username || shortenKey(pubkey)}
</span>
{user.nip05 ? (
<NIP05
pubkey={pubkey}
nip05={user?.nip05}
className="max-w-[15rem] truncate text-sm leading-none text-white/50"
/>
) : (
<span className="max-w-[15rem] truncate text-sm leading-none text-white/50">
{displayNpub(pubkey, 16)}
</span>
)}
</div>
<div className="flex flex-col gap-6">
{user.about || user.bio ? (

View File

@ -67,7 +67,7 @@ export function ActiveAccount() {
/>
<div className="flex w-full flex-1 flex-col items-start gap-1.5">
<p className="max-w-[10rem] truncate font-bold leading-none text-white">
{user?.name || user?.display_name || user?.nip05}
{user?.name || user?.display_name}
</p>
<span className="max-w-[8rem] truncate text-sm leading-none text-white/50">
{displayNpub(db.account.pubkey, 16)}

View File

@ -1,6 +1,7 @@
import { Image } from '@shared/image';
import { useProfile } from '@utils/hooks/useProfile';
import { displayNpub } from '@utils/shortenKey';
export function ComposerUser({ pubkey }: { pubkey: string }) {
const { user } = useProfile(pubkey);
@ -13,9 +14,7 @@ export function ComposerUser({ pubkey }: { pubkey: string }) {
className="h-10 w-10 shrink-0 rounded-lg"
/>
<h5 className="text-base font-semibold leading-none text-white">
{user?.nip05 || user?.name || (
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
)}
{user?.name || user?.display_name || displayNpub(pubkey, 16)}
</h5>
</div>
);

View File

@ -56,3 +56,4 @@ export * from './expand';
export * from './focus';
export * from './chevronUp';
export * from './secure';
export * from './verified';

View File

@ -0,0 +1,21 @@
import { SVGProps } from 'react';
export function VerifiedIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
fill="currentColor"
fillRule="evenodd"
d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm3.58 7.975a.75.75 0 00-1.16-.95l-3.976 4.859L9.03 12.47a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.11-.055l4.5-5.5z"
clipRule="evenodd"
/>
</svg>
);
}

73
src/shared/nip05.tsx Normal file
View File

@ -0,0 +1,73 @@
import { useQuery } from '@tanstack/react-query';
import { twMerge } from 'tailwind-merge';
import { UnverifiedIcon, VerifiedIcon } from '@shared/icons';
interface NIP05 {
names: {
[key: string]: string;
};
}
export function NIP05({
pubkey,
nip05,
className,
}: {
pubkey: string;
nip05: string;
className?: string;
}) {
const { status, data } = useQuery(
[nip05],
async () => {
try {
const username = nip05.split('@')[0];
const service = nip05.split('@')[1];
// #TODO: use tauri native fetch to avoid CORS
const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`;
const res = await fetch(verifyURL, {
method: 'GET',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
});
if (!res.ok) throw new Error(`Failed to fetch NIP-05 service: ${nip05}`);
const data: NIP05 = await res.json();
if (data.names) {
if (data.names.username !== pubkey) return false;
return true;
}
} catch (e) {
throw new Error(`Failed to verify NIP-05, error: ${e}`);
}
},
{
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
staleTime: Infinity,
}
);
if (status === 'loading') {
<div className="h-3 w-20 animate-pulse rounded bg-white/10" />;
}
return (
<div className={twMerge('leadning-none inline-flex items-center gap-1', className)}>
<p>{nip05}</p>
<div className="shrink-0">
{data === true ? (
<VerifiedIcon className="h-3 w-3 text-green-500" />
) : (
<UnverifiedIcon className="h-3 w-3 text-red-500" />
)}
</div>
</div>
);
}

View File

@ -17,17 +17,13 @@ export function MentionUser({ pubkey }: { pubkey: string }) {
onClick={() =>
setWidget(db, {
kind: WidgetKinds.local.user,
title: user?.nip05 || user?.name || user?.display_name,
title: user?.name || user?.display_name,
content: pubkey,
})
}
className="break-words text-fuchsia-400 hover:text-fuchsia-500"
>
{user?.nip05 ||
user?.name ||
user?.display_name ||
user?.username ||
displayNpub(pubkey, 16)}
{user?.name || user?.display_name || user?.username || displayNpub(pubkey, 16)}
</button>
);
}

View File

@ -52,7 +52,7 @@ export function NoteReplyForm({ id, pubkey }: { id: string; pubkey: string }) {
<div>
<p className="mb-1 text-sm leading-none text-white/50">Reply as</p>
<p className="text-sm font-medium leading-none text-white">
{user?.nip05 || user?.name || displayNpub(pubkey, 16)}
{user?.name || displayNpub(pubkey, 16)}
</p>
</div>
</div>

View File

@ -19,7 +19,7 @@ export function RepostUser({ pubkey }: { pubkey: string }) {
/>
<div className="inline-flex items-baseline gap-1">
<h5 className="max-w-[18rem] truncate text-white/50">
{user?.nip05 || user?.name || user?.display_name || shortenKey(pubkey)}
{user?.name || user?.display_name || shortenKey(pubkey)}
</h5>
<span className="text-white/50">reposted</span>
</div>

View File

@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { Image } from '@shared/image';
import { NIP05 } from '@shared/nip05';
import { formatCreatedAt } from '@utils/createdAt';
import { useProfile } from '@utils/hooks/useProfile';
@ -79,7 +80,7 @@ export function User({
size === 'small' ? 'max-w-[10rem]' : 'max-w-[15rem]'
)}
>
{user?.nip05 || user?.name || user?.display_name || displayNpub(pubkey, 16)}
{user?.display_name || user?.name || displayNpub(pubkey, 16)}
</h5>
<span className="leading-none text-white/50">·</span>
<span className="leading-none text-white/50">{createdAt}</span>
@ -87,7 +88,7 @@ export function User({
</div>
<Popover.Portal>
<Popover.Content
className="w-[300px] overflow-hidden rounded-md bg-white/10 backdrop-blur-3xl backdrop-blur-xl focus:outline-none"
className="w-[300px] overflow-hidden rounded-md bg-white/10 backdrop-blur-3xl focus:outline-none"
sideOffset={5}
>
<div className="flex gap-2.5 border-b border-white/5 px-3 py-3">
@ -101,9 +102,17 @@ export function User({
<h5 className="text-sm font-semibold leading-none">
{user?.display_name || user?.name || user?.username}
</h5>
<span className="max-w-[10rem] truncate text-sm leading-none text-white/50">
{user?.nip05 || displayNpub(pubkey, 16)}
</span>
{user?.nip05 ? (
<NIP05
pubkey={pubkey}
nip05={user?.nip05}
className="max-w-[15rem] truncate text-sm leading-none text-white/50"
/>
) : (
<span className="max-w-[15rem] truncate text-sm leading-none text-white/50">
{displayNpub(pubkey, 16)}
</span>
)}
</div>
<div>
<p className="line-clamp-3 break-all leading-tight text-white">

View File

@ -6,6 +6,7 @@ import { UserStats } from '@app/users/components/stats';
import { useStorage } from '@libs/storage/provider';
import { Image } from '@shared/image';
import { NIP05 } from '@shared/nip05';
import { useNostr } from '@utils/hooks/useNostr';
import { useProfile } from '@utils/hooks/useProfile';
@ -58,9 +59,17 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
<h5 className="text-lg font-semibold leading-none">
{user?.displayName || user?.name || 'No name'}
</h5>
<span className="max-w-[15rem] truncate text-sm leading-none text-white/50">
{user?.nip05 || displayNpub(pubkey, 16)}
</span>
{user?.nip05 ? (
<NIP05
pubkey={pubkey}
nip05={user?.nip05}
className="max-w-[15rem] truncate text-sm leading-none text-white/50"
/>
) : (
<span className="max-w-[15rem] truncate text-sm leading-none text-white/50">
{displayNpub(pubkey, 16)}
</span>
)}
</div>
<div className="flex flex-col gap-4">
<p className="mt-2 max-w-[500px] select-text break-words text-white">