snipe/web/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js
pyr0ball 7a704441a6 feat(snipe): Vue 3 frontend scaffold + Docker web service
- web/: Vue 3 + Vite + UnoCSS + Pinia, dark tactical theme (amber/#0d1117)
- AppNav, ListingCard, SearchView with filters/sort, composables
  (useSnipeMode, useKonamiCode, useMotion), Pinia search store
- Steal shimmer, auction countdown, Snipe Mode easter egg all native in Vue
- docker/web/: nginx + multi-stage Dockerfile (node build → nginx serve)
- compose.yml: api (8510) + web (8509) services
- Dockerfile CMD updated to uvicorn for upcoming FastAPI layer
- Clean build: 0 TS errors, 380 modules
2026-03-25 15:11:35 -07:00

249 lines
7.1 KiB
JavaScript

'use strict'
const net = require('node:net')
const { URL } = require('node:url')
let tls // include tls conditionally since it is not always available
const DispatcherBase = require('./dispatcher-base')
const { InvalidArgumentError } = require('../core/errors')
const { Socks5Client } = require('../core/socks5-client')
const { kDispatch, kClose, kDestroy } = require('../core/symbols')
const Pool = require('./pool')
const buildConnector = require('../core/connect')
const { debuglog } = require('node:util')
const debug = debuglog('undici:socks5-proxy')
const kProxyUrl = Symbol('proxy url')
const kProxyHeaders = Symbol('proxy headers')
const kProxyAuth = Symbol('proxy auth')
const kPool = Symbol('pool')
const kConnector = Symbol('connector')
// Static flag to ensure warning is only emitted once per process
let experimentalWarningEmitted = false
/**
* SOCKS5 proxy agent for dispatching requests through a SOCKS5 proxy
*/
class Socks5ProxyAgent extends DispatcherBase {
constructor (proxyUrl, options = {}) {
super()
// Emit experimental warning only once
if (!experimentalWarningEmitted) {
process.emitWarning(
'SOCKS5 proxy support is experimental and subject to change',
'ExperimentalWarning'
)
experimentalWarningEmitted = true
}
if (!proxyUrl) {
throw new InvalidArgumentError('Proxy URL is mandatory')
}
// Parse proxy URL
const url = typeof proxyUrl === 'string' ? new URL(proxyUrl) : proxyUrl
if (url.protocol !== 'socks5:' && url.protocol !== 'socks:') {
throw new InvalidArgumentError('Proxy URL must use socks5:// or socks:// protocol')
}
this[kProxyUrl] = url
this[kProxyHeaders] = options.headers || {}
// Extract auth from URL or options
this[kProxyAuth] = {
username: options.username || (url.username ? decodeURIComponent(url.username) : null),
password: options.password || (url.password ? decodeURIComponent(url.password) : null)
}
// Create connector for proxy connection
this[kConnector] = options.connect || buildConnector({
...options.proxyTls,
servername: options.proxyTls?.servername || url.hostname
})
// Pool for the actual HTTP connections (with SOCKS5 tunnel connect function)
this[kPool] = null
}
/**
* Create a SOCKS5 connection to the proxy
*/
async createSocks5Connection (targetHost, targetPort) {
const proxyHost = this[kProxyUrl].hostname
const proxyPort = parseInt(this[kProxyUrl].port) || 1080
debug('creating SOCKS5 connection to', proxyHost, proxyPort)
// Connect to the SOCKS5 proxy
const socket = await new Promise((resolve, reject) => {
const onConnect = () => {
socket.removeListener('error', onError)
resolve(socket)
}
const onError = (err) => {
socket.removeListener('connect', onConnect)
reject(err)
}
const socket = net.connect({
host: proxyHost,
port: proxyPort
})
socket.once('connect', onConnect)
socket.once('error', onError)
})
// Create SOCKS5 client
const socks5Client = new Socks5Client(socket, this[kProxyAuth])
// Handle SOCKS5 errors
socks5Client.on('error', (err) => {
debug('SOCKS5 error:', err)
socket.destroy()
})
// Perform SOCKS5 handshake
await socks5Client.handshake()
// Wait for authentication (if required)
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('SOCKS5 authentication timeout'))
}, 5000)
const onAuthenticated = () => {
clearTimeout(timeout)
socks5Client.removeListener('error', onError)
resolve()
}
const onError = (err) => {
clearTimeout(timeout)
socks5Client.removeListener('authenticated', onAuthenticated)
reject(err)
}
// Check if already authenticated (for NO_AUTH method)
if (socks5Client.state === 'authenticated') {
clearTimeout(timeout)
resolve()
} else {
socks5Client.once('authenticated', onAuthenticated)
socks5Client.once('error', onError)
}
})
// Send CONNECT command
await socks5Client.connect(targetHost, targetPort)
// Wait for connection
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('SOCKS5 connection timeout'))
}, 5000)
const onConnected = (info) => {
debug('SOCKS5 tunnel established to', targetHost, targetPort, 'via', info)
clearTimeout(timeout)
socks5Client.removeListener('error', onError)
resolve()
}
const onError = (err) => {
clearTimeout(timeout)
socks5Client.removeListener('connected', onConnected)
reject(err)
}
socks5Client.once('connected', onConnected)
socks5Client.once('error', onError)
})
return socket
}
/**
* Dispatch a request through the SOCKS5 proxy
*/
async [kDispatch] (opts, handler) {
const { origin } = opts
debug('dispatching request to', origin, 'via SOCKS5')
try {
// Create Pool with custom connect function if we don't have one yet
if (!this[kPool] || this[kPool].destroyed || this[kPool].closed) {
this[kPool] = new Pool(origin, {
pipelining: opts.pipelining,
connections: opts.connections,
connect: async (connectOpts, callback) => {
try {
const url = new URL(origin)
const targetHost = url.hostname
const targetPort = parseInt(url.port) || (url.protocol === 'https:' ? 443 : 80)
debug('establishing SOCKS5 connection to', targetHost, targetPort)
// Create SOCKS5 tunnel
const socket = await this.createSocks5Connection(targetHost, targetPort)
// Handle TLS if needed
let finalSocket = socket
if (url.protocol === 'https:') {
if (!tls) {
tls = require('node:tls')
}
debug('upgrading to TLS')
finalSocket = tls.connect({
socket,
servername: targetHost,
...connectOpts.tls || {}
})
await new Promise((resolve, reject) => {
finalSocket.once('secureConnect', resolve)
finalSocket.once('error', reject)
})
}
callback(null, finalSocket)
} catch (err) {
debug('SOCKS5 connection error:', err)
callback(err)
}
}
})
}
// Dispatch the request through the pool
return this[kPool][kDispatch](opts, handler)
} catch (err) {
debug('dispatch error:', err)
if (typeof handler.onError === 'function') {
handler.onError(err)
} else {
throw err
}
}
}
async [kClose] () {
if (this[kPool]) {
await this[kPool].close()
}
}
async [kDestroy] (err) {
if (this[kPool]) {
await this[kPool].destroy(err)
}
}
}
module.exports = Socks5ProxyAgent