|
|
import axios, { AxiosError } from 'axios';
|
|
|
|
|
|
export interface ApiError {
|
|
|
message: string;
|
|
|
details?: string;
|
|
|
code?: string;
|
|
|
}
|
|
|
|
|
|
interface ApiErrorResponse {
|
|
|
message?: string;
|
|
|
error?: string;
|
|
|
details?: string;
|
|
|
}
|
|
|
|
|
|
export const handleApiError = (error: unknown): ApiError => {
|
|
|
if (axios.isAxiosError(error)) {
|
|
|
const axiosError = error as AxiosError<ApiErrorResponse>;
|
|
|
const responseData = axiosError.response?.data;
|
|
|
return {
|
|
|
message: axiosError.message,
|
|
|
details: responseData?.message || responseData?.error || 'Unknown error occurred',
|
|
|
code: axiosError.code,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
if (error instanceof Error) {
|
|
|
return {
|
|
|
message: error.message,
|
|
|
details: error.stack,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
message: 'An unexpected error occurred',
|
|
|
details: String(error),
|
|
|
};
|
|
|
};
|
|
|
|
|
|
export const withRetry = async <T>(
|
|
|
operation: () => Promise<T>,
|
|
|
maxRetries: number = 3,
|
|
|
delay: number = 1000
|
|
|
): Promise<T> => {
|
|
|
let lastError: unknown;
|
|
|
|
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
|
try {
|
|
|
return await operation();
|
|
|
} catch (error) {
|
|
|
lastError = error;
|
|
|
if (i < maxRetries - 1) {
|
|
|
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
throw lastError;
|
|
|
};
|
|
|
|
|
|
export const validateInput = (input: string, maxLength: number = 100): boolean => {
|
|
|
if (!input || typeof input !== 'string') return false;
|
|
|
if (input.trim().length === 0) return false;
|
|
|
if (input.length > maxLength) return false;
|
|
|
return true;
|
|
|
};
|
|
|
|
|
|
export const sanitizeInput = (input: string): string => {
|
|
|
return input
|
|
|
.trim()
|
|
|
.replace(/[<>]/g, '')
|
|
|
.replace(/javascript:/gi, '')
|
|
|
.slice(0, 100);
|
|
|
};
|
|
|
|
|
|
|
|
|
export class RateLimiter {
|
|
|
private timestamps: number[] = [];
|
|
|
private readonly windowMs: number;
|
|
|
private readonly maxRequests: number;
|
|
|
|
|
|
constructor(windowMs: number = 60000, maxRequests: number = 100) {
|
|
|
this.windowMs = windowMs;
|
|
|
this.maxRequests = maxRequests;
|
|
|
}
|
|
|
|
|
|
canMakeRequest(): boolean {
|
|
|
const now = Date.now();
|
|
|
this.timestamps = this.timestamps.filter(time => now - time < this.windowMs);
|
|
|
|
|
|
if (this.timestamps.length >= this.maxRequests) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
this.timestamps.push(now);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
export const rateLimiter = new RateLimiter();
|
|
|
|
|
|
|
|
|
export const rateLimitedApiCall = async <T>(
|
|
|
apiCall: () => Promise<T>,
|
|
|
retryCount: number = 3
|
|
|
): Promise<T> => {
|
|
|
if (!rateLimiter.canMakeRequest()) {
|
|
|
throw new Error('Rate limit exceeded. Please try again later.');
|
|
|
}
|
|
|
|
|
|
return withRetry(apiCall, retryCount);
|
|
|
}; |