- Introduced comprehensive documentation for the Nostr Development Kit (NDK) including an overview, quick reference, and troubleshooting guide. - Added detailed examples covering initialization, authentication, event publishing, querying, and user profile management. - Structured the documentation to facilitate quick lookups and deep learning, based on real-world usage patterns from the Plebeian Market application. - Created an index for examples to enhance usability and navigation. - Bumped version to 1.0.0 to reflect the addition of this new skill set.
256 lines
6.8 KiB
TypeScript
256 lines
6.8 KiB
TypeScript
/**
|
|
* NDK Authentication Patterns
|
|
*
|
|
* Examples from: src/lib/stores/auth.ts
|
|
*/
|
|
|
|
import NDK from '@nostr-dev-kit/ndk'
|
|
import { NDKNip07Signer, NDKPrivateKeySigner, NDKNip46Signer } from '@nostr-dev-kit/ndk'
|
|
|
|
// ============================================================
|
|
// NIP-07 - BROWSER EXTENSION SIGNER
|
|
// ============================================================
|
|
|
|
const loginWithExtension = async (ndk: NDK) => {
|
|
try {
|
|
// Create NIP-07 signer (browser extension like Alby, nos2x)
|
|
const signer = new NDKNip07Signer()
|
|
|
|
// Wait for signer to be ready
|
|
await signer.blockUntilReady()
|
|
|
|
// Set signer on NDK instance
|
|
ndk.signer = signer
|
|
|
|
// Get authenticated user
|
|
const user = await signer.user()
|
|
|
|
console.log('✅ Logged in via extension:', user.npub)
|
|
return { user, signer }
|
|
} catch (error) {
|
|
console.error('❌ Extension login failed:', error)
|
|
throw new Error('Failed to login with browser extension. Is it installed?')
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// PRIVATE KEY SIGNER
|
|
// ============================================================
|
|
|
|
const loginWithPrivateKey = async (ndk: NDK, privateKeyHex: string) => {
|
|
try {
|
|
// Validate private key format (64 hex characters)
|
|
if (!/^[0-9a-f]{64}$/.test(privateKeyHex)) {
|
|
throw new Error('Invalid private key format')
|
|
}
|
|
|
|
// Create private key signer
|
|
const signer = new NDKPrivateKeySigner(privateKeyHex)
|
|
|
|
// Wait for signer to be ready
|
|
await signer.blockUntilReady()
|
|
|
|
// Set signer on NDK instance
|
|
ndk.signer = signer
|
|
|
|
// Get authenticated user
|
|
const user = await signer.user()
|
|
|
|
console.log('✅ Logged in with private key:', user.npub)
|
|
return { user, signer }
|
|
} catch (error) {
|
|
console.error('❌ Private key login failed:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// NIP-46 - REMOTE SIGNER (BUNKER)
|
|
// ============================================================
|
|
|
|
const loginWithNip46 = async (
|
|
ndk: NDK,
|
|
bunkerUrl: string,
|
|
localPrivateKey?: string
|
|
) => {
|
|
try {
|
|
// Create or use existing local signer
|
|
const localSigner = localPrivateKey
|
|
? new NDKPrivateKeySigner(localPrivateKey)
|
|
: NDKPrivateKeySigner.generate()
|
|
|
|
// Create NIP-46 remote signer
|
|
const remoteSigner = new NDKNip46Signer(ndk, bunkerUrl, localSigner)
|
|
|
|
// Wait for signer to be ready (may require user approval)
|
|
await remoteSigner.blockUntilReady()
|
|
|
|
// Set signer on NDK instance
|
|
ndk.signer = remoteSigner
|
|
|
|
// Get authenticated user
|
|
const user = await remoteSigner.user()
|
|
|
|
console.log('✅ Logged in via NIP-46:', user.npub)
|
|
|
|
// Store local signer key for reconnection
|
|
return {
|
|
user,
|
|
signer: remoteSigner,
|
|
localSignerKey: localSigner.privateKey
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ NIP-46 login failed:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// AUTO-LOGIN FROM LOCAL STORAGE
|
|
// ============================================================
|
|
|
|
const STORAGE_KEYS = {
|
|
AUTO_LOGIN: 'nostr:auto-login',
|
|
LOCAL_SIGNER: 'nostr:local-signer',
|
|
BUNKER_URL: 'nostr:bunker-url',
|
|
ENCRYPTED_KEY: 'nostr:encrypted-key'
|
|
}
|
|
|
|
const getAuthFromStorage = async (ndk: NDK) => {
|
|
try {
|
|
// Check if auto-login is enabled
|
|
const autoLogin = localStorage.getItem(STORAGE_KEYS.AUTO_LOGIN)
|
|
if (autoLogin !== 'true') {
|
|
return null
|
|
}
|
|
|
|
// Try NIP-46 bunker connection
|
|
const privateKey = localStorage.getItem(STORAGE_KEYS.LOCAL_SIGNER)
|
|
const bunkerUrl = localStorage.getItem(STORAGE_KEYS.BUNKER_URL)
|
|
|
|
if (privateKey && bunkerUrl) {
|
|
return await loginWithNip46(ndk, bunkerUrl, privateKey)
|
|
}
|
|
|
|
// Try encrypted private key
|
|
const encryptedKey = localStorage.getItem(STORAGE_KEYS.ENCRYPTED_KEY)
|
|
if (encryptedKey) {
|
|
// Would need decryption password from user
|
|
return { needsPassword: true, encryptedKey }
|
|
}
|
|
|
|
// Fallback to extension
|
|
return await loginWithExtension(ndk)
|
|
} catch (error) {
|
|
console.error('Auto-login failed:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// SAVE AUTH TO STORAGE
|
|
// ============================================================
|
|
|
|
const saveAuthToStorage = (
|
|
method: 'extension' | 'private-key' | 'nip46',
|
|
data?: {
|
|
privateKey?: string
|
|
bunkerUrl?: string
|
|
encryptedKey?: string
|
|
}
|
|
) => {
|
|
// Enable auto-login
|
|
localStorage.setItem(STORAGE_KEYS.AUTO_LOGIN, 'true')
|
|
|
|
if (method === 'nip46' && data?.privateKey && data?.bunkerUrl) {
|
|
localStorage.setItem(STORAGE_KEYS.LOCAL_SIGNER, data.privateKey)
|
|
localStorage.setItem(STORAGE_KEYS.BUNKER_URL, data.bunkerUrl)
|
|
} else if (method === 'private-key' && data?.encryptedKey) {
|
|
localStorage.setItem(STORAGE_KEYS.ENCRYPTED_KEY, data.encryptedKey)
|
|
}
|
|
// Extension doesn't need storage
|
|
}
|
|
|
|
// ============================================================
|
|
// LOGOUT
|
|
// ============================================================
|
|
|
|
const logout = (ndk: NDK) => {
|
|
// Remove signer from NDK
|
|
ndk.signer = undefined
|
|
|
|
// Clear all auth storage
|
|
Object.values(STORAGE_KEYS).forEach(key => {
|
|
localStorage.removeItem(key)
|
|
})
|
|
|
|
console.log('✅ Logged out successfully')
|
|
}
|
|
|
|
// ============================================================
|
|
// GET CURRENT USER
|
|
// ============================================================
|
|
|
|
const getCurrentUser = async (ndk: NDK) => {
|
|
if (!ndk.signer) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const user = await ndk.signer.user()
|
|
return {
|
|
pubkey: user.pubkey,
|
|
npub: user.npub,
|
|
profile: await user.fetchProfile()
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to get current user:', error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// USAGE EXAMPLE
|
|
// ============================================================
|
|
|
|
async function authExample(ndk: NDK) {
|
|
// Try auto-login first
|
|
let auth = await getAuthFromStorage(ndk)
|
|
|
|
if (!auth) {
|
|
// Manual login options
|
|
console.log('Choose login method:')
|
|
console.log('1. Browser Extension (NIP-07)')
|
|
console.log('2. Private Key')
|
|
console.log('3. Remote Signer (NIP-46)')
|
|
|
|
// Example: login with extension
|
|
auth = await loginWithExtension(ndk)
|
|
saveAuthToStorage('extension')
|
|
}
|
|
|
|
if (auth && 'needsPassword' in auth) {
|
|
// Handle encrypted key case
|
|
console.log('Password required for encrypted key')
|
|
return
|
|
}
|
|
|
|
// Get current user info
|
|
const currentUser = await getCurrentUser(ndk)
|
|
console.log('Current user:', currentUser)
|
|
|
|
// Logout when done
|
|
// logout(ndk)
|
|
}
|
|
|
|
export {
|
|
loginWithExtension,
|
|
loginWithPrivateKey,
|
|
loginWithNip46,
|
|
getAuthFromStorage,
|
|
saveAuthToStorage,
|
|
logout,
|
|
getCurrentUser
|
|
}
|
|
|