
Security News
/Research
Wallet-Draining npm Package Impersonates Nodemailer to Hijack Crypto Transactions
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
universal-api-retry-interceptor
Advanced tools
A universal API interceptor that works with fetch, axios, and any HTTP library with retry logic and offline support
A bulletproof, universal API interceptor that automatically handles failed requests with intelligent retry logic and seamless offline support. Works with ANY HTTP library!
๐ Quick Start โข ๐ฎ Live Demo โข ๐ก Examples โข ๐ค Contributing
In today's interconnected world, network failures are inevitable. Your users deserve applications that gracefully handle network hiccups without breaking their experience. This interceptor transforms your application into a resilient, self-healing system that automatically recovers from network failures.
// Without interceptor - One network hiccup breaks everything
fetch('/api/critical-data')
.then(response => response.json())
.then(data => updateUI(data))
.catch(error => {
// ๐ฅ User sees error, experience ruined
showErrorMessage("Something went wrong!");
});
// With interceptor - Automatically handles failures
startGlobalInterceptor({ maxRetries: 3, delayTime: 1000 });
// Same code, but now bulletproof!
fetch('/api/critical-data')
.then(response => response.json())
.then(data => updateUI(data)) // โจ Just works, even after retries
.catch(error => {
// Only fails after 3 intelligent retry attempts
});
๐ง Universal Compatibility
|
๐ง Intelligent Retry Logic
|
๐ฑ Offline Resilience
|
๐ก๏ธ Production Ready
|
npm install universal-api-retry-interceptor
import { startGlobalInterceptor } from 'universal-api-retry-interceptor';
// One line to make ALL your HTTP requests bulletproof
startGlobalInterceptor({
maxRetries: 3, // Retry failed requests up to 3 times
delayTime: 1000, // Wait 1 second between retries
retryInterval: 5000, // Check for pending requests every 5 seconds
enableLogging: true // See what's happening (disable in production)
});
// That's it! Now ALL your existing HTTP code gets automatic retry logic:
fetch('/api/users'); // โ
Auto-retried on failure
axios.get('/api/posts'); // โ
Auto-retried on failure
$.ajax('/api/data'); // โ
Auto-retried on failure
new XMLHttpRequest(); // โ
Auto-retried on failure
Experience the power yourself!
๐ Interactive Demo - See the interceptor in action with real API calls, retry scenarios, and offline simulation.
// App.js - Set up once, protect everything
import React, { useEffect } from 'react';
import { startGlobalInterceptor } from 'universal-api-retry-interceptor';
import { toast } from 'react-hot-toast';
function App() {
useEffect(() => {
// Initialize the interceptor
startGlobalInterceptor({
maxRetries: 3,
delayTime: 2000,
onRetry: (error, retryCount, requestInfo) => {
toast.info(`Retrying ${requestInfo.url}... (${retryCount}/3)`);
},
onMaxRetriesExceeded: (error, requestInfo) => {
toast.error(`Failed to load ${requestInfo.url} after 3 attempts`);
}
});
}, []);
return <YourApp />;
}
// UserProfile.js - Your existing code works unchanged
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// This fetch is now automatically protected by retry logic
fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(setUser)
.catch(error => {
// Only called after all retries are exhausted
console.error('Failed to load user:', error);
});
}, [userId]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
import { startGlobalInterceptor } from 'universal-api-retry-interceptor';
// Configure for critical operations
startGlobalInterceptor({
maxRetries: 5, // More retries for critical operations
delayTime: 3000, // Longer delays for server recovery
retryCondition: (error, response) => {
// Custom retry logic for e-commerce
if (!response) return true; // Network errors
// Retry server errors but not client errors
return response.status >= 500 ||
response.status === 408 || // Timeout
response.status === 429; // Rate limited
},
onRetry: (error, retryCount, requestInfo) => {
// Inform user about payment retry
if (requestInfo.url.includes('/payment')) {
showPaymentRetryMessage(`Processing payment... (attempt ${retryCount})`);
}
}
});
// Your payment code - now bulletproof
async function processPayment(paymentData) {
try {
const response = await fetch('/api/payments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(paymentData)
});
if (response.ok) {
const result = await response.json();
showSuccessMessage('Payment processed successfully!');
return result;
}
} catch (error) {
// Only reaches here after 5 retry attempts
showErrorMessage('Payment failed. Please try again or contact support.');
throw error;
}
}
// Works seamlessly with any combination of HTTP libraries
import axios from 'axios';
import { startGlobalInterceptor } from 'universal-api-retry-interceptor';
startGlobalInterceptor({
maxRetries: 3,
enableLogging: true
});
// Different parts of your app can use different libraries
class ApiService {
// Using fetch
async getUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// Using axios
async getPosts() {
const response = await axios.get('/api/posts');
return response.data;
}
// Using XMLHttpRequest (legacy code)
getComments(callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/comments');
xhr.onload = () => callback(JSON.parse(xhr.responseText));
xhr.send();
}
}
// All methods above are automatically protected!
startGlobalInterceptor({
delayTime: 1000, // Wait time between retries (ms)
retryInterval: 5000, // How often to check pending requests (ms)
maxRetries: 3, // Maximum retry attempts
enableLogging: false // Enable console logging
});
startGlobalInterceptor({
delayTime: 2000,
retryInterval: 3000,
maxRetries: 5,
enableLogging: true,
// Custom retry conditions
retryCondition: (error, response) => {
// Only retry specific scenarios
if (!response && error?.name === 'TypeError') {
return true; // Network errors
}
if (response) {
// Retry server errors and rate limits
return [500, 502, 503, 504, 408, 429].includes(response.status);
}
return false;
},
// Retry event handler
onRetry: (error, retryCount, requestInfo) => {
console.log(`๐ Retrying ${requestInfo.url} (attempt ${retryCount})`);
// Show user-friendly messages
if (requestInfo.url.includes('/api/critical')) {
showNotification(`Retrying critical operation... (${retryCount}/${maxRetries})`);
}
},
// Max retries exceeded handler
onMaxRetriesExceeded: (error, requestInfo) => {
console.error(`๐ฅ Failed after all retries: ${requestInfo.url}`);
// Custom error handling based on endpoint
if (requestInfo.url.includes('/api/payments')) {
showPaymentErrorDialog();
} else {
showGenericErrorMessage();
}
}
});
import { createApiInterceptor } from 'universal-api-retry-interceptor';
// Create separate interceptors for different services
const criticalApiInterceptor = createApiInterceptor({
maxRetries: 5,
delayTime: 3000
});
const regularApiInterceptor = createApiInterceptor({
maxRetries: 2,
delayTime: 1000
});
// Start/stop as needed
criticalApiInterceptor.start();
// ... later
criticalApiInterceptor.stop();
const interceptor = startGlobalInterceptor({ maxRetries: 3 });
// Update configuration based on network conditions
navigator.connection?.addEventListener('change', () => {
if (navigator.connection.effectiveType === 'slow-2g') {
interceptor.updateConfig({
maxRetries: 5, // More retries on slow networks
delayTime: 5000 // Longer delays
});
}
});
startGlobalInterceptor({
maxRetries: 3,
onRetry: (error, retryCount, requestInfo) => {
// Send analytics
analytics.track('API_Retry', {
url: requestInfo.url,
attempt: retryCount,
error: error?.message
});
},
onMaxRetriesExceeded: (error, requestInfo) => {
// Alert monitoring system
monitoring.alert('API_Failure', {
url: requestInfo.url,
finalError: error?.message,
timestamp: Date.now()
});
}
});
// Enable comprehensive logging
startGlobalInterceptor({
enableLogging: true, // See all interceptor activity
onRetry: (error, retryCount, requestInfo) => {
console.log(`๐ Retry ${retryCount}: ${requestInfo.url}`, {
error: error?.message,
options: requestInfo.options
});
}
});
const interceptor = startGlobalInterceptor();
// Monitor interceptor status
setInterval(() => {
const status = interceptor.getStatus();
console.log('Interceptor Status:', {
active: status.isActive,
online: status.isOnline,
pending: status.pendingRequests
});
}, 5000);
// Simulate offline mode for testing
function simulateOfflineTest() {
// Go offline
window.dispatchEvent(new Event('offline'));
// Make requests (they'll be stored)
fetch('/api/test-data');
axios.get('/api/users');
// Go online after 5 seconds (stored requests will execute)
setTimeout(() => {
window.dispatchEvent(new Event('online'));
}, 5000);
}
startGlobalInterceptor(config?: RetryConfig): UniversalApiRetryInterceptor
Starts a global interceptor that affects all HTTP requests.
stopGlobalInterceptor(): void
Stops the global interceptor and cleans up.
getGlobalInterceptor(): UniversalApiRetryInterceptor | null
Returns the global interceptor instance.
createApiInterceptor(config?: RetryConfig): UniversalApiRetryInterceptor
Creates a new interceptor instance without starting it.
interface RetryConfig {
delayTime?: number; // Delay between retries (ms)
retryInterval?: number; // Interval for checking pending requests (ms)
maxRetries?: number; // Maximum retry attempts
retryCondition?: (error: any, response?: Response) => boolean;
onRetry?: (error: any, retryCount: number, requestInfo: RequestMetadata) => void;
onMaxRetriesExceeded?: (error: any, requestInfo: RequestMetadata) => void;
enableLogging?: boolean; // Enable debug logging
}
start(): void
Starts the interceptor.
stop(): void
Stops the interceptor and cleans up.
getPendingRequestsCount(): number
Returns the number of pending retry requests.
clearPendingRequests(): void
Clears all pending requests.
updateConfig(config: Partial<RetryConfig>): void
Updates the interceptor configuration.
getStatus(): InterceptorStatus
Returns current status information.
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Your Code โโโโโถโ Interceptor โโโโโถโ HTTP Layer โ
โ โ โ โ โ โ
โ fetch() โ โ โข Retry Logic โ โ โข fetch โ
โ axios.get() โ โ โข Offline Queue โ โ โข XMLHttpRequestโ
โ $.ajax() โ โ โข Error Handling โ โ โข Axios โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
// The interceptor automatically handles these scenarios:
// Scenario 1: Temporary server error
fetch('/api/data') // โ 500 error โ retry 3 times โ eventually succeeds
// Scenario 2: Network timeout
axios.get('/api/slow') // โ timeout โ retry with backoff โ succeeds
// Scenario 3: Going offline
fetch('/api/update') // โ offline โ stored โ online โ executed
// Scenario 4: Rate limiting
$.ajax('/api/limited') // โ 429 error โ retry with delay โ succeeds
Feature | Chrome | Firefox | Safari | Edge | Mobile |
---|---|---|---|---|---|
Basic Retry | โ 15+ | โ 41+ | โ 5+ | โ 12+ | โ iOS 5+ |
Offline Detection | โ 15+ | โ 41+ | โ 5+ | โ 12+ | โ iOS 5+ |
Fetch Interception | โ 42+ | โ 39+ | โ 10+ | โ 14+ | โ iOS 10+ |
XHR Interception | โ 15+ | โ 41+ | โ 5+ | โ 12+ | โ iOS 5+ |
fetch
API (for fetch interception)XMLHttpRequest
(for XHR interception)navigator.onLine
(for offline detection)addEventListener
(for network events)// โ
Configure appropriate retry limits
startGlobalInterceptor({
maxRetries: 3, // Reasonable limit
delayTime: 1000 // Not too aggressive
});
// โ
Use custom retry conditions for different endpoints
retryCondition: (error, response) => {
if (url.includes('/payments/')) {
// More conservative for payments
return response?.status === 503; // Only service unavailable
}
// Standard retry for other endpoints
return response?.status >= 500;
}
// โ
Provide user feedback during retries
onRetry: (error, retryCount, requestInfo) => {
if (retryCount === 1) {
showToast('Connection issue detected, retrying...');
}
}
// โ
Handle final failures gracefully
onMaxRetriesExceeded: (error, requestInfo) => {
showErrorDialog('Unable to connect. Please check your connection.');
}
// โ Don't set retry limits too high
startGlobalInterceptor({
maxRetries: 50, // Too many retries
delayTime: 100 // Too aggressive
});
// โ Don't retry everything blindly
retryCondition: () => true; // This will retry 4xx errors unnecessarily
// โ Don't ignore user experience
onRetry: () => {
// No user feedback during retries
}
// โ Don't forget to handle edge cases
// Always provide onMaxRetriesExceeded handler
// โ
Don't retry authentication failures
retryCondition: (error, response) => {
if (response?.status === 401 || response?.status === 403) {
return false; // Don't retry auth errors
}
return response?.status >= 500;
}
// โ
Be careful with sensitive data in logs
enableLogging: process.env.NODE_ENV !== 'production'
// โ
Implement proper timeout handling
// The interceptor respects your original timeout settings
function useApiInterceptor() {
const [retryState, setRetryState] = useState(null);
useEffect(() => {
startGlobalInterceptor({
maxRetries: 3,
onRetry: (error, retryCount, requestInfo) => {
setRetryState({
url: requestInfo.url,
attempt: retryCount,
maxAttempts: 3
});
},
onMaxRetriesExceeded: () => {
setRetryState(null);
}
});
}, []);
return retryState;
}
function App() {
const retryState = useApiInterceptor();
return (
<div>
{retryState && (
<RetryBanner
url={retryState.url}
attempt={retryState.attempt}
maxAttempts={retryState.maxAttempts}
/>
)}
<YourApp />
</div>
);
}
// Gracefully enhance existing error handling
const originalErrorHandler = window.onerror;
startGlobalInterceptor({
onMaxRetriesExceeded: (error, requestInfo) => {
// Custom handling first
handleApiFailure(requestInfo.url, error);
// Fall back to original handler if needed
if (originalErrorHandler) {
originalErrorHandler(error.message, requestInfo.url, 0, 0, error);
}
}
});
// The interceptor is memory efficient:
// - Stores only essential request metadata
// - Automatically cleans up completed requests
// - No localStorage pollution
// - Configurable request limits
const interceptor = startGlobalInterceptor({
maxRetries: 3 // Limits memory usage per request
});
// Monitor memory usage
console.log(`Pending requests: ${interceptor.getPendingRequestsCount()}`);
// Implement intelligent backoff
startGlobalInterceptor({
delayTime: 1000, // Start with 1 second
retryCondition: (error, response) => {
// Don't retry client errors (saves bandwidth)
if (response?.status >= 400 && response?.status < 500) {
return false;
}
return response?.status >= 500;
}
});
// Use connection-aware configuration
if (navigator.connection?.effectiveType === '4g') {
// Faster retries on good connections
interceptor.updateConfig({ delayTime: 500 });
} else {
// Slower retries on poor connections
interceptor.updateConfig({ delayTime: 3000 });
}
// Solution: Ensure the library uses fetch or XMLHttpRequest under the hood
// Most libraries do, but some custom implementations might not be intercepted
// Check what your library uses:
console.log('Library uses fetch:', typeof fetch !== 'undefined');
console.log('Library uses XHR:', typeof XMLHttpRequest !== 'undefined');
// Solution: Check your retry conditions
startGlobalInterceptor({
enableLogging: true, // Enable to see what's happening
retryCondition: (error, response) => {
console.log('Retry condition check:', { error, response });
return true; // Temporarily retry everything for debugging
}
});
// Solution: Optimize your configuration
startGlobalInterceptor({
maxRetries: 2, // Reduce retry count
delayTime: 2000, // Increase delay
retryCondition: (error, response) => {
// Be more selective about what to retry
return response?.status === 503; // Only service unavailable
}
});
enableLogging: true
getStatus()
methodretryCondition
for debuggingWe love contributions! Here's how you can help make this project even better:
# Fork and clone the repository
git clone https://github.com/asuraking1n/universal-api-retry-interceptor.git
cd universal-api-retry-interceptor
# Install dependencies
npm install
# Run tests
npm test
# Build the project
npm run build
# Test your changes with the example
cd example-react-app
npm install
npm start
Transform this into the definitive network resilience solution for web applications, providing:
MIT ยฉ Nishant Kumar Tiwari
This project is licensed under the MIT License - see the LICENSE file for details.
If this project helped you, please consider:
Made with โค๏ธ by Nishant Kumar Tiwari
Empowering developers with bulletproof network resilience ๐
FAQs
A universal API interceptor that works with fetch, axios, and any HTTP library with retry logic and offline support
The npm package universal-api-retry-interceptor receives a total of 0 weekly downloads. As such, universal-api-retry-interceptor popularity was classified as not popular.
We found that universal-api-retry-interceptor demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
/Research
Malicious npm package impersonates Nodemailer and drains wallets by hijacking crypto transactions across multiple blockchains.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socketโs AI scanner detected the supply chain attack and flagged the malware.