feat: refactor
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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("#"));
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
4
packages/ark/src/context.ts
Normal file
4
packages/ark/src/context.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { createContext } from "react";
|
||||
import { type Ark } from "./ark";
|
||||
|
||||
export const LumeContext = createContext<Ark>(undefined);
|
||||
10
packages/ark/src/hooks/useArk.ts
Normal file
10
packages/ark/src/hooks/useArk.ts
Normal 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;
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RepostNote, TextNote, useArk, useStorage } from "@lume/ark";
|
||||
import { RepostNote, TextNote, useArk } from "@lume/ark";
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
import { NDKEvent, NDKFilter, NDKKind } from "@nostr-dev-kit/ndk";
|
||||
@@ -11,7 +11,6 @@ export function HomeRoute({
|
||||
content,
|
||||
}: { colKey: string; content: string }) {
|
||||
const ark = useArk();
|
||||
const storage = useStorage();
|
||||
const ref = useRef<VListHandle>();
|
||||
const cacheKey = `${colKey}-vlist`;
|
||||
|
||||
@@ -40,7 +39,7 @@ export function HomeRoute({
|
||||
filter = {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
"#t": parsed.hashtags.map((item) => item.replace("#", "")),
|
||||
authors: storage.account.contacts,
|
||||
authors: ark.account.contacts,
|
||||
};
|
||||
} else {
|
||||
filter = {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useColumnContext, useStorage } from "@lume/ark";
|
||||
import { useArk, useColumnContext } from "@lume/ark";
|
||||
import { CancelIcon, CheckCircleIcon } from "@lume/icons";
|
||||
import { User } from "@lume/ui";
|
||||
import { useState } from "react";
|
||||
|
||||
export function GroupForm({ id }: { id: number }) {
|
||||
const storage = useStorage();
|
||||
const ark = useArk();
|
||||
const { updateColumn, removeColumn } = useColumnContext();
|
||||
|
||||
const [title, setTitle] = useState<string>(`Group-${id}`);
|
||||
@@ -59,7 +59,7 @@ export function GroupForm({ id }: { id: number }) {
|
||||
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-400">{`${users.length} / ∞`}</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{storage.account?.contacts?.map((item: string) => (
|
||||
{ark.account?.contacts?.map((item: string) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RepostNote, TextNote, useArk, useStorage } from "@lume/ark";
|
||||
import { RepostNote, TextNote, useArk } from "@lume/ark";
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RepostNote, TextNote, useArk, useStorage } from "@lume/ark";
|
||||
import { RepostNote, TextNote, useArk } from "@lume/ark";
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
||||
import { EmptyFeed } from "@lume/ui";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
@@ -9,7 +9,6 @@ import { CacheSnapshot, VList, VListHandle } from "virtua";
|
||||
|
||||
export function HomeRoute({ colKey }: { colKey: string }) {
|
||||
const ark = useArk();
|
||||
const storage = useStorage();
|
||||
const ref = useRef<VListHandle>();
|
||||
const cacheKey = `${colKey}-vlist`;
|
||||
const queryClient = useQueryClient();
|
||||
@@ -34,7 +33,7 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
||||
const events = await ark.getInfiniteEvents({
|
||||
filter: {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
authors: storage.account.contacts,
|
||||
authors: ark.account.contacts,
|
||||
},
|
||||
limit: FETCH_LIMIT,
|
||||
pageParam,
|
||||
@@ -94,7 +93,7 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!storage.account.contacts.length) {
|
||||
if (!ark.account.contacts.length) {
|
||||
return (
|
||||
<div className="px-3 mt-3">
|
||||
<EmptyFeed />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Column, useStorage } from "@lume/ark";
|
||||
import { Column, useArk } from "@lume/ark";
|
||||
import { TimelineIcon } from "@lume/icons";
|
||||
import { IColumn } from "@lume/types";
|
||||
import { EventRoute, UserRoute } from "@lume/ui";
|
||||
@@ -9,7 +9,7 @@ import { HomeRoute } from "./home";
|
||||
|
||||
export function Timeline({ column }: { column: IColumn }) {
|
||||
const colKey = `timeline-${column.id}`;
|
||||
const storage = useStorage();
|
||||
const ark = useArk();
|
||||
const queryClient = useQueryClient();
|
||||
const since = useRef(Math.floor(Date.now() / 1000));
|
||||
|
||||
@@ -35,9 +35,9 @@ export function Timeline({ column }: { column: IColumn }) {
|
||||
<Column.Live
|
||||
filter={{
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
authors: !storage.account.contacts.length
|
||||
? [storage.account.pubkey]
|
||||
: storage.account.contacts,
|
||||
authors: !ark.account.contacts.length
|
||||
? [ark.account.pubkey]
|
||||
: ark.account.contacts,
|
||||
since: since.current,
|
||||
}}
|
||||
onClick={refreshTimeline}
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
RepostNote,
|
||||
TextNote,
|
||||
useArk,
|
||||
useProfile,
|
||||
useStorage,
|
||||
} from "@lume/ark";
|
||||
import { RepostNote, TextNote, useArk, useProfile } from "@lume/ark";
|
||||
import { ArrowRightCircleIcon, LoaderIcon } from "@lume/icons";
|
||||
import { NIP05 } from "@lume/ui";
|
||||
import { FETCH_LIMIT, displayNpub } from "@lume/utils";
|
||||
@@ -17,7 +11,6 @@ import { WindowVirtualizer } from "virtua";
|
||||
|
||||
export function HomeRoute({ id }: { id: string }) {
|
||||
const ark = useArk();
|
||||
const storage = useStorage();
|
||||
|
||||
const { user } = useProfile(id);
|
||||
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
|
||||
@@ -94,7 +87,7 @@ export function HomeRoute({ id }: { id: string }) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (storage.account.contacts.includes(id)) {
|
||||
if (ark.account.contacts.includes(id)) {
|
||||
setFollowed(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@lume/storage",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"main": "./src/index.ts",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
|
||||
2
packages/storage/src/index.ts
Normal file
2
packages/storage/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./storage";
|
||||
export * from "./provider";
|
||||
29
packages/storage/src/provider.tsx
Normal file
29
packages/storage/src/provider.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { locale, platform } from "@tauri-apps/plugin-os";
|
||||
import Database from "@tauri-apps/plugin-sql";
|
||||
import { PropsWithChildren, createContext, useContext } from "react";
|
||||
import { LumeStorage } from "./storage";
|
||||
|
||||
const StorageContext = createContext<LumeStorage>(null);
|
||||
|
||||
const sqliteAdapter = await Database.load("sqlite:lume_v3.db");
|
||||
const platformName = await platform();
|
||||
const osLocale = await locale();
|
||||
|
||||
const db = new LumeStorage(sqliteAdapter, platformName, osLocale);
|
||||
await db.init();
|
||||
|
||||
if (db.settings.depot) await db.launchDepot();
|
||||
|
||||
export const StorageProvider = ({ children }: PropsWithChildren<object>) => {
|
||||
return (
|
||||
<StorageContext.Provider value={db}>{children}</StorageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useStorage = () => {
|
||||
const context = useContext(StorageContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("Please import Storage Provider to use useStorage() hook");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -18,10 +18,10 @@ export class LumeStorage {
|
||||
#depot: Child;
|
||||
readonly platform: Platform;
|
||||
readonly locale: string;
|
||||
public account: Account;
|
||||
public currentUser: Account;
|
||||
public settings: {
|
||||
autoupdate: boolean;
|
||||
bunker: boolean;
|
||||
nsecbunker: boolean;
|
||||
media: boolean;
|
||||
hashtag: boolean;
|
||||
depot: boolean;
|
||||
@@ -37,7 +37,7 @@ export class LumeStorage {
|
||||
this.platform = platform;
|
||||
this.settings = {
|
||||
autoupdate: false,
|
||||
bunker: false,
|
||||
nsecbunker: false,
|
||||
media: true,
|
||||
hashtag: true,
|
||||
depot: false,
|
||||
@@ -52,25 +52,15 @@ export class LumeStorage {
|
||||
const settings = await this.getAllSettings();
|
||||
|
||||
for (const item of settings) {
|
||||
if (item.key === "nsecbunker")
|
||||
this.settings.bunker = !!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 === "media") this.settings.media = !!parseInt(item.value);
|
||||
if (item.key === "depot") this.settings.depot = !!parseInt(item.value);
|
||||
if (item.key === "tunnel_url") this.settings.tunnelUrl = item.value;
|
||||
if (item.key === "lowPower")
|
||||
this.settings.lowPower = !!parseInt(item.value);
|
||||
if (item.key === "translation")
|
||||
this.settings.translation = !!parseInt(item.value);
|
||||
if (item.key === "translateApiKey")
|
||||
this.settings.translateApiKey = item.value;
|
||||
if (item.value.length > 10) {
|
||||
this.settings[item.key] = item.value;
|
||||
} else {
|
||||
this.settings[item.key] = !!parseInt(item.value);
|
||||
}
|
||||
}
|
||||
|
||||
const account = await this.getActiveAccount();
|
||||
if (account) this.account = account;
|
||||
if (account) this.currentUser = account;
|
||||
}
|
||||
|
||||
async #keyring_save(key: string, value: string) {
|
||||
@@ -256,7 +246,7 @@ export class LumeStorage {
|
||||
);
|
||||
|
||||
if (results.length) {
|
||||
this.account = results[0];
|
||||
this.currentUser = results[0];
|
||||
return results[0];
|
||||
}
|
||||
return null;
|
||||
@@ -289,8 +279,8 @@ export class LumeStorage {
|
||||
}
|
||||
|
||||
const account = await this.getActiveAccount();
|
||||
this.account = account;
|
||||
this.account.contacts = [];
|
||||
this.currentUser = account;
|
||||
this.currentUser.contacts = [];
|
||||
|
||||
return account;
|
||||
}
|
||||
@@ -322,7 +312,7 @@ export class LumeStorage {
|
||||
public async updateAccount(column: string, value: string) {
|
||||
const insert = await this.#db.execute(
|
||||
`UPDATE accounts SET ${column} = $1 WHERE id = $2;`,
|
||||
[value, this.account.id],
|
||||
[value, this.currentUser.id],
|
||||
);
|
||||
|
||||
if (insert) {
|
||||
@@ -332,11 +322,11 @@ export class LumeStorage {
|
||||
}
|
||||
|
||||
public async getColumns() {
|
||||
if (!this.account) return [];
|
||||
if (!this.currentUser) return [];
|
||||
|
||||
const columns: Array<IColumn> = await this.#db.select(
|
||||
"SELECT * FROM columns WHERE account_id = $1 ORDER BY created_at DESC;",
|
||||
[this.account.id],
|
||||
[this.currentUser.id],
|
||||
);
|
||||
|
||||
return columns;
|
||||
@@ -349,7 +339,7 @@ export class LumeStorage {
|
||||
) {
|
||||
const insert = await this.#db.execute(
|
||||
"INSERT INTO columns (account_id, kind, title, content) VALUES ($1, $2, $3, $4);",
|
||||
[this.account.id, kind, title, content],
|
||||
[this.currentUser.id, kind, title, content],
|
||||
);
|
||||
|
||||
if (insert) {
|
||||
@@ -429,10 +419,10 @@ export class LumeStorage {
|
||||
}
|
||||
|
||||
public async logout() {
|
||||
this.account = null;
|
||||
this.currentUser = null;
|
||||
return await this.#db.execute(
|
||||
"UPDATE accounts SET is_active = '0' WHERE id = $1;",
|
||||
[this.account.id],
|
||||
[this.currentUser.id],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@lume/ark": "workspace:^",
|
||||
"@lume/icons": "workspace:^",
|
||||
"@lume/storage": "workspace:^",
|
||||
"@lume/utils": "workspace:^",
|
||||
"@nostr-dev-kit/ndk": "^2.3.2",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { useProfile, useStorage } from "@lume/ark";
|
||||
import { useArk, useProfile } from "@lume/ark";
|
||||
import { useNetworkStatus } from "@lume/utils";
|
||||
import * as Avatar from "@radix-ui/react-avatar";
|
||||
import { minidenticon } from "minidenticons";
|
||||
import { Link } from "react-router-dom";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { AccountMoreActions } from "./more";
|
||||
|
||||
export function ActiveAccount() {
|
||||
const storage = useStorage();
|
||||
const ark = useArk();
|
||||
const isOnline = useNetworkStatus();
|
||||
|
||||
const { user } = useProfile(storage.account.pubkey);
|
||||
const { user } = useProfile(ark.account.pubkey);
|
||||
|
||||
const svgURI = `data:image/svg+xml;utf8,${encodeURIComponent(
|
||||
minidenticon(storage.account.pubkey, 90, 50),
|
||||
minidenticon(ark.account.pubkey, 90, 50),
|
||||
)}`;
|
||||
|
||||
return (
|
||||
@@ -21,7 +20,7 @@ export function ActiveAccount() {
|
||||
<Avatar.Root>
|
||||
<Avatar.Image
|
||||
src={user?.picture || user?.image}
|
||||
alt={storage.account.pubkey}
|
||||
alt={ark.account.pubkey}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: "auto" }}
|
||||
@@ -30,7 +29,7 @@ export function ActiveAccount() {
|
||||
<Avatar.Fallback delayMs={150}>
|
||||
<img
|
||||
src={svgURI}
|
||||
alt={storage.account.pubkey}
|
||||
alt={ark.account.pubkey}
|
||||
className="aspect-square h-auto w-full rounded-xl bg-black dark:bg-white"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useStorage } from "@lume/ark";
|
||||
import { useStorage } from "@lume/storage";
|
||||
import * as AlertDialog from "@radix-ui/react-alert-dialog";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useArk, useStorage } from "@lume/ark";
|
||||
import { useArk } from "@lume/ark";
|
||||
import { LoaderIcon } from "@lume/icons";
|
||||
import { FETCH_LIMIT } from "@lume/utils";
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { VList } from "virtua";
|
||||
import { ReplyActivity } from "./reply";
|
||||
import { RepostActivity } from "./repost";
|
||||
import { ZapActivity } from "./zap";
|
||||
@@ -27,7 +26,7 @@ export function ActivityContent() {
|
||||
const events = await ark.getInfiniteEvents({
|
||||
filter: {
|
||||
kinds: [NDKKind.Zap],
|
||||
"#p": [storage.account.pubkey],
|
||||
"#p": [ark.account.pubkey],
|
||||
},
|
||||
limit: 100,
|
||||
pageParam,
|
||||
@@ -52,7 +51,7 @@ export function ActivityContent() {
|
||||
);
|
||||
|
||||
const renderEvent = useCallback((event: NDKEvent) => {
|
||||
if (event.pubkey === storage.account.pubkey) return null;
|
||||
if (event.pubkey === ark.account.pubkey) return null;
|
||||
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MentionNote, useArk, useColumnContext, useStorage } from "@lume/ark";
|
||||
import { MentionNote, useArk, useColumnContext } from "@lume/ark";
|
||||
import { LoaderIcon, TrashIcon } from "@lume/icons";
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { NDKCacheUserProfile } from "@lume/types";
|
||||
import { COL_TYPES, cn, editorValueAtom } from "@lume/utils";
|
||||
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useStorage } from "@lume/ark";
|
||||
import { CheckIcon, LoaderIcon } from "@lume/icons";
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { onboardingAtom } from "@lume/utils";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { motion } from "framer-motion";
|
||||
@@ -20,7 +20,7 @@ export function OnboardingFinishScreen() {
|
||||
const queryKeys = queryCache.getAll().map((cache) => cache.queryKey);
|
||||
|
||||
await queryClient.refetchQueries({
|
||||
queryKey: ["user", storage.account.pubkey],
|
||||
queryKey: ["user", ark.account.pubkey],
|
||||
});
|
||||
|
||||
for (const key of queryKeys) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useArk, useStorage } from "@lume/ark";
|
||||
import { useArk } from "@lume/ark";
|
||||
import { ArrowLeftIcon, LoaderIcon } from "@lume/icons";
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { NDKKind, NDKUserProfile } from "@nostr-dev-kit/ndk";
|
||||
import { motion } from "framer-motion";
|
||||
import { minidenticon } from "minidenticons";
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
RepostNote,
|
||||
TextNote,
|
||||
useArk,
|
||||
useProfile,
|
||||
useStorage,
|
||||
} from "@lume/ark";
|
||||
import { RepostNote, TextNote, useArk, useProfile } from "@lume/ark";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowRightCircleIcon,
|
||||
@@ -22,7 +16,6 @@ import { NIP05 } from "../nip05";
|
||||
|
||||
export function UserRoute() {
|
||||
const ark = useArk();
|
||||
const storage = useStorage();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { id } = useParams();
|
||||
@@ -101,7 +94,7 @@ export function UserRoute() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (storage.account.contacts.includes(id)) {
|
||||
if (ark.account.contacts.includes(id)) {
|
||||
setFollowed(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { atom } from "jotai";
|
||||
|
||||
// Editor
|
||||
export const editorAtom = atom(false);
|
||||
export const editorValueAtom = atom([
|
||||
{
|
||||
@@ -8,7 +9,9 @@ export const editorValueAtom = atom([
|
||||
},
|
||||
]);
|
||||
|
||||
// Onboarding
|
||||
export const onboardingAtom = atom(false);
|
||||
|
||||
// Activity
|
||||
export const activityAtom = atom(false);
|
||||
export const activityUnreadAtom = atom(0);
|
||||
|
||||
Reference in New Issue
Block a user