Merge pull request #169 from lumehq/v4/onboarding

Add basic onboarding dialog to v4
This commit is contained in:
Ren Amamiya
2024-03-06 15:00:07 +07:00
committed by GitHub
20 changed files with 447 additions and 717 deletions

View File

@@ -15,6 +15,7 @@
"@lume/utils": "workspace:^", "@lume/utils": "workspace:^",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@tanstack/query-sync-storage-persister": "^5.24.1", "@tanstack/query-sync-storage-persister": "^5.24.1",

View File

@@ -4,9 +4,9 @@ import { User } from "@lume/ui";
import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { useNavigate, useParams, useSearch } from "@tanstack/react-router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import * as Popover from "@radix-ui/react-popover"; import * as Popover from "@radix-ui/react-popover";
import { Link } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { BackupDialog } from "./backup";
import { LoginDialog } from "./login";
export function Accounts() { export function Accounts() {
const ark = useArk(); const ark = useArk();
@@ -63,7 +63,6 @@ function Active({ pubkey }: { pubkey: string }) {
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
// @ts-ignore, magic !!! // @ts-ignore, magic !!!
const { guest } = useSearch({ strict: false }); const { guest } = useSearch({ strict: false });
const { t } = useTranslation();
if (guest) { if (guest) {
return ( return (
@@ -84,25 +83,17 @@ function Active({ pubkey }: { pubkey: string }) {
side="bottom" side="bottom"
> >
<div> <div>
<h1 className="mb-1 font-semibold">You're using guest account</h1> <h1 className="mb-1 font-semibold">
You're using random account
</h1>
<p className="text-sm text-neutral-500 dark:text-neutral-600"> <p className="text-sm text-neutral-500 dark:text-neutral-600">
You can continue by claim and backup this account, or you can You can continue by claim and backup this account, or you can
import your own account key. import your own account.
</p> </p>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Link <BackupDialog />
to="/backup" <LoginDialog />
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-white text-sm font-medium leading-tight text-neutral-900 hover:bg-neutral-100"
>
Claim & Backup
</Link>
<Link
to="/login"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
>
{t("welcome.login")}
</Link>
</div> </div>
<Popover.Arrow className="fill-black dark:fill-white" /> <Popover.Arrow className="fill-black dark:fill-white" />
</Popover.Content> </Popover.Content>

View File

@@ -0,0 +1,121 @@
import { ArrowRightIcon, CancelIcon } from "@lume/icons";
import * as Dialog from "@radix-ui/react-dialog";
import { Link, useParams } from "@tanstack/react-router";
import { invoke } from "@tauri-apps/api/core";
import { useState } from "react";
import { toast } from "sonner";
export function BackupDialog() {
// @ts-ignore, magic!!!
const { account } = useParams({ strict: false });
const [key, setKey] = useState(null);
const [passphase, setPassphase] = useState("");
const [loading, setLoading] = useState(false);
const encryptKey = async () => {
try {
setLoading(true);
const encrypted: string = await invoke("get_encrypted_key", {
npub: account,
password: passphase,
});
if (encrypted) {
setKey(encrypted);
}
setLoading(false);
} catch (e) {
setLoading(false);
toast.error(String(e));
}
};
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button
type="button"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
>
Claim & Backup
</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/30 backdrop-blur dark:bg-white/30" />
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Dialog.Close className="absolute right-5 top-5 flex w-12 flex-col items-center justify-center gap-1 text-white">
<CancelIcon className="size-8" />
<span className="text-sm font-medium uppercase text-neutral-400 dark:text-neutral-600">
Esc
</span>
</Dialog.Close>
<div className="relative flex h-min w-full max-w-xl flex-col gap-8 rounded-xl bg-white p-5 dark:bg-black">
<div className="flex flex-col">
<h3 className="text-lg font-semibold">
This is your account key
</h3>
<p>
It's use for login to Lume or other Nostr clients. You will lost
access to your account if you lose this key.
</p>
</div>
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<label htmlFor="nsec">Set a passphase to secure your key</label>
<div className="relative">
<input
name="passphase"
type="password"
value={passphase}
onChange={(e) => setPassphase(e.target.value)}
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
</div>
</div>
{key ? (
<div className="flex flex-col gap-2">
<label htmlFor="nsec">
Copy this key and keep it in safe place
</label>
<input
name="nsec"
type="text"
value={key}
readOnly
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
</div>
) : null}
</div>
<div className="flex flex-col gap-3">
{!key ? (
<button
type="button"
onClick={encryptKey}
disabled={loading}
className="inline-flex h-11 w-full items-center justify-between gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
>
<div className="size-5" />
<div>Submit</div>
<ArrowRightIcon className="size-5" />
</button>
) : (
<Link
to="/$account/home"
params={{ account }}
search={{ guest: false }}
className="inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
>
I've safely store my account key
</Link>
)}
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@@ -0,0 +1,120 @@
import { useArk } from "@lume/ark";
import { ArrowRightIcon, CancelIcon } from "@lume/icons";
import * as Dialog from "@radix-ui/react-dialog";
import { useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { toast } from "sonner";
export function LoginDialog() {
const ark = useArk();
const navigate = useNavigate();
const [nsec, setNsec] = useState("");
const [passphase, setPassphase] = useState("");
const [loading, setLoading] = useState(false);
const login = async () => {
try {
setLoading(true);
const save = await ark.save_account(nsec, passphase);
if (save) {
navigate({ to: "/", search: { guest: false } });
}
} catch (e) {
setLoading(false);
toast.error(String(e));
}
};
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button
type="button"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
>
Add account
</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/30 backdrop-blur dark:bg-white/30" />
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Dialog.Close className="absolute right-5 top-5 flex w-12 flex-col items-center justify-center gap-1 text-white">
<CancelIcon className="size-8" />
<span className="text-sm font-medium uppercase text-neutral-400 dark:text-neutral-600">
Esc
</span>
</Dialog.Close>
<div className="relative flex h-min w-full max-w-xl flex-col gap-8 rounded-xl bg-white p-5 dark:bg-black">
<div className="flex flex-col gap-1.5">
<h3 className="text-lg font-semibold">Add new account with</h3>
<div className="flex h-11 items-center overflow-hidden rounded-lg bg-neutral-100 p-1 dark:bg-neutral-900">
<button
type="button"
className="h-full flex-1 rounded-md bg-white text-sm font-medium dark:bg-black"
>
nsec
</button>
<button
type="button"
className="flex h-full flex-1 flex-col items-center justify-center rounded-md text-sm font-medium"
>
<span className="leading-tight">nsecBunker</span>
<span className="text-xs font-normal leading-tight text-neutral-700 dark:text-neutral-300">
coming soon
</span>
</button>
<button
type="button"
className="flex h-full flex-1 flex-col items-center justify-center rounded-md text-sm font-medium"
>
<span className="leading-tight">Address</span>
<span className="text-xs font-normal leading-tight text-neutral-700 dark:text-neutral-300">
coming soon
</span>
</button>
</div>
</div>
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<label htmlFor="nsec">
Enter sign in key start with nsec or ncrypto
</label>
<input
name="nsec"
type="text"
value={nsec}
onChange={(e) => setNsec(e.target.value)}
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="nsec">Passphase (optional)</label>
<input
name="passphase"
type="password"
value={passphase}
onChange={(e) => setPassphase(e.target.value)}
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
</div>
</div>
<div className="flex flex-col gap-3">
<button
type="button"
onClick={login}
className="inline-flex h-11 w-full items-center justify-between gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
>
<div className="size-5" />
<div>Add account</div>
<ArrowRightIcon className="size-5" />
</button>
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@@ -5,6 +5,7 @@ import {
HomeFilledIcon, HomeFilledIcon,
HomeIcon, HomeIcon,
HorizontalDotsIcon, HorizontalDotsIcon,
SettingsIcon,
SpaceFilledIcon, SpaceFilledIcon,
SpaceIcon, SpaceIcon,
} from "@lume/icons"; } from "@lume/icons";
@@ -43,6 +44,12 @@ function App() {
<ComposeFilledIcon className="size-4" /> <ComposeFilledIcon className="size-4" />
New post New post
</button> </button>
<button
type="button"
className="inline-flex size-8 items-center justify-center rounded-full bg-neutral-200 text-neutral-800 hover:bg-neutral-400 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-600"
>
<HorizontalDotsIcon className="size-5" />
</button>
</div> </div>
</div> </div>
<Box> <Box>

View File

@@ -1,20 +0,0 @@
import { createLazyFileRoute } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
export const Route = createLazyFileRoute("/backup")({
component: Screen,
});
function Screen() {
const { t } = useTranslation();
return (
<div className="flex h-full w-full items-center justify-center">
<div className="mx-auto flex w-full max-w-md flex-col gap-8">
<div className="flex flex-col items-center gap-1 text-center">
<h1 className="text-2xl font-semibold">{t("backup.title")}</h1>
</div>
</div>
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
import { useState } from "react"; import { useState } from "react";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
beforeLoad: async ({ location, context }) => { beforeLoad: async ({ context }) => {
const ark = context.ark; const ark = context.ark;
const accounts = await ark.get_all_accounts(); const accounts = await ark.get_all_accounts();

View File

@@ -1,14 +0,0 @@
import { Outlet, createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/login")({
component: Screen,
});
function Screen() {
return (
<div>
<h1>Login</h1>
<Outlet />
</div>
);
}

View File

@@ -22,42 +22,41 @@ export class Ark {
public async get_all_accounts() { public async get_all_accounts() {
try { try {
const accounts: Account[] = []; const accounts: Account[] = [];
const cmd: string[] = await invoke("get_all_nsecs"); const cmd: string[] = await invoke("get_accounts");
for (const item of cmd) { if (cmd) {
accounts.push({ npub: item.replace(".nsec", "") }); for (const item of cmd) {
accounts.push({ npub: item.replace(".npub", "") });
}
this.accounts = accounts;
return accounts;
} }
} catch {
this.accounts = accounts;
return accounts;
} catch (e) {
console.error(e);
return []; return [];
} }
} }
public async load_selected_account(npub: string) { public async load_selected_account(npub: string) {
try { try {
const fullNpub = `${npub}.nsec`;
const cmd: boolean = await invoke("load_selected_account", { const cmd: boolean = await invoke("load_selected_account", {
npub: fullNpub, npub,
}); });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(e); throw new Error(String(e));
return false;
} }
} }
public async create_guest_account() { public async create_guest_account() {
try { try {
const keys = await this.create_keys(); const keys = await this.create_keys();
await this.save_account(keys); await this.save_account(keys.nsec, "");
return keys.npub; return keys.npub;
} catch (e) { } catch (e) {
console.error(e); throw new Error(String(e));
} }
} }
@@ -70,17 +69,16 @@ export class Ark {
} }
} }
public async save_account(keys: Keys) { public async save_account(nsec: string, password: string = "") {
try { try {
const cmd: boolean = await invoke("save_key", { nsec: keys.nsec }); const cmd: boolean = await invoke("save_key", {
nsec,
if (cmd) { password,
await invoke("update_signer", { nsec: keys.nsec }); });
}
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(String(e)); throw new Error(String(e));
} }
} }
@@ -92,7 +90,7 @@ export class Ark {
}); });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(String(e)); throw new Error(String(e));
} }
} }
@@ -106,7 +104,7 @@ export class Ark {
const event: Event = JSON.parse(cmd); const event: Event = JSON.parse(cmd);
return event; return event;
} catch (e) { } catch (e) {
return null; throw new Error(String(e));
} }
} }
@@ -210,8 +208,7 @@ export class Ark {
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(String(e)); throw new Error(String(e));
return false;
} }
} }
@@ -220,7 +217,7 @@ export class Ark {
const cmd: string = await invoke("reply_to", { content, tags }); const cmd: string = await invoke("reply_to", { content, tags });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(String(e)); throw new Error(String(e));
} }
} }
@@ -229,7 +226,7 @@ export class Ark {
const cmd: string = await invoke("repost", { id, pubkey: author }); const cmd: string = await invoke("repost", { id, pubkey: author });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(String(e)); throw new Error(String(e));
} }
} }
@@ -238,7 +235,7 @@ export class Ark {
const cmd: string = await invoke("upvote", { id, pubkey: author }); const cmd: string = await invoke("upvote", { id, pubkey: author });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(String(e)); throw new Error(String(e));
} }
} }
@@ -247,7 +244,7 @@ export class Ark {
const cmd: string = await invoke("downvote", { id, pubkey: author }); const cmd: string = await invoke("downvote", { id, pubkey: author });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(String(e)); throw new Error(String(e));
} }
} }
@@ -366,8 +363,7 @@ export class Ark {
const cmd: string = await invoke("follow", { id, alias }); const cmd: string = await invoke("follow", { id, alias });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(e); throw new Error(String(e));
return false;
} }
} }
@@ -376,8 +372,7 @@ export class Ark {
const cmd: string = await invoke("unfollow", { id }); const cmd: string = await invoke("unfollow", { id });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(e); throw new Error(String(e));
return false;
} }
} }
@@ -389,7 +384,7 @@ export class Ark {
}); });
return cmd; return cmd;
} catch (e) { } catch (e) {
console.error(String(e)); throw new Error(String(e));
} }
} }

