176 lines
5.5 KiB
TypeScript
176 lines
5.5 KiB
TypeScript
/**
|
|
* Proxy Check Operations - Checking proxy functionality
|
|
*/
|
|
import { OperationContext } from '@stock-bot/di';
|
|
import { getLogger } from '@stock-bot/logger';
|
|
import type { ProxyInfo } from '@stock-bot/proxy';
|
|
import { fetch } from '@stock-bot/utils';
|
|
import { PROXY_CONFIG } from '../shared/config';
|
|
|
|
/**
|
|
* Check if a proxy is working
|
|
*/
|
|
export async function checkProxy(proxy: ProxyInfo): Promise<ProxyInfo> {
|
|
const ctx = {
|
|
logger: getLogger('proxy-check'),
|
|
resolve: <T>(_name: string) => {
|
|
throw new Error(`Service container not available for proxy operations`);
|
|
},
|
|
} as any;
|
|
|
|
let success = false;
|
|
ctx.logger.debug(`Checking Proxy:`, {
|
|
protocol: proxy.protocol,
|
|
host: proxy.host,
|
|
port: proxy.port,
|
|
});
|
|
|
|
try {
|
|
// Test the proxy using fetch with proxy support
|
|
const proxyUrl =
|
|
proxy.username && proxy.password
|
|
? `${proxy.protocol}://${encodeURIComponent(proxy.username)}:${encodeURIComponent(proxy.password)}@${proxy.host}:${proxy.port}`
|
|
: `${proxy.protocol}://${proxy.host}:${proxy.port}`;
|
|
|
|
const response = await fetch(PROXY_CONFIG.CHECK_URL, {
|
|
proxy: proxyUrl,
|
|
signal: AbortSignal.timeout(PROXY_CONFIG.CHECK_TIMEOUT),
|
|
logger: ctx.logger,
|
|
} as any);
|
|
|
|
const data = await response.text();
|
|
|
|
const isWorking = response.ok;
|
|
const result: ProxyInfo = {
|
|
...proxy,
|
|
isWorking,
|
|
lastChecked: new Date(),
|
|
};
|
|
|
|
if (isWorking && !data.includes(PROXY_CONFIG.CHECK_IP)) {
|
|
success = true;
|
|
await updateProxyInCache(result, true, ctx);
|
|
} else {
|
|
await updateProxyInCache(result, false, ctx);
|
|
}
|
|
|
|
if (proxy.source) {
|
|
updateProxyStats(proxy.source, success, ctx);
|
|
}
|
|
|
|
ctx.logger.debug('Proxy check completed', {
|
|
host: proxy.host,
|
|
port: proxy.port,
|
|
isWorking,
|
|
});
|
|
|
|
return result;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
const result: ProxyInfo = {
|
|
...proxy,
|
|
isWorking: false,
|
|
error: errorMessage,
|
|
lastChecked: new Date(),
|
|
};
|
|
|
|
// Update cache for failed proxy (increment total, don't update TTL)
|
|
await updateProxyInCache(result, false, ctx);
|
|
|
|
if (proxy.source) {
|
|
updateProxyStats(proxy.source, success, ctx);
|
|
}
|
|
|
|
ctx.logger.debug('Proxy check failed', {
|
|
host: proxy.host,
|
|
port: proxy.port,
|
|
error: errorMessage,
|
|
});
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update proxy data in cache with working/total stats and average response time
|
|
*/
|
|
async function updateProxyInCache(
|
|
proxy: ProxyInfo,
|
|
isWorking: boolean,
|
|
ctx: OperationContext
|
|
): Promise<void> {
|
|
const _cacheKey = `${PROXY_CONFIG.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`;
|
|
|
|
try {
|
|
// For now, skip cache operations without service container
|
|
// TODO: Pass service container to operations
|
|
const existing: ProxyInfo | null = null;
|
|
|
|
// For failed proxies, only update if they already exist
|
|
if (!isWorking && !existing) {
|
|
ctx.logger.debug('Proxy not in cache, skipping failed update', {
|
|
proxy: `${proxy.host}:${proxy.port}`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Calculate new average response time if we have a response time
|
|
let newAverageResponseTime = existing?.averageResponseTime;
|
|
if (proxy.responseTime !== undefined) {
|
|
const existingAvg = existing?.averageResponseTime || 0;
|
|
const existingTotal = existing?.total || 0;
|
|
|
|
// Calculate weighted average: (existing_avg * existing_count + new_response) / (existing_count + 1)
|
|
newAverageResponseTime =
|
|
existingTotal > 0
|
|
? (existingAvg * existingTotal + proxy.responseTime) / (existingTotal + 1)
|
|
: proxy.responseTime;
|
|
}
|
|
|
|
// Build updated proxy data
|
|
const updated = {
|
|
...existing,
|
|
...proxy, // Keep latest proxy info
|
|
total: (existing?.total || 0) + 1,
|
|
working: isWorking ? (existing?.working || 0) + 1 : existing?.working || 0,
|
|
isWorking,
|
|
lastChecked: new Date(),
|
|
// Add firstSeen only for new entries
|
|
...(existing ? {} : { firstSeen: new Date() }),
|
|
// Update average response time if we calculated a new one
|
|
...(newAverageResponseTime !== undefined
|
|
? { averageResponseTime: newAverageResponseTime }
|
|
: {}),
|
|
};
|
|
|
|
// Calculate success rate
|
|
updated.successRate = updated.total > 0 ? (updated.working / updated.total) * 100 : 0;
|
|
|
|
// Save to cache: reset TTL for working proxies, keep existing TTL for failed ones
|
|
const _cacheOptions = isWorking ? { ttl: PROXY_CONFIG.CACHE_TTL } : undefined;
|
|
// Skip cache operations without service container
|
|
// TODO: Pass service container to operations
|
|
|
|
ctx.logger.debug(`Updated ${isWorking ? 'working' : 'failed'} proxy in cache`, {
|
|
proxy: `${proxy.host}:${proxy.port}`,
|
|
working: updated.working,
|
|
total: updated.total,
|
|
successRate: updated.successRate.toFixed(1) + '%',
|
|
avgResponseTime: updated.averageResponseTime
|
|
? `${updated.averageResponseTime.toFixed(0)}ms`
|
|
: 'N/A',
|
|
});
|
|
} catch (error) {
|
|
ctx.logger.error('Failed to update proxy in cache', {
|
|
proxy: `${proxy.host}:${proxy.port}`,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
});
|
|
}
|
|
}
|
|
|
|
function updateProxyStats(sourceId: string, success: boolean, ctx: OperationContext) {
|
|
// Stats are now handled by the global ProxyManager
|
|
ctx.logger.debug('Proxy check result', { sourceId, success });
|
|
|
|
// TODO: Integrate with global ProxyManager stats if needed
|
|
}
|