feat: refactor

This commit is contained in:
2024-01-12 20:32:45 +07:00
parent 67c6177291
commit 0487b8a801
63 changed files with 345 additions and 777 deletions

View File

@@ -1,5 +1,4 @@
import { LumeStorage } from "@lume/storage";
import { type NDKEventWithReplies, type NIP05 } from "@lume/types";
import { Account, type NDKEventWithReplies, type NIP05 } from "@lume/types";
import NDK, {
NDKEvent,
NDKFilter,
@@ -20,18 +19,18 @@ import { NostrFetcher, normalizeRelayUrl } from "nostr-fetch";
import { nip19 } from "nostr-tools";
export class Ark {
#storage: LumeStorage;
public ndk: NDK;
public account: Account;
constructor({
ndk,
storage,
account,
}: {
ndk: NDK;
storage: LumeStorage;
account: Account;
}) {
this.ndk = ndk;
this.#storage = storage;
this.account = account;
}
public async connectDepot() {
@@ -106,8 +105,9 @@ export class Ark {
public async getUserProfile(pubkey?: string) {
try {
const currentUserPubkey = this.account.pubkey;
// get clean pubkey without any special characters
const currentUserPubkey = this.#storage.account.pubkey;
let hexstring = pubkey
? pubkey.replace(/[^a-zA-Z0-9]/g, "").replace("nostr:", "")
: currentUserPubkey;
@@ -141,8 +141,9 @@ export class Ark {
public async getUserContacts(pubkey?: string) {
try {
const currentUserPubkey = this.account.pubkey;
// get clean pubkey without any special characters
const currentUserPubkey = this.#storage.account.pubkey;
let hexstring = pubkey
? pubkey.replace(/[^a-zA-Z0-9]/g, "").replace("nostr:", "")
: currentUserPubkey;
@@ -165,8 +166,8 @@ export class Ark {
const contacts = [...(await user.follows())].map((user) => user.pubkey);
if (!pubkey || pubkey === this.#storage.account.pubkey)
this.#storage.account.contacts = contacts;
if (!pubkey || pubkey === this.account.pubkey)
this.account.contacts = contacts;
return contacts;
} catch (e) {
@@ -177,7 +178,7 @@ export class Ark {
public async getUserRelays({ pubkey }: { pubkey?: string }) {
try {
const user = this.ndk.getUser({
pubkey: pubkey ? pubkey : this.#storage.account.pubkey,
pubkey: pubkey ? pubkey : this.account.pubkey,
});
return await user.relayList();
} catch (e) {
@@ -192,19 +193,19 @@ export class Ark {
});
if (publish) {
this.#storage.account.contacts = tags.map((item) => item[1]);
this.account.contacts = tags.map((item) => item[1]);
return publish;
}
}
public async createContact({ pubkey }: { pubkey: string }) {
const user = this.ndk.getUser({ pubkey: this.#storage.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.#storage.account.pubkey });
const user = this.ndk.getUser({ pubkey: this.account.pubkey });
const contacts = await user.follows();
contacts.delete(new NDKUser({ pubkey: pubkey }));
@@ -362,7 +363,7 @@ export class Ark {
const relayMap = new Map<string, string[]>();
const relayEvents = fetcher.fetchLatestEventsPerAuthor(
{
authors: this.#storage.account.contacts,
authors: this.account.contacts,
relayUrls: connectedRelays,
},
{ kinds: [NDKKind.RelayList] },
@@ -569,7 +570,7 @@ export class Ark {
const event = await this.ndk.fetchEvent({
kinds: [NDKKind.AppRecommendation],
"#d": [unknownKind],
authors: this.#storage.account.contacts || [author],
authors: this.account.contacts || [author],
});
if (event) return event.tags.filter((item) => item[0] !== "d");
@@ -577,7 +578,7 @@ export class Ark {
const altEvent = await this.ndk.fetchEvent({
kinds: [NDKKind.AppHandler],
"#k": [unknownKind],
authors: this.#storage.account.contacts || [author],
authors: this.account.contacts || [author],
});
if (altEvent) return altEvent.tags.filter((item) => item[0] !== "d");

View File

@@ -1,7 +1,7 @@
import { ChevronUpIcon } from "@lume/icons";
import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk";
import { useEffect, useState } from "react";
import { useArk } from "../../provider";
import { useArk } from "../../hooks/useArk";
export function ColumnLiveWidget({
filter,

View File

@@ -1,3 +1,4 @@
import { useStorage } from "@lume/storage";
import { IColumn } from "@lume/types";
import { COL_TYPES } from "@lume/utils";
import {
@@ -8,7 +9,6 @@ import {
useEffect,
useState,
} from "react";
import { useStorage } from "../../provider";
type ColumnContext = {
columns: IColumn[];

View File

@@ -1,6 +1,6 @@
import { NDKAppHandlerEvent } from "@nostr-dev-kit/ndk";
import { useQuery } from "@tanstack/react-query";
import { useArk } from "../../provider";
import { useArk } from "../../hooks/useArk";
export function AppHandler({ tag }: { tag: string[] }) {
const ark = useArk();

View File

@@ -1,6 +1,7 @@
import { webln } from "@getalby/sdk";
import { SendPaymentResponse } from "@getalby/sdk/dist/types";
import { CancelIcon, ZapIcon } from "@lume/icons";
import { useStorage } from "@lume/storage";
import {
compactNumber,
displayNpub,
@@ -13,8 +14,8 @@ import { QRCodeSVG } from "qrcode.react";
import { useEffect, useRef, useState } from "react";
import CurrencyInput from "react-currency-input-field";
import { useNavigate } from "react-router-dom";
import { useArk } from "../../../hooks/useArk";
import { useProfile } from "../../../hooks/useProfile";
import { useArk, useStorage } from "../../../provider";
import { useNoteContext } from "../provider";
export function NoteZap() {
@@ -87,7 +88,7 @@ export function NoteZap() {
useEffect(() => {
async function getWalletConnectURL() {
const uri: string = await invoke("secure_load", {
key: `${storage.account.pubkey}-nwc`,
key: `${ark.account.pubkey}-nwc`,
});
if (uri) setWalletConnectURL(uri);
}

View File

@@ -22,7 +22,7 @@ export function NoteChild({
"\n",
);
const text = parsedContent;
const text = parsedContent as string;
const words = text.split(/( |\n)/);
const hashtags = words.filter((word) => word.startsWith("#"));

View File

@@ -1,3 +1,4 @@
import { useStorage } from "@lume/storage";
import {
AUDIOS,
IMAGES,
@@ -15,7 +16,6 @@ import { nip19 } from "nostr-tools";
import { ReactNode, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import reactStringReplace from "react-string-replace";
import { useStorage } from "../../provider";
import { Hashtag } from "./mentions/hashtag";
import { MentionNote } from "./mentions/note";
import { MentionUser } from "./mentions/user";

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { useArk } from "../../provider";
import { useArk } from "../../hooks/useArk";
import { AppHandler } from "./appHandler";
import { useNoteContext } from "./provider";

View File

@@ -1,7 +1,7 @@
import { NDKEvent, NostrEvent } from "@nostr-dev-kit/ndk";
import { useQuery } from "@tanstack/react-query";
import { Note } from "..";
import { useArk } from "../../../provider";
import { useArk } from "../../../hooks/useArk";
export function RepostNote({
event,

View File

@@ -0,0 +1,4 @@
import { createContext } from "react";
import { type Ark } from "./ark";
export const LumeContext = createContext<Ark>(undefined);

View File

@@ -0,0 +1,10 @@
import { useContext } from "react";
import { LumeContext } from "../context";
export const useArk = () => {
const context = useContext(LumeContext);
if (context === undefined) {
throw new Error("Please import Ark Provider to use useArk() hook");
}
return context;
};

View File

@@ -1,21 +1,24 @@
import { useQuery } from '@tanstack/react-query';
import { useArk } from '../provider';
import { useQuery } from "@tanstack/react-query";
import { useArk } from "./useArk";
export function useEvent(id: string) {
const ark = useArk();
const { status, isLoading, isError, data } = useQuery({
queryKey: ['event', id],
queryFn: async () => {
const event = await ark.getEventById({ id });
if (!event)
throw new Error(`Cannot get event with ${id}, will be retry after 10 seconds`);
return event;
},
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
retry: 2,
});
const ark = useArk();
const { status, isLoading, isError, data } = useQuery({
queryKey: ["event", id],
queryFn: async () => {
const event = await ark.getEventById({ id });
if (!event)
throw new Error(
`Cannot get event with ${id}, will be retry after 10 seconds`,
);
return event;
},
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
staleTime: Infinity,
retry: 2,
});
return { status, isLoading, isError, data };
return { status, isLoading, isError, data };
}

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { useArk } from "../provider";
import { useArk } from "./useArk";
export function useProfile(pubkey: string) {
const ark = useArk();
@@ -20,6 +20,7 @@ export function useProfile(pubkey: string) {
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
staleTime: Infinity,
retry: 2,
});

View File

@@ -1,6 +1,7 @@
import { useStorage } from "@lume/storage";
import { NDKKind, NDKRelayUrl, NDKTag } from "@nostr-dev-kit/ndk";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useArk, useStorage } from "../provider";
import { useArk } from "./useArk";
export function useRelay() {
const ark = useArk();
@@ -14,13 +15,13 @@ export function useRelay() {
) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({
queryKey: ["relays", storage.account.pubkey],
queryKey: ["relays", ark.account.pubkey],
});
// Snapshot the previous value
const prevRelays: NDKTag[] = queryClient.getQueryData([
"relays",
storage.account.pubkey,
ark.account.pubkey,
]);
// create new relay list if not exist
@@ -42,7 +43,7 @@ export function useRelay() {
// Optimistically update to the new value
queryClient.setQueryData(
["relays", storage.account.pubkey],
["relays", ark.account.pubkey],
(prev: NDKTag[]) => [...prev, ["r", relay, purpose ?? ""]],
);
@@ -51,7 +52,7 @@ export function useRelay() {
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ["relays", storage.account.pubkey],
queryKey: ["relays", ark.account.pubkey],
});
},
});
@@ -60,13 +61,13 @@ export function useRelay() {
mutationFn: async (relay: NDKRelayUrl) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({
queryKey: ["relays", storage.account.pubkey],
queryKey: ["relays", ark.account.pubkey],
});
// Snapshot the previous value
const prevRelays: NDKTag[] = queryClient.getQueryData([
"relays",
storage.account.pubkey,
ark.account.pubkey,
]);
if (!prevRelays) return;
@@ -80,14 +81,14 @@ export function useRelay() {
});
// Optimistically update to the new value
queryClient.setQueryData(["relays", storage.account.pubkey], prevRelays);
queryClient.setQueryData(["relays", ark.account.pubkey], prevRelays);
// Return a context object with the snapshotted value
return { prevRelays };
},
onSettled: () => {
queryClient.invalidateQueries({
queryKey: ["relays", storage.account.pubkey],
queryKey: ["relays", ark.account.pubkey],
});
},
});

View File

@@ -1,6 +1,8 @@
export * from "./ark";
export * from "./context";
export * from "./provider";
export * from "./hooks/useEvent";
export * from "./hooks/useArk";
export * from "./hooks/useProfile";
export * from "./hooks/useRelay";
export * from "./components/column/provider";

View File

@@ -1,7 +1,7 @@
import { LoaderIcon } from "@lume/icons";
import { NDKCacheAdapterTauri } from "@lume/ndk-cache-tauri";
import { LumeStorage } from "@lume/storage";
import { QUOTES, delay, sendNativeNotification } from "@lume/utils";
import { useStorage } from "@lume/storage";
import { QUOTES, sendNativeNotification } from "@lume/utils";
import NDK, {
NDKEvent,
NDKKind,
@@ -11,44 +11,28 @@ import NDK, {
NDKRelayAuthPolicies,
} from "@nostr-dev-kit/ndk";
import { fetch } from "@tauri-apps/plugin-http";
import { locale, platform } from "@tauri-apps/plugin-os";
import { relaunch } from "@tauri-apps/plugin-process";
import Database from "@tauri-apps/plugin-sql";
import { check } from "@tauri-apps/plugin-updater";
import Linkify from "linkify-react";
import { normalizeRelayUrl, normalizeRelayUrlSet } from "nostr-fetch";
import { normalizeRelayUrlSet } from "nostr-fetch";
import { PropsWithChildren, useEffect, useState } from "react";
import { createContext, useContextSelector } from "use-context-selector";
import { Ark } from "./ark";
import { LumeContext } from "./context";
type Context = {
storage: LumeStorage;
ark: Ark;
};
const LumeContext = createContext<Context>({
storage: undefined,
ark: undefined,
});
const LumeProvider = ({ children }: PropsWithChildren<object>) => {
const [context, setContext] = useState<Context>(undefined);
const [isNewVersion, setIsNewVersion] = useState(false);
export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
const storage = useStorage();
const [context, setContext] = useState<Ark>(undefined);
async function initNostrSigner({
storage,
nsecbunker,
}: {
storage: LumeStorage;
nsecbunker?: boolean;
}) {
try {
if (!storage.account) return null;
if (!storage.currentUser) return null;
// NIP-46 Signer
if (nsecbunker) {
const localSignerPrivkey = await storage.loadPrivkey(
storage.account.pubkey,
storage.currentUser.pubkey,
);
if (!localSignerPrivkey) return null;
@@ -64,7 +48,7 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
const remoteSigner = new NDKNip46Signer(
bunker,
storage.account.pubkey,
storage.currentUser.pubkey,
localSigner,
);
await remoteSigner.blockUntilReady();
@@ -73,11 +57,8 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
}
// Privkey Signer
const userPrivkey = await storage.loadPrivkey(storage.account.pubkey);
if (!userPrivkey) {
return null;
}
const userPrivkey = await storage.loadPrivkey(storage.currentUser.pubkey);
if (!userPrivkey) return null;
return new NDKPrivateKeySigner(userPrivkey);
} catch (e) {
@@ -87,45 +68,23 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
}
async function init() {
const platformName = await platform();
const osLocale = await locale();
const sqliteAdapter = await Database.load("sqlite:lume_v3.db");
const storage = new LumeStorage(sqliteAdapter, platformName, osLocale);
await storage.init();
// check for new update
if (storage.settings.autoupdate) {
const update = await check();
// install new version
if (update) {
setIsNewVersion(true);
await update.downloadAndInstall();
await relaunch();
}
}
const explicitRelayUrls = normalizeRelayUrlSet([
"wss://bostr.nokotaro.com/",
"wss://nostr.mutinywallet.com",
"wss://nostr.mutinywallet.com/",
]);
if (storage.settings.depot) {
await storage.launchDepot();
await delay(2000);
explicitRelayUrls.push(normalizeRelayUrl("ws://localhost:6090"));
}
// #TODO: user should config outbox relays
const outboxRelayUrls = normalizeRelayUrlSet(["wss://purplepag.es"]);
const outboxRelayUrls = normalizeRelayUrlSet(["wss://purplepag.es/"]);
// #TODO: user should config blacklist relays
// No need to connect depot tunnel url
const blacklistRelayUrls = storage.settings.tunnelUrl.length
? [storage.settings.tunnelUrl, `${storage.settings.tunnelUrl}/`]
: [];
? [
storage.settings.tunnelUrl,
`${storage.settings.tunnelUrl}/`,
"wss://brb.io/",
]
: ["wss://brb.io/"];
const cacheAdapter = new NDKCacheAdapterTauri(storage);
const ndk = new NDK({
@@ -136,7 +95,7 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
enableOutboxModel: !storage.settings.lowPower,
autoConnectUserRelays: !storage.settings.lowPower,
autoFetchUserMutelist: !storage.settings.lowPower,
// clientName: 'Lume',
// clientName: "Lume",
// clientNip89: '',
});
@@ -145,8 +104,7 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
// add signer
const signer = await initNostrSigner({
storage,
nsecbunker: storage.settings.bunker,
nsecbunker: storage.settings.nsecbunker,
});
if (signer) ndk.signer = signer;
@@ -167,19 +125,19 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
};
// update account's metadata
if (storage.account) {
const user = ndk.getUser({ pubkey: storage.account.pubkey });
if (signer) {
const user = ndk.getUser({ pubkey: storage.currentUser.pubkey });
ndk.activeUser = user;
const contacts = await user.follows();
storage.account.contacts = [...contacts].map((user) => user.pubkey);
storage.currentUser.contacts = [...contacts].map((user) => user.pubkey);
// subscribe for new activity
const sub = ndk.subscribe(
{
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap],
"#p": [storage.account.pubkey],
since: Math.floor(Date.now() / 1000),
"#p": [storage.currentUser.pubkey],
},
{ closeOnEose: false, groupable: false },
);
@@ -212,14 +170,14 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
}
// ark utils
const ark = new Ark({ storage, ndk });
const ark = new Ark({ ndk, account: storage.currentUser });
// update context
setContext({ ark, storage });
setContext(ark);
}
useEffect(() => {
if (!context && !isNewVersion) init();
if (!context) init();
}, []);
if (!context) {
@@ -243,37 +201,13 @@ const LumeProvider = ({ children }: PropsWithChildren<object>) => {
</div>
<div className="absolute bottom-5 right-5 inline-flex items-center gap-2.5">
<LoaderIcon className="w-6 h-6 text-blue-500 animate-spin" />
<p className="font-semibold">
{isNewVersion ? "Found a new version, updating..." : "Starting..."}
</p>
<p className="font-semibold">Starting</p>
</div>
</div>
);
}
return (
<LumeContext.Provider
value={{ ark: context.ark, storage: context.storage }}
>
{children}
</LumeContext.Provider>
<LumeContext.Provider value={context}>{children}</LumeContext.Provider>
);
};
const useArk = () => {
const context = useContextSelector(LumeContext, (state) => state.ark);
if (context === undefined) {
throw new Error("Please import Ark Provider to use useArk() hook");
}
return context;
};
const useStorage = () => {
const context = useContextSelector(LumeContext, (state) => state.storage);
if (context === undefined) {
throw new Error("Please import Ark Provider to use useStorage() hook");
}
return context;
};
export { LumeProvider, useArk, useStorage };