From f870f2a0ca9afe38abac03909d2acd15981ad3be Mon Sep 17 00:00:00 2001 From: DocNR Date: Tue, 4 Mar 2025 08:07:27 -0500 Subject: [PATCH] nostr testing component (programs) functioning with all event kinds --- CHANGELOG.md | 64 +++++++++++++++++++++++++++++------- lib/initNDK.ts | 78 ++++++++++++++++++++++++++++++++++++++++++++ lib/mobile-signer.ts | 23 +++---------- lib/stores/ndk.ts | 52 ++++++++++++++++++++--------- 4 files changed, 173 insertions(+), 44 deletions(-) create mode 100644 lib/initNDK.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e57fe2..8689036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Successful Nostr protocol integration + - Implemented NDK-mobile for React Native compatibility + - Added secure key management with Expo SecureStore + - Created event signing and publishing functionality + - Built relay connection management system + - Implemented event caching for offline support + - Added support for various Nostr event kinds (Text, Exercise, Template, Workout) +- Programs component for testing Nostr functionality + - Created tabbed interface with Database and Nostr sections + - Implemented user authentication flow + - Added event creation with multiple event types + - Built query functionality for retrieving events + - Developed event display with detailed tag inspection + - Added login/logout capabilities with secure key handling +- Enhanced crypto support for React Native environment + - Implemented proper cryptographic polyfills + - Added secure random number generation + - Built robust key management system + - Developed signer implementation for Nostr +- Zustand workout store for state management + - Created comprehensive workout state store with Zustand + - Implemented selectors for efficient state access + - Added workout persistence and recovery + - Built automatic timer management with background support + - Developed minimization and maximization functionality - Zustand workout store for state management - Created comprehensive workout state store with Zustand - Implemented selectors for efficient state access @@ -119,39 +144,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Content rendering issues in bottom sheet components ### Technical Details -1. Database Schema Enforcement: +1. Nostr Integration: + - Implemented @nostr-dev-kit/ndk-mobile package for React Native compatibility + - Created dedicated NDK store using Zustand for state management + - Built secure key storage and retrieval using Expo SecureStore + - Implemented event creation, signing, and publishing workflow + - Added relay connection management with status tracking + - Developed proper error handling for network operations + +2. Cryptographic Implementation: + - Integrated react-native-get-random-values for crypto API polyfill + - Implemented NDKMobilePrivateKeySigner for key operations + - Added proper key format handling (hex, nsec) + - Created secure key generation functionality + - Built robust error handling for cryptographic operations + +3. Programs Testing Component: + - Developed dual-purpose interface for Database and Nostr testing + - Implemented login system with key generation and secure storage + - Built event creation interface with multiple event kinds + - Added event querying and display functionality + - Created detailed event inspection with tag visualization + - Added relay status monitoring +4. Database Schema Enforcement: - Added CHECK constraints for equipment types - Added CHECK constraints for exercise types - Added CHECK constraints for categories - Proper handling of foreign key constraints - -2. Input Validation: +5. Input Validation: - Equipment options: bodyweight, barbell, dumbbell, kettlebell, machine, cable, other - Exercise types: strength, cardio, bodyweight - Categories: Push, Pull, Legs, Core - Difficulty levels: beginner, intermediate, advanced - Movement patterns: push, pull, squat, hinge, carry, rotation - -3. Error Handling: +6. Error Handling: - Added SQLite error type definitions - Improved error propagation in LibraryService - Added transaction rollback on constraint violations - -4. Database Services: +7. Database Services: - Added EventCache service for Nostr events - Improved ExerciseService with transaction awareness - Added DevSeederService for development data - Enhanced error handling and logging - -5. Workout State Management with Zustand: +8. Workout State Management with Zustand: - Implemented selector pattern for performance optimization - Added module-level timer references for background operation - Created workout persistence with auto-save functionality - Developed state recovery for crash protection - Added support for future Nostr integration - Implemented workout minimization for multi-tasking - -6. Template Details UI Architecture: +9. Template Details UI Architecture: - Implemented MaterialTopTabNavigator for content organization - Created screen-specific components for each tab - Developed conditional rendering based on template source diff --git a/lib/initNDK.ts b/lib/initNDK.ts new file mode 100644 index 0000000..4ec49ad --- /dev/null +++ b/lib/initNDK.ts @@ -0,0 +1,78 @@ +// lib/initNDK.ts +import 'react-native-get-random-values'; // This must be the first import +import NDK, { NDKCacheAdapterSqlite } from '@nostr-dev-kit/ndk-mobile'; +import * as SecureStore from 'expo-secure-store'; +import { NDKMobilePrivateKeySigner } from './mobile-signer'; + +// Constants for SecureStore +const PRIVATE_KEY_STORAGE_KEY = 'nostr_privkey'; + +// Default relays +const DEFAULT_RELAYS = [ + 'wss://powr.duckdns.org', + 'wss://relay.damus.io', + 'wss://relay.nostr.band', + 'wss://nos.lol' +]; + +export async function initializeNDK() { + console.log('Initializing NDK with mobile adapter...'); + + // Create a mobile-specific cache adapter with a valid maxSize + // The error shows maxSize must be greater than 0 + const cacheAdapter = new NDKCacheAdapterSqlite('powr', 1000); // Use 1000 as maxSize + + // Initialize NDK with mobile-specific options + const ndk = new NDK({ + cacheAdapter, + explicitRelayUrls: DEFAULT_RELAYS, + enableOutboxModel: true, + clientName: 'powr', + }); + + // Initialize cache adapter + await cacheAdapter.initialize(); + + // Connect to relays + await ndk.connect(); + + // Set up relay status tracking + const relayStatus: Record = {}; + DEFAULT_RELAYS.forEach(url => { + relayStatus[url] = 'connecting'; + + const relay = ndk.pool.getRelay(url); + if (relay) { + relay.on('connect', () => { + console.log(`Connected to relay: ${url}`); + relayStatus[url] = 'connected'; + }); + + relay.on('disconnect', () => { + console.log(`Disconnected from relay: ${url}`); + relayStatus[url] = 'disconnected'; + }); + } + }); + + // Check for saved private key + try { + const privateKey = await SecureStore.getItemAsync(PRIVATE_KEY_STORAGE_KEY); + if (privateKey) { + console.log('[NDK] Found saved private key, initializing signer'); + + // Create mobile-specific signer with private key + const signer = new NDKMobilePrivateKeySigner(privateKey); + ndk.signer = signer; + + // Log success + console.log('[NDK] Signer initialized successfully'); + } + } catch (error) { + console.error('[NDK] Error initializing with saved key:', error); + // Remove invalid key + await SecureStore.deleteItemAsync(PRIVATE_KEY_STORAGE_KEY); + } + + return { ndk, relayStatus }; +} \ No newline at end of file diff --git a/lib/mobile-signer.ts b/lib/mobile-signer.ts index c5f2ec8..6b215a9 100644 --- a/lib/mobile-signer.ts +++ b/lib/mobile-signer.ts @@ -1,11 +1,9 @@ // lib/mobile-signer.ts -import '../lib/crypto-polyfill'; // Import crypto polyfill first +import 'react-native-get-random-values'; // First import - most important! import * as Crypto from 'expo-crypto'; -import * as Random from 'expo-random'; -import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; +import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk-mobile'; // Import from ndk-mobile import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import * as nostrTools from 'nostr-tools'; -import { setupCryptoPolyfill } from './crypto-polyfill'; /** * A custom signer implementation for React Native @@ -47,24 +45,12 @@ export class NDKMobilePrivateKeySigner extends NDKPrivateKeySigner { * Generate a new Nostr keypair * Uses Expo's crypto functions directly instead of relying on polyfills */ -// Add this to your generateKeyPair function export function generateKeyPair() { try { - // Ensure crypto polyfill is set up - if (typeof setupCryptoPolyfill === 'function') { - setupCryptoPolyfill(); - } - let privateKeyBytes; - // Try expo-crypto first since expo-random is deprecated - try { - privateKeyBytes = Crypto.getRandomBytes(32); - } catch (e) { - console.warn('expo-crypto failed:', e); - // Fallback to expo-random as last resort - privateKeyBytes = Random.getRandomBytes(32); - } + // Try expo-crypto + privateKeyBytes = Crypto.getRandomBytes(32); const privateKey = bytesToHex(privateKeyBytes); @@ -72,6 +58,7 @@ export function generateKeyPair() { const publicKey = nostrTools.getPublicKey(privateKeyBytes); // Encode keys in bech32 format + // Fixed the parameter types for nsecEncode - it needs Uint8Array not string const nsec = nostrTools.nip19.nsecEncode(privateKeyBytes); const npub = nostrTools.nip19.npubEncode(publicKey); diff --git a/lib/stores/ndk.ts b/lib/stores/ndk.ts index 0de1747..54dd582 100644 --- a/lib/stores/ndk.ts +++ b/lib/stores/ndk.ts @@ -1,12 +1,15 @@ -// stores/ndk.ts -import '@/lib/crypto-polyfill'; // Import crypto polyfill first +// lib/stores/ndk.ts +// IMPORTANT: 'react-native-get-random-values' must be the first import to ensure +// proper crypto polyfill application before other libraries are loaded +import 'react-native-get-random-values'; +import { Platform } from 'react-native'; import { create } from 'zustand'; -import NDK, { NDKFilter, NDKEvent as NDKEventBase } from '@nostr-dev-kit/ndk'; -import { NDKUser } from '@nostr-dev-kit/ndk-mobile'; -import { NDKEvent } from '@nostr-dev-kit/ndk-mobile'; +// Using standard NDK types but importing NDKEvent from ndk-mobile for compatibility +import NDK, { NDKFilter } from '@nostr-dev-kit/ndk'; +import { NDKEvent, NDKUser } from '@nostr-dev-kit/ndk-mobile'; import * as SecureStore from 'expo-secure-store'; +import * as Crypto from 'expo-crypto'; import { NDKMobilePrivateKeySigner, generateKeyPair } from '@/lib/mobile-signer'; -import { setupCryptoPolyfill } from '@/lib/crypto-polyfill'; // Constants for SecureStore const PRIVATE_KEY_STORAGE_KEY = 'nostr_privkey'; @@ -64,6 +67,14 @@ export const useNDKStore = create((set, get) => }); set({ relayStatus }); + // IMPORTANT: Due to the lack of an Expo config plugin for ndk-mobile, + // we're using a standard NDK initialization approach rather than trying to use + // ndk-mobile's native modules, which require a custom build. + // + // When an Expo plugin becomes available for ndk-mobile, we can remove this + // fallback approach and use the initializeNDK() function directly. + console.log('[NDK] Using standard NDK initialization'); + // Initialize NDK with relays const ndk = new NDK({ explicitRelayUrls: DEFAULT_RELAYS @@ -243,7 +254,15 @@ export const useNDKStore = create((set, get) => } }, - // In your publishEvent function in ndk.ts: + // IMPORTANT: This method uses monkey patching to make event signing work + // in React Native environment. This is necessary because the underlying + // Nostr libraries expect Web Crypto API to be available. + // + // When ndk-mobile gets proper Expo support, this function can be simplified to: + // 1. Create the event + // 2. Call event.sign() directly + // 3. Call event.publish() + // without the monkey patching code. publishEvent: async (kind: number, content: string, tags: string[][]) => { try { const { ndk, isAuthenticated, currentUser } = get(); @@ -256,21 +275,24 @@ export const useNDKStore = create((set, get) => throw new Error('Not authenticated'); } - // Define custom functions we'll use to override crypto - const customRandomBytes = (length: number): Uint8Array => { - console.log('Using custom randomBytes in event signing'); - // Use type assertion to avoid TypeScript error - return (Crypto as any).getRandomBytes(length); - }; - // Create event + console.log('Creating event...'); const event = new NDKEvent(ndk); event.kind = kind; event.content = content; event.tags = tags; - // Direct monkey-patching approach + // MONKEY PATCHING APPROACH: + // This is needed because the standard NDK doesn't properly work with + // React Native's crypto implementation. When ndk-mobile adds proper Expo + // support, this can be removed. try { + // Define custom function for random bytes generation + const customRandomBytes = (length: number): Uint8Array => { + console.log('Using custom randomBytes in event signing'); + return (Crypto as any).getRandomBytes(length); + }; + // Try to find and override the randomBytes function const nostrTools = require('nostr-tools'); const nobleHashes = require('@noble/hashes/utils');