Skip to content

Add retry logic for network failures #8

@koistya

Description

@koistya

Description

Add configurable retry logic for browser opening and fetch operations in src/index.ts (lines 84-99). Currently, if the browser fails to open or the fetch fails, the operation simply continues or fails silently.

What needs to be done

Implement intelligent retry logic with:

  1. Configurable retry attempts for browser opening
  2. Exponential backoff for network requests
  3. Proper error aggregation and reporting
  4. Circuit breaker pattern for repeated failures
  5. Differentiation between retryable and non-retryable errors

Why this matters

Network operations can fail for transient reasons:

  • Browser may be slow to start
  • Temporary network glitches
  • OAuth provider rate limiting
  • DNS resolution issues

Without retry logic:

  • Users experience unnecessary failures
  • Transient issues become permanent errors
  • Poor user experience in unstable network conditions

Current code reference

if (openBrowser) {
  await open(authorizationUrl);
} else {
  // Test mode: trigger mock provider redirect without browser
  fetch(authorizationUrl)
    .then(async (response) => {
      // ... handle response
    })
    .catch(() => {
      // Ignore - tests may lack mock provider
    });
}

Implementation considerations

⚠️ Note: This feature requires critical thinking during implementation. Consider:

  1. User experience: How do we balance retry attempts with user wait time? Should we show progress?

  2. Alternative approach: Instead of automatic retries, should we expose hooks for users to implement their own retry logic?

  3. Browser detection: Should we detect if no browser is available and provide a manual URL instead of retrying?

  4. Exponential backoff vs fixed delay: Which retry strategy works best for OAuth flows?

  5. Partial failures: What if the browser opens but the user doesn't complete the flow? Should we retry the entire process?

Suggested implementation

interface RetryOptions {
  maxAttempts?: number;
  initialDelay?: number;
  maxDelay?: number;
  backoffFactor?: number;
  retryableErrors?: (error: Error) => boolean;
  onRetry?: (attempt: number, error: Error) => void;
}

async function withRetry<T>(
  fn: () => Promise<T>,
  options: RetryOptions = {}
): Promise<T> {
  const {
    maxAttempts = 3,
    initialDelay = 1000,
    maxDelay = 30000,
    backoffFactor = 2,
    retryableErrors = () => true,
    onRetry
  } = options;
  
  let lastError: Error;
  
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;
      
      if (attempt === maxAttempts || !retryableErrors(lastError)) {
        throw new AggregateError(
          [lastError],
          `Failed after ${attempt} attempts: ${lastError.message}`
        );
      }
      
      const delay = Math.min(
        initialDelay * Math.pow(backoffFactor, attempt - 1),
        maxDelay
      );
      
      onRetry?.(attempt, lastError);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError!;
}

// Usage in getAuthCode
if (openBrowser) {
  await withRetry(
    () => open(authorizationUrl),
    {
      maxAttempts: 3,
      onRetry: (attempt, error) => {
        console.warn(`Failed to open browser (attempt ${attempt}): ${error.message}`);
      }
    }
  );
}

Testing requirements

  • Test successful retry after transient failure
  • Test maximum retry limit
  • Test exponential backoff timing
  • Test non-retryable error handling
  • Test error aggregation
  • Test cancellation during retry

Skills required

  • TypeScript
  • Error handling patterns
  • Async/await and promises
  • Network programming concepts
  • Testing async operations

Difficulty

Medium - Requires understanding of retry patterns and async error handling

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions