feat(ark): refactor

This commit is contained in:
2023-12-16 07:47:00 +07:00
parent ba93bdbb91
commit 17c64ee357
18 changed files with 449 additions and 483 deletions

View File

@@ -22,8 +22,6 @@ export function CreateAccountScreen() {
const [keys, setKeys] = useState<null | {
npub: string;
nsec: string;
pubkey: string;
privkey: string;
}>(null);
const ark = useArk();
@@ -64,7 +62,6 @@ export function CreateAccountScreen() {
content: JSON.stringify(profile),
kind: NDKKind.Metadata,
tags: [],
publish: true,
});
if (publish) {
@@ -77,18 +74,12 @@ export function CreateAccountScreen() {
await ark.createEvent({
kind: NDKKind.RelayList,
tags: [ark.relays],
publish: true,
});
setKeys({
npub: userNpub,
nsec: userNsec,
pubkey: userPubkey,
privkey: userPrivkey,
});
setKeys({ npub: userNpub, nsec: userNsec });
setLoading(false);
} else {
toast('Cannot publish user profile, please try again later.');
toast.error('Cannot publish user profile, please try again later.');
setLoading(false);
}
} catch (e) {
@@ -222,7 +213,7 @@ export function CreateAccountScreen() {
}}
className="rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950"
>
<User pubkey={keys.pubkey} variant="simple" />
<User pubkey={keys.npub} variant="simple" />
</motion.div>
<motion.div
initial={{ opacity: 0, y: 80 }}

View File

