From c169ced054272e30d619746c0d0673d0b8337e06 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Thu, 4 May 2023 00:54:47 -0700 Subject: [PATCH] Convert mocha tests to jest for all packages (#150) --- .changeset/big-feet-beam.md | 15 + packages/agent-base/package.json | 3 +- packages/agent-base/src/helpers.ts | 15 +- packages/agent-base/test/test-legacy.js | 689 ------------------ packages/data-uri-to-buffer/package.json | 3 +- packages/degenerator/package.json | 3 +- packages/get-uri/package.json | 4 +- packages/get-uri/src/http.ts | 6 +- packages/http-proxy-agent/jest.config.js | 5 + packages/http-proxy-agent/package.json | 11 +- packages/http-proxy-agent/src/index.ts | 2 +- packages/http-proxy-agent/test/test.js | 335 --------- packages/http-proxy-agent/test/test.ts | 191 +++++ packages/http-proxy-agent/test/tsconfig.json | 4 + packages/https-proxy-agent/package.json | 3 +- packages/https-proxy-agent/test/test.ts | 10 +- packages/pac-proxy-agent/jest.config.js | 5 + packages/pac-proxy-agent/package.json | 11 +- packages/pac-proxy-agent/src/index.ts | 2 +- packages/pac-proxy-agent/test/test.js | 464 ------------ packages/pac-proxy-agent/test/test.ts | 362 +++++++++ packages/pac-proxy-agent/test/tsconfig.json | 4 + packages/pac-resolver/jest.config.js | 5 + packages/pac-resolver/package.json | 9 +- packages/pac-resolver/src/index.ts | 45 +- packages/pac-resolver/test/dnsDomainIs.js | 24 - .../pac-resolver/test/dnsDomainIs.test.ts | 14 + packages/pac-resolver/test/dnsDomainLevels.js | 24 - .../pac-resolver/test/dnsDomainLevels.test.ts | 11 + packages/pac-resolver/test/dnsResolve.js | 44 -- packages/pac-resolver/test/dnsResolve.test.ts | 21 + .../test/{isInNet.js => isInNet.test.ts} | 22 +- packages/pac-resolver/test/isPlainHostName.js | 23 - .../pac-resolver/test/isPlainHostName.test.ts | 10 + packages/pac-resolver/test/isResolvable.js | 26 - .../pac-resolver/test/isResolvable.test.ts | 14 + .../pac-resolver/test/localHostOrDomainIs.js | 25 - .../test/localHostOrDomainIs.test.ts | 23 + packages/pac-resolver/test/myIpAddress.js | 16 - .../pac-resolver/test/myIpAddress.test.ts | 9 + packages/pac-resolver/test/shExpMatch.js | 41 -- packages/pac-resolver/test/shExpMatch.test.ts | 51 ++ packages/pac-resolver/test/test.js | 389 ---------- packages/pac-resolver/test/test.ts | 316 ++++++++ packages/pac-resolver/test/timeRange.js | 70 -- packages/pac-resolver/test/timeRange.test.ts | 57 ++ packages/pac-resolver/test/tsconfig.json | 4 + packages/proxy-agent/package.json | 3 +- packages/proxy/package.json | 4 +- packages/proxy/src/bin/proxy.ts | 4 +- packages/socks-proxy-agent/package.json | 8 +- packages/socks-proxy-agent/src/index.ts | 9 +- packages/socks-proxy-agent/test/test.js | 278 ------- packages/socks-proxy-agent/test/test.ts | 254 +++++++ pnpm-lock.yaml | 650 ++--------------- 55 files changed, 1492 insertions(+), 3158 deletions(-) create mode 100644 .changeset/big-feet-beam.md delete mode 100644 packages/agent-base/test/test-legacy.js create mode 100644 packages/http-proxy-agent/jest.config.js delete mode 100644 packages/http-proxy-agent/test/test.js create mode 100644 packages/http-proxy-agent/test/test.ts create mode 100644 packages/http-proxy-agent/test/tsconfig.json create mode 100644 packages/pac-proxy-agent/jest.config.js delete mode 100644 packages/pac-proxy-agent/test/test.js create mode 100644 packages/pac-proxy-agent/test/test.ts create mode 100644 packages/pac-proxy-agent/test/tsconfig.json create mode 100644 packages/pac-resolver/jest.config.js delete mode 100644 packages/pac-resolver/test/dnsDomainIs.js create mode 100644 packages/pac-resolver/test/dnsDomainIs.test.ts delete mode 100644 packages/pac-resolver/test/dnsDomainLevels.js create mode 100644 packages/pac-resolver/test/dnsDomainLevels.test.ts delete mode 100644 packages/pac-resolver/test/dnsResolve.js create mode 100644 packages/pac-resolver/test/dnsResolve.test.ts rename packages/pac-resolver/test/{isInNet.js => isInNet.test.ts} (54%) delete mode 100644 packages/pac-resolver/test/isPlainHostName.js create mode 100644 packages/pac-resolver/test/isPlainHostName.test.ts delete mode 100644 packages/pac-resolver/test/isResolvable.js create mode 100644 packages/pac-resolver/test/isResolvable.test.ts delete mode 100644 packages/pac-resolver/test/localHostOrDomainIs.js create mode 100644 packages/pac-resolver/test/localHostOrDomainIs.test.ts delete mode 100644 packages/pac-resolver/test/myIpAddress.js create mode 100644 packages/pac-resolver/test/myIpAddress.test.ts delete mode 100644 packages/pac-resolver/test/shExpMatch.js create mode 100644 packages/pac-resolver/test/shExpMatch.test.ts delete mode 100644 packages/pac-resolver/test/test.js create mode 100644 packages/pac-resolver/test/test.ts delete mode 100644 packages/pac-resolver/test/timeRange.js create mode 100644 packages/pac-resolver/test/timeRange.test.ts create mode 100644 packages/pac-resolver/test/tsconfig.json delete mode 100644 packages/socks-proxy-agent/test/test.js create mode 100644 packages/socks-proxy-agent/test/test.ts diff --git a/.changeset/big-feet-beam.md b/.changeset/big-feet-beam.md new file mode 100644 index 00000000..3429b543 --- /dev/null +++ b/.changeset/big-feet-beam.md @@ -0,0 +1,15 @@ +--- +'data-uri-to-buffer': patch +'https-proxy-agent': patch +'socks-proxy-agent': patch +'http-proxy-agent': patch +'pac-proxy-agent': patch +'pac-resolver': patch +'degenerator': patch +'proxy-agent': patch +'agent-base': patch +'get-uri': patch +'proxy': patch +--- + +Convert mocha tests to jest for all packages diff --git a/packages/agent-base/package.json b/packages/agent-base/package.json index d4e441be..5c32605a 100644 --- a/packages/agent-base/package.json +++ b/packages/agent-base/package.json @@ -11,8 +11,7 @@ "build": "tsc", "test": "jest --env node --verbose --bail", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "repository": { "type": "git", diff --git a/packages/agent-base/src/helpers.ts b/packages/agent-base/src/helpers.ts index f4a18900..71d960bc 100644 --- a/packages/agent-base/src/helpers.ts +++ b/packages/agent-base/src/helpers.ts @@ -2,6 +2,10 @@ import * as http from 'http'; import * as https from 'https'; import type { Readable } from 'stream'; +export type ThenableRequest = http.ClientRequest & { + then: Promise['then']; +}; + export async function toBuffer(stream: Readable): Promise { let length = 0; const chunks: Buffer[] = []; @@ -28,12 +32,15 @@ export async function json(stream: Readable): Promise { export function req( url: string | URL, opts: https.RequestOptions = {} -): Promise { - return new Promise((resolve, reject) => { +): ThenableRequest { + let req!: ThenableRequest; + const promise = new Promise((resolve, reject) => { const href = typeof url === 'string' ? url : url.href; - (href.startsWith('https:') ? https : http) + req = (href.startsWith('https:') ? https : http) .request(url, opts, resolve) .once('error', reject) - .end(); + .end() as ThenableRequest; }); + req.then = promise.then.bind(promise); + return req; } diff --git a/packages/agent-base/test/test-legacy.js b/packages/agent-base/test/test-legacy.js deleted file mode 100644 index d5f8f856..00000000 --- a/packages/agent-base/test/test-legacy.js +++ /dev/null @@ -1,689 +0,0 @@ -/** - * Module dependencies. - */ - -var fs = require('fs'); -var url = require('url'); -var net = require('net'); -var tls = require('tls'); -var http = require('http'); -var https = require('https'); -var WebSocket = require('ws'); -var assert = require('assert'); -var events = require('events'); -var inherits = require('util').inherits; -var Agent = require('../src'); - -var PassthroughAgent = Agent(function (req, opts) { - return opts.secureEndpoint ? https.globalAgent : http.globalAgent; -}); - -describe('Agent (JavaScript)', function () { - describe('subclass', function () { - it('should be subclassable', function (done) { - function MyAgent() { - Agent.call(this); - } - inherits(MyAgent, Agent); - - MyAgent.prototype.callback = function (req, opts, fn) { - assert.equal(req.path, '/foo'); - assert.equal(req.getHeader('host'), '127.0.0.1:1234'); - assert.equal(opts.secureEndpoint, true); - done(); - }; - - var info = url.parse('https://127.0.0.1:1234/foo'); - info.agent = new MyAgent(); - assert(info.agent instanceof Agent); - assert(info.agent instanceof MyAgent); - https.get(info); - }); - }); - describe('options', function () { - it('should support an options Object as first argument', function () { - var agent = new Agent({ timeout: 1000 }); - assert.equal(1000, agent.timeout); - }); - it('should support an options Object as second argument', function () { - var agent = new Agent(function () {}, { timeout: 1000 }); - assert.equal(1000, agent.timeout); - }); - }); - describe('`this` context', function () { - it('should be the Agent instance', function (done) { - var called = false; - var agent = new Agent(); - agent.callback = function () { - called = true; - assert.equal(this, agent); - }; - var info = url.parse('http://127.0.0.1/foo'); - info.agent = agent; - var req = http.get(info); - req.on('error', function (err) { - assert(/no Duplex stream was returned/.test(err.message)); - done(); - }); - }); - it('should be the Agent instance with callback signature', function (done) { - var called = false; - var agent = new Agent(); - agent.callback = function (req, opts, fn) { - called = true; - assert.equal(this, agent); - fn(); - }; - var info = url.parse('http://127.0.0.1/foo'); - info.agent = agent; - var req = http.get(info); - req.on('error', function (err) { - assert(/no Duplex stream was returned/.test(err.message)); - done(); - }); - }); - }); - describe('"error" event', function () { - it('should be invoked on `http.ClientRequest` instance if `callback()` has not been defined', function (done) { - var agent = new Agent(); - var info = url.parse('http://127.0.0.1/foo'); - info.agent = agent; - var req = http.get(info); - req.on('error', function (err) { - assert.equal( - '"agent-base" has no default implementation, you must subclass and override `callback()`', - err.message - ); - done(); - }); - }); - it('should be invoked on `http.ClientRequest` instance if Error passed to callback function on the first tick', function (done) { - var agent = new Agent(function (req, opts, fn) { - fn(new Error('is this caught?')); - }); - var info = url.parse('http://127.0.0.1/foo'); - info.agent = agent; - var req = http.get(info); - req.on('error', function (err) { - assert.equal('is this caught?', err.message); - done(); - }); - }); - it('should be invoked on `http.ClientRequest` instance if Error passed to callback function after the first tick', function (done) { - var agent = new Agent(function (req, opts, fn) { - setTimeout(function () { - fn(new Error('is this caught?')); - }, 10); - }); - var info = url.parse('http://127.0.0.1/foo'); - info.agent = agent; - var req = http.get(info); - req.on('error', function (err) { - assert.equal('is this caught?', err.message); - done(); - }); - }); - }); - describe('artificial "streams"', function () { - it('should send a GET request', function (done) { - var stream = new events.EventEmitter(); - - // needed for the `http` module to call .write() on the stream - stream.writable = true; - - stream.write = function (str) { - assert(0 == str.indexOf('GET / HTTP/1.1')); - done(); - }; - - // needed for `http` module in Node.js 4 - stream.cork = function () {}; - stream.uncork = function () {}; - - var opts = { - method: 'GET', - host: '127.0.0.1', - path: '/', - port: 80, - agent: new Agent(function (req, opts, fn) { - fn(null, stream); - }), - }; - var req = http.request(opts); - req.end(); - }); - it('should receive a GET response', function (done) { - var stream = new events.EventEmitter(); - var opts = { - method: 'GET', - host: '127.0.0.1', - path: '/', - port: 80, - agent: new Agent(function (req, opts, fn) { - fn(null, stream); - }), - }; - var req = http.request(opts, function (res) { - assert.equal('1.0', res.httpVersion); - assert.equal(200, res.statusCode); - assert.equal('bar', res.headers.foo); - assert.deepEqual(['1', '2'], res.headers['set-cookie']); - done(); - }); - - // have to wait for the "socket" event since `http.ClientRequest` - // doesn't *actually* attach the listeners to the "stream" until - // this happens - req.once('socket', function () { - var buf = Buffer.from( - 'HTTP/1.0 200\r\n' + - 'Foo: bar\r\n' + - 'Set-Cookie: 1\r\n' + - 'Set-Cookie: 2\r\n\r\n' - ); - stream.emit('data', buf); - }); - - req.end(); - }); - }); - - describe('"http" module', function () { - var server; - var port; - - // setup test HTTP server - before(function (done) { - server = http.createServer(); - server.listen(0, function () { - port = server.address().port; - done(); - }); - }); - - beforeEach(function () { - server.removeAllListeners('request'); - }); - - // shut down test HTTP server - after(function (done) { - server.once('close', function () { - done(); - }); - server.close(); - }); - - it('should work for basic HTTP requests', function (done) { - var called = false; - var agent = new Agent(function (req, opts, fn) { - called = true; - var socket = net.connect(opts); - fn(null, socket); - }); - - // add HTTP server "request" listener - var gotReq = false; - server.once('request', function (req, res) { - gotReq = true; - res.setHeader('X-Foo', 'bar'); - res.setHeader('X-Url', req.url); - res.end(); - }); - - var info = url.parse('http://127.0.0.1:' + port + '/foo'); - info.agent = agent; - http.get(info, function (res) { - assert.equal('bar', res.headers['x-foo']); - assert.equal('/foo', res.headers['x-url']); - assert(gotReq); - assert(called); - done(); - }); - }); - - it('should support direct return in `connect()`', function (done) { - var called = false; - var agent = new Agent(function (req, opts) { - called = true; - return net.connect(opts); - }); - - // add HTTP server "request" listener - var gotReq = false; - server.once('request', function (req, res) { - gotReq = true; - res.setHeader('X-Foo', 'bar'); - res.setHeader('X-Url', req.url); - res.end(); - }); - - var info = url.parse('http://127.0.0.1:' + port + '/foo'); - info.agent = agent; - http.get(info, function (res) { - assert.equal('bar', res.headers['x-foo']); - assert.equal('/foo', res.headers['x-url']); - assert(gotReq); - assert(called); - done(); - }); - }); - - it('should support returning a Promise in `connect()`', function (done) { - var called = false; - var agent = new Agent(function (req, opts) { - return new Promise(function (resolve, reject) { - called = true; - resolve(net.connect(opts)); - }); - }); - - // add HTTP server "request" listener - var gotReq = false; - server.once('request', function (req, res) { - gotReq = true; - res.setHeader('X-Foo', 'bar'); - res.setHeader('X-Url', req.url); - res.end(); - }); - - var info = url.parse('http://127.0.0.1:' + port + '/foo'); - info.agent = agent; - http.get(info, function (res) { - assert.equal('bar', res.headers['x-foo']); - assert.equal('/foo', res.headers['x-url']); - assert(gotReq); - assert(called); - done(); - }); - }); - - it('should set the `Connection: close` response header', function (done) { - var called = false; - var agent = new Agent(function (req, opts, fn) { - called = true; - var socket = net.connect(opts); - fn(null, socket); - }); - - // add HTTP server "request" listener - var gotReq = false; - server.once('request', function (req, res) { - gotReq = true; - res.setHeader('X-Url', req.url); - assert.equal('close', req.headers.connection); - res.end(); - }); - - var info = url.parse('http://127.0.0.1:' + port + '/bar'); - info.agent = agent; - http.get(info, function (res) { - assert.equal('/bar', res.headers['x-url']); - assert.equal('close', res.headers.connection); - assert(gotReq); - assert(called); - done(); - }); - }); - - it('should pass through options from `http.request()`', function (done) { - var agent = new Agent(function (req, opts, fn) { - assert.equal('google.com', opts.host); - assert.equal('bar', opts.foo); - done(); - }); - - http.get({ - host: 'google.com', - foo: 'bar', - agent: agent, - }); - }); - - it('should default to port 80', function (done) { - var agent = new Agent(function (req, opts, fn) { - assert.equal(80, opts.port); - done(); - }); - - // (probably) not hitting a real HTTP server here, - // so no need to add a httpServer request listener - http.get({ - host: '127.0.0.1', - path: '/foo', - agent: agent, - }); - }); - - it('should support the "timeout" option', function (done) { - // ensure we timeout after the "error" event had a chance to trigger - this.timeout(1000); - this.slow(800); - - var agent = new Agent( - function (req, opts, fn) { - // this function will time out - }, - { timeout: 100 } - ); - - var opts = url.parse('http://nodejs.org'); - opts.agent = agent; - - var req = http.get(opts); - req.once('error', function (err) { - assert.equal('ETIMEOUT', err.code); - req.abort(); - done(); - }); - }); - - it('should free sockets after use', function (done) { - var agent = new Agent(function (req, opts, fn) { - var socket = net.connect(opts); - fn(null, socket); - }); - - // add HTTP server "request" listener - var gotReq = false; - server.once('request', function (req, res) { - gotReq = true; - res.end(); - }); - - var info = url.parse('http://127.0.0.1:' + port + '/foo'); - info.agent = agent; - http.get(info, function (res) { - res.socket.emit('free'); - assert.equal(true, res.socket.destroyed); - assert(gotReq); - done(); - }); - }); - - describe('PassthroughAgent', function () { - it('should pass through to `http.globalAgent`', function (done) { - // add HTTP server "request" listener - var gotReq = false; - server.once('request', function (req, res) { - gotReq = true; - res.setHeader('X-Foo', 'bar'); - res.setHeader('X-Url', req.url); - res.end(); - }); - - var info = url.parse('http://127.0.0.1:' + port + '/foo'); - info.agent = PassthroughAgent; - http.get(info, function (res) { - assert.equal('bar', res.headers['x-foo']); - assert.equal('/foo', res.headers['x-url']); - assert(gotReq); - done(); - }); - }); - }); - }); - - describe('"https" module', function () { - var server; - var port; - - // setup test HTTPS server - before(function (done) { - var options = { - key: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.key'), - cert: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.pem'), - }; - server = https.createServer(options); - server.listen(0, function () { - port = server.address().port; - done(); - }); - }); - - beforeEach(function () { - server.removeAllListeners('request'); - }); - - // shut down test HTTP server - after(function (done) { - server.once('close', function () { - done(); - }); - server.close(); - }); - - it('should not modify the passed in Options object', function (done) { - var called = false; - var agent = new Agent(function (req, opts, fn) { - called = true; - assert.equal(true, opts.secureEndpoint); - assert.equal(443, opts.port); - assert.equal('localhost', opts.host); - }); - var opts = { agent: agent }; - var req = https.request(opts); - assert.equal(true, called); - assert.equal(false, 'secureEndpoint' in opts); - assert.equal(false, 'port' in opts); - done(); - }); - - it('should work with a String URL', function (done) { - var endpoint = 'https://127.0.0.1:' + port; - var req = https.get(endpoint); - - // it's gonna error out since `rejectUnauthorized` is not being passed in - req.on('error', function (err) { - assert.equal(err.code, 'DEPTH_ZERO_SELF_SIGNED_CERT'); - done(); - }); - }); - - it('should work for basic HTTPS requests', function (done) { - var called = false; - var agent = new Agent(function (req, opts, fn) { - called = true; - assert(opts.secureEndpoint); - var socket = tls.connect(opts); - fn(null, socket); - }); - - // add HTTPS server "request" listener - var gotReq = false; - server.once('request', function (req, res) { - gotReq = true; - res.setHeader('X-Foo', 'bar'); - res.setHeader('X-Url', req.url); - res.end(); - }); - - var info = url.parse('https://127.0.0.1:' + port + '/foo'); - info.agent = agent; - info.rejectUnauthorized = false; - https.get(info, function (res) { - assert.equal('bar', res.headers['x-foo']); - assert.equal('/foo', res.headers['x-url']); - assert(gotReq); - assert(called); - done(); - }); - }); - - it('should pass through options from `https.request()`', function (done) { - var agent = new Agent(function (req, opts, fn) { - assert.equal('google.com', opts.host); - assert.equal('bar', opts.foo); - done(); - }); - - https.get({ - host: 'google.com', - foo: 'bar', - agent: agent, - }); - }); - - it('should default to port 443', function (done) { - var agent = new Agent(function (req, opts, fn) { - assert.equal(true, opts.secureEndpoint); - assert.equal(false, opts.rejectUnauthorized); - assert.equal(443, opts.port); - done(); - }); - - // (probably) not hitting a real HTTPS server here, - // so no need to add a httpsServer request listener - https.get({ - host: '127.0.0.1', - path: '/foo', - agent: agent, - rejectUnauthorized: false, - }); - }); - - describe('PassthroughAgent', function () { - it('should pass through to `https.globalAgent`', function (done) { - // add HTTP server "request" listener - var gotReq = false; - server.once('request', function (req, res) { - gotReq = true; - res.setHeader('X-Foo', 'bar'); - res.setHeader('X-Url', req.url); - res.end(); - }); - - var info = url.parse('https://127.0.0.1:' + port + '/foo'); - info.agent = PassthroughAgent; - info.rejectUnauthorized = false; - https.get(info, function (res) { - assert.equal('bar', res.headers['x-foo']); - assert.equal('/foo', res.headers['x-url']); - assert(gotReq); - done(); - }); - }); - }); - }); - - describe('"ws" server', function () { - var wss; - var server; - var port; - - // setup test HTTP server - before(function (done) { - server = http.createServer(); - wss = new WebSocket.Server({ server: server }); - server.listen(0, function () { - port = server.address().port; - done(); - }); - }); - - beforeEach(function () { - server.removeAllListeners('request'); - wss.removeAllListeners('connection'); - }); - - // shut down test HTTP server - after(function (done) { - server.once('close', function () { - done(); - }); - server.close(); - }); - - it('should work for basic WebSocket connections', function (done) { - function onconnection(ws) { - ws.on('message', function (data) { - assert.equal('ping', data); - ws.send('pong'); - }); - } - wss.on('connection', onconnection); - - var agent = new Agent(function (req, opts, fn) { - var socket = net.connect(opts); - fn(null, socket); - }); - - var client = new WebSocket('ws://127.0.0.1:' + port + '/', { - agent: agent, - }); - - client.on('open', function () { - client.send('ping'); - }); - - client.on('message', function (data) { - assert.equal('pong', data); - client.close(); - done(); - }); - }); - }); - - describe('"wss" server', function () { - var wss; - var server; - var port; - - // setup test HTTP server - before(function (done) { - var options = { - key: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.key'), - cert: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.pem'), - }; - server = https.createServer(options); - wss = new WebSocket.Server({ server: server }); - server.listen(0, function () { - port = server.address().port; - done(); - }); - }); - - beforeEach(function () { - server.removeAllListeners('request'); - wss.removeAllListeners('connection'); - }); - - // shut down test HTTP server - after(function (done) { - server.once('close', function () { - done(); - }); - server.close(); - }); - - it('should work for secure WebSocket connections', function (done) { - function onconnection(ws) { - ws.on('message', function (data) { - assert.equal('ping', data); - ws.send('pong'); - }); - } - wss.on('connection', onconnection); - - var agent = new Agent(function (req, opts, fn) { - var socket = tls.connect(opts); - fn(null, socket); - }); - - var client = new WebSocket('wss://127.0.0.1:' + port + '/', { - agent: agent, - rejectUnauthorized: false, - }); - - client.on('open', function () { - client.send('ping'); - }); - - client.on('message', function (data) { - assert.equal('pong', data); - client.close(); - wss.removeListener('connection', onconnection); - done(); - }); - }); - }); -}); diff --git a/packages/data-uri-to-buffer/package.json b/packages/data-uri-to-buffer/package.json index 76c4fda1..bf11cc8d 100644 --- a/packages/data-uri-to-buffer/package.json +++ b/packages/data-uri-to-buffer/package.json @@ -11,8 +11,7 @@ "build": "tsc", "test": "jest --env node --verbose --bail", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "repository": { "type": "git", diff --git a/packages/degenerator/package.json b/packages/degenerator/package.json index 370a2860..1b1fdef3 100644 --- a/packages/degenerator/package.json +++ b/packages/degenerator/package.json @@ -11,8 +11,7 @@ "build": "tsc", "test": "jest --env node --verbose --bail", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "author": "Nathan Rajlich (http://n8.io/)", "repository": { diff --git a/packages/get-uri/package.json b/packages/get-uri/package.json index 83f167c5..ca4e30f0 100644 --- a/packages/get-uri/package.json +++ b/packages/get-uri/package.json @@ -11,8 +11,7 @@ "build": "tsc", "test": "jest --env node --verbose --bail", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "repository": { "type": "git", @@ -52,7 +51,6 @@ "typescript": "^5.0.4" }, "dependencies": { - "@tootallnate/once": "^2.0.0", "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^4.0.1", "debug": "^4.3.4", diff --git a/packages/get-uri/src/http.ts b/packages/get-uri/src/http.ts index 11ce12d3..1011cb2d 100644 --- a/packages/get-uri/src/http.ts +++ b/packages/get-uri/src/http.ts @@ -1,8 +1,8 @@ import http_ from 'http'; import https from 'https'; -import once from '@tootallnate/once'; -import createDebug from 'debug'; +import { once } from 'events'; import { Readable } from 'stream'; +import createDebug from 'debug'; import { GetUriProtocol } from '.'; import HTTPError from './http-error'; import NotFoundError from './notfound'; @@ -90,7 +90,7 @@ export const http: GetUriProtocol = async (url, opts = {}) => { } const req = mod.get(url, options); - const [res]: [HttpIncomingMessage] = await once(req, 'response'); + const [res]: HttpIncomingMessage[] = await once(req, 'response'); const code = res.statusCode || 0; // assign a Date to this response for the "Cache-Control" delta calculation diff --git a/packages/http-proxy-agent/jest.config.js b/packages/http-proxy-agent/jest.config.js new file mode 100644 index 00000000..ee66e76e --- /dev/null +++ b/packages/http-proxy-agent/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; diff --git a/packages/http-proxy-agent/package.json b/packages/http-proxy-agent/package.json index fa97b96c..f47fd6f7 100644 --- a/packages/http-proxy-agent/package.json +++ b/packages/http-proxy-agent/package.json @@ -9,10 +9,9 @@ ], "scripts": { "build": "tsc", - "test": "mocha", + "test": "jest --env node --verbose --bail", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "repository": { "type": "git", @@ -30,15 +29,17 @@ "url": "https://github.com/TooTallNate/node-http-proxy-agent/issues" }, "dependencies": { - "@tootallnate/once": "^2.0.0", "agent-base": "^6.0.2", "debug": "^4.3.4" }, "devDependencies": { "@types/debug": "^4.1.7", + "@types/jest": "^29.5.1", "@types/node": "^14.18.43", - "mocha": "^6.2.3", + "async-listen": "^2.1.0", + "jest": "^29.5.0", "proxy": "workspace:*", + "ts-jest": "^29.1.0", "tsconfig": "workspace:*", "typescript": "^5.0.4" }, diff --git a/packages/http-proxy-agent/src/index.ts b/packages/http-proxy-agent/src/index.ts index 06556104..3b697fa2 100644 --- a/packages/http-proxy-agent/src/index.ts +++ b/packages/http-proxy-agent/src/index.ts @@ -2,7 +2,7 @@ import * as net from 'net'; import * as tls from 'tls'; import * as http from 'http'; import createDebug from 'debug'; -import once from '@tootallnate/once'; +import { once } from 'events'; import { Agent, AgentConnectOpts } from 'agent-base'; const debug = createDebug('http-proxy-agent'); diff --git a/packages/http-proxy-agent/test/test.js b/packages/http-proxy-agent/test/test.js deleted file mode 100644 index 055b5178..00000000 --- a/packages/http-proxy-agent/test/test.js +++ /dev/null @@ -1,335 +0,0 @@ -const fs = require('fs'); -const url = require('url'); -const http = require('http'); -const https = require('https'); -const assert = require('assert'); -const { createProxy } = require('proxy'); -const { Agent } = require('agent-base'); -const { HttpProxyAgent } = require('../'); - -const sleep = (n) => new Promise((r) => setTimeout(r, n)); - -describe('HttpProxyAgent', () => { - let server; - let serverPort; - - let proxy; - let proxyPort; - - let sslProxy; - let sslProxyPort; - - before((done) => { - // setup HTTP proxy server - proxy = createProxy(); - proxy.listen(() => { - proxyPort = proxy.address().port; - done(); - }); - }); - - before((done) => { - // setup target HTTP server - server = http.createServer(); - server.listen(() => { - serverPort = server.address().port; - done(); - }); - }); - - beforeEach(() => { - server.removeAllListeners('request'); - }); - - before((done) => { - // setup SSL HTTP proxy server - let options = { - key: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.key`), - cert: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.pem`), - }; - sslProxy = createProxy(https.createServer(options)); - sslProxy.listen(() => { - sslProxyPort = sslProxy.address().port; - done(); - }); - }); - - // shut down test HTTP server - after((done) => { - proxy.once('close', () => { - done(); - }); - proxy.close(); - }); - - after((done) => { - server.once('close', () => { - done(); - }); - server.close(); - }); - - after((done) => { - sslProxy.once('close', () => { - done(); - }); - sslProxy.close(); - }); - - describe('constructor', () => { - it('should throw an Error if no "proxy" argument is given', () => { - assert.throws(() => { - new HttpProxyAgent(); - }); - }); - it('should accept a "string" proxy argument', () => { - let agent = new HttpProxyAgent(`http://127.0.0.1:${proxyPort}`); - assert.equal('127.0.0.1', agent.proxy.hostname); - assert.equal(proxyPort, agent.proxy.port); - }); - it('should accept a `URL` instance proxy argument', () => { - let agent = new HttpProxyAgent( - new URL(`http://127.0.0.1:${proxyPort}`) - ); - assert.equal('127.0.0.1', agent.proxy.hostname); - assert.equal(proxyPort, agent.proxy.port); - }); - it('should set a `defaultPort` property', () => { - let opts = url.parse(`http://127.0.0.1:${proxyPort}`); - let agent = new HttpProxyAgent(opts); - assert.equal(80, agent.defaultPort); - }); - describe('secureProxy', () => { - it('should be `false` when "http:" protocol is used', () => { - let agent = new HttpProxyAgent(`http://127.0.0.1:${proxyPort}`); - assert.equal(false, agent.secureProxy); - }); - it('should be `true` when "https:" protocol is used', () => { - let agent = new HttpProxyAgent( - `https://127.0.0.1:${proxyPort}` - ); - assert.equal(true, agent.secureProxy); - }); - }); - }); - - describe('"http" module', () => { - it('should work over an HTTP proxy', (done) => { - // set HTTP "request" event handler for this test - server.once('request', (req, res) => { - res.end(JSON.stringify(req.headers)); - }); - - let proxy = `http://127.0.0.1:${proxyPort}`; - let agent = new HttpProxyAgent(proxy); - - let opts = url.parse(`http://127.0.0.1:${serverPort}`); - opts.agent = agent; - - http.get(opts, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (b) => { - data += b; - }); - res.on('end', () => { - data = JSON.parse(data); - assert.equal(`127.0.0.1:${serverPort}`, data.host); - assert('via' in data); - done(); - }); - }); - }); - it('should work over an HTTPS proxy', (done) => { - // set HTTP "request" event handler for this test - server.once('request', (req, res) => { - res.end(JSON.stringify(req.headers)); - }); - - let proxy = - process.env.HTTPS_PROXY || `https://127.0.0.1:${sslProxyPort}`; - let agent = new HttpProxyAgent(proxy, { - rejectUnauthorized: false, - }); - assert.equal(true, agent.secureProxy); - - http.get(`http://127.0.0.1:${serverPort}`, { agent }, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (b) => { - data += b; - }); - res.on('end', () => { - data = JSON.parse(data); - assert.equal(`127.0.0.1:${serverPort}`, data.host); - assert('via' in data); - done(); - }); - }); - }); - it('should proxy the query string of the request path', (done) => { - // set HTTP "request" event handler for this test - server.once('request', (req, res) => { - res.end( - JSON.stringify({ - url: req.url, - }) - ); - }); - - let proxy = `http://127.0.0.1:${proxyPort}`; - let agent = new HttpProxyAgent(proxy); - - let opts = url.parse( - `http://127.0.0.1:${serverPort}/test?foo=bar&1=2` - ); - opts.agent = agent; - - http.get(opts, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (b) => { - data += b; - }); - res.on('end', () => { - data = JSON.parse(data); - assert.equal('/test?foo=bar&1=2', data.url); - done(); - }); - }); - }); - it('should receive the 407 authorization code on the `http.ClientResponse`', (done) => { - // reject all requests - proxy.authenticate = () => false; - - let proxyUri = `http://127.0.0.1:${proxyPort}`; - let agent = new HttpProxyAgent(proxyUri); - - let opts = {}; - // `host` and `port` don't really matter since the proxy will reject anyways - opts.host = '127.0.0.1'; - opts.port = 80; - opts.agent = agent; - - http.get(opts, (res) => { - assert.equal(407, res.statusCode); - assert('proxy-authenticate' in res.headers); - delete proxy.authenticate; - done(); - }); - }); - it('should send the "Proxy-Authorization" request header', (done) => { - // set a proxy authentication function for this test - proxy.authenticate = (req) => { - // username:password is "foo:bar" - return ( - req.headers['proxy-authorization'] === 'Basic Zm9vOmJhcg==' - ); - }; - - // set HTTP "request" event handler for this test - server.once('request', (req, res) => { - res.end(JSON.stringify(req.headers)); - }); - - let proxyUri = `http://foo:bar@127.0.0.1:${proxyPort}`; - let agent = new HttpProxyAgent(proxyUri); - - let opts = url.parse(`http://127.0.0.1:${serverPort}`); - opts.agent = agent; - - http.get(opts, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (b) => { - data += b; - }); - res.on('end', () => { - data = JSON.parse(data); - assert.equal(`127.0.0.1:${serverPort}`, data.host); - assert('via' in data); - delete proxy.authenticate; - done(); - }); - }); - }); - it('should emit an "error" event on the `http.ClientRequest` if the proxy does not exist', (done) => { - // port 4 is a reserved, but "unassigned" port - let proxyUri = 'http://127.0.0.1:4'; - let agent = new HttpProxyAgent(proxyUri); - - let opts = url.parse('http://nodejs.org'); - opts.agent = agent; - - let req = http.get(opts); - req.once('error', (err) => { - assert.equal('ECONNREFUSED', err.code); - req.abort(); - done(); - }); - }); - it('should work after the first tick of the `http.ClientRequest` instance', (done) => { - // set HTTP "request" event handler for this test - server.once('request', (req, res) => { - res.end(JSON.stringify(req.url)); - }); - - let proxy = `http://127.0.0.1:${proxyPort}`; - let httpProxyAgent = new HttpProxyAgent(proxy); - - // Defer the "connect()" function logic, since calling `req.end()` - // before the socket is returned causes the HTTP header to be - // generated *before* `HttpProxyAgent` can patch the `req.path` - // property, making the header incorrect. - class SleepAgent extends Agent { - async connect(_req, opts) { - assert.equal(opts.secureEndpoint, false); - assert.equal(opts.protocol, 'http:'); - await sleep(10); - return httpProxyAgent; - } - } - const sleepAgent = new SleepAgent(); - - http.get( - `http://127.0.0.1:${serverPort}/test`, - { agent: sleepAgent }, - (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (b) => { - data += b; - }); - res.on('end', () => { - data = JSON.parse(data); - assert.equal('/test', data); - done(); - }); - } - ); - }); - it('should not send a port number for the default port', (done) => { - server.once('request', (req, res) => { - res.end(JSON.stringify(req.headers)); - }); - let proxy = `http://127.0.0.1:${proxyPort}`; - proxy = url.parse(proxy); - let agent = new HttpProxyAgent(proxy); - agent.defaultPort = serverPort; - let opts = url.parse(`http://127.0.0.1:${serverPort}`); - opts.agent = agent; - http.get(opts, (res) => { - let data = ''; - res.setEncoding('utf8'); - res.on('data', (b) => { - data += b; - }); - res.on('end', () => { - data = JSON.parse(data); - assert.equal('127.0.0.1', data.host); - done(); - }); - }); - }); - }); -}); diff --git a/packages/http-proxy-agent/test/test.ts b/packages/http-proxy-agent/test/test.ts new file mode 100644 index 00000000..3eb5eb68 --- /dev/null +++ b/packages/http-proxy-agent/test/test.ts @@ -0,0 +1,191 @@ +import * as fs from 'fs'; +import * as http from 'http'; +import * as https from 'https'; +import assert from 'assert'; +import { createProxy, ProxyServer } from 'proxy'; +import { listen } from 'async-listen'; +import { json, req } from 'agent-base'; +import { HttpProxyAgent } from '../src'; + +describe('HttpProxyAgent', () => { + let httpServer: http.Server; + let httpServerUrl: URL; + + let proxy: ProxyServer; + let proxyUrl: URL; + + let sslProxy: ProxyServer; + let sslProxyUrl: URL; + + beforeAll(async () => { + // setup HTTP proxy server + proxy = createProxy(); + proxyUrl = (await listen(proxy)) as URL; + }); + + beforeAll(async () => { + // setup target HTTP server + httpServer = http.createServer(); + httpServerUrl = (await listen(httpServer)) as URL; + }); + + beforeAll(async () => { + // setup SSL HTTP proxy server + const options = { + key: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.key`), + cert: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.pem`), + }; + sslProxy = createProxy(https.createServer(options)); + sslProxyUrl = (await listen(sslProxy)) as URL; + }); + + beforeEach(() => { + httpServer.removeAllListeners('request'); + delete proxy.authenticate; + }); + + // shut down test HTTP server + afterAll(() => { + proxy.close(); + httpServer.close(); + sslProxy.close(); + }); + + describe('constructor', () => { + it('should accept a "string" proxy argument', () => { + const agent = new HttpProxyAgent(proxyUrl.href); + assert.equal(proxyUrl.hostname, agent.proxy.hostname); + assert.equal(proxyUrl.port, agent.proxy.port); + }); + it('should accept a `URL` instance proxy argument', () => { + const agent = new HttpProxyAgent(proxyUrl); + assert.equal(proxyUrl.hostname, agent.proxy.hostname); + assert.equal(proxyUrl.port, agent.proxy.port); + }); + it('should set a `defaultPort` property', () => { + const agent = new HttpProxyAgent(proxyUrl); + assert.equal(80, agent.defaultPort); + }); + describe('secureProxy', () => { + it('should be `false` when "http:" protocol is used', () => { + const agent = new HttpProxyAgent( + `http://127.0.0.1:${proxyUrl.port}` + ); + assert.equal(false, agent.secureProxy); + }); + it('should be `true` when "https:" protocol is used', () => { + const agent = new HttpProxyAgent( + `https://127.0.0.1:${proxyUrl.port}` + ); + assert.equal(true, agent.secureProxy); + }); + }); + }); + + describe('"http" module', () => { + it('should work over an HTTP proxy', async () => { + // set HTTP "request" event handler for this test + httpServer.once('request', (req, res) => { + res.end(JSON.stringify(req.headers)); + }); + + const agent = new HttpProxyAgent(proxyUrl); + const res = await req(httpServerUrl, { agent }); + const body = await json(res); + expect(body.host).toEqual(httpServerUrl.host); + assert('via' in body); + }); + it('should work over an HTTPS proxy', async () => { + // set HTTP "request" event handler for this test + httpServer.once('request', (req, res) => { + res.end(JSON.stringify(req.headers)); + }); + + const agent = new HttpProxyAgent(sslProxyUrl, { + rejectUnauthorized: false, + }); + + const res = await req(httpServerUrl, { agent }); + const body = await json(res); + expect(body.host).toEqual(httpServerUrl.host); + expect(body).toHaveProperty('via'); + }); + it('should proxy the query string of the request path', async () => { + // set HTTP "request" event handler for this test + httpServer.once('request', (req, res) => { + res.end( + JSON.stringify({ + url: req.url, + }) + ); + }); + + const agent = new HttpProxyAgent(proxyUrl); + + const path = '/test?foo=bar&1=2'; + const res = await req(new URL(path, httpServerUrl), { agent }); + const body = await json(res); + expect(body.url).toEqual(path); + }); + it('should receive the 407 authorization code on the `http.ClientResponse`', async () => { + // reject all requests + proxy.authenticate = () => false; + + const agent = new HttpProxyAgent(proxyUrl); + const res = await req('http://example.com', { agent }); + assert.equal(407, res.statusCode); + assert('proxy-authenticate' in res.headers); + }); + it('should send the "Proxy-Authorization" request header', async () => { + // set a proxy authentication function for this test + let gotAuth = false; + proxy.authenticate = (req) => { + gotAuth = true; + // username:password is "foo:bar" + return ( + req.headers['proxy-authorization'] === 'Basic Zm9vOmJhcg==' + ); + }; + + // set HTTP "request" event handler for this test + httpServer.once('request', (req, res) => { + res.end(JSON.stringify(req.headers)); + }); + + const authProxy = new URL(proxyUrl.href); + authProxy.username = 'foo'; + authProxy.password = 'bar'; + const agent = new HttpProxyAgent(authProxy); + + const res = await req(httpServerUrl, { agent }); + const body = await json(res); + expect(gotAuth).toEqual(true); + expect(body.host).toEqual(httpServerUrl.host); + expect(body).toHaveProperty('via'); + }); + it('should emit an "error" event on the `http.ClientRequest` if the proxy does not exist', async () => { + // port 4 is a reserved, but "unassigned" port + const agent = new HttpProxyAgent('http://127.0.0.1:4'); + + let err: NodeJS.ErrnoException | undefined; + try { + await req('http://example.com', { agent }); + } catch (_err) { + err = _err as NodeJS.ErrnoException; + } + assert(err); + expect(err.code).toEqual('ECONNREFUSED'); + }); + it('should not send a port number for the default port', async () => { + httpServer.once('request', (req, res) => { + res.end(JSON.stringify(req.headers)); + }); + const agent = new HttpProxyAgent(proxyUrl); + agent.defaultPort = +httpServerUrl.port; + + const res = await req(httpServerUrl, { agent }); + const body = await json(res); + expect(body.host).toEqual(httpServerUrl.hostname); + }); + }); +}); diff --git a/packages/http-proxy-agent/test/tsconfig.json b/packages/http-proxy-agent/test/tsconfig.json new file mode 100644 index 00000000..a79e2e63 --- /dev/null +++ b/packages/http-proxy-agent/test/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["test.ts"] +} diff --git a/packages/https-proxy-agent/package.json b/packages/https-proxy-agent/package.json index 56054df2..e6e1ab11 100644 --- a/packages/https-proxy-agent/package.json +++ b/packages/https-proxy-agent/package.json @@ -12,8 +12,7 @@ "test": "jest --env node --verbose --bail test/test.ts", "test-e2e": "jest --env node --verbose --bail test/e2e.test.ts", "lint": "eslint --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "repository": { "type": "git", diff --git a/packages/https-proxy-agent/test/test.ts b/packages/https-proxy-agent/test/test.ts index 9e76c16f..dad550f1 100644 --- a/packages/https-proxy-agent/test/test.ts +++ b/packages/https-proxy-agent/test/test.ts @@ -317,13 +317,19 @@ describe('HttpsProxyAgent', () => { }); try { - const res = await req(sslServerUrl, { agent, rejectUnauthorized: false }); + const res = await req(sslServerUrl, { + agent, + rejectUnauthorized: false, + }); expect(res.headers.connection).toEqual('keep-alive'); res.resume(); const s1 = res.socket; await once(s1, 'free'); - const res2 = await req(sslServerUrl, { agent, rejectUnauthorized: false }); + const res2 = await req(sslServerUrl, { + agent, + rejectUnauthorized: false, + }); expect(res2.headers.connection).toEqual('keep-alive'); res2.resume(); const s2 = res2.socket; diff --git a/packages/pac-proxy-agent/jest.config.js b/packages/pac-proxy-agent/jest.config.js new file mode 100644 index 00000000..ee66e76e --- /dev/null +++ b/packages/pac-proxy-agent/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; diff --git a/packages/pac-proxy-agent/package.json b/packages/pac-proxy-agent/package.json index e4218000..6360bbc9 100644 --- a/packages/pac-proxy-agent/package.json +++ b/packages/pac-proxy-agent/package.json @@ -9,10 +9,9 @@ ], "scripts": { "build": "tsc", - "test": "mocha --reporter spec", + "test": "jest --env node --verbose --bail", "lint": "eslint --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "repository": { "type": "git", @@ -35,7 +34,6 @@ }, "homepage": "https://github.com/TooTallNate/node-pac-proxy-agent", "dependencies": { - "@tootallnate/once": "^2.0.0", "agent-base": "^6.0.2", "debug": "^4.3.4", "get-uri": "^5.0.0", @@ -46,10 +44,13 @@ }, "devDependencies": { "@types/debug": "^4.1.7", + "@types/jest": "^29.5.1", "@types/node": "^14.18.43", - "mocha": "^6.2.3", + "async-listen": "^2.1.0", + "jest": "^29.5.0", "proxy": "workspace:*", "socksv5": "0.0.6", + "ts-jest": "^29.1.0", "tsconfig": "workspace:*", "typescript": "^5.0.4" }, diff --git a/packages/pac-proxy-agent/src/index.ts b/packages/pac-proxy-agent/src/index.ts index 0ece4c32..bcc821d3 100644 --- a/packages/pac-proxy-agent/src/index.ts +++ b/packages/pac-proxy-agent/src/index.ts @@ -2,7 +2,7 @@ import * as net from 'net'; import * as tls from 'tls'; import * as http from 'http'; import * as crypto from 'crypto'; -import once from '@tootallnate/once'; +import { once } from 'events'; import createDebug from 'debug'; import { Readable } from 'stream'; import { format } from 'url'; diff --git a/packages/pac-proxy-agent/test/test.js b/packages/pac-proxy-agent/test/test.js deleted file mode 100644 index 005deab3..00000000 --- a/packages/pac-proxy-agent/test/test.js +++ /dev/null @@ -1,464 +0,0 @@ -/** - * Module dependencies. - */ - -let fs = require('fs'); -let url = require('url'); -let http = require('http'); -let https = require('https'); -let assert = require('assert'); -let socks = require('socksv5'); -let { createProxy } = require('proxy'); -let { toBuffer } = require('agent-base'); -let { PacProxyAgent } = require('../'); - -describe('PacProxyAgent', function () { - // target servers - let httpServer; - let httpPort; - let httpsServer; - let httpsPort; - - // proxy servers - let socksServer; - let socksPort; - let proxyServer; - let proxyPort; - let proxyHttpsServer; - let proxyHttpsPort; - - before(function (done) { - // setup target HTTP server - httpServer = http.createServer(); - httpServer.listen(function () { - httpPort = httpServer.address().port; - done(); - }); - }); - - before(function (done) { - // setup target SSL HTTPS server - let options = { - key: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.key`), - cert: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.pem`), - }; - httpsServer = https.createServer(options); - httpsServer.listen(function () { - httpsPort = httpsServer.address().port; - done(); - }); - }); - - before(function (done) { - // setup SOCKS proxy server - socksServer = socks.createServer(function (_info, accept) { - accept(); - }); - socksServer.listen(function () { - socksPort = socksServer.address().port; - done(); - }); - socksServer.useAuth(socks.auth.None()); - }); - - before(function (done) { - // setup HTTP proxy server - proxyServer = createProxy(); - proxyServer.listen(function () { - proxyPort = proxyServer.address().port; - done(); - }); - }); - - before(function (done) { - // setup SSL HTTPS proxy server - let options = { - key: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.key`), - cert: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.pem`), - }; - proxyHttpsServer = createProxy(https.createServer(options)); - proxyHttpsServer.listen(function () { - proxyHttpsPort = proxyHttpsServer.address().port; - done(); - }); - }); - - after(function (done) { - // socksServer.once('close', function () { done(); }); - socksServer.close(); - done(); - }); - - after(function (done) { - // httpServer.once('close', function () { done(); }); - httpServer.close(); - done(); - }); - - after(function (done) { - // httpsServer.once('close', function () { done(); }); - httpsServer.close(); - done(); - }); - - after(function (done) { - // proxyServer.once('close', function () { done(); }); - proxyServer.close(); - done(); - }); - - after(function (done) { - // proxyHttpsServer.once('close', function () { done(); }); - proxyHttpsServer.close(); - done(); - }); - - it('should allow a `sandbox` to be passed in', function (done) { - this.slow(1000); - - function FindProxyForURL() { - throw new Error(foo() + bar()); - } - - function foo() { - return 'hi'; - } - - function asyncBar() { - return new Promise((r) => r('fooooo')); - } - asyncBar.async = true; - - let uri = `data:,${encodeURIComponent(FindProxyForURL.toString())}`; - let agent = new PacProxyAgent(uri, { - sandbox: { - foo, - bar: asyncBar, - }, - }); - - let opts = url.parse(`http://localhost:${httpPort}/test`); - opts.agent = agent; - - let req = http.get(opts); - req.once('error', function (err) { - assert.equal(err.message, 'hifooooo'); - done(); - }); - }); - - describe('constructor', function () { - it('should throw an Error if no "proxy" argument is given', function () { - assert.throws(() => { - new PacProxyAgent(); - }); - }); - it('should accept a "string" proxy argument', function () { - let agent = new PacProxyAgent('pac+ftp://example.com/proxy.pac'); - assert.equal('ftp://example.com/proxy.pac', agent.uri.href); - }); - it('should accept a `URL` instance proxy argument', function () { - let agent = new PacProxyAgent( - new URL('pac+ftp://example.com/proxy.pac') - ); - assert.equal('ftp://example.com/proxy.pac', agent.uri.href); - }); - }); - - describe('"http" module', function () { - it('should work over an HTTP proxy', function (done) { - httpServer.once('request', function (req, res) { - res.end(JSON.stringify(req.headers)); - }); - - function FindProxyForURL(url, host) { - return 'PROXY localhost:PORT;'; - } - - let uri = `data:,${encodeURIComponent( - FindProxyForURL.toString().replace('PORT', proxyPort) - )}`; - let agent = new PacProxyAgent(uri); - - let opts = url.parse(`http://localhost:${httpPort}/test`); - opts.agent = agent; - - let req = http.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpPort}`, data.host); - assert('via' in data); - done(); - }); - }); - req.once('error', done); - }); - - it('should work over an HTTPS proxy', function (done) { - httpServer.once('request', function (req, res) { - res.end(JSON.stringify(req.headers)); - }); - - function FindProxyForURL(url, host) { - return 'HTTPS localhost:PORT;'; - } - - let uri = `data:,${encodeURIComponent( - FindProxyForURL.toString().replace('PORT', proxyHttpsPort) - )}`; - let agent = new PacProxyAgent(uri, { rejectUnauthorized: false }); - - let opts = url.parse(`http://localhost:${httpPort}/test`); - opts.agent = agent; - - let req = http.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpPort}`, data.host); - assert('via' in data); - done(); - }); - }); - req.once('error', done); - }); - - it('should work over a SOCKS proxy', function (done) { - httpServer.once('request', function (req, res) { - res.end(JSON.stringify(req.headers)); - }); - - function FindProxyForURL() { - return 'SOCKS localhost:PORT;'; - } - - let uri = `data:,${encodeURIComponent( - FindProxyForURL.toString().replace('PORT', socksPort) - )}`; - let agent = new PacProxyAgent(uri); - - let opts = url.parse(`http://localhost:${httpPort}/test`); - opts.agent = agent; - - let req = http.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpPort}`, data.host); - done(); - }); - }); - req.once('error', done); - }); - - it('should fall back to the next proxy after one fails', function (done) { - // This test is slow on Windows :/ - this.timeout(10000); - - let gotReq = false; - httpServer.once('request', function (req, res) { - res.end(JSON.stringify(req.headers)); - gotReq = true; - }); - - function FindProxyForURL(url, host) { - return 'SOCKS bad-domain:8080; HTTP bad-domain:8080; HTTPS bad-domain:8080; DIRECT;'; - } - - let uri = `data:,${encodeURIComponent(String(FindProxyForURL))}`; - let agent = new PacProxyAgent(uri); - - let opts = url.parse(`http://localhost:${httpPort}/test`); - opts.agent = agent; - - let req = http.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpPort}`, data.host); - assert.equal(proxyCount, 4); - assert(gotReq); - done(); - }); - }); - - let proxyCount = 0; - req.on('proxy', function ({ proxy, error, socket }) { - proxyCount++; - if (proxy === 'DIRECT') { - assert(socket); - } else { - assert(error); - } - }); - - req.once('error', done); - }); - - it('should support `fallbackToDirect` option', function (done) { - // This test is slow on Windows :/ - this.timeout(10000); - - let gotReq = false; - httpServer.once('request', function (req, res) { - res.end(JSON.stringify(req.headers)); - gotReq = true; - }); - - function FindProxyForURL(url, host) { - return 'SOCKS 127.0.0.1:4'; - } - - const uri = `data:,${encodeURIComponent(String(FindProxyForURL))}`; - const agent = new PacProxyAgent(uri, { fallbackToDirect: true }); - - const opts = url.parse(`http://localhost:${httpPort}/test`); - opts.agent = agent; - - const req = http.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpPort}`, data.host); - assert(gotReq); - done(); - }); - }); - req.once('error', done); - }); - }); - - describe('"https" module', function () { - it('should work over an HTTP proxy', function (done) { - httpsServer.once('request', function (req, res) { - res.end(JSON.stringify(req.headers)); - }); - - function FindProxyForURL(url, host) { - return 'PROXY localhost:PORT;'; - } - - let uri = `data:,${encodeURIComponent( - FindProxyForURL.toString().replace('PORT', proxyPort) - )}`; - let agent = new PacProxyAgent(uri); - - let opts = url.parse(`https://localhost:${httpsPort}/test`); - opts.agent = agent; - opts.rejectUnauthorized = false; - - let req = https.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpsPort}`, data.host); - done(); - }); - }); - req.once('error', done); - }); - - it('should work over an HTTPS proxy', function (done) { - let gotReq = false; - httpsServer.once('request', function (req, res) { - gotReq = true; - res.end(JSON.stringify(req.headers)); - }); - - function FindProxyForURL(url, host) { - return 'HTTPS localhost:PORT;'; - } - - let uri = `data:,${encodeURIComponent( - FindProxyForURL.toString().replace('PORT', proxyHttpsPort) - )}`; - let agent = new PacProxyAgent(uri, { - rejectUnauthorized: false, - }); - - let opts = url.parse(`https://localhost:${httpsPort}/test`); - opts.agent = agent; - opts.rejectUnauthorized = false; - - let req = https.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpsPort}`, data.host); - assert(gotReq); - done(); - }); - }); - req.once('error', done); - }); - - it('should work over a SOCKS proxy', function (done) { - let gotReq = false; - httpsServer.once('request', function (req, res) { - gotReq = true; - res.end(JSON.stringify(req.headers)); - }); - - function FindProxyForURL(url, host) { - return 'SOCKS localhost:PORT;'; - } - - let uri = `data:,${encodeURIComponent( - FindProxyForURL.toString().replace('PORT', socksPort) - )}`; - let agent = new PacProxyAgent(uri); - - let opts = url.parse(`https://localhost:${httpsPort}/test`); - opts.agent = agent; - opts.rejectUnauthorized = false; - - let req = https.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpsPort}`, data.host); - assert(gotReq); - done(); - }); - }); - req.once('error', done); - }); - - it('should fall back to the next proxy after one fails', function (done) { - // This test is slow on Windows :/ - this.timeout(10000); - - let gotReq = false; - httpsServer.once('request', function (req, res) { - gotReq = true; - res.end(JSON.stringify(req.headers)); - }); - - function FindProxyForURL(url, host) { - return 'SOCKS bad-domain:8080; HTTP bad-domain:8080; HTTPS bad-domain:8080; DIRECT;'; - } - - let uri = `data:,${encodeURIComponent(String(FindProxyForURL))}`; - let agent = new PacProxyAgent(uri); - - let opts = url.parse(`https://localhost:${httpsPort}/test`); - opts.agent = agent; - opts.rejectUnauthorized = false; - - let req = https.get(opts, function (res) { - toBuffer(res).then((buf) => { - let data = JSON.parse(buf.toString()); - assert.equal(`localhost:${httpsPort}`, data.host); - assert.equal(proxyCount, 4); - assert(gotReq); - done(); - }); - }); - - let proxyCount = 0; - req.on('proxy', function ({ proxy, error, socket }) { - proxyCount++; - if (proxy === 'DIRECT') { - assert(socket); - } else { - assert(error); - } - }); - - req.once('error', done); - }); - }); -}); diff --git a/packages/pac-proxy-agent/test/test.ts b/packages/pac-proxy-agent/test/test.ts new file mode 100644 index 00000000..10121944 --- /dev/null +++ b/packages/pac-proxy-agent/test/test.ts @@ -0,0 +1,362 @@ +import assert from 'assert'; +import * as fs from 'fs'; +import * as http from 'http'; +import * as https from 'https'; +// @ts-expect-error no types +import socks from 'socksv5'; +import { listen } from 'async-listen'; +import { ProxyServer, createProxy } from 'proxy'; +import { req, json } from 'agent-base'; +import { PacProxyAgent } from '../src'; + +const sslOptions = { + key: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.key`), + cert: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.pem`), +}; + +describe('PacProxyAgent', () => { + // target servers + let httpServer: http.Server; + let httpServerUrl: URL; + let httpsServer: https.Server; + let httpsServerUrl: URL; + + // proxy servers + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let socksServer: any; + let socksServerUrl: URL; + let proxyServer: ProxyServer; + let proxyServerUrl: URL; + let proxyHttpsServer: ProxyServer; + let proxyHttpsServerUrl: URL; + + beforeAll(async () => { + // setup target HTTP server + httpServer = http.createServer(); + httpServerUrl = (await listen(httpServer)) as URL; + }); + + beforeAll(async () => { + // setup target SSL HTTPS server + httpsServer = https.createServer(sslOptions); + httpsServerUrl = (await listen(httpsServer)) as URL; + }); + + beforeAll(async () => { + // setup SOCKS proxy server + // @ts-expect-error no types for `socksv5` + socksServer = socks.createServer(function (_info, accept) { + accept(); + }); + await listen(socksServer); + const port = socksServer.address().port; + socksServerUrl = new URL(`socks://127.0.0.1:${port}`); + socksServer.useAuth(socks.auth.None()); + }); + + beforeAll(async () => { + // setup HTTP proxy server + proxyServer = createProxy(); + proxyServerUrl = (await listen(proxyServer)) as URL; + }); + + beforeAll(async () => { + // setup SSL HTTPS proxy server + proxyHttpsServer = createProxy(https.createServer(sslOptions)); + proxyHttpsServerUrl = (await listen(proxyHttpsServer)) as URL; + }); + + afterAll(() => { + socksServer.close(); + httpServer.close(); + httpsServer.close(); + proxyServer.close(); + proxyHttpsServer.close(); + }); + + beforeEach(() => { + httpServer.removeAllListeners('request'); + httpsServer.removeAllListeners('request'); + }); + + it('should allow a `sandbox` to be passed in', async () => { + //this.slow(1000); + + function FindProxyForURL() { + // @ts-expect-error `bar()` is passed in via `sandbox` + throw new Error(foo() + bar()); + } + + function foo() { + return 'hi'; + } + + function asyncBar() { + return new Promise((r) => r('fooooo')); + } + asyncBar.async = true; + + const agent = new PacProxyAgent( + `data:,${encodeURIComponent(FindProxyForURL.toString())}`, + { + sandbox: { + foo, + bar: asyncBar, + }, + } + ); + + let err: Error | undefined; + try { + await req(httpServerUrl, { agent }); + } catch (_err) { + err = _err as Error; + } + assert(err); + assert.equal(err.message, 'hifooooo'); + }); + + describe('constructor', () => { + it('should accept a "string" proxy argument', () => { + const agent = new PacProxyAgent('pac+ftp://example.com/proxy.pac'); + assert.equal('ftp://example.com/proxy.pac', agent.uri.href); + }); + it('should accept a `URL` instance proxy argument', () => { + const agent = new PacProxyAgent( + new URL('pac+ftp://example.com/proxy.pac') + ); + assert.equal('ftp://example.com/proxy.pac', agent.uri.href); + }); + }); + + describe('"http" module', () => { + it('should work over an HTTP proxy', async () => { + httpServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + }); + + function FindProxyForURL() { + return 'PROXY localhost:PORT;'; + } + + const uri = `data:,${encodeURIComponent( + FindProxyForURL.toString().replace('PORT', proxyServerUrl.port) + )}`; + const agent = new PacProxyAgent(uri); + + const res = await req(new URL('/test', httpServerUrl), { agent }); + const data = await json(res); + assert.equal(httpServerUrl.host, data.host); + assert('via' in data); + }); + + it('should work over an HTTPS proxy', async () => { + httpServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + }); + + function FindProxyForURL() { + return 'HTTPS localhost:PORT;'; + } + + const uri = `data:,${encodeURIComponent( + FindProxyForURL.toString().replace( + 'PORT', + proxyHttpsServerUrl.port + ) + )}`; + const agent = new PacProxyAgent(uri, { rejectUnauthorized: false }); + + const res = await req(new URL('/test', httpServerUrl), { agent }); + const data = await json(res); + assert.equal(httpServerUrl.host, data.host); + assert('via' in data); + }); + + it('should work over a SOCKS proxy', async () => { + httpServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + }); + + function FindProxyForURL() { + return 'SOCKS localhost:PORT;'; + } + + const uri = `data:,${encodeURIComponent( + FindProxyForURL.toString().replace('PORT', socksServerUrl.port) + )}`; + const agent = new PacProxyAgent(uri); + + const res = await req(new URL('/test', httpServerUrl), { agent }); + const data = await json(res); + assert.equal(httpServerUrl.host, data.host); + }); + + it('should fall back to the next proxy after one fails', async () => { + let gotReq = false; + httpServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + gotReq = true; + }); + + function FindProxyForURL() { + return 'SOCKS bad-domain:8080; HTTP bad-domain:8080; HTTPS bad-domain:8080; DIRECT;'; + } + + const uri = `data:,${encodeURIComponent(String(FindProxyForURL))}`; + const agent = new PacProxyAgent(uri); + + const r = req(new URL('/test', httpServerUrl), { agent }); + + let proxyCount = 0; + r.on('proxy', function ({ proxy, error, socket }) { + proxyCount++; + if (proxy === 'DIRECT') { + assert(socket); + } else { + assert(error); + } + }); + + const res = await r; + const data = await json(res); + assert.equal(httpServerUrl.host, data.host); + assert.equal(proxyCount, 4); + assert(gotReq); + }, 10000); // This test is slow on Windows :/ + + it('should support `fallbackToDirect` option', async () => { + let gotReq = false; + httpServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + gotReq = true; + }); + + function FindProxyForURL() { + return 'SOCKS 127.0.0.1:4'; + } + + const uri = `data:,${encodeURIComponent(String(FindProxyForURL))}`; + const agent = new PacProxyAgent(uri, { fallbackToDirect: true }); + + const res = await req(new URL('/test', httpServerUrl), { agent }); + const data = await json(res); + assert.equal(httpServerUrl.host, data.host); + assert(gotReq); + }, 10000); // This test is slow on Windows :/ + }); + + describe('"https" module', () => { + it('should work over an HTTP proxy', async () => { + httpsServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + }); + + function FindProxyForURL() { + return 'PROXY localhost:PORT;'; + } + + const uri = `data:,${encodeURIComponent( + FindProxyForURL.toString().replace('PORT', proxyServerUrl.port) + )}`; + const agent = new PacProxyAgent(uri); + + const res = await req(new URL('/test', httpsServerUrl), { + agent, + rejectUnauthorized: false, + }); + const data = await json(res); + assert.equal(httpsServerUrl.host, data.host); + }); + + it('should work over an HTTPS proxy', async () => { + let gotReq = false; + httpsServer.once('request', function (req, res) { + gotReq = true; + res.end(JSON.stringify(req.headers)); + }); + + function FindProxyForURL() { + return 'HTTPS localhost:PORT;'; + } + + const uri = `data:,${encodeURIComponent( + FindProxyForURL.toString().replace( + 'PORT', + proxyHttpsServerUrl.port + ) + )}`; + const agent = new PacProxyAgent(uri, { + rejectUnauthorized: false, + }); + + const res = await req(new URL('/test', httpsServerUrl), { + agent, + rejectUnauthorized: false, + }); + const data = await json(res); + assert.equal(httpsServerUrl.host, data.host); + assert(gotReq); + }); + + it('should work over a SOCKS proxy', async () => { + let gotReq = false; + httpsServer.once('request', function (req, res) { + gotReq = true; + res.end(JSON.stringify(req.headers)); + }); + + function FindProxyForURL() { + return 'SOCKS localhost:PORT;'; + } + + const uri = `data:,${encodeURIComponent( + FindProxyForURL.toString().replace('PORT', socksServerUrl.port) + )}`; + const agent = new PacProxyAgent(uri); + + const res = await req(new URL('/test', httpsServerUrl), { + agent, + rejectUnauthorized: false, + }); + const data = await json(res); + assert.equal(httpsServerUrl.host, data.host); + assert(gotReq); + }); + + it('should fall back to the next proxy after one fails', async () => { + let gotReq = false; + httpsServer.once('request', function (req, res) { + gotReq = true; + res.end(JSON.stringify(req.headers)); + }); + + function FindProxyForURL() { + return 'SOCKS bad-domain:8080; HTTP bad-domain:8080; HTTPS bad-domain:8080; DIRECT;'; + } + + const uri = `data:,${encodeURIComponent(String(FindProxyForURL))}`; + const agent = new PacProxyAgent(uri); + const r = req(new URL('/test', httpsServerUrl), { + agent, + rejectUnauthorized: false, + }); + + let proxyCount = 0; + r.on('proxy', function ({ proxy, error, socket }) { + proxyCount++; + if (proxy === 'DIRECT') { + assert(socket); + } else { + assert(error); + } + }); + + const res = await r; + const data = await json(res); + assert.equal(httpsServerUrl.host, data.host); + assert.equal(proxyCount, 4); + assert(gotReq); + }, 10000); // This test is slow on Windows :/ + }); +}); diff --git a/packages/pac-proxy-agent/test/tsconfig.json b/packages/pac-proxy-agent/test/tsconfig.json new file mode 100644 index 00000000..a79e2e63 --- /dev/null +++ b/packages/pac-proxy-agent/test/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["test.ts"] +} diff --git a/packages/pac-resolver/jest.config.js b/packages/pac-resolver/jest.config.js new file mode 100644 index 00000000..ee66e76e --- /dev/null +++ b/packages/pac-resolver/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +}; diff --git a/packages/pac-resolver/package.json b/packages/pac-resolver/package.json index 43ed7d37..03e57a2e 100644 --- a/packages/pac-resolver/package.json +++ b/packages/pac-resolver/package.json @@ -14,18 +14,19 @@ }, "devDependencies": { "@types/ip": "^1.1.0", + "@types/jest": "^29.5.1", "@types/netmask": "^1.0.30", "@types/node": "^14.18.43", - "mocha": "^9.2.1", + "jest": "^29.5.0", + "ts-jest": "^29.1.0", "tsconfig": "workspace:*", "typescript": "^5.0.4" }, "scripts": { "build": "tsc", - "test": "mocha --reporter spec", + "test": "jest --env node --verbose --bail", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "repository": { "type": "git", diff --git a/packages/pac-resolver/src/index.ts b/packages/pac-resolver/src/index.ts index fab57f6b..1f83d545 100644 --- a/packages/pac-resolver/src/index.ts +++ b/packages/pac-resolver/src/index.ts @@ -1,4 +1,3 @@ -import { parse } from 'url'; import { Context } from 'vm'; import { CompileOptions, compile } from 'degenerator'; @@ -53,50 +52,18 @@ export function createPacResolver( opts ); - /* eslint-disable @typescript-eslint/naming-convention, no-redeclare */ - function FindProxyForURL(url: string, host?: string): Promise; function FindProxyForURL( - url: string, - callback: FindProxyForURLCallback - ): void; - function FindProxyForURL( - url: string, - host: string, - callback: FindProxyForURLCallback - ): void; - function FindProxyForURL( - url: string, - _host?: string | FindProxyForURLCallback, - _callback?: FindProxyForURLCallback - ): Promise | void { - let host: string | null = null; - let callback: FindProxyForURLCallback | null = null; - - if (typeof _callback === 'function') { - callback = _callback; - } - - if (typeof _host === 'string') { - host = _host; - } else if (typeof _host === 'function') { - callback = _host; - } - - if (!host) { - host = parse(url).hostname; - } + url: string | URL, + _host?: string + ): Promise { + const urlObj = typeof url === 'string' ? new URL(url) : url; + const host = _host || urlObj.hostname; if (!host) { throw new TypeError('Could not determine `host`'); } - const promise = resolver(url, host); - - if (typeof callback === 'function') { - toCallback(promise, callback); - } else { - return promise; - } + return resolver(urlObj.href, host); } Object.defineProperty(FindProxyForURL, 'toString', { diff --git a/packages/pac-resolver/test/dnsDomainIs.js b/packages/pac-resolver/test/dnsDomainIs.js deleted file mode 100644 index 928bef54..00000000 --- a/packages/pac-resolver/test/dnsDomainIs.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Module dependencies. - */ - -var assert = require('assert'); -const { dnsDomainIs } = require('../').sandbox; - -describe('dnsDomainIs(host, domain)', function () { - var tests = [ - ['www.netscape.com', '.netscape.com', true], - ['www', '.netscape.com', false], - ['www.mcom.com', '.netscape.com', false], - ]; - - tests.forEach(function (test) { - var expected = test.pop(); - it( - 'should return `' + expected + '` for "' + test.join('", "') + '"', - function () { - assert.equal(expected, dnsDomainIs(test[0], test[1])); - } - ); - }); -}); diff --git a/packages/pac-resolver/test/dnsDomainIs.test.ts b/packages/pac-resolver/test/dnsDomainIs.test.ts new file mode 100644 index 00000000..9192f2c4 --- /dev/null +++ b/packages/pac-resolver/test/dnsDomainIs.test.ts @@ -0,0 +1,14 @@ +import dnsDomainIs from '../src/dnsDomainIs'; + +describe('dnsDomainIs(host, domain)', () => { + test.each([ + { host: 'www.netscape.com', domain: '.netscape.com', expected: true }, + { host: 'www', domain: '.netscape.com', expected: false }, + { host: 'www.mcom.com', domain: '.netscape.com', expected: false }, + ])( + 'should return `$expected` for "$host, $domain"', + ({ host, domain, expected }) => { + expect(dnsDomainIs(host, domain)).toEqual(expected); + } + ); +}); diff --git a/packages/pac-resolver/test/dnsDomainLevels.js b/packages/pac-resolver/test/dnsDomainLevels.js deleted file mode 100644 index 7f503b86..00000000 --- a/packages/pac-resolver/test/dnsDomainLevels.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Module dependencies. - */ - -var assert = require('assert'); -var { dnsDomainLevels } = require('../').sandbox; - -describe('dnsDomainLevels(host)', function () { - var tests = [ - ['www', 0], - ['www.netscape', 1], - ['www.netscape.com', 2], - ]; - - tests.forEach(function (test) { - var expected = test.pop(); - it( - 'should return `' + expected + '` for "' + test.join('", "') + '"', - function () { - assert.equal(expected, dnsDomainLevels(test[0])); - } - ); - }); -}); diff --git a/packages/pac-resolver/test/dnsDomainLevels.test.ts b/packages/pac-resolver/test/dnsDomainLevels.test.ts new file mode 100644 index 00000000..31529153 --- /dev/null +++ b/packages/pac-resolver/test/dnsDomainLevels.test.ts @@ -0,0 +1,11 @@ +import dnsDomainLevels from '../src/dnsDomainLevels'; + +describe('dnsDomainLevels(host)', () => { + test.each([ + { input: 'www', expected: 0 }, + { input: 'www.netscape', expected: 1 }, + { input: 'www.netscape.com', expected: 2 }, + ])('should return `$expected` for "$input"', ({ input, expected }) => { + expect(dnsDomainLevels(input)).toEqual(expected); + }); +}); diff --git a/packages/pac-resolver/test/dnsResolve.js b/packages/pac-resolver/test/dnsResolve.js deleted file mode 100644 index d05157dd..00000000 --- a/packages/pac-resolver/test/dnsResolve.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Module dependencies. - */ - -var isIP = require('net').isIP; -var assert = require('assert'); -var { dnsResolve } = require('../').sandbox; - -describe('dnsResolve(host)', function () { - var tests = [ - ['www.netscape.com', true], - ['bogus.domain.foobar', false], - ]; - - tests.forEach(function (test) { - var expected = test.pop(); - if (expected) { - it( - 'should resolve an IPv4 address for "' + - test.join('", "') + - '"', - function (done) { - dnsResolve(test[0]).then((res) => { - assert.equal('string', typeof res); - assert.equal(4, isIP(res)); - done(); - }, done); - } - ); - } else { - it( - 'should return null for if can\'t be resolved "' + - test.join('", "') + - '"', - function (done) { - dnsResolve(test[0]).then((res) => { - assert.equal(null, res); - done(); - }, done); - } - ); - } - }); -}); diff --git a/packages/pac-resolver/test/dnsResolve.test.ts b/packages/pac-resolver/test/dnsResolve.test.ts new file mode 100644 index 00000000..af4ae049 --- /dev/null +++ b/packages/pac-resolver/test/dnsResolve.test.ts @@ -0,0 +1,21 @@ +import assert from 'assert'; +import { isIP } from 'net'; +import dnsResolve from '../src/dnsResolve'; + +describe('dnsResolve(host)', function () { + test.each([ + { input: 'www.netscape.com', expected: true }, + { input: 'bogus.domain.foobar', expected: false }, + ])( + 'should return `$expected` for "$input"', + async ({ input, expected }) => { + const res = await dnsResolve(input); + if (expected) { + assert(typeof res === 'string'); + expect(isIP(res)).toEqual(4); + } else { + expect(res).toBeNull(); + } + } + ); +}); diff --git a/packages/pac-resolver/test/isInNet.js b/packages/pac-resolver/test/isInNet.test.ts similarity index 54% rename from packages/pac-resolver/test/isInNet.js rename to packages/pac-resolver/test/isInNet.test.ts index 63929972..d86059e5 100644 --- a/packages/pac-resolver/test/isInNet.js +++ b/packages/pac-resolver/test/isInNet.test.ts @@ -1,12 +1,7 @@ -/** - * Module dependencies. - */ +import isInNet from '../src/isInNet'; -var assert = require('assert'); -var { isInNet } = require('../').sandbox; - -describe('isInNet(host, pattern, mask)', function () { - var tests = [ +describe('isInNet(host, pattern, mask)', () => { + const tests = [ ['198.95.249.79', '198.95.249.79', '255.255.255.255', true], ['198.95.249.78', '198.95.249.79', '255.255.255.255', false], ['198.95.1.1', '198.95.0.0', '255.255.0.0', true], @@ -15,14 +10,13 @@ describe('isInNet(host, pattern, mask)', function () { ]; tests.forEach(function (test) { - var expected = test.pop(); + const expected = test.pop(); it( 'should return `' + expected + '` for "' + test.join('", "') + '"', - function (done) { - isInNet(test[0], test[1], test[2]).then((res) => { - assert.equal(expected, res); - done(); - }, done); + async () => { + // @ts-expect-error bad types + const res = await isInNet(test[0], test[1], test[2]); + expect(res).toEqual(expected); } ); }); diff --git a/packages/pac-resolver/test/isPlainHostName.js b/packages/pac-resolver/test/isPlainHostName.js deleted file mode 100644 index a6f44673..00000000 --- a/packages/pac-resolver/test/isPlainHostName.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Module dependencies. - */ - -var assert = require('assert'); -var { isPlainHostName } = require('../').sandbox; - -describe('isPlainHostName(host)', function () { - var tests = [ - ['www', true], - ['www.netscape.com', false], - ]; - - tests.forEach(function (test) { - var expected = test.pop(); - it( - 'should return `' + expected + '` for "' + test.join('", "') + '"', - function () { - assert.equal(expected, isPlainHostName(test[0])); - } - ); - }); -}); diff --git a/packages/pac-resolver/test/isPlainHostName.test.ts b/packages/pac-resolver/test/isPlainHostName.test.ts new file mode 100644 index 00000000..89469de5 --- /dev/null +++ b/packages/pac-resolver/test/isPlainHostName.test.ts @@ -0,0 +1,10 @@ +import isPlainHostName from '../src/isPlainHostName'; + +describe('isPlainHostName(host)', function () { + test.each([ + { input: 'www', expected: true }, + { input: 'www.netscape.com', expected: false }, + ])('should return `$expected` for "$input"', ({ input, expected }) => { + expect(isPlainHostName(input)).toEqual(expected); + }); +}); diff --git a/packages/pac-resolver/test/isResolvable.js b/packages/pac-resolver/test/isResolvable.js deleted file mode 100644 index ac76fefb..00000000 --- a/packages/pac-resolver/test/isResolvable.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Module dependencies. - */ - -var assert = require('assert'); -const { isResolvable } = require('../').sandbox; - -describe('isResolvable(host)', function () { - var tests = [ - ['www.netscape.com', true], - ['bogus.domain.foobar', false], - ]; - - tests.forEach(function (test) { - var expected = test.pop(); - it( - 'should return `' + expected + '` for "' + test.join('", "') + '"', - function (done) { - isResolvable(test[0]).then((res) => { - assert.equal(expected, res); - done(); - }, done); - } - ); - }); -}); diff --git a/packages/pac-resolver/test/isResolvable.test.ts b/packages/pac-resolver/test/isResolvable.test.ts new file mode 100644 index 00000000..0b7c033e --- /dev/null +++ b/packages/pac-resolver/test/isResolvable.test.ts @@ -0,0 +1,14 @@ +import isResolvable from '../src/isResolvable'; + +describe('isResolvable(host)', () => { + test.each([ + { input: 'www.netscape.com', expected: true }, + { input: 'bogus.domain.foobar', expected: false }, + ])( + 'should return `$expected` for "$input"', + async ({ input, expected }) => { + const res = await isResolvable(input); + expect(res).toEqual(expected); + } + ); +}); diff --git a/packages/pac-resolver/test/localHostOrDomainIs.js b/packages/pac-resolver/test/localHostOrDomainIs.js deleted file mode 100644 index c99d4d5e..00000000 --- a/packages/pac-resolver/test/localHostOrDomainIs.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Module dependencies. - */ - -var assert = require('assert'); -const { localHostOrDomainIs } = require('../').sandbox; - -describe('localHostOrDomainIs(host, hostdom)', function () { - var tests = [ - ['www.netscape.com', 'www.netscape.com', true], - ['www', 'www.netscape.com', true], - ['www.mcom.com', 'www.netscape.com', false], - ['home.netscape.com', 'www.netscape.com', false], - ]; - - tests.forEach(function (test) { - var expected = test.pop(); - it( - 'should return `' + expected + '` for "' + test.join('", "') + '"', - function () { - assert.equal(expected, localHostOrDomainIs(test[0], test[1])); - } - ); - }); -}); diff --git a/packages/pac-resolver/test/localHostOrDomainIs.test.ts b/packages/pac-resolver/test/localHostOrDomainIs.test.ts new file mode 100644 index 00000000..f8bfb906 --- /dev/null +++ b/packages/pac-resolver/test/localHostOrDomainIs.test.ts @@ -0,0 +1,23 @@ +import localHostOrDomainIs from '../src/localHostOrDomainIs'; + +describe('localHostOrDomainIs(host, hostdom)', () => { + test.each([ + { + host: 'www.netscape.com', + hostdom: 'www.netscape.com', + expected: true, + }, + { host: 'www', hostdom: 'www.netscape.com', expected: true }, + { host: 'www.mcom.com', hostdom: 'www.netscape.com', expected: false }, + { + host: 'home.netscape.com', + hostdom: 'www.netscape.com', + expected: false, + }, + ])( + 'should return `$expected` for "$host", "$hostdom"', + ({ host, hostdom, expected }) => { + expect(localHostOrDomainIs(host, hostdom)).toEqual(expected); + } + ); +}); diff --git a/packages/pac-resolver/test/myIpAddress.js b/packages/pac-resolver/test/myIpAddress.js deleted file mode 100644 index ea8bc9a3..00000000 --- a/packages/pac-resolver/test/myIpAddress.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Module dependencies. - */ - -var isIP = require('net').isIP; -var assert = require('assert'); -var { myIpAddress } = require('../').sandbox; - -describe('myIpAddress()', function () { - it('should return an IPv4 address', function (done) { - myIpAddress().then((ip) => { - assert.equal(4, isIP(ip)); - done(); - }, done); - }); -}); diff --git a/packages/pac-resolver/test/myIpAddress.test.ts b/packages/pac-resolver/test/myIpAddress.test.ts new file mode 100644 index 00000000..3e3bcfb8 --- /dev/null +++ b/packages/pac-resolver/test/myIpAddress.test.ts @@ -0,0 +1,9 @@ +import { isIP } from 'net'; +import myIpAddress from '../src/myIpAddress'; + +describe('myIpAddress()', function () { + it('should return an IPv4 address', async () => { + const ip = await myIpAddress(); + expect(isIP(ip)).toEqual(4); + }); +}); diff --git a/packages/pac-resolver/test/shExpMatch.js b/packages/pac-resolver/test/shExpMatch.js deleted file mode 100644 index 9c8318aa..00000000 --- a/packages/pac-resolver/test/shExpMatch.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Module dependencies. - */ - -var assert = require('assert'); -var { shExpMatch } = require('../').sandbox; - -describe('shExpMatch(str, shexp)', function () { - var tests = [ - ['http://home.netscape.com/people/ari/index.html', '*/ari/*', true], - [ - 'http://home.netscape.com/people/montulli/index.html', - '*/ari/*', - false, - ], - [ - 'http://home.example.com/people/yourpage/index.html', - '.*/mypage/.*', - false, - ], - ['www.hotmail.com', '*hotmail.com*', true], - ['phishing-scam.com?email=someone@hotmail.com', '*hotmail.com*', true], - ['abcdomain.com', '(*.abcdomain.com|abcdomain.com)', true], - ['foo.abcdomain.com', '(*.abcdomain.com|abcdomain.com)', true], - ['abddomain.com', '(*.abcdomain.com|abcdomain.com)', false], - ['abcdomain.com', '*.n.com', false], - ['a.com', '?.com', true], - ['b.com', '?.com', true], - ['ab.com', '?.com', false], - ]; - - tests.forEach(function (test) { - var expected = test.pop(); - it( - 'should return `' + expected + '` for "' + test.join('", "') + '"', - function () { - assert.equal(expected, shExpMatch(test[0], test[1])); - } - ); - }); -}); diff --git a/packages/pac-resolver/test/shExpMatch.test.ts b/packages/pac-resolver/test/shExpMatch.test.ts new file mode 100644 index 00000000..3fd4c44f --- /dev/null +++ b/packages/pac-resolver/test/shExpMatch.test.ts @@ -0,0 +1,51 @@ +import shExpMatch from '../src/shExpMatch'; + +describe('shExpMatch(str, shexp)', () => { + test.each([ + { + str: 'http://home.netscape.com/people/ari/index.html', + shexp: '*/ari/*', + expected: true, + }, + { + str: 'http://home.netscape.com/people/montulli/index.html', + shexp: '*/ari/*', + expected: false, + }, + { + str: 'http://home.example.com/people/yourpage/index.html', + shexp: '.*/mypage/.*', + expected: false, + }, + { str: 'www.hotmail.com', shexp: '*hotmail.com*', expected: true }, + { + str: 'phishing-scam.com?email=someone@hotmail.com', + shexp: '*hotmail.com*', + expected: true, + }, + { + str: 'abcdomain.com', + shexp: '(*.abcdomain.com|abcdomain.com)', + expected: true, + }, + { + str: 'foo.abcdomain.com', + shexp: '(*.abcdomain.com|abcdomain.com)', + expected: true, + }, + { + str: 'abddomain.com', + shexp: '(*.abcdomain.com|abcdomain.com)', + expected: false, + }, + { str: 'abcdomain.com', shexp: '*.n.com', expected: false }, + { str: 'a.com', shexp: '?.com', expected: true }, + { str: 'b.com', shexp: '?.com', expected: true }, + { str: 'ab.com', shexp: '?.com', expected: false }, + ])( + 'should return `$expected` for "$str", "$shexp"', + ({ str, shexp, expected }) => { + expect(shExpMatch(str, shexp)).toEqual(expected); + } + ); +}); diff --git a/packages/pac-resolver/test/test.js b/packages/pac-resolver/test/test.js deleted file mode 100644 index 6d88b8d3..00000000 --- a/packages/pac-resolver/test/test.js +++ /dev/null @@ -1,389 +0,0 @@ -const assert = require('assert'); -const { resolve } = require('path'); -const { readFileSync } = require('fs'); -const { createPacResolver } = require('../'); - -describe('FindProxyForURL', function () { - it('should return `undefined` by default', function (done) { - var FindProxyForURL = createPacResolver( - 'function FindProxyForURL (url, host) {' + ' /* noop */' + '}' - ); - FindProxyForURL('http://foo.com/', 'foo.com', function (err, res) { - if (err) return done(err); - assert.strictEqual(undefined, res); - done(); - }); - }); - - it('should return the value that gets returned', function (done) { - var FindProxyForURL = createPacResolver( - 'function FindProxyForURL (url, host) {' + - ' return { foo: "bar" };' + - '}' - ); - FindProxyForURL('http://foo.com/', 'foo.com', function (err, res) { - if (err) return done(err); - assert.deepEqual({ foo: 'bar' }, res); - done(); - }); - }); - - it('should not modify the passed-in options object', function (done) { - function foo() {} - const opts = { sandbox: { foo } }; - const FindProxyForURL = createPacResolver( - 'function FindProxyForURL (url, host) { return typeof foo; }', - opts - ); - assert.deepEqual(opts, { sandbox: { foo } }); - FindProxyForURL('http://foo.com/', function (err, res) { - if (err) return done(err); - assert.deepEqual('function', res); - done(); - }); - }); - - it('should prevent untrusted code from escaping the sandbox', function () { - let err; - try { - createPacResolver( - `// Real PAC config: - function FindProxyForURL(url, host) { - return "DIRECT"; - } - - // But also run arbitrary code: - var f = this.constructor.constructor(\` - process.exit(1); - \`); - - f(); - ` - ); - } catch (_err) { - err = _err; - } - assert.strictEqual(err.message, 'process is not defined'); - }); - - describe('official docs Example #1', function () { - var FindProxyForURL = createPacResolver( - 'function FindProxyForURL(url, host) {' + - ' if (isPlainHostName(host) ||' + - ' dnsDomainIs(host, ".netscape.com"))' + - ' return "DIRECT";' + - ' else' + - ' return "PROXY w3proxy.netscape.com:8080; DIRECT";' + - '}' - ); - - it('should return "DIRECT" for "localhost"', function (done) { - FindProxyForURL( - 'http://localhost/hello', - 'localhost', - function (err, res) { - if (err) return done(err); - assert.equal('DIRECT', res); - done(); - } - ); - }); - - it('should return "DIRECT" for "foo.netscape.com"', function (done) { - FindProxyForURL( - 'http://foo.netscape.com/', - 'foo.netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal('DIRECT', res); - done(); - } - ); - }); - - it('should return "PROXY …" for "google.com"', function (done) { - FindProxyForURL( - 'http://google.com/t', - 'google.com', - function (err, res) { - if (err) return done(err); - assert.equal( - 'PROXY w3proxy.netscape.com:8080; DIRECT', - res - ); - done(); - } - ); - }); - }); - - describe('official docs Example #1b', function () { - var FindProxyForURL = createPacResolver( - 'function FindProxyForURL(url, host)' + - '{' + - ' if ((isPlainHostName(host) ||' + - ' dnsDomainIs(host, ".netscape.com")) &&' + - ' !localHostOrDomainIs(host, "www.netscape.com") &&' + - ' !localHostOrDomainIs(host, "merchant.netscape.com"))' + - ' return "DIRECT";' + - ' else' + - ' return "PROXY w3proxy.netscape.com:8080; DIRECT";' + - '}' - ); - - it('should return "DIRECT" for "localhost"', function (done) { - FindProxyForURL( - 'http://localhost/hello', - 'localhost', - function (err, res) { - if (err) return done(err); - assert.equal('DIRECT', res); - done(); - } - ); - }); - - it('should return "DIRECT" for "foo.netscape.com"', function (done) { - FindProxyForURL( - 'http://foo.netscape.com/', - 'foo.netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal('DIRECT', res); - done(); - } - ); - }); - - it('should return "PROXY …" for "www.netscape.com"', function (done) { - FindProxyForURL( - 'http://www.netscape.com/', - 'www.netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal( - 'PROXY w3proxy.netscape.com:8080; DIRECT', - res - ); - done(); - } - ); - }); - - it('should return "PROXY …" for "merchant.netscape.com"', function (done) { - FindProxyForURL( - 'http://merchant.netscape.com/', - 'merchant.netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal( - 'PROXY w3proxy.netscape.com:8080; DIRECT', - res - ); - done(); - } - ); - }); - }); - - describe('official docs Example #5', function () { - var FindProxyForURL = createPacResolver( - 'function FindProxyForURL(url, host)' + - '{' + - ' if (url.substring(0, 5) == "http:") {' + - ' return "PROXY http-proxy.mydomain.com:8080";' + - ' }' + - ' else if (url.substring(0, 4) == "ftp:") {' + - ' return "PROXY ftp-proxy.mydomain.com:8080";' + - ' }' + - ' else if (url.substring(0, 7) == "gopher:") {' + - ' return "PROXY gopher-proxy.mydomain.com:8080";' + - ' }' + - ' else if (url.substring(0, 6) == "https:" ||' + - ' url.substring(0, 6) == "snews:") {' + - ' return "PROXY security-proxy.mydomain.com:8080";' + - ' }' + - ' else {' + - ' return "DIRECT";' + - ' }' + - '}' - ); - - it('should return "DIRECT" for "foo://netscape.com"', function (done) { - FindProxyForURL( - 'foo://netscape.com/hello', - 'netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal('DIRECT', res); - done(); - } - ); - }); - - it('should return "PROXY http…" for "http://netscape.com"', function (done) { - FindProxyForURL( - 'http://netscape.com/hello', - 'netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal('PROXY http-proxy.mydomain.com:8080', res); - done(); - } - ); - }); - - it('should return "PROXY ftp…" for "ftp://netscape.com"', function (done) { - FindProxyForURL( - 'ftp://netscape.com/hello', - 'netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal('PROXY ftp-proxy.mydomain.com:8080', res); - done(); - } - ); - }); - - it('should return "PROXY gopher…" for "gopher://netscape.com"', function (done) { - FindProxyForURL( - 'gopher://netscape.com/hello', - 'netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal('PROXY gopher-proxy.mydomain.com:8080', res); - done(); - } - ); - }); - - it('should return "PROXY security…" for "https://netscape.com"', function (done) { - FindProxyForURL( - 'https://netscape.com/hello', - 'netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal('PROXY security-proxy.mydomain.com:8080', res); - done(); - } - ); - }); - - it('should return "PROXY security…" for "snews://netscape.com"', function (done) { - FindProxyForURL( - 'snews://netscape.com/hello', - 'netscape.com', - function (err, res) { - if (err) return done(err); - assert.equal('PROXY security-proxy.mydomain.com:8080', res); - done(); - } - ); - }); - }); - - describe('GitHub issue #3', function () { - var FindProxyForURL = createPacResolver( - 'function FindProxyForURL(url, host) {\n' + - ' if (isHostInAnySubnet(host, ["10.1.2.0", "10.1.3.0"], "255.255.255.0")) {\n' + - ' return "HTTPS proxy.example.com";\n' + - ' }\n' + - '\n' + - ' if (isHostInAnySubnet(host, ["10.2.2.0", "10.2.3.0"], "255.255.255.0")) {\n' + - ' return "HTTPS proxy.example.com";\n' + - ' }\n' + - '\n' + - ' // Everything else, go direct:\n' + - ' return "DIRECT";\n' + - '}\n' + - '\n' + - '// Checks if the single host is within a list of subnets using the single mask.\n' + - 'function isHostInAnySubnet(host, subnets, mask) {\n' + - ' var subnets_length = subnets.length;\n' + - ' for (i = 0; i < subnets_length; i++) {\n' + - ' if (isInNet(host, subnets[i], mask)) {\n' + - ' return true;\n' + - ' }\n' + - ' }\n' + - '}\n' - ); - - it('should return "HTTPS proxy.example.com" for "http://10.1.2.3/bar.html"', function (done) { - FindProxyForURL( - 'http://10.1.2.3/bar.html', - '10.1.2.3', - function (err, res) { - if (err) return done(err); - assert.equal('HTTPS proxy.example.com', res); - done(); - } - ); - }); - - it('should return "DIRECT" for "http://foo.com/bar.html"', function (done) { - FindProxyForURL( - 'http://foo.com/bar.html', - 'foo.com', - function (err, res) { - if (err) return done(err); - assert.equal('DIRECT', res); - done(); - } - ); - }); - }); - - // https://github.com/breakwa11/gfw_whitelist - // https://github.com/TooTallNate/node-pac-resolver/issues/20 - describe('GitHub issue #20', function () { - const FindProxyForURL = createPacResolver( - readFileSync(resolve(__dirname, 'fixtures/gfw_whitelist.pac')) - ); - - it('should return "DIRECT" for "https://example.cn"', function (done) { - FindProxyForURL('https://example.cn/').then((res) => { - assert.equal('DIRECT;', res); - done(); - }, done); - }); - - it('should return "SOCKS5 127.0.0.1:1080;" for "https://example.com"', function (done) { - FindProxyForURL('https://example.com/').then((res) => { - assert.equal('SOCKS5 127.0.0.1:1080;', res); - done(); - }, done); - }); - }); - - describe('`filename` option', function () { - const code = String(function FindProxyForURL() { - throw new Error('fail'); - }); - - it('should include `proxy.pac` in stack traces by default', function (done) { - const FindProxyForURL = createPacResolver(code); - FindProxyForURL('https://example.com/').catch((err) => { - assert(err); - assert.equal(err.message, 'fail'); - assert( - err.stack.indexOf('at FindProxyForURL (proxy.pac:') !== -1 - ); - done(); - }); - }); - - it('should include `fail.pac` in stack traces by option', function (done) { - const FindProxyForURL = createPacResolver(code, { - filename: 'fail.pac', - }); - FindProxyForURL('https://example.com/').catch((err) => { - assert(err); - assert.equal(err.message, 'fail'); - assert( - err.stack.indexOf('at FindProxyForURL (fail.pac:') !== -1 - ); - done(); - }); - }); - }); -}); diff --git a/packages/pac-resolver/test/test.ts b/packages/pac-resolver/test/test.ts new file mode 100644 index 00000000..fba8b4b5 --- /dev/null +++ b/packages/pac-resolver/test/test.ts @@ -0,0 +1,316 @@ +import assert from 'assert'; +import { resolve } from 'path'; +import { readFileSync } from 'fs'; +import { createPacResolver } from '../src'; + +describe('FindProxyForURL', () => { + it('should return `undefined` by default', async () => { + const FindProxyForURL = createPacResolver( + 'function FindProxyForURL (url, host) {' + ' /* noop */' + '}' + ); + const res = await FindProxyForURL('http://foo.com/', 'foo.com'); + expect(res).toBeUndefined(); + }); + + it('should return the value that gets returned', async () => { + const FindProxyForURL = createPacResolver( + 'function FindProxyForURL (url, host) {' + + ' return { foo: "bar" };' + + '}' + ); + const res = await FindProxyForURL('http://foo.com/', 'foo.com'); + expect(res).toEqual({ foo: 'bar' }); + }); + + it('should not modify the passed-in options object', async () => { + function foo() { + // empty + } + const opts = { sandbox: { foo } }; + const FindProxyForURL = createPacResolver( + 'function FindProxyForURL (url, host) { return typeof foo; }', + opts + ); + assert.deepEqual(opts, { sandbox: { foo } }); + const res = await FindProxyForURL('http://foo.com/'); + expect(res).toEqual('function'); + }); + + it('should prevent untrusted code from escaping the sandbox', () => { + let err: Error | undefined; + try { + createPacResolver( + `// Real PAC config: + function FindProxyForURL(url, host) { + return "DIRECT"; + } + + // But also run arbitrary code: + var f = this.constructor.constructor(\` + process.exit(1); + \`); + + f(); + ` + ); + } catch (_err) { + err = _err as Error; + } + assert(err); + expect(err.message).toEqual('process is not defined'); + }); + + describe('official docs Example #1', () => { + const FindProxyForURL = createPacResolver( + 'function FindProxyForURL(url, host) {' + + ' if (isPlainHostName(host) ||' + + ' dnsDomainIs(host, ".netscape.com"))' + + ' return "DIRECT";' + + ' else' + + ' return "PROXY w3proxy.netscape.com:8080; DIRECT";' + + '}' + ); + + it('should return "DIRECT" for "localhost"', async () => { + const res = await FindProxyForURL( + 'http://localhost/hello', + 'localhost' + ); + expect(res).toEqual('DIRECT'); + }); + + it('should return "DIRECT" for "foo.netscape.com"', async () => { + const res = await FindProxyForURL( + 'http://foo.netscape.com/', + 'foo.netscape.com' + ); + expect(res).toEqual('DIRECT'); + }); + + it('should return "PROXY …" for "google.com"', async () => { + const res = await FindProxyForURL( + 'http://google.com/t', + 'google.com' + ); + expect(res).toEqual('PROXY w3proxy.netscape.com:8080; DIRECT'); + }); + }); + + describe('official docs Example #1b', () => { + const FindProxyForURL = createPacResolver( + 'function FindProxyForURL(url, host)' + + '{' + + ' if ((isPlainHostName(host) ||' + + ' dnsDomainIs(host, ".netscape.com")) &&' + + ' !localHostOrDomainIs(host, "www.netscape.com") &&' + + ' !localHostOrDomainIs(host, "merchant.netscape.com"))' + + ' return "DIRECT";' + + ' else' + + ' return "PROXY w3proxy.netscape.com:8080; DIRECT";' + + '}' + ); + + it('should return "DIRECT" for "localhost"', async () => { + const res = await FindProxyForURL( + 'http://localhost/hello', + 'localhost' + ); + expect(res).toEqual('DIRECT'); + }); + + it('should return "DIRECT" for "foo.netscape.com"', async () => { + const res = await FindProxyForURL( + 'http://foo.netscape.com/', + 'foo.netscape.com' + ); + expect(res).toEqual('DIRECT'); + }); + + it('should return "PROXY …" for "www.netscape.com"', async () => { + const res = await FindProxyForURL( + 'http://www.netscape.com/', + 'www.netscape.com' + ); + expect(res).toEqual('PROXY w3proxy.netscape.com:8080; DIRECT'); + }); + + it('should return "PROXY …" for "merchant.netscape.com"', async () => { + const res = await FindProxyForURL( + 'http://merchant.netscape.com/', + 'merchant.netscape.com' + ); + expect(res).toEqual('PROXY w3proxy.netscape.com:8080; DIRECT'); + }); + }); + + describe('official docs Example #5', () => { + const FindProxyForURL = createPacResolver( + 'function FindProxyForURL(url, host)' + + '{' + + ' if (url.substring(0, 5) == "http:") {' + + ' return "PROXY http-proxy.mydomain.com:8080";' + + ' }' + + ' else if (url.substring(0, 4) == "ftp:") {' + + ' return "PROXY ftp-proxy.mydomain.com:8080";' + + ' }' + + ' else if (url.substring(0, 7) == "gopher:") {' + + ' return "PROXY gopher-proxy.mydomain.com:8080";' + + ' }' + + ' else if (url.substring(0, 6) == "https:" ||' + + ' url.substring(0, 6) == "snews:") {' + + ' return "PROXY security-proxy.mydomain.com:8080";' + + ' }' + + ' else {' + + ' return "DIRECT";' + + ' }' + + '}' + ); + + it('should return "DIRECT" for "foo://netscape.com"', async () => { + const res = await FindProxyForURL( + 'foo://netscape.com/hello', + 'netscape.com' + ); + expect(res).toEqual('DIRECT'); + }); + + it('should return "PROXY http…" for "http://netscape.com"', async () => { + const res = await FindProxyForURL( + 'http://netscape.com/hello', + 'netscape.com' + ); + expect(res).toEqual('PROXY http-proxy.mydomain.com:8080'); + }); + + it('should return "PROXY ftp…" for "ftp://netscape.com"', async () => { + const res = await FindProxyForURL( + 'ftp://netscape.com/hello', + 'netscape.com' + ); + expect(res).toEqual('PROXY ftp-proxy.mydomain.com:8080'); + }); + + it('should return "PROXY gopher…" for "gopher://netscape.com"', async () => { + const res = await FindProxyForURL( + 'gopher://netscape.com/hello', + 'netscape.com' + ); + expect(res).toEqual('PROXY gopher-proxy.mydomain.com:8080'); + }); + + it('should return "PROXY security…" for "https://netscape.com"', async () => { + const res = await FindProxyForURL( + 'https://netscape.com/hello', + 'netscape.com' + ); + expect(res).toEqual('PROXY security-proxy.mydomain.com:8080'); + }); + + it('should return "PROXY security…" for "snews://netscape.com"', async () => { + const res = await FindProxyForURL( + 'snews://netscape.com/hello', + 'netscape.com' + ); + expect(res).toEqual('PROXY security-proxy.mydomain.com:8080'); + }); + }); + + describe('GitHub issue #3', () => { + const FindProxyForURL = createPacResolver( + 'function FindProxyForURL(url, host) {\n' + + ' if (isHostInAnySubnet(host, ["10.1.2.0", "10.1.3.0"], "255.255.255.0")) {\n' + + ' return "HTTPS proxy.example.com";\n' + + ' }\n' + + '\n' + + ' if (isHostInAnySubnet(host, ["10.2.2.0", "10.2.3.0"], "255.255.255.0")) {\n' + + ' return "HTTPS proxy.example.com";\n' + + ' }\n' + + '\n' + + ' // Everything else, go direct:\n' + + ' return "DIRECT";\n' + + '}\n' + + '\n' + + '// Checks if the single host is within a list of subnets using the single mask.\n' + + 'function isHostInAnySubnet(host, subnets, mask) {\n' + + ' var subnets_length = subnets.length;\n' + + ' for (i = 0; i < subnets_length; i++) {\n' + + ' if (isInNet(host, subnets[i], mask)) {\n' + + ' return true;\n' + + ' }\n' + + ' }\n' + + '}\n' + ); + + it('should return "HTTPS proxy.example.com" for "http://10.1.2.3/bar.html"', async () => { + const res = await FindProxyForURL( + 'http://10.1.2.3/bar.html', + '10.1.2.3' + ); + expect(res).toEqual('HTTPS proxy.example.com'); + }); + + it('should return "DIRECT" for "http://foo.com/bar.html"', async () => { + const res = await FindProxyForURL( + 'http://foo.com/bar.html', + 'foo.com' + ); + expect(res).toEqual('DIRECT'); + }); + }); + + // https://github.com/breakwa11/gfw_whitelist + // https://github.com/TooTallNate/node-pac-resolver/issues/20 + describe('GitHub issue #20', () => { + const FindProxyForURL = createPacResolver( + readFileSync(resolve(__dirname, 'fixtures/gfw_whitelist.pac')) + ); + + it('should return "DIRECT" for "https://example.cn"', async () => { + const res = await FindProxyForURL('https://example.cn/'); + expect(res).toEqual('DIRECT;'); + }); + + it('should return "SOCKS5 127.0.0.1:1080;" for "https://example.com"', async () => { + const res = await FindProxyForURL('https://example.com/'); + expect(res).toEqual('SOCKS5 127.0.0.1:1080;'); + }); + }); + + describe('`filename` option', () => { + const code = String(function FindProxyForURL() { + throw new Error('fail'); + }); + + it('should include `proxy.pac` in stack traces by default', async () => { + let err: Error | undefined; + const FindProxyForURL = createPacResolver(code); + try { + await FindProxyForURL('https://example.com/'); + } catch (_err) { + err = _err as Error; + } + assert(err); + expect(err.message).toEqual('fail'); + expect( + err.stack?.indexOf('at FindProxyForURL (proxy.pac:') + ).not.toEqual(-1); + }); + + it('should include `fail.pac` in stack traces by option', async () => { + let err: Error | undefined; + const FindProxyForURL = createPacResolver(code, { + filename: 'fail.pac', + }); + try { + await FindProxyForURL('https://example.com/'); + } catch (_err) { + err = _err as Error; + } + assert(err); + expect(err.message).toEqual('fail'); + expect( + err.stack?.indexOf('at FindProxyForURL (fail.pac:') + ).not.toEqual(-1); + }); + }); +}); diff --git a/packages/pac-resolver/test/timeRange.js b/packages/pac-resolver/test/timeRange.js deleted file mode 100644 index 711b4837..00000000 --- a/packages/pac-resolver/test/timeRange.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Module dependencies. - */ - -var assert = require('assert'); -const { timeRange } = require('../').sandbox; -var vanillaGetHours = Date.prototype.getHours; -var vanillaGetMinutes = Date.prototype.getMinutes; -var vanillaGetSeconds = Date.prototype.getSeconds; -var vanillaGetUTCHours = Date.prototype.getUTCHours; -var vanillaGetUTCMinutes = Date.prototype.getUTCMinutes; -var vanillaGetUTCSeconds = Date.prototype.getUTCSeconds; - -describe('hooks', function () { - before(function () { - // Setting local time as 01:24:30 - Date.prototype.getHours = function () { - return 1; - }; - Date.prototype.getMinutes = function () { - return 24; - }; - Date.prototype.getSeconds = function () { - return 30; - }; - - // Setting UTC time as 19:54:30 - Date.prototype.getUTCHours = function () { - return 19; - }; - Date.prototype.getUTCMinutes = function () { - return 54; - }; - Date.prototype.getUTCSeconds = function () { - return 30; - }; - }); - - after(function () { - Date.prototype.getHours = vanillaGetHours; - Date.prototype.getUTCHours = vanillaGetUTCHours; - Date.prototype.getUTCMinutes = vanillaGetUTCMinutes; - Date.prototype.getUTCSeconds = vanillaGetUTCSeconds; - }); - - describe('timeRange()', function () { - var tests = [ - [1, true], - [1, 2, true], - [0, 0, 0, 30, false], - [0, 0, 0, 0, 30, 0, false], - [0, 0, 0, 0, 30, 0, 'GMT', false], - [0, 0, 0, 20, 0, 0, 'GMT', true], - ]; - - tests.forEach(function (test) { - var expected = test.pop(); - it( - 'should return `' + - expected + - '` for "' + - test.join('", "') + - '"', - function () { - assert.equal(expected, timeRange.apply(this, test)); - } - ); - }); - }); -}); diff --git a/packages/pac-resolver/test/timeRange.test.ts b/packages/pac-resolver/test/timeRange.test.ts new file mode 100644 index 00000000..fd316246 --- /dev/null +++ b/packages/pac-resolver/test/timeRange.test.ts @@ -0,0 +1,57 @@ +import timeRange from '../src/timeRange'; + +const vanillaGetHours = Date.prototype.getHours; +const vanillaGetUTCHours = Date.prototype.getUTCHours; +const vanillaGetUTCMinutes = Date.prototype.getUTCMinutes; +const vanillaGetUTCSeconds = Date.prototype.getUTCSeconds; + +describe('hooks', () => { + beforeAll(() => { + // Setting local time as 01:24:30 + Date.prototype.getHours = () => { + return 1; + }; + Date.prototype.getMinutes = () => { + return 24; + }; + Date.prototype.getSeconds = () => { + return 30; + }; + + // Setting UTC time as 19:54:30 + Date.prototype.getUTCHours = () => { + return 19; + }; + Date.prototype.getUTCMinutes = () => { + return 54; + }; + Date.prototype.getUTCSeconds = () => { + return 30; + }; + }); + + afterAll(() => { + Date.prototype.getHours = vanillaGetHours; + Date.prototype.getUTCHours = vanillaGetUTCHours; + Date.prototype.getUTCMinutes = vanillaGetUTCMinutes; + Date.prototype.getUTCSeconds = vanillaGetUTCSeconds; + }); + + describe('timeRange()', () => { + test.each([ + { inputs: [1], expected: true }, + { inputs: [1, 2], expected: true }, + { inputs: [0, 0, 0, 30], expected: false }, + { inputs: [0, 0, 0, 0, 30, 0], expected: false }, + { inputs: [0, 0, 0, 0, 30, 0, 'GMT'], expected: false }, + { inputs: [0, 0, 0, 20, 0, 0, 'GMT'], expected: true }, + ])( + 'should return `$expected` for `$inputs`', + ({ expected, inputs }) => { + // @ts-expect-error TS complains about `.apply()` + // eslint-disable-next-line prefer-spread + expect(timeRange.apply(null, inputs)).toEqual(expected); + } + ); + }); +}); diff --git a/packages/pac-resolver/test/tsconfig.json b/packages/pac-resolver/test/tsconfig.json new file mode 100644 index 00000000..e6f83d5e --- /dev/null +++ b/packages/pac-resolver/test/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["*.ts"] +} diff --git a/packages/proxy-agent/package.json b/packages/proxy-agent/package.json index cfdf4b02..56e852d6 100644 --- a/packages/proxy-agent/package.json +++ b/packages/proxy-agent/package.json @@ -11,8 +11,7 @@ "build": "tsc", "test": "jest --env node --verbose --bail", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "engines": { "node": ">= 14" diff --git a/packages/proxy/package.json b/packages/proxy/package.json index d4f395b8..6897582f 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -11,8 +11,7 @@ "build": "tsc", "test": "jest --env node --verbose --bail", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "keywords": [ "http", @@ -34,7 +33,6 @@ }, "license": "MIT", "dependencies": { - "@tootallnate/once": "^2.0.0", "args": "^5.0.3", "basic-auth-parser": "0.0.2-1", "debug": "^4.3.4" diff --git a/packages/proxy/src/bin/proxy.ts b/packages/proxy/src/bin/proxy.ts index 35912588..141d85bf 100755 --- a/packages/proxy/src/bin/proxy.ts +++ b/packages/proxy/src/bin/proxy.ts @@ -2,7 +2,7 @@ import args from 'args'; import createDebug from 'debug'; import { spawn } from 'child_process'; -import once from '@tootallnate/once'; +import { once } from 'events'; // @ts-expect-error no types for "basic-auth-parser" import basicAuthParser = require('basic-auth-parser'); import { createProxy } from '../proxy'; @@ -83,7 +83,7 @@ if (authenticate) { stdio: ['ignore', 'inherit', 'inherit'], }); - const [code, signal] = await once(child, 'exit'); + const [code, signal]: number[] = await once(child, 'exit'); debug('authentication child process "exit" event: %s %s', code, signal); return code === 0; }; diff --git a/packages/socks-proxy-agent/package.json b/packages/socks-proxy-agent/package.json index 03a0a9a5..4a63ece1 100644 --- a/packages/socks-proxy-agent/package.json +++ b/packages/socks-proxy-agent/package.json @@ -116,13 +116,14 @@ "devDependencies": { "@types/async-retry": "^1.4.5", "@types/debug": "^4.1.7", + "@types/dns2": "^2.0.3", "@types/jest": "^29.5.1", "@types/node": "^14.18.43", + "async-listen": "^2.1.0", "async-retry": "^1.3.3", "cacheable-lookup": "^6.1.0", "dns2": "^2.1.0", "jest": "^29.5.0", - "mocha": "^9.2.2", "proxy": "workspace:*", "socksv5": "github:TooTallNate/socksv5#fix/dstSock-close-event", "ts-jest": "^29.1.0", @@ -134,11 +135,10 @@ }, "scripts": { "build": "tsc", - "test": "mocha --reporter spec test/test.js", + "test": "jest --env node --verbose --bail test/test.ts", "test-e2e": "jest --env node --verbose --bail test/e2e.test.ts", "lint": "eslint . --ext .ts", - "pack": "node ../../scripts/pack.mjs", - "prepublishOnly": "npm run build" + "pack": "node ../../scripts/pack.mjs" }, "license": "MIT" } diff --git a/packages/socks-proxy-agent/src/index.ts b/packages/socks-proxy-agent/src/index.ts index 22b00768..f4de8cbd 100644 --- a/packages/socks-proxy-agent/src/index.ts +++ b/packages/socks-proxy-agent/src/index.ts @@ -86,9 +86,9 @@ export class SocksProxyAgent extends Agent { 'socks5h', ] as const; - private readonly shouldLookup: boolean; - private readonly proxy: SocksProxy; - public timeout: number | null; + readonly shouldLookup: boolean; + readonly proxy: SocksProxy; + timeout: number | null; constructor(uri: string | URL, opts?: SocksProxyAgentOptions) { super(opts); @@ -122,8 +122,7 @@ export class SocksProxyAgent extends Agent { // Client-side DNS resolution for "4" and "5" socks proxy versions. host = await new Promise((resolve, reject) => { // Use the request's custom lookup, if one was configured: - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - lookupFn(host!, {}, (err, res) => { + lookupFn(host, {}, (err, res) => { if (err) { reject(err); } else { diff --git a/packages/socks-proxy-agent/test/test.js b/packages/socks-proxy-agent/test/test.js deleted file mode 100644 index 69e9840d..00000000 --- a/packages/socks-proxy-agent/test/test.js +++ /dev/null @@ -1,278 +0,0 @@ -/* global describe, before, after, it */ - -const socks = require('socksv5'); -const assert = require('assert'); -const https = require('https'); -const http = require('http'); -const url = require('url'); -const path = require('path'); -const fs = require('fs'); - -const dns2 = require('dns2'); -const CacheableLookup = require('cacheable-lookup'); - -const { req, json } = require('agent-base'); -const { SocksProxyAgent } = require('..'); - -describe('SocksProxyAgent', () => { - let httpServer; - let httpPort; - let httpsServer; - let httpsPort; - let socksServer; - let socksPort; - - before(function (done) { - // setup SOCKS proxy server - socksServer = socks.createServer(function (_info, accept) { - accept(); - }); - socksServer.listen(0, '127.0.0.1', () => { - socksPort = socksServer.address().port; - done(); - }); - socksServer.useAuth(socks.auth.None()); - }); - - before(function (done) { - // setup target HTTP server - httpServer = http.createServer(); - httpServer.listen(() => { - httpPort = httpServer.address().port; - done(); - }); - }); - - before(function (done) { - // setup target SSL HTTPS server - const options = { - key: fs.readFileSync( - path.resolve(__dirname, 'ssl-cert-snakeoil.key') - ), - cert: fs.readFileSync( - path.resolve(__dirname, 'ssl-cert-snakeoil.pem') - ), - }; - httpsServer = https.createServer(options); - httpsServer.listen(() => { - httpsPort = httpsServer.address().port; - done(); - }); - }); - - after(function (done) { - socksServer.once('close', () => { - done(); - }); - socksServer.close(); - }); - - after(function (done) { - httpServer.once('close', () => { - done(); - }); - httpServer.close(); - }); - - after(function (done) { - httpsServer.once('close', () => { - done(); - }); - httpsServer.close(); - }); - - describe('constructor', () => { - it('should throw an Error if no "proxy" argument is given', () => { - assert.throws(() => new SocksProxyAgent()); - }); - it('should accept a "string" proxy argument', () => { - const agent = new SocksProxyAgent(`socks://127.0.0.1:${socksPort}`); - assert.equal('127.0.0.1', agent.proxy.host); - assert.equal(socksPort, agent.proxy.port); - }); - it('should accept a `new URL()` result object argument', () => { - const opts = new URL(`socks://127.0.0.1:${socksPort}`); - const agent = new SocksProxyAgent(opts); - assert.equal('127.0.0.1', agent.proxy.host); - assert.equal(socksPort, agent.proxy.port); - }); - it('setup timeout', function (done) { - httpServer.once('request', function (req, res) { - assert.equal('/timeout', req.url); - res.statusCode = 200; - setTimeout(() => res.end('Written after 1000'), 500); - }); - - const agent = new SocksProxyAgent( - `socks://127.0.0.1:${socksPort}`, - { timeout: 50 } - ); - - const opts = { - protocol: 'http:', - host: `127.0.0.1:${httpPort}`, - port: httpPort, - hostname: '127.0.0.1', - path: '/timeout', - agent, - headers: { foo: 'bar' }, - }; - - const req = http.get(opts); - - req.once('error', (err) => { - assert.equal(err.message, 'socket hang up'); - done(); - }); - }); - }); - - describe('"http" module', () => { - it('should work against an HTTP endpoint', async () => { - httpServer.once('request', function (req, res) { - assert.equal('/foo', req.url); - res.statusCode = 404; - res.end(JSON.stringify(req.headers)); - }); - - const res = await req(`http://127.0.0.1:${httpPort}/foo`, { - agent: new SocksProxyAgent(`socks://127.0.0.1:${socksPort}`), - headers: { foo: 'bar' }, - }); - assert.equal(404, res.statusCode); - - const body = await json(res); - assert.equal('bar', body.foo); - }); - }); - - describe('"https" module', () => { - it('should work against an HTTPS endpoint', async () => { - httpsServer.once('request', function (req, res) { - assert.equal('/foo', req.url); - res.statusCode = 404; - res.end(JSON.stringify(req.headers)); - }); - - const agent = new SocksProxyAgent(`socks://127.0.0.1:${socksPort}`); - - const res = await req(`https://127.0.0.1:${httpsPort}/foo`, { - agent, - rejectUnauthorized: false, - headers: { foo: 'bar' }, - }); - assert.equal(404, res.statusCode); - - const body = await json(res); - assert.equal('bar', body.foo); - }); - }); - - describe('Custom lookup option', () => { - let dnsServer; - let dnsQueries; - - before((done) => { - dnsQueries = []; - - // A custom DNS server that always replies with 127.0.0.1: - dnsServer = dns2.createServer({ - udp: true, - handle: (request, send) => { - const response = - dns2.Packet.createResponseFromRequest(request); - const [question] = request.questions; - const { name } = question; - - dnsQueries.push({ - type: question.type, - name: question.name, - }); - - response.answers.push({ - name, - type: dns2.Packet.TYPE.A, - class: dns2.Packet.CLASS.IN, - ttl: 300, - address: '127.0.0.1', - }); - send(response); - }, - }); - dnsServer.listen({ udp: 5333 }); - dnsServer.on('listening', () => done()); - }); - - after(() => { - dnsServer.close(); - }); - - it("should use a requests's custom lookup function with socks5", function (done) { - httpServer.once('request', function (req, res) { - assert.equal('/foo', req.url); - res.statusCode = 404; - res.end(); - }); - - let agent = new SocksProxyAgent(`socks5://127.0.0.1:${socksPort}`); - let opts = url.parse( - `http://non-existent-domain.test:${httpPort}/foo` - ); - opts.agent = agent; - - opts.lookup = (hostname, _opts, callback) => { - if (hostname === 'non-existent-domain.test') - callback(null, '127.0.0.1'); - else callback(new Error('Bad domain')); - }; - - let req = http.get(opts, function (res) { - assert.equal(404, res.statusCode); - done(); - }); - req.once('error', done); - }); - - it('should support caching DNS requests', function (done) { - httpServer.on('request', function (req, res) { - res.statusCode = 200; - res.end(); - }); - - let agent = new SocksProxyAgent(`socks5://127.0.0.1:${socksPort}`); - let opts = url.parse(`http://test-domain.test:${httpPort}/foo`); - opts.agent = agent; - - const cacheableLookup = new CacheableLookup(); - cacheableLookup.servers = ['127.0.0.1:5333']; - opts.lookup = cacheableLookup.lookup; - - // No DNS queries made initially - assert.deepEqual(dnsQueries, []); - - http.get(opts, function (res) { - assert.equal(200, res.statusCode); - - // Initial DNS query for first request - assert.deepEqual(dnsQueries, [ - { name: 'test-domain.test', type: dns2.Packet.TYPE.A }, - { name: 'test-domain.test', type: dns2.Packet.TYPE.AAAA }, - ]); - - http.get(opts, function (res) { - assert.equal(200, res.statusCode); - - // Still the same. No new DNS queries, so the response was cached - assert.deepEqual(dnsQueries, [ - { name: 'test-domain.test', type: dns2.Packet.TYPE.A }, - { - name: 'test-domain.test', - type: dns2.Packet.TYPE.AAAA, - }, - ]); - done(); - }).once('error', done); - }).once('error', done); - }); - }); -}); diff --git a/packages/socks-proxy-agent/test/test.ts b/packages/socks-proxy-agent/test/test.ts new file mode 100644 index 00000000..89783b03 --- /dev/null +++ b/packages/socks-proxy-agent/test/test.ts @@ -0,0 +1,254 @@ +import assert from 'assert'; +import * as https from 'https'; +import * as http from 'http'; +import * as path from 'path'; +import * as fs from 'fs'; +import dns2 from 'dns2'; +// @ts-expect-error no types +import socks from 'socksv5'; +import CacheableLookup from 'cacheable-lookup'; +import { listen } from 'async-listen'; +import { req, json } from 'agent-base'; +import { SocksProxyAgent } from '../src'; +import { once } from 'events'; + +describe('SocksProxyAgent', () => { + let httpServer: http.Server; + let httpServerUrl: URL; + let httpsServer: https.Server; + let httpsServerUrl: URL; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let socksServer: any; + let socksServerUrl: URL; + + beforeAll(async () => { + // setup SOCKS proxy server + // @ts-expect-error no types for `socksv5` + socksServer = socks.createServer(function (_info, accept) { + accept(); + }); + await listen(socksServer); + const port = socksServer.address().port; + socksServerUrl = new URL(`socks://127.0.0.1:${port}`); + socksServer.useAuth(socks.auth.None()); + }); + + beforeAll(async () => { + // setup target HTTP server + httpServer = http.createServer(); + httpServerUrl = (await listen(httpServer)) as URL; + }); + + beforeAll(async () => { + // setup target SSL HTTPS server + const options = { + key: fs.readFileSync( + path.resolve(__dirname, 'ssl-cert-snakeoil.key') + ), + cert: fs.readFileSync( + path.resolve(__dirname, 'ssl-cert-snakeoil.pem') + ), + }; + httpsServer = https.createServer(options); + httpsServerUrl = (await listen(httpsServer)) as URL; + }); + + afterAll(() => { + socksServer.close(); + httpServer.close(); + httpsServer.close(); + }); + + beforeEach(() => { + httpServer.removeAllListeners('request'); + httpsServer.removeAllListeners('request'); + }); + + describe('constructor', () => { + it('should accept a "string" proxy argument', () => { + const agent = new SocksProxyAgent(socksServerUrl.href); + assert.equal(socksServerUrl.hostname, agent.proxy.host); + assert.equal(+socksServerUrl.port, agent.proxy.port); + }); + it('should accept a `new URL()` result object argument', () => { + const agent = new SocksProxyAgent(socksServerUrl); + assert.equal(socksServerUrl.hostname, agent.proxy.host); + assert.equal(+socksServerUrl.port, agent.proxy.port); + }); + it('should respect `timeout` option during connection to socks server', async () => { + const agent = new SocksProxyAgent(socksServerUrl, { timeout: 1 }); + + let err: Error | undefined; + try { + await req('http://example.com', { agent }); + } catch (_err) { + err = _err as Error; + } + assert(err); + assert.equal(err.message, 'Proxy connection timed out'); + }); + }); + + describe('"http" module', () => { + it('should work against an HTTP endpoint', async () => { + httpServer.once('request', function (req, res) { + assert.equal('/foo', req.url); + res.statusCode = 404; + res.end(JSON.stringify(req.headers)); + }); + + const res = await req(new URL('/foo', httpServerUrl), { + agent: new SocksProxyAgent(socksServerUrl), + headers: { foo: 'bar' }, + }); + assert.equal(404, res.statusCode); + + const body = await json(res); + assert.equal('bar', body.foo); + }); + }); + + describe('"https" module', () => { + it('should work against an HTTPS endpoint', async () => { + httpsServer.once('request', function (req, res) { + assert.equal('/foo', req.url); + res.statusCode = 404; + res.end(JSON.stringify(req.headers)); + }); + + const agent = new SocksProxyAgent(socksServerUrl); + + const res = await req( + `https://127.0.0.1:${httpsServerUrl.port}/foo`, + { + agent, + rejectUnauthorized: false, + headers: { foo: 'bar' }, + } + ); + assert.equal(404, res.statusCode); + + const body = await json(res); + assert.equal('bar', body.foo); + }); + }); + + describe('Custom lookup option', () => { + let dnsServer: ReturnType; + let dnsQueries: { type: string; name: string }[]; + + beforeAll(async () => { + dnsQueries = []; + + // A custom DNS server that always replies with 127.0.0.1: + dnsServer = dns2.createServer({ + udp: true, + handle: (request, send) => { + const response = + dns2.Packet.createResponseFromRequest(request); + const [question] = request.questions; + const { name } = question; + + dnsQueries.push({ + // @ts-expect-error meh + type: question.type, + name: question.name, + }); + + response.answers.push({ + name, + type: dns2.Packet.TYPE.A, + class: dns2.Packet.CLASS.IN, + ttl: 300, + address: '127.0.0.1', + }); + send(response); + }, + }); + dnsServer.listen({ udp: 5333 }); + await once(dnsServer, 'listening'); + }); + + afterAll(() => { + dnsServer.close(); + }); + + it("should use a requests's custom lookup function with socks5", async () => { + httpServer.once('request', function (req, res) { + assert.equal('/foo', req.url); + res.statusCode = 404; + res.end(); + }); + + const agent = new SocksProxyAgent( + socksServerUrl.href.replace('socks', 'socks5') + ); + + try { + await req( + `http://non-existent-domain.test:${httpServerUrl.port}/foo`, + { + agent, + lookup(hostname, _opts, callback) { + if (hostname === 'non-existent-domain.test') { + // @ts-expect-error meh + callback(null, '127.0.0.1'); + return; + } + // @ts-expect-error meh + callback(new Error('Bad domain')); + }, + } + ); + } catch (err) { + console.log(err); + } + }); + + it('should support caching DNS requests', async () => { + httpServer.on('request', function (req, res) { + res.statusCode = 200; + res.end(); + }); + + const agent = new SocksProxyAgent( + socksServerUrl.href.replace('socks', 'socks5') + ); + + const cacheableLookup = new CacheableLookup(); + cacheableLookup.servers = ['127.0.0.1:5333']; + + // No DNS queries made initially + assert.deepEqual(dnsQueries, []); + + const res = await req( + `http://test-domain.test:${httpServerUrl.port}/foo`, + // @ts-expect-error meh + { agent, lookup: cacheableLookup.lookup } + ); + assert.equal(200, res.statusCode); + + // Initial DNS query for first request + assert.deepEqual(dnsQueries, [ + { name: 'test-domain.test', type: dns2.Packet.TYPE.A }, + { name: 'test-domain.test', type: dns2.Packet.TYPE.AAAA }, + ]); + + const res2 = await req( + `http://test-domain.test:${httpServerUrl.port}/foo`, + // @ts-expect-error meh + { agent, lookup: cacheableLookup.lookup } + ); + assert.equal(200, res2.statusCode); + + // Still the same. No new DNS queries, so the response was cached + assert.deepEqual(dnsQueries, [ + { name: 'test-domain.test', type: dns2.Packet.TYPE.A }, + { + name: 'test-domain.test', + type: dns2.Packet.TYPE.AAAA, + }, + ]); + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 075e4350..7a5d6584 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,9 +132,6 @@ importers: packages/get-uri: dependencies: - '@tootallnate/once': - specifier: ^2.0.0 - version: 2.0.0 basic-ftp: specifier: ^5.0.2 version: 5.0.2 @@ -187,9 +184,6 @@ importers: packages/http-proxy-agent: dependencies: - '@tootallnate/once': - specifier: ^2.0.0 - version: 2.0.0 agent-base: specifier: ^6.0.2 version: link:../agent-base @@ -200,15 +194,24 @@ importers: '@types/debug': specifier: ^4.1.7 version: 4.1.7 + '@types/jest': + specifier: ^29.5.1 + version: 29.5.1 '@types/node': specifier: ^14.18.43 version: 14.18.43 - mocha: - specifier: ^6.2.3 - version: 6.2.3 + async-listen: + specifier: ^2.1.0 + version: 2.1.0 + jest: + specifier: ^29.5.0 + version: 29.5.0(@types/node@14.18.43) proxy: specifier: workspace:* version: link:../proxy + ts-jest: + specifier: ^29.1.0 + version: 29.1.0(@babel/core@7.21.4)(jest@29.5.0)(typescript@5.0.4) tsconfig: specifier: workspace:* version: link:../tsconfig @@ -261,9 +264,6 @@ importers: packages/pac-proxy-agent: dependencies: - '@tootallnate/once': - specifier: ^2.0.0 - version: 2.0.0 agent-base: specifier: ^6.0.2 version: link:../agent-base @@ -289,18 +289,27 @@ importers: '@types/debug': specifier: ^4.1.7 version: 4.1.7 + '@types/jest': + specifier: ^29.5.1 + version: 29.5.1 '@types/node': specifier: ^14.18.43 version: 14.18.43 - mocha: - specifier: ^6.2.3 - version: 6.2.3 + async-listen: + specifier: ^2.1.0 + version: 2.1.0 + jest: + specifier: ^29.5.0 + version: 29.5.0(@types/node@14.18.43) proxy: specifier: workspace:* version: link:../proxy socksv5: specifier: 0.0.6 version: 0.0.6 + ts-jest: + specifier: ^29.1.0 + version: 29.1.0(@babel/core@7.21.4)(jest@29.5.0)(typescript@5.0.4) tsconfig: specifier: workspace:* version: link:../tsconfig @@ -323,15 +332,21 @@ importers: '@types/ip': specifier: ^1.1.0 version: 1.1.0 + '@types/jest': + specifier: ^29.5.1 + version: 29.5.1 '@types/netmask': specifier: ^1.0.30 version: 1.0.30 '@types/node': specifier: ^14.18.43 version: 14.18.43 - mocha: - specifier: ^9.2.1 - version: 9.2.1 + jest: + specifier: ^29.5.0 + version: 29.5.0(@types/node@14.18.43) + ts-jest: + specifier: ^29.1.0 + version: 29.1.0(@babel/core@7.21.4)(jest@29.5.0)(typescript@5.0.4) tsconfig: specifier: workspace:* version: link:../tsconfig @@ -341,9 +356,6 @@ importers: packages/proxy: dependencies: - '@tootallnate/once': - specifier: ^2.0.0 - version: 2.0.0 args: specifier: ^5.0.3 version: 5.0.3 @@ -464,12 +476,18 @@ importers: '@types/debug': specifier: ^4.1.7 version: 4.1.7 + '@types/dns2': + specifier: ^2.0.3 + version: 2.0.3 '@types/jest': specifier: ^29.5.1 version: 29.5.1 '@types/node': specifier: ^14.18.43 version: 14.18.43 + async-listen: + specifier: ^2.1.0 + version: 2.1.0 async-retry: specifier: ^1.3.3 version: 1.3.3 @@ -482,9 +500,6 @@ importers: jest: specifier: ^29.5.0 version: 29.5.0(@types/node@14.18.43) - mocha: - specifier: ^9.2.2 - version: 9.2.2 proxy: specifier: workspace:* version: link:../proxy @@ -1417,11 +1432,6 @@ packages: '@sinonjs/commons': 2.0.0 dev: true - /@tootallnate/once@2.0.0: - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - dev: false - /@types/agent-base@4.2.0: resolution: {integrity: sha512-8mrhPstU+ZX0Ugya8tl5DsDZ1I5ZwQzbL/8PA0z8Gj0k9nql7nkaMzmPVLj+l/nixWaliXi+EBiLA8bptw3z7Q==} dependencies: @@ -1478,6 +1488,12 @@ packages: '@types/ms': 0.7.31 dev: true + /@types/dns2@2.0.3: + resolution: {integrity: sha512-sO14jUYelc2DzwHcCbwp7tZsZfB2x17/zIdHCAeUBINAz2cc36iVFLqCPCB7rn73CzoyoCmpkEnh1rA8C0puPw==} + dependencies: + '@types/node': 14.18.43 + dev: true + /@types/escodegen@0.0.6: resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==} dev: true @@ -1756,10 +1772,6 @@ packages: eslint-visitor-keys: 3.4.0 dev: true - /@ungap/promise-all-settled@1.1.2: - resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} - dev: true - /acorn-jsx@5.3.2(acorn@7.4.1): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1803,16 +1815,6 @@ packages: uri-js: 4.4.1 dev: true - /ansi-colors@3.2.3: - resolution: {integrity: sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==} - engines: {node: '>=6'} - dev: true - - /ansi-colors@4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} - engines: {node: '>=6'} - dev: true - /ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -1825,16 +1827,6 @@ packages: type-fest: 0.21.3 dev: true - /ansi-regex@3.0.1: - resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} - engines: {node: '>=4'} - dev: true - - /ansi-regex@4.1.1: - resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} - engines: {node: '>=6'} - dev: true - /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1872,10 +1864,6 @@ packages: sprintf-js: 1.0.3 dev: true - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - /args@5.0.3: resolution: {integrity: sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA==} engines: {node: '>= 6.0.0'} @@ -1908,17 +1896,6 @@ packages: es-shim-unscopables: 1.0.0 dev: true - /array.prototype.reduce@1.0.5: - resolution: {integrity: sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - es-array-method-boxes-properly: 1.0.0 - is-string: 1.0.7 - dev: true - /arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -2059,11 +2036,6 @@ packages: is-windows: 1.0.2 dev: true - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} - dev: true - /bl@1.2.3: resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} dependencies: @@ -2097,10 +2069,6 @@ packages: wcwidth: 1.0.1 dev: true - /browser-stdout@1.3.1: - resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - dev: true - /browserslist@4.21.5: resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -2199,21 +2167,6 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true - /ci-info@3.8.0: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} @@ -2239,14 +2192,6 @@ packages: winston: 0.8.3 dev: true - /cliui@5.0.0: - resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==} - dependencies: - string-width: 3.1.0 - strip-ansi: 5.2.0 - wrap-ansi: 5.1.0 - dev: true - /cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: @@ -2379,32 +2324,6 @@ packages: resolution: {integrity: sha512-rzAoghPcnojOgYhvqb9ZYR5Ws9hDM2SGB6xLSUX9pEAKYnbxyexHZypDLgx3+SF3SR+at6waVRCxKDm6q2Z6+g==} dev: true - /debug@3.2.6(supports-color@6.0.0): - resolution: {integrity: sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==} - deprecated: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - supports-color: 6.0.0 - dev: true - - /debug@4.3.3(supports-color@8.1.1): - resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - supports-color: 8.1.1 - dev: true - /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -2429,11 +2348,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /decamelize@4.0.0: - resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} - engines: {node: '>=10'} - dev: true - /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true @@ -2480,16 +2394,6 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /diff@3.5.0: - resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} - engines: {node: '>=0.3.1'} - dev: true - - /diff@5.0.0: - resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} - engines: {node: '>=0.3.1'} - dev: true - /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2517,10 +2421,6 @@ packages: engines: {node: '>=12'} dev: true - /emoji-regex@7.0.3: - resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} - dev: true - /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true @@ -2578,10 +2478,6 @@ packages: which-typed-array: 1.1.9 dev: true - /es-array-method-boxes-properly@1.0.0: - resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} - dev: true - /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} engines: {node: '>= 0.4'} @@ -2885,13 +2781,6 @@ packages: to-regex-range: 5.0.1 dev: true - /find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - dependencies: - locate-path: 3.0.0 - dev: true - /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2923,18 +2812,6 @@ packages: rimraf: 3.0.2 dev: true - /flat@4.1.1: - resolution: {integrity: sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==} - hasBin: true - dependencies: - is-buffer: 2.0.5 - dev: true - - /flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - dev: true - /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true @@ -3059,28 +2936,6 @@ packages: path-scurry: 1.7.0 dev: true - /glob@7.1.3: - resolution: {integrity: sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /glob@7.2.0: - resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -3142,11 +2997,6 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /growl@1.10.5: - resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} - engines: {node: '>=4.x'} - dev: true - /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -3195,11 +3045,6 @@ packages: function-bind: 1.1.1 dev: true - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - dev: true - /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true @@ -3321,13 +3166,6 @@ packages: has-bigints: 1.0.2 dev: true - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: true - /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} @@ -3336,11 +3174,6 @@ packages: has-tostringtag: 1.0.0 dev: true - /is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - dev: true - /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -3371,11 +3204,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-fullwidth-code-point@2.0.0: - resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} - engines: {node: '>=4'} - dev: true - /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -3415,11 +3243,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} - engines: {node: '>=8'} - dev: true - /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -3471,11 +3294,6 @@ packages: has-tostringtag: 1.0.0 dev: true - /is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - dev: true - /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: @@ -3491,10 +3309,6 @@ packages: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true - /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -3986,14 +3800,6 @@ packages: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true - /js-yaml@3.13.1: - resolution: {integrity: sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==} - hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - dev: true - /js-yaml@3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true @@ -4002,13 +3808,6 @@ packages: esprima: 4.0.1 dev: true - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - /jsesc@2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} @@ -4097,14 +3896,6 @@ packages: strip-bom: 3.0.0 dev: true - /locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - dev: true - /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -4135,25 +3926,6 @@ packages: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} dev: true - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true - - /log-symbols@2.2.0: - resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} - engines: {node: '>=4'} - dependencies: - chalk: 2.4.2 - dev: true - - /log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - dev: true - /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: @@ -4260,25 +4032,12 @@ packages: engines: {node: '>=4'} dev: true - /minimatch@3.0.4: - resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} - dependencies: - brace-expansion: 1.1.11 - dev: true - /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 dev: true - /minimatch@4.2.1: - resolution: {integrity: sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==} - engines: {node: '>=10'} - dependencies: - brace-expansion: 1.1.11 - dev: true - /minimatch@9.0.0: resolution: {integrity: sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==} engines: {node: '>=16 || 14 >=14.17'} @@ -4295,10 +4054,6 @@ packages: kind-of: 6.0.3 dev: true - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true - /minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} @@ -4309,134 +4064,14 @@ packages: engines: {node: '>= 8.0.0'} dev: true - /mkdirp@0.5.4: - resolution: {integrity: sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==} - deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.) - hasBin: true - dependencies: - minimist: 1.2.8 - dev: true - - /mocha@6.2.3: - resolution: {integrity: sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==} - engines: {node: '>= 6.0.0'} - hasBin: true - dependencies: - ansi-colors: 3.2.3 - browser-stdout: 1.3.1 - debug: 3.2.6(supports-color@6.0.0) - diff: 3.5.0 - escape-string-regexp: 1.0.5 - find-up: 3.0.0 - glob: 7.1.3 - growl: 1.10.5 - he: 1.2.0 - js-yaml: 3.13.1 - log-symbols: 2.2.0 - minimatch: 3.0.4 - mkdirp: 0.5.4 - ms: 2.1.1 - node-environment-flags: 1.0.5 - object.assign: 4.1.0 - strip-json-comments: 2.0.1 - supports-color: 6.0.0 - which: 1.3.1 - wide-align: 1.1.3 - yargs: 13.3.2 - yargs-parser: 13.1.2 - yargs-unparser: 1.6.0 - dev: true - - /mocha@9.2.1: - resolution: {integrity: sha512-T7uscqjJVS46Pq1XDXyo9Uvey9gd3huT/DD9cYBb4K2Xc/vbKRPUWK067bxDQRK0yIz6Jxk73IrnimvASzBNAQ==} - engines: {node: '>= 12.0.0'} - hasBin: true - dependencies: - '@ungap/promise-all-settled': 1.1.2 - ansi-colors: 4.1.1 - browser-stdout: 1.3.1 - chokidar: 3.5.3 - debug: 4.3.3(supports-color@8.1.1) - diff: 5.0.0 - escape-string-regexp: 4.0.0 - find-up: 5.0.0 - glob: 7.2.0 - growl: 1.10.5 - he: 1.2.0 - js-yaml: 4.1.0 - log-symbols: 4.1.0 - minimatch: 3.0.4 - ms: 2.1.3 - nanoid: 3.2.0 - serialize-javascript: 6.0.0 - strip-json-comments: 3.1.1 - supports-color: 8.1.1 - which: 2.0.2 - workerpool: 6.2.0 - yargs: 16.2.0 - yargs-parser: 20.2.4 - yargs-unparser: 2.0.0 - dev: true - - /mocha@9.2.2: - resolution: {integrity: sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==} - engines: {node: '>= 12.0.0'} - hasBin: true - dependencies: - '@ungap/promise-all-settled': 1.1.2 - ansi-colors: 4.1.1 - browser-stdout: 1.3.1 - chokidar: 3.5.3 - debug: 4.3.3(supports-color@8.1.1) - diff: 5.0.0 - escape-string-regexp: 4.0.0 - find-up: 5.0.0 - glob: 7.2.0 - growl: 1.10.5 - he: 1.2.0 - js-yaml: 4.1.0 - log-symbols: 4.1.0 - minimatch: 4.2.1 - ms: 2.1.3 - nanoid: 3.3.1 - serialize-javascript: 6.0.0 - strip-json-comments: 3.1.1 - supports-color: 8.1.1 - which: 2.0.2 - workerpool: 6.2.0 - yargs: 16.2.0 - yargs-parser: 20.2.4 - yargs-unparser: 2.0.0 - dev: true - /mri@1.1.4: resolution: {integrity: sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==} engines: {node: '>=4'} dev: false - /ms@2.1.1: - resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==} - dev: true - /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true - - /nanoid@3.2.0: - resolution: {integrity: sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /nanoid@3.3.1: - resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -4455,13 +4090,6 @@ packages: engines: {node: '>= 0.4.0'} dev: false - /node-environment-flags@1.0.5: - resolution: {integrity: sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==} - dependencies: - object.getownpropertydescriptors: 2.1.6 - semver: 5.7.1 - dev: true - /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true @@ -4500,16 +4128,6 @@ packages: engines: {node: '>= 0.4'} dev: true - /object.assign@4.1.0: - resolution: {integrity: sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.0 - function-bind: 1.1.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: true - /object.assign@4.1.4: resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} engines: {node: '>= 0.4'} @@ -4520,17 +4138,6 @@ packages: object-keys: 1.1.1 dev: true - /object.getownpropertydescriptors@2.1.6: - resolution: {integrity: sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==} - engines: {node: '>= 0.8'} - dependencies: - array.prototype.reduce: 1.0.5 - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - safe-array-concat: 1.0.0 - dev: true - /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -4598,13 +4205,6 @@ packages: yocto-queue: 0.1.0 dev: true - /p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - dependencies: - p-limit: 2.3.0 - dev: true - /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -4646,11 +4246,6 @@ packages: lines-and-columns: 1.2.4 dev: true - /path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - dev: true - /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4801,12 +4396,6 @@ packages: engines: {node: '>=8'} dev: true - /randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - dependencies: - safe-buffer: 5.2.1 - dev: true - /react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} dev: true @@ -4856,13 +4445,6 @@ packages: util-deprecate: 1.0.2 dev: true - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - dev: true - /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -4957,16 +4539,6 @@ packages: queue-microtask: 1.2.3 dev: true - /safe-array-concat@1.0.0: - resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} - engines: {node: '>=0.4'} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.0 - has-symbols: 1.0.3 - isarray: 2.0.5 - dev: true - /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} dev: true @@ -5013,12 +4585,6 @@ packages: lru-cache: 6.0.0 dev: true - /serialize-javascript@6.0.0: - resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} - dependencies: - randombytes: 2.1.0 - dev: true - /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true @@ -5210,23 +4776,6 @@ packages: strip-ansi: 6.0.1 dev: true - /string-width@2.1.1: - resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} - engines: {node: '>=4'} - dependencies: - is-fullwidth-code-point: 2.0.0 - strip-ansi: 4.0.0 - dev: true - - /string-width@3.1.0: - resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==} - engines: {node: '>=6'} - dependencies: - emoji-regex: 7.0.3 - is-fullwidth-code-point: 2.0.0 - strip-ansi: 5.2.0 - dev: true - /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -5267,20 +4816,6 @@ packages: safe-buffer: 5.1.2 dev: true - /strip-ansi@4.0.0: - resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} - engines: {node: '>=4'} - dependencies: - ansi-regex: 3.0.1 - dev: true - - /strip-ansi@5.2.0: - resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} - engines: {node: '>=6'} - dependencies: - ansi-regex: 4.1.1 - dev: true - /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -5310,11 +4845,6 @@ packages: min-indent: 1.0.1 dev: true - /strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - dev: true - /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -5326,13 +4856,6 @@ packages: dependencies: has-flag: 3.0.0 - /supports-color@6.0.0: - resolution: {integrity: sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==} - engines: {node: '>=6'} - dependencies: - has-flag: 3.0.0 - dev: true - /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5722,12 +5245,6 @@ packages: isexe: 2.0.0 dev: true - /wide-align@1.1.3: - resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==} - dependencies: - string-width: 2.1.1 - dev: true - /winston@0.8.3: resolution: {integrity: sha512-fPoamsHq8leJ62D1M9V/f15mjQ1UHe4+7j1wpAT3fqgA5JqhJkk4aIfPEjfMTI9x6ZTjaLOpMAjluLtmgO5b6g==} engines: {node: '>= 0.6.0'} @@ -5745,19 +5262,6 @@ packages: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} - /workerpool@6.2.0: - resolution: {integrity: sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==} - dev: true - - /wrap-ansi@5.1.0: - resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==} - engines: {node: '>=6'} - dependencies: - ansi-styles: 3.2.1 - string-width: 3.1.0 - strip-ansi: 5.2.0 - dev: true - /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -5825,13 +5329,6 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true - /yargs-parser@13.1.2: - resolution: {integrity: sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==} - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - dev: true - /yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -5840,55 +5337,11 @@ packages: decamelize: 1.2.0 dev: true - /yargs-parser@20.2.4: - resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} - engines: {node: '>=10'} - dev: true - - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true - /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} dev: true - /yargs-unparser@1.6.0: - resolution: {integrity: sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==} - engines: {node: '>=6'} - dependencies: - flat: 4.1.1 - lodash: 4.17.21 - yargs: 13.3.2 - dev: true - - /yargs-unparser@2.0.0: - resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} - engines: {node: '>=10'} - dependencies: - camelcase: 6.3.0 - decamelize: 4.0.0 - flat: 5.0.2 - is-plain-obj: 2.1.0 - dev: true - - /yargs@13.3.2: - resolution: {integrity: sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==} - dependencies: - cliui: 5.0.0 - find-up: 3.0.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 3.1.0 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 13.1.2 - dev: true - /yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} @@ -5906,19 +5359,6 @@ packages: yargs-parser: 18.1.3 dev: true - /yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - dependencies: - cliui: 7.0.4 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.9 - dev: true - /yargs@17.7.1: resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==} engines: {node: '>=12'}