UNPKG

apx-toolkit

Version:

Automatically discover APIs and generate complete integration packages: code in 12 languages, TypeScript types, test suites, SDK packages, API documentation, mock servers, performance reports, and contract tests. Saves 2-4 weeks of work in seconds.

163 lines (144 loc) 4.45 kB
/** * Rate Limit Detector * Automatically detects rate limiting information from API responses * Makes the tool a developer's dream by providing rate limit awareness */ import type { Response } from 'playwright'; import type { RateLimitInfo } from '../types.js'; /** * Detects rate limiting information from response headers */ export function detectRateLimits(response: Response): RateLimitInfo | undefined { const headers = response.headers(); const rateLimitInfo: Partial<RateLimitInfo> = {}; // Common rate limit header patterns const headerPatterns = { limit: [ 'x-ratelimit-limit', 'x-rate-limit-limit', 'ratelimit-limit', 'rate-limit-limit', ], remaining: [ 'x-ratelimit-remaining', 'x-rate-limit-remaining', 'ratelimit-remaining', 'rate-limit-remaining', ], reset: [ 'x-ratelimit-reset', 'x-rate-limit-reset', 'ratelimit-reset', 'rate-limit-reset', ], resetAfter: [ 'x-ratelimit-reset-after', 'x-rate-limit-reset-after', 'retry-after', ], retryAfter: [ 'retry-after', 'x-retry-after', ], }; // Check for limit for (const pattern of headerPatterns.limit) { const value = findHeader(headers, pattern); if (value) { const num = parseInt(value, 10); if (!isNaN(num)) { rateLimitInfo.limit = num; break; } } } // Check for remaining for (const pattern of headerPatterns.remaining) { const value = findHeader(headers, pattern); if (value) { const num = parseInt(value, 10); if (!isNaN(num)) { rateLimitInfo.remaining = num; break; } } } // Check for reset timestamp for (const pattern of headerPatterns.reset) { const value = findHeader(headers, pattern); if (value) { const num = parseInt(value, 10); if (!isNaN(num)) { rateLimitInfo.reset = num; break; } } } // Check for reset after (seconds) for (const pattern of headerPatterns.resetAfter) { const value = findHeader(headers, pattern); if (value) { const num = parseFloat(value); if (!isNaN(num)) { rateLimitInfo.resetAfter = num; break; } } } // Check for retry after for (const pattern of headerPatterns.retryAfter) { const value = findHeader(headers, pattern); if (value) { const num = parseFloat(value); if (!isNaN(num)) { rateLimitInfo.retryAfter = num; break; } } } // Only return if we found at least one rate limit indicator if (Object.keys(rateLimitInfo).length > 0) { return rateLimitInfo as RateLimitInfo; } return undefined; } /** * Finds header value (case-insensitive) */ function findHeader(headers: Record<string, string>, pattern: string): string | undefined { const lowerPattern = pattern.toLowerCase(); // Direct match if (headers[lowerPattern]) { return headers[lowerPattern]; } // Case-insensitive search for (const [key, value] of Object.entries(headers)) { if (key.toLowerCase() === lowerPattern) { return value; } } return undefined; } /** * Formats rate limit info for documentation */ export function formatRateLimitInfo(rateLimit: RateLimitInfo): string { const parts: string[] = []; if (rateLimit.limit !== undefined) { parts.push(`Limit: ${rateLimit.limit}`); } if (rateLimit.remaining !== undefined) { parts.push(`Remaining: ${rateLimit.remaining}`); } if (rateLimit.reset !== undefined) { const resetDate = new Date(rateLimit.reset * 1000); parts.push(`Resets at: ${resetDate.toISOString()}`); } if (rateLimit.resetAfter !== undefined) { parts.push(`Resets in: ${rateLimit.resetAfter}s`); } if (rateLimit.retryAfter !== undefined) { parts.push(`Retry after: ${rateLimit.retryAfter}s`); } return parts.join(', '); }