feat: add NIP-55 Android signer support for Amber/Primal login

When window.nostr isn't available (common on mobile Chrome where
extensions can't inject), fall back to NIP-55 nostrsigner: intent
URIs. This opens Amber/Primal directly to sign a NIP-98 event,
then redirects back with the signed event for JWT authentication.

- Build nostrsigner: URI with unsigned NIP-98 event + callback URL
- Process NIP-55 callback on page mount (extract signed event from URL)
- Auto-detect Android to show "SIGN IN WITH AMBER / PRIMAL" label
- Reduced window.nostr polling from 3s to 2s before NIP-55 fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 16:15:16 +00:00
co-authored by Claude Opus 4.6
parent df70f5f093
commit 63cc00fcb6
2 changed files with 116 additions and 4 deletions
+28 -4
View File
@@ -10,7 +10,7 @@ import WalletConnect from '../components/WalletConnect.vue'
import { authFetch } from '../lib/nostr-auth'
const router = useRouter()
const { pubkey, bot, profilePicUrl, isLoggedIn, hasExtension, hasStoredKey, isLoading, login, waitForSigner, generateLogin, loginWithNsec, registerBot, registerHuman, getStoredNsec, logout } = useNostr()
const { pubkey, bot, profilePicUrl, isLoggedIn, hasExtension, hasStoredKey, isLoading, login, waitForSigner, generateLogin, loginWithNsec, registerBot, registerHuman, getStoredNsec, logout, initiateNip55Login, processNip55Return, hasAndroidSigner } = useNostr()
const showNsecBackup = ref(false)
const generatedNsec = ref('')
const nsecInput = ref('')
@@ -131,7 +131,25 @@ function startRateLimitTimer(msg: string) {
}, 1000)
}
onMounted(() => {
onMounted(async () => {
// Check for NIP-55 callback (returning from Amber/Primal signer)
try {
const nip55Result = await processNip55Return()
if (nip55Result) {
if (nip55Result.bot) {
isHumanMode.value = !!nip55Result.bot.isHuman
step.value = 'ready'
} else {
step.value = 'choose-mode'
}
pollQueue()
pollHandle = setInterval(pollQueue, 3000)
return
}
} catch (e) {
error.value = 'Signer login failed. Try again.'
}
// If already logged in with a bot, go straight to ready
if (isLoggedIn.value) {
step.value = 'ready'
@@ -189,8 +207,14 @@ async function handleSignerLogin() {
if (!window.nostr) {
// Mobile signers inject late — wait briefly
error.value = 'Looking for signer...'
const found = await waitForSigner(3000)
const found = await waitForSigner(2000)
if (!found) {
// On Android, try NIP-55 intent to open Amber/Primal directly
if (hasAndroidSigner.value) {
error.value = 'Opening signer app...'
initiateNip55Login()
return
}
error.value = 'No Nostr signer detected. Install a NIP-07 extension (Nos2x, Alby) or use Amber on Android.'
return
}
@@ -588,7 +612,7 @@ function handleSignOut() {
@click="handleSignerLogin"
>
<span v-if="isLoading" class="w-5 h-5 border-2 border-neon-yellow/30 border-t-neon-yellow rounded-full animate-spin" />
{{ isLoading ? 'CONNECTING...' : 'USE NOSTR SIGNER' }}
{{ isLoading ? 'CONNECTING...' : hasAndroidSigner ? 'SIGN IN WITH AMBER / PRIMAL' : 'USE NOSTR SIGNER' }}
</button>
<!-- Sign in with stored key -->