diff --git a/src/app/auth/create.tsx b/src/app/auth/create.tsx
index 6f471ddf..c360e5ca 100644
--- a/src/app/auth/create.tsx
+++ b/src/app/auth/create.tsx
@@ -132,7 +132,7 @@ export function CreateAccountScreen() {
- Let's set up your Nostr account.
+ Let's set up your account.
{!keys ? (
diff --git a/src/app/auth/import.tsx b/src/app/auth/import.tsx
index 7b86be3f..85dc373f 100644
--- a/src/app/auth/import.tsx
+++ b/src/app/auth/import.tsx
@@ -1,3 +1,4 @@
+import { NDKNip46Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { readText } from '@tauri-apps/plugin-clipboard-manager';
import { motion } from 'framer-motion';
import { nip19 } from 'nostr-tools';
@@ -6,30 +7,52 @@ import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { twMerge } from 'tailwind-merge';
+import { useNDK } from '@libs/ndk/provider';
import { useStorage } from '@libs/storage/provider';
import { ArrowLeftIcon } from '@shared/icons';
import { User } from '@shared/user';
export function ImportAccountScreen() {
- const { db } = useStorage();
const navigate = useNavigate();
+ const { db } = useStorage();
+ const { ndk } = useNDK();
+
const [npub, setNpub] = useState('');
const [nsec, setNsec] = useState('');
const [pubkey, setPubkey] = useState(undefined);
- const [created, setCreated] = useState(false);
+ const [created, setCreated] = useState({ ok: false, remote: false });
const [savedPrivkey, setSavedPrivkey] = useState(false);
const submitNpub = async () => {
- if (npub.length < 6) return toast('You must enter valid npub');
- if (!npub.startsWith('npub1')) return toast('npub must be starts with npub1');
+ if (npub.length < 6) return toast.error('You must enter valid npub');
+ if (!npub.startsWith('npub1')) return toast.error('npub must be starts with npub1');
try {
const pubkey = nip19.decode(npub).data as string;
setPubkey(pubkey);
} catch (e) {
- return toast(`npub invalid: ${e}`);
+ return toast.error(`npub invalid: ${e}`);
+ }
+ };
+
+ const connectNsecBunker = async () => {
+ if (npub.length < 6) return toast.error('You must enter valid npub');
+ if (!npub.startsWith('npub1')) return toast.error('npub must be starts with npub1');
+
+ try {
+ const pubkey = nip19.decode(npub.split('#')[0]).data as string;
+ const localSigner = NDKPrivateKeySigner.generate();
+ await db.secureSave(pubkey + '-bunker', localSigner.privateKey);
+
+ const remoteSigner = new NDKNip46Signer(ndk, npub, localSigner);
+ ndk.signer = remoteSigner;
+
+ setPubkey(pubkey);
+ setCreated({ ok: false, remote: true });
+ } catch (e) {
+ return toast.error(e);
}
};
@@ -41,7 +64,9 @@ export function ImportAccountScreen() {
const createAccount = async () => {
try {
await db.createAccount(npub, pubkey);
- setCreated(true);
+ setCreated((prev) => ({ ...prev, ok: true }));
+
+ if (created.remote) navigate('/auth/onboarding', { state: { newuser: false } });
} catch (e) {
return toast(`Create account failed: ${e}`);
}
@@ -82,7 +107,7 @@ export function ImportAccountScreen() {
- Import your Nostr account.
+ Import your account.
@@ -90,7 +115,7 @@ export function ImportAccountScreen() {
Enter your public key:
-
+
{!pubkey ? (
-
- Continue
-
+
+
+ Continue
+
+
+ Continue with nsecBunker
+
+
) : null}
@@ -126,116 +160,116 @@ export function ImportAccountScreen() {
>
Account found
-
+
+
+ Change
+
- {!created ? (
-
-
- Change account
-
-
- Continue
-
-
+ {!created.ok ? (
+
+ Continue
+
) : null}
) : null}
- {created ? (
+ {created.ok ? (
<>
-
-
-
- Enter your private key (optional):
-
-
-
-
setNsec(e.target.value)}
- spellCheck={false}
- autoComplete="off"
- autoCorrect="off"
- autoCapitalize="off"
- placeholder="nsec1"
- className="h-11 w-full rounded-lg bg-neutral-200 px-3 placeholder:text-neutral-500 dark:bg-neutral-800 dark:placeholder:text-neutral-400"
- />
- {nsec.length < 5 ? (
-
-
- Paste
-
-
+ {!created.remote ? (
+
+
+
+ Enter your private key (optional):
+
+
+
+
setNsec(e.target.value)}
+ spellCheck={false}
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ placeholder="nsec1"
+ className="h-11 w-full rounded-lg bg-neutral-200 px-3 placeholder:text-neutral-500 dark:bg-neutral-800 dark:placeholder:text-neutral-400"
+ />
+ {nsec.length < 5 ? (
+
+
+ Paste
+
+
+ ) : null}
+
+ {nsec.length > 5 ? (
+
+ {savedPrivkey ? 'Saved' : 'Save'}
+
) : null}
- {nsec.length > 5 ? (
-
- {savedPrivkey ? 'Saved' : 'Save'}
-
- ) : null}
-
-
-
- Private Key is used to sign your event. For example, if you
- want to make a new post or send a message to your contact, you need to
- use your private key to sign this event.
-
-
- 1. In case you store private key in Lume
-
-
- Lume will put your private key to{' '}
-
- {db.platform === 'macos'
- ? 'Apple Keychain (macOS)'
- : db.platform === 'windows'
- ? 'Credential Manager (Windows)'
- : 'Secret Service (Linux)'}
-
- , it will be secured by your OS
-
-
- 2. In case you do not store private key in Lume
-
-
- When you make an event that requires a sign by your private key, Lume
- will show a prompt for you to enter private key. It will be cleared
- after signing and not stored anywhere.
-
-
-
+
+
+ Private Key is used to sign your event. For example, if you
+ want to make a new post or send a message to your contact, you need
+ to use your private key to sign this event.
+
+
+ 1. In case you store private key in Lume
+
+
+ Lume will put your private key to{' '}
+
+ {db.platform === 'macos'
+ ? 'Apple Keychain (macOS)'
+ : db.platform === 'windows'
+ ? 'Credential Manager (Windows)'
+ : 'Secret Service (Linux)'}
+
+ , it will be secured by your OS
+
+
+ 2. In case you do not store private key in Lume
+
+
+ When you make an event that requires a sign by your private key,
+ Lume will show a prompt for you to enter private key. It will be
+ cleared after signing and not stored anywhere.
+
+
+
+ ) : null}
- Finish
+ Continue
>
) : null}
diff --git a/src/app/auth/welcome.tsx b/src/app/auth/welcome.tsx
index 6e4b1d46..a0d192ff 100644
--- a/src/app/auth/welcome.tsx
+++ b/src/app/auth/welcome.tsx
@@ -15,13 +15,13 @@ export function WelcomeScreen() {
to="/auth/create"
className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-blue-500 font-medium text-white hover:bg-blue-600"
>
- Create new Nostr account
+ Create new account
- Log in with key
+ Log in
diff --git a/src/libs/ndk/instance.ts b/src/libs/ndk/instance.ts
index b4412361..dc4f24ab 100644
--- a/src/libs/ndk/instance.ts
+++ b/src/libs/ndk/instance.ts
@@ -1,4 +1,4 @@
-import NDK from '@nostr-dev-kit/ndk';
+import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import NDKCacheAdapterDexie from '@nostr-dev-kit/ndk-cache-dexie';
import { ndkAdapter } from '@nostr-fetch/adapter-ndk';
import { message } from '@tauri-apps/plugin-dialog';
@@ -52,9 +52,30 @@ export const NDKInstance = () => {
}
}
+ async function getSigner(instance: NDK) {
+ if (!db.account) return null;
+
+ // NIP-46 Signer
+ const localSignerPrivkey = await db.secureLoad(db.account.pubkey + '-bunker');
+ if (localSignerPrivkey) {
+ const localSigner = new NDKPrivateKeySigner(localSignerPrivkey);
+ const remoteSigner = new NDKNip46Signer(instance, db.account.id, localSigner);
+ // await remoteSigner.blockUntilReady();
+
+ return remoteSigner;
+ }
+
+ // Privkey Signer
+ const userPrivkey = await db.secureLoad(db.account.pubkey);
+ if (userPrivkey) return new NDKPrivateKeySigner(userPrivkey);
+
+ return null;
+ }
+
async function initNDK() {
- const explicitRelayUrls = await getExplicitRelays();
const outboxSetting = await db.getSettingValue('outbox');
+ const explicitRelayUrls = await getExplicitRelays();
+
const dexieAdapter = new NDKCacheAdapterDexie({ dbName: 'lume_ndkcache' });
const instance = new NDK({
explicitRelayUrls,
@@ -64,7 +85,11 @@ export const NDKInstance = () => {
});
try {
- await instance.connect();
+ // connect
+ await instance.connect(2000);
+ // add signer
+ const signer = await getSigner(instance);
+ instance.signer = signer;
} catch (error) {
await message(`NDK instance init failed: ${error}`, {
title: 'Lume',
diff --git a/src/shared/composer/composer.tsx b/src/shared/composer/composer.tsx
index 66284694..69593887 100644
--- a/src/shared/composer/composer.tsx
+++ b/src/shared/composer/composer.tsx
@@ -1,3 +1,4 @@
+import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { message } from '@tauri-apps/plugin-dialog';
import Image from '@tiptap/extension-image';
import Placeholder from '@tiptap/extension-placeholder';
@@ -7,20 +8,19 @@ import { convert } from 'html-to-text';
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';
+import { useNDK } from '@libs/ndk/provider';
+
import { MediaUploader, MentionPopup } from '@shared/composer';
import { CancelIcon, LoaderIcon } from '@shared/icons';
import { MentionNote } from '@shared/notes';
import { useComposer } from '@stores/composer';
-import { useNostr } from '@utils/hooks/useNostr';
-import { sendNativeNotification } from '@utils/notification';
-
export function Composer() {
const [loading, setLoading] = useState(false);
const [reply, clearReply] = useComposer((state) => [state.reply, state.clearReply]);
- const { publish } = useNostr();
+ const { ndk } = useNDK();
const expand = useComposer((state) => state.expand);
const editor = useEditor({
@@ -92,18 +92,21 @@ export function Composer() {
});
// publish message
- await publish({ content: serializedContent, kind: 1, tags });
+ const event = new NDKEvent(ndk);
+ event.content = serializedContent;
+ event.kind = NDKKind.Text;
+ event.tags = tags;
- // send native notifiation
- await sendNativeNotification('Post has been published successfully.');
-
- // update state
- setLoading(false);
- // reset editor
- editor.commands.clearContent();
- // reset reply
- if (reply.id) {
- clearReply();
+ const publish = event.publish();
+ if (publish) {
+ // update state
+ setLoading(false);
+ // reset editor
+ editor.commands.clearContent();
+ // reset reply
+ if (reply.id) {
+ clearReply();
+ }
}
} catch {
setLoading(false);
diff --git a/src/utils/hooks/useNostr.ts b/src/utils/hooks/useNostr.ts
index 7c40e331..c6bd222a 100644
--- a/src/utils/hooks/useNostr.ts
+++ b/src/utils/hooks/useNostr.ts
@@ -304,20 +304,14 @@ export function useNostr() {
kind: NDKKind | number;
tags: string[][];
}): Promise => {
- const privkey: string = await db.secureLoad(db.account.pubkey);
- // #TODO: show prompt
- if (!privkey) return;
-
const event = new NDKEvent(ndk);
- const signer = new NDKPrivateKeySigner(privkey);
-
event.content = content;
event.kind = kind;
event.created_at = Math.floor(Date.now() / 1000);
event.pubkey = db.account.pubkey;
event.tags = tags;
- await event.sign(signer);
+ await event.sign();
await event.publish();
return event;
diff --git a/src/utils/types.d.ts b/src/utils/types.d.ts
index 06d1617b..9c42afa2 100644
--- a/src/utils/types.d.ts
+++ b/src/utils/types.d.ts
@@ -22,8 +22,7 @@ export interface DBEvent {
}
export interface Account extends NDKUserProfile {
- id: number;
- npub: string;
+ id: string;
pubkey: string;
follows: null | string[];
circles: null | string[];