Files
next.orly.dev/.claude/skills/ndk/examples/02-authentication.ts
mleku 27f92336ae Add NDK skill documentation and examples
- 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.
2025-11-06 14:34:06 +00:00

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
}