View File

@@ -1,18 +1,13 @@
export function ArrowRightIcon(props: JSX.IntrinsicElements['svg']) { export function ArrowRightIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
{...props} <path
xmlns="http://www.w3.org/2000/svg" stroke="currentColor"
viewBox="0 0 24 24" strokeLinecap="round"
width="24" strokeLinejoin="round"
height="24" strokeWidth="2"
fill="none" d="m14 6 6 6-6 6m5-6H4"
stroke="currentColor" />
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="M15.17 6a30.23 30.23 0 0 1 5.62 5.406c.14.174.21.384.21.594m-5.83 6a30.232 30.232 0 0 0 5.62-5.406A.949.949 0 0 0 21 12m0 0H3" />
</svg> </svg>
); );
} }

View File

@@ -1,18 +1,12 @@
export function CancelIcon(props: JSX.IntrinsicElements['svg']) { export function CancelIcon(props: JSX.IntrinsicElements["svg"]) {
return ( return (
<svg <svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
{...props} <path
xmlns="http://www.w3.org/2000/svg" stroke="currentColor"
viewBox="0 0 24 24" strokeLinecap="round"
width="24" strokeWidth="2"
height="24" d="m5 5 14 14m0-14L5 19"
fill="none" />
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="m6 18 6-6m0 0 6-6m-6 6L6 6m6 6 6 6" />
</svg> </svg>
); );
} }

