diff --git a/apps/desktop2/package.json b/apps/desktop2/package.json index 256e1f49..763359f7 100644 --- a/apps/desktop2/package.json +++ b/apps/desktop2/package.json @@ -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", diff --git a/apps/desktop2/src/components/accounts.tsx b/apps/desktop2/src/components/accounts.tsx index dbf134fd..de125ef6 100644 --- a/apps/desktop2/src/components/accounts.tsx +++ b/apps/desktop2/src/components/accounts.tsx @@ -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" >
-

You're using guest account

+

+ You're using random account +

You can continue by claim and backup this account, or you can - import your own account key. + import your own account.

- - Claim & Backup - - - {t("welcome.login")} - + +
diff --git a/apps/desktop2/src/components/backup.tsx b/apps/desktop2/src/components/backup.tsx new file mode 100644 index 00000000..24f68e55 --- /dev/null +++ b/apps/desktop2/src/components/backup.tsx @@ -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 ( + + + + + + + + + + + Esc + + +
+
+

+ This is your account key +

+

+ It's use for login to Lume or other Nostr clients. You will lost + access to your account if you lose this key. +

+
+
+
+ +
+ 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" + /> +
+
+ {key ? ( +
+ + +
+ ) : null} +
+
+ {!key ? ( + + ) : ( + + I've safely store my account key + + )} +
+
+
+
+
+ ); +} diff --git a/apps/desktop2/src/components/login.tsx b/apps/desktop2/src/components/login.tsx new file mode 100644 index 00000000..0d8cf6f2 --- /dev/null +++ b/apps/desktop2/src/components/login.tsx @@ -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 ( + + + + + + + + + + + Esc + + +
+
+

Add new account with

+
+ + + +
+
+
+
+ + 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" + /> +
+
+ + 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" + /> +
+
+
+ +
+
+
+
+
+ ); +} diff --git a/apps/desktop2/src/routes/$account.tsx b/apps/desktop2/src/routes/$account.tsx index 546d0a3d..bf3defaf 100644 --- a/apps/desktop2/src/routes/$account.tsx +++ b/apps/desktop2/src/routes/$account.tsx @@ -5,6 +5,7 @@ import { HomeFilledIcon, HomeIcon, HorizontalDotsIcon, + SettingsIcon, SpaceFilledIcon, SpaceIcon, } from "@lume/icons"; @@ -43,6 +44,12 @@ function App() { New post + diff --git a/apps/desktop2/src/routes/backup.lazy.tsx b/apps/desktop2/src/routes/backup.lazy.tsx deleted file mode 100644 index 31cdd902..00000000 --- a/apps/desktop2/src/routes/backup.lazy.tsx +++ /dev/null @@ -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 ( -
-
-
-

{t("backup.title")}

-
-
-
- ); -} diff --git a/apps/desktop2/src/routes/index.tsx b/apps/desktop2/src/routes/index.tsx index 760b708a..98d6fa86 100644 --- a/apps/desktop2/src/routes/index.tsx +++ b/apps/desktop2/src/routes/index.tsx @@ -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(); diff --git a/apps/desktop2/src/routes/login.tsx b/apps/desktop2/src/routes/login.tsx deleted file mode 100644 index 4bc1d153..00000000 --- a/apps/desktop2/src/routes/login.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Outlet, createFileRoute } from "@tanstack/react-router"; - -export const Route = createFileRoute("/login")({ - component: Screen, -}); - -function Screen() { - return ( -
-

Login

- -
- ); -} diff --git a/packages/ark/src/ark.ts b/packages/ark/src/ark.ts index af64ad67..bc79b58e 100644 --- a/packages/ark/src/ark.ts +++ b/packages/ark/src/ark.ts @@ -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)); } } diff --git a/packages/icons/src/arrowRight.tsx b/packages/icons/src/arrowRight.tsx index 33ebdc07..99ffaa37 100644 --- a/packages/icons/src/arrowRight.tsx +++ b/packages/icons/src/arrowRight.tsx @@ -1,18 +1,13 @@ -export function ArrowRightIcon(props: JSX.IntrinsicElements['svg']) { +export function ArrowRightIcon(props: JSX.IntrinsicElements["svg"]) { return ( - - + + ); } diff --git a/packages/icons/src/cancel.tsx b/packages/icons/src/cancel.tsx index b46e69ba..50644f62 100644 --- a/packages/icons/src/cancel.tsx +++ b/packages/icons/src/cancel.tsx @@ -1,18 +1,12 @@ -export function CancelIcon(props: JSX.IntrinsicElements['svg']) { +export function CancelIcon(props: JSX.IntrinsicElements["svg"]) { return ( - - + + ); } diff --git a/packages/icons/src/settings.tsx b/packages/icons/src/settings.tsx index 02f98a47..ac69645d 100644 --- a/packages/icons/src/settings.tsx +++ b/packages/icons/src/settings.tsx @@ -7,14 +7,16 @@ export function SettingsIcon( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 738bc3fa..57a975da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 327a3ecf..4694becd 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -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" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c8df6cdf..335fd1aa 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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 diff --git a/src-tauri/src/commands/folder.rs b/src-tauri/src/commands/folder.rs index 4b41b6fa..723d1895 100644 --- a/src-tauri/src/commands/folder.rs +++ b/src-tauri/src/commands/folder.rs @@ -49,15 +49,15 @@ pub async fn show_in_folder(path: String) { } #[tauri::command] -pub fn get_all_nsecs(app_handle: tauri::AppHandle) -> Result, ()> { - let dir = app_handle.path().app_config_dir().unwrap(); +pub fn get_accounts(app_handle: tauri::AppHandle) -> Result, ()> { + 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 diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6dae87f6..e3b54913 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -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::>() + .join(","); + let _ = w.eval(&format!("window.onFileOpen(`{urls}`)")); + } } - _ => {} }); } diff --git a/src-tauri/src/nostr/keys.rs b/src-tauri/src/nostr/keys.rs index 1a528986..7bf6e960 100644 --- a/src-tauri/src/nostr/keys.rs +++ b/src-tauri/src/nostr/keys.rs @@ -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 { #[tauri::command] pub async fn save_key( nsec: &str, + password: &str, app_handle: tauri::AppHandle, state: State<'_, Nostr>, -) -> Result { - if let Ok(nostr_secret_key) = SecretKey::from_bech32(nsec) { - let nostr_keys = Keys::new(nostr_secret_key); - let nostr_npub = nostr_keys.public_key().to_bech32().unwrap(); - let signer = NostrSigner::Keys(nostr_keys); +) -> Result { + let secret_key: Result; - // 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 { - 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 { } #[tauri::command] -pub async fn load_selected_account( - npub: &str, - app_handle: tauri::AppHandle, - state: State<'_, Nostr>, -) -> Result { - 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 { + 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 { + 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()) } } diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index d1716725..9dbca7c7 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -1,22 +1,12 @@ use tauri::{tray::ClickType, Manager, Runtime}; pub fn create_tray(app: &tauri::AppHandle) -> 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" => { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9b0e8f19..c93fa334 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -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": {