From 0372eb64fee95a71281789036b0fda51d8caa8de Mon Sep 17 00:00:00 2001
From: hugotiburtino <45924645+hugotiburtino@users.noreply.github.com>
Date: Fri, 3 Apr 2026 11:17:33 -0300
Subject: [PATCH 1/4] feat(nip-46): make login with bunker work
---
docker-relay-config.toml | 3 +-
src/lib/components/auth/LoginSheet.svelte | 39 +++--
src/lib/stores/authStore.svelte.ts | 63 +++++++-
src/routes/cardsboard/LoginDialog.svelte | 178 +++++++++++-----------
4 files changed, 181 insertions(+), 102 deletions(-)
diff --git a/docker-relay-config.toml b/docker-relay-config.toml
index c00927f0..1aacaa31 100644
--- a/docker-relay-config.toml
+++ b/docker-relay-config.toml
@@ -27,5 +27,6 @@ level = "info"
# 30023: Long Text
# 30142: Nostr Learning Resource
# 20000: Ephemeral Events, um online-Users zu zeigen
-event_kind_allowlist = [0, 1, 5, 8571, 30000, 30301, 30302, 30303, 30023, 20001, 30142, 20000]
+# 24133: Für nip-46
+event_kind_allowlist = [0, 1, 5, 8571, 30000, 30301, 30302, 30303, 30023, 20001, 30142, 20000, 24133]
diff --git a/src/lib/components/auth/LoginSheet.svelte b/src/lib/components/auth/LoginSheet.svelte
index 500a2845..2ca26840 100644
--- a/src/lib/components/auth/LoginSheet.svelte
+++ b/src/lib/components/auth/LoginSheet.svelte
@@ -210,32 +210,47 @@
-
Coming Soon
- Connect to remote wallets via NIP-46
+ Verbinde dich mit einem Remote Signer via NIP-46 (Nostr Connect). Unterstützt nsec.app, Amber und andere Bunker.
- Connection String
+ Bunker-Verbindungszeichenfolge
-
+
+
+
+
+
So funktioniert's:
+
+ Öffne dein Nostr-Wallet
+ Erstelle eine neue "Bunker"-Verbindung
+ Kopiere die Bunker-URL (beginnt mit bunker://)
+ Füge sie hier ein und klicke auf "Mit NIP-46 verbinden"
+
+
- Connect Wallet (Not Available)
+ {#if isLoading}
+
+ Verbinde mit Remote Signer...
+ {:else}
+ Mit NIP-46 verbinden
+ {/if}
diff --git a/src/lib/stores/authStore.svelte.ts b/src/lib/stores/authStore.svelte.ts
index 66f7364d..1745fa1b 100644
--- a/src/lib/stores/authStore.svelte.ts
+++ b/src/lib/stores/authStore.svelte.ts
@@ -1,7 +1,7 @@
import { persisted } from "svelte-persisted-store";
import { NDKNip07Signer, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import type NDK from "@nostr-dev-kit/ndk";
-import type { NDKUser } from "@nostr-dev-kit/ndk";
+import { NDKNip46Signer, type NDKUser } from "@nostr-dev-kit/ndk";
import { get } from 'svelte/store'
import { settingsStore } from "./settingsStore.svelte.js";
@@ -207,8 +207,65 @@ export class AuthStore {
* NIP-46 Remote Signing - FUTURE
*/
public async loginWithNip46(connectionString: string): Promise {
- // TODO: Implement NIP-46
- throw new Error("NIP-46 not yet implemented");
+ try {
+ this.isLoading = true;
+ this.errorMessage = null;
+
+ if (!connectionString || connectionString.trim() === '') {
+ const message = 'Bitte gib eine gültige Bunker-Verbindungszeichenfolge ein.';
+ toast.error(message);
+ return Promise.reject(message);
+ }
+ const signer = NDKNip46Signer.bunker(this.ndk, connectionString);
+
+ signer.on("authUrl", (url) => { window.open(url, "auth", "width=600,height=600") })
+
+ this.ndk.signer = signer;
+
+ await this.ndk.connect(10000)
+
+ const user = await signer.blockUntilReady()
+
+ await user.fetchProfile();
+
+ this.currentUser = user;
+ await this.saveSession(user, "nip46");
+
+ try {
+ getSyncManager().updateSigner(signer);
+ console.log('✅ SyncManager signer updated after NIP-46 login');
+
+ // 🔄 Reconnect AUTH_REQUIRED relays now that signer is available
+ const { reconnectAuthRelays } = await import('./syncManager.svelte.js');
+ await reconnectAuthRelays();
+ } catch (error) {
+ console.warn('⚠️ SyncManager signer update warning:', error);
+ }
+
+ try {
+ const { boardStore } = await import('./kanbanStore.svelte.js');
+ boardStore.updateBoardAuthor?.();
+
+ // 🆕 DEMO-BOARD MIGRATION: Vollständige Board-Migration nach Login
+ await boardStore.onAuthChanged?.();
+
+ await boardStore.loadBoardsFromNostrForCurrentUser?.();
+ boardStore.subscribeToBoardUpdatesForCurrentUser?.();
+ console.log('[AuthStore] ✅ Boards synced from Nostr after NIP-46 login');
+ } catch (error) {
+ console.warn('[AuthStore] ⚠️ Failed to sync boards from Nostr after NIP-46 login:', error);
+ }
+
+ return user
+ } catch (error) {
+ const { message = 'NIP-46 Login fehlgeschlagen' } = error as Error;
+ this.errorMessage = message;
+ toast.error(message)
+ return Promise.reject(error);
+ } finally {
+ this.isLoading = false;
+ }
+
}
/**
diff --git a/src/routes/cardsboard/LoginDialog.svelte b/src/routes/cardsboard/LoginDialog.svelte
index 0d972046..3ae30918 100644
--- a/src/routes/cardsboard/LoginDialog.svelte
+++ b/src/routes/cardsboard/LoginDialog.svelte
@@ -17,6 +17,7 @@
let { open = $bindable(false) }: { open: boolean } = $props();
let nsecInput = $state('');
+ let nip46ConnectionString = $state('');
let isLoading = $derived(authStore.isLoading);
let errorMessage = $derived(authStore.errorMessage);
let isAuthenticated = $derived(authStore.isAuthenticated);
@@ -98,6 +99,17 @@
open = false;
}
}
+ async function handleNip46Login() {
+ if (!nip46ConnectionString.trim()) return;
+
+ try {
+ await authStore.loginWithNip46(nip46ConnectionString);
+ open = false;
+ nip46ConnectionString = '';
+ } catch (error) {
+ console.error('NIP-46 login failed:', error);
+ }
+ }
@@ -113,19 +125,16 @@
-
+
Browser-Extension
nsec
-
+
@@ -211,91 +220,88 @@
⚠️ Niemals den privaten Schlüssel öffentlich teilen oder in Production nutzen!
- {#if errorMessage}
-
- {errorMessage}
-
- {/if}
-
-
- {#if isLoading}
- Wird geladen...
- {:else}
-
- Mit nsec anmelden
- {/if}
-
-
-
-
-
-
-
- Remote Signer (NIP-46) verwenden — URL des Signer-Services und optionaler Pubkey des Signers.
-
-
-
+ {#if errorMessage}
+
+ {errorMessage}
+ {/if}
- {#if errorMessage}
-
- {errorMessage}
-
+
{
+ isLoading = true;
+ errorMessage = null;
+ try {
+ await authStore.loginWithNsec(nsecInput);
+ open = false;
+ } catch (error: any) {
+ errorMessage = error.message || 'Login fehlgeschlagen';
+ } finally {
+ isLoading = false;
+ }
+ }}
+ disabled={isLoading}
+ variant="outline"
+ class="w-full"
+ >
+ {#if isLoading}
+ Logging in...
+ {:else}
+
+ Mit nsec anmelden
{/if}
+
+
-
- ⚠️ Remote Signing: Vertraue nur Signer, denen du vertraust. Private Keys verbleiben beim Signer.
-
+
+
+
+
Bunker-Verbindungszeichenfolge
+
+
+
+
+
So funktioniert's:
+
+ Öffne dein Nostr-Wallet
+ Erstelle eine neue "Bunker"-Verbindung
+ Kopiere die Bunker-URL (beginnt mit bunker://)
+ Füge sie hier ein und klicke auf "Mit NIP-46 verbinden"
+
+
+
- {
- const form = (e.currentTarget as HTMLElement).closest('form');
- const remoteUrl = (form?.querySelector('input[name="nip46-url"]') as HTMLInputElement)?.value || '';
- if (typeof authStore.loginWithNip46 !== 'function') {
- console.error('loginWithNip46 not implemented on authStore');
- return;
- }
- const success = await authStore.loginWithNip46(remoteUrl);
- if (success) {
- open = false;
- if (form) {
- (form.querySelector('input[name="nip46-url"]') as HTMLInputElement).value = '';
- (form.querySelector('input[name="nip46-pubkey"]') as HTMLInputElement).value = '';
- }
- }
- }}
- disabled={isLoading}
- variant="outline"
- class="w-full"
- >
- {#if isLoading}
- Wird geladen...
- {:else}
-
- Mit NIP-46 anmelden
- {/if}
-
-
+ {#if errorMessage}
+
+ {errorMessage}
+
+ {/if}
+
+
+ {#if isLoading}
+ Verbinde mit Remote Signer...
+ {:else}
+
+ Mit NIP-46 verbinden
+ {/if}
+
+
-
+
-
- 🔒 Deine Authentifizierungsdaten werden lokal gespeichert
-
-
+
+ 🔒 Deine Authentifizierungsdaten werden lokal gespeichert
+
+
From 8cf21273a031d13c4eb95096cb3537a7507d2150 Mon Sep 17 00:00:00 2001
From: hugotiburtino <45924645+hugotiburtino@users.noreply.github.com>
Date: Fri, 3 Apr 2026 11:17:51 -0300
Subject: [PATCH 2/4] remove dead code
---
src/lib/stores/syncManager.svelte.ts | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/lib/stores/syncManager.svelte.ts b/src/lib/stores/syncManager.svelte.ts
index d58d2b43..5f5511b4 100644
--- a/src/lib/stores/syncManager.svelte.ts
+++ b/src/lib/stores/syncManager.svelte.ts
@@ -119,10 +119,7 @@ export class SyncManager {
}
public updateSigner(signer: NDKSigner | undefined): void {
- const wasSigner = this.signer ? 'yes' : 'no';
- const isSigner = signer ? 'yes' : 'no';
this.signer = signer;
- // console.log(`[SyncManager] Signer updated: ${wasSigner} ${isSigner}`);
if (signer && this.isOnline && this.eventQueue.length > 0) {
console.log(`[SyncManager] New signer available! Syncing ${this.eventQueue.length} queued event(s)...`);
this.syncQueue();
From 546131663db284693640b431fc3a3c993cbd31b2 Mon Sep 17 00:00:00 2001
From: hugotiburtino <45924645+hugotiburtino@users.noreply.github.com>
Date: Fri, 3 Apr 2026 11:58:09 -0300
Subject: [PATCH 3/4] fix e2e test
---
src/routes/cardsboard/LoginDialog.svelte | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/routes/cardsboard/LoginDialog.svelte b/src/routes/cardsboard/LoginDialog.svelte
index 3ae30918..dc88d8ad 100644
--- a/src/routes/cardsboard/LoginDialog.svelte
+++ b/src/routes/cardsboard/LoginDialog.svelte
@@ -239,7 +239,7 @@
isLoading = false;
}
}}
- disabled={isLoading}
+ disabled={isLoading || !nsecInput.trim()}
variant="outline"
class="w-full"
>
@@ -288,7 +288,7 @@
disabled={isLoading || !nip46ConnectionString.trim()}
variant="outline"
class="w-full"
- >
+ >
{#if isLoading}
Verbinde mit Remote Signer...
{:else}
From fa7b4bfc9838913a38915ca614742c7ea021090e Mon Sep 17 00:00:00 2001
From: hugotiburtino <45924645+hugotiburtino@users.noreply.github.com>
Date: Wed, 8 Apr 2026 00:12:00 -0300
Subject: [PATCH 4/4] feat(bunker): add qr code connection, it can only be
tested in deployment
---
src/lib/components/auth/LoginSheet.svelte | 274 ++++++++++++++++++----
src/lib/stores/authStore.svelte.ts | 128 ++++++++++
src/routes/cardsboard/LoginDialog.svelte | 269 +++++++++++++++++----
3 files changed, 588 insertions(+), 83 deletions(-)
diff --git a/src/lib/components/auth/LoginSheet.svelte b/src/lib/components/auth/LoginSheet.svelte
index 2ca26840..17ad2ace 100644
--- a/src/lib/components/auth/LoginSheet.svelte
+++ b/src/lib/components/auth/LoginSheet.svelte
@@ -1,5 +1,6 @@
!newOpen && onClose()}>
-
-
+
+
@@ -97,7 +201,7 @@
-
+
@@ -209,49 +313,135 @@
-
-
- Verbinde dich mit einem Remote Signer via NIP-46 (Nostr Connect). Unterstützt nsec.app, Amber und andere Bunker.
-
-
-
- Bunker-Verbindungszeichenfolge
-
-
-
+ {#if !qrCodeDataUrl}
+
+
+
+
+
Scan with Mobile Bunker App
+
+
+ Use Amber or another mobile bunker app to sign in securely
+
+
+ {#if isGeneratingQr}
+
+ Generating QR Code...
+ {:else}
+
+ Generate QR Code
+ {/if}
+
+
+
+
+
+
+
+
+ Paste Bunker Connection String
+
+
+
+
+
+
+
+
+ {#if isLoading}
+
+ Connecting...
+ {:else}
+ Connect with Bunker URL
+ {/if}
+
+
+
+ {:else}
+
+
+
+
+ {#if qrCodeDataUrl}
+
+ {/if}
+
+ {#if isWaitingForApproval}
+
+
+ Waiting for approval on your mobile device...
+
+ {/if}
+
-
-
So funktioniert's:
-
- Öffne dein Nostr-Wallet
- Erstelle eine neue "Bunker"-Verbindung
- Kopiere die Bunker-URL (beginnt mit bunker://)
- Füge sie hier ein und klicke auf "Mit NIP-46 verbinden"
+
+
+
Instructions:
+
+ Open Amber or your bunker app on mobile
+ Scan this QR code
+ Approve the connection request
+ You'll be automatically signed in
-
-
-
-
- {#if isLoading}
-
- Verbinde mit Remote Signer...
- {:else}
- Mit NIP-46 verbinden
- {/if}
-
+
+ {#if connectionUrl}
+
+
Or copy the connection URL:
+
+
+
+ {#if copied}
+
+ {:else}
+
+ {/if}
+
+
+
+ {/if}
+
+
+ Generate New QR Code
+
+
+ {/if}
diff --git a/src/lib/stores/authStore.svelte.ts b/src/lib/stores/authStore.svelte.ts
index 1745fa1b..5efcb9d6 100644
--- a/src/lib/stores/authStore.svelte.ts
+++ b/src/lib/stores/authStore.svelte.ts
@@ -265,7 +265,132 @@ export class AuthStore {
} finally {
this.isLoading = false;
}
+ }
+
+ /**
+ * Generate NIP-46 QR Code for Mobile App Login
+ * Creates a nostrconnect URL that can be scanned by mobile apps (Amber, etc.)
+ * Returns the URL and waits for approval
+ */
+ public async generateNip46QRCode(): Promise<{
+ url: string;
+ waitForApproval: () => Promise
;
+ }> {
+ try {
+ this.isLoading = true;
+ this.errorMessage = null;
+
+ console.log('[AuthStore] Starting NIP-46 QR generation...');
+
+ // Get user's configured relays from NDK pool
+ const userRelays = Array.from(this.ndk.pool?.relays.values() || [])
+ .map(relay => relay.url)
+ .filter(url => url && url.startsWith('wss://'));
+
+ console.log('[AuthStore] Using relays for NIP-46:', userRelays);
+
+ // Create local signer (ephemeral keypair for this session)
+ console.log('[AuthStore] Creating local signer...');
+ const localSigner = NDKPrivateKeySigner.generate();
+
+ // Create NIP-46 signer that will wait for remote connection
+ console.log('[AuthStore] Creating NIP-46 signer...');
+ const remoteSigner = new NDKNip46Signer(this.ndk, undefined, localSigner);
+
+ // Try createAccount with a 5-second timeout
+ const createAccountWithTimeout = () => {
+ return Promise.race([
+ remoteSigner.createAccount(),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('createAccount timeout - constructing URL manually')), 5000)
+ )
+ ]);
+ };
+
+ let nostrconnectUrl: string;
+ try {
+ nostrconnectUrl = await createAccountWithTimeout();
+ console.log('[AuthStore] createAccount() succeeded');
+ } catch (timeoutError) {
+ // Fallback: Construct the URL manually using user's configured relays
+ console.warn('[AuthStore] createAccount() timed out, using manual URL construction');
+ const localPubkey = await localSigner.user();
+ const pubkeyHex = localPubkey.pubkey;
+
+ // Build relay parameters - include all user relays for better connectivity
+ const relayParams = userRelays.map(url => `relay=${encodeURIComponent(url)}`).join('&');
+ const metadata = encodeURIComponent(JSON.stringify({name: 'Kanban Board', url: window.location.origin}));
+
+ nostrconnectUrl = `nostrconnect://${pubkeyHex}?${relayParams}&metadata=${metadata}`;
+ console.log('[AuthStore] Manual URL construction complete with', userRelays.length, 'relays');
+ }
+
+ console.log('🔗 Generated nostrconnect URL:', nostrconnectUrl.substring(0, 80) + '...');
+
+ // ✅ IMPORTANT: Set loading to false after URL generation
+ // The waitForApproval will handle its own loading state
+ this.isLoading = false;
+ // Return URL and a promise that resolves when connection is approved
+ return {
+ url: nostrconnectUrl,
+ waitForApproval: async () => {
+ console.log('[AuthStore] Waiting for mobile approval...');
+ this.isLoading = true;
+
+ // Set the signer and wait for it to be ready
+ this.ndk.signer = remoteSigner;
+
+ // Wait for remote app to approve (this will block until approved)
+ const user = await remoteSigner.blockUntilReady();
+
+ console.log('[AuthStore] Mobile approval received!');
+
+ // Fetch user profile
+ await user.fetchProfile();
+
+ this.currentUser = user;
+ await this.saveSession(user, "nip46");
+
+ // Update SyncManager
+ try {
+ getSyncManager().updateSigner(remoteSigner);
+ console.log('✅ SyncManager signer updated after NIP-46 QR login');
+
+ const { reconnectAuthRelays } = await import('./syncManager.svelte.js');
+ await reconnectAuthRelays();
+ } catch (error) {
+ console.warn('⚠️ SyncManager signer update warning:', error);
+ }
+
+ // Load boards from Nostr
+ try {
+ const { boardStore } = await import('./kanbanStore.svelte.js');
+ boardStore.updateBoardAuthor?.();
+ await boardStore.onAuthChanged?.();
+ await boardStore.loadBoardsFromNostrForCurrentUser?.();
+ boardStore.subscribeToBoardUpdatesForCurrentUser?.();
+ console.log('[AuthStore] ✅ Boards synced from Nostr after NIP-46 QR login');
+ } catch (error) {
+ console.warn('[AuthStore] ⚠️ Failed to sync boards from Nostr after NIP-46 QR login:', error);
+ }
+
+ this.isLoading = false;
+ return user;
+ }
+ };
+ } catch (error) {
+ this.isLoading = false;
+ const { message = 'NIP-46 QR generation failed' } = error as Error;
+ this.errorMessage = message;
+ console.error('[AuthStore] QR generation error:', error);
+ console.error('[AuthStore] Error details:', {
+ message: (error as Error).message,
+ stack: (error as Error).stack
+ });
+ toast.error(message);
+ return Promise.reject(error);
+ }
}
/**
@@ -1016,6 +1141,9 @@ class AuthStoreProxy {
loginWithNip46(relayUrl: string) {
return AuthStoreWrapper.getInstance().loginWithNip46(relayUrl);
}
+ generateNip46QRCode() {
+ return AuthStoreWrapper.getInstance().generateNip46QRCode();
+ }
logout() {
return AuthStoreWrapper.getInstance().logout();
}
diff --git a/src/routes/cardsboard/LoginDialog.svelte b/src/routes/cardsboard/LoginDialog.svelte
index dc88d8ad..5056c369 100644
--- a/src/routes/cardsboard/LoginDialog.svelte
+++ b/src/routes/cardsboard/LoginDialog.svelte
@@ -10,9 +10,14 @@
import { Label } from "$lib/components/ui/label/index.js";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "$lib/components/ui/tabs/index.js";
import { authStore } from "$lib/stores/authStore.svelte.js";
+ import QRCode from 'qrcode';
import LogInIcon from "@lucide/svelte/icons/log-in";
import UserIcon from "@lucide/svelte/icons/user";
import KeyRoundIcon from "@lucide/svelte/icons/key-round";
+ import QrCodeIcon from "@lucide/svelte/icons/qr-code";
+ import SmartphoneIcon from "@lucide/svelte/icons/smartphone";
+ import CopyIcon from "@lucide/svelte/icons/copy";
+ import CheckIcon from "@lucide/svelte/icons/check";
let { open = $bindable(false) }: { open: boolean } = $props();
@@ -21,6 +26,13 @@
let isLoading = $derived(authStore.isLoading);
let errorMessage = $derived(authStore.errorMessage);
let isAuthenticated = $derived(authStore.isAuthenticated);
+
+ // QR Code state
+ let qrCodeDataUrl = $state(null);
+ let connectionUrl = $state(null);
+ let isGeneratingQr = $state(false);
+ let isWaitingForApproval = $state(false);
+ let copied = $state(false);
// Track if dialog was open when auth was NOT active, then close after fresh auth // This prevents premature closing if user was already authenticated
let wasUnauthenticatedWhenOpened = $state(false);
@@ -46,6 +58,15 @@
}, 500);
}
});
+
+ // Reset QR code when dialog closes
+ $effect(() => {
+ if (!open) {
+ resetQrCode();
+ nsecInput = '';
+ nip46ConnectionString = '';
+ }
+ });
// Browser detection
let browserType = $state<'chrome' | 'firefox' | 'safari' | 'edge' | 'opera' | 'brave' | 'unknown'>('unknown');
@@ -110,11 +131,77 @@
console.error('NIP-46 login failed:', error);
}
}
+
+ async function generateQrCode() {
+ try {
+ isGeneratingQr = true;
+
+ console.log('[LoginDialog] Starting QR code generation...');
+
+ // Generate the nostrconnect URL using authStore
+ const result = await authStore.generateNip46QRCode();
+ console.log('[LoginDialog] Got nostrconnect URL:', result.url.substring(0, 50) + '...');
+
+ connectionUrl = result.url;
+
+ // Generate QR code as data URL
+ qrCodeDataUrl = await QRCode.toDataURL(result.url, {
+ width: 300,
+ margin: 2,
+ color: {
+ dark: '#000000',
+ light: '#FFFFFF'
+ }
+ });
+
+ console.log('[LoginDialog] QR code generated successfully');
+
+ // Finish generating state
+ isGeneratingQr = false;
+
+ // Start waiting for approval
+ isWaitingForApproval = true;
+ console.log('[LoginDialog] Waiting for mobile approval...');
+
+ // Wait for the mobile app to approve (don't await - let it run in background)
+ result.waitForApproval()
+ .then((user) => {
+ if (user) {
+ console.log('[LoginDialog] Mobile approval received!');
+ isWaitingForApproval = false;
+ open = false;
+ }
+ })
+ .catch((approvalError: any) => {
+ console.error('[LoginDialog] Approval failed:', approvalError);
+ isWaitingForApproval = false;
+ });
+
+ } catch (err: any) {
+ console.error('[LoginDialog] QR generation error:', err);
+ isGeneratingQr = false;
+ isWaitingForApproval = false;
+ }
+ }
+
+ function copyToClipboard() {
+ if (connectionUrl) {
+ navigator.clipboard.writeText(connectionUrl);
+ copied = true;
+ setTimeout(() => copied = false, 2000);
+ }
+ }
+
+ function resetQrCode() {
+ qrCodeDataUrl = null;
+ connectionUrl = null;
+ isWaitingForApproval = false;
+ }
-
-
+
+
Login
@@ -124,7 +211,8 @@
-
+
+
Browser-Extension
@@ -254,53 +342,152 @@
-
-
Bunker-Verbindungszeichenfolge
-
-
+ {#if !qrCodeDataUrl}
+
+
+
+
+
Scan with Mobile Bunker App
+
+
+ Use Amber or another mobile bunker app to sign in securely
+
+
+ {#if isGeneratingQr}
+
+ Generating QR Code...
+ {:else}
+
+ Generate QR Code
+ {/if}
+
-
-
So funktioniert's:
-
- Öffne dein Nostr-Wallet
- Erstelle eine neue "Bunker"-Verbindung
- Kopiere die Bunker-URL (beginnt mit bunker://)
- Füge sie hier ein und klicke auf "Mit NIP-46 verbinden"
-
+
+
-
-
- {#if errorMessage}
-
- {errorMessage}
+
+
+
+
Bunker Connection String
+
+
+
+
+
How it works:
+
+ Open your Nostr wallet
+ Create a new "Bunker" connection
+ Copy the bunker URL (starts with bunker://)
+ Paste it here and click "Connect with NIP-46"
+
+
- {/if}
-
- {#if isLoading}
- Verbinde mit Remote Signer...
+ {#if errorMessage}
+
+ {errorMessage}
+
+ {/if}
+
+
+ {#if isLoading}
+ Connecting to Remote Signer...
+ {:else}
+
+ Connect with NIP-46
+ {/if}
+
{:else}
-
- Mit NIP-46 verbinden
+
+
+
+
+ {#if qrCodeDataUrl}
+
+ {/if}
+
+ {#if isWaitingForApproval}
+
+
+ Waiting for approval on your mobile device...
+
+ {/if}
+
+
+
+
+
Instructions:
+
+ Open Amber or your bunker app on mobile
+ Scan this QR code
+ Approve the connection request
+ You'll be automatically signed in
+
+
+
+ {#if connectionUrl}
+
+
Or copy the connection URL:
+
+
+
+ {#if copied}
+
+ {:else}
+
+ {/if}
+
+
+
+ {/if}
+
+
+ Generate New QR Code
+
+
{/if}
-
-
+
+
-
+
🔒 Deine Authentifizierungsdaten werden lokal gespeichert