diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 94aa780..66f8d48 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -1,7 +1,7 @@
// app/(tabs)/index.tsx
import React, { useState, useEffect, useCallback } from 'react';
-import { ScrollView, View, TouchableOpacity } from 'react-native'
-import { useFocusEffect } from '@react-navigation/native';
+import { ScrollView, View, TouchableOpacity, Platform } from 'react-native'
+import { useFocusEffect, useTheme } from '@react-navigation/native';
import { router } from 'expo-router'
import {
AlertDialog,
@@ -15,8 +15,6 @@ import {
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { TabScreen } from '@/components/layout/TabScreen'
import Header from '@/components/Header'
-import HomeWorkout from '@/components/workout/HomeWorkout'
-import FavoriteTemplate from '@/components/workout/FavoriteTemplate'
import { useWorkoutStore } from '@/stores/workoutStore'
import { Text } from '@/components/ui/text'
import { getRandomWorkoutTitle } from '@/utils/workoutTitles'
@@ -63,6 +61,8 @@ export default function WorkoutScreen() {
endWorkout
} = useWorkoutStore();
+ const theme = useTheme();
+
useFocusEffect(
useCallback(() => {
loadFavorites();
@@ -241,7 +241,10 @@ export default function WorkoutScreen() {
onPress={() => console.log('Open notifications')}
>
-
+
diff --git a/app/(tabs)/library/programs.tsx b/app/(tabs)/library/programs.tsx
index 7d0e413..76517cc 100644
--- a/app/(tabs)/library/programs.tsx
+++ b/app/(tabs)/library/programs.tsx
@@ -161,74 +161,28 @@ export default function ProgramsScreen() {
console.warn('[Database Reset] Error clearing keys:', keyError);
}
- // Define explicit type for tables
- let tables: { name: string }[] = [];
-
- // Try to get existing tables
- try {
- tables = await db.getAllAsync<{ name: string }>(
- "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
- );
- console.log(`[Database Reset] Found ${tables.length} tables to drop`);
- } catch (tableListError) {
- console.warn('[Database Reset] Error listing tables:', tableListError);
- // Initialize with empty array if query fails
- tables = [];
- }
-
- // Drop tables one by one
- for (const table of tables) {
- try {
- await db.execAsync(`DROP TABLE IF EXISTS "${table.name}";`);
- console.log(`[Database Reset] Dropped table: ${table.name}`);
- } catch (dropError) {
- console.error(`[Database Reset] Error dropping table ${table.name}:`, dropError);
- }
- }
-
- // Use a delay to allow any pending operations to complete
- await new Promise(resolve => setTimeout(resolve, 1000));
-
- // Create a completely new database instance instead of using the existing one
- // This will bypass the "Access to closed resource" issue
- Alert.alert(
- 'Database Tables Dropped',
- 'All database tables have been dropped. The app needs to be restarted to complete the reset process.',
- [
- {
- text: 'Restart Now',
- style: 'destructive',
- onPress: () => {
- // In a production app, you would use something like RN's DevSettings.reload()
- // For Expo, we'll suggest manual restart
- Alert.alert(
- 'Manual Restart Required',
- 'Please completely close the app and reopen it to finish the database reset.',
- [{ text: 'OK', style: 'default' }]
- );
- }
- }
- ]
- );
+ // Use the new complete reset method
+ await schema.resetDatabaseCompletely(db);
+ // Show success message
setTestResults({
success: true,
- message: 'Database tables dropped. Please restart the app to complete the reset.'
+ message: 'Database completely reset. The app will need to be restarted to see the changes.'
});
+ // Recommend a restart
+ Alert.alert(
+ 'Database Reset Complete',
+ 'The database has been completely reset. Please restart the app for the changes to take effect fully.',
+ [{ text: 'OK', style: 'default' }]
+ );
+
} catch (error) {
console.error('[Database Reset] Error resetting database:', error);
setTestResults({
success: false,
message: error instanceof Error ? error.message : 'Unknown error during database reset'
});
-
- // Still recommend a restart since the database might be in an inconsistent state
- Alert.alert(
- 'Database Reset Error',
- 'There was an error during database reset. Please restart the app and try again.',
- [{ text: 'OK', style: 'default' }]
- );
}
};
diff --git a/components/DatabaseProvider.tsx b/components/DatabaseProvider.tsx
index 8bb624e..80f50ea 100644
--- a/components/DatabaseProvider.tsx
+++ b/components/DatabaseProvider.tsx
@@ -36,6 +36,32 @@ interface DatabaseProviderProps {
children: React.ReactNode;
}
+// Add a DelayedInitializer component to ensure database is fully ready
+const DelayedInitializer: React.FC<{children: React.ReactNode}> = ({children}) => {
+ const [ready, setReady] = React.useState(false);
+
+ React.useEffect(() => {
+ // Small delay to ensure database is fully ready
+ const timer = setTimeout(() => {
+ console.log('[Database] Delayed initialization complete');
+ setReady(true);
+ }, 300); // 300ms delay should be sufficient
+
+ return () => clearTimeout(timer);
+ }, []);
+
+ if (!ready) {
+ return (
+
+
+ Finishing initialization...
+
+ );
+ }
+
+ return <>{children}>;
+};
+
export function DatabaseProvider({ children }: DatabaseProviderProps) {
const [isReady, setIsReady] = React.useState(false);
const [error, setError] = React.useState(null);
@@ -63,11 +89,19 @@ export function DatabaseProvider({ children }: DatabaseProviderProps) {
React.useEffect(() => {
async function initDatabase() {
try {
+ console.log('[DB] Starting database initialization...');
+
+ // Add a small delay to ensure system is ready (especially on Android)
+ await new Promise(resolve => setTimeout(resolve, 200));
+
console.log('[DB] Opening database...');
const db = openDatabaseSync('powr.db');
console.log('[DB] Creating schema...');
await schema.createTables(db);
+
+ // Explicitly check for critical tables after schema creation
+ await schema.ensureCriticalTablesExist(db);
// Initialize services
console.log('[DB] Initializing services...');
@@ -101,7 +135,12 @@ export function DatabaseProvider({ children }: DatabaseProviderProps) {
// Seed development database
if (__DEV__) {
console.log('[DB] Seeding development database...');
- await devSeeder.seedDatabase();
+ try {
+ await devSeeder.seedDatabase();
+ } catch (seedError) {
+ console.error('[DB] Error seeding database:', seedError);
+ // Continue even if seeding fails
+ }
await logDatabaseInfo();
}
@@ -137,7 +176,9 @@ export function DatabaseProvider({ children }: DatabaseProviderProps) {
return (
- {children}
+
+ {children}
+
);
diff --git a/components/Header.tsx b/components/Header.tsx
index 9ee0b6d..d8160ff 100644
--- a/components/Header.tsx
+++ b/components/Header.tsx
@@ -77,7 +77,7 @@ export default function Header({
onPress={() => {}}
>
-
+
{/* Notification indicator - you can conditionally render this */}
diff --git a/components/RelayManagement.tsx b/components/RelayManagement.tsx
index 0f96772..95d37bb 100644
--- a/components/RelayManagement.tsx
+++ b/components/RelayManagement.tsx
@@ -1,23 +1,31 @@
+// components/RelayManagement.tsx
import React, { useEffect, useState } from 'react';
-import { View, Text, FlatList, TouchableOpacity, Modal, ActivityIndicator } from 'react-native';
+import { View, Text, FlatList, TouchableOpacity, Modal, ActivityIndicator, TextInput, SafeAreaView, KeyboardAvoidingView, Platform } from 'react-native';
import { useRelayStore } from '@/lib/stores/relayStore';
import { Switch } from '@/components/ui/switch';
import { Button } from '@/components/ui/button';
-import { X } from 'lucide-react-native';
+import { X, RefreshCw, PlusCircle, AlertTriangle } from 'lucide-react-native';
import { RelayWithStatus } from '@/lib/db/services/RelayService';
-// Define proper interface for component props
interface RelayManagementProps {
isVisible: boolean;
onClose: () => void;
}
-// Simple RelayManagement component
export default function RelayManagement({ isVisible, onClose }: RelayManagementProps) {
const relays = useRelayStore(state => state.relays);
const isLoading = useRelayStore(state => state.isLoading);
+ const isSaving = useRelayStore(state => state.isSaving);
const loadRelays = useRelayStore(state => state.loadRelays);
const updateRelay = useRelayStore(state => state.updateRelay);
+ const applyChanges = useRelayStore(state => state.applyChanges);
+ const resetToDefaults = useRelayStore(state => state.resetToDefaults);
+ const addRelay = useRelayStore(state => state.addRelay);
+
+ const [newRelayUrl, setNewRelayUrl] = useState('');
+ const [showAddRelay, setShowAddRelay] = useState(false);
+ const [confirmReset, setConfirmReset] = useState(false);
+ const [hasUnappliedChanges, setHasUnappliedChanges] = useState(false);
useEffect(() => {
if (isVisible) {
@@ -25,7 +33,52 @@ export default function RelayManagement({ isVisible, onClose }: RelayManagementP
}
}, [isVisible]);
- // Status indicator color with proper typing
+ // Track if there are unapplied changes
+ const handleRelayUpdate = (url: string, changes: Partial) => {
+ updateRelay(url, changes);
+ setHasUnappliedChanges(true);
+ };
+
+ // Handle applying changes
+ const handleApplyChanges = async () => {
+ const success = await applyChanges();
+ if (success) {
+ setHasUnappliedChanges(false);
+ // Success notification could be added here
+ }
+ };
+
+ // Add new relay
+ const handleAddRelay = async () => {
+ if (!newRelayUrl || !newRelayUrl.startsWith('wss://')) {
+ alert('Please enter a valid relay URL starting with wss://');
+ return;
+ }
+
+ try {
+ await addRelay(newRelayUrl);
+ setNewRelayUrl('');
+ setShowAddRelay(false);
+ setHasUnappliedChanges(true);
+ } catch (error) {
+ alert(`Failed to add relay: ${error instanceof Error ? error.message : String(error)}`);
+ }
+ };
+
+ // Reset to defaults with confirmation
+ const handleResetToDefaults = async () => {
+ if (confirmReset) {
+ await resetToDefaults();
+ setConfirmReset(false);
+ setHasUnappliedChanges(true);
+ } else {
+ setConfirmReset(true);
+ // Auto-reset the confirmation after 3 seconds
+ setTimeout(() => setConfirmReset(false), 3000);
+ }
+ };
+
+ // Status indicator color
const getStatusColor = (status: string): string => {
switch (status) {
case 'connected': return '#10b981'; // Green
@@ -36,58 +89,49 @@ export default function RelayManagement({ isVisible, onClose }: RelayManagementP
}
};
- // Render a relay item with proper typing
+ // Render a relay item
const renderRelayItem = ({ item }: { item: RelayWithStatus }) => (
-
-
-
-
- {item.url}
+
+
+
+
+ {item.url}
-
+
{item.status}
-
-
- Read
+
+
+ Read
updateRelay(item.url, { read: !item.read })}
+ onCheckedChange={() => handleRelayUpdate(item.url, { read: !item.read })}
/>
-
- Write
+
+ Write
updateRelay(item.url, { write: !item.write })}
+ onCheckedChange={() => handleRelayUpdate(item.url, { write: !item.write })}
/>
);
+ // Main Render
return (
-
-
-
- Manage Relays
+
+
+ {/* Header */}
+
+ Relay Management
-
+
- {isLoading ? (
-
-
- Loading relays...
-
- ) : (
- <>
- item.url}
- renderItem={renderRelayItem}
- style={{ maxHeight: '70%' }}
- />
-
-
-
-
-
+ {/* Content */}
+
+ {isLoading ? (
+
+
+ Loading relays...
- >
- )}
-
-
+ ) : (
+ <>
+ {/* Summary */}
+
+
+ {relays.length} Relays ({relays.filter(r => r.status === 'connected').length} Connected)
+
+
+
+
+
+
+ {/* Relay List */}
+ {relays.length === 0 ? (
+
+ No relays configured
+
+
+ ) : (
+ item.url}
+ ListEmptyComponent={
+
+ No relays found
+
+ }
+ contentContainerStyle={{ paddingBottom: showAddRelay ? 180 : 100 }}
+ />
+ )}
+
+ {/* Add Relay Form */}
+ {showAddRelay && (
+
+ Add New Relay
+
+
+
+
+
+
+ )}
+ >
+ )}
+
+
+ {/* Footer */}
+
+
+
+
+
+
+
+
+
+
+
);
}
\ No newline at end of file
diff --git a/lib/db/schema.ts b/lib/db/schema.ts
index ff02c9b..0b8faeb 100644
--- a/lib/db/schema.ts
+++ b/lib/db/schema.ts
@@ -2,7 +2,7 @@
import { SQLiteDatabase } from 'expo-sqlite';
import { Platform } from 'react-native';
-export const SCHEMA_VERSION = 3; // Incrementing to add the relays table
+export const SCHEMA_VERSION = 6; // Incremented from 5 to 6 for relay table removal
class Schema {
private async getCurrentVersion(db: SQLiteDatabase): Promise {
@@ -44,9 +44,17 @@ class Schema {
const currentVersion = await this.getCurrentVersion(db);
console.log(`[Schema] Current version: ${currentVersion}, Target version: ${SCHEMA_VERSION}`);
- // If we already have the current version, no need to recreate tables
+ // If we already have the current version, check for missing tables
if (currentVersion === SCHEMA_VERSION) {
- console.log(`[Schema] Database already at version ${SCHEMA_VERSION}`);
+ console.log(`[Schema] Database already at version ${SCHEMA_VERSION}, checking for missing tables`);
+ await this.ensureCriticalTablesExist(db);
+ return;
+ }
+
+ // Handle higher version numbers - especially important for Android
+ if (currentVersion > SCHEMA_VERSION) {
+ console.log(`[Schema] Database version ${currentVersion} is newer than target ${SCHEMA_VERSION}, checking for missing tables`);
+ await this.ensureCriticalTablesExist(db);
return;
}
@@ -89,6 +97,129 @@ class Schema {
}
}
+ // Add this method to check for and create critical tables
+ async ensureCriticalTablesExist(db: SQLiteDatabase): Promise {
+ try {
+ console.log('[Schema] Checking for missing critical tables...');
+
+ // Check if workouts table exists
+ const workoutsTableExists = await db.getFirstAsync<{ count: number }>(
+ `SELECT count(*) as count FROM sqlite_master
+ WHERE type='table' AND name='workouts'`
+ );
+
+ if (!workoutsTableExists || workoutsTableExists.count === 0) {
+ console.log('[Schema] Creating missing workouts tables...');
+
+ // Create workouts table
+ await db.execAsync(`
+ CREATE TABLE IF NOT EXISTS workouts (
+ id TEXT PRIMARY KEY,
+ title TEXT NOT NULL,
+ type TEXT NOT NULL,
+ start_time INTEGER NOT NULL,
+ end_time INTEGER,
+ is_completed BOOLEAN NOT NULL DEFAULT 0,
+ created_at INTEGER NOT NULL,
+ updated_at INTEGER NOT NULL,
+ template_id TEXT,
+ nostr_event_id TEXT,
+ share_status TEXT NOT NULL DEFAULT 'local',
+ source TEXT NOT NULL DEFAULT 'local',
+ total_volume REAL,
+ total_reps INTEGER,
+ notes TEXT
+ );
+ CREATE INDEX IF NOT EXISTS idx_workouts_start_time ON workouts(start_time);
+ CREATE INDEX IF NOT EXISTS idx_workouts_template_id ON workouts(template_id);
+ `);
+
+ // Create workout_exercises table
+ await db.execAsync(`
+ CREATE TABLE IF NOT EXISTS workout_exercises (
+ id TEXT PRIMARY KEY,
+ workout_id TEXT NOT NULL,
+ exercise_id TEXT NOT NULL,
+ display_order INTEGER NOT NULL,
+ notes TEXT,
+ created_at INTEGER NOT NULL,
+ updated_at INTEGER NOT NULL,
+ FOREIGN KEY(workout_id) REFERENCES workouts(id) ON DELETE CASCADE
+ );
+ CREATE INDEX IF NOT EXISTS idx_workout_exercises_workout_id ON workout_exercises(workout_id);
+ `);
+
+ // Create workout_sets table
+ await db.execAsync(`
+ CREATE TABLE IF NOT EXISTS workout_sets (
+ id TEXT PRIMARY KEY,
+ workout_exercise_id TEXT NOT NULL,
+ type TEXT NOT NULL DEFAULT 'normal',
+ weight REAL,
+ reps INTEGER,
+ rpe REAL,
+ duration INTEGER,
+ is_completed BOOLEAN NOT NULL DEFAULT 0,
+ completed_at INTEGER,
+ created_at INTEGER NOT NULL,
+ updated_at INTEGER NOT NULL,
+ FOREIGN KEY(workout_exercise_id) REFERENCES workout_exercises(id) ON DELETE CASCADE
+ );
+ CREATE INDEX IF NOT EXISTS idx_workout_sets_exercise_id ON workout_sets(workout_exercise_id);
+ `);
+ }
+
+ // Check if templates table exists
+ const templatesTableExists = await db.getFirstAsync<{ count: number }>(
+ `SELECT count(*) as count FROM sqlite_master
+ WHERE type='table' AND name='templates'`
+ );
+
+ if (!templatesTableExists || templatesTableExists.count === 0) {
+ console.log('[Schema] Creating missing templates tables...');
+
+ // Create templates table
+ await db.execAsync(`
+ CREATE TABLE IF NOT EXISTS templates (
+ id TEXT PRIMARY KEY,
+ title TEXT NOT NULL,
+ type TEXT NOT NULL,
+ description TEXT,
+ created_at INTEGER NOT NULL,
+ updated_at INTEGER NOT NULL,
+ nostr_event_id TEXT,
+ source TEXT NOT NULL DEFAULT 'local',
+ parent_id TEXT
+ );
+ CREATE INDEX IF NOT EXISTS idx_templates_updated_at ON templates(updated_at);
+ `);
+
+ // Create template_exercises table
+ await db.execAsync(`
+ CREATE TABLE IF NOT EXISTS template_exercises (
+ id TEXT PRIMARY KEY,
+ template_id TEXT NOT NULL,
+ exercise_id TEXT NOT NULL,
+ display_order INTEGER NOT NULL,
+ target_sets INTEGER,
+ target_reps INTEGER,
+ target_weight REAL,
+ notes TEXT,
+ created_at INTEGER NOT NULL,
+ updated_at INTEGER NOT NULL,
+ FOREIGN KEY(template_id) REFERENCES templates(id) ON DELETE CASCADE
+ );
+ CREATE INDEX IF NOT EXISTS idx_template_exercises_template_id ON template_exercises(template_id);
+ `);
+ }
+
+ console.log('[Schema] Critical tables check complete');
+ } catch (error) {
+ console.error('[Schema] Error ensuring critical tables exist:', error);
+ throw error;
+ }
+ }
+
private async dropAllTables(db: SQLiteDatabase): Promise {
try {
console.log('[Schema] Getting list of tables to drop...');
@@ -288,20 +419,6 @@ class Schema {
CREATE INDEX idx_favorites_content_id ON favorites(content_id);
`);
- // Create relays table
- console.log('[Schema] Creating relays table...');
- await db.execAsync(`
- CREATE TABLE relays (
- url TEXT PRIMARY KEY,
- read INTEGER NOT NULL DEFAULT 1,
- write INTEGER NOT NULL DEFAULT 1,
- priority INTEGER,
- created_at INTEGER NOT NULL,
- updated_at INTEGER NOT NULL
- );
- CREATE INDEX idx_relays_priority ON relays(priority DESC);
- `);
-
// === NEW TABLES === //
// Create workouts table
@@ -427,25 +544,36 @@ class Schema {
}
}
- async resetDatabase(db: SQLiteDatabase): Promise {
+ async resetDatabaseCompletely(db: SQLiteDatabase): Promise {
if (!__DEV__) {
console.log('[Schema] Database reset is only available in development mode');
return;
}
try {
- console.log('[Schema] Resetting database...');
+ console.log('[Schema] Completely resetting database...');
- // Clear schema_version to force recreation of all tables
- await db.execAsync('DROP TABLE IF EXISTS schema_version');
- console.log('[Schema] Dropped schema_version table');
+ // Get all tables
+ const tables = await db.getAllAsync<{ name: string }>(
+ "SELECT name FROM sqlite_master WHERE type='table'"
+ );
- // Now create tables from scratch
+ // Drop all tables including schema_version
+ for (const { name } of tables) {
+ try {
+ await db.execAsync(`DROP TABLE IF EXISTS ${name}`);
+ console.log(`[Schema] Dropped table: ${name}`);
+ } catch (dropError) {
+ console.error(`[Schema] Error dropping table ${name}:`, dropError);
+ }
+ }
+
+ // Create tables from scratch
await this.createTables(db);
- console.log('[Schema] Database reset complete');
+ console.log('[Schema] Database completely reset');
} catch (error) {
- console.error('[Schema] Error resetting database:', error);
+ console.error('[Schema] Error completely resetting database:', error);
throw error;
}
}
diff --git a/lib/db/services/DevSeederService.ts b/lib/db/services/DevSeederService.ts
index 6dd5dce..8889221 100644
--- a/lib/db/services/DevSeederService.ts
+++ b/lib/db/services/DevSeederService.ts
@@ -8,6 +8,7 @@ import { logDatabaseInfo } from '../debug';
import { mockExerciseEvents, convertNostrToExercise } from '../../mocks/exercises';
import { DbService } from '../db-service';
import NDK, { NDKEvent } from '@nostr-dev-kit/ndk-mobile';
+import { NostrEvent } from '@/types/nostr'; // Assuming you have this type defined
export class DevSeederService {
private db: SQLiteDatabase;
@@ -181,6 +182,71 @@ export class DevSeederService {
}
}
+ /**
+ * Seed the database with real events from Nostr relays instead of mock data
+ * @param filter The filter to use when fetching events from relays
+ * @param limit Maximum number of events to seed (optional)
+ */
+ async seedFromNostr(filter: any, limit?: number) {
+ if (!this.ndk) {
+ console.log('NDK not available for seeding from Nostr');
+ return;
+ }
+
+ try {
+ console.log(`Seeding from Nostr with filter:`, filter);
+
+ // Fetch events from relays
+ const events = await this.ndk.fetchEvents(filter);
+
+ console.log(`Found ${events.size} events on Nostr`);
+
+ // Convert to array and limit if needed
+ const eventsArray = Array.from(events);
+ const eventsToProcess = limit ? eventsArray.slice(0, limit) : eventsArray;
+
+ // Process each event individually
+ let successCount = 0;
+
+ for (const ndkEvent of eventsToProcess) {
+ try {
+ // Convert NDKEvent to your NostrEvent format
+ const nostrEvent: NostrEvent = {
+ id: ndkEvent.id || '',
+ pubkey: ndkEvent.pubkey || '',
+ created_at: ndkEvent.created_at || 0, // Set a default value of 0 if undefined
+ kind: ndkEvent.kind || 0,
+ tags: ndkEvent.tags || [],
+ content: ndkEvent.content || '',
+ sig: ndkEvent.sig || ''
+ };
+
+ // Cache the event
+ if (this.eventCache) {
+ await this.eventCache.setEvent(nostrEvent, true);
+ }
+
+ // Process based on kind
+ if (ndkEvent.kind === 33401) { // Exercise
+ const exercise = convertNostrToExercise(nostrEvent);
+ await this.exerciseService.createExercise(exercise, true);
+ successCount++;
+ }
+ // Add more event type processing here as needed
+
+ } catch (error) {
+ console.error(`Error processing Nostr event:`, error);
+ // Continue with next event
+ }
+ }
+
+ console.log(`Successfully seeded ${successCount} items from Nostr`);
+
+ } catch (error) {
+ console.error('Error seeding from Nostr:', error);
+ }
+ }
+
async clearDatabase() {
if (!__DEV__) return;
diff --git a/lib/db/services/RelayService.ts b/lib/db/services/RelayService.ts
index 52d11a8..1f5712e 100644
--- a/lib/db/services/RelayService.ts
+++ b/lib/db/services/RelayService.ts
@@ -1,16 +1,5 @@
// lib/db/services/RelayService.ts
-import { SQLiteDatabase } from 'expo-sqlite';
-import { NDKCommon, NDKRelayCommon, safeAddRelay, safeRemoveRelay } from '@/types/ndk-common';
-
-// Status constants to match NDK implementations
-const NDK_RELAY_STATUS = {
- CONNECTING: 0,
- CONNECTED: 1,
- DISCONNECTING: 2,
- DISCONNECTED: 3,
- RECONNECTING: 4,
- AUTH_REQUIRED: 5
-};
+import NDK, { NDKRelay } from '@nostr-dev-kit/ndk-mobile';
// Default relays to use when none are configured
export const DEFAULT_RELAYS = [
@@ -26,8 +15,8 @@ export interface RelayConfig {
read: boolean;
write: boolean;
priority?: number;
- created_at: number;
- updated_at: number;
+ created_at?: number;
+ updated_at?: number;
}
export interface RelayWithStatus extends RelayConfig {
@@ -36,141 +25,106 @@ export interface RelayWithStatus extends RelayConfig {
/**
* Service for managing Nostr relays
+ * This implementation uses NDK's built-in relay management capabilities
*/
export class RelayService {
- private db: SQLiteDatabase;
- private ndk: NDKCommon | null = null;
+ private ndk: NDK | null = null;
private debug: boolean = false;
- constructor(db: SQLiteDatabase) {
- this.db = db;
+ constructor() {
+ // No database required anymore
}
enableDebug() {
- this.debug = true;
- console.log('[RelayService] Debug mode enabled');
- }
+ this.debug = true;
+ console.log('[RelayService] Debug mode enabled');
+ }
- private logDebug(message: string, ...args: any[]) {
- if (this.debug) {
- console.log(`[RelayService Debug] ${message}`, ...args);
- }
+ private logDebug(message: string, ...args: any[]) {
+ if (this.debug) {
+ console.log(`[RelayService Debug] ${message}`, ...args);
}
+ }
/**
* Set NDK instance for relay operations
*/
- setNDK(ndk: NDKCommon) {
+ setNDK(ndk: NDK) {
this.ndk = ndk;
console.log('[RelayService] NDK instance set');
}
- /**
- * Get all relays from database
- */
- async getAllRelays(): Promise {
- try {
- const relays = await this.db.getAllAsync(
- 'SELECT url, read, write, priority, created_at, updated_at FROM relays ORDER BY priority DESC, created_at DESC'
- );
-
- console.log(`[RelayService] Found ${relays.length} relays in database`);
-
- return relays.map(relay => ({
- ...relay,
- read: Boolean(relay.read),
- write: Boolean(relay.write)
- }));
- } catch (error) {
- console.error('[RelayService] Error getting relays:', error);
- return [];
- }
- }
-
/**
* Get all relays with their current connection status
*/
async getAllRelaysWithStatus(): Promise {
try {
- const relays = await this.getAllRelays();
-
if (!this.ndk) {
- console.warn('[RelayService] NDK not initialized, returning relays with disconnected status');
- return relays.map(relay => ({
- ...relay,
- status: 'disconnected'
- }));
+ console.warn('[RelayService] NDK not initialized, returning empty relay list');
+ return [];
}
- // Log the relays in the NDK pool for debugging
- console.log('[RelayService] Checking status for relays. Current NDK pool:');
- this.ndk.pool.relays.forEach((ndkRelay, url) => {
- console.log(` - ${url}: status=${ndkRelay.status}`);
+ const relays: RelayWithStatus[] = [];
+
+ // Get relays directly from NDK pool
+ this.ndk.pool.relays.forEach((relay, url) => {
+ const status = this.getRelayStatus(relay);
+
+ relays.push({
+ url,
+ read: relay.read ?? true,
+ write: relay.write ?? true,
+ status,
+ priority: 0, // Default priority
+ created_at: Date.now(),
+ updated_at: Date.now()
+ });
});
- return relays.map(relay => {
- const status = this.getRelayStatus(relay);
- console.log(`[RelayService] Status for relay ${relay.url}: ${status}`);
-
- return {
- ...relay,
- status
- };
- });
+ console.log(`[RelayService] Found ${relays.length} relays in NDK pool`);
+ return relays;
} catch (error) {
console.error('[RelayService] Error getting relays with status:', error);
return [];
}
}
- private normalizeRelayUrl(url: string): string {
- // Remove trailing slash if present
- return url.replace(/\/$/, '');
- }
-
/**
- * Add a new relay to the database
+ * Add a new relay to NDK
*/
- async addRelay(url: string, read = true, write = true, priority?: number): Promise {
+ async addRelay(url: string, read = true, write = true): Promise {
try {
- // Normalize the URL
- url = this.normalizeRelayUrl(url.trim());
+ if (!this.ndk) {
+ throw new Error('NDK not initialized');
+ }
+
+ // Normalize URL by removing trailing slash
+ url = url.replace(/\/$/, '');
// Validate URL format
if (!url.startsWith('wss://')) {
throw new Error('Relay URL must start with wss://');
}
- const now = Date.now();
+ console.log(`[RelayService] Adding relay ${url} with read=${read}, write=${write}`);
- // Check if relay already exists
- const existingRelay = await this.db.getFirstAsync<{ url: string }>(
- 'SELECT url FROM relays WHERE url = ?',
- [url]
- );
+ // Get or create the relay using the NDK pool
+ const relay = this.ndk.pool.getRelay(url, true);
- if (existingRelay) {
- console.log(`[RelayService] Relay ${url} already exists, updating instead`);
- return this.updateRelay(url, { read, write, priority });
+ // Set read/write permissions
+ relay.read = read;
+ relay.write = write;
+
+ // Connect to the relay if not already connected
+ if (!relay.connected) {
+ try {
+ await relay.connect();
+ } catch (connectError) {
+ console.warn(`[RelayService] Warning: Error connecting to relay ${url}:`, connectError);
+ // Continue even if connection fails - it will auto-reconnect
+ }
}
- // If no priority specified, make it higher than the current highest
- if (priority === undefined) {
- const highestPriority = await this.db.getFirstAsync<{ priority: number }>(
- 'SELECT MAX(priority) as priority FROM relays'
- );
-
- priority = ((highestPriority?.priority || 0) + 1);
- }
-
- console.log(`[RelayService] Adding relay ${url} with read=${read}, write=${write}, priority=${priority}`);
-
- // Add the relay
- await this.db.runAsync(
- 'INSERT INTO relays (url, read, write, priority, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
- [url, read ? 1 : 0, write ? 1 : 0, priority, now, now]
- );
-
console.log(`[RelayService] Successfully added relay ${url}`);
return true;
} catch (error) {
@@ -184,63 +138,30 @@ export class RelayService {
*/
async updateRelay(url: string, changes: Partial): Promise {
try {
- const now = Date.now();
-
- // Check if relay exists
- const existingRelay = await this.db.getFirstAsync<{ url: string }>(
- 'SELECT url FROM relays WHERE url = ?',
- [url]
- );
-
- if (!existingRelay) {
- console.log(`[RelayService] Relay ${url} does not exist, adding instead`);
- const read = changes.read !== undefined ? changes.read : true;
- const write = changes.write !== undefined ? changes.write : true;
- return this.addRelay(url, read, write, changes.priority);
+ if (!this.ndk) {
+ throw new Error('NDK not initialized');
}
- // Prepare update fields
- const updates: string[] = [];
- const params: any[] = [];
+ // Get the relay from NDK pool
+ const relay = this.ndk.pool.getRelay(url, false);
+ if (!relay) {
+ console.log(`[RelayService] Relay ${url} not found, adding instead`);
+ const read = changes.read !== undefined ? changes.read : true;
+ const write = changes.write !== undefined ? changes.write : true;
+ return this.addRelay(url, read, write);
+ }
+
+ // Update relay properties
if (changes.read !== undefined) {
- updates.push('read = ?');
- params.push(changes.read ? 1 : 0);
+ relay.read = changes.read;
}
if (changes.write !== undefined) {
- updates.push('write = ?');
- params.push(changes.write ? 1 : 0);
+ relay.write = changes.write;
}
- if (changes.priority !== undefined) {
- updates.push('priority = ?');
- params.push(changes.priority);
- }
-
- // Always update the updated_at timestamp
- updates.push('updated_at = ?');
- params.push(now);
-
- // Add the URL to the parameters
- params.push(url);
-
- console.log(`[RelayService] Updating relay ${url} with changes:`,
- Object.entries(changes)
- .filter(([key]) => ['read', 'write', 'priority'].includes(key))
- .map(([key, value]) => `${key}=${value}`)
- .join(', ')
- );
-
- // Execute update
- if (updates.length > 0) {
- await this.db.runAsync(
- `UPDATE relays SET ${updates.join(', ')} WHERE url = ?`,
- params
- );
- }
-
- console.log(`[RelayService] Successfully updated relay ${url}`);
+ console.log(`[RelayService] Updated relay ${url} with new settings`);
return true;
} catch (error) {
console.error('[RelayService] Error updating relay:', error);
@@ -249,130 +170,73 @@ export class RelayService {
}
/**
- * Remove a relay from the database
+ * Remove a relay from NDK
*/
async removeRelay(url: string): Promise {
try {
+ if (!this.ndk) {
+ throw new Error('NDK not initialized');
+ }
+
console.log(`[RelayService] Removing relay ${url}`);
- await this.db.runAsync('DELETE FROM relays WHERE url = ?', [url]);
- console.log(`[RelayService] Successfully removed relay ${url}`);
- return true;
+
+ // Use NDK pool's removeRelay method
+ const success = this.ndk.pool.removeRelay(url);
+
+ if (success) {
+ console.log(`[RelayService] Successfully removed relay ${url}`);
+ } else {
+ console.log(`[RelayService] Relay ${url} was not found in the pool`);
+ }
+
+ return success;
} catch (error) {
console.error('[RelayService] Error removing relay:', error);
throw error;
}
}
- /**
- * Get relays that are enabled for reading, writing, or both
- */
- async getEnabledRelays(): Promise {
- try {
- const relays = await this.db.getAllAsync<{ url: string }>(
- 'SELECT url FROM relays WHERE read = 1 OR write = 1 ORDER BY priority DESC, created_at DESC'
- );
-
- console.log(`[RelayService] Found ${relays.length} enabled relays`);
- return relays.map(relay => relay.url);
- } catch (error) {
- console.error('[RelayService] Error getting enabled relays:', error);
- return [];
- }
- }
-
/**
* Apply relay configuration to NDK
- * This implementation uses the safeAddRelay and safeRemoveRelay utilities
*/
- async applyRelayConfig(ndk?: NDKCommon): Promise {
+ async applyRelayConfig(relays: RelayConfig[]): Promise {
try {
- // Use provided NDK or the stored one
- const ndkInstance = ndk || this.ndk;
-
- if (!ndkInstance) {
+ if (!this.ndk) {
throw new Error('NDK not initialized');
}
- // Get all relay configurations
- const relayConfigs = await this.getAllRelays();
-
- if (relayConfigs.length === 0) {
- console.warn('[RelayService] No relays found, using defaults');
- await this.resetToDefaults();
- return this.applyRelayConfig(ndkInstance); // Recursive call after reset
+ if (relays.length === 0) {
+ console.warn('[RelayService] No relays provided, using defaults');
+ return this.resetToDefaults();
}
- console.log(`[RelayService] Applying configuration for ${relayConfigs.length} relays`);
+ console.log(`[RelayService] Applying configuration for ${relays.length} relays`);
- // Get the current relay URLs
- const currentRelayUrls: string[] = [];
- try {
- ndkInstance.pool.relays.forEach((_, url) => currentRelayUrls.push(url));
- console.log(`[RelayService] NDK currently has ${currentRelayUrls.length} relays`);
- } catch (error) {
- console.error('[RelayService] Error getting current relay URLs:', error);
- }
+ // Get current relays from NDK
+ const currentRelays = Array.from(this.ndk.pool.relays.keys());
- // Disconnect from relays that are not in the config or have changed permissions
- for (const url of currentRelayUrls) {
- // Get config for this URL if it exists
- const config = relayConfigs.find(r => r.url === url);
-
- // If the relay doesn't exist in our config or the read/write status changed,
- // we should remove it and possibly add it back with new settings
- if (!config || (!config.read && !config.write)) {
+ // Remove relays that aren't in the new configuration
+ for (const url of currentRelays) {
+ if (!relays.find(r => r.url === url)) {
console.log(`[RelayService] Removing relay ${url} from NDK pool`);
- safeRemoveRelay(ndkInstance, url);
+ this.ndk.pool.removeRelay(url);
}
}
- // Add or reconfigure relays
- for (const relay of relayConfigs) {
- if (relay.read || relay.write) {
- try {
- let ndkRelay = ndkInstance.pool.getRelay(relay.url);
-
- if (ndkRelay) {
- // Update relay's read/write config if needed
- try {
- const needsUpdate = (ndkRelay.read !== relay.read) ||
- (ndkRelay.write !== relay.write);
-
- if (needsUpdate) {
- console.log(`[RelayService] Updating relay ${relay.url} settings: read=${relay.read}, write=${relay.write}`);
- // Set properties directly
- ndkRelay.read = relay.read;
- ndkRelay.write = relay.write;
- }
- } catch (error) {
- // If we can't set properties directly, remove and re-add the relay
- console.log(`[RelayService] Recreating relay ${relay.url} due to error:`, error);
- safeRemoveRelay(ndkInstance, relay.url);
- ndkRelay = safeAddRelay(ndkInstance, relay.url, {
- read: relay.read,
- write: relay.write
- });
- }
- } else {
- // Add new relay
- console.log(`[RelayService] Adding new relay ${relay.url} to NDK pool`);
- ndkRelay = safeAddRelay(ndkInstance, relay.url, {
- read: relay.read,
- write: relay.write
- });
- }
-
- // Connect the relay if it was added successfully
- if (ndkRelay && typeof ndkRelay.connect === 'function') {
- console.log(`[RelayService] Connecting to relay ${relay.url}`);
- ndkRelay.connect().catch((error: any) => {
- console.error(`[RelayService] Error connecting to relay ${relay.url}:`, error);
- });
- }
- } catch (innerError) {
- console.error(`[RelayService] Error adding/updating relay ${relay.url}:`, innerError);
- // Continue with other relays even if one fails
- }
+ // Add or update relays from the configuration
+ for (const relay of relays) {
+ const ndkRelay = this.ndk.pool.getRelay(relay.url, false);
+
+ if (ndkRelay) {
+ // Update existing relay
+ ndkRelay.read = relay.read;
+ ndkRelay.write = relay.write;
+ } else {
+ // Add new relay
+ console.log(`[RelayService] Adding relay ${relay.url} to NDK pool`);
+ const newRelay = this.ndk.pool.getRelay(relay.url, true);
+ newRelay.read = relay.read;
+ newRelay.write = relay.write;
}
}
@@ -384,10 +248,75 @@ export class RelayService {
}
}
+ /**
+ * Initialize relays - used during app startup
+ */
+ async initializeRelays(): Promise {
+ try {
+ if (!this.ndk) {
+ throw new Error('NDK not initialized');
+ }
+
+ console.log('[RelayService] Initializing relays');
+
+ // Check if we already have relays in the pool
+ if (this.ndk.pool.size() === 0) {
+ // No relays, add the defaults
+ console.log('[RelayService] No relays found, adding defaults');
+ for (const url of DEFAULT_RELAYS) {
+ const relay = this.ndk.pool.getRelay(url, true);
+ await relay.connect();
+ }
+ }
+
+ return true;
+ } catch (error) {
+ console.error('[RelayService] Error initializing relays:', error);
+ // Reset to defaults on error
+ return this.resetToDefaults();
+ }
+ }
+
+ /**
+ * Reset relays to default set
+ */
+ async resetToDefaults(): Promise {
+ try {
+ if (!this.ndk) {
+ throw new Error('NDK not initialized');
+ }
+
+ console.log('[RelayService] Resetting relays to defaults');
+
+ // Clear existing relays from NDK
+ const currentRelays = Array.from(this.ndk.pool.relays.keys());
+ for (const url of currentRelays) {
+ this.ndk.pool.removeRelay(url);
+ }
+
+ // Add default relays
+ for (const url of DEFAULT_RELAYS) {
+ const relay = this.ndk.pool.getRelay(url, true);
+ try {
+ await relay.connect();
+ } catch (error) {
+ console.error(`[RelayService] Error connecting to relay ${url}:`, error);
+ // Continue even if connection fails
+ }
+ }
+
+ console.log(`[RelayService] Successfully reset to ${DEFAULT_RELAYS.length} default relays`);
+ return true;
+ } catch (error) {
+ console.error('[RelayService] Error resetting relays to defaults:', error);
+ throw error;
+ }
+ }
+
/**
* Import relays from user metadata (kind:3 events)
*/
- async importFromUserMetadata(pubkey: string, ndk: any): Promise {
+ async importFromUserMetadata(pubkey: string, ndk: NDK): Promise {
try {
if (!ndk) {
throw new Error('NDK not initialized');
@@ -422,371 +351,50 @@ export class RelayService {
console.log(`[RelayService] Found relay list in event created at ${new Date(latestCreatedAt * 1000).toISOString()}`);
- // Safely log event details without circular references
- try {
- console.log('[RelayService] Event ID:', latestEvent.id);
- console.log('[RelayService] Event Kind:', latestEvent.kind);
- console.log('[RelayService] Event Created At:', latestEvent.created_at);
- console.log('[RelayService] Event Tags Count:', latestEvent.tags ? latestEvent.tags.length : 0);
-
- // Safely log the tags
- if (latestEvent.tags && Array.isArray(latestEvent.tags)) {
- console.log('[RelayService] Tags:');
- latestEvent.tags.forEach((tag: any[], index: number) => {
- console.log(` Tag ${index}:`, JSON.stringify(tag));
- });
- }
- } catch (error) {
- console.log('[RelayService] Error logging event details:', error);
- }
+ let relaysFound = false;
+ const relayConfigs: RelayConfig[] = [];
- // Get highest current priority
- const highestPriority = await this.db.getFirstAsync<{ priority: number }>(
- 'SELECT MAX(priority) as priority FROM relays'
- );
-
- let maxPriority = (highestPriority?.priority || 0);
- let importCount = 0;
- let updatedCount = 0;
-
- // Check if any relay tags exist
- let relayTagsFound = false;
-
- // Process each relay in the event
+ // Process relay tags from event
if (latestEvent.tags && Array.isArray(latestEvent.tags)) {
for (const tag of latestEvent.tags) {
- try {
- console.log(`[RelayService] Processing tag: ${JSON.stringify(tag)}`);
+ if (tag[0] === 'r' && tag.length > 1) {
+ relaysFound = true;
+ const url = tag[1];
- // More flexible tag detection - handle 'r', 'R', or 'relay' tag types
- if ((tag[0] === 'r' || tag[0] === 'R' || tag[0] === 'relay') && tag.length > 1 && tag[1]) {
- relayTagsFound = true;
- console.log(`[RelayService] Found relay tag: ${tag[1]}`);
-
- const url = tag[1];
-
- // Ensure URL is properly formatted
- if (!url.startsWith('wss://') && !url.startsWith('ws://')) {
- console.log(`[RelayService] Skipping invalid relay URL: ${url}`);
- continue;
- }
-
- // Check for read/write specification in the tag
- let read = true;
- let write = true;
-
- if (tag.length > 2) {
- // Handle various common formatting patterns
- const readWriteSpec = tag[2]?.toLowerCase();
- if (readWriteSpec === 'write') {
- read = false;
- write = true;
- console.log(`[RelayService] Relay ${url} configured as write-only`);
- } else if (readWriteSpec === 'read') {
- read = true;
- write = false;
- console.log(`[RelayService] Relay ${url} configured as read-only`);
- } else {
- console.log(`[RelayService] Unrecognized read/write spec: ${readWriteSpec}, using default (read+write)`);
- }
- }
-
- try {
- // Check if the relay already exists
- const existingRelay = await this.db.getFirstAsync<{ url: string }>(
- 'SELECT url FROM relays WHERE url = ?',
- [url]
- );
-
- const now = Date.now();
-
- if (existingRelay) {
- // Update existing relay
- await this.db.runAsync(
- 'UPDATE relays SET read = ?, write = ?, updated_at = ? WHERE url = ?',
- [read ? 1 : 0, write ? 1 : 0, now, url]
- );
- updatedCount++;
- console.log(`[RelayService] Updated existing relay: ${url} (read=${read}, write=${write})`);
- } else {
- // Add new relay with incremented priority
- maxPriority++;
- await this.db.runAsync(
- 'INSERT INTO relays (url, read, write, priority, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
- [url, read ? 1 : 0, write ? 1 : 0, maxPriority, now, now]
- );
- importCount++;
- console.log(`[RelayService] Added new relay: ${url} (read=${read}, write=${write}, priority=${maxPriority})`);
- }
- } catch (innerError) {
- console.error(`[RelayService] Error importing relay ${url}:`, innerError);
- // Continue with other relays
- }
+ // Skip invalid URLs
+ if (!url.startsWith('wss://')) continue;
+
+ // Parse read/write settings
+ let read = true;
+ let write = true;
+
+ if (tag.length > 2) {
+ read = tag[2] !== 'write'; // If "write", then not read
+ write = tag[2] !== 'read'; // If "read", then not write
}
- } catch (tagError) {
- console.log('[RelayService] Error processing tag:', tagError);
+
+ relayConfigs.push({ url, read, write });
}
}
}
- // Check for relays in content (some clients store them there)
- if (!relayTagsFound) {
- console.log('[RelayService] No relay tags found in event tags, checking content...');
-
- try {
- // Only try to parse the content if it's a string
- if (typeof latestEvent.content === 'string') {
- const contentObj = JSON.parse(latestEvent.content);
-
- // Only log specific properties to avoid circular references
- console.log('[RelayService] Content has relays property:', contentObj.hasOwnProperty('relays'));
-
- // Some clients store relays in content as an object
- if (contentObj.relays && typeof contentObj.relays === 'object') {
- console.log('[RelayService] Found relay URLs in content:', Object.keys(contentObj.relays));
-
- // Process relays from content object
- for (const [url, permissions] of Object.entries(contentObj.relays)) {
- try {
- if (typeof url === 'string' && (url.startsWith('wss://') || url.startsWith('ws://'))) {
- relayTagsFound = true;
-
- let read = true;
- let write = true;
-
- // Handle different formats of permissions
- if (typeof permissions === 'object' && permissions !== null) {
- // Format: { "wss://relay.example.com": { "read": true, "write": false } }
- if ('read' in permissions) read = Boolean((permissions as any).read);
- if ('write' in permissions) write = Boolean((permissions as any).write);
- } else if (typeof permissions === 'string') {
- // Format: { "wss://relay.example.com": "read" }
- read = (permissions as string).includes('read');
- write = (permissions as string).includes('write');
- }
-
- console.log(`[RelayService] Found relay in content: ${url} (read=${read}, write=${write})`);
-
- // Then add or update the relay just like above...
- try {
- const existingRelay = await this.db.getFirstAsync<{ url: string }>(
- 'SELECT url FROM relays WHERE url = ?',
- [url]
- );
-
- const now = Date.now();
-
- if (existingRelay) {
- await this.db.runAsync(
- 'UPDATE relays SET read = ?, write = ?, updated_at = ? WHERE url = ?',
- [read ? 1 : 0, write ? 1 : 0, now, url]
- );
- updatedCount++;
- console.log(`[RelayService] Updated existing relay from content: ${url} (read=${read}, write=${write})`);
- } else {
- maxPriority++;
- await this.db.runAsync(
- 'INSERT INTO relays (url, read, write, priority, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
- [url, read ? 1 : 0, write ? 1 : 0, maxPriority, now, now]
- );
- importCount++;
- console.log(`[RelayService] Added new relay from content: ${url} (read=${read}, write=${write}, priority=${maxPriority})`);
- }
- } catch (innerError) {
- console.error(`[RelayService] Error importing relay ${url} from content:`, innerError);
- }
- }
- } catch (relayError) {
- console.log('[RelayService] Error processing relay from content:', relayError);
- }
- }
- }
- } else {
- console.log('[RelayService] Content is not a string:', typeof latestEvent.content);
- }
- } catch (e) {
- // Convert the unknown error to a string safely
- const errorMessage = e instanceof Error ? e.message : String(e);
- console.log('[RelayService] Content is not JSON or does not contain relay information:', errorMessage);
- }
+ if (!relaysFound || relayConfigs.length === 0) {
+ console.log('[RelayService] No relay tags found in event');
+ return false;
}
- // Check the raw event string that might be available
- if (!relayTagsFound && latestEvent.rawEvent && typeof latestEvent.rawEvent === 'string') {
- console.log('[RelayService] Checking raw event string for relay information');
- try {
- const rawEventObj = JSON.parse(latestEvent.rawEvent);
- if (rawEventObj.tags && Array.isArray(rawEventObj.tags)) {
- console.log(`[RelayService] Raw event has ${rawEventObj.tags.length} tags`);
-
- for (const tag of rawEventObj.tags) {
- try {
- if ((tag[0] === 'r' || tag[0] === 'R') && tag.length > 1 && tag[1]) {
- relayTagsFound = true;
- const url = tag[1];
-
- console.log(`[RelayService] Found relay in raw event: ${url}`);
-
- // Process like above...
- if (url.startsWith('wss://') || url.startsWith('ws://')) {
- let read = true;
- let write = true;
-
- if (tag.length > 2) {
- const readWriteSpec = tag[2]?.toLowerCase();
- if (readWriteSpec === 'write') {
- read = false;
- write = true;
- } else if (readWriteSpec === 'read') {
- read = true;
- write = false;
- }
- }
-
- try {
- const existingRelay = await this.db.getFirstAsync<{ url: string }>(
- 'SELECT url FROM relays WHERE url = ?',
- [url]
- );
-
- const now = Date.now();
-
- if (existingRelay) {
- await this.db.runAsync(
- 'UPDATE relays SET read = ?, write = ?, updated_at = ? WHERE url = ?',
- [read ? 1 : 0, write ? 1 : 0, now, url]
- );
- updatedCount++;
- } else {
- maxPriority++;
- await this.db.runAsync(
- 'INSERT INTO relays (url, read, write, priority, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
- [url, read ? 1 : 0, write ? 1 : 0, maxPriority, now, now]
- );
- importCount++;
- }
- } catch (innerError) {
- console.error(`[RelayService] Error importing relay ${url} from raw event:`, innerError);
- }
- }
- }
- } catch (tagError) {
- console.log('[RelayService] Error processing tag from raw event:', tagError);
- }
- }
- }
- } catch (rawError) {
- // Convert the unknown error to a string safely
- const errorMessage = rawError instanceof Error ? rawError.message : String(rawError);
- console.log('[RelayService] Error parsing raw event:', errorMessage);
- }
- }
-
- // Try to access user cached relays
- if (!relayTagsFound && ndk && ndk.pool && ndk.pool.relays) {
- console.log('[RelayService] Checking for relays in the user NDK pool');
-
- try {
- // Try to access the user's connected relays
- const userRelays = Array.from(ndk.pool.relays.keys());
- if (userRelays.length > 0) {
- console.log(`[RelayService] Found ${userRelays.length} relays in user's NDK pool:`, userRelays);
-
- // Import these relays
- for (const url of userRelays) {
- if (typeof url === 'string' && (url.startsWith('wss://') || url.startsWith('ws://'))) {
- try {
- const existingRelay = await this.db.getFirstAsync<{ url: string }>(
- 'SELECT url FROM relays WHERE url = ?',
- [url]
- );
-
- const now = Date.now();
-
- if (existingRelay) {
- // We'll only update the timestamp, not the permissions
- await this.db.runAsync(
- 'UPDATE relays SET updated_at = ? WHERE url = ?',
- [now, url]
- );
- updatedCount++;
- console.log(`[RelayService] Updated existing relay from NDK pool: ${url}`);
- } else {
- maxPriority++;
- await this.db.runAsync(
- 'INSERT INTO relays (url, read, write, priority, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
- [url, 1, 1, maxPriority, now, now]
- );
- importCount++;
- console.log(`[RelayService] Added new relay from NDK pool: ${url}`);
- }
- } catch (innerError) {
- console.error(`[RelayService] Error importing relay ${url} from NDK pool:`, innerError);
- }
- }
- }
-
- // Set flag to true because we found relays
- relayTagsFound = userRelays.length > 0;
- }
- } catch (poolError) {
- console.log('[RelayService] Error accessing NDK pool relays:', poolError);
- }
- }
-
- if (!relayTagsFound) {
- console.log('[RelayService] No relay information found in any format');
- }
-
- console.log(`[RelayService] Imported ${importCount} new relays, updated ${updatedCount} existing relays`);
- return importCount > 0 || updatedCount > 0;
+ // Apply the found relay configuration
+ return this.applyRelayConfig(relayConfigs);
} catch (error) {
console.error('[RelayService] Error importing relays from metadata:', error);
throw error;
}
}
- /**
- * Reset relays to default set
- */
- async resetToDefaults(): Promise {
- try {
- console.log('[RelayService] Resetting relays to defaults');
-
- // Clear existing relays
- await this.db.runAsync('DELETE FROM relays');
-
- // Add default relays
- const now = Date.now();
- let addedCount = 0;
-
- for (let i = 0; i < DEFAULT_RELAYS.length; i++) {
- const url = DEFAULT_RELAYS[i];
- const priority = DEFAULT_RELAYS.length - i; // Higher priority for first relays
-
- try {
- await this.db.runAsync(
- 'INSERT INTO relays (url, read, write, priority, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
- [url, 1, 1, priority, now, now]
- );
- addedCount++;
- } catch (innerError) {
- console.error(`[RelayService] Error adding default relay ${url}:`, innerError);
- }
- }
-
- console.log(`[RelayService] Successfully reset to ${addedCount} default relays`);
- return addedCount > 0;
- } catch (error) {
- console.error('[RelayService] Error resetting relays to defaults:', error);
- throw error;
- }
- }
-
/**
* Create a kind:3 event with the user's relay preferences
*/
- async publishRelayList(ndk?: any): Promise {
+ async publishRelayList(ndk?: NDK): Promise {
try {
// Use provided NDK or the stored one
const ndkInstance = ndk || this.ndk;
@@ -797,27 +405,28 @@ export class RelayService {
console.log('[RelayService] Publishing relay list to Nostr');
- // Get all relays
- const relays = await this.getAllRelays();
+ // Get all relays directly from NDK pool
+ const relays: RelayConfig[] = [];
+ ndkInstance.pool.relays.forEach((relay, url) => {
+ relays.push({
+ url,
+ read: relay.read ?? true,
+ write: relay.write ?? true
+ });
+ });
if (relays.length === 0) {
console.warn('[RelayService] No relays to publish');
return false;
}
- // Create event using any NDK version
- const NDKEvent = ndkInstance.constructor.name === 'NDK' ?
- ndkInstance.constructor.NDKEvent :
- require('@nostr-dev-kit/ndk-mobile').NDKEvent;
-
+ // Create kind:3 event
+ const { NDKEvent } = require('@nostr-dev-kit/ndk-mobile');
const event = new NDKEvent(ndkInstance);
event.kind = 3;
// Add relay tags
for (const relay of relays) {
- // Skip disabled relays
- if (!relay.read && !relay.write) continue;
-
if (relay.read && relay.write) {
// Full access
event.tags.push(['r', relay.url]);
@@ -844,166 +453,32 @@ export class RelayService {
}
}
- /**
- * Initialize relays from database or defaults
- * If no relays in database, add defaults
- */
- async initializeRelays(): Promise {
- try {
- console.log('[RelayService] Initializing relays');
-
- // First verify the relays table exists and has the correct structure
- await this.checkAndDebugRelays();
-
- // Check if there are any relays in the database
- const count = await this.db.getFirstAsync<{ count: number }>(
- 'SELECT COUNT(*) as count FROM relays'
- );
-
- // If no relays, add defaults
- if (!count || count.count === 0) {
- console.log('[RelayService] No relays found in database, adding defaults');
- await this.resetToDefaults();
- } else {
- console.log(`[RelayService] Found ${count.count} relays in database`);
- }
-
- // Return enabled relays
- const enabledRelays = await this.getEnabledRelays();
- console.log(`[RelayService] Returning ${enabledRelays.length} enabled relays`);
- return enabledRelays;
- } catch (error) {
- console.error('[RelayService] Error initializing relays:', error);
- console.log('[RelayService] Falling back to default relays');
- // Return defaults on error
- return DEFAULT_RELAYS;
- }
- }
-
/**
* Helper to convert NDK relay status to our status format
*/
- private getRelayStatus(relay: any): 'connected' | 'connecting' | 'disconnected' | 'error' {
- try {
- // Check if the relay has a trailing slash in the URL
- const urlWithoutSlash = relay.url ? relay.url.replace(/\/$/, '') : '';
- const urlWithSlash = urlWithoutSlash + '/';
-
- // Try to get the relay from NDK pool - check both with and without trailing slash
- const ndkRelay = this.ndk?.pool.getRelay(urlWithoutSlash) ||
- this.ndk?.pool.getRelay(urlWithSlash);
-
- if (ndkRelay) {
- console.log(`[RelayService] Detailed relay status for ${relay.url}: status=${ndkRelay.status}, connected=${!!ndkRelay.connected}`);
-
- // The most reliable way to check connection status is to check the 'connected' property
- if (ndkRelay.connected) {
+ private getRelayStatus(relay: NDKRelay): 'connected' | 'connecting' | 'disconnected' | 'error' {
+ try {
+ // Check if the relay is connected
+ if (relay.connected) {
return 'connected';
}
- // NDK relay status: 0=connecting, 1=connected, 2=disconnecting, 3=disconnected, 4=reconnecting, 5=auth_required
- if (ndkRelay.status === 1) {
- return 'connected';
- } else if (ndkRelay.status === 0 || ndkRelay.status === 4) { // CONNECTING or RECONNECTING
- return 'connecting';
- } else if (ndkRelay.status === 5) { // AUTH_REQUIRED - This is actually a connected state!
- return 'connected'; // This is the key fix
- } else {
- return 'disconnected';
- }
- }
-
- // If we can't find the relay in the NDK pool
- return 'disconnected';
- } catch (error) {
- console.error(`[RelayService] Error getting relay status:`, error);
- return 'disconnected';
- }
-}
-
- /**
- * Check and debug relays table and content
- */
- private async checkAndDebugRelays(): Promise {
- try {
- console.log('[RelayService] Checking database for relays...');
-
- // Check if table exists
- const tableExists = await this.db.getFirstAsync<{ count: number }>(
- `SELECT count(*) as count FROM sqlite_master
- WHERE type='table' AND name='relays'`
- );
-
- if (!tableExists || tableExists.count === 0) {
- console.error('[RelayService] Relays table does not exist!');
- return;
- }
-
- console.log('[RelayService] Relays table exists');
-
- // Check relay count
- const count = await this.db.getFirstAsync<{ count: number }>(
- 'SELECT COUNT(*) as count FROM relays'
- );
-
- console.log(`[RelayService] Found ${count?.count || 0} relays in database`);
-
- if (count && count.count > 0) {
- // Get sample relays
- const sampleRelays = await this.db.getAllAsync(
- 'SELECT url, read, write, priority FROM relays LIMIT 5'
- );
-
- console.log('[RelayService] Sample relays:', sampleRelays);
+ // Map NDK status to our status format
+ switch (relay.status) {
+ case 0: // CONNECTING
+ case 4: // RECONNECTING
+ return 'connecting';
+ case 1: // CONNECTED
+ case 5: // AUTH_REQUIRED (is actually a connected state)
+ return 'connected';
+ case 3: // DISCONNECTED
+ return 'disconnected';
+ default:
+ return 'disconnected';
}
} catch (error) {
- console.error('[RelayService] Error checking relays:', error);
+ console.error('[RelayService] Error getting relay status:', error);
+ return 'error';
}
}
-
- /**
- * Import user's relay preferences on login
- */
- async importUserRelaysOnLogin(user: any, ndk: any): Promise {
- console.log('[RelayService] Checking for user relay preferences...');
- if (!user || !user.pubkey) return;
-
- try {
- // First check if we already have relays in the database
- const existingCount = await this.db.getFirstAsync<{ count: number }>(
- 'SELECT COUNT(*) as count FROM relays'
- );
-
- // If we have relays and they're not just the defaults, skip import
- if (existingCount && existingCount.count !== undefined && existingCount.count > 0) {
- console.log(`[RelayService] Found ${existingCount.count} existing relays, checking if we need to import more`);
- } else {
- console.log('[RelayService] No existing relays found, will attempt to import');
- }
-
- console.log('[RelayService] Attempting to import user relay preferences');
-
- // Try to import from metadata
- const success = await this.importFromUserMetadata(user.pubkey, ndk);
-
- if (success) {
- console.log('[RelayService] Successfully imported user relay preferences');
- // Apply the imported configuration immediately
- await this.applyRelayConfig(ndk);
- } else {
- console.log('[RelayService] No relay preferences found, resetting to defaults');
- await this.resetToDefaults();
- await this.applyRelayConfig(ndk);
- }
- } catch (error) {
- console.error('[RelayService] Error importing user relays:', error);
- // On error, reset to defaults
- try {
- console.log('[RelayService] Error occurred, resetting to defaults');
- await this.resetToDefaults();
- await this.applyRelayConfig(ndk);
- } catch (resetError) {
- console.error('[RelayService] Error resetting to defaults:', resetError);
- }
- }
- }}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/lib/initNDK.ts b/lib/initNDK.ts
index 43e8bf8..8e6e136 100644
--- a/lib/initNDK.ts
+++ b/lib/initNDK.ts
@@ -2,58 +2,39 @@
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 { openDatabaseSync } from 'expo-sqlite';
import { RelayService, DEFAULT_RELAYS } from '@/lib/db/services/RelayService';
-import { NDKCommon } from '@/types/ndk-common';
import { extendNDK } from '@/types/ndk-extensions';
/**
- * Initialize NDK with relays from database or defaults
+ * Initialize NDK with relays
*/
export async function initializeNDK() {
console.log('[NDK] Initializing NDK with mobile adapter...');
// Create a mobile-specific cache adapter
const cacheAdapter = new NDKCacheAdapterSqlite('powr', 1000);
+ await cacheAdapter.initialize();
- // Initialize database and relay service
- const db = openDatabaseSync('powr.db');
- const relayService = new RelayService(db);
-
+ // Initialize relay service
+ const relayService = new RelayService();
relayService.enableDebug();
- // Load relays from database or use defaults
- console.log('[NDK] Loading relay configuration...');
- let relays: string[];
-
- try {
- // Try to initialize relays from database (will add defaults if none exist)
- relays = await relayService.initializeRelays();
- console.log(`[NDK] Loaded ${relays.length} relays from database:`, relays);
- } catch (error) {
- console.error('[NDK] Error loading relays from database:', error);
- console.log('[NDK] Falling back to default relays');
- relays = DEFAULT_RELAYS;
- }
-
// Create settings store
const settingsStore = {
get: SecureStore.getItemAsync,
set: SecureStore.setItemAsync,
delete: SecureStore.deleteItemAsync,
getSync: (key: string) => {
- // This is a synchronous wrapper - for mobile we need to handle this differently
- // since SecureStore is async-only
console.log('[Settings] Warning: getSync called but returning null, not supported in this implementation');
return null;
}
};
- // Initialize NDK with options
- console.log(`[NDK] Creating NDK instance with ${relays.length} relays`);
+ // Initialize NDK with default relays first
+ console.log(`[NDK] Creating NDK instance with default relays`);
let ndk = new NDK({
cacheAdapter,
- explicitRelayUrls: relays,
+ explicitRelayUrls: DEFAULT_RELAYS,
enableOutboxModel: true,
autoConnectUserRelays: true,
clientName: 'powr',
@@ -62,41 +43,8 @@ export async function initializeNDK() {
// Extend NDK with helper methods for better compatibility
ndk = extendNDK(ndk);
- // Initialize cache adapter
- await cacheAdapter.initialize();
-
- // Set up the RelayService with the NDK instance for future use
- relayService.setNDK(ndk as unknown as NDKCommon);
-
- // Setup relay status tracking
- const relayStatus: Record = {};
- relays.forEach(url => {
- relayStatus[url] = 'connecting';
- });
-
- // Set up listeners before connecting
- relays.forEach(url => {
- const relay = ndk.pool.getRelay(url);
- if (relay) {
- // Connection success
- relay.on('connect', () => {
- console.log(`[NDK] Relay connected: ${url}`);
- relayStatus[url] = 'connected';
- });
-
- // Connection closed
- relay.on('disconnect', () => {
- console.log(`[NDK] Relay disconnected: ${url}`);
- relayStatus[url] = 'disconnected';
- });
-
- // For errors, use the notice event which is used for errors in NDK
- relay.on('notice', (notice: string) => {
- console.error(`[NDK] Relay notice/error for ${url}:`, notice);
- relayStatus[url] = 'error';
- });
- }
- });
+ // Set the NDK instance in the RelayService
+ relayService.setNDK(ndk);
try {
console.log('[NDK] Connecting to relays...');
@@ -105,27 +53,24 @@ export async function initializeNDK() {
// Wait a moment for connections to establish
await new Promise(resolve => setTimeout(resolve, 2000));
+ // Get updated relay statuses
+ const relaysWithStatus = await relayService.getAllRelaysWithStatus();
+
// Count connected relays
- const connectedRelays = Object.entries(relayStatus)
- .filter(([_, status]) => status === 'connected')
- .map(([url]) => url);
+ const connectedRelays = relaysWithStatus
+ .filter(relay => relay.status === 'connected')
+ .map(relay => relay.url);
- console.log(`[NDK] Connected to ${connectedRelays.length}/${relays.length} relays`);
+ console.log(`[NDK] Connected to ${connectedRelays.length}/${relaysWithStatus.length} relays`);
- // Add more detailed relay status logging
+ // Log detailed relay status
console.log('[NDK] Detailed relay status:');
- relays.forEach(url => {
- const relay = ndk.pool.getRelay(url);
- console.log(` - ${url}: ${relay ?
- (relay.status === 1 ? 'connected' :
- relay.status === 0 ? 'connecting' :
- relay.status === 3 ? 'disconnected' :
- `status=${relay.status}`) : 'not found'}`);
+ relaysWithStatus.forEach(relay => {
+ console.log(` - ${relay.url}: ${relay.status}`);
});
return {
ndk,
- relayStatus,
relayService,
connectedRelayCount: connectedRelays.length,
connectedRelays
@@ -135,7 +80,6 @@ export async function initializeNDK() {
// Still return the NDK instance so the app can work offline
return {
ndk,
- relayStatus,
relayService,
connectedRelayCount: 0,
connectedRelays: []
diff --git a/lib/stores/ndk.ts b/lib/stores/ndk.ts
index 0f97bd5..012ab32 100644
--- a/lib/stores/ndk.ts
+++ b/lib/stores/ndk.ts
@@ -6,11 +6,10 @@ import NDK, {
NDKUser,
NDKRelay,
NDKPrivateKeySigner
-} from '@nostr-dev-kit/ndk';
+} from '@nostr-dev-kit/ndk-mobile';
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
import * as SecureStore from 'expo-secure-store';
import { RelayService } from '@/lib/db/services/RelayService';
-import { openDatabaseSync } from 'expo-sqlite';
// Constants for SecureStore
const PRIVATE_KEY_STORAGE_KEY = 'nostr_privkey';
@@ -133,85 +132,129 @@ export const useNDKStore = create((set, get) =>
login: async (privateKeyInput?: string) => {
set({ isLoading: true, error: null });
+ console.log('[NDK] Login attempt starting');
try {
const { ndk } = get();
if (!ndk) {
+ console.log('[NDK] Error: NDK not initialized');
throw new Error('NDK not initialized');
}
+ console.log('[NDK] Processing private key input');
+
// If no private key is provided, generate one
let privateKeyHex = privateKeyInput;
if (!privateKeyHex) {
+ console.log('[NDK] No key provided, generating new key');
const { privateKey } = get().generateKeys();
privateKeyHex = privateKey;
+ } else {
+ console.log('[NDK] Using provided key, format:',
+ privateKeyHex.startsWith('nsec') ? 'nsec' : 'hex',
+ 'length:', privateKeyHex.length);
}
// Handle nsec format
if (privateKeyHex.startsWith('nsec')) {
try {
+ console.log('[NDK] Decoding nsec format key');
const decoded = nip19.decode(privateKeyHex);
+ console.log('[NDK] Decoded type:', decoded.type);
if (decoded.type === 'nsec') {
// Get the data as hex
privateKeyHex = bytesToHex(decoded.data as any);
+ console.log('[NDK] Converted to hex, new length:', privateKeyHex.length);
}
} catch (error) {
- console.error('Error decoding nsec:', error);
+ console.error('[NDK] Error decoding nsec:', error);
throw new Error('Invalid nsec format');
}
}
+ console.log('[NDK] Creating signer with key length:', privateKeyHex.length);
+
// Create signer with private key
const signer = new NDKPrivateKeySigner(privateKeyHex);
+ console.log('[NDK] Signer created, setting on NDK');
ndk.signer = signer;
// Get user
+ console.log('[NDK] Getting user from signer');
const user = await ndk.signer.user();
if (!user) {
+ console.log('[NDK] Error: Could not get user from signer');
throw new Error('Could not get user from signer');
}
+ console.log('[NDK] User retrieved, pubkey:', user.pubkey ? user.pubkey.substring(0, 8) + '...' : 'undefined');
+
// Fetch user profile
console.log('[NDK] Fetching user profile');
- await user.fetchProfile();
+ try {
+ await user.fetchProfile();
+ console.log('[NDK] Profile fetched successfully');
+ } catch (profileError) {
+ console.warn('[NDK] Warning: Could not fetch user profile:', profileError);
+ // Continue even if profile fetch fails
+ }
// Process profile data to ensure image property is set
if (user.profile) {
+ console.log('[NDK] Profile data available');
if (!user.profile.image && (user.profile as any).picture) {
user.profile.image = (user.profile as any).picture;
+ console.log('[NDK] Set image from picture property');
}
- console.log('[NDK] User profile loaded:', user.profile);
+ console.log('[NDK] User profile loaded:',
+ user.profile.name || user.profile.displayName || 'No name available');
+ } else {
+ console.log('[NDK] No profile data available');
}
// Save the private key hex string securely
+ console.log('[NDK] Saving private key to secure storage');
await SecureStore.setItemAsync(PRIVATE_KEY_STORAGE_KEY, privateKeyHex);
// After successful login, import user relay preferences
try {
- console.log('[NDK] Login successful, importing user relay preferences');
- const db = openDatabaseSync('powr.db');
- const relayService = new RelayService(db);
+ console.log('[NDK] Creating RelayService to import user preferences');
+ const relayService = new RelayService();
// Set NDK on the relay service
- relayService.setNDK(ndk as any);
+ console.log('[NDK] Setting NDK on RelayService');
+ relayService.setNDK(ndk as any); // Using type assertion
- // Import and apply user relay preferences
- await relayService.importUserRelaysOnLogin(user, ndk);
+ // Import user relay preferences from metadata (kind:3 events)
+ if (user.pubkey) {
+ console.log('[NDK] Importing relay metadata for user:', user.pubkey.substring(0, 8) + '...');
+ try {
+ await relayService.importFromUserMetadata(user.pubkey, ndk);
+ console.log('[NDK] Successfully imported user relay preferences');
+ } catch (importError) {
+ console.warn('[NDK] Could not import user relay preferences:', importError);
+ // Continue even if import fails
+ }
+ } else {
+ console.log('[NDK] Cannot import relay metadata: No pubkey available');
+ }
} catch (relayError) {
- console.error('[NDK] Error importing user relay preferences:', relayError);
+ console.error('[NDK] Error with RelayService:', relayError);
// Continue with login even if relay import fails
}
+ console.log('[NDK] Login successful, updating state');
set({
currentUser: user,
isAuthenticated: true,
isLoading: false
});
+ console.log('[NDK] Login complete');
return true;
} catch (error) {
- console.error('[NDK] Login error:', error);
+ console.error('[NDK] Login error detailed:', error);
set({
error: error instanceof Error ? error : new Error('Failed to login'),
isLoading: false
diff --git a/lib/stores/relayStore.ts b/lib/stores/relayStore.ts
index df0f556..41585e3 100644
--- a/lib/stores/relayStore.ts
+++ b/lib/stores/relayStore.ts
@@ -1,6 +1,5 @@
// lib/stores/relayStore.ts
import { create } from 'zustand';
-import { openDatabaseSync } from 'expo-sqlite';
import type { RelayWithStatus } from '@/lib/db/services/RelayService';
import { RelayService } from '@/lib/db/services/RelayService';
import { useNDKStore } from './ndk';
@@ -11,8 +10,7 @@ let relayServiceInstance: RelayService | null = null;
const getRelayService = (): RelayService => {
if (!relayServiceInstance) {
- const db = openDatabaseSync('powr.db');
- relayServiceInstance = new RelayService(db);
+ relayServiceInstance = new RelayService();
console.log('[RelayStore] Created RelayService instance');
}
return relayServiceInstance;
@@ -60,7 +58,8 @@ export const useRelayStore = create((set, g
const ndk = ndkState.ndk as unknown as NDKCommon;
if (ndk) {
- relayService.setNDK(ndk);
+ // Use type assertion to ensure compatibility
+ relayService.setNDK(ndk as any);
}
const relays = await relayService.getAllRelaysWithStatus();
@@ -159,7 +158,8 @@ export const useRelayStore = create((set, g
}
// Apply relay config changes to NDK
- const success = await relayService.applyRelayConfig(ndk);
+ const relays = get().relays;
+ const success = await relayService.applyRelayConfig(relays);
// Wait a moment before reloading to give connections time to establish
await new Promise(resolve => setTimeout(resolve, 2000));