(request, info)
| 794 | else if (b.state === 'CLOSED') b.failureCount = Math.max(0, b.failureCount - 1); |
| 795 | } else { |
| 796 | b.failureCount++; |
| 797 | b.lastFailureTime = Date.now(); |
| 798 | if (b.failureCount >= CIRCUIT_BREAKER.FAILURE_THRESHOLD) b.state = 'OPEN'; |
| 799 | } |
| 800 | } |
| 801 | |
| 802 | function validateBotToken(token) { |
| 803 | token = decodePathSegment(token); |
| 804 | |
| 805 | const cached = tokenValidationCache.get(token); |
| 806 | if (cached && Date.now() < cached.expires) return cached.valid; |
| 807 | |
| 808 | if (tokenValidationCache.size >= CACHE_MAX_SIZE) { |
| 809 | tokenValidationCache.delete(tokenValidationCache.keys().next().value); |
| 810 | } |
| 811 | |
| 812 | let valid = false; |
| 813 | if (token && token.length >= 35 && token.length <= 200 && token.includes(':')) { |
| 814 | const [botId, botHash] = token.split(':'); |
| 815 | valid = !!( |
| 816 | botId && botHash && |
| 817 | botId.length >= 5 && botHash.length >= 25 && |
| 818 | /^\d+$/.test(botId) && /^[A-Za-z0-9_-]+$/.test(botHash) |
| 819 | ); |
| 820 | } |
| 821 | |
| 822 | tokenValidationCache.set(token, { valid, expires: Date.now() + CACHE_TTL }); |
| 823 | return valid; |
| 824 | } |
| 825 | |
| 826 | async function proxyWithRetry(request, info) { |
| 827 | let lastError; |
| 828 | const canRetry = !info.isFile && (request.method === 'GET' || request.method === 'HEAD'); |
| 829 | const maxRetries = canRetry ? RETRY_CONFIG.MAX_RETRIES : 0; |
| 830 | |
| 831 | for (let attempt = 0; attempt <= maxRetries; attempt++) { |
| 832 | try { |
| 833 | if (attempt > 0) { |
| 834 | stats.retries++; |
| 835 | const delay = Math.min( |
| 836 | RETRY_CONFIG.INITIAL_DELAY * Math.pow(RETRY_CONFIG.BACKOFF_FACTOR, attempt - 1), |
| 837 | RETRY_CONFIG.MAX_DELAY |
| 838 | ); |
| 839 | await new Promise(r => setTimeout(r, delay)); |
| 840 | } |
| 841 | |
| 842 | const response = await proxyToTelegram(request, info); |
| 843 | if (response.ok || response.status < 500) return response; |
| 844 | |
| 845 | lastError = new Error('HTTP ' + response.status); |
| 846 | |
| 847 | } catch (error) { |
| 848 | lastError = error; |
| 849 | if (error.name === 'AbortError') continue; |
| 850 | if (attempt === maxRetries) throw error; |
| 851 | } |
| 852 | } |
| 853 |
no test coverage detected