View File

@@ -7,14 +7,16 @@ export function SettingsIcon(
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}> <svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
<path <path
stroke="currentColor" stroke="currentColor"
stroke-linejoin="round" strokeLinecap="square"
stroke-width="2" strokeLinejoin="round"
d="m7.99 5.398-.685-.158A1.722 1.722 0 0 0 5.24 7.305l.158.684a1.946 1.946 0 0 1-.817 2.057l-.832.555a1.682 1.682 0 0 0 0 2.798l.832.555c.673.449.999 1.268.817 2.057l-.158.684a1.722 1.722 0 0 0 2.065 2.065l.684-.158a1.946 1.946 0 0 1 2.057.817l.555.832a1.682 1.682 0 0 0 2.798 0l.555-.832a1.946 1.946 0 0 1 2.057-.817l.684.158a1.722 1.722 0 0 0 2.065-2.065l-.158-.684a1.946 1.946 0 0 1 .817-2.057l.832-.555a1.682 1.682 0 0 0 0-2.798l-.832-.555a1.946 1.946 0 0 1-.817-2.057l.158-.684a1.722 1.722 0 0 0-2.065-2.065l-.684.158a1.946 1.946 0 0 1-2.057-.817l-.555-.832a1.682 1.682 0 0 0-2.798 0l-.555.832a1.946 1.946 0 0 1-2.057.817Z" strokeWidth="2"
d="M11.02 3.552a2 2 0 0 1 1.96 0l6 3.374A2 2 0 0 1 20 8.67v6.66a2 2 0 0 1-1.02 1.743l-6 3.375a2 2 0 0 1-1.96 0l-6-3.374A2 2 0 0 1 4 15.33V8.67a2 2 0 0 1 1.02-1.744l6-3.374Z"
/> />
<path <path
stroke="currentColor" stroke="currentColor"
stroke-linejoin="round" strokeLinecap="square"
stroke-width="2" strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
/> />
</svg> </svg>

