Import Upstream version 5.0.0+~cs8.0.0
This commit is contained in:
commit
bcfbfc69a2
|
@ -0,0 +1,37 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{*.json,*.json.example,*.gyp,*.yml,*.yaml,*.workflow}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{*.py,*.asm}]
|
||||
indent_style = space
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[*.asm]
|
||||
indent_size = 8
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Ideal settings - some plugins might support these.
|
||||
[*.js]
|
||||
quote_type = single
|
||||
|
||||
[{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}]
|
||||
curly_bracket_next_line = false
|
||||
spaces_around_operators = true
|
||||
spaces_around_brackets = outside
|
||||
# close enough to 1TB
|
||||
indent_brace_style = K&R
|
|
@ -0,0 +1,86 @@
|
|||
module.exports = {
|
||||
'extends': [
|
||||
'airbnb',
|
||||
'prettier'
|
||||
],
|
||||
'parser': '@typescript-eslint/parser',
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018,
|
||||
'sourceType': 'module',
|
||||
'modules': true
|
||||
},
|
||||
'plugins': [
|
||||
'@typescript-eslint'
|
||||
],
|
||||
'settings': {
|
||||
'import/resolver': {
|
||||
'typescript': {
|
||||
}
|
||||
}
|
||||
},
|
||||
'rules': {
|
||||
'quotes': [
|
||||
2,
|
||||
'single',
|
||||
{
|
||||
'allowTemplateLiterals': true
|
||||
}
|
||||
],
|
||||
'class-methods-use-this': 0,
|
||||
'consistent-return': 0,
|
||||
'func-names': 0,
|
||||
'global-require': 0,
|
||||
'guard-for-in': 0,
|
||||
'import/no-duplicates': 0,
|
||||
'import/no-dynamic-require': 0,
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'lines-between-class-members': 0,
|
||||
'no-await-in-loop': 0,
|
||||
'no-bitwise': 0,
|
||||
'no-console': 0,
|
||||
'no-continue': 0,
|
||||
'no-control-regex': 0,
|
||||
'no-empty': 0,
|
||||
'no-loop-func': 0,
|
||||
'no-nested-ternary': 0,
|
||||
'no-param-reassign': 0,
|
||||
'no-plusplus': 0,
|
||||
'no-restricted-globals': 0,
|
||||
'no-restricted-syntax': 0,
|
||||
'no-shadow': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
'no-use-before-define': 0,
|
||||
'prefer-const': 0,
|
||||
'prefer-destructuring': 0,
|
||||
'camelcase': 0,
|
||||
'no-unused-vars': 0, // in favor of '@typescript-eslint/no-unused-vars'
|
||||
// 'indent': 0 // in favor of '@typescript-eslint/indent'
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
// '@typescript-eslint/indent': ['error', 2] // this might conflict with a lot ongoing changes
|
||||
'@typescript-eslint/no-array-constructor': 'error',
|
||||
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||
'@typescript-eslint/class-name-casing': 'error',
|
||||
'@typescript-eslint/interface-name-prefix': 'error',
|
||||
'@typescript-eslint/no-empty-interface': 'error',
|
||||
'@typescript-eslint/no-inferrable-types': 'error',
|
||||
'@typescript-eslint/no-misused-new': 'error',
|
||||
'@typescript-eslint/no-namespace': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-parameter-properties': 'error',
|
||||
'@typescript-eslint/no-triple-slash-reference': 'error',
|
||||
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
||||
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||
// '@typescript-eslint/array-type': 'error',
|
||||
// '@typescript-eslint/ban-types': 'error',
|
||||
// '@typescript-eslint/explicit-function-return-type': 'warn',
|
||||
// '@typescript-eslint/explicit-member-accessibility': 'error',
|
||||
// '@typescript-eslint/member-delimiter-style': 'error',
|
||||
// '@typescript-eslint/no-angle-bracket-type-assertion': 'error',
|
||||
// '@typescript-eslint/no-explicit-any': 'warn',
|
||||
// '@typescript-eslint/no-object-literal-type-assertion': 'error',
|
||||
// '@typescript-eslint/no-use-before-define': 'error',
|
||||
// '@typescript-eslint/no-var-requires': 'error',
|
||||
// '@typescript-eslint/prefer-interface': 'error'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
name: Node CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '!*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Test Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node-version: [6.x, 8.x, 10.x, 12.x]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Print Node.js Version
|
||||
run: node --version
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Run "build" step
|
||||
run: npm run build --if-present
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
env:
|
||||
CI: true
|
|
@ -0,0 +1,4 @@
|
|||
/node_modules
|
||||
/yarn.lock
|
||||
/?.?s
|
||||
/dist
|
|
@ -0,0 +1,3 @@
|
|||
History.md
|
||||
test
|
||||
.travis.yml
|
|
@ -0,0 +1,137 @@
|
|||
https-proxy-agent
|
||||
================
|
||||
### An HTTP(s) proxy `http.Agent` implementation for HTTPS
|
||||
[![Build Status](https://github.com/TooTallNate/node-https-proxy-agent/workflows/Node%20CI/badge.svg)](https://github.com/TooTallNate/node-https-proxy-agent/actions?workflow=Node+CI)
|
||||
|
||||
This module provides an `http.Agent` implementation that connects to a specified
|
||||
HTTP or HTTPS proxy server, and can be used with the built-in `https` module.
|
||||
|
||||
Specifically, this `Agent` implementation connects to an intermediary "proxy"
|
||||
server and issues the [CONNECT HTTP method][CONNECT], which tells the proxy to
|
||||
open a direct TCP connection to the destination server.
|
||||
|
||||
Since this agent implements the CONNECT HTTP method, it also works with other
|
||||
protocols that use this method when connecting over proxies (i.e. WebSockets).
|
||||
See the "Examples" section below for more.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install with `npm`:
|
||||
|
||||
``` bash
|
||||
$ npm install https-proxy-agent
|
||||
```
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
#### `https` module example
|
||||
|
||||
``` js
|
||||
var url = require('url');
|
||||
var https = require('https');
|
||||
var HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
// HTTP/HTTPS proxy to connect to
|
||||
var proxy = process.env.http_proxy || 'http://168.63.76.32:3128';
|
||||
console.log('using proxy server %j', proxy);
|
||||
|
||||
// HTTPS endpoint for the proxy to connect to
|
||||
var endpoint = process.argv[2] || 'https://graph.facebook.com/tootallnate';
|
||||
console.log('attempting to GET %j', endpoint);
|
||||
var options = url.parse(endpoint);
|
||||
|
||||
// create an instance of the `HttpsProxyAgent` class with the proxy server information
|
||||
var agent = new HttpsProxyAgent(proxy);
|
||||
options.agent = agent;
|
||||
|
||||
https.get(options, function (res) {
|
||||
console.log('"response" event!', res.headers);
|
||||
res.pipe(process.stdout);
|
||||
});
|
||||
```
|
||||
|
||||
#### `ws` WebSocket connection example
|
||||
|
||||
``` js
|
||||
var url = require('url');
|
||||
var WebSocket = require('ws');
|
||||
var HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
// HTTP/HTTPS proxy to connect to
|
||||
var proxy = process.env.http_proxy || 'http://168.63.76.32:3128';
|
||||
console.log('using proxy server %j', proxy);
|
||||
|
||||
// WebSocket endpoint for the proxy to connect to
|
||||
var endpoint = process.argv[2] || 'ws://echo.websocket.org';
|
||||
var parsed = url.parse(endpoint);
|
||||
console.log('attempting to connect to WebSocket %j', endpoint);
|
||||
|
||||
// create an instance of the `HttpsProxyAgent` class with the proxy server information
|
||||
var options = url.parse(proxy);
|
||||
|
||||
var agent = new HttpsProxyAgent(options);
|
||||
|
||||
// finally, initiate the WebSocket connection
|
||||
var socket = new WebSocket(endpoint, { agent: agent });
|
||||
|
||||
socket.on('open', function () {
|
||||
console.log('"open" event!');
|
||||
socket.send('hello world');
|
||||
});
|
||||
|
||||
socket.on('message', function (data, flags) {
|
||||
console.log('"message" event! %j %j', data, flags);
|
||||
socket.close();
|
||||
});
|
||||
```
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
### new HttpsProxyAgent(Object options)
|
||||
|
||||
The `HttpsProxyAgent` class implements an `http.Agent` subclass that connects
|
||||
to the specified "HTTP(s) proxy server" in order to proxy HTTPS and/or WebSocket
|
||||
requests. This is achieved by using the [HTTP `CONNECT` method][CONNECT].
|
||||
|
||||
The `options` argument may either be a string URI of the proxy server to use, or an
|
||||
"options" object with more specific properties:
|
||||
|
||||
* `host` - String - Proxy host to connect to (may use `hostname` as well). Required.
|
||||
* `port` - Number - Proxy port to connect to. Required.
|
||||
* `protocol` - String - If `https:`, then use TLS to connect to the proxy.
|
||||
* `headers` - Object - Additional HTTP headers to be sent on the HTTP CONNECT method.
|
||||
* Any other options given are passed to the `net.connect()`/`tls.connect()` functions.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
[CONNECT]: http://en.wikipedia.org/wiki/HTTP_tunnel#HTTP_CONNECT_Tunneling
|
|
@ -0,0 +1,37 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{*.json,*.json.example,*.gyp,*.yml,*.yaml,*.workflow}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{*.py,*.asm}]
|
||||
indent_style = space
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[*.asm]
|
||||
indent_size = 8
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Ideal settings - some plugins might support these.
|
||||
[*.js]
|
||||
quote_type = single
|
||||
|
||||
[{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}]
|
||||
curly_bracket_next_line = false
|
||||
spaces_around_operators = true
|
||||
spaces_around_brackets = outside
|
||||
# close enough to 1TB
|
||||
indent_brace_style = K&R
|
|
@ -0,0 +1,86 @@
|
|||
module.exports = {
|
||||
'extends': [
|
||||
'airbnb',
|
||||
'prettier'
|
||||
],
|
||||
'parser': '@typescript-eslint/parser',
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018,
|
||||
'sourceType': 'module',
|
||||
'modules': true
|
||||
},
|
||||
'plugins': [
|
||||
'@typescript-eslint'
|
||||
],
|
||||
'settings': {
|
||||
'import/resolver': {
|
||||
'typescript': {
|
||||
}
|
||||
}
|
||||
},
|
||||
'rules': {
|
||||
'quotes': [
|
||||
2,
|
||||
'single',
|
||||
{
|
||||
'allowTemplateLiterals': true
|
||||
}
|
||||
],
|
||||
'class-methods-use-this': 0,
|
||||
'consistent-return': 0,
|
||||
'func-names': 0,
|
||||
'global-require': 0,
|
||||
'guard-for-in': 0,
|
||||
'import/no-duplicates': 0,
|
||||
'import/no-dynamic-require': 0,
|
||||
'import/no-extraneous-dependencies': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'lines-between-class-members': 0,
|
||||
'no-await-in-loop': 0,
|
||||
'no-bitwise': 0,
|
||||
'no-console': 0,
|
||||
'no-continue': 0,
|
||||
'no-control-regex': 0,
|
||||
'no-empty': 0,
|
||||
'no-loop-func': 0,
|
||||
'no-nested-ternary': 0,
|
||||
'no-param-reassign': 0,
|
||||
'no-plusplus': 0,
|
||||
'no-restricted-globals': 0,
|
||||
'no-restricted-syntax': 0,
|
||||
'no-shadow': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
'no-use-before-define': 0,
|
||||
'prefer-const': 0,
|
||||
'prefer-destructuring': 0,
|
||||
'camelcase': 0,
|
||||
'no-unused-vars': 0, // in favor of '@typescript-eslint/no-unused-vars'
|
||||
// 'indent': 0 // in favor of '@typescript-eslint/indent'
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
// '@typescript-eslint/indent': ['error', 2] // this might conflict with a lot ongoing changes
|
||||
'@typescript-eslint/no-array-constructor': 'error',
|
||||
'@typescript-eslint/adjacent-overload-signatures': 'error',
|
||||
'@typescript-eslint/class-name-casing': 'error',
|
||||
'@typescript-eslint/interface-name-prefix': 'error',
|
||||
'@typescript-eslint/no-empty-interface': 'error',
|
||||
'@typescript-eslint/no-inferrable-types': 'error',
|
||||
'@typescript-eslint/no-misused-new': 'error',
|
||||
'@typescript-eslint/no-namespace': 'error',
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-parameter-properties': 'error',
|
||||
'@typescript-eslint/no-triple-slash-reference': 'error',
|
||||
'@typescript-eslint/prefer-namespace-keyword': 'error',
|
||||
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||
// '@typescript-eslint/array-type': 'error',
|
||||
// '@typescript-eslint/ban-types': 'error',
|
||||
// '@typescript-eslint/explicit-function-return-type': 'warn',
|
||||
// '@typescript-eslint/explicit-member-accessibility': 'error',
|
||||
// '@typescript-eslint/member-delimiter-style': 'error',
|
||||
// '@typescript-eslint/no-angle-bracket-type-assertion': 'error',
|
||||
// '@typescript-eslint/no-explicit-any': 'warn',
|
||||
// '@typescript-eslint/no-object-literal-type-assertion': 'error',
|
||||
// '@typescript-eslint/no-use-before-define': 'error',
|
||||
// '@typescript-eslint/no-var-requires': 'error',
|
||||
// '@typescript-eslint/prefer-interface': 'error'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
name: Node CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '!*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Test Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node-version: [6.x, 8.x, 10.x, 12.x]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Print Node.js Version
|
||||
run: node --version
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Run "build" step
|
||||
run: npm run build --if-present
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
env:
|
||||
CI: true
|
|
@ -0,0 +1,4 @@
|
|||
/dist
|
||||
/yarn.lock
|
||||
/node_modules
|
||||
/?.?s
|
|
@ -0,0 +1,74 @@
|
|||
http-proxy-agent
|
||||
================
|
||||
### An HTTP(s) proxy `http.Agent` implementation for HTTP
|
||||
[![Build Status](https://github.com/TooTallNate/node-http-proxy-agent/workflows/Node%20CI/badge.svg)](https://github.com/TooTallNate/node-http-proxy-agent/actions?workflow=Node+CI)
|
||||
|
||||
This module provides an `http.Agent` implementation that connects to a specified
|
||||
HTTP or HTTPS proxy server, and can be used with the built-in `http` module.
|
||||
|
||||
__Note:__ For HTTP proxy usage with the `https` module, check out
|
||||
[`node-https-proxy-agent`](https://github.com/TooTallNate/node-https-proxy-agent).
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install with `npm`:
|
||||
|
||||
``` bash
|
||||
$ npm install http-proxy-agent
|
||||
```
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
``` js
|
||||
var url = require('url');
|
||||
var http = require('http');
|
||||
var HttpProxyAgent = require('http-proxy-agent');
|
||||
|
||||
// HTTP/HTTPS proxy to connect to
|
||||
var proxy = process.env.http_proxy || 'http://168.63.76.32:3128';
|
||||
console.log('using proxy server %j', proxy);
|
||||
|
||||
// HTTP endpoint for the proxy to connect to
|
||||
var endpoint = process.argv[2] || 'http://nodejs.org/api/';
|
||||
console.log('attempting to GET %j', endpoint);
|
||||
var opts = url.parse(endpoint);
|
||||
|
||||
// create an instance of the `HttpProxyAgent` class with the proxy server information
|
||||
var agent = new HttpProxyAgent(proxy);
|
||||
opts.agent = agent;
|
||||
|
||||
http.get(opts, function (res) {
|
||||
console.log('"response" event!', res.headers);
|
||||
res.pipe(process.stdout);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2013 Nathan Rajlich <nathan@tootallnate.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "http-proxy-agent",
|
||||
"version": "5.0.0",
|
||||
"description": "An HTTP(s) proxy `http.Agent` implementation for HTTP",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "tsc",
|
||||
"test": "mocha",
|
||||
"test-lint": "eslint src --ext .js,.ts",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/TooTallNate/node-http-proxy-agent.git"
|
||||
},
|
||||
"keywords": [
|
||||
"http",
|
||||
"proxy",
|
||||
"endpoint",
|
||||
"agent"
|
||||
],
|
||||
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/TooTallNate/node-http-proxy-agent/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tootallnate/once": "2",
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "4",
|
||||
"@types/node": "^12.19.2",
|
||||
"@typescript-eslint/eslint-plugin": "1.6.0",
|
||||
"@typescript-eslint/parser": "1.1.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-airbnb": "17.1.0",
|
||||
"eslint-config-prettier": "4.1.0",
|
||||
"eslint-import-resolver-typescript": "1.1.1",
|
||||
"eslint-plugin-import": "2.16.0",
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-react": "7.12.4",
|
||||
"mocha": "^6.2.2",
|
||||
"proxy": "1",
|
||||
"rimraf": "^3.0.0",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
import net from 'net';
|
||||
import tls from 'tls';
|
||||
import url from 'url';
|
||||
import createDebug from 'debug';
|
||||
import once from '@tootallnate/once';
|
||||
import { Agent, ClientRequest, RequestOptions } from 'agent-base';
|
||||
import { HttpProxyAgentOptions } from '.';
|
||||
|
||||
const debug = createDebug('http-proxy-agent');
|
||||
|
||||
interface HttpProxyAgentClientRequest extends ClientRequest {
|
||||
path: string;
|
||||
output?: string[];
|
||||
outputData?: {
|
||||
data: string;
|
||||
}[];
|
||||
_header?: string | null;
|
||||
_implicitHeader(): void;
|
||||
}
|
||||
|
||||
function isHTTPS(protocol?: string | null): boolean {
|
||||
return typeof protocol === 'string' ? /^https:?$/i.test(protocol) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `HttpProxyAgent` implements an HTTP Agent subclass that connects
|
||||
* to the specified "HTTP proxy server" in order to proxy HTTP requests.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
export default class HttpProxyAgent extends Agent {
|
||||
private secureProxy: boolean;
|
||||
private proxy: HttpProxyAgentOptions;
|
||||
|
||||
constructor(_opts: string | HttpProxyAgentOptions) {
|
||||
let opts: HttpProxyAgentOptions;
|
||||
if (typeof _opts === 'string') {
|
||||
opts = url.parse(_opts);
|
||||
} else {
|
||||
opts = _opts;
|
||||
}
|
||||
if (!opts) {
|
||||
throw new Error(
|
||||
'an HTTP(S) proxy server `host` and `port` must be specified!'
|
||||
);
|
||||
}
|
||||
debug('Creating new HttpProxyAgent instance: %o', opts);
|
||||
super(opts);
|
||||
|
||||
const proxy: HttpProxyAgentOptions = { ...opts };
|
||||
|
||||
// If `true`, then connect to the proxy server over TLS.
|
||||
// Defaults to `false`.
|
||||
this.secureProxy = opts.secureProxy || isHTTPS(proxy.protocol);
|
||||
|
||||
// Prefer `hostname` over `host`, and set the `port` if needed.
|
||||
proxy.host = proxy.hostname || proxy.host;
|
||||
if (typeof proxy.port === 'string') {
|
||||
proxy.port = parseInt(proxy.port, 10);
|
||||
}
|
||||
if (!proxy.port && proxy.host) {
|
||||
proxy.port = this.secureProxy ? 443 : 80;
|
||||
}
|
||||
|
||||
if (proxy.host && proxy.path) {
|
||||
// If both a `host` and `path` are specified then it's most likely
|
||||
// the result of a `url.parse()` call... we need to remove the
|
||||
// `path` portion so that `net.connect()` doesn't attempt to open
|
||||
// that as a Unix socket file.
|
||||
delete proxy.path;
|
||||
delete proxy.pathname;
|
||||
}
|
||||
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the node-core HTTP client library is creating a
|
||||
* new HTTP request.
|
||||
*
|
||||
* @api protected
|
||||
*/
|
||||
async callback(
|
||||
req: HttpProxyAgentClientRequest,
|
||||
opts: RequestOptions
|
||||
): Promise<net.Socket> {
|
||||
const { proxy, secureProxy } = this;
|
||||
const parsed = url.parse(req.path);
|
||||
|
||||
if (!parsed.protocol) {
|
||||
parsed.protocol = 'http:';
|
||||
}
|
||||
|
||||
if (!parsed.hostname) {
|
||||
parsed.hostname = opts.hostname || opts.host || null;
|
||||
}
|
||||
|
||||
if (parsed.port == null && typeof opts.port) {
|
||||
parsed.port = String(opts.port);
|
||||
}
|
||||
|
||||
if (parsed.port === '80') {
|
||||
// if port is 80, then we can remove the port so that the
|
||||
// ":80" portion is not on the produced URL
|
||||
parsed.port = '';
|
||||
}
|
||||
|
||||
// Change the `http.ClientRequest` instance's "path" field
|
||||
// to the absolute path of the URL that will be requested.
|
||||
req.path = url.format(parsed);
|
||||
|
||||
// Inject the `Proxy-Authorization` header if necessary.
|
||||
if (proxy.auth) {
|
||||
req.setHeader(
|
||||
'Proxy-Authorization',
|
||||
`Basic ${Buffer.from(proxy.auth).toString('base64')}`
|
||||
);
|
||||
}
|
||||
|
||||
// Create a socket connection to the proxy server.
|
||||
let socket: net.Socket;
|
||||
if (secureProxy) {
|
||||
debug('Creating `tls.Socket`: %o', proxy);
|
||||
socket = tls.connect(proxy as tls.ConnectionOptions);
|
||||
} else {
|
||||
debug('Creating `net.Socket`: %o', proxy);
|
||||
socket = net.connect(proxy as net.NetConnectOpts);
|
||||
}
|
||||
|
||||
// At this point, the http ClientRequest's internal `_header` field
|
||||
// might have already been set. If this is the case then we'll need
|
||||
// to re-generate the string since we just changed the `req.path`.
|
||||
if (req._header) {
|
||||
let first: string;
|
||||
let endOfHeaders: number;
|
||||
debug('Regenerating stored HTTP header string for request');
|
||||
req._header = null;
|
||||
req._implicitHeader();
|
||||
if (req.output && req.output.length > 0) {
|
||||
// Node < 12
|
||||
debug(
|
||||
'Patching connection write() output buffer with updated header'
|
||||
);
|
||||
first = req.output[0];
|
||||
endOfHeaders = first.indexOf('\r\n\r\n') + 4;
|
||||
req.output[0] = req._header + first.substring(endOfHeaders);
|
||||
debug('Output buffer: %o', req.output);
|
||||
} else if (req.outputData && req.outputData.length > 0) {
|
||||
// Node >= 12
|
||||
debug(
|
||||
'Patching connection write() output buffer with updated header'
|
||||
);
|
||||
first = req.outputData[0].data;
|
||||
endOfHeaders = first.indexOf('\r\n\r\n') + 4;
|
||||
req.outputData[0].data =
|
||||
req._header + first.substring(endOfHeaders);
|
||||
debug('Output buffer: %o', req.outputData[0].data);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the socket's `connect` event, so that this `callback()`
|
||||
// function throws instead of the `http` request machinery. This is
|
||||
// important for i.e. `PacProxyAgent` which determines a failed proxy
|
||||
// connection via the `callback()` function throwing.
|
||||
await once(socket, 'connect');
|
||||
|
||||
return socket;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import net from 'net';
|
||||
import tls from 'tls';
|
||||
import { Url } from 'url';
|
||||
import { AgentOptions } from 'agent-base';
|
||||
import _HttpProxyAgent from './agent';
|
||||
|
||||
function createHttpProxyAgent(
|
||||
opts: string | createHttpProxyAgent.HttpProxyAgentOptions
|
||||
): _HttpProxyAgent {
|
||||
return new _HttpProxyAgent(opts);
|
||||
}
|
||||
|
||||
namespace createHttpProxyAgent {
|
||||
interface BaseHttpProxyAgentOptions {
|
||||
secureProxy?: boolean;
|
||||
host?: string | null;
|
||||
path?: string | null;
|
||||
port?: string | number | null;
|
||||
}
|
||||
|
||||
export interface HttpProxyAgentOptions
|
||||
extends AgentOptions,
|
||||
BaseHttpProxyAgentOptions,
|
||||
Partial<
|
||||
Omit<
|
||||
Url & net.NetConnectOpts & tls.ConnectionOptions,
|
||||
keyof BaseHttpProxyAgentOptions
|
||||
>
|
||||
> {}
|
||||
|
||||
export type HttpProxyAgent = _HttpProxyAgent;
|
||||
export const HttpProxyAgent = _HttpProxyAgent;
|
||||
|
||||
createHttpProxyAgent.prototype = _HttpProxyAgent.prototype;
|
||||
}
|
||||
|
||||
export = createHttpProxyAgent;
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQCzURxIqzer0ACAbX/lHdsn4Gd9PLKrf7EeDYfIdV0HZKPD8WDr
|
||||
bBx2/fBu0OW2sjnzv/SVZbJ0DAuPE/p0+eT0qb2qC10iz9iTD7ribd7gxhirVb8y
|
||||
b3fBjXsxc8V8p4Ny1LcvNSqCjwUbJqdRogfoJeTiqPM58z5sNzuv5iq7iwIDAQAB
|
||||
AoGAPMQy4olrP0UotlzlJ36bowLP70ffgHCwU+/f4NWs5fF78c3du0oSx1w820Dd
|
||||
Z7E0JF8bgnlJJTxjumPZz0RUCugrEHBKJmzEz3cxF5E3+7NvteZcjKn9D67RrM5x
|
||||
1/uSZ9cqKE9cYvY4fSuHx18diyZ4axR/wB1Pea2utjjDM+ECQQDb9ZbmmaWMiRpQ
|
||||
5Up+loxP7BZNPsEVsm+DVJmEFbaFgGfncWBqSIqnPNjMwTwj0OigTwCAEGPkfRVW
|
||||
T0pbYWCxAkEA0LK7SCTwzyDmhASUalk0x+3uCAA6ryFdwJf/wd8TRAvVOmkTEldX
|
||||
uJ7ldLvfrONYO3v56uKTU/SoNdZYzKtO+wJAX2KM4ctXYy5BXztPpr2acz4qHa1N
|
||||
Bh+vBAC34fOYhyQ76r3b1btHhWZ5jbFuZwm9F2erC94Ps5IaoqcX07DSwQJAPKGw
|
||||
h2U0EPkd/3zVIZCJJQya+vgWFIs9EZcXVtvYXQyTBkVApTN66MhBIYjzkub5205J
|
||||
bVQmOV37AKklY1DhwQJAA1wos0cYxro02edzatxd0DIR2r4qqOqLkw6BhYHhq6HJ
|
||||
ZvIcQkHqdSXzdETFc01I1znDGGIrJHcnvKWgBPoEUg==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,12 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIB1TCCAT4CCQDV5mPlzm9+izANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDEyQ3
|
||||
NTI3YmQ3Ny1hYjNlLTQ3NGItYWNlNy1lZWQ2MDUzOTMxZTcwHhcNMTUwNzA2MjI0
|
||||
NTA3WhcNMjUwNzAzMjI0NTA3WjAvMS0wKwYDVQQDEyQ3NTI3YmQ3Ny1hYjNlLTQ3
|
||||
NGItYWNlNy1lZWQ2MDUzOTMxZTcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
|
||||
ALNRHEirN6vQAIBtf+Ud2yfgZ308sqt/sR4Nh8h1XQdko8PxYOtsHHb98G7Q5bay
|
||||
OfO/9JVlsnQMC48T+nT55PSpvaoLXSLP2JMPuuJt3uDGGKtVvzJvd8GNezFzxXyn
|
||||
g3LUty81KoKPBRsmp1GiB+gl5OKo8znzPmw3O6/mKruLAgMBAAEwDQYJKoZIhvcN
|
||||
AQEFBQADgYEACzoHUF8UV2Z6541Q2wKEA0UFUzmUjf/E1XwBO+1P15ZZ64uw34B4
|
||||
1RwMPtAo9RY/PmICTWtNxWGxkzwb2JtDWtnxVER/lF8k2XcXPE76fxTHJF/BKk9J
|
||||
QU8OTD1dd9gHCBviQB9TqntRZ5X7axjtuWjb2umY+owBYzAHZkp1HKI=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,389 @@
|
|||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
const url = require('url');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const assert = require('assert');
|
||||
const Proxy = require('proxy');
|
||||
const { Agent } = require('agent-base');
|
||||
const { HttpProxyAgent } = require('../');
|
||||
|
||||
const sleep = n => new Promise(r => setTimeout(r, n));
|
||||
|
||||
describe('HttpProxyAgent', function() {
|
||||
let server;
|
||||
let serverPort;
|
||||
|
||||
let proxy;
|
||||
let proxyPort;
|
||||
|
||||
let sslProxy;
|
||||
let sslProxyPort;
|
||||
|
||||
before(function(done) {
|
||||
// setup HTTP proxy server
|
||||
proxy = Proxy();
|
||||
proxy.listen(function() {
|
||||
proxyPort = proxy.address().port;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
before(function(done) {
|
||||
// setup target HTTP server
|
||||
server = http.createServer();
|
||||
server.listen(function() {
|
||||
serverPort = server.address().port;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
server.removeAllListeners('request');
|
||||
});
|
||||
|
||||
before(function(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 = Proxy(https.createServer(options));
|
||||
sslProxy.listen(function() {
|
||||
sslProxyPort = sslProxy.address().port;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// shut down test HTTP server
|
||||
after(function(done) {
|
||||
proxy.once('close', function() {
|
||||
done();
|
||||
});
|
||||
proxy.close();
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
server.once('close', function() {
|
||||
done();
|
||||
});
|
||||
server.close();
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
sslProxy.once('close', function() {
|
||||
done();
|
||||
});
|
||||
sslProxy.close();
|
||||
});
|
||||
|
||||
describe('constructor', function() {
|
||||
it('should throw an Error if no "proxy" argument is given', function() {
|
||||
assert.throws(function() {
|
||||
new HttpProxyAgent();
|
||||
});
|
||||
});
|
||||
it('should accept a "string" proxy argument', function() {
|
||||
let agent = new HttpProxyAgent(`http://127.0.0.1:${proxyPort}`);
|
||||
assert.equal('127.0.0.1', agent.proxy.host);
|
||||
assert.equal(proxyPort, agent.proxy.port);
|
||||
});
|
||||
it('should accept a `url.parse()` result object argument', function() {
|
||||
let opts = url.parse(`http://127.0.0.1:${proxyPort}`);
|
||||
let agent = new HttpProxyAgent(opts);
|
||||
assert.equal('127.0.0.1', agent.proxy.host);
|
||||
assert.equal(proxyPort, agent.proxy.port);
|
||||
});
|
||||
it('should set a `defaultPort` property', function() {
|
||||
let opts = url.parse(`http://127.0.0.1:${proxyPort}`);
|
||||
let agent = new HttpProxyAgent(opts);
|
||||
assert.equal(80, agent.defaultPort);
|
||||
});
|
||||
describe('secureProxy', function() {
|
||||
it('should default to `false`', function() {
|
||||
let agent = new HttpProxyAgent({ port: proxyPort });
|
||||
assert.equal(false, agent.secureProxy);
|
||||
});
|
||||
it('should be `false` when "http:" protocol is used', function() {
|
||||
let agent = new HttpProxyAgent({
|
||||
port: proxyPort,
|
||||
protocol: 'http:'
|
||||
});
|
||||
assert.equal(false, agent.secureProxy);
|
||||
});
|
||||
it('should be `true` when "https:" protocol is used', function() {
|
||||
let agent = new HttpProxyAgent({
|
||||
port: proxyPort,
|
||||
protocol: 'https:'
|
||||
});
|
||||
assert.equal(true, agent.secureProxy);
|
||||
});
|
||||
it('should be `true` when "https" protocol is used', function() {
|
||||
let agent = new HttpProxyAgent({
|
||||
port: proxyPort,
|
||||
protocol: 'https'
|
||||
});
|
||||
assert.equal(true, agent.secureProxy);
|
||||
});
|
||||
it('should support a `defaultPort` option', function() {
|
||||
let agent = new HttpProxyAgent({
|
||||
port: proxyPort,
|
||||
secureProxy: true
|
||||
});
|
||||
assert.equal(true, agent.secureProxy);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"http" module', function() {
|
||||
it('should work over an HTTP proxy', function(done) {
|
||||
// set HTTP "request" event handler for this test
|
||||
server.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_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, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
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', function(done) {
|
||||
// set HTTP "request" event handler for this test
|
||||
server.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy ||
|
||||
`https://127.0.0.1:${sslProxyPort}`;
|
||||
proxy = url.parse(proxy);
|
||||
proxy.rejectUnauthorized = false;
|
||||
let agent = new HttpProxyAgent(proxy);
|
||||
assert.equal(true, agent.secureProxy);
|
||||
|
||||
let opts = url.parse(`http://127.0.0.1:${serverPort}`);
|
||||
opts.agent = agent;
|
||||
|
||||
http.get(opts, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
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', function(done) {
|
||||
// set HTTP "request" event handler for this test
|
||||
server.once('request', function(req, res) {
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
url: req.url
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_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, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
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`', function(done) {
|
||||
// set a proxy authentication function for this test
|
||||
proxy.authenticate = function(req, fn) {
|
||||
// reject all requests
|
||||
fn(null, false);
|
||||
};
|
||||
|
||||
let proxyUri =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
`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, function(res) {
|
||||
assert.equal(407, res.statusCode);
|
||||
assert('proxy-authenticate' in res.headers);
|
||||
delete proxy.authenticate;
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should send the "Proxy-Authorization" request header', function(done) {
|
||||
// set a proxy authentication function for this test
|
||||
proxy.authenticate = function(req, fn) {
|
||||
// username:password is "foo:bar"
|
||||
fn(
|
||||
null,
|
||||
req.headers['proxy-authorization'] == 'Basic Zm9vOmJhcg=='
|
||||
);
|
||||
};
|
||||
|
||||
// set HTTP "request" event handler for this test
|
||||
server.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
|
||||
let proxyUri =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
`http://127.0.0.1:${proxyPort}`;
|
||||
let proxyOpts = url.parse(proxyUri);
|
||||
proxyOpts.auth = 'foo:bar';
|
||||
let agent = new HttpProxyAgent(proxyOpts);
|
||||
|
||||
let opts = url.parse(`http://127.0.0.1:${serverPort}`);
|
||||
opts.agent = agent;
|
||||
|
||||
http.get(opts, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
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', function(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', function(err) {
|
||||
assert.equal('ECONNREFUSED', err.code);
|
||||
req.abort();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should work after the first tick of the `http.ClientRequest` instance', function(done) {
|
||||
// set HTTP "request" event handler for this test
|
||||
server.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.url));
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_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.
|
||||
const sleepAgent = new Agent((req, opts) => {
|
||||
assert.equal(opts.secureEndpoint, false);
|
||||
assert.equal(opts.protocol, 'http:');
|
||||
assert(!req._header);
|
||||
return sleep(10).then(() => {
|
||||
assert.equal(typeof req._header, 'string');
|
||||
return httpProxyAgent;
|
||||
});
|
||||
});
|
||||
|
||||
const opts = url.parse(`http://127.0.0.1:${serverPort}/test`);
|
||||
opts.agent = sleepAgent;
|
||||
|
||||
http.get(opts, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
data = JSON.parse(data);
|
||||
assert.equal('/test', data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should not send a port number for the default port', function(done) {
|
||||
server.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
let proxy =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_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, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
data = JSON.parse(data);
|
||||
assert.equal('127.0.0.1', data.host);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "CommonJS",
|
||||
"target": "es2015",
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext"],
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"typeRoots": [
|
||||
"./@types",
|
||||
"./node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"name": "https-proxy-agent",
|
||||
"version": "5.0.0",
|
||||
"description": "An HTTP(s) proxy `http.Agent` implementation for HTTPS",
|
||||
"main": "dist/index",
|
||||
"types": "dist/index",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "tsc",
|
||||
"test": "mocha --reporter spec",
|
||||
"test-lint": "eslint src --ext .js,.ts",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/TooTallNate/node-https-proxy-agent.git"
|
||||
},
|
||||
"keywords": [
|
||||
"https",
|
||||
"proxy",
|
||||
"endpoint",
|
||||
"agent"
|
||||
],
|
||||
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/TooTallNate/node-https-proxy-agent/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "4",
|
||||
"@types/node": "^12.12.11",
|
||||
"@typescript-eslint/eslint-plugin": "1.6.0",
|
||||
"@typescript-eslint/parser": "1.1.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-airbnb": "17.1.0",
|
||||
"eslint-config-prettier": "4.1.0",
|
||||
"eslint-import-resolver-typescript": "1.1.1",
|
||||
"eslint-plugin-import": "2.16.0",
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-react": "7.12.4",
|
||||
"mocha": "^6.2.2",
|
||||
"proxy": "1",
|
||||
"rimraf": "^3.0.0",
|
||||
"typescript": "^3.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,215 @@
|
|||
import net from 'net';
|
||||
import tls from 'tls';
|
||||
import url from 'url';
|
||||
import assert from 'assert';
|
||||
import createDebug from 'debug';
|
||||
import { OutgoingHttpHeaders } from 'http';
|
||||
import { Agent, ClientRequest, RequestOptions } from 'agent-base';
|
||||
import { HttpsProxyAgentOptions } from '.';
|
||||
import parseProxyResponse from './parse-proxy-response';
|
||||
|
||||
const debug = createDebug('https-proxy-agent:agent');
|
||||
|
||||
/**
|
||||
* The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to
|
||||
* the specified "HTTP(s) proxy server" in order to proxy HTTPS requests.
|
||||
*
|
||||
* Outgoing HTTP requests are first tunneled through the proxy server using the
|
||||
* `CONNECT` HTTP request method to establish a connection to the proxy server,
|
||||
* and then the proxy server connects to the destination target and issues the
|
||||
* HTTP request from the proxy server.
|
||||
*
|
||||
* `https:` requests have their socket connection upgraded to TLS once
|
||||
* the connection to the proxy server has been established.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
export default class HttpsProxyAgent extends Agent {
|
||||
private secureProxy: boolean;
|
||||
private proxy: HttpsProxyAgentOptions;
|
||||
|
||||
constructor(_opts: string | HttpsProxyAgentOptions) {
|
||||
let opts: HttpsProxyAgentOptions;
|
||||
if (typeof _opts === 'string') {
|
||||
opts = url.parse(_opts);
|
||||
} else {
|
||||
opts = _opts;
|
||||
}
|
||||
if (!opts) {
|
||||
throw new Error(
|
||||
'an HTTP(S) proxy server `host` and `port` must be specified!'
|
||||
);
|
||||
}
|
||||
debug('creating new HttpsProxyAgent instance: %o', opts);
|
||||
super(opts);
|
||||
|
||||
const proxy: HttpsProxyAgentOptions = { ...opts };
|
||||
|
||||
// If `true`, then connect to the proxy server over TLS.
|
||||
// Defaults to `false`.
|
||||
this.secureProxy = opts.secureProxy || isHTTPS(proxy.protocol);
|
||||
|
||||
// Prefer `hostname` over `host`, and set the `port` if needed.
|
||||
proxy.host = proxy.hostname || proxy.host;
|
||||
if (typeof proxy.port === 'string') {
|
||||
proxy.port = parseInt(proxy.port, 10);
|
||||
}
|
||||
if (!proxy.port && proxy.host) {
|
||||
proxy.port = this.secureProxy ? 443 : 80;
|
||||
}
|
||||
|
||||
// ALPN is supported by Node.js >= v5.
|
||||
// attempt to negotiate http/1.1 for proxy servers that support http/2
|
||||
if (this.secureProxy && !('ALPNProtocols' in proxy)) {
|
||||
proxy.ALPNProtocols = ['http 1.1'];
|
||||
}
|
||||
|
||||
if (proxy.host && proxy.path) {
|
||||
// If both a `host` and `path` are specified then it's most likely
|
||||
// the result of a `url.parse()` call... we need to remove the
|
||||
// `path` portion so that `net.connect()` doesn't attempt to open
|
||||
// that as a Unix socket file.
|
||||
delete proxy.path;
|
||||
delete proxy.pathname;
|
||||
}
|
||||
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the node-core HTTP client library is creating a
|
||||
* new HTTP request.
|
||||
*
|
||||
* @api protected
|
||||
*/
|
||||
async callback(
|
||||
req: ClientRequest,
|
||||
opts: RequestOptions
|
||||
): Promise<net.Socket> {
|
||||
const { proxy, secureProxy } = this;
|
||||
|
||||
// Create a socket connection to the proxy server.
|
||||
let socket: net.Socket;
|
||||
if (secureProxy) {
|
||||
debug('Creating `tls.Socket`: %o', proxy);
|
||||
socket = tls.connect(proxy as tls.ConnectionOptions);
|
||||
} else {
|
||||
debug('Creating `net.Socket`: %o', proxy);
|
||||
socket = net.connect(proxy as net.NetConnectOpts);
|
||||
}
|
||||
|
||||
const headers: OutgoingHttpHeaders = { ...proxy.headers };
|
||||
const hostname = `${opts.host}:${opts.port}`;
|
||||
let payload = `CONNECT ${hostname} HTTP/1.1\r\n`;
|
||||
|
||||
// Inject the `Proxy-Authorization` header if necessary.
|
||||
if (proxy.auth) {
|
||||
headers['Proxy-Authorization'] = `Basic ${Buffer.from(
|
||||
proxy.auth
|
||||
).toString('base64')}`;
|
||||
}
|
||||
|
||||
// The `Host` header should only include the port
|
||||
// number when it is not the default port.
|
||||
let { host, port, secureEndpoint } = opts;
|
||||
if (!isDefaultPort(port, secureEndpoint)) {
|
||||
host += `:${port}`;
|
||||
}
|
||||
headers.Host = host;
|
||||
|
||||
headers.Connection = 'close';
|
||||
for (const name of Object.keys(headers)) {
|
||||
payload += `${name}: ${headers[name]}\r\n`;
|
||||
}
|
||||
|
||||
const proxyResponsePromise = parseProxyResponse(socket);
|
||||
|
||||
socket.write(`${payload}\r\n`);
|
||||
|
||||
const {
|
||||
statusCode,
|
||||
buffered
|
||||
} = await proxyResponsePromise;
|
||||
|
||||
if (statusCode === 200) {
|
||||
req.once('socket', resume);
|
||||
|
||||
if (opts.secureEndpoint) {
|
||||
const servername = opts.servername || opts.host;
|
||||
if (!servername) {
|
||||
throw new Error('Could not determine "servername"');
|
||||
}
|
||||
// The proxy is connecting to a TLS server, so upgrade
|
||||
// this socket connection to a TLS connection.
|
||||
debug('Upgrading socket connection to TLS');
|
||||
return tls.connect({
|
||||
...omit(opts, 'host', 'hostname', 'path', 'port'),
|
||||
socket,
|
||||
servername
|
||||
});
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
// Some other status code that's not 200... need to re-play the HTTP
|
||||
// header "data" events onto the socket once the HTTP machinery is
|
||||
// attached so that the node core `http` can parse and handle the
|
||||
// error status code.
|
||||
|
||||
// Close the original socket, and a new "fake" socket is returned
|
||||
// instead, so that the proxy doesn't get the HTTP request
|
||||
// written to it (which may contain `Authorization` headers or other
|
||||
// sensitive data).
|
||||
//
|
||||
// See: https://hackerone.com/reports/541502
|
||||
socket.destroy();
|
||||
|
||||
const fakeSocket = new net.Socket();
|
||||
fakeSocket.readable = true;
|
||||
|
||||
// Need to wait for the "socket" event to re-play the "data" events.
|
||||
req.once('socket', (s: net.Socket) => {
|
||||
debug('replaying proxy buffer for failed request');
|
||||
assert(s.listenerCount('data') > 0);
|
||||
|
||||
// Replay the "buffered" Buffer onto the fake `socket`, since at
|
||||
// this point the HTTP module machinery has been hooked up for
|
||||
// the user.
|
||||
s.push(buffered);
|
||||
s.push(null);
|
||||
});
|
||||
|
||||
return fakeSocket;
|
||||
}
|
||||
}
|
||||
|
||||
function resume(socket: net.Socket | tls.TLSSocket): void {
|
||||
socket.resume();
|
||||
}
|
||||
|
||||
function isDefaultPort(port: number, secure: boolean): boolean {
|
||||
return Boolean((!secure && port === 80) || (secure && port === 443));
|
||||
}
|
||||
|
||||
function isHTTPS(protocol?: string | null): boolean {
|
||||
return typeof protocol === 'string' ? /^https:?$/i.test(protocol) : false;
|
||||
}
|
||||
|
||||
function omit<T extends object, K extends [...(keyof T)[]]>(
|
||||
obj: T,
|
||||
...keys: K
|
||||
): {
|
||||
[K2 in Exclude<keyof T, K[number]>]: T[K2];
|
||||
} {
|
||||
const ret = {} as {
|
||||
[K in keyof typeof obj]: (typeof obj)[K];
|
||||
};
|
||||
let key: keyof typeof obj;
|
||||
for (key in obj) {
|
||||
if (!keys.includes(key)) {
|
||||
ret[key] = obj[key];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import net from 'net';
|
||||
import tls from 'tls';
|
||||
import { Url } from 'url';
|
||||
import { AgentOptions } from 'agent-base';
|
||||
import { OutgoingHttpHeaders } from 'http';
|
||||
import _HttpsProxyAgent from './agent';
|
||||
|
||||
function createHttpsProxyAgent(
|
||||
opts: string | createHttpsProxyAgent.HttpsProxyAgentOptions
|
||||
): _HttpsProxyAgent {
|
||||
return new _HttpsProxyAgent(opts);
|
||||
}
|
||||
|
||||
namespace createHttpsProxyAgent {
|
||||
interface BaseHttpsProxyAgentOptions {
|
||||
headers?: OutgoingHttpHeaders;
|
||||
secureProxy?: boolean;
|
||||
host?: string | null;
|
||||
path?: string | null;
|
||||
port?: string | number | null;
|
||||
}
|
||||
|
||||
export interface HttpsProxyAgentOptions
|
||||
extends AgentOptions,
|
||||
BaseHttpsProxyAgentOptions,
|
||||
Partial<
|
||||
Omit<
|
||||
Url & net.NetConnectOpts & tls.ConnectionOptions,
|
||||
keyof BaseHttpsProxyAgentOptions
|
||||
>
|
||||
> {}
|
||||
|
||||
export type HttpsProxyAgent = _HttpsProxyAgent;
|
||||
export const HttpsProxyAgent = _HttpsProxyAgent;
|
||||
|
||||
createHttpsProxyAgent.prototype = _HttpsProxyAgent.prototype;
|
||||
}
|
||||
|
||||
export = createHttpsProxyAgent;
|
|
@ -0,0 +1,82 @@
|
|||
import createDebug from 'debug';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
const debug = createDebug('https-proxy-agent:parse-proxy-response');
|
||||
|
||||
export interface ProxyResponse {
|
||||
statusCode: number;
|
||||
buffered: Buffer;
|
||||
}
|
||||
|
||||
export default function parseProxyResponse(
|
||||
socket: Readable
|
||||
): Promise<ProxyResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// we need to buffer any HTTP traffic that happens with the proxy before we get
|
||||
// the CONNECT response, so that if the response is anything other than an "200"
|
||||
// response code, then we can re-play the "data" events on the socket once the
|
||||
// HTTP parser is hooked up...
|
||||
let buffersLength = 0;
|
||||
const buffers: Buffer[] = [];
|
||||
|
||||
function read() {
|
||||
const b = socket.read();
|
||||
if (b) ondata(b);
|
||||
else socket.once('readable', read);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
socket.removeListener('end', onend);
|
||||
socket.removeListener('error', onerror);
|
||||
socket.removeListener('close', onclose);
|
||||
socket.removeListener('readable', read);
|
||||
}
|
||||
|
||||
function onclose(err?: Error) {
|
||||
debug('onclose had error %o', err);
|
||||
}
|
||||
|
||||
function onend() {
|
||||
debug('onend');
|
||||
}
|
||||
|
||||
function onerror(err: Error) {
|
||||
cleanup();
|
||||
debug('onerror %o', err);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
function ondata(b: Buffer) {
|
||||
buffers.push(b);
|
||||
buffersLength += b.length;
|
||||
|
||||
const buffered = Buffer.concat(buffers, buffersLength);
|
||||
const endOfHeaders = buffered.indexOf('\r\n\r\n');
|
||||
|
||||
if (endOfHeaders === -1) {
|
||||
// keep buffering
|
||||
debug('have not received end of HTTP headers yet...');
|
||||
read();
|
||||
return;
|
||||
}
|
||||
|
||||
const firstLine = buffered.toString(
|
||||
'ascii',
|
||||
0,
|
||||
buffered.indexOf('\r\n')
|
||||
);
|
||||
const statusCode = +firstLine.split(' ')[1];
|
||||
debug('got proxy server response: %o', firstLine);
|
||||
resolve({
|
||||
statusCode,
|
||||
buffered
|
||||
});
|
||||
}
|
||||
|
||||
socket.on('error', onerror);
|
||||
socket.on('close', onclose);
|
||||
socket.on('end', onend);
|
||||
|
||||
read();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQCzURxIqzer0ACAbX/lHdsn4Gd9PLKrf7EeDYfIdV0HZKPD8WDr
|
||||
bBx2/fBu0OW2sjnzv/SVZbJ0DAuPE/p0+eT0qb2qC10iz9iTD7ribd7gxhirVb8y
|
||||
b3fBjXsxc8V8p4Ny1LcvNSqCjwUbJqdRogfoJeTiqPM58z5sNzuv5iq7iwIDAQAB
|
||||
AoGAPMQy4olrP0UotlzlJ36bowLP70ffgHCwU+/f4NWs5fF78c3du0oSx1w820Dd
|
||||
Z7E0JF8bgnlJJTxjumPZz0RUCugrEHBKJmzEz3cxF5E3+7NvteZcjKn9D67RrM5x
|
||||
1/uSZ9cqKE9cYvY4fSuHx18diyZ4axR/wB1Pea2utjjDM+ECQQDb9ZbmmaWMiRpQ
|
||||
5Up+loxP7BZNPsEVsm+DVJmEFbaFgGfncWBqSIqnPNjMwTwj0OigTwCAEGPkfRVW
|
||||
T0pbYWCxAkEA0LK7SCTwzyDmhASUalk0x+3uCAA6ryFdwJf/wd8TRAvVOmkTEldX
|
||||
uJ7ldLvfrONYO3v56uKTU/SoNdZYzKtO+wJAX2KM4ctXYy5BXztPpr2acz4qHa1N
|
||||
Bh+vBAC34fOYhyQ76r3b1btHhWZ5jbFuZwm9F2erC94Ps5IaoqcX07DSwQJAPKGw
|
||||
h2U0EPkd/3zVIZCJJQya+vgWFIs9EZcXVtvYXQyTBkVApTN66MhBIYjzkub5205J
|
||||
bVQmOV37AKklY1DhwQJAA1wos0cYxro02edzatxd0DIR2r4qqOqLkw6BhYHhq6HJ
|
||||
ZvIcQkHqdSXzdETFc01I1znDGGIrJHcnvKWgBPoEUg==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,12 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIB1TCCAT4CCQDV5mPlzm9+izANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDEyQ3
|
||||
NTI3YmQ3Ny1hYjNlLTQ3NGItYWNlNy1lZWQ2MDUzOTMxZTcwHhcNMTUwNzA2MjI0
|
||||
NTA3WhcNMjUwNzAzMjI0NTA3WjAvMS0wKwYDVQQDEyQ3NTI3YmQ3Ny1hYjNlLTQ3
|
||||
NGItYWNlNy1lZWQ2MDUzOTMxZTcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
|
||||
ALNRHEirN6vQAIBtf+Ud2yfgZ308sqt/sR4Nh8h1XQdko8PxYOtsHHb98G7Q5bay
|
||||
OfO/9JVlsnQMC48T+nT55PSpvaoLXSLP2JMPuuJt3uDGGKtVvzJvd8GNezFzxXyn
|
||||
g3LUty81KoKPBRsmp1GiB+gl5OKo8znzPmw3O6/mKruLAgMBAAEwDQYJKoZIhvcN
|
||||
AQEFBQADgYEACzoHUF8UV2Z6541Q2wKEA0UFUzmUjf/E1XwBO+1P15ZZ64uw34B4
|
||||
1RwMPtAo9RY/PmICTWtNxWGxkzwb2JtDWtnxVER/lF8k2XcXPE76fxTHJF/BKk9J
|
||||
QU8OTD1dd9gHCBviQB9TqntRZ5X7axjtuWjb2umY+owBYzAHZkp1HKI=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,410 @@
|
|||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
let fs = require('fs');
|
||||
let url = require('url');
|
||||
let http = require('http');
|
||||
let https = require('https');
|
||||
let assert = require('assert');
|
||||
let Proxy = require('proxy');
|
||||
let HttpsProxyAgent = require('../');
|
||||
|
||||
describe('HttpsProxyAgent', function() {
|
||||
let server;
|
||||
let serverPort;
|
||||
|
||||
let sslServer;
|
||||
let sslServerPort;
|
||||
|
||||
let proxy;
|
||||
let proxyPort;
|
||||
|
||||
let sslProxy;
|
||||
let sslProxyPort;
|
||||
|
||||
before(function(done) {
|
||||
// setup target HTTP server
|
||||
server = http.createServer();
|
||||
server.listen(function() {
|
||||
serverPort = server.address().port;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
before(function(done) {
|
||||
// setup HTTP proxy server
|
||||
proxy = Proxy();
|
||||
proxy.listen(function() {
|
||||
proxyPort = proxy.address().port;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
before(function(done) {
|
||||
// setup target HTTPS server
|
||||
let options = {
|
||||
key: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.key`),
|
||||
cert: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.pem`)
|
||||
};
|
||||
sslServer = https.createServer(options);
|
||||
sslServer.listen(function() {
|
||||
sslServerPort = sslServer.address().port;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
before(function(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 = Proxy(https.createServer(options));
|
||||
sslProxy.listen(function() {
|
||||
sslProxyPort = sslProxy.address().port;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// shut down test HTTP server
|
||||
after(function(done) {
|
||||
server.once('close', function() {
|
||||
done();
|
||||
});
|
||||
server.close();
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
proxy.once('close', function() {
|
||||
done();
|
||||
});
|
||||
proxy.close();
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
sslServer.once('close', function() {
|
||||
done();
|
||||
});
|
||||
sslServer.close();
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
sslProxy.once('close', function() {
|
||||
done();
|
||||
});
|
||||
sslProxy.close();
|
||||
});
|
||||
|
||||
describe('constructor', function() {
|
||||
it('should throw an Error if no "proxy" argument is given', function() {
|
||||
assert.throws(function() {
|
||||
new HttpsProxyAgent();
|
||||
});
|
||||
});
|
||||
it('should accept a "string" proxy argument', function() {
|
||||
let agent = new HttpsProxyAgent(`http://localhost:${proxyPort}`);
|
||||
assert.equal('localhost', agent.proxy.host);
|
||||
assert.equal(proxyPort, agent.proxy.port);
|
||||
});
|
||||
it('should accept a `url.parse()` result object argument', function() {
|
||||
let opts = url.parse(`http://localhost:${proxyPort}`);
|
||||
let agent = new HttpsProxyAgent(opts);
|
||||
assert.equal('localhost', agent.proxy.host);
|
||||
assert.equal(proxyPort, agent.proxy.port);
|
||||
});
|
||||
describe('secureProxy', function() {
|
||||
it('should default to `false`', function() {
|
||||
let agent = new HttpsProxyAgent({ port: proxyPort });
|
||||
assert.equal(false, agent.secureProxy);
|
||||
});
|
||||
it('should be `false` when "http:" protocol is used', function() {
|
||||
let agent = new HttpsProxyAgent({
|
||||
port: proxyPort,
|
||||
protocol: 'http:'
|
||||
});
|
||||
assert.equal(false, agent.secureProxy);
|
||||
});
|
||||
it('should be `true` when "https:" protocol is used', function() {
|
||||
let agent = new HttpsProxyAgent({
|
||||
port: proxyPort,
|
||||
protocol: 'https:'
|
||||
});
|
||||
assert.equal(true, agent.secureProxy);
|
||||
});
|
||||
it('should be `true` when "https" protocol is used', function() {
|
||||
let agent = new HttpsProxyAgent({
|
||||
port: proxyPort,
|
||||
protocol: 'https'
|
||||
});
|
||||
assert.equal(true, agent.secureProxy);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"http" module', function() {
|
||||
beforeEach(function() {
|
||||
delete proxy.authenticate;
|
||||
});
|
||||
|
||||
it('should work over an HTTP proxy', function(done) {
|
||||
server.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
`http://localhost:${proxyPort}`;
|
||||
let agent = new HttpsProxyAgent(proxy);
|
||||
|
||||
let opts = url.parse(`http://localhost:${serverPort}`);
|
||||
opts.agent = agent;
|
||||
|
||||
let req = http.get(opts, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
data = JSON.parse(data);
|
||||
assert.equal(`localhost:${serverPort}`, data.host);
|
||||
done();
|
||||
});
|
||||
});
|
||||
req.once('error', done);
|
||||
});
|
||||
it('should work over an HTTPS proxy', function(done) {
|
||||
server.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy ||
|
||||
`https://localhost:${sslProxyPort}`;
|
||||
proxy = url.parse(proxy);
|
||||
proxy.rejectUnauthorized = false;
|
||||
let agent = new HttpsProxyAgent(proxy);
|
||||
|
||||
let opts = url.parse(`http://localhost:${serverPort}`);
|
||||
opts.agent = agent;
|
||||
|
||||
http.get(opts, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
data = JSON.parse(data);
|
||||
assert.equal(`localhost:${serverPort}`, data.host);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should receive the 407 authorization code on the `http.ClientResponse`', function(done) {
|
||||
// set a proxy authentication function for this test
|
||||
proxy.authenticate = function(req, fn) {
|
||||
// reject all requests
|
||||
fn(null, false);
|
||||
};
|
||||
|
||||
let proxyUri =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
`http://localhost:${proxyPort}`;
|
||||
let agent = new HttpsProxyAgent(proxyUri);
|
||||
|
||||
let opts = {};
|
||||
// `host` and `port` don't really matter since the proxy will reject anyways
|
||||
opts.host = 'localhost';
|
||||
opts.port = 80;
|
||||
opts.agent = agent;
|
||||
|
||||
let req = http.get(opts, function(res) {
|
||||
assert.equal(407, res.statusCode);
|
||||
assert('proxy-authenticate' in res.headers);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should not error if the proxy responds with 407 and the request is aborted', function(done) {
|
||||
proxy.authenticate = function(req, fn) {
|
||||
fn(null, false);
|
||||
};
|
||||
|
||||
const proxyUri =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
`http://localhost:${proxyPort}`;
|
||||
|
||||
const req = http.get(
|
||||
{
|
||||
agent: new HttpsProxyAgent(proxyUri)
|
||||
},
|
||||
function(res) {
|
||||
assert.equal(407, res.statusCode);
|
||||
req.abort();
|
||||
}
|
||||
);
|
||||
|
||||
req.on('abort', done);
|
||||
});
|
||||
it('should emit an "end" event on the `http.IncomingMessage` if the proxy responds with non-200 status code', function(done) {
|
||||
proxy.authenticate = function(req, fn) {
|
||||
fn(null, false);
|
||||
};
|
||||
|
||||
const proxyUri =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
`http://localhost:${proxyPort}`;
|
||||
|
||||
const req = http.get(
|
||||
{
|
||||
agent: new HttpsProxyAgent(proxyUri)
|
||||
},
|
||||
function(res) {
|
||||
assert.equal(407, res.statusCode);
|
||||
|
||||
res.resume();
|
||||
res.on('end', done);
|
||||
}
|
||||
);
|
||||
});
|
||||
it('should emit an "error" event on the `http.ClientRequest` if the proxy does not exist', function(done) {
|
||||
// port 4 is a reserved, but "unassigned" port
|
||||
let proxyUri = 'http://localhost:4';
|
||||
let agent = new HttpsProxyAgent(proxyUri);
|
||||
|
||||
let opts = url.parse('http://nodejs.org');
|
||||
opts.agent = agent;
|
||||
|
||||
let req = http.get(opts);
|
||||
req.once('error', function(err) {
|
||||
assert.equal('ECONNREFUSED', err.code);
|
||||
req.abort();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow custom proxy "headers"', function(done) {
|
||||
server.once('connect', function(req, socket, head) {
|
||||
assert.equal('CONNECT', req.method);
|
||||
assert.equal('bar', req.headers.foo);
|
||||
socket.destroy();
|
||||
done();
|
||||
});
|
||||
|
||||
let uri = `http://localhost:${serverPort}`;
|
||||
let proxyOpts = url.parse(uri);
|
||||
proxyOpts.headers = {
|
||||
Foo: 'bar'
|
||||
};
|
||||
let agent = new HttpsProxyAgent(proxyOpts);
|
||||
|
||||
let opts = {};
|
||||
// `host` and `port` don't really matter since the proxy will reject anyways
|
||||
opts.host = 'localhost';
|
||||
opts.port = 80;
|
||||
opts.agent = agent;
|
||||
|
||||
http.get(opts);
|
||||
});
|
||||
});
|
||||
|
||||
describe('"https" module', function() {
|
||||
it('should work over an HTTP proxy', function(done) {
|
||||
sslServer.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTP_PROXY ||
|
||||
process.env.http_proxy ||
|
||||
`http://localhost:${proxyPort}`;
|
||||
let agent = new HttpsProxyAgent(proxy);
|
||||
|
||||
let opts = url.parse(`https://localhost:${sslServerPort}`);
|
||||
opts.rejectUnauthorized = false;
|
||||
opts.agent = agent;
|
||||
|
||||
https.get(opts, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
data = JSON.parse(data);
|
||||
assert.equal(`localhost:${sslServerPort}`, data.host);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work over an HTTPS proxy', function(done) {
|
||||
sslServer.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy ||
|
||||
`https://localhost:${sslProxyPort}`;
|
||||
proxy = url.parse(proxy);
|
||||
proxy.rejectUnauthorized = false;
|
||||
let agent = new HttpsProxyAgent(proxy);
|
||||
|
||||
let opts = url.parse(`https://localhost:${sslServerPort}`);
|
||||
opts.agent = agent;
|
||||
opts.rejectUnauthorized = false;
|
||||
|
||||
https.get(opts, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
data = JSON.parse(data);
|
||||
assert.equal(`localhost:${sslServerPort}`, data.host);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send a port number for the default port', function(done) {
|
||||
sslServer.once('request', function(req, res) {
|
||||
res.end(JSON.stringify(req.headers));
|
||||
});
|
||||
|
||||
let proxy =
|
||||
process.env.HTTPS_PROXY ||
|
||||
process.env.https_proxy ||
|
||||
`https://localhost:${sslProxyPort}`;
|
||||
proxy = url.parse(proxy);
|
||||
proxy.rejectUnauthorized = false;
|
||||
let agent = new HttpsProxyAgent(proxy);
|
||||
agent.defaultPort = sslServerPort;
|
||||
|
||||
let opts = url.parse(`https://localhost:${sslServerPort}`);
|
||||
opts.agent = agent;
|
||||
opts.rejectUnauthorized = false;
|
||||
|
||||
https.get(opts, function(res) {
|
||||
let data = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(b) {
|
||||
data += b;
|
||||
});
|
||||
res.on('end', function() {
|
||||
data = JSON.parse(data);
|
||||
assert.equal('localhost', data.host);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{*.json,*.json.example,*.gyp,*.yml,*.yaml,*.workflow}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{*.py,*.asm}]
|
||||
indent_style = space
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[*.asm]
|
||||
indent_size = 8
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Ideal settings - some plugins might support these.
|
||||
[*.js]
|
||||
quote_type = single
|
||||
|
||||
[{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}]
|
||||
curly_bracket_next_line = false
|
||||
spaces_around_operators = true
|
||||
spaces_around_brackets = outside
|
||||
# close enough to 1TB
|
||||
indent_brace_style = K&R
|
|
@ -0,0 +1,40 @@
|
|||
name: Node CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Test Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node-version: [10.x, 12.x, 14.x, 16.x]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Print Node.js Version
|
||||
run: node --version
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Run "build" step
|
||||
run: npm run build --if-present
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
env:
|
||||
CI: true
|
|
@ -0,0 +1,6 @@
|
|||
/yarn.lock
|
||||
/node_modules
|
||||
/src/?.?s
|
||||
/dist
|
||||
/.vscode
|
||||
/*.tgz
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Nathan Rajlich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,93 @@
|
|||
# @tootallnate/once
|
||||
|
||||
### Creates a Promise that waits for a single event
|
||||
|
||||
## Installation
|
||||
|
||||
Install with `npm`:
|
||||
|
||||
```bash
|
||||
$ npm install @tootallnate/once
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### once(emitter: EventEmitter, name: string, opts?: OnceOptions): Promise<[...Args]>
|
||||
|
||||
Creates a Promise that waits for event `name` to occur on `emitter`, and resolves
|
||||
the promise with an array of the values provided to the event handler. If an
|
||||
`error` event occurs before the event specified by `name`, then the Promise is
|
||||
rejected with the error argument.
|
||||
|
||||
```typescript
|
||||
import once from '@tootallnate/once';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
setTimeout(() => {
|
||||
emitter.emit('foo', 'bar');
|
||||
}, 100);
|
||||
|
||||
const [result] = await once(emitter, 'foo');
|
||||
console.log({ result });
|
||||
// { result: 'bar' }
|
||||
```
|
||||
|
||||
#### Promise Strong Typing
|
||||
|
||||
The main feature that this module provides over other "once" implementations is that
|
||||
the Promise that is returned is _**strongly typed**_ based on the type of `emitter`
|
||||
and the `name` of the event. Some examples are shown below.
|
||||
|
||||
_The process "exit" event contains a single number for exit code:_
|
||||
|
||||
```typescript
|
||||
const [code] = await once(process, 'exit');
|
||||
// ^ number
|
||||
```
|
||||
_A child process "exit" event contains either an exit code or a signal:_
|
||||
|
||||
```typescript
|
||||
const child = spawn('echo', []);
|
||||
const [code, signal] = await once(child, 'exit');
|
||||
// ^ number | null
|
||||
// ^ string | null
|
||||
```
|
||||
|
||||
_A forked child process "message" event is type `any`, so you can cast the Promise directly:_
|
||||
|
||||
```typescript
|
||||
const child = fork('file.js');
|
||||
|
||||
// With `await`
|
||||
const [message, _]: [WorkerPayload, unknown] = await once(child, 'message');
|
||||
|
||||
// With Promise
|
||||
const messagePromise: Promise<[WorkerPayload, unknown]> = once(child, 'message');
|
||||
|
||||
// Better yet would be to leave it as `any`, and validate the payload
|
||||
// at runtime with i.e. `ajv` + `json-schema-to-typescript`
|
||||
```
|
||||
|
||||
_If the TypeScript definition does not contain an overload for the specified event name, then the Promise will have type `unknown[]` and your code will need to narrow the result manually:_
|
||||
|
||||
```typescript
|
||||
interface CustomEmitter extends EventEmitter {
|
||||
on(name: 'foo', listener: (a: string, b: number) => void): this;
|
||||
}
|
||||
|
||||
const emitter: CustomEmitter = new EventEmitter();
|
||||
|
||||
// "foo" event is a defined overload, so it's properly typed
|
||||
const fooPromise = once(emitter, 'foo');
|
||||
// ^ Promise<[a: string, b: number]>
|
||||
|
||||
// "bar" event in not a defined overload, so it gets `unknown[]`
|
||||
const barPromise = once(emitter, 'bar');
|
||||
// ^ Promise<unknown[]>
|
||||
```
|
||||
|
||||
### OnceOptions
|
||||
|
||||
- `signal` - `AbortSignal` instance to unbind event handlers before the Promise has been fulfilled.
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "@tootallnate/once",
|
||||
"version": "3.0.0",
|
||||
"description": "Creates a Promise that waits for a single event",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/TooTallNate/once.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/TooTallNate/once/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/node": "^12.12.11",
|
||||
"abort-controller": "^3.0.0",
|
||||
"jest": "^27.2.1",
|
||||
"rimraf": "^3.0.0",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"diagnostics": false,
|
||||
"isolatedModules": true
|
||||
}
|
||||
},
|
||||
"verbose": false,
|
||||
"testEnvironment": "node",
|
||||
"testMatch": [
|
||||
"<rootDir>/test/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* This script generated a TypeScript file that defines
|
||||
* a type based on this StackOverflow answer:
|
||||
* https://stackoverflow.com/a/52761156/376773
|
||||
*/
|
||||
|
||||
process.stdout.write('export type OverloadedParameters<T> = \n');
|
||||
|
||||
const overloads = parseInt(process.argv[2], 10) || 5;
|
||||
|
||||
for (let i = overloads; i > 0; i--) {
|
||||
process.stdout.write(`\tT extends { `);
|
||||
for (let j = 1; j <= i; j++) {
|
||||
process.stdout.write(
|
||||
`(...args: infer A${j}): any; `
|
||||
);
|
||||
}
|
||||
process.stdout.write(`} ? `);
|
||||
for (let j = 1; j <= i; j++) {
|
||||
process.stdout.write(`A${j} `);
|
||||
if (j < i) {
|
||||
process.stdout.write(`| `);
|
||||
}
|
||||
}
|
||||
process.stdout.write(`:\n`);
|
||||
}
|
||||
|
||||
process.stdout.write(`\tany;\n`);
|
|
@ -0,0 +1,34 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import { EventNames, EventListenerParameters, AbortSignal } from './types.js';
|
||||
|
||||
export interface OnceOptions {
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export default function once<
|
||||
Emitter extends EventEmitter,
|
||||
Event extends EventNames<Emitter>
|
||||
>(
|
||||
emitter: Emitter,
|
||||
name: Event,
|
||||
{ signal }: OnceOptions = {}
|
||||
): Promise<EventListenerParameters<Emitter, Event>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
function cleanup() {
|
||||
signal?.removeEventListener('abort', cleanup);
|
||||
emitter.removeListener(name, onEvent);
|
||||
emitter.removeListener('error', onError);
|
||||
}
|
||||
function onEvent(...args: EventListenerParameters<Emitter, Event>) {
|
||||
cleanup();
|
||||
resolve(args);
|
||||
}
|
||||
function onError(err: Error) {
|
||||
cleanup();
|
||||
reject(err);
|
||||
}
|
||||
signal?.addEventListener('abort', cleanup);
|
||||
emitter.on(name, onEvent);
|
||||
emitter.on('error', onError);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
export type OverloadedParameters<T> =
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; (...args: infer A13): any; (...args: infer A14): any; (...args: infer A15): any; (...args: infer A16): any; (...args: infer A17): any; (...args: infer A18): any; (...args: infer A19): any; (...args: infer A20): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | A13 | A14 | A15 | A16 | A17 | A18 | A19 | A20 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; (...args: infer A13): any; (...args: infer A14): any; (...args: infer A15): any; (...args: infer A16): any; (...args: infer A17): any; (...args: infer A18): any; (...args: infer A19): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | A13 | A14 | A15 | A16 | A17 | A18 | A19 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; (...args: infer A13): any; (...args: infer A14): any; (...args: infer A15): any; (...args: infer A16): any; (...args: infer A17): any; (...args: infer A18): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | A13 | A14 | A15 | A16 | A17 | A18 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; (...args: infer A13): any; (...args: infer A14): any; (...args: infer A15): any; (...args: infer A16): any; (...args: infer A17): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | A13 | A14 | A15 | A16 | A17 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; (...args: infer A13): any; (...args: infer A14): any; (...args: infer A15): any; (...args: infer A16): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | A13 | A14 | A15 | A16 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; (...args: infer A13): any; (...args: infer A14): any; (...args: infer A15): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | A13 | A14 | A15 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; (...args: infer A13): any; (...args: infer A14): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | A13 | A14 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; (...args: infer A13): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 | A13 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; (...args: infer A12): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 | A12 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; (...args: infer A11): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 | A11 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; (...args: infer A10): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 | A10 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; (...args: infer A9): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 | A9 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; (...args: infer A8): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 | A8 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; (...args: infer A7): any; } ? A1 | A2 | A3 | A4 | A5 | A6 | A7 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; (...args: infer A6): any; } ? A1 | A2 | A3 | A4 | A5 | A6 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; (...args: infer A5): any; } ? A1 | A2 | A3 | A4 | A5 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; (...args: infer A4): any; } ? A1 | A2 | A3 | A4 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; (...args: infer A3): any; } ? A1 | A2 | A3 :
|
||||
T extends { (...args: infer A1): any; (...args: infer A2): any; } ? A1 | A2 :
|
||||
T extends { (...args: infer A1): any; } ? A1 :
|
||||
any;
|
|
@ -0,0 +1,38 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import { OverloadedParameters } from './overloaded-parameters';
|
||||
|
||||
export type FirstParameter<T> = T extends [infer R, ...any[]] ? R : never;
|
||||
|
||||
export type EventListener<F, T extends string | symbol> = F extends [
|
||||
T,
|
||||
infer R,
|
||||
...any[]
|
||||
]
|
||||
? R
|
||||
: never;
|
||||
|
||||
export type EventParameters<
|
||||
Emitter extends EventEmitter
|
||||
> = OverloadedParameters<Emitter['on']>;
|
||||
|
||||
export type EventNames<Emitter extends EventEmitter> = FirstParameter<
|
||||
EventParameters<Emitter>
|
||||
>;
|
||||
|
||||
export type EventListenerParameters<
|
||||
Emitter extends EventEmitter,
|
||||
Event extends EventNames<Emitter>
|
||||
> = WithDefault<
|
||||
Parameters<EventListener<EventParameters<Emitter>, Event>>,
|
||||
unknown[]
|
||||
>;
|
||||
|
||||
export type WithDefault<T, D> = [T] extends [never] ? D : T;
|
||||
|
||||
export interface AbortSignal {
|
||||
addEventListener: (name: string, listener: (...args: any[]) => any) => void;
|
||||
removeEventListener: (
|
||||
name: string,
|
||||
listener: (...args: any[]) => any
|
||||
) => void;
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
import { spawn } from 'child_process';
|
||||
import { EventEmitter } from 'events';
|
||||
import { AbortController } from 'abort-controller';
|
||||
import once from '../src';
|
||||
|
||||
describe('once()', () => {
|
||||
it('should work with vanilla EventEmitter', async () => {
|
||||
const emitter = new EventEmitter();
|
||||
const promise = once(emitter, 'foo');
|
||||
emitter.emit('foo', 'bar');
|
||||
const [foo] = await promise;
|
||||
expect(foo).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should work with vanilla EventEmitter - nextTick', async () => {
|
||||
const ee = new EventEmitter();
|
||||
process.nextTick(() => {
|
||||
ee.emit('myevent', 42);
|
||||
});
|
||||
const [value] = await once(ee, 'myevent');
|
||||
expect(value).toEqual(42);
|
||||
});
|
||||
|
||||
it('should reject Promise upon "error" event', async () => {
|
||||
let err: Error | null = null;
|
||||
const emitter = new EventEmitter();
|
||||
const promise = once(emitter, 'foo').catch((_err) => {
|
||||
err = _err;
|
||||
});
|
||||
emitter.emit('error', new Error('test'));
|
||||
await promise;
|
||||
expect(err!.message).toEqual('test');
|
||||
});
|
||||
|
||||
it('should reject Promise upon "error" event - nextTick', async () => {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
const err = new Error('kaboom');
|
||||
process.nextTick(() => {
|
||||
ee.emit('error', err);
|
||||
});
|
||||
|
||||
try {
|
||||
await once(ee, 'myevent');
|
||||
throw new Error('Should not happen');
|
||||
} catch (err: any) {
|
||||
expect(err.message).toEqual('kaboom');
|
||||
}
|
||||
});
|
||||
|
||||
it('should work with interface extending EventEmitter with overload', async () => {
|
||||
interface TestEmitter extends EventEmitter {
|
||||
on(name: 'foo', listener: (a: string, b: number) => void): this;
|
||||
}
|
||||
|
||||
const emitter: TestEmitter = new EventEmitter();
|
||||
const promise = once(emitter, 'foo');
|
||||
emitter.emit('foo', 'bar', 4);
|
||||
const [a, b] = await promise;
|
||||
expect(a).toEqual('bar');
|
||||
expect(b).toEqual(4);
|
||||
});
|
||||
|
||||
it('should allow casting from an `any` param', async () => {
|
||||
interface TestEmitter extends EventEmitter {
|
||||
on(name: 'foo', listener: (a: any, b: number) => void): this;
|
||||
}
|
||||
|
||||
const emitter: TestEmitter = new EventEmitter();
|
||||
const promise: Promise<[string, unknown]> = once(emitter, 'foo');
|
||||
emitter.emit('foo', 'bar', 4);
|
||||
const [a] = await promise;
|
||||
// TypeScript will fail if `a` is not `string` type
|
||||
expect(a.toUpperCase()).toEqual('BAR');
|
||||
});
|
||||
|
||||
it('should work with ChildProcess "exit" event', async () => {
|
||||
const child = spawn('echo', ['hi']);
|
||||
const [code, signal] = await once(child, 'exit');
|
||||
expect(code).toEqual(0);
|
||||
expect(signal).toBeNull();
|
||||
});
|
||||
|
||||
it('should be abortable with `AbortController`', async () => {
|
||||
let wasResolved = false;
|
||||
const emitter = new EventEmitter();
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
const onResolve = () => {
|
||||
wasResolved = true;
|
||||
};
|
||||
once(emitter, 'foo', { signal }).then(onResolve, onResolve);
|
||||
|
||||
// First time without `abort()`, so it will be fulfilled
|
||||
emitter.emit('foo');
|
||||
|
||||
// Promise is fulfilled on next tick, so wait a bit
|
||||
await new Promise((r) => process.nextTick(r));
|
||||
|
||||
expect(wasResolved).toEqual(true);
|
||||
|
||||
// Reset
|
||||
wasResolved = false;
|
||||
|
||||
once(emitter, 'foo', { signal }).then(onResolve, onResolve);
|
||||
|
||||
// This time abort
|
||||
controller.abort();
|
||||
emitter.emit('foo');
|
||||
|
||||
// Promise is fulfilled on next tick, so wait a bit
|
||||
await new Promise((r) => process.nextTick(r));
|
||||
|
||||
expect(wasResolved).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"include": ["**/*.test.ts"]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"module": "ES2020",
|
||||
"target": "ES2019",
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ES2019"],
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"typeRoots": [
|
||||
"./@types",
|
||||
"./node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "CommonJS",
|
||||
"target": "es2015",
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext"],
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"typeRoots": [
|
||||
"./@types",
|
||||
"./node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue