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:^",
"@radix-ui/react-checkbox": "^1.0.4",
"@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-popover": "^1.0.7",
"@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 { useEffect, useState } from "react";
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 { BackupDialog } from "./backup";
import { LoginDialog } from "./login";
export function Accounts() {
const ark = useArk();
@@ -63,7 +63,6 @@ function Active({ pubkey }: { pubkey: string }) {
const [open, setOpen] = useState(true);
// @ts-ignore, magic !!!
const { guest } = useSearch({ strict: false });
const { t } = useTranslation();
if (guest) {
return (
@@ -84,25 +83,17 @@ function Active({ pubkey }: { pubkey: string }) {
side="bottom"
>
<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">
You can continue by claim and backup this account, or you can
import your own account key.
import your own account.
</p>
</div>
<div className="flex flex-col gap-2">
<Link
to="/backup"
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>
<BackupDialog />
<LoginDialog />
</div>
<Popover.Arrow className="fill-black dark:fill-white" />
</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,
HomeIcon,
HorizontalDotsIcon,
SettingsIcon,
SpaceFilledIcon,
SpaceIcon,
} from "@lume/icons";
@@ -43,6 +44,12 @@ function App() {
<ComposeFilledIcon className="size-4" />
New post
</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>
<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";
export const Route = createFileRoute("/")({
beforeLoad: async ({ location, context }) => {
beforeLoad: async ({ context }) => {
const ark = context.ark;
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() {
try {
const accounts: Account[] = [];
const cmd: string[] = await invoke("get_all_nsecs");
const cmd: string[] = await invoke("get_accounts");
for (const item of cmd) {
accounts.push({ npub: item.replace(".nsec", "") });
if (cmd) {
for (const item of cmd) {
accounts.push({ npub: item.replace(".npub", "") });
}
this.accounts = accounts;
return accounts;
}
this.accounts = accounts;
return accounts;
} catch (e) {
console.error(e);
} catch {
return [];
}
}
public async load_selected_account(npub: string) {
try {
const fullNpub = `${npub}.nsec`;
const cmd: boolean = await invoke("load_selected_account", {
npub: fullNpub,
npub,
});
return cmd;
} catch (e) {
console.error(e);
return false;
throw new Error(String(e));
}
}
public async create_guest_account() {
try {
const keys = await this.create_keys();
await this.save_account(keys);
await this.save_account(keys.nsec, "");
return keys.npub;
} 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 {
const cmd: boolean = await invoke("save_key", { nsec: keys.nsec });
if (cmd) {
await invoke("update_signer", { nsec: keys.nsec });
}
const cmd: boolean = await invoke("save_key", {
nsec,
password,
});
return cmd;
} catch (e) {
console.error(String(e));
throw new Error(String(e));
}
}
@@ -92,7 +90,7 @@ export class Ark {
});
return cmd;
} catch (e) {
console.error(String(e));
throw new Error(String(e));
}
}
@@ -106,7 +104,7 @@ export class Ark {
const event: Event = JSON.parse(cmd);
return event;
} catch (e) {
return null;
throw new Error(String(e));
}
}
@@ -210,8 +208,7 @@ export class Ark {
return cmd;
} catch (e) {
console.error(String(e));
return false;
throw new Error(String(e));
}
}
@@ -220,7 +217,7 @@ export class Ark {
const cmd: string = await invoke("reply_to", { content, tags });
return cmd;
} 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 });
return cmd;
} 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 });
return cmd;
} 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 });
return cmd;
} 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 });
return cmd;
} catch (e) {
console.error(e);
return false;
throw new Error(String(e));
}
}
@@ -376,8 +372,7 @@ export class Ark {
const cmd: string = await invoke("unfollow", { id });
return cmd;
} catch (e) {
console.error(e);
return false;
throw new Error(String(e));
}
}
@@ -389,7 +384,7 @@ export class Ark {
});
return cmd;
} 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 (
<svg
{...props}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24"
fill="none"
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 width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m14 6 6 6-6 6m5-6H4"
/>
</svg>
);
}

View File

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

View File