12
pnpm-lock.yaml generated
View File

@@ -78,6 +78,9 @@ importers:
'@radix-ui/react-collapsible': '@radix-ui/react-collapsible':
specifier: ^1.0.3 specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0) version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-dialog':
specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(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-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0) version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
@@ -226,7 +229,7 @@ importers:
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0) version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(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.61)(react-dom@18.2.0)(react@18.2.0) version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(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-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0) version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
@@ -874,7 +877,7 @@ importers:
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0) version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(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.61)(react-dom@18.2.0)(react@18.2.0) version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(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-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0) version: 2.0.6(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
@@ -1912,7 +1915,7 @@ packages:
'@radix-ui/primitive': 1.0.1 '@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.61)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.61)(react@18.2.0)
'@radix-ui/react-dialog': 1.0.5(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0)
'@types/react': 18.2.61 '@types/react': 18.2.61
@@ -2072,7 +2075,7 @@ packages:
react: 18.2.0 react: 18.2.0
dev: false dev: false
/@radix-ui/react-dialog@1.0.5(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.61)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
peerDependencies: peerDependencies:
'@types/react': '*' '@types/react': '*'
@@ -2099,6 +2102,7 @@ packages:
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.61)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.61)(react@18.2.0)
'@types/react': 18.2.61 '@types/react': 18.2.61
'@types/react-dom': 18.2.19
aria-hidden: 1.2.3 aria-hidden: 1.2.3
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)

