Files
OmniRoute/src/lib/proxyLogger.js
diegosouzapw 127cdc9143 feat(proxy): implement TLS fingerprint spoofing via wreq-js
- Add TlsClient module (Chrome 124 fingerprint via wreq-js)
- Integrate TLS client as opt-in layer in proxyFetch.js (ENABLE_TLS_FINGERPRINT)
- Add per-request TLS tracking via AsyncLocalStorage
- Add TLS fingerprint column and badge to Proxy Logger dashboard
- Add TLS Fingerprint section to ProxyLogDetail modal
- Add wreq-js dependency
- Document ENABLE_TLS_FINGERPRINT in .env.example

Adapted from upstream PR decolua/9router#137, preserving all
OmniRoute-specific proxy features (AsyncLocalStorage context,
proxyDispatcher, SOCKS5, Symbol-based patch state).
2026-02-16 17:12:24 -03:00

124 lines
3.5 KiB
JavaScript

/**
* Proxy Logger — In-memory ring buffer for proxy events
* Mirrors the call-log pattern used by RequestLoggerV2
*/
import { v4 as uuidv4 } from "uuid";
const MAX_ENTRIES = 500;
const proxyLogs = [];
/**
* Log a proxy event
* @param {Object} entry
* @param {"success"|"error"|"timeout"} entry.status
* @param {Object} entry.proxy - { type, host, port }
* @param {"key"|"combo"|"provider"|"global"|"direct"} entry.level
* @param {string} [entry.levelId]
* @param {string} [entry.provider]
* @param {string} [entry.targetUrl]
* @param {string} [entry.publicIp]
* @param {number} [entry.latencyMs]
* @param {string} [entry.error]
* @param {string} [entry.connectionId]
* @param {string} [entry.comboId]
*/
export function logProxyEvent(entry) {
const log = {
id: uuidv4(),
timestamp: new Date().toISOString(),
status: entry.status || "success",
proxy: entry.proxy || null,
level: entry.level || "direct",
levelId: entry.levelId || null,
provider: entry.provider || null,
targetUrl: entry.targetUrl || null,
publicIp: entry.publicIp || null,
latencyMs: entry.latencyMs || 0,
error: entry.error || null,
connectionId: entry.connectionId || null,
comboId: entry.comboId || null,
account: entry.account || null,
tlsFingerprint: entry.tlsFingerprint || false,
};
proxyLogs.unshift(log); // newest first
// Trim to max
if (proxyLogs.length > MAX_ENTRIES) {
proxyLogs.length = MAX_ENTRIES;
}
return log;
}
/**
* Get proxy logs with optional filters
* @param {Object} filters
* @param {string} [filters.status] - "success"|"error"|"timeout"
* @param {string} [filters.type] - "http"|"https"|"socks5"
* @param {string} [filters.provider]
* @param {string} [filters.level] - "key"|"combo"|"provider"|"global"|"direct"
* @param {string} [filters.search] - free text search
* @param {number} [filters.limit] - max results (default 300)
* @returns {Array}
*/
export function getProxyLogs(filters = {}) {
let logs = [...proxyLogs];
if (filters.status) {
if (filters.status === "ok") {
logs = logs.filter((l) => l.status === "success");
} else {
logs = logs.filter((l) => l.status === filters.status);
}
}
if (filters.type) {
logs = logs.filter((l) => l.proxy?.type === filters.type);
}
if (filters.provider) {
logs = logs.filter((l) => l.provider === filters.provider);
}
if (filters.level) {
logs = logs.filter((l) => l.level === filters.level);
}
if (filters.search) {
const q = filters.search.toLowerCase();
logs = logs.filter(
(l) =>
(l.proxy?.host || "").toLowerCase().includes(q) ||
(l.provider || "").toLowerCase().includes(q) ||
(l.targetUrl || "").toLowerCase().includes(q) ||
(l.publicIp || "").toLowerCase().includes(q) ||
(l.level || "").toLowerCase().includes(q) ||
(l.error || "").toLowerCase().includes(q) ||
(l.account || "").toLowerCase().includes(q)
);
}
const limit = filters.limit || 300;
return logs.slice(0, limit);
}
/**
* Clear all proxy logs
*/
export function clearProxyLogs() {
proxyLogs.length = 0;
}
/**
* Get proxy log stats
*/
export function getProxyLogStats() {
const total = proxyLogs.length;
const success = proxyLogs.filter((l) => l.status === "success").length;
const error = proxyLogs.filter((l) => l.status === "error").length;
const timeout = proxyLogs.filter((l) => l.status === "timeout").length;
const direct = proxyLogs.filter((l) => l.level === "direct").length;
return { total, success, error, timeout, direct };
}