diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 229cd6ae..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": [ - "@apify/ts" - ], - "parserOptions": { - "project": "tsconfig.eslint.json" - }, - "overrides": [ - { - "files": [ - "test/**/*.js" - ], - "env": { - "jest": true - } - } - ] - } diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 71172878..854289cc 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -14,8 +14,8 @@ jobs: strategy: matrix: - os: [ubuntu-latest] # add windows-latest later - node-version: [14, 16] + os: [ubuntu-22.04] # add windows-latest later + node-version: [14, 16, 18] steps: - @@ -27,19 +27,19 @@ jobs: node-version: ${{ matrix.node-version }} - name: Cache Node Modules - if: ${{ matrix.node-version == 16 }} + if: ${{ matrix.node-version == 18 }} uses: actions/cache@v2 with: path: | node_modules build - key: cache-${{ github.run_id }}-v16 + key: cache-${{ github.run_id }}-v18 - name: Install Dependencies run: npm install - name: Add localhost-test to Linux hosts file - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: sudo echo "127.0.0.1 localhost-test" | sudo tee -a /etc/hosts # - # name: Add localhost-test to Windows hosts file @@ -52,16 +52,16 @@ jobs: lint: name: Lint needs: [build_and_test] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - name: Use Node.js 16 + name: Use Node.js 18 uses: actions/setup-node@v1 with: - node-version: 16 + node-version: 18 - name: Load Cache uses: actions/cache@v2 @@ -69,6 +69,6 @@ jobs: path: | node_modules build - key: cache-${{ github.run_id }}-v16 + key: cache-${{ github.run_id }}-v18 - run: npm run lint diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9e511a89..7c873eae 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,8 +18,8 @@ jobs: strategy: matrix: - os: [ubuntu-latest] # add windows-latest later - node-version: [14, 16] + os: [ubuntu-22.04] # add windows-latest later + node-version: [14, 16, 18] steps: - @@ -31,18 +31,18 @@ jobs: node-version: ${{ matrix.node-version }} - name: Cache Node Modules - if: ${{ matrix.node-version == 16 }} + if: ${{ matrix.node-version == 18 }} uses: actions/cache@v2 with: path: | node_modules build - key: cache-${{ github.run_id }}-v16 + key: cache-${{ github.run_id }}-v18 - name: Install Dependencies run: npm install - name: Add localhost-test to Linux hosts file - if: ${{ matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.os == 'ubuntu-22.04' }} run: sudo echo "127.0.0.1 localhost-test" | sudo tee -a /etc/hosts # - # name: Add localhost-test to Windows hosts file @@ -55,16 +55,16 @@ jobs: lint: name: Lint needs: [build_and_test] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - - name: Use Node.js 16 + name: Use Node.js 18 uses: actions/setup-node@v1 with: - node-version: 16 + node-version: 18 - name: Load Cache uses: actions/cache@v2 @@ -72,7 +72,7 @@ jobs: path: | node_modules build - key: cache-${{ github.run_id }}-v16 + key: cache-${{ github.run_id }}-v18 - run: npm run lint @@ -82,14 +82,14 @@ jobs: deploy: name: Publish to NPM needs: [lint] - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 16 + node-version: 18 registry-url: https://registry.npmjs.org/ - name: Load Cache @@ -98,7 +98,7 @@ jobs: path: | node_modules build - key: cache-${{ github.run_id }}-v16 + key: cache-${{ github.run_id }}-v18 - # Determine if this is a beta or latest release name: Set Release Tag diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..756ebcfa --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,20 @@ +import apify from '@apify/eslint-config'; + +// eslint-disable-next-line import/no-default-export +export default [ + { ignores: ['**/dist'] }, // Ignores need to happen first + ...apify, + { + languageOptions: { + sourceType: 'module', + + parserOptions: { + project: 'tsconfig.eslint.json', + }, + }, + rules: { + 'no-param-reassign': 'off', + 'import/extensions': 'off', + }, + }, +]; diff --git a/package.json b/package.json index 36ba9664..dbab3f1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxy-chain", - "version": "2.5.6", + "version": "2.5.7", "description": "Node.js implementation of a proxy server (think Squid) with support for SSL, authentication, upstream proxy chaining, and protocol tunneling.", "main": "dist/index.js", "keywords": [ @@ -38,24 +38,22 @@ "local-proxy": "node ./dist/run_locally.js", "test": "nyc cross-env NODE_OPTIONS=--insecure-http-parser mocha --bail", "lint": "eslint src", - "lint-fix": "eslint src --fix" + "lint:fix": "eslint src --fix" }, "engines": { "node": ">=14" }, "devDependencies": { - "@apify/eslint-config-ts": "^0.2.3", + "@apify/eslint-config": "^0.5.0-beta.2", "@apify/tsconfig": "^0.1.0", "@types/jest": "^28.1.2", "@types/node": "^18.8.3", - "@typescript-eslint/eslint-plugin": "5.29.0", - "@typescript-eslint/parser": "5.29.0", "basic-auth": "^2.0.1", "basic-auth-parser": "^0.0.2", "body-parser": "^1.19.0", "chai": "^4.3.4", "cross-env": "^7.0.3", - "eslint": "^8.10.0", + "eslint": "^9.18.0", "express": "^4.17.1", "faye-websocket": "^0.11.4", "got-scraping": "^3.2.4-beta.0", @@ -73,6 +71,7 @@ "through": "^2.3.8", "ts-node": "^10.2.1", "typescript": "^4.4.3", + "typescript-eslint": "^8.20.0", "underscore": "^1.13.1", "ws": "^8.2.2" }, diff --git a/src/anonymize_proxy.ts b/src/anonymize_proxy.ts index 0f5deff1..d862c578 100644 --- a/src/anonymize_proxy.ts +++ b/src/anonymize_proxy.ts @@ -1,7 +1,8 @@ -import net from 'net'; -import http from 'http'; -import { Buffer } from 'buffer'; +import type { Buffer } from 'buffer'; +import type http from 'http'; +import type net from 'net'; import { URL } from 'url'; + import { Server, SOCKS_PROTOCOLS } from './server'; import { nodeify } from './utils/nodeify'; @@ -17,7 +18,7 @@ export interface AnonymizeProxyOptions { * Parses and validates a HTTP proxy URL. If the proxy requires authentication, then the function * starts an open local proxy server that forwards to the upstream proxy. */ -export const anonymizeProxy = ( +export const anonymizeProxy = async ( options: string | AnonymizeProxyOptions, callback?: (error: Error | null) => void, ): Promise => { @@ -39,7 +40,6 @@ export const anonymizeProxy = ( const parsedProxyUrl = new URL(proxyUrl); if (!['http:', ...SOCKS_PROTOCOLS].includes(parsedProxyUrl.protocol)) { - // eslint-disable-next-line max-len throw new Error(`Invalid "proxyUrl" provided: URL must have one of the following protocols: "http", ${SOCKS_PROTOCOLS.map((p) => `"${p.replace(':', '')}"`).join(', ')} (was "${parsedProxyUrl}")`); } @@ -50,8 +50,8 @@ export const anonymizeProxy = ( let server: Server & { port: number }; - const startServer = () => { - return Promise.resolve().then(() => { + const startServer = async () => { + return Promise.resolve().then(async () => { server = new Server({ // verbose: true, port, @@ -83,7 +83,7 @@ export const anonymizeProxy = ( * and its result if `false`. Otherwise the result is `true`. * @param closeConnections If true, pending proxy connections are forcibly closed. */ -export const closeAnonymizedProxy = ( +export const closeAnonymizedProxy = async ( anonymizedProxyUrl: string, closeConnections: boolean, callback?: (error: Error | null, result?: boolean) => void, diff --git a/src/chain.ts b/src/chain.ts index 73c661ef..c3ed6cb5 100644 --- a/src/chain.ts +++ b/src/chain.ts @@ -1,13 +1,14 @@ +import type { Buffer } from 'buffer'; +import type dns from 'dns'; +import type { EventEmitter } from 'events'; import http from 'http'; import https from 'https'; -import dns from 'dns'; -import { URL } from 'url'; -import { EventEmitter } from 'events'; -import { Buffer } from 'buffer'; +import type { URL } from 'url'; + +import type { Socket } from './socket'; +import { badGatewayStatusCodes, createCustomStatusHttpResponse, errorCodeToStatusCode } from './statuses'; import { countTargetBytes } from './utils/count_target_bytes'; import { getBasicAuthorizationHeader } from './utils/get_basic'; -import { Socket } from './socket'; -import { badGatewayStatusCodes, createCustomStatusHttpResponse, errorCodeToStatusCode } from './statuses'; interface Options { method: string; diff --git a/src/chain_socks.ts b/src/chain_socks.ts index 2fe8a528..3f47f74f 100644 --- a/src/chain_socks.ts +++ b/src/chain_socks.ts @@ -1,12 +1,14 @@ -import http from 'http'; -import net from 'net'; -import { Buffer } from 'buffer'; +import type { Buffer } from 'buffer'; +import type { EventEmitter } from 'events'; +import type http from 'http'; +import type net from 'net'; import { URL } from 'url'; -import { EventEmitter } from 'events'; -import { SocksClient, SocksClientError, type SocksProxy } from 'socks'; -import { countTargetBytes } from './utils/count_target_bytes'; -import { Socket } from './socket'; + +import { type SocksClientError, SocksClient, type SocksProxy } from 'socks'; + +import type { Socket } from './socket'; import { createCustomStatusHttpResponse, socksErrorMessageToStatusCode } from './statuses'; +import { countTargetBytes } from './utils/count_target_bytes'; export interface HandlerOpts { upstreamProxyUrlParsed: URL; diff --git a/src/custom_connect.ts b/src/custom_connect.ts index 7f2e3387..3b3ad2dd 100644 --- a/src/custom_connect.ts +++ b/src/custom_connect.ts @@ -1,5 +1,5 @@ -import net from 'net'; import type http from 'http'; +import type net from 'net'; import { promisify } from 'util'; export const customConnect = async (socket: net.Socket, server: http.Server): Promise => { diff --git a/src/custom_response.ts b/src/custom_response.ts index 149579f1..5c3c0c97 100644 --- a/src/custom_response.ts +++ b/src/custom_response.ts @@ -1,5 +1,5 @@ -import type http from 'http'; import type { Buffer } from 'buffer'; +import type http from 'http'; export interface CustomResponse { statusCode?: number; diff --git a/src/direct.ts b/src/direct.ts index c1c867c6..2c42f231 100644 --- a/src/direct.ts +++ b/src/direct.ts @@ -1,10 +1,11 @@ +import type { Buffer } from 'buffer'; +import type dns from 'dns'; +import type { EventEmitter } from 'events'; import net from 'net'; -import dns from 'dns'; -import { Buffer } from 'buffer'; import { URL } from 'url'; -import { EventEmitter } from 'events'; + +import type { Socket } from './socket'; import { countTargetBytes } from './utils/count_target_bytes'; -import { Socket } from './socket'; export interface HandlerOpts { localAddress?: string; diff --git a/src/forward.ts b/src/forward.ts index 83f481fe..f49a56ef 100644 --- a/src/forward.ts +++ b/src/forward.ts @@ -1,13 +1,14 @@ -import dns from 'dns'; +import type dns from 'dns'; import http from 'http'; import https from 'https'; import stream from 'stream'; +import type { URL } from 'url'; import util from 'util'; -import { URL } from 'url'; -import { validHeadersOnly } from './utils/valid_headers_only'; -import { getBasicAuthorizationHeader } from './utils/get_basic'; -import { countTargetBytes } from './utils/count_target_bytes'; + import { badGatewayStatusCodes, errorCodeToStatusCode } from './statuses'; +import { countTargetBytes } from './utils/count_target_bytes'; +import { getBasicAuthorizationHeader } from './utils/get_basic'; +import { validHeadersOnly } from './utils/valid_headers_only'; const pipeline = util.promisify(stream.pipeline); diff --git a/src/forward_socks.ts b/src/forward_socks.ts index 66d6ab1f..75672941 100644 --- a/src/forward_socks.ts +++ b/src/forward_socks.ts @@ -1,11 +1,13 @@ import http from 'http'; import stream from 'stream'; +import type { URL } from 'url'; import util from 'util'; -import { URL } from 'url'; + import { SocksProxyAgent } from 'socks-proxy-agent'; -import { validHeadersOnly } from './utils/valid_headers_only'; -import { countTargetBytes } from './utils/count_target_bytes'; + import { badGatewayStatusCodes, errorCodeToStatusCode } from './statuses'; +import { countTargetBytes } from './utils/count_target_bytes'; +import { validHeadersOnly } from './utils/valid_headers_only'; const pipeline = util.promisify(stream.pipeline); @@ -74,7 +76,7 @@ export const forwardSocks = async ( ); resolve(); - } catch (error) { + } catch { // Client error, pipeline already destroys the streams, ignore. resolve(); } diff --git a/src/server.ts b/src/server.ts index 4b4a8fdb..90abcd6f 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,25 +1,30 @@ -import net from 'net'; -import dns from 'dns'; +/* eslint-disable no-use-before-define */ +import { Buffer } from 'buffer'; +import type dns from 'dns'; +import { EventEmitter } from 'events'; import http from 'http'; -import util from 'util'; +import type net from 'net'; import { URL } from 'url'; -import { EventEmitter } from 'events'; -import { Buffer } from 'buffer'; -import { parseAuthorizationHeader } from './utils/parse_authorization_header'; -import { redactUrl } from './utils/redact_url'; -import { nodeify } from './utils/nodeify'; -import { getTargetStats } from './utils/count_target_bytes'; -import { RequestError } from './request_error'; -import { chain, HandlerOpts as ChainOpts } from './chain'; -import { forward, HandlerOpts as ForwardOpts } from './forward'; -import { direct } from './direct'; -import { handleCustomResponse, HandlerOpts as CustomResponseOpts } from './custom_response'; -import { Socket } from './socket'; -import { normalizeUrlPort } from './utils/normalize_url_port'; -import { badGatewayStatusCodes } from './statuses'; +import util from 'util'; + +import type { HandlerOpts as ChainOpts } from './chain'; +import { chain } from './chain'; +import { chainSocks } from './chain_socks'; import { customConnect } from './custom_connect'; +import type { HandlerOpts as CustomResponseOpts } from './custom_response'; +import { handleCustomResponse } from './custom_response'; +import { direct } from './direct'; +import type { HandlerOpts as ForwardOpts } from './forward'; +import { forward } from './forward'; import { forwardSocks } from './forward_socks'; -import { chainSocks } from './chain_socks'; +import { RequestError } from './request_error'; +import type { Socket } from './socket'; +import { badGatewayStatusCodes } from './statuses'; +import { getTargetStats } from './utils/count_target_bytes'; +import { nodeify } from './utils/nodeify'; +import { normalizeUrlPort } from './utils/normalize_url_port'; +import { parseAuthorizationHeader } from './utils/parse_authorization_header'; +import { redactUrl } from './utils/redact_url'; export const SOCKS_PROTOCOLS = ['socks:', 'socks4:', 'socks4a:', 'socks5:', 'socks5h:']; @@ -270,16 +275,18 @@ export class Server extends EventEmitter { if (handlerOpts.customResponseFunction) { this.log(proxyChainId, 'Using handleCustomResponse()'); - return await handleCustomResponse(request, response, handlerOpts as CustomResponseOpts); + await handleCustomResponse(request, response, handlerOpts as CustomResponseOpts); + return; } if (handlerOpts.upstreamProxyUrlParsed && SOCKS_PROTOCOLS.includes(handlerOpts.upstreamProxyUrlParsed.protocol)) { this.log(proxyChainId, 'Using forwardSocks()'); - return await forwardSocks(request, response, handlerOpts as ForwardOpts); + await forwardSocks(request, response, handlerOpts as ForwardOpts); + return; } this.log(proxyChainId, 'Using forward()'); - return await forward(request, response, handlerOpts as ForwardOpts); + await forward(request, response, handlerOpts as ForwardOpts); } catch (error) { this.failRequest(request, this.normalizeHandlerError(error as NodeJS.ErrnoException)); } @@ -300,20 +307,23 @@ export class Server extends EventEmitter { if (handlerOpts.customConnectServer) { socket.unshift(head); // See chain.ts for why we do this - return await customConnect(socket, handlerOpts.customConnectServer); + await customConnect(socket, handlerOpts.customConnectServer); + return; } if (handlerOpts.upstreamProxyUrlParsed) { if (SOCKS_PROTOCOLS.includes(handlerOpts.upstreamProxyUrlParsed.protocol)) { this.log(socket.proxyChainId, `Using chainSocks() => ${request.url}`); - return await chainSocks(data); + await chainSocks(data); + return; } this.log(socket.proxyChainId, `Using chain() => ${request.url}`); - return await chain(data); + chain(data); + return; } this.log(socket.proxyChainId, `Using direct() => ${request.url}`); - return await direct(data); + direct(data); } catch (error) { this.failRequest(request, this.normalizeHandlerError(error as NodeJS.ErrnoException)); } @@ -363,7 +373,7 @@ export class Server extends EventEmitter { let parsed; try { parsed = new URL(request.url!); - } catch (error) { + } catch { // If URL is invalid, throw HTTP 400 error throw new RequestError(`Target "${request.url}" could not be parsed`, 400); } @@ -454,7 +464,6 @@ export class Server extends EventEmitter { } if (!['http:', 'https:', ...SOCKS_PROTOCOLS].includes(handlerOpts.upstreamProxyUrlParsed.protocol)) { - // eslint-disable-next-line max-len throw new Error(`Invalid "upstreamProxyUrl" provided: URL must have one of the following protocols: "http", "https", ${SOCKS_PROTOCOLS.map((p) => `"${p.replace(':', '')}"`).join(', ')} (was "${funcResult.upstreamProxyUrl}")`); } } @@ -555,7 +564,7 @@ export class Server extends EventEmitter { /** * Starts listening at a port specified in the constructor. */ - listen(callback?: (error: NodeJS.ErrnoException | null) => void): Promise { + async listen(callback?: (error: NodeJS.ErrnoException | null) => void): Promise { const promise = new Promise((resolve, reject) => { // Unfortunately server.listen() is not a normal function that fails on error, // so we need this trickery @@ -640,7 +649,7 @@ export class Server extends EventEmitter { * Closes the proxy server. * @param closeConnections If true, pending proxy connections are forcibly closed. */ - close(closeConnections: boolean, callback?: (error: NodeJS.ErrnoException | null) => void): Promise { + async close(closeConnections: boolean, callback?: (error: NodeJS.ErrnoException | null) => void): Promise { if (typeof closeConnections === 'function') { callback = closeConnections; closeConnections = false; diff --git a/src/statuses.ts b/src/statuses.ts index cd7c0fa2..4c0a3bee 100644 --- a/src/statuses.ts +++ b/src/statuses.ts @@ -78,5 +78,5 @@ export const socksErrorMessageToStatusCode = (socksErrorMessage: string): typeof return badGatewayStatusCodes.AUTH_FAILED; default: return badGatewayStatusCodes.GENERIC_ERROR; - }; + } }; diff --git a/src/tcp_tunnel_tools.ts b/src/tcp_tunnel_tools.ts index 25e36915..c6b43e0c 100644 --- a/src/tcp_tunnel_tools.ts +++ b/src/tcp_tunnel_tools.ts @@ -1,5 +1,6 @@ -import { URL } from 'url'; import net from 'net'; +import { URL } from 'url'; + import { chain } from './chain'; import { nodeify } from './utils/nodeify'; @@ -15,7 +16,7 @@ const getAddress = (server: net.Server) => { return `${host}:${port}`; }; -export function createTunnel( +export async function createTunnel( proxyUrl: string, targetHost: string, options: { @@ -40,13 +41,13 @@ export function createTunnel( const verbose = options && options.verbose; - const server = net.createServer(); + const server: net.Server & { log?: (...args: unknown[]) => void } = net.createServer(); const log = (...args: unknown[]): void => { if (verbose) console.log(...args); }; - (server as any).log = log; + server.log = log; server.on('connection', (sourceSocket) => { const remoteAddress = `${sourceSocket.remoteAddress}:${sourceSocket.remotePort}`; @@ -91,7 +92,7 @@ export function createTunnel( return nodeify(promise, callback); } -export function closeTunnel( +export async function closeTunnel( serverPath: string, closeConnections: boolean | undefined, callback: (error: Error | null, result?: boolean) => void, @@ -101,15 +102,24 @@ export function closeTunnel( if (!port) throw new Error('serverPath must contain port'); const promise = new Promise((resolve) => { - if (!runningServers[serverPath]) return resolve(false); - if (!closeConnections) return resolve(true); + if (!runningServers[serverPath]) { + resolve(false); + return; + } + if (!closeConnections) { + resolve(true); + return; + } for (const connection of runningServers[serverPath].connections) { connection.destroy(); } resolve(true); }) - .then((serverExists) => new Promise((resolve) => { - if (!serverExists) return resolve(false); + .then(async (serverExists) => new Promise((resolve) => { + if (!serverExists) { + resolve(false); + return; + } runningServers[serverPath].server.close(() => { delete runningServers[serverPath]; resolve(true); diff --git a/src/utils/count_target_bytes.ts b/src/utils/count_target_bytes.ts index fc6544bb..5a035115 100644 --- a/src/utils/count_target_bytes.ts +++ b/src/utils/count_target_bytes.ts @@ -1,4 +1,4 @@ -import net from 'net'; +import type net from 'net'; const targetBytesWritten = Symbol('targetBytesWritten'); const targetBytesRead = Symbol('targetBytesRead'); @@ -15,8 +15,7 @@ interface Extras { } // @ts-expect-error TS is not aware that `source` is used in the assertion. -// eslint-disable-next-line @typescript-eslint/no-empty-function -function typeSocket(source: unknown): asserts source is net.Socket & Extras {}; +function typeSocket(source: unknown): asserts source is net.Socket & Extras {} export const countTargetBytes = (source: net.Socket, target: net.Socket): void => { typeSocket(source); diff --git a/src/utils/decode_uri_component_safe.ts b/src/utils/decode_uri_component_safe.ts index c8eb234f..b7734de0 100644 --- a/src/utils/decode_uri_component_safe.ts +++ b/src/utils/decode_uri_component_safe.ts @@ -1,7 +1,7 @@ export const decodeURIComponentSafe = (encodedURIComponent: string): string => { try { return decodeURIComponent(encodedURIComponent); - } catch (e) { + } catch { return encodedURIComponent; } }; diff --git a/src/utils/get_basic.ts b/src/utils/get_basic.ts index da85b288..898c10a8 100644 --- a/src/utils/get_basic.ts +++ b/src/utils/get_basic.ts @@ -1,4 +1,5 @@ -import { URL } from 'url'; +import type { URL } from 'url'; + import { decodeURIComponentSafe } from './decode_uri_component_safe'; export const getBasicAuthorizationHeader = (url: URL): string => { diff --git a/src/utils/nodeify.ts b/src/utils/nodeify.ts index 284c56d0..378124b8 100644 --- a/src/utils/nodeify.ts +++ b/src/utils/nodeify.ts @@ -1,10 +1,10 @@ // Replacement for Bluebird's Promise.nodeify() -export const nodeify = (promise: Promise, callback?: (error: Error | null, result?: T) => void): Promise => { +export const nodeify = async (promise: Promise, callback?: (error: Error | null, result?: T) => void): Promise => { if (typeof callback !== 'function') return promise; promise.then( (result) => callback(null, result), - callback as any, + callback, ).catch((error) => { // Need to .catch because it doesn't crash the process on Node.js 14 process.nextTick(() => { diff --git a/src/utils/normalize_url_port.ts b/src/utils/normalize_url_port.ts index a8ed0df0..df6e6e4a 100644 --- a/src/utils/normalize_url_port.ts +++ b/src/utils/normalize_url_port.ts @@ -14,7 +14,7 @@ export const normalizeUrlPort = (url: URL): number => { return Number(url.port); } - if (mapping.hasOwnProperty(url.protocol)) { + if (url.protocol in mapping) { return mapping[url.protocol as keyof typeof mapping]; } diff --git a/src/utils/valid_headers_only.ts b/src/utils/valid_headers_only.ts index ee84ec46..aa6ec7ed 100644 --- a/src/utils/valid_headers_only.ts +++ b/src/utils/valid_headers_only.ts @@ -1,4 +1,5 @@ import { validateHeaderName, validateHeaderValue } from 'http'; + import { isHopByHopHeader } from './is_hop_by_hop_header'; /** @@ -16,19 +17,16 @@ export const validHeadersOnly = (rawHeaders: string[]): string[] => { try { validateHeaderName(name); validateHeaderValue(name, value); - } catch (error) { - // eslint-disable-next-line no-continue + } catch { continue; } if (isHopByHopHeader(name)) { - // eslint-disable-next-line no-continue continue; } if (name.toLowerCase() === 'host') { if (containsHost) { - // eslint-disable-next-line no-continue continue; } diff --git a/test/anonymize_proxy.js b/test/anonymize_proxy.js index 46d2e24f..9ceeef3a 100644 --- a/test/anonymize_proxy.js +++ b/test/anonymize_proxy.js @@ -9,6 +9,7 @@ const request = require('request'); const express = require('express'); const { anonymizeProxy, closeAnonymizedProxy, listenConnectAnonymizedProxy } = require('../src/index'); +const { expectThrowsAsync } = require('./utils/throws_async'); let expressServer; let proxyServer; @@ -105,43 +106,35 @@ describe('utils.anonymizeProxy', function () { // Need larger timeout for Travis CI this.timeout(5 * 1000); it('throws for invalid args', () => { - assert.throws(() => { anonymizeProxy(null); }, Error); - assert.throws(() => { anonymizeProxy(); }, Error); - assert.throws(() => { anonymizeProxy({}); }, Error); + expectThrowsAsync(async () => { await anonymizeProxy(null); }); + expectThrowsAsync(async () => { await anonymizeProxy(); }); + expectThrowsAsync(async () => { await anonymizeProxy({}); }); - assert.throws(() => { closeAnonymizedProxy({}); }, Error); - assert.throws(() => { closeAnonymizedProxy(); }, Error); - assert.throws(() => { closeAnonymizedProxy(null); }, Error); + expectThrowsAsync(async () => { await closeAnonymizedProxy({}); }); + expectThrowsAsync(async () => { await closeAnonymizedProxy(); }); + expectThrowsAsync(async () => { await closeAnonymizedProxy(null); }); }); it('throws for unsupported https: protocol', () => { - assert.throws(() => { anonymizeProxy('https://whatever.com'); }, Error); - assert.throws(() => { - anonymizeProxy({ url: 'https://whatever.com' }); - }, Error); + expectThrowsAsync(async () => { await anonymizeProxy('https://whatever.com'); }); + expectThrowsAsync(async () => { await anonymizeProxy({ url: 'https://whatever.com' }); }); }); it('throws for invalid ports', () => { - assert.throws(() => { - anonymizeProxy({ url: 'http://whatever.com', port: -16 }); - }, Error); - assert.throws(() => { - anonymizeProxy({ + expectThrowsAsync(async () => { await anonymizeProxy({ url: 'http://whatever.com', port: -16 }); }); + expectThrowsAsync(async () => { + await anonymizeProxy({ url: 'http://whatever.com', port: 4324324324, }); - }, Error); + }); }); it('throws for invalid URLs', () => { - assert.throws(() => { anonymizeProxy('://whatever.com'); }, Error); - assert.throws(() => { anonymizeProxy('https://whatever.com'); }, Error); - assert.throws(() => { - anonymizeProxy({ url: '://whatever.com' }); - }, Error); - assert.throws(() => { - anonymizeProxy({ url: 'https://whatever.com' }); - }, Error); + expectThrowsAsync(async () => { await anonymizeProxy('://whatever.com'); }); + expectThrowsAsync(async () => { await anonymizeProxy('https://whatever.com'); }); + expectThrowsAsync(async () => { await anonymizeProxy({ url: '://whatever.com' }); }); + expectThrowsAsync(async () => { await anonymizeProxy({ url: 'https://whatever.com' }); }); }); it('keeps already anonymous proxies (both with callbacks and promises)', () => { diff --git a/test/tcp_tunnel.js b/test/tcp_tunnel.js index cd660cdc..1b9705b4 100644 --- a/test/tcp_tunnel.js +++ b/test/tcp_tunnel.js @@ -4,6 +4,7 @@ const http = require('http'); const proxy = require('proxy'); const { createTunnel, closeTunnel } = require('../src/index'); +const { expectThrowsAsync } = require('./utils/throws_async'); const destroySocket = (socket) => new Promise((resolve, reject) => { if (!socket || socket.destroyed) return resolve(); @@ -42,16 +43,16 @@ const closeServer = (server, connections) => new Promise((resolve, reject) => { describe('tcp_tunnel.createTunnel', () => { it('throws error if proxyUrl is not in correct format', () => { - assert.throws(() => { createTunnel('socks://user:password@whatever.com:123', 'localhost:9000'); }, /must have the "http" protocol/); - assert.throws(() => { createTunnel('socks5://user:password@whatever.com', 'localhost:9000'); }, /must have the "http" protocol/); + expectThrowsAsync(async () => { await createTunnel('socks://user:password@whatever.com:123', 'localhost:9000'); }, /must have the "http" protocol/); + expectThrowsAsync(async () => { await createTunnel('socks5://user:password@whatever.com', 'localhost:9000'); }, /must have the "http" protocol/); }); it('throws error if target is not in correct format', () => { - assert.throws(() => { createTunnel('http://user:password@whatever.com:12'); }, 'Missing target hostname'); - assert.throws(() => { createTunnel('http://user:password@whatever.com:12', null); }, 'Missing target hostname'); - assert.throws(() => { createTunnel('http://user:password@whatever.com:12', ''); }, 'Missing target hostname'); - assert.throws(() => { createTunnel('http://user:password@whatever.com:12', 'whatever'); }, 'Missing target port'); - assert.throws(() => { createTunnel('http://user:password@whatever.com:12', 'whatever:'); }, 'Missing target port'); - assert.throws(() => { createTunnel('http://user:password@whatever.com:12', ':whatever'); }, /Invalid URL/); + expectThrowsAsync(async () => { await createTunnel('http://user:password@whatever.com:12'); }, 'Missing target hostname'); + expectThrowsAsync(async () => { await createTunnel('http://user:password@whatever.com:12', null); }, 'Missing target hostname'); + expectThrowsAsync(async () => { await createTunnel('http://user:password@whatever.com:12', ''); }, 'Missing target hostname'); + expectThrowsAsync(async () => { await createTunnel('http://user:password@whatever.com:12', 'whatever'); }, 'Missing target port'); + expectThrowsAsync(async () => { await createTunnel('http://user:password@whatever.com:12', 'whatever:'); }, 'Missing target port'); + expectThrowsAsync(async () => { await createTunnel('http://user:password@whatever.com:12', ':whatever'); }, /Invalid URL/); }); it('correctly tunnels to tcp service and then is able to close the connection', () => { const proxyServerConnections = []; diff --git a/test/utils/throws_async.js b/test/utils/throws_async.js new file mode 100644 index 00000000..c7108924 --- /dev/null +++ b/test/utils/throws_async.js @@ -0,0 +1,25 @@ +const { expect } = require('chai'); + +/** + * Expect an async function to throw + * @param {*} func Async function to be tested + * @param {*} errorMessage Error message to be expected, can be a string or a RegExp + */ +const expectThrowsAsync = async (func, errorMessage) => { + let error = null; + try { + await func(); + } catch (err) { + error = err; + } + expect(error).to.be.an('Error'); + if (errorMessage) { + if (errorMessage instanceof RegExp) { + expect(error.message).to.match(errorMessage); + } else { + expect(error.message).to.contain(errorMessage); + } + } +}; + +exports.expectThrowsAsync = expectThrowsAsync;