diff --git a/src/app/settings/editProfile.tsx b/src/app/settings/editProfile.tsx
index c0151a8c..e8a4e55c 100644
--- a/src/app/settings/editProfile.tsx
+++ b/src/app/settings/editProfile.tsx
@@ -4,7 +4,7 @@ import { message } from '@tauri-apps/plugin-dialog';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
-import { useArk } from '@libs/ark';
+import { useArk, useStorage } from '@libs/ark';
import { CheckCircleIcon, LoaderIcon, PlusIcon, UnverifiedIcon } from '@shared/icons';
export function EditProfileScreen() {
@@ -14,6 +14,8 @@ export function EditProfileScreen() {
const [nip05, setNIP05] = useState({ verified: true, text: '' });
const ark = useArk();
+ const storage = useStorage();
+
const {
register,
handleSubmit,
@@ -22,7 +24,10 @@ export function EditProfileScreen() {
formState: { isValid, errors },
} = useForm({
defaultValues: async () => {
- const res: NDKUserProfile = queryClient.getQueryData(['user', ark.account.pubkey]);
+ const res: NDKUserProfile = queryClient.getQueryData([
+ 'user',
+ storage.account.pubkey,
+ ]);
if (res.image) {
setPicture(res.image);
}
@@ -41,7 +46,7 @@ export function EditProfileScreen() {
const uploadAvatar = async () => {
try {
- if (!ark.readyToSign) return navigate('/new/privkey');
+ if (!ark.ndk.signer) return navigate('/new/privkey');
setLoading(true);
@@ -85,7 +90,10 @@ export function EditProfileScreen() {
};
if (data.nip05) {
- const verify = ark.validateNIP05({ pubkey: ark.account.pubkey, nip05: data.nip05 });
+ const verify = ark.validateNIP05({
+ pubkey: storage.account.pubkey,
+ nip05: data.nip05,
+ });
if (verify) {
content = { ...content, nip05: data.nip05 };
} else {
@@ -106,7 +114,7 @@ export function EditProfileScreen() {
if (publish) {
// invalid cache
queryClient.invalidateQueries({
- queryKey: ['user', ark.account.pubkey],
+ queryKey: ['user', storage.account.pubkey],
});
// reset form
reset();
diff --git a/src/app/settings/general.tsx b/src/app/settings/general.tsx
index 7174026c..ac71b85a 100644
--- a/src/app/settings/general.tsx
+++ b/src/app/settings/general.tsx
@@ -5,11 +5,12 @@ import { disable, enable, isEnabled } from '@tauri-apps/plugin-autostart';
import { isPermissionGranted, requestPermission } from '@tauri-apps/plugin-notification';
import { useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';
-import { useArk } from '@libs/ark';
+import { useStorage } from '@libs/ark';
import { DarkIcon, LightIcon, SystemModeIcon } from '@shared/icons';
export function GeneralSettingScreen() {
- const ark = useArk();
+ const storage = useStorage();
+
const [settings, setSettings] = useState({
autoupdate: false,
autolaunch: false,
@@ -39,28 +40,28 @@ export function GeneralSettingScreen() {
};
const toggleOutbox = async () => {
- await ark.createSetting('outbox', String(+!settings.outbox));
+ await storage.createSetting('outbox', String(+!settings.outbox));
// update state
setSettings((prev) => ({ ...prev, outbox: !settings.outbox }));
};
const toggleMedia = async () => {
- await ark.createSetting('media', String(+!settings.media));
- ark.settings.media = !settings.media;
+ await storage.createSetting('media', String(+!settings.media));
+ storage.settings.media = !settings.media;
// update state
setSettings((prev) => ({ ...prev, media: !settings.media }));
};
const toggleHashtag = async () => {
- await ark.createSetting('hashtag', String(+!settings.hashtag));
- ark.settings.hashtag = !settings.hashtag;
+ await storage.createSetting('hashtag', String(+!settings.hashtag));
+ storage.settings.hashtag = !settings.hashtag;
// update state
setSettings((prev) => ({ ...prev, hashtag: !settings.hashtag }));
};
const toggleAutoupdate = async () => {
- await ark.createSetting('autoupdate', String(+!settings.autoupdate));
- ark.settings.autoupdate = !settings.autoupdate;
+ await storage.createSetting('autoupdate', String(+!settings.autoupdate));
+ storage.settings.autoupdate = !settings.autoupdate;
// update state
setSettings((prev) => ({ ...prev, autoupdate: !settings.autoupdate }));
};
@@ -84,7 +85,7 @@ export function GeneralSettingScreen() {
const permissionGranted = await isPermissionGranted();
setSettings((prev) => ({ ...prev, notification: permissionGranted }));
- const data = await ark.getAllSettings();
+ const data = await storage.getAllSettings();
if (!data) return;
data.forEach((item) => {
diff --git a/src/app/users/components/profile.tsx b/src/app/users/components/profile.tsx
index 0c9c0fd8..5bce595f 100644
--- a/src/app/users/components/profile.tsx
+++ b/src/app/users/components/profile.tsx
@@ -4,13 +4,15 @@ import { useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { UserStats } from '@app/users/components/stats';
-import { useArk } from '@libs/ark';
+import { useArk, useStorage } from '@libs/ark';
import { NIP05 } from '@shared/nip05';
import { displayNpub } from '@utils/formater';
import { useProfile } from '@utils/hooks/useProfile';
export function UserProfile({ pubkey }: { pubkey: string }) {
const ark = useArk();
+ const storage = useStorage();
+
const { user } = useProfile(pubkey);
const [followed, setFollowed] = useState(false);
@@ -21,7 +23,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
const follow = async () => {
try {
- if (!ark.readyToSign) return navigate('/new/privkey');
+ if (!ark.ndk.signer) return navigate('/new/privkey');
setFollowed(true);
const add = await ark.createContact({ pubkey });
@@ -38,7 +40,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
const unfollow = async () => {
try {
- if (!ark.readyToSign) return navigate('/new/privkey');
+ if (!ark.ndk.signer) return navigate('/new/privkey');
setFollowed(false);
await ark.deleteContact({ pubkey });
@@ -48,7 +50,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
};
useEffect(() => {
- if (ark.account.contacts.includes(pubkey)) {
+ if (storage.account.contacts.includes(pubkey)) {
setFollowed(true);
}
}, []);
diff --git a/src/app/users/index.tsx b/src/app/users/index.tsx
index dec44863..b2a92fd2 100644
--- a/src/app/users/index.tsx
+++ b/src/app/users/index.tsx
@@ -3,14 +3,8 @@ import { useInfiniteQuery } from '@tanstack/react-query';
import { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { UserProfile } from '@app/users/components/profile';
-import { useArk } from '@libs/ark';
+import { NoteSkeleton, RepostNote, TextNote, useArk } from '@libs/ark';
import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
-import {
- MemoizedRepost,
- MemoizedTextNote,
- NoteSkeleton,
- UnknownNote,
-} from '@shared/notes';
import { FETCH_LIMIT } from '@utils/constants';
export function UserScreen() {
@@ -57,11 +51,11 @@ export function UserScreen() {
(event: NDKEvent) => {
switch (event.kind) {
case NDKKind.Text:
- return
;
+ return
;
case NDKKind.Repost:
- return
;
+ return
;
default:
- return
;
+ return
;
}
},
[data]
@@ -76,11 +70,7 @@ export function UserScreen() {
{status === 'pending' ? (
-
+
) : (
allEvents.map((item) => renderItem(item))
)}
diff --git a/src/libs/ark/ark.ts b/src/libs/ark/ark.ts
index e22322be..6dd99b45 100644
--- a/src/libs/ark/ark.ts
+++ b/src/libs/ark/ark.ts
@@ -10,76 +10,37 @@ import NDK, {
NDKUser,
NostrEvent,
} from '@nostr-dev-kit/ndk';
-import { ndkAdapter } from '@nostr-fetch/adapter-ndk';
-import { appConfigDir, resolveResource } from '@tauri-apps/api/path';
-import { invoke } from '@tauri-apps/api/primitives';
import { open } from '@tauri-apps/plugin-dialog';
import { readBinaryFile } from '@tauri-apps/plugin-fs';
import { fetch } from '@tauri-apps/plugin-http';
-import { Platform } from '@tauri-apps/plugin-os';
-import { Child, Command } from '@tauri-apps/plugin-shell';
-import Database from '@tauri-apps/plugin-sql';
-import {
- NostrEventExt,
- NostrFetcher,
- normalizeRelayUrl,
- normalizeRelayUrlSet,
-} from 'nostr-fetch';
+import { NostrFetcher, normalizeRelayUrl } from 'nostr-fetch';
import { nip19 } from 'nostr-tools';
-import { NDKCacheAdapterTauri } from '@libs/cache';
-import { delay } from '@utils/delay';
-import {
- type Account,
- type NDKCacheUser,
- type NDKCacheUserProfile,
- type NDKEventWithReplies,
- type NIP05,
- type WidgetProps,
-} from '@utils/types';
+import { LumeStorage } from '@libs/storage';
+import { Account, type NDKEventWithReplies, type NIP05 } from '@utils/types';
export class Ark {
- #storage: Database;
- #depot: Child;
+ #storage: LumeStorage;
+ #fetcher: NostrFetcher;
public ndk: NDK;
- public fetcher: NostrFetcher;
- public account: Account | null;
- public relays: string[] | null;
- public readyToSign: boolean;
- readonly platform: Platform | null;
- readonly settings: {
- autoupdate: boolean;
- bunker: boolean;
- outbox: boolean;
- media: boolean;
- hashtag: boolean;
- depot: boolean;
- tunnelUrl: string;
- };
+ public account: Account;
- constructor({ storage, platform }: { storage: Database; platform: Platform }) {
+ constructor({
+ ndk,
+ storage,
+
+ fetcher,
+ }: {
+ ndk: NDK;
+ storage: LumeStorage;
+
+ fetcher: NostrFetcher;
+ }) {
+ this.ndk = ndk;
this.#storage = storage;
- this.platform = platform;
- this.settings = {
- autoupdate: false,
- bunker: false,
- outbox: false,
- media: true,
- hashtag: true,
- depot: false,
- tunnelUrl: '',
- };
- }
-
- public async launchDepot() {
- const configPath = await resolveResource('resources/config.toml');
- const dataPath = await appConfigDir();
-
- const command = Command.sidecar('bin/depot', ['-c', configPath, '-d', dataPath]);
- this.#depot = await command.spawn();
+ this.#fetcher = fetcher;
}
public async connectDepot() {
- if (!this.#depot) return;
return this.ndk.addExplicitRelay(
new NDKRelay(normalizeRelayUrl('ws://localhost:6090')),
undefined,
@@ -87,349 +48,11 @@ export class Ark {
);
}
- public checkDepot() {
- if (this.#depot) return true;
- return false;
- }
-
- async #keyring_save(key: string, value: string) {
- return await invoke('secure_save', { key, value });
- }
-
- async #keyring_load(key: string) {
- try {
- const value: string = await invoke('secure_load', { key });
- if (!value) return null;
- return value;
- } catch {
- return null;
- }
- }
-
- async #keyring_remove(key: string) {
- return await invoke('secure_remove', { key });
- }
-
- async #initNostrSigner({ nsecbunker }: { nsecbunker?: boolean }) {
- const account = await this.getActiveAccount();
- if (!account) return null;
-
- // update active account
- this.account = account;
-
- try {
- // NIP-46 Signer
- if (nsecbunker) {
- const localSignerPrivkey = await this.#keyring_load(
- `${this.account.id}-nsecbunker`
- );
-
- if (!localSignerPrivkey) {
- this.readyToSign = false;
- return null;
- }
-
- const localSigner = new NDKPrivateKeySigner(localSignerPrivkey);
- const bunker = new NDK({
- explicitRelayUrls: normalizeRelayUrlSet([
- 'wss://relay.nsecbunker.com/',
- 'wss://nostr.vulpem.com/',
- ]),
- });
- await bunker.connect(3000);
-
- const remoteSigner = new NDKNip46Signer(bunker, this.account.pubkey, localSigner);
- await remoteSigner.blockUntilReady();
-
- this.readyToSign = true;
- return remoteSigner;
- }
-
- // Privkey Signer
- const userPrivkey = await this.#keyring_load(this.account.pubkey);
-
- if (!userPrivkey) {
- this.readyToSign = false;
- return null;
- }
-
- this.readyToSign = true;
- return new NDKPrivateKeySigner(userPrivkey);
- } catch (e) {
- console.log(e);
- 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);
- if (item.key === 'media') this.settings.media = !!parseInt(item.value);
- if (item.key === 'hashtag') this.settings.hashtag = !!parseInt(item.value);
- if (item.key === 'autoupdate') this.settings.autoupdate = !!parseInt(item.value);
- if (item.key === 'depot') this.settings.depot = !!parseInt(item.value);
- if (item.key === 'tunnel_url') this.settings.tunnelUrl = item.value;
- }
-
- const explicitRelayUrls = normalizeRelayUrlSet([
- 'wss://relay.damus.io',
- 'wss://relay.nostr.band/all',
- 'wss://nostr.mutinywallet.com',
- ]);
-
- if (this.settings.depot) {
- await this.launchDepot();
- await delay(2000);
-
- explicitRelayUrls.push(normalizeRelayUrl('ws://localhost:6090'));
- }
-
- // #TODO: user should config outbox relays
- const outboxRelayUrls = normalizeRelayUrlSet(['wss://purplepag.es']);
-
- // #TODO: user should config blacklist relays
- // No need to connect depot tunnel url
- const blacklistRelayUrls = this.settings.tunnelUrl.length
- ? [this.settings.tunnelUrl, this.settings.tunnelUrl + '/']
- : [];
-
- const cacheAdapter = new NDKCacheAdapterTauri(this.#storage);
- const ndk = new NDK({
- cacheAdapter,
- explicitRelayUrls,
- outboxRelayUrls,
- blacklistRelayUrls,
- enableOutboxModel: this.settings.outbox,
- autoConnectUserRelays: true,
- autoFetchUserMutelist: true,
- // clientName: 'Lume',
- // clientNip89: '',
- });
-
- // add signer if exist
- const signer = await this.#initNostrSigner({ nsecbunker: this.settings.bunker });
- if (signer) ndk.signer = signer;
-
- // connect
- await ndk.connect(3000);
- const fetcher = NostrFetcher.withCustomPool(ndkAdapter(ndk));
-
- // update account's metadata
- if (this.account) {
- const user = ndk.getUser({ pubkey: this.account.pubkey });
- ndk.activeUser = user;
-
- 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;
- }
-
public updateNostrSigner({ signer }: { signer: NDKNip46Signer | NDKPrivateKeySigner }) {
this.ndk.signer = signer;
- this.readyToSign = true;
return this.ndk.signer;
}
- public async getAllCacheUsers() {
- const results: Array
= await this.#storage.select(
- 'SELECT * FROM ndk_users ORDER BY createdAt DESC;'
- );
-
- if (!results.length) return [];
-
- const users: NDKCacheUserProfile[] = results.map((item) => ({
- pubkey: item.pubkey,
- ...JSON.parse(item.profile as string),
- }));
- return users;
- }
-
- public async checkAccount() {
- const result: Array<{ total: string }> = await this.#storage.select(
- 'SELECT COUNT(*) AS "total" FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;'
- );
- return parseInt(result[0].total);
- }
-
- public async getActiveAccount() {
- const results: Array = await this.#storage.select(
- 'SELECT * FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;'
- );
-
- if (results.length) {
- return results[0];
- } else {
- return null;
- }
- }
-
- public async createAccount({
- id,
- pubkey,
- privkey,
- }: {
- id: string;
- pubkey: string;
- privkey?: string;
- }) {
- const existAccounts: Array = await this.#storage.select(
- 'SELECT * FROM accounts WHERE pubkey = $1 ORDER BY id DESC LIMIT 1;',
- [pubkey]
- );
-
- if (existAccounts.length) {
- await this.#storage.execute(
- "UPDATE accounts SET is_active = '1' WHERE pubkey = $1;",
- [pubkey]
- );
- } else {
- await this.#storage.execute(
- 'INSERT OR IGNORE INTO accounts (id, pubkey, is_active) VALUES ($1, $2, $3);',
- [id, pubkey, 1]
- );
-
- if (privkey) await this.#keyring_save(pubkey, privkey);
- }
-
- const account = await this.getActiveAccount();
- this.account = account;
- this.account.contacts = [];
-
- return account;
- }
-
- /**
- * Save private key to OS secure storage
- * @deprecated this method will be remove in the next update
- */
- public async createPrivkey(name: string, privkey: string) {
- return await this.#keyring_save(name, privkey);
- }
-
- /**
- * Load private key from OS secure storage
- * @deprecated this method will be remove in the next update
- */
- public async loadPrivkey(name: string) {
- return await this.#keyring_load(name);
- }
-
- /**
- * Remove private key from OS secure storage
- * @deprecated this method will be remove in the next update
- */
- public async removePrivkey(name: string) {
- return await this.#keyring_remove(name);
- }
-
- public async updateAccount(column: string, value: string) {
- const insert = await this.#storage.execute(
- `UPDATE accounts SET ${column} = $1 WHERE id = $2;`,
- [value, this.account.id]
- );
-
- if (insert) {
- const account = await this.getActiveAccount();
- return account;
- }
- }
-
- public async getWidgets() {
- const widgets: Array = await this.#storage.select(
- 'SELECT * FROM widgets WHERE account_id = $1 ORDER BY created_at DESC;',
- [this.account.id]
- );
- return widgets;
- }
-
- public async createWidget(kind: number, title: string, content: string | string[]) {
- const insert = await this.#storage.execute(
- 'INSERT INTO widgets (account_id, kind, title, content) VALUES ($1, $2, $3, $4);',
- [this.account.id, kind, title, content]
- );
-
- if (insert) {
- const widgets: Array = await this.#storage.select(
- 'SELECT * FROM widgets ORDER BY id DESC LIMIT 1;'
- );
- if (widgets.length < 1) console.error('get created widget failed');
- return widgets[0];
- } else {
- console.error('create widget failed');
- }
- }
-
- public async removeWidget(id: string) {
- const res = await this.#storage.execute('DELETE FROM widgets WHERE id = $1;', [id]);
- if (res) return id;
- }
-
- public async createSetting(key: string, value: string | undefined) {
- const currentSetting = await this.checkSettingValue(key);
-
- if (!currentSetting) {
- return await this.#storage.execute(
- 'INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);',
- [key, value]
- );
- }
-
- return await this.#storage.execute('UPDATE settings SET value = $1 WHERE key = $2;', [
- value,
- key,
- ]);
- }
-
- public async getAllSettings() {
- const results: { key: string; value: string }[] = await this.#storage.select(
- 'SELECT * FROM settings ORDER BY id DESC;'
- );
- if (results.length < 1) return [];
- return results;
- }
-
- public async checkSettingValue(key: string) {
- const results: { key: string; value: string }[] = await this.#storage.select(
- 'SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;',
- [key]
- );
- if (!results.length) return false;
- return results[0].value;
- }
-
- public async getSettingValue(key: string) {
- const results: { key: string; value: string }[] = await this.#storage.select(
- 'SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;',
- [key]
- );
- if (!results.length) return '0';
- return results[0].value;
- }
-
- public async clearCache() {
- await this.#storage.execute('DELETE FROM ndk_events;');
- await this.#storage.execute('DELETE FROM ndk_eventtags;');
- await this.#storage.execute('DELETE FROM ndk_users;');
- }
-
- 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,
- ]);
-
- this.account = null;
- this.ndk.signer = null;
- }
-
public subscribe({
filter,
closeOnEose = false,
@@ -520,37 +143,52 @@ 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.#storage.account.pubkey,
+ });
const contacts = [...(await user.follows(undefined, outbox))].map(
(user) => user.pubkey
);
- if (pubkey === this.account.pubkey) this.account.contacts = contacts;
+ if (pubkey === this.#storage.account.pubkey)
+ this.#storage.account.contacts = contacts;
return contacts;
} catch (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.#storage.account.pubkey,
+ });
return await user.relayList();
} catch (e) {
throw new Error(e);
- return null;
+ }
+ }
+
+ public async newContactList({ tags }: { tags: NDKTag[] }) {
+ const publish = await this.createEvent({
+ kind: NDKKind.Contacts,
+ tags: tags,
+ });
+
+ if (publish) {
+ this.#storage.account.contacts = tags.map((item) => item[1]);
+ return publish;
}
}
public async createContact({ pubkey }: { pubkey: string }) {
- const user = this.ndk.getUser({ pubkey: this.account.pubkey });
+ const user = this.ndk.getUser({ pubkey: this.#storage.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.#storage.account.pubkey });
const contacts = await user.follows();
contacts.delete(new NDKUser({ pubkey: pubkey }));
@@ -644,7 +282,7 @@ export class Ark {
if (!data) {
const relayUrls = [...this.ndk.pool.relays.values()].map((item) => item.url);
- const rawEvents = (await this.fetcher.fetchAllEvents(
+ const rawEvents = (await this.#fetcher.fetchAllEvents(
relayUrls,
{
kinds: [NDKKind.Text],
@@ -686,11 +324,12 @@ export class Ark {
public async getAllRelaysFromContacts() {
const LIMIT = 1;
+ const connectedRelays = this.ndk.pool.connectedRelays().map((item) => item.url);
const relayMap = new Map();
- const relayEvents = this.fetcher.fetchLatestEventsPerAuthor(
+ const relayEvents = this.#fetcher.fetchLatestEventsPerAuthor(
{
- authors: this.account.contacts,
- relayUrls: this.relays,
+ authors: this.#storage.account.contacts,
+ relayUrls: connectedRelays,
},
{ kinds: [NDKKind.RelayList] },
LIMIT
@@ -725,8 +364,9 @@ export class Ark {
}) {
const rootIds = new Set();
const dedupQueue = new Set();
+ const connectedRelays = this.ndk.pool.connectedRelays().map((item) => item.url);
- const events = await this.fetcher.fetchLatestEvents(this.relays, filter, limit, {
+ const events = await this.#fetcher.fetchLatestEvents(connectedRelays, filter, limit, {
asOf: pageParam === 0 ? undefined : pageParam,
abortSignal: signal,
});
@@ -767,7 +407,7 @@ export class Ark {
signal?: AbortSignal;
dedup?: boolean;
}) {
- const events = await this.fetcher.fetchLatestEvents(
+ const events = await this.#fetcher.fetchLatestEvents(
[normalizeRelayUrl(relayUrl)],
filter,
limit,
@@ -856,107 +496,6 @@ export class Ark {
return false;
}
- /**
- * Return all NIP-04 messages
- * @deprecated NIP-04 will be replace by NIP-44 in the next update
- */
- public async getAllChats() {
- const events = await this.fetcher.fetchAllEvents(
- this.relays,
- {
- kinds: [NDKKind.EncryptedDirectMessage],
- '#p': [this.account.pubkey],
- },
- { since: 0 }
- );
-
- const dedup: NDKEvent[] = Object.values(
- events.reduce((ev, { id, content, pubkey, created_at, tags }) => {
- if (ev[pubkey]) {
- if (ev[pubkey].created_at < created_at) {
- ev[pubkey] = { id, content, pubkey, created_at, tags };
- }
- } else {
- ev[pubkey] = { id, content, pubkey, created_at, tags };
- }
- return ev;
- }, {})
- );
-
- return dedup;
- }
-
- /**
- * Return all NIP-04 messages by pubkey
- * @deprecated NIP-04 will be replace by NIP-44 in the next update
- */
- public async getAllMessagesByPubkey({ pubkey }: { pubkey: string }) {
- let senderMessages: NostrEventExt[] = [];
-
- if (pubkey !== this.account.pubkey) {
- senderMessages = await this.fetcher.fetchAllEvents(
- this.relays,
- {
- kinds: [NDKKind.EncryptedDirectMessage],
- authors: [pubkey],
- '#p': [this.account.pubkey],
- },
- { since: 0 }
- );
- }
-
- const userMessages = await this.fetcher.fetchAllEvents(
- this.relays,
- {
- kinds: [NDKKind.EncryptedDirectMessage],
- authors: [this.account.pubkey],
- '#p': [pubkey],
- },
- { since: 0 }
- );
-
- const all = [...senderMessages, ...userMessages].sort(
- (a, b) => a.created_at - b.created_at
- );
-
- return all as unknown as NDKEvent[];
- }
-
- public async nip04Decrypt({ event }: { event: NDKEvent }) {
- try {
- const sender = new NDKUser({
- pubkey:
- this.account.pubkey === event.pubkey
- ? event.tags.find((el) => el[0] === 'p')[1]
- : event.pubkey,
- });
- const content = await this.ndk.signer.decrypt(sender, event.content);
-
- return content;
- } catch (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 event = new NDKEvent(this.ndk);
- event.content = message;
- event.kind = NDKKind.EncryptedDirectMessage;
- event.tag(recipient);
-
- const publish = await event.publish();
-
- if (!publish) throw new Error('Failed to send NIP-04 encrypted message');
- return { id: event.id, seens: [...publish.values()].map((item) => item.url) };
- } catch (e) {
- throw new Error(e);
- }
- }
-
public async replyTo({ content, event }: { content: string; event: NDKEvent }) {
try {
const replyEvent = new NDKEvent(this.ndk);
diff --git a/src/libs/ark/components/note/builds/reply.tsx b/src/libs/ark/components/note/builds/reply.tsx
new file mode 100644
index 00000000..f4a853ba
--- /dev/null
+++ b/src/libs/ark/components/note/builds/reply.tsx
@@ -0,0 +1,67 @@
+import * as Collapsible from '@radix-ui/react-collapsible';
+import { useState } from 'react';
+import { twMerge } from 'tailwind-merge';
+import { NavArrowDownIcon } from '@shared/icons';
+import { User } from '@shared/user';
+import { NDKEventWithReplies } from '@utils/types';
+import { Note } from '..';
+
+export function Reply({
+ event,
+ rootEvent,
+}: {
+ event: NDKEventWithReplies;
+ rootEvent: string;
+}) {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+
+
+
+
+ {event.replies?.length > 0 ? (
+
+
+
+ {event.replies?.length +
+ ' ' +
+ (event.replies?.length === 1 ? 'reply' : 'replies')}
+
+
+ ) : null}
+
+
+
+
+
+
+
+
+ {event.replies?.length > 0 ? (
+
+ {event.replies?.map((childEvent) => (
+
+
+
+
+
+ ))}
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/src/shared/notes/repost.tsx b/src/libs/ark/components/note/builds/repost.tsx
similarity index 51%
rename from src/shared/notes/repost.tsx
rename to src/libs/ark/components/note/builds/repost.tsx
index 4a0a7a2d..2100f968 100644
--- a/src/shared/notes/repost.tsx
+++ b/src/libs/ark/components/note/builds/repost.tsx
@@ -1,17 +1,9 @@
import { NDKEvent, NDKKind, NostrEvent } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
-import { memo } from 'react';
-import { useArk } from '@libs/ark';
-import {
- MemoizedArticleKind,
- MemoizedFileKind,
- MemoizedTextKind,
- NoteActions,
- NoteSkeleton,
-} from '@shared/notes';
-import { User } from '@shared/user';
+import { useArk } from '@libs/ark/provider';
+import { Note } from '..';
-export function Repost({ event }: { event: NDKEvent }) {
+export function RepostNote({ event }: { event: NDKEvent }) {
const ark = useArk();
const {
isLoading,
@@ -25,7 +17,6 @@ export function Repost({ event }: { event: NDKEvent }) {
const embed = JSON.parse(event.content) as NostrEvent;
return new NDKEvent(ark.ndk, embed);
}
-
const id = event.tags.find((el) => el[0] === 'e')[1];
return await ark.getEventById({ id });
} catch {
@@ -39,29 +30,22 @@ export function Repost({ event }: { event: NDKEvent }) {
if (!repostEvent) return null;
switch (repostEvent.kind) {
case NDKKind.Text:
- return ;
+ return ;
case 1063:
- return ;
- case NDKKind.Article:
- return ;
+ return ;
default:
return null;
}
};
if (isLoading) {
- return (
-
-
-
- );
+ return ;
}
if (isError) {
return (
-
Failed to load event
@@ -73,21 +57,26 @@ export function Repost({ event }: { event: NDKEvent }) {
}
return (
-
-
-
-
-
- {renderContentByKind()}
-
+
+
+
+
+ {renderContentByKind()}
+
-
+
);
}
-
-export const MemoizedRepost = memo(Repost);
diff --git a/src/libs/ark/components/note/builds/skeleton.tsx b/src/libs/ark/components/note/builds/skeleton.tsx
new file mode 100644
index 00000000..6b52ca9b
--- /dev/null
+++ b/src/libs/ark/components/note/builds/skeleton.tsx
@@ -0,0 +1,24 @@
+import { Note } from '..';
+
+export function NoteSkeleton() {
+ return (
+
+
+
+ );
+}
diff --git a/src/libs/ark/components/note/builds/text.tsx b/src/libs/ark/components/note/builds/text.tsx
new file mode 100644
index 00000000..483a5b6b
--- /dev/null
+++ b/src/libs/ark/components/note/builds/text.tsx
@@ -0,0 +1,25 @@
+import { NDKEvent } from '@nostr-dev-kit/ndk';
+import { useArk } from '@libs/ark/provider';
+import { Note } from '..';
+
+export function TextNote({ event }: { event: NDKEvent }) {
+ const ark = useArk();
+ const thread = ark.getEventThread({ tags: event.tags });
+
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/src/libs/ark/components/note/pin.tsx b/src/libs/ark/components/note/buttons/pin.tsx
similarity index 76%
rename from src/libs/ark/components/note/pin.tsx
rename to src/libs/ark/components/note/buttons/pin.tsx
index 836e4337..09b906dc 100644
--- a/src/libs/ark/components/note/pin.tsx
+++ b/src/libs/ark/components/note/buttons/pin.tsx
@@ -1,14 +1,24 @@
import * as Tooltip from '@radix-ui/react-tooltip';
+import { useWidget } from '@libs/ark';
import { PinIcon } from '@shared/icons';
+import { WIDGET_KIND } from '@utils/constants';
+
+export function NotePin({ eventId }: { eventId: string }) {
+ const { addWidget } = useWidget();
-export function NotePin({ action }: { action: () => void }) {
return (