438
src-tauri/Cargo.lock generated
View File

@@ -50,49 +50,6 @@ 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.10" version = "0.8.10"
@@ -223,12 +180,6 @@ dependencies = [
"x11rb", "x11rb",
] ]
[[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"
@@ -1090,12 +1041,6 @@ 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"
@@ -1247,33 +1192,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.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.52",
]
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.20.8" version = "0.20.8"
@@ -1309,19 +1227,6 @@ dependencies = [
"syn 2.0.52", "syn 2.0.52",
] ]
[[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"
@@ -1467,17 +1372,6 @@ 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.52",
]
[[package]] [[package]]
name = "dlib" name = "dlib"
version = "0.5.2" version = "0.5.2"
@@ -1761,12 +1655,6 @@ 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"
@@ -1789,15 +1677,6 @@ 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"
@@ -1818,50 +1697,6 @@ 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"
@@ -2651,75 +2486,6 @@ 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.10",
"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",
]
[[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 0.10.0",
"syn 2.0.52",
"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.52",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.60" version = "0.1.60"
@@ -2836,25 +2602,6 @@ 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"
@@ -2866,12 +2613,6 @@ 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"
@@ -3202,7 +2943,6 @@ dependencies = [
name = "lume" name = "lume"
version = "4.0.0" version = "4.0.0"
dependencies = [ dependencies = [
"age",
"keyring", "keyring",
"nostr-sdk", "nostr-sdk",
"serde", "serde",
@@ -3335,12 +3075,6 @@ 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"
@@ -3476,16 +3210,6 @@ 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.28.1" version = "0.28.1"
@@ -4158,26 +3882,6 @@ 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.52",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.13" version = "0.2.13"
@@ -4207,12 +3911,6 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[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"
@@ -4661,52 +4359,12 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "rust-embed"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.52",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581"
dependencies = [
"sha2",
"walkdir",
]
[[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"
@@ -4931,15 +4589,6 @@ 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"
@@ -5002,21 +4651,6 @@ 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.22" version = "1.0.22"
@@ -6128,15 +5762,6 @@ 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"
@@ -6256,15 +5881,6 @@ 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"
@@ -6469,15 +6085,6 @@ 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"
@@ -6495,25 +6102,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[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"
@@ -7417,18 +7005,6 @@ 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"
@@ -7617,20 +7193,6 @@ name = "zeroize"
version = "1.7.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" 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.52",
]
[[package]] [[package]]
name = "zip" name = "zip"

View File

@@ -5,7 +5,7 @@ description = "nostr client"
authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"] authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"]
repository = "https://github.com/lumehq/lume" repository = "https://github.com/lumehq/lume"
edition = "2021" edition = "2021"
rust-version = "1.71" rust-version = "1.68"
[build-dependencies] [build-dependencies]
tauri-build = { version = "2.0.0-beta", features = [] } tauri-build = { version = "2.0.0-beta", features = [] }
@@ -37,7 +37,6 @@ tauri-plugin-upload = "2.0.0-beta"
tauri-plugin-window-state = "2.0.0-beta" tauri-plugin-window-state = "2.0.0-beta"
webpage = { version = "2.0", features = ["serde"] } webpage = { version = "2.0", features = ["serde"] }
keyring = "2" keyring = "2"
age = "0.10.0"
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1

View File

@@ -49,15 +49,15 @@ pub async fn show_in_folder(path: String) {
} }
#[tauri::command] #[tauri::command]
pub fn get_all_nsecs(app_handle: tauri::AppHandle) -> Result<Vec<String>, ()> { pub fn get_accounts(app_handle: tauri::AppHandle) -> Result<Vec<String>, ()> {
let dir = app_handle.path().app_config_dir().unwrap(); let dir = app_handle.path().home_dir().unwrap();
if let Ok(paths) = std::fs::read_dir(dir) { if let Ok(paths) = std::fs::read_dir(dir.join("Lume/")) {
let files = paths let files = paths
.filter_map(|res| res.ok()) .filter_map(|res| res.ok())
.map(|dir_entry| dir_entry.path()) .map(|dir_entry| dir_entry.path())
.filter_map(|path| { .filter_map(|path| {
if path.extension().map_or(false, |ext| ext == "nsec") { if path.extension().map_or(false, |ext| ext == "npub") {
Some(path.file_name().unwrap().to_str().unwrap().to_string()) Some(path.file_name().unwrap().to_str().unwrap().to_string())
} else { } else {
None None

View File

@@ -7,8 +7,8 @@ pub mod commands;
pub mod nostr; pub mod nostr;
pub mod tray; pub mod tray;
use age::secrecy::ExposeSecret; use std::fs;
use keyring::Entry;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use tauri::Manager; use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_autostart::MacosLauncher;
@@ -22,19 +22,15 @@ fn main() {
.setup(|app| { .setup(|app| {
let _tray = tray::create_tray(app.handle()).unwrap(); let _tray = tray::create_tray(app.handle()).unwrap();
let handle = app.handle().clone(); let handle = app.handle().clone();
let config_dir = handle.path().app_config_dir().unwrap(); let resource_dir = handle.path().resource_dir().unwrap();
let keyring_entry = Entry::new("Lume Secret Storage", "AppKey").unwrap(); let home_dir = handle.path().home_dir().unwrap();
// Create new master key if not exist // create data folder if not exist
if let Err(_) = keyring_entry.get_password() { fs::create_dir_all(home_dir.join("Lume/")).unwrap();
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(resource_dir.join("lume.db"))
.await .await
.expect("Open database failed."); .expect("Open database failed.");
@@ -89,8 +85,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::save_key,
nostr::keys::get_public_key, nostr::keys::get_encrypted_key,
nostr::keys::update_signer,
nostr::keys::verify_signer, nostr::keys::verify_signer,
nostr::keys::load_selected_account, nostr::keys::load_selected_account,
nostr::keys::event_to_bech32, nostr::keys::event_to_bech32,
@@ -120,15 +115,21 @@ fn main() {
nostr::event::upvote, nostr::event::upvote,
nostr::event::downvote, nostr::event::downvote,
commands::folder::show_in_folder, commands::folder::show_in_folder,
commands::folder::get_all_nsecs, commands::folder::get_accounts,
commands::opg::fetch_opg, commands::opg::fetch_opg,
]) ])
.build(tauri::generate_context!()) .build(tauri::generate_context!())
.expect("error while running tauri application") .expect("error while running tauri application")
.run(|_app_handle, event| match event { .run(|app, event| {
tauri::RunEvent::ExitRequested { api, .. } => { if let tauri::RunEvent::Opened { urls } = event {
api.prevent_exit(); if let Some(w) = app.get_webview_window("main") {
let urls = urls
.iter()
.map(|u| u.as_str())
.collect::<Vec<_>>()
.join(",");
let _ = w.eval(&format!("window.onFileOpen(`{urls}`)"));
}
} }
_ => {}
}); });
} }

View File

@@ -1,10 +1,8 @@
use crate::Nostr; use crate::Nostr;
use keyring::Entry; use keyring::Entry;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use std::io::{BufReader, Read};
use std::iter;
use std::time::Duration; use std::time::Duration;
use std::{fs::File, io::Write, str::FromStr}; use std::{fs::File, str::FromStr};
use tauri::{Manager, State}; use tauri::{Manager, State};
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
@@ -30,66 +28,50 @@ pub fn create_keys() -> Result<CreateKeysResponse, ()> {
#[tauri::command] #[tauri::command]
pub async fn save_key( pub async fn save_key(
nsec: &str, nsec: &str,
password: &str,
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
state: State<'_, Nostr>, state: State<'_, Nostr>,
) -> Result<bool, ()> { ) -> Result<bool, String> {
if let Ok(nostr_secret_key) = SecretKey::from_bech32(nsec) { let secret_key: Result<SecretKey, String>;
let nostr_keys = Keys::new(nostr_secret_key);
let nostr_npub = nostr_keys.public_key().to_bech32().unwrap();
let signer = NostrSigner::Keys(nostr_keys);
// Update client's signer if nsec.starts_with("ncrypto") {
let client = &state.client; let encrypted_key = EncryptedSecretKey::from_bech32(nsec).unwrap();
client.set_signer(Some(signer)).await; secret_key = match encrypted_key.to_secret_key(password) {
Ok(val) => Ok(val),
let keyring_entry = Entry::new("Lume Secret Storage", "AppKey").unwrap(); Err(_) => Err("Wrong passphase".into()),
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(true)
} else { } else {
Ok(false) secret_key = match SecretKey::from_bech32(nsec) {
Ok(val) => Ok(val),
Err(_) => Err("nsec is not valid".into()),
}
} }
}
#[tauri::command] match secret_key {
pub fn get_public_key(nsec: &str) -> Result<String, ()> { Ok(val) => {
let secret_key = SecretKey::from_bech32(nsec).unwrap(); let nostr_keys = Keys::new(val);
let keys = Keys::new(secret_key); let npub = nostr_keys.public_key().to_bech32().unwrap();
Ok( let nsec = nostr_keys.secret_key().unwrap().to_bech32().unwrap();
keys
.public_key()
.to_bech32()
.expect("get public key failed"),
)
}
#[tauri::command] let home_dir = app_handle.path().home_dir().unwrap();
pub async fn update_signer(nsec: &str, state: State<'_, Nostr>) -> Result<(), ()> { let app_dir = home_dir.join("Lume/");
let client = &state.client;
let secret_key = SecretKey::from_bech32(nsec).unwrap();
let keys = Keys::new(secret_key);
let signer = NostrSigner::Keys(keys);
client.set_signer(Some(signer)).await; let file_path = npub.clone() + ".npub";
let _ = File::create(app_dir.join(file_path)).unwrap();
Ok(()) let keyring = Entry::new("Lume Secret Storage", &npub).unwrap();
let _ = keyring.set_password(&nsec);
let signer = NostrSigner::Keys(nostr_keys);
let client = &state.client;
// Update client's signer
client.set_signer(Some(signer)).await;
Ok(true)
}
Err(msg) => Err(msg.into()),
}
} }
#[tauri::command] #[tauri::command]
@@ -104,76 +86,63 @@ pub async fn verify_signer(state: State<'_, Nostr>) -> Result<bool, ()> {
} }
#[tauri::command] #[tauri::command]
pub async fn load_selected_account( pub fn get_encrypted_key(npub: &str, password: &str) -> Result<String, String> {
npub: &str, let keyring = Entry::new("Lume Secret Storage", npub).unwrap();
app_handle: tauri::AppHandle,
state: State<'_, Nostr>,
) -> Result<bool, String> {
let client = &state.client;
let config_dir = app_handle.path().app_config_dir().unwrap();
let keyring_entry = Entry::new("Lume Secret Storage", "AppKey").unwrap();
// Get master password if let Ok(nsec) = keyring.get_password() {
if let Ok(key) = keyring_entry.get_password() { let secret_key = SecretKey::from_bech32(nsec).expect("Get secret key failed");
// Build master key let new_key = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium);
let app_key = age::x25519::Identity::from_str(&key.to_string()).unwrap();
// Open nsec file if let Ok(key) = new_key {
if let Ok(file) = File::open(config_dir.join(npub)) { Ok(key.to_bech32().unwrap())
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");
// Get decrypted nsec key
let nsec_key = String::from_utf8(decrypted).unwrap();
// Build nostr signer
let secret_key = SecretKey::from_bech32(nsec_key).expect("Get secret key failed");
let keys = Keys::new(secret_key);
let public_key = keys.public_key();
let signer = NostrSigner::Keys(keys);
// Update signer
client.set_signer(Some(signer)).await;
// Get user's relay list
let filter = Filter::new()
.author(public_key)
.kind(Kind::RelayList)
.limit(1);
let query = client
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
.await;
// Connect user's relay list
if let Ok(events) = query {
if let Some(event) = events.first() {
let list = nip65::extract_relay_list(&event);
for item in list.into_iter() {
client
.connect_relay(item.0.to_string())
.await
.unwrap_or_default();
}
}
}
Ok(true)
} else { } else {
Ok(false) Err("Encrypt key failed".into())
} }
} else { } else {
Err("App Key not found".into()) Err("Key not found".into())
}
}
#[tauri::command]
pub async fn load_selected_account(npub: &str, state: State<'_, Nostr>) -> Result<bool, String> {
let client = &state.client;
let keyring = Entry::new("Lume Secret Storage", npub).unwrap();
if let Ok(nsec) = keyring.get_password() {
// Build nostr signer
let secret_key = SecretKey::from_bech32(nsec).expect("Get secret key failed");
let keys = Keys::new(secret_key);
let public_key = keys.public_key();
let signer = NostrSigner::Keys(keys);
// Update signer
client.set_signer(Some(signer)).await;
// Get user's relay list
let filter = Filter::new()
.author(public_key)
.kind(Kind::RelayList)
.limit(1);
let query = client
.get_events_of(vec![filter], Some(Duration::from_secs(10)))
.await;
// Connect user's relay list
if let Ok(events) = query {
if let Some(event) = events.first() {
let list = nip65::extract_relay_list(&event);
for item in list.into_iter() {
client
.connect_relay(item.0.to_string())
.await
.unwrap_or_default();
}
}
}
Ok(true)
} else {
Err("nsec not found".into())
} }
} }

View File

@@ -1,22 +1,12 @@
use tauri::{tray::ClickType, Manager, Runtime}; use tauri::{tray::ClickType, Manager, Runtime};
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> { pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
let tray = app.tray().unwrap();
let menu = tauri::menu::MenuBuilder::new(app) let menu = tauri::menu::MenuBuilder::new(app)
.item(&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap()) .item(&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap())
.build() .build()
.unwrap(); .unwrap();
let _ = tray.set_menu(Some(menu));
let tray = tauri::tray::TrayIconBuilder::with_id("main_tray")
.tooltip("Lume")
.icon(tauri::Icon::Rgba {
rgba: include_bytes!("../icons/icon.png").to_vec(),
width: 500,
height: 500,
})
.icon_as_template(true)
.menu(&menu)
.build(app)
.unwrap();
tray.on_menu_event(move |app, event| match event.id.0.as_str() { tray.on_menu_event(move |app, event| match event.id.0.as_str() {
"quit" => { "quit" => {

View File

@@ -12,6 +12,11 @@
"app": { "app": {
"macOSPrivateApi": true, "macOSPrivateApi": true,
"withGlobalTauri": true, "withGlobalTauri": true,
"trayIcon": {
"id": "main_tray",
"iconPath": "./icons/tray.png",
"iconAsTemplate": true
},
"security": { "security": {
"assetProtocol": { "assetProtocol": {
"enable": true, "enable": true,
@@ -92,7 +97,15 @@
"type": "downloadBootstrapper" "type": "downloadBootstrapper"
}, },
"wix": null "wix": null
} },
"fileAssociations": [
{
"name": "bech32",
"description": "Nostr Bech32",
"ext": ["npub", "nsec", "nprofile", "nevent", "naddr", "nrelay"],
"role": "Viewer"
}
]
}, },
"plugins": { "plugins": {
"updater": { "updater": {