- Add full Telegram bot functionality with Z.AI API integration
- Implement 4 tools: Bash, FileEdit, WebSearch, Git
- Add 3 agents: Code Reviewer, Architect, DevOps Engineer
- Add 6 skills for common coding tasks
- Add systemd service file for 24/7 operation
- Add nginx configuration for HTTPS webhook
- Add comprehensive documentation
- Implement WebSocket server for real-time updates
- Add logging system with Winston
- Add environment validation
🤖 zCode CLI X - Agentic coder with Z.AI + Telegram integration
154 lines
6.6 KiB
JavaScript
154 lines
6.6 KiB
JavaScript
import { createServer } from '@pondwader/socks5-server';
|
|
import { logForDebugging } from '../utils/debug.js';
|
|
import { connectViaParentProxy, dialDirect, isValidHost, selectParentProxyUrl, shouldBypassParentProxy, } from './parent-proxy.js';
|
|
export function createSocksProxyServer(options) {
|
|
const socksServer = createServer();
|
|
socksServer.setRulesetValidator(async (conn) => {
|
|
try {
|
|
const hostname = conn.destAddress;
|
|
const port = conn.destPort;
|
|
// SOCKS5 DOMAINNAME is a raw length-prefixed byte string with zero
|
|
// validation from the protocol or the library. Reject control chars
|
|
// (null bytes, CRLF) here so they never reach the allowlist matcher,
|
|
// where string suffix matching would be trivially fooled.
|
|
if (!isValidHost(hostname)) {
|
|
logForDebugging(`Rejecting malformed SOCKS host: ${JSON.stringify(hostname)}`, { level: 'error' });
|
|
return false;
|
|
}
|
|
logForDebugging(`Connection request to ${hostname}:${port}`);
|
|
const allowed = await options.filter(port, hostname);
|
|
if (!allowed) {
|
|
logForDebugging(`Connection blocked to ${hostname}:${port}`, {
|
|
level: 'error',
|
|
});
|
|
return false;
|
|
}
|
|
logForDebugging(`Connection allowed to ${hostname}:${port}`);
|
|
return true;
|
|
}
|
|
catch (error) {
|
|
logForDebugging(`Error validating connection: ${error}`, {
|
|
level: 'error',
|
|
});
|
|
return false;
|
|
}
|
|
});
|
|
// Override the default connection handler so we can route through a parent
|
|
// HTTP proxy when one is configured. The default handler does a straight
|
|
// net.connect() which fails when direct egress is blocked.
|
|
socksServer.setConnectionHandler((conn, sendStatus) => {
|
|
const host = conn.destAddress;
|
|
const port = conn.destPort;
|
|
// Track client liveness so we can abort the upstream dial if they bail.
|
|
let clientGone = false;
|
|
let upstreamRef;
|
|
conn.socket.once('close', () => {
|
|
clientGone = true;
|
|
upstreamRef?.destroy();
|
|
});
|
|
conn.socket.on('error', () => upstreamRef?.destroy());
|
|
// SOCKS is an opaque TCP tunnel — semantically identical to HTTP
|
|
// CONNECT — so always prefer HTTPS_PROXY if set, regardless of dest port.
|
|
const parentUrl = options.parentProxy && !shouldBypassParentProxy(options.parentProxy, host)
|
|
? selectParentProxyUrl(options.parentProxy, { isHttps: true })
|
|
: undefined;
|
|
const open = parentUrl
|
|
? connectViaParentProxy(parentUrl, host, port)
|
|
: dialDirect(host, port);
|
|
open
|
|
.then(upstream => {
|
|
upstreamRef = upstream;
|
|
upstream.on('error', () => conn.socket.destroy());
|
|
if (clientGone) {
|
|
upstream.destroy();
|
|
return;
|
|
}
|
|
sendStatus('REQUEST_GRANTED');
|
|
upstream.pipe(conn.socket);
|
|
conn.socket.pipe(upstream);
|
|
upstream.on('close', () => conn.socket.destroy());
|
|
})
|
|
.catch(err => {
|
|
logForDebugging(`SOCKS connect to ${host}:${port} failed: ${err.message}`, { level: 'error' });
|
|
if (!clientGone) {
|
|
try {
|
|
sendStatus('HOST_UNREACHABLE');
|
|
}
|
|
catch {
|
|
// socket may have closed between the check and the write
|
|
}
|
|
}
|
|
});
|
|
});
|
|
return {
|
|
server: socksServer,
|
|
getPort() {
|
|
// Access the internal server to get the port
|
|
// We need to use type assertion here as the server property is private
|
|
try {
|
|
const serverInternal = socksServer?.server;
|
|
if (serverInternal && typeof serverInternal?.address === 'function') {
|
|
const address = serverInternal.address();
|
|
if (address && typeof address === 'object' && 'port' in address) {
|
|
return address.port;
|
|
}
|
|
}
|
|
}
|
|
catch (error) {
|
|
// Server might not be listening yet or property access failed
|
|
logForDebugging(`Error getting port: ${error}`, { level: 'error' });
|
|
}
|
|
return undefined;
|
|
},
|
|
listen(port, hostname) {
|
|
return new Promise((resolve, reject) => {
|
|
const serverInternal = socksServer?.server;
|
|
serverInternal?.once('error', reject);
|
|
const listeningCallback = () => {
|
|
serverInternal?.removeListener('error', reject);
|
|
const actualPort = this.getPort();
|
|
if (actualPort) {
|
|
logForDebugging(`SOCKS proxy listening on ${hostname}:${actualPort}`);
|
|
resolve(actualPort);
|
|
}
|
|
else {
|
|
reject(new Error('Failed to get SOCKS proxy server port'));
|
|
}
|
|
};
|
|
socksServer.listen(port, hostname, listeningCallback);
|
|
});
|
|
},
|
|
async close() {
|
|
return new Promise((resolve, reject) => {
|
|
socksServer.close(error => {
|
|
if (error) {
|
|
// Only reject for actual errors, not for "already closed" states
|
|
// Check for common "already closed" error patterns
|
|
const errorMessage = error.message?.toLowerCase() || '';
|
|
const isAlreadyClosed = errorMessage.includes('not running') ||
|
|
errorMessage.includes('already closed') ||
|
|
errorMessage.includes('not listening');
|
|
if (!isAlreadyClosed) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
},
|
|
unref() {
|
|
// Access the internal server to call unref
|
|
try {
|
|
const serverInternal = socksServer?.server;
|
|
if (serverInternal && typeof serverInternal?.unref === 'function') {
|
|
serverInternal.unref();
|
|
}
|
|
}
|
|
catch (error) {
|
|
logForDebugging(`Error calling unref: ${error}`, { level: 'error' });
|
|
}
|
|
},
|
|
};
|
|
}
|
|
//# sourceMappingURL=socks-proxy.js.map
|