@@ -7,14 +7,16 @@ export function SettingsIcon(
<svg width="24" height="24" fill="none" viewBox="0 0 24 24" {...props}>
<path
stroke="currentColor"
stroke-linejoin="round"
stroke-width="2"
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"
strokeLinecap="square"
strokeLinejoin="round"
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
stroke="currentColor"
stroke-linejoin="round"
stroke-width="2"
strokeLinecap="square"
strokeLinejoin="round"
strokeWidth="2"
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
/>
</svg>

12
pnpm-lock.yaml generated
View File

@@ -78,6 +78,9 @@ importers:
'@radix-ui/react-collapsible':
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)
'@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':
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)
@@ -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)
'@radix-ui/react-dialog':
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':
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)
@@ -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)
'@radix-ui/react-dialog':
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':
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)
@@ -1912,7 +1915,7 @@ packages:
'@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-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-slot': 1.0.2(@types/react@18.2.61)(react@18.2.0)
'@types/react': 18.2.61
@@ -2072,7 +2075,7 @@ packages:
react: 18.2.0
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==}
peerDependencies:
'@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-use-controllable-state': 1.0.1(@types/react@18.2.61)(react@18.2.0)
'@types/react': 18.2.61
'@types/react-dom': 18.2.19
aria-hidden: 1.2.3
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",
]
[[package]]
name = "age"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edeef7d7b199195a2d7d7a8155d2d04aee736e60c5c7bdd7097d115369a8817d"
dependencies = [
"age-core",
"base64",
"bech32",
"chacha20poly1305",
"cookie-factory",
"hmac",
"i18n-embed",
"i18n-embed-fl",
"lazy_static",
"nom",
"pin-project",
"rand 0.8.5",
"rust-embed",
"scrypt",
"sha2",
"subtle",
"x25519-dalek",
"zeroize",
]
[[package]]
name = "age-core"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5f11899bc2bbddd135edbc30c36b1924fa59d0746bb45beb5933fafe3fe509b"
dependencies = [
"base64",
"chacha20poly1305",
"cookie-factory",
"hkdf",
"io_tee",
"nom",
"rand 0.8.5",
"secrecy",
"sha2",
]
[[package]]
name = "ahash"
version = "0.8.10"
@@ -223,12 +180,6 @@ dependencies = [
"x11rb",
]
[[package]]
name = "arc-swap"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]]
name = "as-raw-xcb-connection"
version = "1.0.1"
@@ -1090,12 +1041,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "cookie-factory"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -1247,33 +1192,6 @@ dependencies = [
"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]]
name = "darling"
version = "0.20.8"
@@ -1309,19 +1227,6 @@ dependencies = [
"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]]
name = "data-encoding"
version = "2.5.0"
@@ -1467,17 +1372,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "displaydoc"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
]
[[package]]
name = "dlib"
version = "0.5.2"
@@ -1761,12 +1655,6 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "fiat-crypto"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382"
[[package]]
name = "field-offset"
version = "0.3.6"
@@ -1789,15 +1677,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "find-crate"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2"
dependencies = [
"toml 0.5.11",
]
[[package]]
name = "flatbuffers"
version = "23.5.26"
@@ -1818,50 +1697,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "fluent"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61f69378194459db76abd2ce3952b790db103ceb003008d3d50d97c41ff847a7"
dependencies = [
"fluent-bundle",
"unic-langid",
]
[[package]]
name = "fluent-bundle"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e242c601dec9711505f6d5bbff5bedd4b61b2469f2e8bb8e57ee7c9747a87ffd"
dependencies = [
"fluent-langneg",
"fluent-syntax",
"intl-memoizer",
"intl_pluralrules",
"rustc-hash",
"self_cell 0.10.3",
"smallvec",
"unic-langid",
]
[[package]]
name = "fluent-langneg"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
dependencies = [
"unic-langid",
]
[[package]]
name = "fluent-syntax"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0abed97648395c902868fee9026de96483933faa54ea3b40d652f7dfe61ca78"
dependencies = [
"thiserror",
]
[[package]]
name = "fnv"
version = "1.0.7"
@@ -2651,75 +2486,6 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "i18n-config"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9ce3c48cbc21fd5b22b9331f32b5b51f6ad85d969b99e793427332e76e7640"
dependencies = [
"log",
"serde",
"serde_derive",
"thiserror",
"toml 0.8.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]]
name = "iana-time-zone"
version = "0.1.60"
@@ -2836,25 +2602,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "intl-memoizer"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f"
dependencies = [
"type-map",
"unic-langid",
]
[[package]]
name = "intl_pluralrules"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
dependencies = [
"unic-langid",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
@@ -2866,12 +2613,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "io_tee"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304"
[[package]]
name = "ipnet"
version = "2.9.0"
@@ -3202,7 +2943,6 @@ dependencies = [
name = "lume"
version = "4.0.0"
dependencies = [
"age",
"keyring",
"nostr-sdk",
"serde",
@@ -3335,12 +3075,6 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "minisign-verify"
version = "0.2.1"
@@ -3476,16 +3210,6 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nostr"
version = "0.28.1"
@@ -4158,26 +3882,6 @@ dependencies = [
"siphasher",
]
[[package]]
name = "pin-project"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
@@ -4207,12 +3911,6 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "platforms"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c"
[[package]]
name = "plist"
version = "1.6.0"
@@ -4661,52 +4359,12 @@ dependencies = [
"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]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.0"
@@ -4931,15 +4589,6 @@ dependencies = [
"cc",
]
[[package]]
name = "secrecy"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
dependencies = [
"zeroize",
]
[[package]]
name = "secret-service"
version = "3.0.1"
@@ -5002,21 +4651,6 @@ dependencies = [
"thin-slice",
]
[[package]]
name = "self_cell"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d"
dependencies = [
"self_cell 1.0.3",
]
[[package]]
name = "self_cell"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba"
[[package]]
name = "semver"
version = "1.0.22"
@@ -6128,15 +5762,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "tinystr"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece"
dependencies = [
"displaydoc",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@@ -6256,15 +5881,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml"
version = "0.7.8"
@@ -6469,15 +6085,6 @@ dependencies = [
"utf-8",
]
[[package]]
name = "type-map"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46"
dependencies = [
"rustc-hash",
]
[[package]]
name = "typenum"
version = "1.17.0"
@@ -6495,25 +6102,6 @@ dependencies = [
"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]]
name = "unicode-bidi"
version = "0.3.15"
@@ -7417,18 +7005,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34"
[[package]]
name = "x25519-dalek"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
dependencies = [
"curve25519-dalek",
"rand_core 0.6.4",
"serde",
"zeroize",
]
[[package]]
name = "xattr"
version = "1.3.1"
@@ -7617,20 +7193,6 @@ 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.52",
]
[[package]]
name = "zip"

View File

@@ -5,7 +5,7 @@ description = "nostr client"
authors = ["npub1zfss807aer0j26mwp2la0ume0jqde3823rmu97ra6sgyyg956e0s6xw445"]
repository = "https://github.com/lumehq/lume"
edition = "2021"
rust-version = "1.71"
rust-version = "1.68"
[build-dependencies]
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"
webpage = { version = "2.0", features = ["serde"] }
keyring = "2"
age = "0.10.0"
[profile.release]
codegen-units = 1

View File

@@ -49,15 +49,15 @@ pub async fn show_in_folder(path: String) {
}
#[tauri::command]
pub fn get_all_nsecs(app_handle: tauri::AppHandle) -> Result<Vec<String>, ()> {
let dir = app_handle.path().app_config_dir().unwrap();
pub fn get_accounts(app_handle: tauri::AppHandle) -> Result<Vec<String>, ()> {
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
.filter_map(|res| res.ok())
.map(|dir_entry| dir_entry.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())
} else {
None

View File

@@ -7,8 +7,8 @@ pub mod commands;
pub mod nostr;
pub mod tray;
use age::secrecy::ExposeSecret;
use keyring::Entry;
use std::fs;
use nostr_sdk::prelude::*;
use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher;
@@ -22,19 +22,15 @@ fn main() {
.setup(|app| {
let _tray = tray::create_tray(app.handle()).unwrap();
let handle = app.handle().clone();
let config_dir = handle.path().app_config_dir().unwrap();
let keyring_entry = Entry::new("Lume Secret Storage", "AppKey").unwrap();
let resource_dir = handle.path().resource_dir().unwrap();
let home_dir = handle.path().home_dir().unwrap();
// Create new master key if not exist
if let Err(_) = keyring_entry.get_password() {
let app_key = age::x25519::Identity::generate().to_string();
let app_secret = app_key.expose_secret();
let _ = keyring_entry.set_password(app_secret);
}
// create data folder if not exist
fs::create_dir_all(home_dir.join("Lume/")).unwrap();
tauri::async_runtime::spawn(async move {
// 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
.expect("Open database failed.");
@@ -89,8 +85,7 @@ fn main() {
.invoke_handler(tauri::generate_handler![
nostr::keys::create_keys,
nostr::keys::save_key,
nostr::keys::get_public_key,
nostr::keys::update_signer,
nostr::keys::get_encrypted_key,
nostr::keys::verify_signer,
nostr::keys::load_selected_account,
nostr::keys::event_to_bech32,
@@ -120,15 +115,21 @@ fn main() {
nostr::event::upvote,
nostr::event::downvote,
commands::folder::show_in_folder,
commands::folder::get_all_nsecs,
commands::folder::get_accounts,
commands::opg::fetch_opg,
])
.build(tauri::generate_context!())
.expect("error while running tauri application")
.run(|_app_handle, event| match event {
tauri::RunEvent::ExitRequested { api, .. } => {
api.prevent_exit();
.run(|app, event| {
if let tauri::RunEvent::Opened { urls } = event {
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 keyring::Entry;
use nostr_sdk::prelude::*;
use std::io::{BufReader, Read};
use std::iter;
use std::time::Duration;
use std::{fs::File, io::Write, str::FromStr};
use std::{fs::File, str::FromStr};
use tauri::{Manager, State};
#[derive(serde::Serialize)]
@@ -30,66 +28,50 @@ pub fn create_keys() -> Result<CreateKeysResponse, ()> {
#[tauri::command]
pub async fn save_key(
nsec: &str,
password: &str,
app_handle: tauri::AppHandle,
state: State<'_, Nostr>,
) -> Result<bool, ()> {
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 signer = NostrSigner::Keys(nostr_keys);
) -> Result<bool, String> {
let secret_key: Result<SecretKey, String>;
// Update client's signer
let client = &state.client;
client.set_signer(Some(signer)).await;
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(true)
if nsec.starts_with("ncrypto") {
let encrypted_key = EncryptedSecretKey::from_bech32(nsec).unwrap();
secret_key = match encrypted_key.to_secret_key(password) {
Ok(val) => Ok(val),
Err(_) => Err("Wrong passphase".into()),
};
} else {
Ok(false)
secret_key = match SecretKey::from_bech32(nsec) {
Ok(val) => Ok(val),
Err(_) => Err("nsec is not valid".into()),
}
}
}
#[tauri::command]
pub fn get_public_key(nsec: &str) -> Result<String, ()> {
let secret_key = SecretKey::from_bech32(nsec).unwrap();
let keys = Keys::new(secret_key);
Ok(
keys
.public_key()
.to_bech32()
.expect("get public key failed"),
)
}
match secret_key {
Ok(val) => {
let nostr_keys = Keys::new(val);
let npub = nostr_keys.public_key().to_bech32().unwrap();
let nsec = nostr_keys.secret_key().unwrap().to_bech32().unwrap();
#[tauri::command]
pub async fn update_signer(nsec: &str, state: State<'_, Nostr>) -> Result<(), ()> {
let client = &state.client;
let secret_key = SecretKey::from_bech32(nsec).unwrap();
let keys = Keys::new(secret_key);
let signer = NostrSigner::Keys(keys);
let home_dir = app_handle.path().home_dir().unwrap();
let app_dir = home_dir.join("Lume/");
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]
@@ -104,76 +86,63 @@ pub async fn verify_signer(state: State<'_, Nostr>) -> Result<bool, ()> {
}
#[tauri::command]
pub async fn load_selected_account(
npub: &str,
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();
pub fn get_encrypted_key(npub: &str, password: &str) -> Result<String, String> {
let keyring = Entry::new("Lume Secret Storage", npub).unwrap();
// Get master password
if let Ok(key) = keyring_entry.get_password() {
// Build master key
let app_key = age::x25519::Identity::from_str(&key.to_string()).unwrap();
if let Ok(nsec) = keyring.get_password() {
let secret_key = SecretKey::from_bech32(nsec).expect("Get secret key failed");
let new_key = EncryptedSecretKey::new(&secret_key, password, 16, KeySecurity::Medium);
// Open nsec file
if let Ok(file) = File::open(config_dir.join(npub)) {
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)
if let Ok(key) = new_key {
Ok(key.to_bech32().unwrap())
} else {
Ok(false)
Err("Encrypt key failed".into())
}
} 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};
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
let tray = app.tray().unwrap();
let menu = tauri::menu::MenuBuilder::new(app)
.item(&tauri::menu::MenuItem::with_id(app, "quit", "Quit", true, None::<&str>).unwrap())
.build()
.unwrap();
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();
let _ = tray.set_menu(Some(menu));
tray.on_menu_event(move |app, event| match event.id.0.as_str() {
"quit" => {

View File

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