-
-
Notifications
You must be signed in to change notification settings - Fork 2
Description
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:
- Configurable retry attempts for browser opening
- Exponential backoff for network requests
- Proper error aggregation and reporting
- Circuit breaker pattern for repeated failures
- 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
-
User experience: How do we balance retry attempts with user wait time? Should we show progress?
-
Alternative approach: Instead of automatic retries, should we expose hooks for users to implement their own retry logic?
-
Browser detection: Should we detect if no browser is available and provide a manual URL instead of retrying?
-
Exponential backoff vs fixed delay: Which retry strategy works best for OAuth flows?
-
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