Skip to content

Commit

Permalink
feat(major): drop simple-get (#443)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: drop simple-get

* perf: drop simple-get

* feat: undici agent and socks

* fix: undici as dev dependency

* feat: require user passed proxy objects for http and ws

* chore: include undici for tests
  • Loading branch information
ThaUnknown authored Oct 31, 2023
1 parent e14738b commit bce64e1
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 230 deletions.
61 changes: 19 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ npm install bittorrent-tracker
To connect to a tracker, just do this:

```js
var Client = require('bittorrent-tracker')
import Client from 'bittorrent-tracker'

var requiredOpts = {
const requiredOpts = {
infoHash: new Buffer('012345678901234567890'), // hex string or Buffer
peerId: new Buffer('01234567890123456789'), // hex string or Buffer
announce: [], // list of tracker server urls
port: 6881 // torrent client port, (in browser, optional)
}

var optionalOpts = {
const optionalOpts = {
// RTCPeerConnection config object (only used in browser)
rtcConfig: {},
// User-Agent header for http requests
Expand All @@ -81,47 +81,24 @@ var optionalOpts = {
customParam: 'blah' // custom parameters supported
}
},
// Proxy config object
// Proxy options (used to proxy requests in node)
proxyOpts: {
// Socks proxy options (used to proxy requests in node)
socksProxy: {
// Configuration from socks module (https://github.com/JoshGlazebrook/socks)
proxy: {
// IP Address of Proxy (Required)
ipaddress: "1.2.3.4",
// TCP Port of Proxy (Required)
port: 1080,
// Proxy Type [4, 5] (Required)
// Note: 4 works for both 4 and 4a.
// Type 4 does not support UDP association relay
type: 5,

// SOCKS 4 Specific:

// UserId used when making a SOCKS 4/4a request. (Optional)
userid: "someuserid",

// SOCKS 5 Specific:

// Authentication used for SOCKS 5 (when it's required) (Optional)
authentication: {
username: "Josh",
password: "somepassword"
}
},

// Amount of time to wait for a connection to be established. (Optional)
// - defaults to 10000ms (10 seconds)
timeout: 10000
},
// NodeJS HTTP agents (used to proxy HTTP and Websocket requests in node)
// Populated with Socks.Agent if socksProxy is provided
httpAgent: {},
httpsAgent: {}
// For WSS trackers this is always a http.Agent
// For UDP trackers this is an object of options for the Socks Connection
// For HTTP trackers this is either an undici Agent if using Node16 or later, or http.Agent if using versions prior to Node 16, ex:
// import Socks from 'socks'
// proxyOpts.socksProxy = new Socks.Agent(optionsObject, isHttps)
// or if using Node 16 or later
// import { socksDispatcher } from 'fetch-socks'
// proxyOpts.socksProxy = socksDispatcher(optionsObject)
socksProxy: new SocksProxy(socksOptionsObject),
// Populated with socksProxy if it's provided
httpAgent: new http.Agent(agentOptionsObject),
httpsAgent: new https.Agent(agentOptionsObject)
},
}

var client = new Client(requiredOpts)
const client = new Client(requiredOpts)

client.on('error', function (err) {
// fatal client error!
Expand Down Expand Up @@ -182,7 +159,7 @@ client.on('scrape', function (data) {
To start a BitTorrent tracker server to track swarms of peers:

```js
const Server = require('bittorrent-tracker').Server
import { Server } from 'bittorrent-tracker'

const server = new Server({
udp: true, // enable udp server? [default=true]
Expand Down Expand Up @@ -289,7 +266,7 @@ The http server will handle requests for the following paths: `/announce`, `/scr
Scraping multiple torrent info is possible with a static `Client.scrape` method:

```js
var Client = require('bittorrent-tracker')
import Client from 'bittorrent-tracker'
Client.scrape({ announce: announceUrl, infoHash: [ infoHash1, infoHash2 ]}, function (err, results) {
results[infoHash1].announce
results[infoHash1].infoHash
Expand Down
108 changes: 58 additions & 50 deletions lib/client/http-tracker.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import arrayRemove from 'unordered-array-remove'
import bencode from 'bencode'
import clone from 'clone'
import Debug from 'debug'
import get from 'simple-get'
import Socks from 'socks'
import fetch from 'cross-fetch-ponyfill'
import { bin2hex, hex2bin, arr2text, text2arr, arr2hex } from 'uint8-util'

import common from '../common.js'
Expand All @@ -13,6 +11,14 @@ import compact2string from 'compact2string'
const debug = Debug('bittorrent-tracker:http-tracker')
const HTTP_SCRAPE_SUPPORT = /\/(announce)[^/]*$/

function abortTimeout (ms) {
const controller = new AbortController()
setTimeout(() => {
controller.abort()
}, ms).unref?.()
return controller
}

/**
* HTTP torrent tracker client (for an individual tracker)
*
Expand Down Expand Up @@ -112,70 +118,72 @@ class HTTPTracker extends Tracker {
}
}

_request (requestUrl, params, cb) {
const self = this
async _request (requestUrl, params, cb) {
const parsedUrl = new URL(requestUrl + (requestUrl.indexOf('?') === -1 ? '?' : '&') + common.querystringStringify(params))
let agent
if (this.client._proxyOpts) {
agent = parsedUrl.protocol === 'https:' ? this.client._proxyOpts.httpsAgent : this.client._proxyOpts.httpAgent
if (!agent && this.client._proxyOpts.socksProxy) {
agent = new Socks.Agent(clone(this.client._proxyOpts.socksProxy), (parsedUrl.protocol === 'https:'))
agent = this.client._proxyOpts.socksProxy
}
}

this.cleanupFns.push(cleanup)

let request = get.concat({
url: parsedUrl.toString(),
agent,
timeout: common.REQUEST_TIMEOUT,
headers: {
'user-agent': this.client._userAgent || ''
}
}, onResponse)

function cleanup () {
if (request) {
arrayRemove(self.cleanupFns, self.cleanupFns.indexOf(cleanup))
request.abort()
request = null
const cleanup = () => {
if (!controller.signal.aborted) {
arrayRemove(this.cleanupFns, this.cleanupFns.indexOf(cleanup))
controller.abort()
controller = null
}
if (self.maybeDestroyCleanup) self.maybeDestroyCleanup()
if (this.maybeDestroyCleanup) this.maybeDestroyCleanup()
}

function onResponse (err, res, data) {
cleanup()
if (self.destroyed) return
this.cleanupFns.push(cleanup)

let res
let controller = abortTimeout(common.REQUEST_TIMEOUT)
try {
res = await fetch(parsedUrl.toString(), {
agent,
signal: controller.signal,
dispatcher: agent,
headers: {
'user-agent': this.client._userAgent || ''
}
})
} catch (err) {
if (err) return cb(err)
if (res.statusCode !== 200) {
return cb(new Error(`Non-200 response code ${res.statusCode} from ${self.announceUrl}`))
}
if (!data || data.length === 0) {
return cb(new Error(`Invalid tracker response from${self.announceUrl}`))
}

try {
data = bencode.decode(data)
} catch (err) {
return cb(new Error(`Error decoding tracker response: ${err.message}`))
}
const failure = data['failure reason'] && arr2text(data['failure reason'])
if (failure) {
debug(`failure from ${requestUrl} (${failure})`)
return cb(new Error(failure))
}
}
let data = new Uint8Array(await res.arrayBuffer())
cleanup()
if (this.destroyed) return

const warning = data['warning message'] && arr2text(data['warning message'])
if (warning) {
debug(`warning from ${requestUrl} (${warning})`)
self.client.emit('warning', new Error(warning))
}
if (res.status !== 200) {
return cb(new Error(`Non-200 response code ${res.statusCode} from ${this.announceUrl}`))
}
if (!data || data.length === 0) {
return cb(new Error(`Invalid tracker response from${this.announceUrl}`))
}

debug(`response from ${requestUrl}`)
try {
data = bencode.decode(data)
} catch (err) {
return cb(new Error(`Error decoding tracker response: ${err.message}`))
}
const failure = data['failure reason'] && arr2text(data['failure reason'])
if (failure) {
debug(`failure from ${requestUrl} (${failure})`)
return cb(new Error(failure))
}

cb(null, data)
const warning = data['warning message'] && arr2text(data['warning message'])
if (warning) {
debug(`warning from ${requestUrl} (${warning})`)
this.client.emit('warning', new Error(warning))
}

debug(`response from ${requestUrl}`)

cb(null, data)
}

_onAnnounceResponse (data) {
Expand Down
4 changes: 1 addition & 3 deletions lib/client/websocket-tracker.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import clone from 'clone'
import Debug from 'debug'
import Peer from '@thaunknown/simple-peer/lite.js'
import Socket from '@thaunknown/simple-websocket'
import Socks from 'socks'
import { arr2text, arr2hex, hex2bin, bin2hex, randomBytes } from 'uint8-util'

import common from '../common.js'
Expand Down Expand Up @@ -185,7 +183,7 @@ class WebSocketTracker extends Tracker {
if (this.client._proxyOpts) {
agent = parsedUrl.protocol === 'wss:' ? this.client._proxyOpts.httpsAgent : this.client._proxyOpts.httpAgent
if (!agent && this.client._proxyOpts.socksProxy) {
agent = new Socks.Agent(clone(this.client._proxyOpts.socksProxy), (parsedUrl.protocol === 'wss:'))
agent = this.client._proxyOpts.socksProxy
}
}
this.socket = socketPool[this.announceUrl] = new Socket({ url: this.announceUrl, agent })
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"chrome-dgram": "^3.0.6",
"clone": "^2.0.0",
"compact2string": "^1.4.1",
"cross-fetch-ponyfill": "^1.0.1",
"debug": "^4.1.1",
"ip": "^1.1.5",
"lru": "^3.1.0",
Expand All @@ -43,7 +44,6 @@
"random-iterate": "^1.0.1",
"run-parallel": "^1.2.0",
"run-series": "^1.1.9",
"simple-get": "^4.0.0",
"socks": "^2.0.0",
"string2compact": "^2.0.0",
"uint8-util": "^2.1.9",
Expand All @@ -57,6 +57,7 @@
"semantic-release": "21.1.2",
"standard": "*",
"tape": "5.7.2",
"undici": "^5.27.0",
"webtorrent-fixtures": "2.0.2",
"wrtc": "0.4.7"
},
Expand Down
28 changes: 23 additions & 5 deletions test/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import http from 'http'
import fixtures from 'webtorrent-fixtures'
import net from 'net'
import test from 'tape'
import undici from 'undici'

const peerId1 = Buffer.from('01234567890123456789')
const peerId2 = Buffer.from('12345678901234567890')
Expand Down Expand Up @@ -572,12 +573,29 @@ function testClientStartHttpAgent (t, serverType) {
t.plan(5)

common.createServer(t, serverType, function (server, announceUrl) {
const agent = new http.Agent()
let agentUsed = false
agent.createConnection = function (opts, fn) {
agentUsed = true
return net.createConnection(opts, fn)
let agent
if (global.fetch && serverType !== 'ws') {
const connector = undici.buildConnector({ rejectUnauthorized: false })
agent = new undici.Agent({
connect (opts, cb) {
agentUsed = true
connector(opts, (err, socket) => {
if (err) {
cb(err, null)
} else {
cb(null, socket)
}
})
}
})
} else {
agent = new http.Agent()
agent.createConnection = function (opts, fn) {
agentUsed = true
return net.createConnection(opts, fn)
}
}
let agentUsed = false
const client = new Client({
infoHash: fixtures.leaves.parsedTorrent.infoHash,
announce: announceUrl,
Expand Down
Loading

0 comments on commit bce64e1

Please sign in to comment.