diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 19dae46c..8f022ba1 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,5 +1,5 @@ { - "name": "lume", + "name": "@lume/desktop", "private": true, "version": "3.0.0", "scripts": { diff --git a/apps/desktop/src/app.tsx b/apps/desktop/src/app.tsx index 4cb8af63..bd61b78f 100644 --- a/apps/desktop/src/app.tsx +++ b/apps/desktop/src/app.tsx @@ -1,10 +1,18 @@ -import { ColumnProvider, LumeProvider } from "@lume/ark"; +import { LoaderIcon } from "@lume/icons"; import { StorageProvider } from "@lume/storage"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { invoke } from "@tauri-apps/api/core"; +import { fetch } from "@tauri-apps/plugin-http"; import { I18nextProvider } from "react-i18next"; +import { + RouterProvider, + createBrowserRouter, + defer, + redirect, +} from "react-router-dom"; import { Toaster } from "sonner"; import i18n from "./i18n"; -import Router from "./router"; +import { ErrorScreen } from "./routes/error"; const queryClient = new QueryClient({ defaultOptions: { @@ -14,17 +22,278 @@ const queryClient = new QueryClient({ }, }); +const router = createBrowserRouter([ + { + async lazy() { + const { AppLayout } = await import("@lume/ui"); + return { Component: AppLayout }; + }, + children: [ + { + path: "/", + errorElement: , + async lazy() { + const { HomeLayout } = await import("@lume/ui"); + return { Component: HomeLayout }; + }, + loader: async () => { + const signer = await invoke("verify_signer"); + if (!signer) return redirect("auth"); + return null; + }, + children: [ + { + index: true, + async lazy() { + const { HomeScreen } = await import("./routes/home"); + return { Component: HomeScreen }; + }, + }, + ], + }, + { + path: "settings", + async lazy() { + const { SettingsLayout } = await import("@lume/ui"); + return { Component: SettingsLayout }; + }, + children: [ + { + index: true, + async lazy() { + const { GeneralSettingScreen } = await import( + "./routes/settings/general" + ); + return { Component: GeneralSettingScreen }; + }, + }, + { + path: "profile", + async lazy() { + const { ProfileSettingScreen } = await import( + "./routes/settings/profile" + ); + return { Component: ProfileSettingScreen }; + }, + }, + { + path: "backup", + async lazy() { + const { BackupSettingScreen } = await import( + "./routes/settings/backup" + ); + return { Component: BackupSettingScreen }; + }, + }, + { + path: "advanced", + async lazy() { + const { AdvancedSettingScreen } = await import( + "./routes/settings/advanced" + ); + return { Component: AdvancedSettingScreen }; + }, + }, + { + path: "nwc", + async lazy() { + const { NWCScreen } = await import("./routes/settings/nwc"); + return { Component: NWCScreen }; + }, + }, + { + path: "about", + async lazy() { + const { AboutScreen } = await import("./routes/settings/about"); + return { Component: AboutScreen }; + }, + }, + ], + }, + { + path: "activity", + async lazy() { + const { ActivityScreen } = await import("./routes/activty"); + return { Component: ActivityScreen }; + }, + children: [ + { + path: ":id", + async lazy() { + const { ActivityIdScreen } = await import("./routes/activty/id"); + return { Component: ActivityIdScreen }; + }, + }, + ], + }, + { + path: "relays", + async lazy() { + const { RelaysScreen } = await import("./routes/relays"); + return { Component: RelaysScreen }; + }, + children: [ + { + index: true, + async lazy() { + const { RelayGlobalScreen } = await import( + "./routes/relays/global" + ); + return { Component: RelayGlobalScreen }; + }, + }, + { + path: "follows", + async lazy() { + const { RelayFollowsScreen } = await import( + "./routes/relays/follows" + ); + return { Component: RelayFollowsScreen }; + }, + }, + { + path: ":url", + loader: async ({ request, params }) => { + return defer({ + relay: fetch(`https://${params.url}`, { + method: "GET", + headers: { + Accept: "application/nostr+json", + }, + signal: request.signal, + }).then((res) => res.json()), + }); + }, + async lazy() { + const { RelayUrlScreen } = await import("./routes/relays/url"); + return { Component: RelayUrlScreen }; + }, + }, + ], + }, + { + path: "depot", + children: [ + { + index: true, + async lazy() { + const { DepotScreen } = await import("./routes/depot"); + return { Component: DepotScreen }; + }, + }, + { + path: "onboarding", + async lazy() { + const { DepotOnboardingScreen } = await import( + "./routes/depot/onboarding" + ); + return { Component: DepotOnboardingScreen }; + }, + }, + ], + }, + ], + }, + { + path: "auth", + errorElement: , + async lazy() { + const { AuthLayout } = await import("@lume/ui"); + return { Component: AuthLayout }; + }, + children: [ + { + index: true, + async lazy() { + const { WelcomeScreen } = await import("./routes/auth/welcome"); + return { Component: WelcomeScreen }; + }, + }, + { + path: "create", + async lazy() { + const { CreateAccountScreen } = await import("./routes/auth/create"); + return { Component: CreateAccountScreen }; + }, + }, + { + path: "create-keys", + async lazy() { + const { CreateAccountKeys } = await import( + "./routes/auth/create-keys" + ); + return { Component: CreateAccountKeys }; + }, + }, + { + path: "create-address", + loader: async () => { + // return await ark.getOAuthServices(); + return null; + }, + async lazy() { + const { CreateAccountAddress } = await import( + "./routes/auth/create-address" + ); + return { Component: CreateAccountAddress }; + }, + }, + { + path: "login", + async lazy() { + const { LoginScreen } = await import("./routes/auth/login"); + return { Component: LoginScreen }; + }, + }, + { + path: "login-key", + async lazy() { + const { LoginWithKey } = await import("./routes/auth/login-key"); + return { Component: LoginWithKey }; + }, + }, + { + path: "login-nsecbunker", + async lazy() { + const { LoginWithNsecbunker } = await import( + "./routes/auth/login-nsecbunker" + ); + return { Component: LoginWithNsecbunker }; + }, + }, + { + path: "login-oauth", + async lazy() { + const { LoginWithOAuth } = await import("./routes/auth/login-oauth"); + return { Component: LoginWithOAuth }; + }, + }, + { + path: "onboarding", + async lazy() { + const { OnboardingScreen } = await import("./routes/auth/onboarding"); + return { Component: OnboardingScreen }; + }, + }, + ], + }, +]); + export default function App() { return ( - - - - - + + + + } + future={{ v7_startTransition: true }} + /> diff --git a/apps/desktop/src/router.tsx b/apps/desktop/src/router.tsx deleted file mode 100644 index 23844d38..00000000 --- a/apps/desktop/src/router.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import { useArk } from "@lume/ark"; -import { LoaderIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; -import { AppLayout, AuthLayout, HomeLayout, SettingsLayout } from "@lume/ui"; -import { fetch } from "@tauri-apps/plugin-http"; -import { - RouterProvider, - createBrowserRouter, - defer, - redirect, -} from "react-router-dom"; -import { ErrorScreen } from "./routes/error"; - -export default function Router() { - const ark = useArk(); - const storage = useStorage(); - - const router = createBrowserRouter([ - { - element: , - children: [ - { - path: "/", - element: , - errorElement: , - loader: async () => { - if (!ark.account) return redirect("auth"); - return null; - }, - children: [ - { - index: true, - async lazy() { - const { HomeScreen } = await import("./routes/home"); - return { Component: HomeScreen }; - }, - }, - ], - }, - { - path: "settings", - element: , - children: [ - { - index: true, - async lazy() { - const { GeneralSettingScreen } = await import( - "./routes/settings/general" - ); - return { Component: GeneralSettingScreen }; - }, - }, - { - path: "profile", - async lazy() { - const { ProfileSettingScreen } = await import( - "./routes/settings/profile" - ); - return { Component: ProfileSettingScreen }; - }, - }, - { - path: "backup", - async lazy() { - const { BackupSettingScreen } = await import( - "./routes/settings/backup" - ); - return { Component: BackupSettingScreen }; - }, - }, - { - path: "advanced", - async lazy() { - const { AdvancedSettingScreen } = await import( - "./routes/settings/advanced" - ); - return { Component: AdvancedSettingScreen }; - }, - }, - { - path: "nwc", - async lazy() { - const { NWCScreen } = await import("./routes/settings/nwc"); - return { Component: NWCScreen }; - }, - }, - { - path: "about", - async lazy() { - const { AboutScreen } = await import("./routes/settings/about"); - return { Component: AboutScreen }; - }, - }, - ], - }, - { - path: "activity", - async lazy() { - const { ActivityScreen } = await import("./routes/activty"); - return { Component: ActivityScreen }; - }, - children: [ - { - path: ":id", - async lazy() { - const { ActivityIdScreen } = await import( - "./routes/activty/id" - ); - return { Component: ActivityIdScreen }; - }, - }, - ], - }, - { - path: "relays", - async lazy() { - const { RelaysScreen } = await import("./routes/relays"); - return { Component: RelaysScreen }; - }, - children: [ - { - index: true, - async lazy() { - const { RelayGlobalScreen } = await import( - "./routes/relays/global" - ); - return { Component: RelayGlobalScreen }; - }, - }, - { - path: "follows", - async lazy() { - const { RelayFollowsScreen } = await import( - "./routes/relays/follows" - ); - return { Component: RelayFollowsScreen }; - }, - }, - { - path: ":url", - loader: async ({ request, params }) => { - return defer({ - relay: fetch(`https://${params.url}`, { - method: "GET", - headers: { - Accept: "application/nostr+json", - }, - signal: request.signal, - }).then((res) => res.json()), - }); - }, - async lazy() { - const { RelayUrlScreen } = await import("./routes/relays/url"); - return { Component: RelayUrlScreen }; - }, - }, - ], - }, - { - path: "depot", - children: [ - { - index: true, - loader: () => { - const depot = storage.checkDepot(); - if (!depot) return redirect("/depot/onboarding/"); - return null; - }, - async lazy() { - const { DepotScreen } = await import("./routes/depot"); - return { Component: DepotScreen }; - }, - }, - { - path: "onboarding", - async lazy() { - const { DepotOnboardingScreen } = await import( - "./routes/depot/onboarding" - ); - return { Component: DepotOnboardingScreen }; - }, - }, - ], - }, - ], - }, - { - path: "auth", - element: , - errorElement: , - children: [ - { - index: true, - async lazy() { - const { WelcomeScreen } = await import("./routes/auth/welcome"); - return { Component: WelcomeScreen }; - }, - }, - { - path: "create", - async lazy() { - const { CreateAccountScreen } = await import( - "./routes/auth/create" - ); - return { Component: CreateAccountScreen }; - }, - }, - { - path: "create-keys", - async lazy() { - const { CreateAccountKeys } = await import( - "./routes/auth/create-keys" - ); - return { Component: CreateAccountKeys }; - }, - }, - { - path: "create-address", - loader: async () => { - return await ark.getOAuthServices(); - }, - async lazy() { - const { CreateAccountAddress } = await import( - "./routes/auth/create-address" - ); - return { Component: CreateAccountAddress }; - }, - }, - { - path: "login", - async lazy() { - const { LoginScreen } = await import("./routes/auth/login"); - return { Component: LoginScreen }; - }, - }, - { - path: "login-key", - async lazy() { - const { LoginWithKey } = await import("./routes/auth/login-key"); - return { Component: LoginWithKey }; - }, - }, - { - path: "login-nsecbunker", - async lazy() { - const { LoginWithNsecbunker } = await import( - "./routes/auth/login-nsecbunker" - ); - return { Component: LoginWithNsecbunker }; - }, - }, - { - path: "login-oauth", - async lazy() { - const { LoginWithOAuth } = await import( - "./routes/auth/login-oauth" - ); - return { Component: LoginWithOAuth }; - }, - }, - { - path: "onboarding", - async lazy() { - const { OnboardingScreen } = await import( - "./routes/auth/onboarding" - ); - return { Component: OnboardingScreen }; - }, - }, - ], - }, - ]); - - return ( - - - - } - future={{ v7_startTransition: true }} - /> - ); -} diff --git a/apps/desktop/src/routes/auth/create-keys.tsx b/apps/desktop/src/routes/auth/create-keys.tsx index b0bd4358..487681d5 100644 --- a/apps/desktop/src/routes/auth/create-keys.tsx +++ b/apps/desktop/src/routes/auth/create-keys.tsx @@ -1,23 +1,19 @@ -import { useArk } from "@lume/ark"; import { CheckIcon, EyeOffIcon, EyeOnIcon, LoaderIcon } from "@lume/icons"; -import { useStorage } from "@lume/storage"; +import { Keys } from "@lume/types"; import { onboardingAtom } from "@lume/utils"; -import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; import * as Checkbox from "@radix-ui/react-checkbox"; +import { invoke } from "@tauri-apps/api/core"; import { desktopDir } from "@tauri-apps/api/path"; import { save } from "@tauri-apps/plugin-dialog"; import { writeTextFile } from "@tauri-apps/plugin-fs"; import { useSetAtom } from "jotai"; import { nanoid } from "nanoid"; -import { getPublicKey, nip19 } from "nostr-tools"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { toast } from "sonner"; export function CreateAccountKeys() { - const ark = useArk(); - const storage = useStorage(); const setOnboarding = useSetAtom(onboardingAtom); const navigate = useNavigate(); @@ -31,36 +27,14 @@ export function CreateAccountKeys() { try { setLoading(true); - const privkey = nip19.decode(key).data as string; - const signer = new NDKPrivateKeySigner(privkey); - const pubkey = getPublicKey(privkey); - - ark.updateNostrSigner({ signer }); - - const downloadPath = await desktopDir(); - const fileName = `nostr_keys_${nanoid(4)}.txt`; - const filePath = await save({ - defaultPath: `${downloadPath}/${fileName}`, - }); - - if (!filePath) { - return toast.info("You need to save account keys before continue."); - } - - await writeTextFile( - filePath, - `Nostr Account\nGenerated by Lume (lume.nu)\n---\nPrivate key: ${key}`, - ); - - const newAccount = await storage.createAccount({ - pubkey: pubkey, - privkey: privkey, - }); - ark.account = newAccount; + // trigger save key + await invoke("save_key", { nsec: key }); + // update state setLoading(false); setOnboarding({ open: true, newUser: true }); + // redirect to next step return navigate("/auth/onboarding", { replace: true }); } catch (e) { setLoading(false); @@ -69,8 +43,11 @@ export function CreateAccountKeys() { }; useEffect(() => { - const privkey = NDKPrivateKeySigner.generate().privateKey; - setKey(nip19.nsecEncode(privkey)); + async function createAccountKeys() { + const keys: Keys = await invoke("create_keys"); + setKey(keys.nsec); + } + createAccountKeys(); }, []); return ( diff --git a/apps/desktop/src/routes/error.tsx b/apps/desktop/src/routes/error.tsx index 7ade36bb..8ea4e36f 100644 --- a/apps/desktop/src/routes/error.tsx +++ b/apps/desktop/src/routes/error.tsx @@ -1,8 +1,5 @@ -import { useArk } from "@lume/ark"; -import { useStorage } from "@lume/storage"; import { downloadDir } from "@tauri-apps/api/path"; import { message, save } from "@tauri-apps/plugin-dialog"; -import { writeTextFile } from "@tauri-apps/plugin-fs"; import { relaunch } from "@tauri-apps/plugin-process"; import { useRouteError } from "react-router-dom"; @@ -12,8 +9,6 @@ interface RouteError { } export function ErrorScreen() { - const ark = useArk(); - const storage = useStorage(); const error = useRouteError() as RouteError; const restart = async () => { @@ -27,6 +22,7 @@ export function ErrorScreen() { const filePath = await save({ defaultPath: `${downloadPath}/${fileName}`, }); + /* const nsec = await storage.loadPrivkey(ark.account.pubkey); if (filePath) { @@ -42,6 +38,7 @@ export function ErrorScreen() { ); } } // else { user cancel action } + */ } catch (e) { await message(e, { title: "Cannot download account keys", diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index d0be4dc5..8573c08e 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -1,17 +1,10 @@ import react from "@vitejs/plugin-react-swc"; -import million from "million/compiler"; import { defineConfig } from "vite"; import topLevelAwait from "vite-plugin-top-level-await"; import viteTsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ plugins: [ - million.vite({ - auto: { - threshold: 0.05, - }, - mute: true, - }), react(), viteTsconfigPaths(), topLevelAwait({ diff --git a/packages/ark/package.json b/packages/ark/package.json index b547fd37..b8518db1 100644 --- a/packages/ark/package.json +++ b/packages/ark/package.json @@ -6,6 +6,7 @@ "dependencies": { "@getalby/sdk": "^3.2.3", "@lume/icons": "workspace:^", + "@lume/storage": "workspace:^", "@lume/utils": "workspace:^", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts index 3ea531f8..0837aca1 100644 --- a/packages/ark/src/ark.ts +++ b/packages/ark/src/ark.ts @@ -4,8 +4,8 @@ import { invoke } from "@tauri-apps/api/core"; export class Ark { public account: CurrentAccount; - constructor(account: CurrentAccount) { - this.account = account; + constructor() { + this.account = null; } public async event_to_bech32(id: string, relays: string[]) { @@ -67,12 +67,12 @@ export class Ark { }; } - public async get_metadata(id: string) { + public async get_profile(id: string) { try { - const cmd: Metadata = await invoke("get_metadata", { id }); + const cmd: Metadata = await invoke("get_profile", { id }); return cmd; } catch (e) { - console.error("failed to get metadata", id); + console.error("failed to get profile", id); } } diff --git a/packages/ark/src/components/note/buttons/repost.tsx b/packages/ark/src/components/note/buttons/repost.tsx index 9cc7f07e..4d0110f8 100644 --- a/packages/ark/src/components/note/buttons/repost.tsx +++ b/packages/ark/src/components/note/buttons/repost.tsx @@ -3,7 +3,6 @@ import { cn, editorAtom, editorValueAtom } from "@lume/utils"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as Tooltip from "@radix-ui/react-tooltip"; import { useSetAtom } from "jotai"; -import { nip19 } from "nostr-tools"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; diff --git a/packages/ark/src/components/user/about.tsx b/packages/ark/src/components/user/about.tsx index aa4cf0ca..8f6247ed 100644 --- a/packages/ark/src/components/user/about.tsx +++ b/packages/ark/src/components/user/about.tsx @@ -31,7 +31,7 @@ export function UserAbout({ className }: { className?: string }) { return (
- {user.about?.trim() || user.bio?.trim() || "No bio"} + {user.profile.about?.trim() || "No bio"}
); } diff --git a/packages/ark/src/components/user/avatar.tsx b/packages/ark/src/components/user/avatar.tsx index 22d14057..7d24b031 100644 --- a/packages/ark/src/components/user/avatar.tsx +++ b/packages/ark/src/components/user/avatar.tsx @@ -46,7 +46,7 @@ export function UserAvatar({ className }: { className?: string }) { /> ) : ( - {user.displayName || user.name || "Anon"} + {user.profile.display_name || user.profile.name || "Anon"} ); } diff --git a/packages/ark/src/components/user/nip05.tsx b/packages/ark/src/components/user/nip05.tsx index 25e8c118..d34ca6ed 100644 --- a/packages/ark/src/components/user/nip05.tsx +++ b/packages/ark/src/components/user/nip05.tsx @@ -1,26 +1,17 @@ import { VerifiedIcon } from "@lume/icons"; import { cn, displayNpub } from "@lume/utils"; import { useQuery } from "@tanstack/react-query"; -import { useArk } from "../../hooks/useArk"; import { useUserContext } from "./provider"; -export function UserNip05({ - pubkey, - className, -}: { pubkey: string; className?: string }) { - const ark = useArk(); +export function UserNip05({ className }: { className?: string }) { const user = useUserContext(); const { isLoading, data: verified } = useQuery({ - queryKey: ["nip05", user?.nip05], + queryKey: ["nip05", user?.profile.nip05], queryFn: async ({ signal }: { signal: AbortSignal }) => { if (!user) return false; - if (!user.nip05) return false; - return ark.validateNIP05({ - pubkey, - nip05: user.nip05, - signal, - }); + if (!user.profile.nip05) return false; + return false; }, enabled: !!user, }); @@ -39,11 +30,11 @@ export function UserNip05({ return (

- {!user?.nip05 - ? displayNpub(pubkey, 16) - : user?.nip05?.startsWith("_@") - ? user?.nip05?.replace("_@", "") - : user?.nip05} + {!user?.profile.nip05 + ? displayNpub(user.pubkey, 16) + : user?.profile.nip05?.startsWith("_@") + ? user?.profile.nip05?.replace("_@", "") + : user?.profile.nip05}

{!isLoading && verified ? ( diff --git a/packages/ark/src/components/user/provider.tsx b/packages/ark/src/components/user/provider.tsx index bd97492a..df533312 100644 --- a/packages/ark/src/components/user/provider.tsx +++ b/packages/ark/src/components/user/provider.tsx @@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import { ReactNode, createContext, useContext } from "react"; import { useArk } from "../../hooks/useArk"; -const UserContext = createContext(null); +const UserContext = createContext<{ pubkey: string; profile: Metadata }>(null); export function UserProvider({ pubkey, @@ -11,12 +11,12 @@ export function UserProvider({ embed, }: { pubkey: string; children: ReactNode; embed?: string }) { const ark = useArk(); - const { data: user } = useQuery({ + const { data: profile } = useQuery({ queryKey: ["user", pubkey], queryFn: async () => { if (embed) return JSON.parse(embed) as Metadata; - const profile = await ark.get_metadata(pubkey); + const profile = await ark.get_profile(pubkey); if (!profile) throw new Error( @@ -32,7 +32,11 @@ export function UserProvider({ retry: 2, }); - return {children}; + return ( + + {children} + + ); } export function useUserContext() { diff --git a/packages/ark/src/context.ts b/packages/ark/src/context.ts deleted file mode 100644 index 918a4536..00000000 --- a/packages/ark/src/context.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { createContext } from "react"; -import { type Ark } from "./ark"; - -export const LumeContext = createContext(undefined); diff --git a/packages/ark/src/hooks/useArk.ts b/packages/ark/src/hooks/useArk.ts index 8d45b8dc..df7bde1b 100644 --- a/packages/ark/src/hooks/useArk.ts +++ b/packages/ark/src/hooks/useArk.ts @@ -1,8 +1,8 @@ import { useContext } from "react"; -import { LumeContext } from "../context"; +import { ArkContext } from "../provider"; export const useArk = () => { - const context = useContext(LumeContext); + const context = useContext(ArkContext); if (context === undefined) { throw new Error("Please import Ark Provider to use useArk() hook"); } diff --git a/packages/ark/src/hooks/useProfile.ts b/packages/ark/src/hooks/useProfile.ts index 87ff0917..73784c91 100644 --- a/packages/ark/src/hooks/useProfile.ts +++ b/packages/ark/src/hooks/useProfile.ts @@ -1,4 +1,4 @@ -import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { useArk } from "./useArk"; export function useProfile(pubkey: string) { @@ -10,7 +10,7 @@ export function useProfile(pubkey: string) { } = useQuery({ queryKey: ["user", pubkey], queryFn: async () => { - const profile = await ark.get_metadata(pubkey); + const profile = await ark.get_profile(pubkey); if (!profile) throw new Error( `Cannot get metadata for ${pubkey}, will be retry after 10 seconds`, diff --git a/packages/ark/src/hooks/useRelayList.ts b/packages/ark/src/hooks/useRelayList.ts index 85753c69..b20488bf 100644 --- a/packages/ark/src/hooks/useRelayList.ts +++ b/packages/ark/src/hooks/useRelayList.ts @@ -1,6 +1,4 @@ -import { NDKKind, NDKTag } from "@nostr-dev-kit/ndk"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { normalizeRelayUrl } from "nostr-fetch"; import { useArk } from "./useArk"; export function useRelaylist() { diff --git a/packages/ark/src/index.ts b/packages/ark/src/index.ts index 5d591207..3e8307f7 100644 --- a/packages/ark/src/index.ts +++ b/packages/ark/src/index.ts @@ -1,5 +1,4 @@ export * from "./ark"; -export * from "./context"; export * from "./provider"; export * from "./hooks/useEvent"; export * from "./hooks/useArk"; diff --git a/packages/ark/src/provider.tsx b/packages/ark/src/provider.tsx index 492481bc..a2b0812c 100644 --- a/packages/ark/src/provider.tsx +++ b/packages/ark/src/provider.tsx @@ -1,18 +1,9 @@ -import { PropsWithChildren, useEffect, useState } from "react"; +import { PropsWithChildren, createContext, useMemo } from "react"; import { Ark } from "./ark"; -import { LumeContext } from "./context"; -export const LumeProvider = ({ children }: PropsWithChildren) => { - const [ark, setArk] = useState(undefined); +export const ArkContext = createContext(undefined); - useEffect(() => { - async function setupArk() { - const _ark = new Ark(); - setArk(_ark); - } - - if (!ark) setupArk(); - }, []); - - return {children}; +export const ArkProvider = ({ children }: PropsWithChildren) => { + const ark = useMemo(() => new Ark(), []); + return {children}; }; diff --git a/packages/storage/package.json b/packages/storage/package.json index f7e00b4c..67e0bbbe 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -8,7 +8,7 @@ "access": "public" }, "dependencies": { - "nostr-tools": "~1.17.0", + "@tauri-apps/plugin-store": "2.0.0-beta.0", "react": "^18.2.0" }, "devDependencies": { diff --git a/packages/storage/src/provider.tsx b/packages/storage/src/provider.tsx index 5122462b..d9f8a208 100644 --- a/packages/storage/src/provider.tsx +++ b/packages/storage/src/provider.tsx @@ -1,18 +1,15 @@ import { locale, platform } from "@tauri-apps/plugin-os"; -import Database from "@tauri-apps/plugin-sql"; +import { Store } from "@tauri-apps/plugin-store"; import { PropsWithChildren, createContext, useContext } from "react"; import { LumeStorage } from "./storage"; const StorageContext = createContext(null); -const sqliteAdapter = await Database.load("sqlite:lume_v3.db"); +const store = new Store("lume.data"); 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(); +const db = new LumeStorage(store, platformName, osLocale); export const StorageProvider = ({ children }: PropsWithChildren) => { return ( diff --git a/packages/storage/src/storage.ts b/packages/storage/src/storage.ts index b31d3565..e68be0f1 100644 --- a/packages/storage/src/storage.ts +++ b/packages/storage/src/storage.ts @@ -1,458 +1,41 @@ -import { - Account, - IColumn, - Interests, - NDKCacheEvent, - NDKCacheEventTag, - NDKCacheUser, - NDKCacheUserProfile, -} from "@lume/types"; -import { invoke } from "@tauri-apps/api/core"; -import { resolve, appConfigDir, resolveResource } from "@tauri-apps/api/path"; -import { VITE_FLATPAK_RESOURCE } from "@lume/utils"; import { Platform } from "@tauri-apps/plugin-os"; -import { Child, Command } from "@tauri-apps/plugin-shell"; -import Database from "@tauri-apps/plugin-sql"; -import { nip19 } from "nostr-tools"; +import { Store } from "@tauri-apps/plugin-store"; export class LumeStorage { - #db: Database; - #depot: Child; + #store: Store; readonly platform: Platform; readonly locale: string; - public currentUser: Account; - public interests: Interests; - public nwc: string; public settings: { autoupdate: boolean; nsecbunker: boolean; media: boolean; hashtag: boolean; - depot: boolean; - tunnelUrl: string; lowPower: boolean; translation: boolean; translateApiKey: string; instantZap: boolean; + defaultZapAmount: number; }; - constructor(db: Database, platform: Platform, locale: string) { - this.#db = db; + constructor(store: Store, platform: Platform, locale: string) { + this.#store = store; this.locale = locale; this.platform = platform; - this.interests = null; - this.nwc = null; this.settings = { autoupdate: false, nsecbunker: false, media: true, hashtag: true, - depot: false, - tunnelUrl: "", lowPower: false, translation: false, translateApiKey: "", instantZap: false, + defaultZapAmount: 21, }; } - public async init() { - const settings = await this.getAllSettings(); - const account = await this.getActiveAccount(); - - if (account) { - this.currentUser = account; - this.interests = await this.getInterests(); - } - - for (const item of settings) { - if (item.value.length > 10) { - this.settings[item.key] = item.value; - } else { - this.settings[item.key] = !!parseInt(item.value); - } - } - } - - 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 }); - } - - public async launchDepot() { - const configPath = - VITE_FLATPAK_RESOURCE !== null - ? await resolve("/", VITE_FLATPAK_RESOURCE) - : await resolveResource("resources/config.toml"); - const dataPath = await appConfigDir(); - - const command = Command.sidecar("bin/depot", [ - "-c", - configPath, - "-d", - dataPath, - ]); - this.#depot = await command.spawn(); - } - - public checkDepot() { - if (this.#depot) return true; - return false; - } - - public async stopDepot() { - if (this.#depot) return this.#depot.kill(); - } - - public async getCacheUser(pubkey: string) { - const results: Array = await this.#db.select( - "SELECT * FROM ndk_users WHERE pubkey = $1 ORDER BY pubkey DESC LIMIT 1;", - [pubkey], - ); - - if (!results.length) return null; - - if (typeof results[0].profile === "string") - results[0].profile = JSON.parse(results[0].profile); - - return results[0]; - } - - public async getCacheEvent(id: string) { - const results: Array = await this.#db.select( - "SELECT * FROM ndk_events WHERE id = $1 ORDER BY id DESC LIMIT 1;", - [id], - ); - - if (!results.length) return null; - return results[0]; - } - - public async getCacheEvents(ids: string[]) { - const idsArr = `'${ids.join("','")}'`; - - const results: Array = await this.#db.select( - `SELECT * FROM ndk_events WHERE id IN (${idsArr}) ORDER BY id;`, - ); - - if (!results.length) return []; - return results; - } - - public async getCacheEventsByPubkey(pubkey: string) { - const results: Array = await this.#db.select( - "SELECT * FROM ndk_events WHERE pubkey = $1 ORDER BY id;", - [pubkey], - ); - - if (!results.length) return []; - return results; - } - - public async getCacheEventsByKind(kind: number) { - const results: Array = await this.#db.select( - "SELECT * FROM ndk_events WHERE kind = $1 ORDER BY id;", - [kind], - ); - - if (!results.length) return []; - return results; - } - - public async getCacheEventsByKindAndAuthor(kind: number, pubkey: string) { - const results: Array = await this.#db.select( - "SELECT * FROM ndk_events WHERE kind = $1 AND pubkey = $2 ORDER BY id;", - [kind, pubkey], - ); - - if (!results.length) return []; - return results; - } - - public async getCacheEventTagsByTagValue(tagValue: string) { - const results: Array = await this.#db.select( - "SELECT * FROM ndk_eventtags WHERE tagValue = $1 ORDER BY id;", - [tagValue], - ); - - if (!results.length) return []; - return results; - } - - public async setCacheEvent({ - id, - pubkey, - content, - kind, - createdAt, - relay, - event, - }: NDKCacheEvent) { - return await this.#db.execute( - "INSERT OR IGNORE INTO ndk_events (id, pubkey, content, kind, createdAt, relay, event) VALUES ($1, $2, $3, $4, $5, $6, $7);", - [id, pubkey, content, kind, createdAt, relay, event], - ); - } - - public async setCacheEventTag({ - id, - eventId, - tag, - value, - tagValue, - }: NDKCacheEventTag) { - return await this.#db.execute( - "INSERT OR IGNORE INTO ndk_eventtags (id, eventId, tag, value, tagValue) VALUES ($1, $2, $3, $4, $5);", - [id, eventId, tag, value, tagValue], - ); - } - - public async setCacheProfiles(profiles: Array) { - return await Promise.all( - profiles.map( - async (profile) => - await this.#db.execute( - "INSERT OR IGNORE INTO ndk_users (pubkey, profile, createdAt) VALUES ($1, $2, $3);", - [profile.pubkey, profile.profile, profile.createdAt], - ), - ), - ); - } - - public async getAllCacheUsers() { - const results: Array = await this.#db.select( - "SELECT * FROM ndk_users ORDER BY createdAt DESC;", - ); - - if (!results.length) return []; - - const users: NDKCacheUserProfile[] = results.map((item) => ({ - npub: nip19.npubEncode(item.pubkey), - ...JSON.parse(item.profile as string), - })); - - return users; - } - - public async checkAccount() { - const result: Array<{ total: string }> = await this.#db.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.#db.select( - 'SELECT * FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;', - ); - - if (results.length) { - this.currentUser = results[0]; - return results[0]; - } - return null; - } - - public async createAccount({ - pubkey, - privkey, - }: { - pubkey: string; - privkey?: string; - }) { - const existAccounts: Array = await this.#db.select( - "SELECT * FROM accounts WHERE pubkey = $1 ORDER BY id DESC LIMIT 1;", - [pubkey], - ); - - if (existAccounts.length) { - await this.#db.execute( - "UPDATE accounts SET is_active = '1' WHERE pubkey = $1;", - [pubkey], - ); - } else { - await this.#db.execute( - "INSERT OR IGNORE INTO accounts (pubkey, is_active) VALUES ($1, $2);", - [pubkey, 1], - ); - - if (privkey) await this.#keyring_save(pubkey, privkey); - } - - const account = await this.getActiveAccount(); - 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.#db.execute( - `UPDATE accounts SET ${column} = $1 WHERE id = $2;`, - [value, this.currentUser.id], - ); - - if (insert) { - const account = await this.getActiveAccount(); - return account; - } - } - - public async getColumns() { - if (!this.currentUser) return []; - - const columns: Array = await this.#db.select( - "SELECT * FROM columns WHERE account_id = $1 ORDER BY created_at DESC;", - [this.currentUser.id], - ); - - return columns; - } - - public async createColumn( - kind: number, - title: string, - content: string | string[], - ) { - const insert = await this.#db.execute( - "INSERT INTO columns (account_id, kind, title, content) VALUES ($1, $2, $3, $4);", - [this.currentUser.id, kind, title, content], - ); - - if (insert) { - const columns: Array = await this.#db.select( - "SELECT * FROM columns WHERE id = $1 ORDER BY id DESC LIMIT 1;", - [insert.lastInsertId], - ); - if (!columns.length) console.error("get created widget failed"); - return columns[0]; - } - } - - public async updateColumn(id: number, title: string, content: string) { - return await this.#db.execute( - "UPDATE columns SET title = $1, content = $2 WHERE id = $3;", - [title, content, id], - ); - } - - public async removeColumn(id: number) { - const res = await this.#db.execute("DELETE FROM columns WHERE id = $1;", [ - id, - ]); - if (res) return id; - } - - public async createSetting(key: string, value: string | undefined) { - const currentSetting = await this.checkSettingValue(key); - - if (!currentSetting) { - if (key !== "translateApiKey" && key !== "tunnelUrl") - this.settings[key] === !!parseInt(value); - - return await this.#db.execute( - "INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);", - [key, value], - ); - } - - return await this.#db.execute( - "UPDATE settings SET value = $1 WHERE key = $2;", - [value, key], - ); - } - - public async getAllSettings() { - const results: { key: string; value: string }[] = await this.#db.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.#db.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.#db.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 getInterests() { - const results: { key: string; value: string }[] = await this.#db.select( - "SELECT * FROM settings WHERE key = 'interests' ORDER BY id DESC LIMIT 1;", - ); - - if (!results.length) return null; - if (!results[0].value.length) return null; - - return JSON.parse(results[0].value) as Interests; - } - - public async clearCache() { - await this.#db.execute("DELETE FROM ndk_events;"); - await this.#db.execute("DELETE FROM ndk_eventtags;"); - await this.#db.execute("DELETE FROM ndk_users;"); - } - - public async clearProfileCache(pubkey: string) { - await this.#db.execute("DELETE FROM ndk_users WHERE pubkey = $1;", [ - pubkey, - ]); - } - - public async logout() { - await this.createSetting("nsecbunker", "0"); - await this.#db.execute( - "UPDATE accounts SET is_active = '0' WHERE id = $1;", - [this.currentUser.id], - ); - - this.currentUser = null; - this.nwc = null; + public async createSetting(key: string, value: string | boolean) { + this.settings[key] = value; + await this.#store.set(this.settings[key], { value }); } } diff --git a/packages/types/package.json b/packages/types/package.json index c455b362..e78d98fb 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,7 @@ { "name": "@lume/types", "version": "0.0.0", + "main": "./index.d.ts", "types": "./index.d.ts", "private": true, "license": "MIT", diff --git a/packages/ui/src/layouts/app.tsx b/packages/ui/src/layouts/app.tsx index 3c099cb5..8b715c87 100644 --- a/packages/ui/src/layouts/app.tsx +++ b/packages/ui/src/layouts/app.tsx @@ -1,24 +1,21 @@ +import { useStorage } from "@lume/storage"; import { cn } from "@lume/utils"; -import { type Platform } from "@tauri-apps/plugin-os"; import { Outlet } from "react-router-dom"; import { Editor } from "../editor/column"; import { Navigation } from "../navigation"; import { SearchDialog } from "../search/dialog"; -import { WindowTitleBar } from "../titlebar"; -export function AppLayout({ platform }: { platform: Platform }) { +export function AppLayout() { + const storage = useStorage(); + return (
- {platform === "windows" ? ( - - ) : ( -
- )} +
diff --git a/packages/ui/src/layouts/auth.tsx b/packages/ui/src/layouts/auth.tsx index e262f96f..6f29cfda 100644 --- a/packages/ui/src/layouts/auth.tsx +++ b/packages/ui/src/layouts/auth.tsx @@ -1,9 +1,7 @@ -import { ArrowLeftIcon, SettingsIcon } from "@lume/icons"; -import { type Platform } from "@tauri-apps/plugin-os"; +import { ArrowLeftIcon } from "@lume/icons"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; -import { WindowTitleBar } from "../titlebar"; -export function AuthLayout({ platform }: { platform: Platform }) { +export function AuthLayout() { const location = useLocation(); const navigate = useNavigate(); @@ -11,11 +9,7 @@ export function AuthLayout({ platform }: { platform: Platform }) { return (
- {platform === "windows" ? ( - - ) : ( -
- )} +
{canGoBack ? ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60e0ef5c..0468b3fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -345,30 +345,33 @@ importers: '@lume/icons': specifier: workspace:^ version: link:../icons + '@lume/storage': + specifier: workspace:^ + version: link:../storage '@lume/utils': specifier: workspace:^ version: link:../utils '@radix-ui/react-avatar': specifier: ^1.0.4 - version: 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) + version: 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-collapsible': specifier: ^1.0.3 - version: 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) + version: 1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-dialog': specifier: ^1.0.5 - version: 1.0.5(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) + version: 1.0.5(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-dropdown-menu': specifier: ^2.0.6 - version: 2.0.6(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) + version: 2.0.6(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-hover-card': specifier: ^1.0.7 - version: 1.0.7(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) + version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-popover': specifier: ^1.0.7 - version: 1.0.7(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) + version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-tooltip': specifier: ^1.0.7 - version: 1.0.7(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) + version: 1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query': specifier: ^5.18.1 version: 5.18.1(react@18.2.0) @@ -1052,9 +1055,9 @@ importers: packages/storage: dependencies: - nostr-tools: - specifier: ~1.17.0 - version: 1.17.0(typescript@5.3.3) + '@tauri-apps/plugin-store': + specifier: 2.0.0-beta.0 + version: 2.0.0-beta.0 react: specifier: ^18.2.0 version: 18.2.0 @@ -2569,26 +2572,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-arrow@1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-avatar@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==} peerDependencies: @@ -2613,29 +2596,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-avatar@1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==} peerDependencies: @@ -2692,33 +2652,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-collapsible@1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} peerDependencies: @@ -2743,29 +2676,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-collection@1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.52)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -2828,39 +2738,6 @@ packages: react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) dev: false - /@radix-ui/react-dialog@1.0.5(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - aria-hidden: 1.2.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) - dev: false - /@radix-ui/react-direction@1.0.1(@types/react@18.2.52)(react@18.2.0): resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} peerDependencies: @@ -2900,30 +2777,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-dismissable-layer@1.0.5(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-dropdown-menu@2.0.6(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==} peerDependencies: @@ -2951,32 +2804,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-dropdown-menu@2.0.6(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-menu': 2.0.6(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.52)(react@18.2.0): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -3014,28 +2841,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-focus-scope@1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-hover-card@1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==} peerDependencies: @@ -3065,34 +2870,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-hover-card@1.0.7(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-popper': 1.1.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-id@1.0.1(@types/react@18.2.52)(react@18.2.0): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -3146,43 +2923,6 @@ packages: react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) dev: false - /@radix-ui/react-menu@2.0.6(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-popper': 1.1.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-roving-focus': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - aria-hidden: 1.2.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) - dev: false - /@radix-ui/react-popover@1.0.7(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} peerDependencies: @@ -3218,40 +2958,6 @@ packages: react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) dev: false - /@radix-ui/react-popover@1.0.7(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-popper': 1.1.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - aria-hidden: 1.2.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) - dev: false - /@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} peerDependencies: @@ -3282,35 +2988,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-popper@1.1.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-arrow': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/rect': 1.0.1 - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} peerDependencies: @@ -3332,26 +3009,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-portal@1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} peerDependencies: @@ -3374,27 +3031,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-presence@1.0.1(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: @@ -3416,26 +3052,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-primitive@1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} peerDependencies: @@ -3465,34 +3081,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-roving-focus@1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-collection': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-direction': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-select@2.0.0(@types/react-dom@18.2.18)(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==} peerDependencies: @@ -3608,37 +3196,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-tooltip@1.0.7(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/primitive': 1.0.1 - '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-context': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.0.5(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-popper': 1.1.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.0.4(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.0.1(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.0.2(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.52)(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.52)(react@18.2.0): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -3762,26 +3319,6 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false - /@radix-ui/react-visually-hidden@1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.23.9 - '@radix-ui/react-primitive': 1.0.3(@types/react@18.2.52)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.52 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - /@radix-ui/rect@1.0.1: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} dependencies: @@ -4424,6 +3961,12 @@ packages: '@tauri-apps/api': 2.0.0-beta.0 dev: false + /@tauri-apps/plugin-store@2.0.0-beta.0: + resolution: {integrity: sha512-DT3pzMyNcgO90hDgmnN7j5fYQIaaD54gbi0oKi7n4Nwa6y5GqHsgpnzot9IBSOTS6kYy6D8yrN43XN/xwG4vUg==} + dependencies: + '@tauri-apps/api': 2.0.0-beta.0 + dev: false + /@tauri-apps/plugin-updater@2.0.0-beta.0: resolution: {integrity: sha512-TkKzngrgg8dQOr869OcObLdN10yXNiT/ERQp7sRYvV0vMpRJhYSIwTkpF+UkZGuEXtSqqE0FJEnb+4WuCelMdw==} dependencies: diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f86a2374..8aa6e012 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -17,6 +17,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aes" version = "0.7.5" @@ -40,6 +50,49 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "age" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edeef7d7b199195a2d7d7a8155d2d04aee736e60c5c7bdd7097d115369a8817d" +dependencies = [ + "age-core", + "base64", + "bech32", + "chacha20poly1305", + "cookie-factory", + "hmac", + "i18n-embed", + "i18n-embed-fl", + "lazy_static", + "nom", + "pin-project", + "rand 0.8.5", + "rust-embed", + "scrypt", + "sha2", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "age-core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5f11899bc2bbddd135edbc30c36b1924fa59d0746bb45beb5933fafe3fe509b" +dependencies = [ + "base64", + "chacha20poly1305", + "cookie-factory", + "hkdf", + "io_tee", + "nom", + "rand 0.8.5", + "secrecy", + "sha2", +] + [[package]] name = "ahash" version = "0.8.7" @@ -170,6 +223,12 @@ dependencies = [ "x11rb 0.12.0", ] +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + [[package]] name = "as-raw-xcb-connection" version = "1.0.1" @@ -819,6 +878,19 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher 0.4.4", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.33" @@ -849,6 +921,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -962,6 +1035,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1125,6 +1204,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "darling" version = "0.20.5" @@ -1160,6 +1266,19 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -1306,6 +1425,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1543,6 +1673,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fiat-crypto" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + [[package]] name = "field-offset" version = "0.3.6" @@ -1565,6 +1701,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml 0.5.11", +] + [[package]] name = "flatbuffers" version = "23.5.26" @@ -1585,6 +1730,50 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluent" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell 0.10.3", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78" +dependencies = [ + "thiserror", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2372,6 +2561,75 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "i18n-config" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ce3c48cbc21fd5b22b9331f32b5b51f6ad85d969b99e793427332e76e7640" +dependencies = [ + "log", + "serde", + "serde_derive", + "thiserror", + "toml 0.8.2", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94205d95764f5bb9db9ea98fa77f89653365ca748e27161f5bbea2ffd50e459c" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "lazy_static", + "log", + "parking_lot", + "rust-embed", + "thiserror", + "unic-langid", + "walkdir 2.4.0", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc1f8715195dffc4caddcf1cf3128da15fe5d8a137606ea8856c9300047d5a2" +dependencies = [ + "dashmap", + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.48", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81093c4701672f59416582fe3145676126fd23ba5db910acad0793c1108aaa58" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -2488,6 +2746,25 @@ dependencies = [ "web-sys", ] +[[package]] +name = "intl-memoizer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -2499,6 +2776,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "io_tee" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" + [[package]] name = "ipnet" version = "2.9.0" @@ -2827,6 +3110,7 @@ dependencies = [ name = "lume" version = "3.0.0" dependencies = [ + "age", "keyring", "nostr-sdk", "serde", @@ -2960,6 +3244,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "minisign-verify" version = "0.2.1" @@ -3082,6 +3372,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nostr" version = "0.27.0" @@ -3537,6 +3837,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3687,6 +3997,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -3716,6 +4046,12 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + [[package]] name = "plist" version = "1.6.0" @@ -3773,6 +4109,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -4149,12 +4496,52 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rust-embed" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir 2.4.0", +] + +[[package]] +name = "rust-embed-impl" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.48", + "walkdir 2.4.0", +] + +[[package]] +name = "rust-embed-utils" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +dependencies = [ + "sha2", + "walkdir 2.4.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -4240,6 +4627,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "same-file" version = "0.1.3" @@ -4306,6 +4702,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2 0.12.2", + "salsa20", + "sha2", +] + [[package]] name = "sct" version = "0.7.1" @@ -4337,6 +4744,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + [[package]] name = "secret-service" version = "3.0.1" @@ -4399,6 +4815,21 @@ dependencies = [ "thin-slice", ] +[[package]] +name = "self_cell" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" +dependencies = [ + "self_cell 1.0.3", +] + +[[package]] +name = "self_cell" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" + [[package]] name = "semver" version = "1.0.21" @@ -5537,6 +5968,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "tinystr" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece" +dependencies = [ + "displaydoc", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -5643,6 +6083,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.7.8" @@ -5824,6 +6273,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "type-map" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46" +dependencies = [ + "rustc-hash", +] + [[package]] name = "typenum" version = "1.17.0" @@ -5841,6 +6299,25 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "unic-langid" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238722e6d794ed130f91f4ea33e01fcff4f188d92337a21297892521c72df516" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd55a2063fdea4ef1f8633243a7b0524cbeef1905ae04c31a1c9b9775c55bc6" +dependencies = [ + "serde", + "tinystr", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -5868,6 +6345,16 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -6762,6 +7249,18 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "xattr" version = "1.3.1" @@ -6880,6 +7379,26 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "zip" version = "0.6.6" @@ -6894,7 +7413,7 @@ dependencies = [ "crossbeam-utils", "flate2", "hmac", - "pbkdf2", + "pbkdf2 0.11.0", "sha1", "time", "zstd", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 17be8290..b1297e76 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -38,6 +38,7 @@ tauri-plugin-window-state = "2.0.0-beta" tauri-plugin-theme = { git = "https://github.com/wyhaya/tauri-plugin-theme" } webpage = { version = "2.0", features = ["serde"] } keyring = "2" +age = "0.10.0" [features] # by default Tauri runs in production mode diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json index 29169e3b..a1470bf2 100644 --- a/src-tauri/capabilities/main.json +++ b/src-tauri/capabilities/main.json @@ -1,41 +1,42 @@ { - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "desktop-capability", - "description": "Capability for the desktop", - "platforms": ["linux", "macOS", "windows"], - "windows": ["main", "settings", "event-*", "user-*", "column-*"], - "permissions": [ - "path:default", - "event:default", - "window:default", - "app:default", - "resources:default", - "menu:default", - "tray:default", - "theme:allow-set-theme", - "theme:allow-get-theme", - "notification:allow-is-permission-granted", - "notification:allow-request-permission", - "notification:default", - "os:allow-locale", - { - "identifier": "http:default", - "allow": [ - { - "url": "http://**/" - }, - { - "url": "https://**/" - } - ] - }, - { - "identifier": "fs:allow-read-text-file", - "allow": [ - { - "path": "$RESOURCE/locales/*" - } - ] - } - ] + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "desktop-capability", + "description": "Capability for the desktop", + "platforms": ["linux", "macOS", "windows"], + "windows": ["main", "settings", "event-*", "user-*", "column-*"], + "permissions": [ + "path:default", + "event:default", + "window:default", + "app:default", + "resources:default", + "menu:default", + "tray:default", + "theme:allow-set-theme", + "theme:allow-get-theme", + "notification:allow-is-permission-granted", + "notification:allow-request-permission", + "notification:default", + "os:allow-locale", + "os:allow-platform", + { + "identifier": "http:default", + "allow": [ + { + "url": "http://**/" + }, + { + "url": "https://**/" + } + ] + }, + { + "identifier": "fs:allow-read-text-file", + "allow": [ + { + "path": "$RESOURCE/locales/*" + } + ] + } + ] } diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index 8423fac5..af7cc912 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","context":"local","windows":["main","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","theme:allow-set-theme","theme:allow-get-theme","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}} \ No newline at end of file +{"desktop-capability":{"identifier":"desktop-capability","description":"Capability for the desktop","context":"local","windows":["main","settings","event-*","user-*","column-*"],"permissions":["path:default","event:default","window:default","app:default","resources:default","menu:default","tray:default","theme:allow-set-theme","theme:allow-get-theme","notification:allow-is-permission-granted","notification:allow-request-permission","notification:default","os:allow-locale","os:allow-platform",{"identifier":"http:default","allow":[{"url":"http://**/"},{"url":"https://**/"}]},{"identifier":"fs:allow-read-text-file","allow":[{"path":"$RESOURCE/locales/*"}]}],"platforms":["linux","macOS","windows"]}} \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 13e8859e..d237a6b1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -6,8 +6,16 @@ pub mod commands; pub mod nostr; +use age::secrecy::ExposeSecret; use keyring::Entry; use nostr_sdk::prelude::*; +use std::error::Error; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::iter; +use std::path::Path; +use std::path::PathBuf; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tauri::Manager; @@ -25,6 +33,39 @@ fn main() { let handle = app.handle().clone(); let config_dir = handle.path().app_config_dir().unwrap(); + let keyring_entry = Entry::new("Lume Secret Storage", "AppKey").unwrap(); + let mut stored_nsec_key = None; + + if let Ok(key) = keyring_entry.get_password() { + let app_key = age::x25519::Identity::from_str(&key.to_string()).unwrap(); + if let Ok(nsec_paths) = get_nsec_paths(config_dir.as_path()) { + let last_nsec_path = nsec_paths.last(); + if let Some(nsec_path) = last_nsec_path { + let file = File::open(nsec_path).expect("Open nsec file failed"); + let file_buf = BufReader::new(file); + let decryptor = match age::Decryptor::new_buffered(file_buf).expect("Decryptor failed") + { + age::Decryptor::Recipients(d) => d, + _ => unreachable!(), + }; + + let mut decrypted = vec![]; + let mut reader = decryptor + .decrypt(iter::once(&app_key as &dyn age::Identity)) + .expect("Decrypt nsec file failed"); + reader + .read_to_end(&mut decrypted) + .expect("Read secret key failed"); + + stored_nsec_key = Some(String::from_utf8(decrypted).expect("Not valid")) + } + } + } else { + let app_key = age::x25519::Identity::generate().to_string(); + let app_secret = app_key.expose_secret(); + let _ = keyring_entry.set_password(app_secret); + } + tauri::async_runtime::spawn(async move { // Create nostr database connection let nostr_db = SQLiteDatabase::open(config_dir.join("nostr.db")) @@ -48,13 +89,12 @@ fn main() { // Connect client.connect().await; - // Get stored account - let entry = Entry::new("Lume", "Account").unwrap(); + // Prepare contact list let mut contact_list = None; // Run somethings if account existed - if let Ok(key) = entry.get_password() { - let secret_key = SecretKey::from_bech32(key).unwrap(); + if let Some(key) = stored_nsec_key { + let secret_key = SecretKey::from_bech32(key).expect("Get secret key failed"); let keys = Keys::new(secret_key); let signer = ClientSigner::Keys(keys); @@ -97,6 +137,7 @@ fn main() { )) .invoke_handler(tauri::generate_handler![ nostr::keys::create_keys, + nostr::keys::save_key, nostr::keys::get_public_key, nostr::keys::update_signer, nostr::keys::verify_signer, @@ -120,3 +161,19 @@ fn main() { .run(ctx) .expect("error while running tauri application"); } + +fn get_nsec_paths(dir: &Path) -> Result, Box> { + let paths = std::fs::read_dir(dir)? + .filter_map(|res| res.ok()) + .map(|dir_entry| dir_entry.path()) + .filter_map(|path| { + if path.extension().map_or(false, |ext| ext == "nsec") { + Some(path) + } else { + None + } + }) + .collect::>(); + + Ok(paths) +} diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs index c899f102..6d4a49cb 100644 --- a/src-tauri/src/nostr/keys.rs +++ b/src-tauri/src/nostr/keys.rs @@ -1,7 +1,8 @@ use crate::Nostr; +use keyring::Entry; use nostr_sdk::prelude::*; -use std::str::FromStr; -use tauri::State; +use std::{fs::File, io::Write, str::FromStr}; +use tauri::{Manager, State}; #[derive(serde::Serialize)] pub struct CreateKeysResponse { @@ -24,14 +25,46 @@ pub fn create_keys() -> Result { } #[tauri::command] -pub fn get_public_key(nsec: String) -> Result { +pub fn save_key(nsec: &str, app_handle: tauri::AppHandle) -> Result<(), ()> { + if let Ok(nostr_secret_key) = SecretKey::from_bech32(nsec) { + let nostr_keys = Keys::new(nostr_secret_key); + let nostr_npub = nostr_keys.public_key().to_bech32().unwrap(); + + let keyring_entry = Entry::new("Lume Secret Storage", "AppKey").unwrap(); + let secret_key = keyring_entry.get_password().unwrap(); + let app_key = age::x25519::Identity::from_str(&secret_key).unwrap(); + let app_pubkey = app_key.to_public(); + + let config_dir = app_handle.path().app_config_dir().unwrap(); + let encryptor = + age::Encryptor::with_recipients(vec![Box::new(app_pubkey)]).expect("we provided a recipient"); + + let file_ext = ".nsec".to_owned(); + let file_path = nostr_npub + &file_ext; + let mut file = File::create(config_dir.join(file_path)).unwrap(); + let mut writer = encryptor + .wrap_output(&mut file) + .expect("Init writer failed"); + writer + .write_all(nsec.as_bytes()) + .expect("Write nsec failed"); + writer.finish().expect("Save nsec failed"); + + Ok(()) + } else { + Err(()) + } +} + +#[tauri::command] +pub fn get_public_key(nsec: &str) -> Result { let secret_key = SecretKey::from_bech32(nsec).unwrap(); let keys = Keys::new(secret_key); Ok(keys.public_key().to_bech32().expect("secret key failed")) } #[tauri::command] -pub async fn update_signer(nsec: String, nostr: State<'_, Nostr>) -> Result<(), ()> { +pub async fn update_signer(nsec: &str, nostr: State<'_, Nostr>) -> Result<(), ()> { let client = &nostr.client; let secret_key = SecretKey::from_bech32(nsec).unwrap(); let keys = Keys::new(secret_key); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 41e9a065..29258ac0 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,113 +1,113 @@ { - "$schema": "../node_modules/@tauri-apps/cli/schema.json", - "productName": "Lume", - "version": "3.0.1", - "identifier": "nu.lume.Lume", - "build": { - "beforeBuildCommand": "pnpm run build", - "beforeDevCommand": "pnpm desktop:dev", - "devUrl": "http://localhost:3000", - "frontendDist": "../dist" - }, - "app": { - "macOSPrivateApi": true, - "withGlobalTauri": true, - "security": { - "assetProtocol": { - "enable": true, - "scope": [ - "$APPDATA/*", - "$DATA/*", - "$LOCALDATA/*", - "$DESKTOP/*", - "$DOCUMENT/*", - "$DOWNLOAD/*", - "$HOME/*", - "$PICTURE/*", - "$PUBLIC/*", - "$VIDEO/*", - "$APPCONFIG/*", - "$RESOURCE/*" - ] - } - }, - "trayIcon": { - "iconPath": "icons/tray.png" - } - }, - "bundle": { - "licenseFile": "../LICENSE", - "longDescription": "nostr client for desktop", - "shortDescription": "nostr client", - "targets": "all", - "active": true, - "category": "SocialNetworking", - "resources": ["resources/*", "./locales/*"], - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "linux": { - "appimage": { - "bundleMediaFramework": true, - "files": {} - }, - "deb": { - "files": {} - }, - "rpm": { - "epoch": 0, - "files": {}, - "release": "1" - } - }, - "macOS": { - "dmg": { - "appPosition": { - "x": 180, - "y": 170 - }, - "applicationFolderPosition": { - "x": 480, - "y": 170 - }, - "windowSize": { - "height": 400, - "width": 660 - } - }, - "files": {}, - "minimumSystemVersion": "10.15" - }, - "windows": { - "allowDowngrades": true, - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "nsis": null, - "timestampUrl": null, - "tsp": false, - "webviewFixedRuntimePath": null, - "webviewInstallMode": { - "silent": true, - "type": "downloadBootstrapper" - }, - "wix": null - } - }, - "plugins": { - "updater": { - "active": true, - "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK", - "windows": { - "installMode": "quiet" - }, - "endpoints": [ - "https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}", - "https://lus.reya3772.workers.dev/{{target}}/{{current_version}}" - ] - } - } + "$schema": "../node_modules/@tauri-apps/cli/schema.json", + "productName": "Lume", + "version": "3.0.1", + "identifier": "nu.lume.Lume", + "build": { + "beforeBuildCommand": "pnpm run build", + "beforeDevCommand": "pnpm desktop:dev", + "devUrl": "http://localhost:3000", + "frontendDist": "../dist" + }, + "app": { + "macOSPrivateApi": true, + "withGlobalTauri": true, + "security": { + "assetProtocol": { + "enable": true, + "scope": [ + "$APPDATA/*", + "$DATA/*", + "$LOCALDATA/*", + "$DESKTOP/*", + "$DOCUMENT/*", + "$DOWNLOAD/*", + "$HOME/*", + "$PICTURE/*", + "$PUBLIC/*", + "$VIDEO/*", + "$APPCONFIG/*", + "$RESOURCE/*" + ] + } + }, + "trayIcon": { + "iconPath": "icons/tray.png" + } + }, + "bundle": { + "licenseFile": "../LICENSE", + "longDescription": "nostr client for desktop", + "shortDescription": "nostr client", + "targets": "all", + "active": true, + "category": "SocialNetworking", + "resources": ["resources/*", "./locales/*"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "linux": { + "appimage": { + "bundleMediaFramework": true, + "files": {} + }, + "deb": { + "files": {} + }, + "rpm": { + "epoch": 0, + "files": {}, + "release": "1" + } + }, + "macOS": { + "dmg": { + "appPosition": { + "x": 180, + "y": 170 + }, + "applicationFolderPosition": { + "x": 480, + "y": 170 + }, + "windowSize": { + "height": 400, + "width": 660 + } + }, + "files": {}, + "minimumSystemVersion": "10.15" + }, + "windows": { + "allowDowngrades": true, + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "nsis": null, + "timestampUrl": null, + "tsp": false, + "webviewFixedRuntimePath": null, + "webviewInstallMode": { + "silent": true, + "type": "downloadBootstrapper" + }, + "wix": null + } + }, + "plugins": { + "updater": { + "active": true, + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK", + "windows": { + "installMode": "quiet" + }, + "endpoints": [ + "https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}", + "https://lus.reya3772.workers.dev/{{target}}/{{current_version}}" + ] + } + } }