feat: add new account management

This commit is contained in:
2024-02-08 17:17:45 +07:00
parent d7bbda6e7b
commit 17052aeeaa
35 changed files with 1140 additions and 1484 deletions

View File

@@ -1,5 +1,5 @@
{ {
"name": "lume", "name": "@lume/desktop",
"private": true, "private": true,
"version": "3.0.0", "version": "3.0.0",
"scripts": { "scripts": {

View File

@@ -1,10 +1,18 @@
import { ColumnProvider, LumeProvider } from "@lume/ark"; import { LoaderIcon } from "@lume/icons";
import { StorageProvider } from "@lume/storage"; import { StorageProvider } from "@lume/storage";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 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 { I18nextProvider } from "react-i18next";
import {
RouterProvider,
createBrowserRouter,
defer,
redirect,
} from "react-router-dom";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
import i18n from "./i18n"; import i18n from "./i18n";
import Router from "./router"; import { ErrorScreen } from "./routes/error";
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { 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: <ErrorScreen />,
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: <ErrorScreen />,
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() { export default function App() {
return ( return (
<I18nextProvider i18n={i18n} defaultNS={"translation"}> <I18nextProvider i18n={i18n} defaultNS={"translation"}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Toaster position="top-center" theme="system" closeButton /> <Toaster position="top-center" theme="system" closeButton />
<StorageProvider> <StorageProvider>
<LumeProvider> <RouterProvider
<ColumnProvider> router={router}
<Router /> fallbackElement={
</ColumnProvider> <div className="flex items-center justify-center w-full h-full">
</LumeProvider> <LoaderIcon className="w-6 h-6 animate-spin" />
</div>
}
future={{ v7_startTransition: true }}
/>
</StorageProvider> </StorageProvider>
</QueryClientProvider> </QueryClientProvider>
</I18nextProvider> </I18nextProvider>

View File

@@ -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: <AppLayout platform={storage.platform} />,
children: [
{
path: "/",
element: <HomeLayout />,
errorElement: <ErrorScreen />,
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: <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,
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: <AuthLayout platform={storage.platform} />,
errorElement: <ErrorScreen />,
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 (
<RouterProvider
router={router}
fallbackElement={
<div className="flex items-center justify-center w-full h-full">
<LoaderIcon className="w-6 h-6 animate-spin" />
</div>
}
future={{ v7_startTransition: true }}
/>
);
}

View File

@@ -1,23 +1,19 @@
import { useArk } from "@lume/ark";
import { CheckIcon, EyeOffIcon, EyeOnIcon, LoaderIcon } from "@lume/icons"; import { CheckIcon, EyeOffIcon, EyeOnIcon, LoaderIcon } from "@lume/icons";
import { useStorage } from "@lume/storage"; import { Keys } from "@lume/types";
import { onboardingAtom } from "@lume/utils"; import { onboardingAtom } from "@lume/utils";
import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import * as Checkbox from "@radix-ui/react-checkbox"; import * as Checkbox from "@radix-ui/react-checkbox";
import { invoke } from "@tauri-apps/api/core";
import { desktopDir } from "@tauri-apps/api/path"; import { desktopDir } from "@tauri-apps/api/path";
import { save } from "@tauri-apps/plugin-dialog"; import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs"; import { writeTextFile } from "@tauri-apps/plugin-fs";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { getPublicKey, nip19 } from "nostr-tools";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { toast } from "sonner"; import { toast } from "sonner";
export function CreateAccountKeys() { export function CreateAccountKeys() {
const ark = useArk();
const storage = useStorage();
const setOnboarding = useSetAtom(onboardingAtom); const setOnboarding = useSetAtom(onboardingAtom);
const navigate = useNavigate(); const navigate = useNavigate();
@@ -31,36 +27,14 @@ export function CreateAccountKeys() {
try { try {
setLoading(true); setLoading(true);
const privkey = nip19.decode(key).data as string; // trigger save key
const signer = new NDKPrivateKeySigner(privkey); await invoke("save_key", { nsec: key });
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;
// update state
setLoading(false); setLoading(false);
setOnboarding({ open: true, newUser: true }); setOnboarding({ open: true, newUser: true });
// redirect to next step
return navigate("/auth/onboarding", { replace: true }); return navigate("/auth/onboarding", { replace: true });
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
@@ -69,8 +43,11 @@ export function CreateAccountKeys() {
}; };
useEffect(() => { useEffect(() => {
const privkey = NDKPrivateKeySigner.generate().privateKey; async function createAccountKeys() {
setKey(nip19.nsecEncode(privkey)); const keys: Keys = await invoke("create_keys");
setKey(keys.nsec);
}
createAccountKeys();
}, []); }, []);
return ( return (

View File

@@ -1,8 +1,5 @@
import { useArk } from "@lume/ark";
import { useStorage } from "@lume/storage";
import { downloadDir } from "@tauri-apps/api/path"; import { downloadDir } from "@tauri-apps/api/path";
import { message, save } from "@tauri-apps/plugin-dialog"; import { message, save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
import { relaunch } from "@tauri-apps/plugin-process"; import { relaunch } from "@tauri-apps/plugin-process";
import { useRouteError } from "react-router-dom"; import { useRouteError } from "react-router-dom";
@@ -12,8 +9,6 @@ interface RouteError {
} }
export function ErrorScreen() { export function ErrorScreen() {
const ark = useArk();
const storage = useStorage();
const error = useRouteError() as RouteError; const error = useRouteError() as RouteError;
const restart = async () => { const restart = async () => {
@@ -27,6 +22,7 @@ export function ErrorScreen() {
const filePath = await save({ const filePath = await save({
defaultPath: `${downloadPath}/${fileName}`, defaultPath: `${downloadPath}/${fileName}`,
}); });
/*
const nsec = await storage.loadPrivkey(ark.account.pubkey); const nsec = await storage.loadPrivkey(ark.account.pubkey);
if (filePath) { if (filePath) {
@@ -42,6 +38,7 @@ export function ErrorScreen() {
); );
} }
} // else { user cancel action } } // else { user cancel action }
*/
} catch (e) { } catch (e) {
await message(e, { await message(e, {
title: "Cannot download account keys", title: "Cannot download account keys",

View File

@@ -1,17 +1,10 @@
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react-swc";
import million from "million/compiler";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import topLevelAwait from "vite-plugin-top-level-await"; import topLevelAwait from "vite-plugin-top-level-await";
import viteTsconfigPaths from "vite-tsconfig-paths"; import viteTsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
million.vite({
auto: {
threshold: 0.05,
},
mute: true,
}),
react(), react(),
viteTsconfigPaths(), viteTsconfigPaths(),
topLevelAwait({ topLevelAwait({

View File

@@ -6,6 +6,7 @@
"dependencies": { "dependencies": {
"@getalby/sdk": "^3.2.3", "@getalby/sdk": "^3.2.3",
"@lume/icons": "workspace:^", "@lume/icons": "workspace:^",
"@lume/storage": "workspace:^",
"@lume/utils": "workspace:^", "@lume/utils": "workspace:^",
"@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",

View File

@@ -4,8 +4,8 @@ import { invoke } from "@tauri-apps/api/core";
export class Ark { export class Ark {
public account: CurrentAccount; public account: CurrentAccount;
constructor(account: CurrentAccount) { constructor() {
this.account = account; this.account = null;
} }
public async event_to_bech32(id: string, relays: string[]) { 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 { try {
const cmd: Metadata = await invoke("get_metadata", { id }); const cmd: Metadata = await invoke("get_profile", { id });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error("failed to get metadata", id); console.error("failed to get profile", id);
} }
} }

View File

@@ -3,7 +3,6 @@ import { cn, editorAtom, editorValueAtom } from "@lume/utils";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import * as Tooltip from "@radix-ui/react-tooltip"; import * as Tooltip from "@radix-ui/react-tooltip";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { nip19 } from "nostr-tools";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";

View File

@@ -31,7 +31,7 @@ export function UserAbout({ className }: { className?: string }) {
return ( return (
<div className={cn("select-text break-p", className)}> <div className={cn("select-text break-p", className)}>
{user.about?.trim() || user.bio?.trim() || "No bio"} {user.profile.about?.trim() || "No bio"}
</div> </div>
); );
} }

View File

@@ -46,7 +46,7 @@ export function UserAvatar({ className }: { className?: string }) {
/> />
) : ( ) : (
<Avatar.Image <Avatar.Image
src={user.image} src={user.profile.picture}
alt={user.pubkey} alt={user.pubkey}
loading="eager" loading="eager"
decoding="async" decoding="async"

View File

@@ -15,7 +15,7 @@ export function UserCover({ className }: { className?: string }) {
); );
} }
if (user && !user.banner) { if (user && !user.profile.banner) {
return ( return (
<div <div
className={cn("bg-gradient-to-b from-sky-400 to-sky-200", className)} className={cn("bg-gradient-to-b from-sky-400 to-sky-200", className)}
@@ -25,7 +25,7 @@ export function UserCover({ className }: { className?: string }) {
return ( return (
<img <img
src={user.banner} src={user.profile.banner}
alt="banner" alt="banner"
loading="lazy" loading="lazy"
decoding="async" decoding="async"

View File

@@ -17,7 +17,7 @@ export function UserName({ className }: { className?: string }) {
return ( return (
<div className={cn("max-w-[12rem] truncate", className)}> <div className={cn("max-w-[12rem] truncate", className)}>
{user.displayName || user.name || "Anon"} {user.profile.display_name || user.profile.name || "Anon"}
</div> </div>
); );
} }

View File

@@ -1,26 +1,17 @@
import { VerifiedIcon } from "@lume/icons"; import { VerifiedIcon } from "@lume/icons";
import { cn, displayNpub } from "@lume/utils"; import { cn, displayNpub } from "@lume/utils";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useArk } from "../../hooks/useArk";
import { useUserContext } from "./provider"; import { useUserContext } from "./provider";
export function UserNip05({ export function UserNip05({ className }: { className?: string }) {
pubkey,
className,
}: { pubkey: string; className?: string }) {
const ark = useArk();
const user = useUserContext(); const user = useUserContext();
const { isLoading, data: verified } = useQuery({ const { isLoading, data: verified } = useQuery({
queryKey: ["nip05", user?.nip05], queryKey: ["nip05", user?.profile.nip05],
queryFn: async ({ signal }: { signal: AbortSignal }) => { queryFn: async ({ signal }: { signal: AbortSignal }) => {
if (!user) return false; if (!user) return false;
if (!user.nip05) return false; if (!user.profile.nip05) return false;
return ark.validateNIP05({ return false;
pubkey,
nip05: user.nip05,
signal,
});
}, },
enabled: !!user, enabled: !!user,
}); });
@@ -39,11 +30,11 @@ export function UserNip05({
return ( return (
<div className="inline-flex items-center gap-1"> <div className="inline-flex items-center gap-1">
<p className={cn("text-sm", className)}> <p className={cn("text-sm", className)}>
{!user?.nip05 {!user?.profile.nip05
? displayNpub(pubkey, 16) ? displayNpub(user.pubkey, 16)
: user?.nip05?.startsWith("_@") : user?.profile.nip05?.startsWith("_@")
? user?.nip05?.replace("_@", "") ? user?.profile.nip05?.replace("_@", "")
: user?.nip05} : user?.profile.nip05}
</p> </p>
{!isLoading && verified ? ( {!isLoading && verified ? (
<VerifiedIcon className="size-4 text-teal-500" /> <VerifiedIcon className="size-4 text-teal-500" />

View File

@@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query";
import { ReactNode, createContext, useContext } from "react"; import { ReactNode, createContext, useContext } from "react";
import { useArk } from "../../hooks/useArk"; import { useArk } from "../../hooks/useArk";
const UserContext = createContext<Metadata>(null); const UserContext = createContext<{ pubkey: string; profile: Metadata }>(null);
export function UserProvider({ export function UserProvider({
pubkey, pubkey,
@@ -11,12 +11,12 @@ export function UserProvider({
embed, embed,
}: { pubkey: string; children: ReactNode; embed?: string }) { }: { pubkey: string; children: ReactNode; embed?: string }) {
const ark = useArk(); const ark = useArk();
const { data: user } = useQuery({ const { data: profile } = useQuery({
queryKey: ["user", pubkey], queryKey: ["user", pubkey],
queryFn: async () => { queryFn: async () => {
if (embed) return JSON.parse(embed) as Metadata; if (embed) return JSON.parse(embed) as Metadata;
const profile = await ark.get_metadata(pubkey); const profile = await ark.get_profile(pubkey);
if (!profile) if (!profile)
throw new Error( throw new Error(
@@ -32,7 +32,11 @@ export function UserProvider({
retry: 2, retry: 2,
}); });
return <UserContext.Provider value={user}>{children}</UserContext.Provider>; return (
<UserContext.Provider value={{ pubkey, profile }}>
{children}
</UserContext.Provider>
);
} }
export function useUserContext() { export function useUserContext() {

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { useArk } from "./useArk"; import { useArk } from "./useArk";
export function useProfile(pubkey: string) { export function useProfile(pubkey: string) {
@@ -10,7 +10,7 @@ export function useProfile(pubkey: string) {
} = useQuery({ } = useQuery({
queryKey: ["user", pubkey], queryKey: ["user", pubkey],
queryFn: async () => { queryFn: async () => {
const profile = await ark.get_metadata(pubkey); const profile = await ark.get_profile(pubkey);
if (!profile) if (!profile)
throw new Error( throw new Error(
`Cannot get metadata for ${pubkey}, will be retry after 10 seconds`, `Cannot get metadata for ${pubkey}, will be retry after 10 seconds`,

View File

@@ -1,6 +1,4 @@
import { NDKKind, NDKTag } from "@nostr-dev-kit/ndk";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { normalizeRelayUrl } from "nostr-fetch";
import { useArk } from "./useArk"; import { useArk } from "./useArk";
export function useRelaylist() { export function useRelaylist() {

View File

@@ -1,5 +1,4 @@
export * from "./ark"; export * from "./ark";
export * from "./context";
export * from "./provider"; export * from "./provider";
export * from "./hooks/useEvent"; export * from "./hooks/useEvent";
export * from "./hooks/useArk"; export * from "./hooks/useArk";

View File

@@ -1,18 +1,9 @@
import { PropsWithChildren, useEffect, useState } from "react"; import { PropsWithChildren, createContext, useMemo } from "react";
import { Ark } from "./ark"; import { Ark } from "./ark";
import { LumeContext } from "./context";
export const LumeProvider = ({ children }: PropsWithChildren<object>) => { export const ArkContext = createContext<Ark>(undefined);
const [ark, setArk] = useState<Ark>(undefined);
useEffect(() => { export const ArkProvider = ({ children }: PropsWithChildren<object>) => {
async function setupArk() { const ark = useMemo(() => new Ark(), []);
const _ark = new Ark(); return <ArkContext.Provider value={ark}>{children}</ArkContext.Provider>;
setArk(_ark);
}
if (!ark) setupArk();
}, []);
return <LumeContext.Provider value={ark}>{children}</LumeContext.Provider>;
}; };

View File

@@ -8,7 +8,7 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"nostr-tools": "~1.17.0", "@tauri-apps/plugin-store": "2.0.0-beta.0",
"react": "^18.2.0" "react": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,18 +1,15 @@
import { locale, platform } from "@tauri-apps/plugin-os"; 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 { PropsWithChildren, createContext, useContext } from "react";
import { LumeStorage } from "./storage"; import { LumeStorage } from "./storage";
const StorageContext = createContext<LumeStorage>(null); const StorageContext = createContext<LumeStorage>(null);
const sqliteAdapter = await Database.load("sqlite:lume_v3.db"); const store = new Store("lume.data");
const platformName = await platform(); const platformName = await platform();
const osLocale = await locale(); const osLocale = await locale();
const db = new LumeStorage(sqliteAdapter, platformName, osLocale); const db = new LumeStorage(store, platformName, osLocale);
await db.init();
if (db.settings.depot) await db.launchDepot();
export const StorageProvider = ({ children }: PropsWithChildren<object>) => { export const StorageProvider = ({ children }: PropsWithChildren<object>) => {
return ( return (

View File

@@ -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 { Platform } from "@tauri-apps/plugin-os";
import { Child, Command } from "@tauri-apps/plugin-shell"; import { Store } from "@tauri-apps/plugin-store";
import Database from "@tauri-apps/plugin-sql";
import { nip19 } from "nostr-tools";
export class LumeStorage { export class LumeStorage {
#db: Database; #store: Store;
#depot: Child;
readonly platform: Platform; readonly platform: Platform;
readonly locale: string; readonly locale: string;
public currentUser: Account;
public interests: Interests;
public nwc: string;
public settings: { public settings: {
autoupdate: boolean; autoupdate: boolean;
nsecbunker: boolean; nsecbunker: boolean;
media: boolean; media: boolean;
hashtag: boolean; hashtag: boolean;
depot: boolean;
tunnelUrl: string;
lowPower: boolean; lowPower: boolean;
translation: boolean; translation: boolean;
translateApiKey: string; translateApiKey: string;
instantZap: boolean; instantZap: boolean;
defaultZapAmount: number;
}; };
constructor(db: Database, platform: Platform, locale: string) { constructor(store: Store, platform: Platform, locale: string) {
this.#db = db; this.#store = store;
this.locale = locale; this.locale = locale;
this.platform = platform; this.platform = platform;
this.interests = null;
this.nwc = null;
this.settings = { this.settings = {
autoupdate: false, autoupdate: false,
nsecbunker: false, nsecbunker: false,
media: true, media: true,
hashtag: true, hashtag: true,
depot: false,
tunnelUrl: "",
lowPower: false, lowPower: false,
translation: false, translation: false,
translateApiKey: "", translateApiKey: "",
instantZap: false, instantZap: false,
defaultZapAmount: 21,
}; };
} }
public async init() { public async createSetting(key: string, value: string | boolean) {
const settings = await this.getAllSettings(); this.settings[key] = value;
const account = await this.getActiveAccount(); await this.#store.set(this.settings[key], { value });
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<NDKCacheUser> = 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<NDKCacheEvent> = 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<NDKCacheEvent> = 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<NDKCacheEvent> = 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<NDKCacheEvent> = 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<NDKCacheEvent> = 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<NDKCacheEventTag> = 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<NDKCacheUser>) {
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<NDKCacheUser> = 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<Account> = 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<Account> = 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<IColumn> = 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<IColumn> = 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;
} }
} }

View File

@@ -1,6 +1,7 @@
{ {
"name": "@lume/types", "name": "@lume/types",
"version": "0.0.0", "version": "0.0.0",
"main": "./index.d.ts",
"types": "./index.d.ts", "types": "./index.d.ts",
"private": true, "private": true,
"license": "MIT", "license": "MIT",

View File

@@ -1,24 +1,21 @@
import { useStorage } from "@lume/storage";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { type Platform } from "@tauri-apps/plugin-os";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import { Editor } from "../editor/column"; import { Editor } from "../editor/column";
import { Navigation } from "../navigation"; import { Navigation } from "../navigation";
import { SearchDialog } from "../search/dialog"; import { SearchDialog } from "../search/dialog";
import { WindowTitleBar } from "../titlebar";
export function AppLayout({ platform }: { platform: Platform }) { export function AppLayout() {
const storage = useStorage();
return ( return (
<div <div
className={cn( className={cn(
"flex h-screen w-screen flex-col", "flex h-screen w-screen flex-col",
platform !== "macos" ? "bg-neutral-50 dark:bg-neutral-950" : "", storage.platform !== "macos" ? "bg-neutral-50 dark:bg-neutral-950" : "",
)} )}
> >
{platform === "windows" ? ( <div data-tauri-drag-region className="h-9 shrink-0" />
<WindowTitleBar platform={platform} />
) : (
<div data-tauri-drag-region className="h-9 shrink-0" />
)}
<div className="flex w-full h-full min-h-0"> <div className="flex w-full h-full min-h-0">
<Navigation /> <Navigation />
<Editor /> <Editor />

View File

@@ -1,9 +1,7 @@
import { ArrowLeftIcon, SettingsIcon } from "@lume/icons"; import { ArrowLeftIcon } from "@lume/icons";
import { type Platform } from "@tauri-apps/plugin-os";
import { Outlet, useLocation, useNavigate } from "react-router-dom"; 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 location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -11,11 +9,7 @@ export function AuthLayout({ platform }: { platform: Platform }) {
return ( return (
<div className="flex flex-col w-screen h-screen bg-black text-neutral-50"> <div className="flex flex-col w-screen h-screen bg-black text-neutral-50">
{platform === "windows" ? ( <div data-tauri-drag-region className="h-9 shrink-0" />
<WindowTitleBar platform={platform} />
) : (
<div data-tauri-drag-region className="h-9 shrink-0" />
)}
<div className="relative w-full h-full"> <div className="relative w-full h-full">
<div className="absolute top-8 z-10 flex items-center justify-between w-full px-9"> <div className="absolute top-8 z-10 flex items-center justify-between w-full px-9">
{canGoBack ? ( {canGoBack ? (

495
pnpm-lock.yaml generated
View File

@@ -345,30 +345,33 @@ importers:
'@lume/icons': '@lume/icons':
specifier: workspace:^ specifier: workspace:^
version: link:../icons version: link:../icons
'@lume/storage':
specifier: workspace:^
version: link:../storage
'@lume/utils': '@lume/utils':
specifier: workspace:^ specifier: workspace:^
version: link:../utils version: link:../utils
'@radix-ui/react-avatar': '@radix-ui/react-avatar':
specifier: ^1.0.4 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': '@radix-ui/react-collapsible':
specifier: ^1.0.3 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': '@radix-ui/react-dialog':
specifier: ^1.0.5 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': '@radix-ui/react-dropdown-menu':
specifier: ^2.0.6 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': '@radix-ui/react-hover-card':
specifier: ^1.0.7 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': '@radix-ui/react-popover':
specifier: ^1.0.7 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': '@radix-ui/react-tooltip':
specifier: ^1.0.7 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': '@tanstack/react-query':
specifier: ^5.18.1 specifier: ^5.18.1
version: 5.18.1(react@18.2.0) version: 5.18.1(react@18.2.0)
@@ -1052,9 +1055,9 @@ importers:
packages/storage: packages/storage:
dependencies: dependencies:
nostr-tools: '@tauri-apps/plugin-store':
specifier: ~1.17.0 specifier: 2.0.0-beta.0
version: 1.17.0(typescript@5.3.3) version: 2.0.0-beta.0
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
@@ -2569,26 +2572,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw==}
peerDependencies: peerDependencies:
@@ -2613,29 +2596,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==}
peerDependencies: peerDependencies:
@@ -2692,33 +2652,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
peerDependencies: peerDependencies:
@@ -2743,29 +2676,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.52)(react@18.2.0):
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
peerDependencies: peerDependencies:
@@ -2828,39 +2738,6 @@ packages:
react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0)
dev: false 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): /@radix-ui/react-direction@1.0.1(@types/react@18.2.52)(react@18.2.0):
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
peerDependencies: peerDependencies:
@@ -2900,30 +2777,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==}
peerDependencies: peerDependencies:
@@ -2951,32 +2804,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.52)(react@18.2.0):
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
peerDependencies: peerDependencies:
@@ -3014,28 +2841,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==}
peerDependencies: peerDependencies:
@@ -3065,34 +2870,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@radix-ui/react-id@1.0.1(@types/react@18.2.52)(react@18.2.0):
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
peerDependencies: peerDependencies:
@@ -3146,43 +2923,6 @@ packages:
react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==}
peerDependencies: peerDependencies:
@@ -3218,40 +2958,6 @@ packages:
react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0) react-remove-scroll: 2.5.5(@types/react@18.2.52)(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==}
peerDependencies: peerDependencies:
@@ -3282,35 +2988,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==}
peerDependencies: peerDependencies:
@@ -3332,26 +3009,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==}
peerDependencies: peerDependencies:
@@ -3374,27 +3031,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
peerDependencies: peerDependencies:
@@ -3416,26 +3052,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
peerDependencies: peerDependencies:
@@ -3465,34 +3081,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@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==} resolution: {integrity: sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==}
peerDependencies: peerDependencies:
@@ -3608,37 +3196,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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): /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.52)(react@18.2.0):
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
peerDependencies: peerDependencies:
@@ -3762,26 +3319,6 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false 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: /@radix-ui/rect@1.0.1:
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
dependencies: dependencies:
@@ -4424,6 +3961,12 @@ packages:
'@tauri-apps/api': 2.0.0-beta.0 '@tauri-apps/api': 2.0.0-beta.0
dev: false 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: /@tauri-apps/plugin-updater@2.0.0-beta.0:
resolution: {integrity: sha512-TkKzngrgg8dQOr869OcObLdN10yXNiT/ERQp7sRYvV0vMpRJhYSIwTkpF+UkZGuEXtSqqE0FJEnb+4WuCelMdw==} resolution: {integrity: sha512-TkKzngrgg8dQOr869OcObLdN10yXNiT/ERQp7sRYvV0vMpRJhYSIwTkpF+UkZGuEXtSqqE0FJEnb+4WuCelMdw==}
dependencies: dependencies:

521
src-tauri/Cargo.lock generated
View File

@@ -17,6 +17,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 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]] [[package]]
name = "aes" name = "aes"
version = "0.7.5" version = "0.7.5"
@@ -40,6 +50,49 @@ dependencies = [
"cpufeatures", "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]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.7" version = "0.8.7"
@@ -170,6 +223,12 @@ dependencies = [
"x11rb 0.12.0", "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]] [[package]]
name = "as-raw-xcb-connection" name = "as-raw-xcb-connection"
version = "1.0.1" version = "1.0.1"
@@ -819,6 +878,19 @@ dependencies = [
"cpufeatures", "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]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.33" version = "0.4.33"
@@ -849,6 +921,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [ dependencies = [
"crypto-common", "crypto-common",
"inout", "inout",
"zeroize",
] ]
[[package]] [[package]]
@@ -962,6 +1035,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "cookie-factory"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@@ -1125,6 +1204,33 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "darling" name = "darling"
version = "0.20.5" version = "0.20.5"
@@ -1160,6 +1266,19 @@ dependencies = [
"syn 2.0.48", "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]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.5.0" version = "2.5.0"
@@ -1306,6 +1425,17 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 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]] [[package]]
name = "dlib" name = "dlib"
version = "0.5.2" version = "0.5.2"
@@ -1543,6 +1673,12 @@ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[package]]
name = "fiat-crypto"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382"
[[package]] [[package]]
name = "field-offset" name = "field-offset"
version = "0.3.6" version = "0.3.6"
@@ -1565,6 +1701,15 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "flatbuffers" name = "flatbuffers"
version = "23.5.26" version = "23.5.26"
@@ -1585,6 +1730,50 @@ dependencies = [
"miniz_oxide", "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]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@@ -2372,6 +2561,75 @@ dependencies = [
"tokio-native-tls", "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]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.60" version = "0.1.60"
@@ -2488,6 +2746,25 @@ dependencies = [
"web-sys", "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]] [[package]]
name = "io-lifetimes" name = "io-lifetimes"
version = "1.0.11" version = "1.0.11"
@@ -2499,6 +2776,12 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.9.0" version = "2.9.0"
@@ -2827,6 +3110,7 @@ dependencies = [
name = "lume" name = "lume"
version = "3.0.0" version = "3.0.0"
dependencies = [ dependencies = [
"age",
"keyring", "keyring",
"nostr-sdk", "nostr-sdk",
"serde", "serde",
@@ -2960,6 +3244,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "minisign-verify" name = "minisign-verify"
version = "0.2.1" version = "0.2.1"
@@ -3082,6 +3372,16 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" 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]] [[package]]
name = "nostr" name = "nostr"
version = "0.27.0" version = "0.27.0"
@@ -3537,6 +3837,16 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@@ -3687,6 +3997,26 @@ dependencies = [
"siphasher", "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]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.13" version = "0.2.13"
@@ -3716,6 +4046,12 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
[[package]]
name = "platforms"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
[[package]] [[package]]
name = "plist" name = "plist"
version = "1.6.0" version = "1.6.0"
@@ -3773,6 +4109,17 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@@ -4149,12 +4496,52 @@ dependencies = [
"smallvec", "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]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"
@@ -4240,6 +4627,15 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" 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]] [[package]]
name = "same-file" name = "same-file"
version = "0.1.3" version = "0.1.3"
@@ -4306,6 +4702,17 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 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]] [[package]]
name = "sct" name = "sct"
version = "0.7.1" version = "0.7.1"
@@ -4337,6 +4744,15 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "secrecy"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
dependencies = [
"zeroize",
]
[[package]] [[package]]
name = "secret-service" name = "secret-service"
version = "3.0.1" version = "3.0.1"
@@ -4399,6 +4815,21 @@ dependencies = [
"thin-slice", "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]] [[package]]
name = "semver" name = "semver"
version = "1.0.21" version = "1.0.21"
@@ -5537,6 +5968,15 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tinystr"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece"
dependencies = [
"displaydoc",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@@ -5643,6 +6083,15 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.7.8" version = "0.7.8"
@@ -5824,6 +6273,15 @@ dependencies = [
"utf-8", "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]] [[package]]
name = "typenum" name = "typenum"
version = "1.17.0" version = "1.17.0"
@@ -5841,6 +6299,25 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.15" version = "0.3.15"
@@ -5868,6 +6345,16 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 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]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
@@ -6762,6 +7249,18 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" 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]] [[package]]
name = "xattr" name = "xattr"
version = "1.3.1" version = "1.3.1"
@@ -6880,6 +7379,26 @@ dependencies = [
"syn 2.0.48", "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]] [[package]]
name = "zip" name = "zip"
version = "0.6.6" version = "0.6.6"
@@ -6894,7 +7413,7 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
"flate2", "flate2",
"hmac", "hmac",
"pbkdf2", "pbkdf2 0.11.0",
"sha1", "sha1",
"time", "time",
"zstd", "zstd",

View File

@@ -38,6 +38,7 @@ tauri-plugin-window-state = "2.0.0-beta"
tauri-plugin-theme = { git = "https://github.com/wyhaya/tauri-plugin-theme" } tauri-plugin-theme = { git = "https://github.com/wyhaya/tauri-plugin-theme" }
webpage = { version = "2.0", features = ["serde"] } webpage = { version = "2.0", features = ["serde"] }
keyring = "2" keyring = "2"
age = "0.10.0"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode

View File

@@ -1,41 +1,42 @@
{ {
"$schema": "../gen/schemas/desktop-schema.json", "$schema": "../gen/schemas/desktop-schema.json",
"identifier": "desktop-capability", "identifier": "desktop-capability",
"description": "Capability for the desktop", "description": "Capability for the desktop",
"platforms": ["linux", "macOS", "windows"], "platforms": ["linux", "macOS", "windows"],
"windows": ["main", "settings", "event-*", "user-*", "column-*"], "windows": ["main", "settings", "event-*", "user-*", "column-*"],
"permissions": [ "permissions": [
"path:default", "path:default",
"event:default", "event:default",
"window:default", "window:default",
"app:default", "app:default",
"resources:default", "resources:default",
"menu:default", "menu:default",
"tray:default", "tray:default",
"theme:allow-set-theme", "theme:allow-set-theme",
"theme:allow-get-theme", "theme:allow-get-theme",
"notification:allow-is-permission-granted", "notification:allow-is-permission-granted",
"notification:allow-request-permission", "notification:allow-request-permission",
"notification:default", "notification:default",
"os:allow-locale", "os:allow-locale",
{ "os:allow-platform",
"identifier": "http:default", {
"allow": [ "identifier": "http:default",
{ "allow": [
"url": "http://**/" {
}, "url": "http://**/"
{ },
"url": "https://**/" {
} "url": "https://**/"
] }
}, ]
{ },
"identifier": "fs:allow-read-text-file", {
"allow": [ "identifier": "fs:allow-read-text-file",
{ "allow": [
"path": "$RESOURCE/locales/*" {
} "path": "$RESOURCE/locales/*"
] }
} ]
] }
]
} }

View File

@@ -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"]}} {"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"]}}

View File

@@ -6,8 +6,16 @@
pub mod commands; pub mod commands;
pub mod nostr; pub mod nostr;
use age::secrecy::ExposeSecret;
use keyring::Entry; use keyring::Entry;
use nostr_sdk::prelude::*; 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::sync::Arc;
use std::time::Duration; use std::time::Duration;
use tauri::Manager; use tauri::Manager;
@@ -25,6 +33,39 @@ fn main() {
let handle = app.handle().clone(); let handle = app.handle().clone();
let config_dir = handle.path().app_config_dir().unwrap(); 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 { tauri::async_runtime::spawn(async move {
// Create nostr database connection // Create nostr database connection
let nostr_db = SQLiteDatabase::open(config_dir.join("nostr.db")) let nostr_db = SQLiteDatabase::open(config_dir.join("nostr.db"))
@@ -48,13 +89,12 @@ fn main() {
// Connect // Connect
client.connect().await; client.connect().await;
// Get stored account // Prepare contact list
let entry = Entry::new("Lume", "Account").unwrap();
let mut contact_list = None; let mut contact_list = None;
// Run somethings if account existed // Run somethings if account existed
if let Ok(key) = entry.get_password() { if let Some(key) = stored_nsec_key {
let secret_key = SecretKey::from_bech32(key).unwrap(); let secret_key = SecretKey::from_bech32(key).expect("Get secret key failed");
let keys = Keys::new(secret_key); let keys = Keys::new(secret_key);
let signer = ClientSigner::Keys(keys); let signer = ClientSigner::Keys(keys);
@@ -97,6 +137,7 @@ fn main() {
)) ))
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
nostr::keys::create_keys, nostr::keys::create_keys,
nostr::keys::save_key,
nostr::keys::get_public_key, nostr::keys::get_public_key,
nostr::keys::update_signer, nostr::keys::update_signer,
nostr::keys::verify_signer, nostr::keys::verify_signer,
@@ -120,3 +161,19 @@ fn main() {
.run(ctx) .run(ctx)
.expect("error while running tauri application"); .expect("error while running tauri application");
} }
fn get_nsec_paths(dir: &Path) -> Result<Vec<PathBuf>, Box<dyn Error>> {
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::<Vec<_>>();
Ok(paths)
}

View File

@@ -1,7 +1,8 @@
use crate::Nostr; use crate::Nostr;
use keyring::Entry;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use std::str::FromStr; use std::{fs::File, io::Write, str::FromStr};
use tauri::State; use tauri::{Manager, State};
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
pub struct CreateKeysResponse { pub struct CreateKeysResponse {
@@ -24,14 +25,46 @@ pub fn create_keys() -> Result<CreateKeysResponse, ()> {
} }
#[tauri::command] #[tauri::command]
pub fn get_public_key(nsec: String) -> Result<String, ()> { 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<String, ()> {
let secret_key = SecretKey::from_bech32(nsec).unwrap(); let secret_key = SecretKey::from_bech32(nsec).unwrap();
let keys = Keys::new(secret_key); let keys = Keys::new(secret_key);
Ok(keys.public_key().to_bech32().expect("secret key failed")) Ok(keys.public_key().to_bech32().expect("secret key failed"))
} }
#[tauri::command] #[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 client = &nostr.client;
let secret_key = SecretKey::from_bech32(nsec).unwrap(); let secret_key = SecretKey::from_bech32(nsec).unwrap();
let keys = Keys::new(secret_key); let keys = Keys::new(secret_key);

View File

@@ -1,113 +1,113 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "../node_modules/@tauri-apps/cli/schema.json",
"productName": "Lume", "productName": "Lume",
"version": "3.0.1", "version": "3.0.1",
"identifier": "nu.lume.Lume", "identifier": "nu.lume.Lume",
"build": { "build": {
"beforeBuildCommand": "pnpm run build", "beforeBuildCommand": "pnpm run build",
"beforeDevCommand": "pnpm desktop:dev", "beforeDevCommand": "pnpm desktop:dev",
"devUrl": "http://localhost:3000", "devUrl": "http://localhost:3000",
"frontendDist": "../dist" "frontendDist": "../dist"
}, },
"app": { "app": {
"macOSPrivateApi": true, "macOSPrivateApi": true,
"withGlobalTauri": true, "withGlobalTauri": true,
"security": { "security": {
"assetProtocol": { "assetProtocol": {
"enable": true, "enable": true,
"scope": [ "scope": [
"$APPDATA/*", "$APPDATA/*",
"$DATA/*", "$DATA/*",
"$LOCALDATA/*", "$LOCALDATA/*",
"$DESKTOP/*", "$DESKTOP/*",
"$DOCUMENT/*", "$DOCUMENT/*",
"$DOWNLOAD/*", "$DOWNLOAD/*",
"$HOME/*", "$HOME/*",
"$PICTURE/*", "$PICTURE/*",
"$PUBLIC/*", "$PUBLIC/*",
"$VIDEO/*", "$VIDEO/*",
"$APPCONFIG/*", "$APPCONFIG/*",
"$RESOURCE/*" "$RESOURCE/*"
] ]
} }
}, },
"trayIcon": { "trayIcon": {
"iconPath": "icons/tray.png" "iconPath": "icons/tray.png"
} }
}, },
"bundle": { "bundle": {
"licenseFile": "../LICENSE", "licenseFile": "../LICENSE",
"longDescription": "nostr client for desktop", "longDescription": "nostr client for desktop",
"shortDescription": "nostr client", "shortDescription": "nostr client",
"targets": "all", "targets": "all",
"active": true, "active": true,
"category": "SocialNetworking", "category": "SocialNetworking",
"resources": ["resources/*", "./locales/*"], "resources": ["resources/*", "./locales/*"],
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
"icons/128x128@2x.png", "icons/128x128@2x.png",
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"linux": { "linux": {
"appimage": { "appimage": {
"bundleMediaFramework": true, "bundleMediaFramework": true,
"files": {} "files": {}
}, },
"deb": { "deb": {
"files": {} "files": {}
}, },
"rpm": { "rpm": {
"epoch": 0, "epoch": 0,
"files": {}, "files": {},
"release": "1" "release": "1"
} }
}, },
"macOS": { "macOS": {
"dmg": { "dmg": {
"appPosition": { "appPosition": {
"x": 180, "x": 180,
"y": 170 "y": 170
}, },
"applicationFolderPosition": { "applicationFolderPosition": {
"x": 480, "x": 480,
"y": 170 "y": 170
}, },
"windowSize": { "windowSize": {
"height": 400, "height": 400,
"width": 660 "width": 660
} }
}, },
"files": {}, "files": {},
"minimumSystemVersion": "10.15" "minimumSystemVersion": "10.15"
}, },
"windows": { "windows": {
"allowDowngrades": true, "allowDowngrades": true,
"certificateThumbprint": null, "certificateThumbprint": null,
"digestAlgorithm": "sha256", "digestAlgorithm": "sha256",
"nsis": null, "nsis": null,
"timestampUrl": null, "timestampUrl": null,
"tsp": false, "tsp": false,
"webviewFixedRuntimePath": null, "webviewFixedRuntimePath": null,
"webviewInstallMode": { "webviewInstallMode": {
"silent": true, "silent": true,
"type": "downloadBootstrapper" "type": "downloadBootstrapper"
}, },
"wix": null "wix": null
} }
}, },
"plugins": { "plugins": {
"updater": { "updater": {
"active": true, "active": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK", "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEU3OTdCMkM3RjU5QzE2NzkKUldSNUZwejF4N0tYNTVHYjMrU0JkL090SlEyNUVLYU5TM2hTU3RXSWtEWngrZWJ4a0pydUhXZHEK",
"windows": { "windows": {
"installMode": "quiet" "installMode": "quiet"
}, },
"endpoints": [ "endpoints": [
"https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}", "https://lus.reya3772.workers.dev/v1/{{target}}/{{arch}}/{{current_version}}",
"https://lus.reya3772.workers.dev/{{target}}/{{current_version}}" "https://lus.reya3772.workers.dev/{{target}}/{{current_version}}"
] ]
} }
} }
} }