@@ -70,7 +70,6 @@ export function FollowScreen() {
if (item.startsWith('npub1')) return ['p', nip19.decode(item).data as string];
return ['p', item];
}),
publish: true,
});
if (publish) {

View File

@@ -1,21 +1,17 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { Link } from 'react-router-dom';
import { useArk } from '@libs/ark';
import { EditIcon, ReactionIcon, ReplyIcon, RepostIcon, ZapIcon } from '@shared/icons';
import { TextNote } from '@shared/notes';
export function TutorialNoteScreen() {
const ark = useArk();
const exampleEvent = ark.createNDKEvent({
event: {
id: 'a3527670dd9b178bf7c2a9ea673b63bc8bfe774942b196691145343623c45821',
pubkey: '04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9',
created_at: 1701355223,
kind: 1,
tags: [],
content: 'good morning nostr, stay humble and stack sats 🫡',
sig: '9e0bd67ec25598744f20bff0fe360fdf190c4240edb9eea260e50f77e07f94ea767ececcc6270819b7f64e5e7ca1fe20b4971f46dc120e6db43114557f3a6dae',
},
const exampleEvent = new NDKEvent(undefined, {
id: 'a3527670dd9b178bf7c2a9ea673b63bc8bfe774942b196691145343623c45821',
pubkey: '04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9',
created_at: 1701355223,
kind: 1,
tags: [],
content: 'good morning nostr, stay humble and stack sats 🫡',
sig: '9e0bd67ec25598744f20bff0fe360fdf190c4240edb9eea260e50f77e07f94ea767ececcc6270819b7f64e5e7ca1fe20b4971f46dc120e6db43114557f3a6dae',
});
return (

View File

@@ -93,11 +93,10 @@ export function NewArticleScreen() {
content,
tags,
kind: NDKKind.Article,
publish: true,
});
if (publish) {
toast.success(`Broadcasted to ${publish} relays successfully.`);
toast.success(`Broadcasted to ${publish.seens.length} relays successfully.`);
// update state
setLoading(false);

View File

@@ -91,11 +91,10 @@ export function NewFileScreen() {
kind: 1063,
tags: metadata,
content: caption,
publish: true,
});
if (publish) {
toast.success(`Broadcasted to ${publish} relays successfully.`);
toast.success(`Broadcasted to ${publish.seens.length} relays successfully.`);
setMetadata(null);
setIsPublish(false);
}

View File

@@ -1,4 +1,4 @@
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { NDKKind } from '@nostr-dev-kit/ndk';
import CharacterCount from '@tiptap/extension-character-count';
import Image from '@tiptap/extension-image';
import Mention from '@tiptap/extension-mention';
@@ -82,18 +82,16 @@ export function NewPostScreen() {
const rootReplyTo = searchParams.get('rootReplyTo');
// publish event
const event = (await ark.createEvent({
const publish = await ark.createEvent({
kind: NDKKind.Text,
tags: [],
content: serializedContent,
replyTo,
rootReplyTo,
})) as NDKEvent;
const publish = await event.publish();
});
if (publish) {
toast.success(`Broadcasted to ${publish.size} relays successfully.`);
toast.success(`Broadcasted to ${publish.seens.length} relays successfully.`);
// update state
setLoading(false);
@@ -103,7 +101,7 @@ export function NewPostScreen() {
if (!replyTo) {
addWidget.mutate({
title: 'Thread',
content: event.id,
content: publish.id,
kind: WIDGET_KIND.thread,
});
}

View File

@@ -101,7 +101,6 @@ export function EditProfileScreen() {
kind: NDKKind.Metadata,
tags: [],
content: JSON.stringify(content),
publish: true,
});
if (publish) {

View File

@@ -22,8 +22,8 @@ import {
normalizeRelayUrl,
normalizeRelayUrlSet,
} from 'nostr-fetch';
import { toast } from 'sonner';
import { NDKCacheAdapterTauri } from '@libs/ark';
import { nip19 } from 'nostr-tools';
import { NDKCacheAdapterTauri } from '@libs/cache';
import {
Account,
NDKCacheUser,
@@ -34,9 +34,9 @@ import {
} from '@utils/types';
export class Ark {
#ndk: NDK;
#fetcher: NostrFetcher;
#storage: Database;
public ndk: NDK;
public fetcher: NostrFetcher;
public account: Account | null;
public relays: string[] | null;
public readyToSign: boolean;
@@ -123,20 +123,13 @@ export class Ark {
return new NDKPrivateKeySigner(userPrivkey);
} catch (e) {
console.log(e);
if (e === 'Token already redeemed') {
toast.info(
'nsecbunker token already redeemed. You need to re-login with another token.'
);
await this.logout();
}
this.readyToSign = false;
return null;
}
}
public async init() {
const settings = await this.getAllSettings();
for (const item of settings) {
if (item.key === 'nsecbunker') this.settings.bunker = !!parseInt(item.value);
if (item.key === 'outbox') this.settings.outbox = !!parseInt(item.value);
@@ -147,8 +140,7 @@ export class Ark {
const explicitRelayUrls = normalizeRelayUrlSet([
'wss://relay.damus.io',
'wss://relay.nostr.band',
'wss://nos.lol',
'wss://relay.nostr.band/all',
'wss://nostr.mutinywallet.com',
]);
@@ -184,19 +176,19 @@ export class Ark {
const user = ndk.getUser({ pubkey: this.account.pubkey });
ndk.activeUser = user;
const contacts = await user.follows(undefined /* outbox */);
const contacts = await user.follows();
this.account.contacts = [...contacts].map((user) => user.pubkey);
}
this.relays = [...ndk.pool.relays.values()].map((relay) => relay.url);
this.#ndk = ndk;
this.#fetcher = fetcher;
this.ndk = ndk;
this.fetcher = fetcher;
}
public updateNostrSigner({ signer }: { signer: NDKNip46Signer | NDKPrivateKeySigner }) {
this.#ndk.signer = signer;
this.ndk.signer = signer;
this.readyToSign = true;
return this.#ndk.signer;
return this.ndk.signer;
}
public async getAllCacheUsers() {
@@ -390,14 +382,14 @@ export class Ark {
}
public async logout() {
await this.#keyring_remove(this.account.pubkey);
await this.#keyring_remove(`${this.account.pubkey}-nsecbunker`);
await this.#storage.execute("UPDATE accounts SET is_active = '0' WHERE id = $1;", [
this.account.id,
]);
await this.#keyring_remove(this.account.pubkey);
await this.#keyring_remove(`${this.account.pubkey}-nsecbunker`);
this.account = null;
this.#ndk.signer = null;
this.ndk.signer = null;
}
public subscribe({
@@ -409,53 +401,44 @@ export class Ark {
closeOnEose: boolean;
cb: (event: NDKEvent) => void;
}) {
const sub = this.#ndk.subscribe(filter, { closeOnEose });
const sub = this.ndk.subscribe(filter, { closeOnEose });
sub.addListener('event', (event: NDKEvent) => cb(event));
return sub;
}
public createNDKEvent({ event }: { event: NostrEvent | NostrEventExt }) {
return new NDKEvent(this.#ndk, event);
}
public async createEvent({
kind,
tags,
content,
rootReplyTo = undefined,
replyTo = undefined,
publish,
}: {
kind: NDKKind | number;
tags: NDKTag[];
content?: string;
rootReplyTo?: string;
replyTo?: string;
publish?: boolean;
}) {
try {
const event = new NDKEvent(this.#ndk);
const event = new NDKEvent(this.ndk);
if (content) event.content = content;
event.kind = kind;
event.tags = tags;
if (rootReplyTo) {
const rootEvent = await this.#ndk.fetchEvent(rootReplyTo);
const rootEvent = await this.ndk.fetchEvent(rootReplyTo);
if (rootEvent) event.tag(rootEvent, 'root');
}
if (replyTo) {
const replyEvent = await this.#ndk.fetchEvent(replyTo);
const replyEvent = await this.ndk.fetchEvent(replyTo);
if (replyEvent) event.tag(replyEvent, 'reply');
}
if (publish) {
const publishedEvent = await event.publish();
if (!publishedEvent) throw new Error('Failed to publish event');
return publishedEvent.size;
}
const publish = await event.publish();
return event;
if (!publish) throw new Error('Failed to publish event');
return { id: event.id, seens: [...publish.values()].map((item) => item.url) };
} catch (e) {
throw new Error(e);
}
@@ -463,7 +446,23 @@ export class Ark {
public async getUserProfile({ pubkey }: { pubkey: string }) {
try {
const user = this.#ndk.getUser({ pubkey });
// get clean pubkey without any special characters
let hexstring = pubkey.replace(/[^a-zA-Z0-9]/g, '');
if (
hexstring.startsWith('npub1') ||
hexstring.startsWith('nprofile1') ||
hexstring.startsWith('naddr1')
) {
const decoded = nip19.decode(hexstring);
if (decoded.type === 'nprofile') hexstring = decoded.data.pubkey;
if (decoded.type === 'npub') hexstring = decoded.data;
if (decoded.type === 'naddr') hexstring = decoded.data.pubkey;
}
const user = this.ndk.getUser({ pubkey: hexstring });
const profile = await user.fetchProfile({
cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST,
});
@@ -471,8 +470,7 @@ export class Ark {
if (!profile) return null;
return profile;
} catch (e) {
console.error(e);
return null;
throw new Error(e);
}
}
@@ -484,7 +482,7 @@ export class Ark {
outbox?: boolean;
}) {
try {
const user = this.#ndk.getUser({ pubkey: pubkey ? pubkey : this.account.pubkey });
const user = this.ndk.getUser({ pubkey: pubkey ? pubkey : this.account.pubkey });
const contacts = [...(await user.follows(undefined, outbox))].map(
(user) => user.pubkey
);
@@ -492,33 +490,33 @@ export class Ark {
if (pubkey === this.account.pubkey) this.account.contacts = contacts;
return contacts;
} catch (e) {
console.error(e);
throw new Error(e);
return [];
}
}
public async getUserRelays({ pubkey }: { pubkey?: string }) {
try {
const user = this.#ndk.getUser({ pubkey: pubkey ? pubkey : this.account.pubkey });
const user = this.ndk.getUser({ pubkey: pubkey ? pubkey : this.account.pubkey });
return await user.relayList();
} catch (e) {
console.error(e);
throw new Error(e);
return null;
}
}
public async createContact({ pubkey }: { pubkey: string }) {
const user = this.#ndk.getUser({ pubkey: this.account.pubkey });
const user = this.ndk.getUser({ pubkey: this.account.pubkey });
const contacts = await user.follows();
return await user.follow(new NDKUser({ pubkey: pubkey }), contacts);
}
public async deleteContact({ pubkey }: { pubkey: string }) {
const user = this.#ndk.getUser({ pubkey: this.account.pubkey });
const user = this.ndk.getUser({ pubkey: this.account.pubkey });
const contacts = await user.follows();
contacts.delete(new NDKUser({ pubkey: pubkey }));
const event = new NDKEvent(this.#ndk);
const event = new NDKEvent(this.ndk);
event.content = '';
event.kind = NDKKind.Contacts;
event.tags = [...contacts].map((item) => [
@@ -532,22 +530,40 @@ export class Ark {
}
public async getAllEvents({ filter }: { filter: NDKFilter }) {
const events = await this.#ndk.fetchEvents(filter);
const events = await this.ndk.fetchEvents(filter);
if (!events) return [];
return [...events];
}
public async getEventById({ id }: { id: string }) {
const event = await this.#ndk.fetchEvent(id, {
let eventId: string = id;
if (
eventId.startsWith('nevent1') ||
eventId.startsWith('note1') ||
eventId.startsWith('naddr1')
) {
const decode = nip19.decode(eventId);
if (decode.type === 'nevent') eventId = decode.data.id;
if (decode.type === 'note') eventId = decode.data;
if (decode.type === 'naddr') {
return await this.ndk.fetchEvent({
kinds: [decode.data.kind],
'#d': [decode.data.identifier],
authors: [decode.data.pubkey],
});
}
}
return await this.ndk.fetchEvent(id, {
cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST,
});
if (!event) return null;
return event;
}
public async getEventByFilter({ filter }: { filter: NDKFilter }) {
const event = await this.#ndk.fetchEvent(filter, {
const event = await this.ndk.fetchEvent(filter, {
cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST,
});
@@ -589,8 +605,8 @@ export class Ark {
let events = data || null;
if (!data) {
const relayUrls = [...this.#ndk.pool.relays.values()].map((item) => item.url);
const rawEvents = (await this.#fetcher.fetchAllEvents(
const relayUrls = [...this.ndk.pool.relays.values()].map((item) => item.url);
const rawEvents = (await this.fetcher.fetchAllEvents(
relayUrls,
{
kinds: [NDKKind.Text],
@@ -600,7 +616,7 @@ export class Ark {
{ sort: true }
)) as unknown as NostrEvent[];
events = rawEvents.map(
(event) => new NDKEvent(this.#ndk, event)
(event) => new NDKEvent(this.ndk, event)
) as NDKEvent[] as NDKEventWithReplies[];
}
@@ -633,7 +649,7 @@ export class Ark {
public async getAllRelaysFromContacts() {
const LIMIT = 1;
const relayMap = new Map<string, string[]>();
const relayEvents = this.#fetcher.fetchLatestEventsPerAuthor(
const relayEvents = this.fetcher.fetchLatestEventsPerAuthor(
{
authors: this.account.contacts,
relayUrls: this.relays,
@@ -672,13 +688,13 @@ export class Ark {
const rootIds = new Set();
const dedupQueue = new Set();
const events = await this.#fetcher.fetchLatestEvents(this.relays, filter, limit, {
const events = await this.fetcher.fetchLatestEvents(this.relays, filter, limit, {
asOf: pageParam === 0 ? undefined : pageParam,
abortSignal: signal,
});
const ndkEvents = events.map((event) => {
return new NDKEvent(this.#ndk, event);
return new NDKEvent(this.ndk, event);
});
if (dedup) {
@@ -713,7 +729,7 @@ export class Ark {
signal?: AbortSignal;
dedup?: boolean;
}) {
const events = await this.#fetcher.fetchLatestEvents(
const events = await this.fetcher.fetchLatestEvents(
[normalizeRelayUrl(relayUrl)],
filter,
limit,
@@ -724,7 +740,7 @@ export class Ark {
);
const ndkEvents = events.map((event) => {
return new NDKEvent(this.#ndk, event);
return new NDKEvent(this.ndk, event);
});
return ndkEvents.sort((a, b) => b.created_at - a.created_at);
@@ -807,7 +823,7 @@ export class Ark {
* @deprecated NIP-04 will be replace by NIP-44 in the next update
*/
public async getAllChats() {
const events = await this.#fetcher.fetchAllEvents(
const events = await this.fetcher.fetchAllEvents(
this.relays,
{
kinds: [NDKKind.EncryptedDirectMessage],
@@ -840,7 +856,7 @@ export class Ark {
let senderMessages: NostrEventExt<false>[] = [];
if (pubkey !== this.account.pubkey) {
senderMessages = await this.#fetcher.fetchAllEvents(
senderMessages = await this.fetcher.fetchAllEvents(
this.relays,
{
kinds: [NDKKind.EncryptedDirectMessage],
@@ -851,7 +867,7 @@ export class Ark {
);
}
const userMessages = await this.#fetcher.fetchAllEvents(
const userMessages = await this.fetcher.fetchAllEvents(
this.relays,
{
kinds: [NDKKind.EncryptedDirectMessage],
@@ -876,20 +892,20 @@ export class Ark {
? event.tags.find((el) => el[0] === 'p')[1]
: event.pubkey,
});
const content = await this.#ndk.signer.decrypt(sender, event.content);
const content = await this.ndk.signer.decrypt(sender, event.content);
return content;
} catch (e) {
console.error(e);
throw new Error(e);
}
}
public async nip04Encrypt({ content, pubkey }: { content: string; pubkey: string }) {
try {
const recipient = new NDKUser({ pubkey });
const message = await this.#ndk.signer.encrypt(recipient, content);
const message = await this.ndk.signer.encrypt(recipient, content);
const event = new NDKEvent(this.#ndk);
const event = new NDKEvent(this.ndk);
event.content = message;
event.kind = NDKKind.EncryptedDirectMessage;
event.tag(recipient);
@@ -897,22 +913,22 @@ export class Ark {
const publish = await event.publish();
if (!publish) throw new Error('Failed to send NIP-04 encrypted message');
return publish;
return { id: event.id, seens: [...publish.values()].map((item) => item.url) };
} catch (e) {
console.error(e);
throw new Error(e);
}
}
public async replyTo({ content, event }: { content: string; event: NDKEvent }) {
try {
const replyEvent = new NDKEvent(this.#ndk);
const replyEvent = new NDKEvent(this.ndk);
replyEvent.content = content;
replyEvent.kind = NDKKind.Text;
replyEvent.tag(event, 'reply');
return await replyEvent.publish();
} catch (e) {
console.error(e);
throw new Error(e);
}
}
}

View File

@@ -1,3 +1,2 @@
export * from './ark';
export * from './cache';
export * from './provider';

View File

@@ -183,12 +183,14 @@ export class NDKCacheAdapterTauri implements NDKCacheAdapter {
const filterKeys = Object.keys(_filter || {}).sort();
try {
(await this.byKindAndAuthor(filterKeys, filter, subscription)) ||
(await this.byAuthors(filterKeys, filter, subscription)) ||
(await this.byKinds(filterKeys, filter, subscription)) ||
(await this.byIdsQuery(filterKeys, filter, subscription)) ||
(await this.byNip33Query(filterKeys, filter, subscription)) ||
(await this.byTagsAndOptionallyKinds(filterKeys, filter, subscription));
await Promise.allSettled([
this.byKindAndAuthor(filterKeys, filter, subscription),
this.byAuthors(filterKeys, filter, subscription),
this.byKinds(filterKeys, filter, subscription),
this.byIdsQuery(filterKeys, filter, subscription),
this.byNip33Query(filterKeys, filter, subscription),
this.byTagsAndOptionallyKinds(filterKeys, filter, subscription),
]);
} catch (error) {
console.error(error);
}
@@ -196,7 +198,7 @@ export class NDKCacheAdapterTauri implements NDKCacheAdapter {
public async setEvent(
event: NDKEvent,
_filter: NDKFilter,
filters: NDKFilter[],
relay?: NDKRelay
): Promise<void> {
if (event.kind === 0) {

View File

@@ -23,7 +23,7 @@ export function Repost({ event }: { event: NDKEvent }) {
try {
if (event.content.length > 50) {
const embed = JSON.parse(event.content) as NostrEvent;
return ark.createNDKEvent({ event: embed });
return new NDKEvent(ark.ndk, embed);
}
const id = event.tags.find((el) => el[0] === 'e')[1];

View File

@@ -1,7 +1,5 @@
import { NDKEvent, NostrEvent } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { nip19 } from 'nostr-tools';
import { AddressPointer } from 'nostr-tools/lib/types/nip19';
import { useArk } from '@libs/ark';
export function useEvent(id: undefined | string, embed?: undefined | string) {
@@ -9,32 +7,14 @@ export function useEvent(id: undefined | string, embed?: undefined | string) {
const { status, isFetching, isError, data } = useQuery({
queryKey: ['event', id],
queryFn: async () => {
let event: NDKEvent = undefined;
const naddr = id.startsWith('naddr')
? (nip19.decode(id).data as AddressPointer)
: null;
// return event refer from naddr
if (naddr) {
const events = await ark.getAllEvents({
filter: {
kinds: [naddr.kind],
'#d': [naddr.identifier],
authors: [naddr.pubkey],
},
});
event = events.slice(-1)[0];
}
// return embed event (nostr.band api)
if (embed) {
const embedEvent: NostrEvent = JSON.parse(embed);
event = ark.createNDKEvent({ event: embedEvent });
return new NDKEvent(ark.ndk, embedEvent);
}
// get event from relay
event = await ark.getEventById({ id });
const event = await ark.getEventById({ id });
if (!event)
throw new Error(`Cannot get event with ${id}, will be retry after 10 seconds`);

View File

@@ -1,6 +1,5 @@
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { nip19 } from 'nostr-tools';
import { useArk } from '@libs/ark';
export function useProfile(pubkey: string, embed?: string) {
@@ -19,16 +18,7 @@ export function useProfile(pubkey: string, embed?: string) {
return profile;
}
// get clean pubkey without any special characters
let hexstring = pubkey.replace(/[^a-zA-Z0-9]/g, '');
if (hexstring.startsWith('npub1') || hexstring.startsWith('nprofile1')) {
const decoded = nip19.decode(hexstring);
if (decoded.type === 'nprofile') hexstring = decoded.data.pubkey;
if (decoded.type === 'npub') hexstring = decoded.data;
}
const profile = await ark.getUserProfile({ pubkey: hexstring });
const profile = await ark.getUserProfile({ pubkey });
if (!profile)
throw new Error(

View File

@@ -22,7 +22,6 @@ export function useRelay() {
await ark.createEvent({
kind: NDKKind.RelayList,
tags: [['r', relay, purpose ?? '']],
publish: true,
});
}
@@ -33,7 +32,6 @@ export function useRelay() {
await ark.createEvent({
kind: NDKKind.RelayList,
tags: [...prevRelays, ['r', relay, purpose ?? '']],
publish: true,
});
// Optimistically update to the new value
@@ -69,7 +67,6 @@ export function useRelay() {
await ark.createEvent({
kind: NDKKind.RelayList,
tags: prevRelays,
publish: true,
});
// Optimistically update to the new value

View File

@@ -3,9 +3,13 @@ export function fileType(url: string) {
return 'image';
}
if (url.match(/\.(mp4|mov|webm|wmv|flv|mts|avi|ogv|mkv|mp3|m3u8)$/)) {
if (url.match(/\.(mp4|mov|webm|wmv|flv|mts|avi|ogv|mkv)$/)) {
return 'video';
}
if (url.match(/\.(mp3|ogg|wav)$/)) {
return 'audio';
}
return 'link';
}