|
|
|
@ -29,6 +29,9 @@ class NostrService {
|
|
|
|
final Map<String, StreamController<Map<String, dynamic>>>
|
|
|
|
final Map<String, StreamController<Map<String, dynamic>>>
|
|
|
|
_messageControllers = {};
|
|
|
|
_messageControllers = {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Retry timers for relays that are attempting to reconnect.
|
|
|
|
|
|
|
|
final Map<String, Timer> _retryTimers = {};
|
|
|
|
|
|
|
|
|
|
|
|
/// Creates a [NostrService] instance.
|
|
|
|
/// Creates a [NostrService] instance.
|
|
|
|
NostrService();
|
|
|
|
NostrService();
|
|
|
|
|
|
|
|
|
|
|
|
@ -67,6 +70,9 @@ class NostrService {
|
|
|
|
orElse: () => throw NostrException('Relay not found: $relayUrl'),
|
|
|
|
orElse: () => throw NostrException('Relay not found: $relayUrl'),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
relay.isEnabled = enabled;
|
|
|
|
relay.isEnabled = enabled;
|
|
|
|
|
|
|
|
if (enabled) {
|
|
|
|
|
|
|
|
relay.retryCount = 0; // Reset retry count when enabling
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If disabling, also disconnect
|
|
|
|
// If disabling, also disconnect
|
|
|
|
if (!enabled && relay.isConnected) {
|
|
|
|
if (!enabled && relay.isConnected) {
|
|
|
|
@ -156,6 +162,7 @@ class NostrService {
|
|
|
|
// No errors occurred, connection is established
|
|
|
|
// No errors occurred, connection is established
|
|
|
|
connectionConfirmed = true;
|
|
|
|
connectionConfirmed = true;
|
|
|
|
relay.isConnected = true;
|
|
|
|
relay.isConnected = true;
|
|
|
|
|
|
|
|
relay.retryCount = 0; // Reset retry count on successful connection
|
|
|
|
Logger.info('Connection confirmed for relay: $relayUrl (no errors after 500ms)');
|
|
|
|
Logger.info('Connection confirmed for relay: $relayUrl (no errors after 500ms)');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@ -167,6 +174,7 @@ class NostrService {
|
|
|
|
if (!connectionConfirmed) {
|
|
|
|
if (!connectionConfirmed) {
|
|
|
|
connectionConfirmed = true;
|
|
|
|
connectionConfirmed = true;
|
|
|
|
relay.isConnected = true;
|
|
|
|
relay.isConnected = true;
|
|
|
|
|
|
|
|
relay.retryCount = 0; // Reset retry count on successful connection
|
|
|
|
Logger.info('Connection confirmed for relay: $relayUrl (first message received)');
|
|
|
|
Logger.info('Connection confirmed for relay: $relayUrl (first message received)');
|
|
|
|
connectionTimer?.cancel();
|
|
|
|
connectionTimer?.cancel();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -207,9 +215,7 @@ class NostrService {
|
|
|
|
connectionTimer?.cancel();
|
|
|
|
connectionTimer?.cancel();
|
|
|
|
Logger.error('WebSocket error for relay: $relayUrl', error);
|
|
|
|
Logger.error('WebSocket error for relay: $relayUrl', error);
|
|
|
|
relay.isConnected = false;
|
|
|
|
relay.isConnected = false;
|
|
|
|
// Automatically disable relay when connection error occurs
|
|
|
|
_handleConnectionFailure(relayUrl, relay, controller, 'connection error');
|
|
|
|
relay.isEnabled = false;
|
|
|
|
|
|
|
|
Logger.warning('Relay $relayUrl disabled due to connection error');
|
|
|
|
|
|
|
|
controller.addError(NostrException('Relay error: $error'));
|
|
|
|
controller.addError(NostrException('Relay error: $error'));
|
|
|
|
},
|
|
|
|
},
|
|
|
|
onDone: () {
|
|
|
|
onDone: () {
|
|
|
|
@ -217,9 +223,7 @@ class NostrService {
|
|
|
|
connectionTimer?.cancel();
|
|
|
|
connectionTimer?.cancel();
|
|
|
|
Logger.warning('WebSocket stream closed for relay: $relayUrl');
|
|
|
|
Logger.warning('WebSocket stream closed for relay: $relayUrl');
|
|
|
|
relay.isConnected = false;
|
|
|
|
relay.isConnected = false;
|
|
|
|
// Automatically disable relay when connection closes
|
|
|
|
_handleConnectionFailure(relayUrl, relay, controller, 'stream closure');
|
|
|
|
relay.isEnabled = false;
|
|
|
|
|
|
|
|
Logger.warning('Relay $relayUrl disabled due to stream closure');
|
|
|
|
|
|
|
|
controller.close();
|
|
|
|
controller.close();
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
);
|
|
|
|
@ -230,6 +234,101 @@ class NostrService {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Handles connection failure with retry logic.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// [relayUrl] - The URL of the relay that failed.
|
|
|
|
|
|
|
|
/// [relay] - The relay object.
|
|
|
|
|
|
|
|
/// [controller] - The stream controller for this relay.
|
|
|
|
|
|
|
|
/// [reason] - Reason for the failure (for logging).
|
|
|
|
|
|
|
|
void _handleConnectionFailure(
|
|
|
|
|
|
|
|
String relayUrl,
|
|
|
|
|
|
|
|
NostrRelay relay,
|
|
|
|
|
|
|
|
StreamController<Map<String, dynamic>> controller,
|
|
|
|
|
|
|
|
String reason,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
// Cancel any existing retry timer
|
|
|
|
|
|
|
|
_retryTimers[relayUrl]?.cancel();
|
|
|
|
|
|
|
|
_retryTimers.remove(relayUrl);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only retry if relay is enabled
|
|
|
|
|
|
|
|
if (!relay.isEnabled) {
|
|
|
|
|
|
|
|
Logger.info('Relay $relayUrl is disabled, skipping retry');
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
relay.retryCount++;
|
|
|
|
|
|
|
|
Logger.warning('Relay $relayUrl failed ($reason), retry attempt ${relay.retryCount}/${NostrRelay.maxRetryAttempts}');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (relay.retryCount >= NostrRelay.maxRetryAttempts) {
|
|
|
|
|
|
|
|
// Max retries reached - disable the relay
|
|
|
|
|
|
|
|
relay.isEnabled = false;
|
|
|
|
|
|
|
|
relay.retryCount = 0; // Reset for future attempts
|
|
|
|
|
|
|
|
Logger.warning('Relay $relayUrl disabled after ${NostrRelay.maxRetryAttempts} failed attempts');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Schedule retry after delay (5 seconds)
|
|
|
|
|
|
|
|
_scheduleRetry(relayUrl, relay, controller, reason);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Schedules a retry attempt for a failed relay connection.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// [relayUrl] - The URL of the relay to retry.
|
|
|
|
|
|
|
|
/// [relay] - The relay object.
|
|
|
|
|
|
|
|
/// [controller] - The stream controller for this relay.
|
|
|
|
|
|
|
|
/// [reason] - Reason for the retry (for logging).
|
|
|
|
|
|
|
|
void _scheduleRetry(
|
|
|
|
|
|
|
|
String relayUrl,
|
|
|
|
|
|
|
|
NostrRelay relay,
|
|
|
|
|
|
|
|
StreamController<Map<String, dynamic>> controller,
|
|
|
|
|
|
|
|
String reason,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
Logger.info('Scheduling retry for relay $relayUrl in 5 seconds (attempt ${relay.retryCount}/${NostrRelay.maxRetryAttempts})');
|
|
|
|
|
|
|
|
_retryTimers[relayUrl] = Timer(const Duration(seconds: 5), () {
|
|
|
|
|
|
|
|
_retryTimers.remove(relayUrl);
|
|
|
|
|
|
|
|
if (relay.isEnabled && !relay.isConnected) {
|
|
|
|
|
|
|
|
Logger.info('Retrying connection to relay: $relayUrl');
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// Clean up old connection
|
|
|
|
|
|
|
|
disconnectRelay(relayUrl);
|
|
|
|
|
|
|
|
// Attempt to reconnect
|
|
|
|
|
|
|
|
connectRelay(relayUrl).then((stream) {
|
|
|
|
|
|
|
|
// Reset retry count on successful connection
|
|
|
|
|
|
|
|
relay.retryCount = 0;
|
|
|
|
|
|
|
|
Logger.info('Relay $relayUrl reconnected successfully');
|
|
|
|
|
|
|
|
// Cancel the stream subscription as we're just testing the connection
|
|
|
|
|
|
|
|
stream.listen(null).cancel();
|
|
|
|
|
|
|
|
}).catchError((e) {
|
|
|
|
|
|
|
|
Logger.warning('Retry failed for relay $relayUrl: $e');
|
|
|
|
|
|
|
|
// Handle retry failure - check if we should retry again or disable
|
|
|
|
|
|
|
|
if (relay.isEnabled) {
|
|
|
|
|
|
|
|
if (relay.retryCount >= NostrRelay.maxRetryAttempts) {
|
|
|
|
|
|
|
|
relay.isEnabled = false;
|
|
|
|
|
|
|
|
relay.retryCount = 0;
|
|
|
|
|
|
|
|
Logger.warning('Relay $relayUrl disabled after ${NostrRelay.maxRetryAttempts} failed attempts');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Schedule another retry (retryCount already incremented in _handleConnectionFailure)
|
|
|
|
|
|
|
|
_scheduleRetry(relayUrl, relay, controller, 'retry failure');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
Logger.error('Error during retry for relay $relayUrl', e);
|
|
|
|
|
|
|
|
// Handle synchronous exception
|
|
|
|
|
|
|
|
if (relay.isEnabled) {
|
|
|
|
|
|
|
|
if (relay.retryCount >= NostrRelay.maxRetryAttempts) {
|
|
|
|
|
|
|
|
relay.isEnabled = false;
|
|
|
|
|
|
|
|
relay.retryCount = 0;
|
|
|
|
|
|
|
|
Logger.warning('Relay $relayUrl disabled after ${NostrRelay.maxRetryAttempts} failed attempts');
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Schedule another retry (retryCount already incremented in _handleConnectionFailure)
|
|
|
|
|
|
|
|
_scheduleRetry(relayUrl, relay, controller, 'synchronous retry failure');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Disconnects from a relay.
|
|
|
|
/// Disconnects from a relay.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// [relayUrl] - The URL of the relay to disconnect from.
|
|
|
|
/// [relayUrl] - The URL of the relay to disconnect from.
|
|
|
|
|