import 'dart:math'; import 'package:logging/logging.dart'; import 'package:pshared/utils/exception.dart'; class RetryHelper { static final _logger = Logger('auth.retry'); /// Executes an operation with exponential backoff retry logic static Future withExponentialBackoff( Future Function() operation, { int maxRetries = 3, Duration initialDelay = const Duration(milliseconds: 500), double backoffMultiplier = 2.0, Duration maxDelay = const Duration(seconds: 30), bool Function(Exception)? shouldRetry, }) async { Exception? lastException; // Total attempts = initial attempt + maxRetries final totalAttempts = maxRetries + 1; for (int attempt = 1; attempt <= totalAttempts; attempt++) { try { _logger.fine('Attempting operation (attempt $attempt/$totalAttempts)'); return await operation(); } catch (e) { lastException = toException(e); // Don't retry if we've reached max attempts if (attempt == totalAttempts) { _logger.warning('Operation failed after $totalAttempts attempts: $lastException'); rethrow; } // Check if we should retry this specific error if (shouldRetry != null && !shouldRetry(lastException)) { _logger.fine('Operation failed with non-retryable error: $lastException'); rethrow; } // Calculate delay with exponential backoff final delayMs = min( initialDelay.inMilliseconds * pow(backoffMultiplier, attempt - 1).toInt(), maxDelay.inMilliseconds, ); final delay = Duration(milliseconds: delayMs); _logger.fine('Operation failed (attempt $attempt), retrying in ${delay.inMilliseconds}ms: $lastException'); await Future.delayed(delay); } } // This should never be reached due to rethrow above, but just in case throw lastException ?? Exception('Retry logic error'); } /// Determines if an error is retryable (network/temporary errors) static bool isRetryableError(Exception error) { final errorString = error.toString().toLowerCase(); // Network connectivity issues if (errorString.contains('socket') || errorString.contains('connection') || errorString.contains('timeout') || errorString.contains('network')) { return true; } // Server temporary errors (5xx) if (errorString.contains('500') || errorString.contains('502') || errorString.contains('503') || errorString.contains('504')) { return true; } // Rate limiting if (errorString.contains('429')) { return true; } return false; } }