docs: add quixzoom-auth-core product to AAMOS

- Product documentation in docs/products/
- Updated MEMORY.md with product info
- quiXzoom Auth Core as AAMOS Identity product
This commit is contained in:
Bernt
2026-07-14 09:58:53 +00:00
parent 58ca4e68db
commit 48ea61cdcc
2730 changed files with 293827 additions and 7213 deletions
+24
View File
@@ -0,0 +1,24 @@
Copyright (c) 2026, Gajus Kuizinas (https://gajus.com/)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Gajus Kuizinas (https://gajus.com/) nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ANUARY BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+316
View File
@@ -0,0 +1,316 @@
# global-agent
[![NPM version](http://img.shields.io/npm/v/global-agent.svg?style=flat-square)](https://www.npmjs.org/package/global-agent)
[![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)
[![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas)
Global HTTP/HTTPS proxy configurable using environment variables.
* [Usage](#usage)
* [Setup proxy using `global-agent/bootstrap`](#setup-proxy-using-global-agentbootstrap)
* [Setup proxy using `bootstrap` routine](#setup-proxy-using-bootstrap-routine)
* [Runtime configuration](#runtime-configuration)
* [Exclude URLs](#exclude-urls)
* [API](#api)
* [`createGlobalProxyAgent`](#createglobalproxyagent)
* [Environment variables](#environment-variables)
* [`global.GLOBAL_AGENT`](#globalglobal_agent)
* [Supported libraries](#supported-libraries)
* [FAQ](#faq)
* [What is the reason `global-agent` overrides explicitly configured HTTP(S) agent?](#what-is-the-reason-global-agent-overrides-explicitly-configured-https-agent)
* [What is the reason `global-agent/bootstrap` does not use `HTTP_PROXY`?](#what-is-the-reason-global-agentbootstrap-does-not-use-http_proxy)
* [What is the difference from `global-tunnel` and `tunnel`?](#what-is-the-difference-from-global-tunnel-and-tunnel)
## Usage
### Setup proxy using `global-agent/bootstrap`
To configure HTTP proxy:
1. Import `global-agent/bootstrap`.
1. Export HTTP proxy address as `GLOBAL_AGENT_HTTP_PROXY` environment variable.
Code:
```js
import 'global-agent/bootstrap';
// or:
// import {bootstrap} from 'global-agent';
// bootstrap();
```
Bash:
```bash
$ export GLOBAL_AGENT_HTTP_PROXY=http://127.0.0.1:8080
```
Alternatively, you can preload module using Node.js `--require, -r` configuration, e.g.
```bash
$ export GLOBAL_AGENT_HTTP_PROXY=http://127.0.0.1:8080
$ node -r 'global-agent/bootstrap' your-script.js
```
### Setup proxy using `bootstrap` routine
Instead of importing a self-initialising script with side-effects as demonstrated in the [setup proxy using `global-agent/bootstrap`](#setup-proxy-using-global-agentbootstrap) documentation, you can import `bootstrap` routine and explicitly evaluate the bootstrap logic, e.g.
```js
import {
bootstrap
} from 'global-agent';
bootstrap();
```
This is useful if you need to conditionally bootstrap `global-agent`, e.g.
```js
import {
bootstrap
} from 'global-agent';
import globalTunnel from 'global-tunnel-ng';
const MAJOR_NODEJS_VERSION = parseInt(process.version.slice(1).split('.')[0], 10);
if (MAJOR_NODEJS_VERSION >= 10) {
// `global-agent` works with Node.js v10 and above.
bootstrap();
} else {
// `global-tunnel-ng` works only with Node.js v10 and below.
globalTunnel.initialize();
}
```
### Setup proxy using `createGlobalProxyAgent`
If you do not want to use `global.GLOBAL_AGENT` variable, then you can use `createGlobalProxyAgent` to instantiate a controlled instance of `global-agent`, e.g.
```js
import {
createGlobalProxyAgent
} from 'global-agent';
const globalProxyAgent = createGlobalProxyAgent();
```
Unlike `bootstrap` routine, `createGlobalProxyAgent` factory does not create `global.GLOBAL_AGENT` variable and does not guard against multiple initializations of `global-agent`. The result object of `createGlobalProxyAgent` is equivalent to `global.GLOBAL_AGENT`.
### Runtime configuration
`global-agent/bootstrap` script copies `process.env.GLOBAL_AGENT_HTTP_PROXY` value to `global.GLOBAL_AGENT.HTTP_PROXY` and continues to use the latter variable.
You can override the `global.GLOBAL_AGENT.HTTP_PROXY` value at runtime to change proxy behaviour, e.g.
```js
http.get('http://127.0.0.1:8000');
global.GLOBAL_AGENT.HTTP_PROXY = 'http://127.0.0.1:8001';
http.get('http://127.0.0.1:8000');
global.GLOBAL_AGENT.HTTP_PROXY = 'http://127.0.0.1:8002';
```
The first HTTP request is going to use http://127.0.0.1:8001 proxy and the second request is going to use http://127.0.0.1:8002.
All `global-agent` configuration is available under `global.GLOBAL_AGENT` namespace.
### Exclude URLs
The `GLOBAL_AGENT_NO_PROXY` environment variable specifies a pattern of URLs that should be excluded from proxying. `GLOBAL_AGENT_NO_PROXY` value is a comma-separated list of domain names. Asterisks can be used as wildcards, e.g.
```bash
export GLOBAL_AGENT_NO_PROXY='*.foo.com,baz.com'
```
says to contact all machines with the 'foo.com' TLD and 'baz.com' domains directly.
### Separate proxy for HTTPS
The environment variable `GLOBAL_AGENT_HTTPS_PROXY` can be set to specify a separate proxy for HTTPS requests. When this variable is not set `GLOBAL_AGENT_HTTP_PROXY` is used for both HTTP and HTTPS requests.
## API
### `createGlobalProxyAgent`
```js
/**
* @property environmentVariableNamespace Defines namespace of `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables. (Default: `GLOBAL_AGENT_`)
* @property forceGlobalAgent Forces to use `global-agent` HTTP(S) agent even when request was explicitly constructed with another agent. (Default: `true`)
* @property socketConnectionTimeout Destroys socket if connection is not established within the timeout. (Default: `60000`)
* @property ca Single CA certificate or an array of CA certificates that is trusted for secure connections to the registry.
* @property logger Custom logger instance for debug logging. Must implement `child`, `debug`, `error`, `info`, `trace`, and `warn` methods.
*/
type ProxyAgentConfigurationInputType = {|
+environmentVariableNamespace?: string,
+forceGlobalAgent?: boolean,
+socketConnectionTimeout?: number,
+ca?: string[] | string,
+logger?: Logger,
|};
(configurationInput: ProxyAgentConfigurationInputType) => ProxyAgentConfigurationType;
```
### Custom Logger
You can provide a custom logger to `global-agent` for debugging purposes. The logger must implement the following interface:
```ts
type Logger = {
child: (context: object) => Logger,
debug: (context: object | string, message?: string) => void,
error: (context: object | string, message?: string) => void,
info: (context: object | string, message?: string) => void,
trace: (context: object | string, message?: string) => void,
warn: (context: object | string, message?: string) => void,
};
```
Example using a custom logger:
```js
import { createGlobalProxyAgent } from 'global-agent';
createGlobalProxyAgent({
logger: {
child: () => logger,
debug: console.debug,
error: console.error,
info: console.info,
trace: console.trace,
warn: console.warn,
},
});
```
### Environment variables
|Name|Description|Default|
|---|---|---|
|`GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE`|Defines namespace of `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables.|`GLOBAL_AGENT_`|
|`GLOBAL_AGENT_FORCE_GLOBAL_AGENT`|Forces to use `global-agent` HTTP(S) agent even when request was explicitly constructed with another agent.|`true`|
|`GLOBAL_AGENT_SOCKET_CONNECTION_TIMEOUT`|Destroys socket if connection is not established within the timeout.|`60000`|
|`${NAMESPACE}HTTP_PROXY`|Sets the initial proxy controller HTTP_PROXY value.|N/A|
|`${NAMESPACE}HTTPS_PROXY`|Sets the initial proxy controller HTTPS_PROXY value.|N/A|
|`${NAMESPACE}NO_PROXY`|Sets the initial proxy controller NO_PROXY value.|N/A|
### `global.GLOBAL_AGENT`
`global.GLOBAL_AGENT` is initialized by `bootstrap` routine.
`global.GLOBAL_AGENT` has the following properties:
|Name|Configurable|Description|
|---|---|---|
|`HTTP_PROXY`|Yes|Sets HTTP proxy to use.|
|`HTTPS_PROXY`|Yes|Sets a distinct proxy to use for HTTPS requests.|
|`NO_PROXY`|Yes|Specifies a pattern of URLs that should be excluded from proxying. See [Exclude URLs](#exclude-urls).|
## Certificate Authority (CA)
### `addCACertificates`
This method can be accessed using https to add CA certificates to the global-agent.
Uses:
```js
if (typeof https.globalAgent.addCACertificates === 'function') {
//certificate - an array of ca certificates to be added to the global-agent
https.globalAgent.addCACertificates(certificate);
}
```
Method Definition:
```js
/**
* This method can be used to append new ca certificates to existing ca certificates
* @param {string[] | string} ca a ca certificate or an array of ca certificates
*/
public addCACertificates (ca: string[] | string) {
if (!ca) {
log.error('Invalid input ca certificate');
} else if (this.ca) {
if (typeof ca === typeof this.ca) {
// concat valid ca certificates with the existing certificates,
if (typeof this.ca === 'string') {
this.ca = this.ca.concat(ca as string);
} else {
this.ca = this.ca.concat(ca as string[]);
}
} else {
log.error('Input ca certificate type mismatched with existing ca certificate type');
}
} else {
this.ca = ca;
}
}
```
### `clearCACertificates`
This method can be accessed using https to clear existing CA certificates from global-agent.
Uses:
```js
if (typeof https.globalAgent.clearCACertificates === 'function') {
https.globalAgent.clearCACertificates();
}
```
Method Definition:
```js
/**
* This method clears existing CA Certificates.
* It sets ca to undefined
*/
public clearCACertificates () {
this.ca = undefined;
}
```
## Supported libraries
`global-agent` works with all libraries that internally use [`http.request`](https://nodejs.org/api/http.html#http_http_request_options_callback).
`global-agent` has been tested to work with:
* [`got`](https://www.npmjs.com/package/got)
* [`axios`](https://www.npmjs.com/package/axios)
* [`request`](https://www.npmjs.com/package/request)
## FAQ
### What is the reason `global-agent` overrides explicitly configured HTTP(S) agent?
By default, `global-agent` overrides [`agent` property](https://nodejs.org/api/http.html#http_http_request_options_callback) of any HTTP request, even if `agent` property was explicitly set when constructing a HTTP request. This behaviour allows to intercept requests of libraries that use a custom instance of an agent per default (e.g. Stripe SDK [uses an `http(s).globalAgent` instance pre-configured with `keepAlive: true`](https://github.com/stripe/stripe-node/blob/e542902dd8fbe591fe3c3ce07a7e89d1d60e4cf7/lib/StripeResource.js#L11-L12)).
This behaviour can be disabled with `GLOBAL_AGENT_FORCE_GLOBAL_AGENT=false` environment variable. When disabled, then `global-agent` will only set `agent` property when it is not already defined or if `agent` is an instance of `http(s).globalAgent`.
### What is the reason `global-agent/bootstrap` does not use `HTTP_PROXY`?
Some libraries (e.g. [`request`](https://npmjs.org/package/request)) change their behaviour when `HTTP_PROXY` environment variable is present. Using a namespaced environment variable prevents conflicting library behaviour.
You can override this behaviour by configuring `GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE` variable, e.g.
```bash
$ export GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE=
```
Now script initialized using `global-agent/bootstrap` will use `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables.
### What is the difference from `global-tunnel` and `tunnel`?
[`global-tunnel`](https://github.com/salesforce/global-tunnel) (including [`global-tunnel-ng`](https://github.com/np-maintain/global-tunnel) and [`tunnel`](https://npmjs.com/package/tunnel)) are designed to support legacy Node.js versions. They use various [workarounds](https://github.com/koichik/node-tunnel/blob/5fb2fb424788597146b7be6729006cad1cf9e9a8/lib/tunnel.js#L134-L144) and rely on [monkey-patching `http.request`, `http.get`, `https.request` and `https.get` methods](https://github.com/np-maintain/global-tunnel/blob/51413dcf0534252b5049ec213105c7063ccc6367/index.js#L302-L338).
In contrast, `global-agent` supports Node.js v10 and above, and does not implements workarounds for the older Node.js versions.
+11
View File
@@ -0,0 +1,11 @@
export type LogMethod = (context: object | string, message?: string) => void;
export type Logger = {
child: (context: object) => Logger;
debug: LogMethod;
error: LogMethod;
info: LogMethod;
trace: LogMethod;
warn: LogMethod;
};
export declare const setLogger: (newLogger: Logger) => void;
export declare const logger: Logger;
+56
View File
@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = exports.setLogger = void 0;
// oxlint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => { };
const createNoopLogger = () => {
return {
child: () => {
return createNoopLogger();
},
debug: noop,
error: noop,
info: noop,
trace: noop,
warn: noop,
};
};
let currentLogger = createNoopLogger();
const setLogger = (newLogger) => {
currentLogger = newLogger;
};
exports.setLogger = setLogger;
const createDelegatingLogger = (getContext) => {
const getLogger = () => {
let targetLogger = currentLogger;
for (const [key, value] of Object.entries(getContext())) {
targetLogger = targetLogger.child({ [key]: value });
}
return targetLogger;
};
return {
child: (context) => {
return createDelegatingLogger(() => {
return { ...getContext(), ...context };
});
},
debug: (context, message) => {
getLogger().debug(context, message);
},
error: (context, message) => {
getLogger().error(context, message);
},
info: (context, message) => {
getLogger().info(context, message);
},
trace: (context, message) => {
getLogger().trace(context, message);
},
warn: (context, message) => {
getLogger().warn(context, message);
},
};
};
exports.logger = createDelegatingLogger(() => {
return { package: 'global-agent' };
});
+46
View File
@@ -0,0 +1,46 @@
/// <reference types="node" />
/// <reference types="node" />
import type * as http from 'http';
import type * as https from 'https';
import type { AgentType, ConnectionCallbackType, ConnectionConfigurationType, GetUrlProxyMethodType, IsProxyConfiguredMethodType, MustUrlUseProxyMethodType, ProtocolType } from '../types';
type AgentRequestOptions = {
host?: string;
path?: string;
port: number;
};
type HttpRequestOptions = AgentRequestOptions & Omit<http.RequestOptions, keyof AgentRequestOptions> & {
secureEndpoint: false;
};
type HttpsRequestOptions = AgentRequestOptions & Omit<https.RequestOptions, keyof AgentRequestOptions> & {
secureEndpoint: true;
};
type RequestOptions = HttpRequestOptions | HttpsRequestOptions;
declare abstract class Agent {
defaultPort: number;
protocol: ProtocolType;
fallbackAgent: AgentType;
isProxyConfigured: IsProxyConfiguredMethodType;
mustUrlUseProxy: MustUrlUseProxyMethodType;
getUrlProxy: GetUrlProxyMethodType;
socketConnectionTimeout: number;
ca: string[] | string | undefined;
constructor(isProxyConfigured: IsProxyConfiguredMethodType, mustUrlUseProxy: MustUrlUseProxyMethodType, getUrlProxy: GetUrlProxyMethodType, fallbackAgent: AgentType, socketConnectionTimeout: number, ca: string[] | string | undefined);
/**
* This method can be used to append new ca certificates to existing ca certificates
*
* @param {string[] | string} ca a ca certificate or an array of ca certificates
*/
addCACertificates(ca: string[] | string): void;
/**
* This method clears existing CA Certificates.
* It sets ca to undefined
*/
clearCACertificates(): void;
/**
* Evaluate value for tls reject unauthorized variable
*/
getRejectUnauthorized(): boolean;
abstract createConnection(configuration: ConnectionConfigurationType, callback: ConnectionCallbackType): void;
addRequest(request: http.ClientRequest, configuration: RequestOptions): void;
}
export default Agent;
+216
View File
@@ -0,0 +1,216 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const net_1 = __importDefault(require("net"));
const serialize_error_1 = require("serialize-error");
const Logger_1 = require("../Logger");
const log = Logger_1.logger.child({
namespace: 'Agent',
});
let requestId = 0;
class Agent {
constructor(isProxyConfigured, mustUrlUseProxy, getUrlProxy, fallbackAgent, socketConnectionTimeout, ca) {
this.fallbackAgent = fallbackAgent;
this.isProxyConfigured = isProxyConfigured;
this.mustUrlUseProxy = mustUrlUseProxy;
this.getUrlProxy = getUrlProxy;
this.socketConnectionTimeout = socketConnectionTimeout;
this.ca = ca;
}
/**
* This method can be used to append new ca certificates to existing ca certificates
*
* @param {string[] | string} ca a ca certificate or an array of ca certificates
*/
addCACertificates(ca) {
if (!ca) {
log.error('Invalid input ca certificate');
}
else if (this.ca) {
if (typeof ca === typeof this.ca) {
// concat valid ca certificates with the existing certificates,
if (typeof this.ca === 'string') {
this.ca = this.ca.concat(ca);
}
else {
this.ca = this.ca.concat(ca);
}
}
else {
log.error('Input ca certificate type mismatched with existing ca certificate type');
}
}
else {
this.ca = ca;
}
}
/**
* This method clears existing CA Certificates.
* It sets ca to undefined
*/
clearCACertificates() {
this.ca = undefined;
}
/**
* Evaluate value for tls reject unauthorized variable
*/
getRejectUnauthorized() {
// oxlint-disable-next-line node/no-process-env
const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
let returnValue = true;
if (typeof rejectUnauthorized === 'boolean') {
returnValue = rejectUnauthorized;
}
else if (typeof rejectUnauthorized === 'number') {
returnValue = rejectUnauthorized === 1;
}
else if (typeof rejectUnauthorized === 'string') {
returnValue = ['true', 't', 'yes', 'y', 'on', '1'].includes(rejectUnauthorized.trim().toLowerCase());
}
return returnValue;
}
addRequest(request, configuration) {
var _a, _b, _c, _d, _e, _f, _g, _h;
let requestUrl;
// It is possible that addRequest was constructed for a proxied request already, e.g.
// "request" package does this when it detects that a proxy should be used
// https://github.com/request/request/blob/212570b6971a732b8dd9f3c73354bcdda158a737/request.js#L402
// https://gist.github.com/gajus/e2074cd3b747864ffeaabbd530d30218
if ((_a = request.path.startsWith('http://')) !== null && _a !== void 0 ? _a : request.path.startsWith('https://')) {
requestUrl = request.path;
}
else if (request.method === 'CONNECT') {
requestUrl = 'https://' + request.path;
}
else {
requestUrl = this.protocol + '//' + ((_b = configuration.hostname) !== null && _b !== void 0 ? _b : configuration.host) + (configuration.port === 80 || configuration.port === 443 ? '' : ':' + configuration.port) + request.path;
}
// If a request should go to a local socket, proxying it through an HTTP
// server does not make sense as the information about the target socket
// will be lost and the proxy won't be able to correctly handle the request.
if (configuration.socketPath) {
log.trace({
destination: configuration.socketPath,
}, 'not proxying request; destination is a socket');
// @ts-expect-error seems like we are using wrong type for fallbackAgent.
this.fallbackAgent.addRequest(request, configuration);
return;
}
if (!this.isProxyConfigured()) {
log.trace({
destination: requestUrl,
}, 'not proxying request; GLOBAL_AGENT.HTTP_PROXY is not configured');
// @ts-expect-error seems like we are using wrong type for fallbackAgent.
this.fallbackAgent.addRequest(request, configuration);
return;
}
if (!this.mustUrlUseProxy(requestUrl)) {
log.trace({
destination: requestUrl,
}, 'not proxying request; url matches GLOBAL_AGENT.NO_PROXY');
// @ts-expect-error seems like we are using wrong type for fallbackAgent.
this.fallbackAgent.addRequest(request, configuration);
return;
}
const currentRequestId = requestId++;
const proxy = this.getUrlProxy(requestUrl);
if (this.protocol === 'http:') {
request.path = requestUrl;
if (proxy.authorization) {
request.setHeader('proxy-authorization', 'Basic ' + Buffer.from(proxy.authorization).toString('base64'));
}
}
log.trace({
destination: requestUrl,
proxy: 'http://' + proxy.hostname + ':' + proxy.port,
requestId: currentRequestId,
}, 'proxying request');
request.on('error', (error) => {
log.error({
error: (0, serialize_error_1.serializeError)(error),
}, 'request error');
});
request.once('response', (response) => {
log.trace({
headers: response.headers,
requestId: currentRequestId,
statusCode: response.statusCode,
}, 'proxying response');
});
request.shouldKeepAlive = false;
const connectionConfiguration = {
host: (_d = (_c = configuration.hostname) !== null && _c !== void 0 ? _c : configuration.host) !== null && _d !== void 0 ? _d : '',
port: (_e = configuration.port) !== null && _e !== void 0 ? _e : 80,
proxy,
tls: {},
};
// add optional tls options for https requests.
// @see https://nodejs.org/docs/latest-v12.x/api/https.html#https_https_request_url_options_callback :
// > The following additional options from tls.connect()
// > - https://nodejs.org/docs/latest-v12.x/api/tls.html#tls_tls_connect_options_callback -
// > are also accepted:
// > ca, cert, ciphers, clientCertEngine, crl, dhparam, ecdhCurve, honorCipherOrder,
// > key, passphrase, pfx, rejectUnauthorized, secureOptions, secureProtocol, servername, sessionIdContext.
if (configuration.secureEndpoint) {
// Determine servername - Node.js doesn't allow IP addresses as servername
const host = (_f = configuration.servername) !== null && _f !== void 0 ? _f : connectionConfiguration.host;
const servername = net_1.default.isIP(host) ? undefined : host;
connectionConfiguration.tls = {
ca: (_g = configuration.ca) !== null && _g !== void 0 ? _g : this.ca,
cert: configuration.cert,
ciphers: configuration.ciphers,
clientCertEngine: configuration.clientCertEngine,
crl: configuration.crl,
dhparam: configuration.dhparam,
ecdhCurve: configuration.ecdhCurve,
honorCipherOrder: configuration.honorCipherOrder,
key: configuration.key,
passphrase: configuration.passphrase,
pfx: configuration.pfx,
rejectUnauthorized: (_h = configuration.rejectUnauthorized) !== null && _h !== void 0 ? _h : this.getRejectUnauthorized(),
secureOptions: configuration.secureOptions,
secureProtocol: configuration.secureProtocol,
servername,
sessionIdContext: configuration.sessionIdContext,
};
}
this.createConnection(connectionConfiguration, (error, socket) => {
log.trace({
target: connectionConfiguration,
}, 'connecting');
// @see https://github.com/nodejs/node/issues/5757#issuecomment-305969057
if (socket) {
socket.setTimeout(this.socketConnectionTimeout, () => {
socket.destroy();
});
socket.once('connect', () => {
log.trace({
target: connectionConfiguration,
}, 'connected');
socket.setTimeout(0);
});
socket.once('secureConnect', () => {
log.trace({
target: connectionConfiguration,
}, 'connected (secure)');
socket.setTimeout(0);
});
}
if (error) {
request.emit('error', error);
}
else if (socket) {
log.debug('created socket');
socket.on('error', (socketError) => {
log.error({
error: (0, serialize_error_1.serializeError)(socketError),
}, 'socket error');
});
request.onSocket(socket);
}
});
}
}
exports.default = Agent;
@@ -0,0 +1,7 @@
import type { AgentType, ConnectionCallbackType, ConnectionConfigurationType, GetUrlProxyMethodType, IsProxyConfiguredMethodType, MustUrlUseProxyMethodType } from '../types';
import Agent from './Agent';
declare class HttpProxyAgent extends Agent {
constructor(isProxyConfigured: IsProxyConfiguredMethodType, mustUrlUseProxy: MustUrlUseProxyMethodType, getUrlProxy: GetUrlProxyMethodType, fallbackAgent: AgentType, socketConnectionTimeout: number, ca: string[] | string | undefined);
createConnection(configuration: ConnectionConfigurationType, callback: ConnectionCallbackType): void;
}
export default HttpProxyAgent;
+20
View File
@@ -0,0 +1,20 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const net_1 = __importDefault(require("net"));
const Agent_1 = __importDefault(require("./Agent"));
class HttpProxyAgent extends Agent_1.default {
// @see https://github.com/sindresorhus/eslint-plugin-unicorn/issues/169#issuecomment-486980290
constructor(isProxyConfigured, mustUrlUseProxy, getUrlProxy, fallbackAgent, socketConnectionTimeout, ca) {
super(isProxyConfigured, mustUrlUseProxy, getUrlProxy, fallbackAgent, socketConnectionTimeout, ca);
this.protocol = 'http:';
this.defaultPort = 80;
}
createConnection(configuration, callback) {
const socket = net_1.default.connect(configuration.proxy.port, configuration.proxy.hostname);
callback(null, socket);
}
}
exports.default = HttpProxyAgent;
@@ -0,0 +1,7 @@
import type { AgentType, ConnectionCallbackType, ConnectionConfigurationType, GetUrlProxyMethodType, IsProxyConfiguredMethodType, MustUrlUseProxyMethodType } from '../types';
import Agent from './Agent';
declare class HttpsProxyAgent extends Agent {
constructor(isProxyConfigured: IsProxyConfiguredMethodType, mustUrlUseProxy: MustUrlUseProxyMethodType, getUrlProxy: GetUrlProxyMethodType, fallbackAgent: AgentType, socketConnectionTimeout: number, ca: string[] | string | undefined);
createConnection(configuration: ConnectionConfigurationType, callback: ConnectionCallbackType): void;
}
export default HttpsProxyAgent;
+48
View File
@@ -0,0 +1,48 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const net_1 = __importDefault(require("net"));
const tls_1 = __importDefault(require("tls"));
const Agent_1 = __importDefault(require("./Agent"));
class HttpsProxyAgent extends Agent_1.default {
constructor(isProxyConfigured, mustUrlUseProxy, getUrlProxy, fallbackAgent, socketConnectionTimeout, ca) {
super(isProxyConfigured, mustUrlUseProxy, getUrlProxy, fallbackAgent, socketConnectionTimeout, ca);
this.protocol = 'https:';
this.defaultPort = 443;
}
createConnection(configuration, callback) {
const socket = net_1.default.connect(configuration.proxy.port, configuration.proxy.hostname);
socket.on('error', (error) => {
callback(error);
});
socket.once('data', (data) => {
var _a;
// Proxies with HTTPS as protocal are not allowed by parseProxyUrl(), so it should be safe to assume that the response is plain text
const statusLine = data.toString().split('\r\n')[0];
const statusLineExp = /^HTTP\/(\d)\.(\d) (\d{3}) ?(.*)$/;
const statusCode = (_a = statusLineExp.exec(statusLine)) === null || _a === void 0 ? void 0 : _a[3];
if (typeof statusCode === 'string' && Number(statusCode) >= 400) {
const error = new Error(`Proxy server refused connecting to '${configuration.host}:${configuration.port}' (${statusLine})`);
socket.destroy();
callback(error);
return;
}
const secureSocket = tls_1.default.connect({
...configuration.tls,
socket,
});
callback(null, secureSocket);
});
let connectMessage = '';
connectMessage += 'CONNECT ' + configuration.host + ':' + configuration.port + ' HTTP/1.1\r\n';
connectMessage += 'Host: ' + configuration.host + ':' + configuration.port + '\r\n';
if (configuration.proxy.authorization) {
connectMessage += 'Proxy-Authorization: Basic ' + Buffer.from(configuration.proxy.authorization).toString('base64') + '\r\n';
}
connectMessage += '\r\n';
socket.write(connectMessage);
}
}
exports.default = HttpsProxyAgent;
+3
View File
@@ -0,0 +1,3 @@
export { default as Agent, } from './Agent';
export { default as HttpProxyAgent, } from './HttpProxyAgent';
export { default as HttpsProxyAgent, } from './HttpsProxyAgent';
+12
View File
@@ -0,0 +1,12 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpsProxyAgent = exports.HttpProxyAgent = exports.Agent = void 0;
var Agent_1 = require("./Agent");
Object.defineProperty(exports, "Agent", { enumerable: true, get: function () { return __importDefault(Agent_1).default; } });
var HttpProxyAgent_1 = require("./HttpProxyAgent");
Object.defineProperty(exports, "HttpProxyAgent", { enumerable: true, get: function () { return __importDefault(HttpProxyAgent_1).default; } });
var HttpsProxyAgent_1 = require("./HttpsProxyAgent");
Object.defineProperty(exports, "HttpsProxyAgent", { enumerable: true, get: function () { return __importDefault(HttpsProxyAgent_1).default; } });
+4
View File
@@ -0,0 +1,4 @@
export declare class UnexpectedStateError extends Error {
code: string;
constructor(message: string, code?: string);
}
+10
View File
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnexpectedStateError = void 0;
class UnexpectedStateError extends Error {
constructor(message, code = 'UNEXPECTED_STATE_ERROR') {
super(message);
this.code = code;
}
}
exports.UnexpectedStateError = UnexpectedStateError;
@@ -0,0 +1,7 @@
import type { ProxyAgentConfigurationInputType } from '../types';
declare const _default: (configurationInput?: ProxyAgentConfigurationInputType) => {
HTTP_PROXY: string | null;
HTTPS_PROXY: string | null;
NO_PROXY: string | null;
};
export default _default;
@@ -0,0 +1,131 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const gte_1 = __importDefault(require("semver/functions/gte"));
const Logger_1 = require("../Logger");
const classes_1 = require("../classes");
const errors_1 = require("../errors");
const utilities_1 = require("../utilities");
const parseBoolean_1 = require("../utilities/parseBoolean");
const createProxyController_1 = __importDefault(require("./createProxyController"));
const httpGet = http_1.default.get;
const httpRequest = http_1.default.request;
const httpsGet = https_1.default.get;
const httpsRequest = https_1.default.request;
const log = Logger_1.logger.child({
namespace: 'createGlobalProxyAgent',
});
const defaultConfigurationInput = {
environmentVariableNamespace: undefined,
forceGlobalAgent: undefined,
socketConnectionTimeout: 60000,
};
const createConfiguration = (configurationInput) => {
// oxlint-disable-next-line node/no-process-env
const environment = process.env;
const defaultConfiguration = {
environmentVariableNamespace: typeof environment.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE === 'string' ? environment.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE : 'GLOBAL_AGENT_',
forceGlobalAgent: typeof environment.GLOBAL_AGENT_FORCE_GLOBAL_AGENT === 'string' ? (0, parseBoolean_1.parseBoolean)(environment.GLOBAL_AGENT_FORCE_GLOBAL_AGENT) : true,
socketConnectionTimeout: typeof environment.GLOBAL_AGENT_SOCKET_CONNECTION_TIMEOUT === 'string' ? Number.parseInt(environment.GLOBAL_AGENT_SOCKET_CONNECTION_TIMEOUT, 10) : defaultConfigurationInput.socketConnectionTimeout,
};
return {
...defaultConfiguration,
...Object.fromEntries(Object.entries(configurationInput).filter(([, v]) => v !== undefined)),
};
};
exports.default = (configurationInput = defaultConfigurationInput) => {
var _a, _b, _c;
const configuration = createConfiguration(configurationInput);
if (configurationInput.logger) {
(0, Logger_1.setLogger)(configurationInput.logger);
}
const proxyController = (0, createProxyController_1.default)();
// oxlint-disable-next-line node/no-process-env
proxyController.HTTP_PROXY = (_a = process.env[configuration.environmentVariableNamespace + 'HTTP_PROXY']) !== null && _a !== void 0 ? _a : null;
// oxlint-disable-next-line node/no-process-env
proxyController.HTTPS_PROXY = (_b = process.env[configuration.environmentVariableNamespace + 'HTTPS_PROXY']) !== null && _b !== void 0 ? _b : null;
// oxlint-disable-next-line node/no-process-env
proxyController.NO_PROXY = (_c = process.env[configuration.environmentVariableNamespace + 'NO_PROXY']) !== null && _c !== void 0 ? _c : null;
log.info({
configuration,
state: proxyController,
}, 'global agent has been initialized');
const mustUrlUseProxy = (getProxy) => {
return (url) => {
if (!getProxy()) {
return false;
}
if (!proxyController.NO_PROXY) {
return true;
}
return !(0, utilities_1.isUrlMatchingNoProxy)(url, proxyController.NO_PROXY);
};
};
const getUrlProxy = (getProxy) => {
return () => {
const proxy = getProxy();
if (!proxy) {
throw new errors_1.UnexpectedStateError('HTTP(S) proxy must be configured.');
}
return (0, utilities_1.parseProxyUrl)(proxy);
};
};
const getHttpProxy = () => {
return proxyController.HTTP_PROXY;
};
const BoundHttpProxyAgent = class extends classes_1.HttpProxyAgent {
constructor() {
super(() => {
return Boolean(getHttpProxy());
}, mustUrlUseProxy(getHttpProxy), getUrlProxy(getHttpProxy), http_1.default.globalAgent, configuration.socketConnectionTimeout, configuration.ca);
}
};
const httpAgent = new BoundHttpProxyAgent();
const getHttpsProxy = () => {
var _a;
return (_a = proxyController.HTTPS_PROXY) !== null && _a !== void 0 ? _a : proxyController.HTTP_PROXY;
};
const BoundHttpsProxyAgent = class extends classes_1.HttpsProxyAgent {
constructor() {
super(() => {
return Boolean(getHttpsProxy());
}, mustUrlUseProxy(getHttpsProxy), getUrlProxy(getHttpsProxy), https_1.default.globalAgent, configuration.socketConnectionTimeout, configuration.ca);
}
};
const httpsAgent = new BoundHttpsProxyAgent();
// Overriding globalAgent was added in v11.7.
// @see https://nodejs.org/uk/blog/release/v11.7.0/
if ((0, gte_1.default)(process.version, 'v11.7.0')) {
// @see https://github.com/facebook/flow/issues/7670
// @ts-expect-error Node.js version compatibility
http_1.default.globalAgent = httpAgent;
// @ts-expect-error Node.js version compatibility
https_1.default.globalAgent = httpsAgent;
}
// The reason this logic is used in addition to overriding http(s).globalAgent
// is because there is no guarantee that we set http(s).globalAgent variable
// before an instance of http(s).Agent has been already constructed by someone,
// e.g. Stripe SDK creates instances of http(s).Agent at the top-level.
// @see https://github.com/gajus/global-agent/pull/13
//
// We still want to override http(s).globalAgent when possible to enable logic
// in `bindHttpMethod`.
if ((0, gte_1.default)(process.version, 'v10.0.0')) {
// @ts-expect-error seems like we are using wrong type for httpAgent
http_1.default.get = (0, utilities_1.bindHttpMethod)(httpGet, httpAgent, configuration.forceGlobalAgent);
// @ts-expect-error seems like we are using wrong type for httpAgent
http_1.default.request = (0, utilities_1.bindHttpMethod)(httpRequest, httpAgent, configuration.forceGlobalAgent);
// @ts-expect-error seems like we are using wrong type for httpAgent
https_1.default.get = (0, utilities_1.bindHttpMethod)(httpsGet, httpsAgent, configuration.forceGlobalAgent);
// @ts-expect-error seems like we are using wrong type for httpAgent
https_1.default.request = (0, utilities_1.bindHttpMethod)(httpsRequest, httpsAgent, configuration.forceGlobalAgent);
}
else {
log.warn('attempt to initialize global-agent in unsupported Node.js version was ignored');
}
return proxyController;
};
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,539 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
const net_1 = __importDefault(require("net"));
const vitest_1 = require("vitest");
const axios_1 = __importDefault(require("axios"));
const get_port_1 = __importDefault(require("get-port"));
const got_1 = __importDefault(require("got"));
const pem_1 = __importDefault(require("pem"));
const request_1 = __importDefault(require("request"));
const sinon_1 = require("sinon");
const createGlobalProxyAgent_1 = __importDefault(require("./createGlobalProxyAgent"));
const defaultHttpAgent = http_1.default.globalAgent;
const defaultHttpsAgent = https_1.default.globalAgent;
// Backup original value of NODE_TLS_REJECT_UNAUTHORIZED
// oxlint-disable-next-line node/no-process-env
const defaultNodeTlsRejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
let lastPort = 3000;
let localProxyServers = [];
let localHttpServers = [];
let localHttpsServers = [];
let generatedCerts = null;
const getNextPort = () => {
return (0, get_port_1.default)({
port: get_port_1.default.makeRange(lastPort++, 3500),
});
};
// Generate self-signed certificates for HTTPS testing
const generateCertificates = () => {
return new Promise((resolve, reject) => {
if (generatedCerts) {
resolve(generatedCerts);
return;
}
pem_1.default.createCertificate({ days: 1, selfSigned: true }, (error, keys) => {
if (error) {
reject(error);
return;
}
generatedCerts = { cert: keys.certificate, key: keys.serviceKey };
resolve(generatedCerts);
});
});
};
(0, vitest_1.beforeAll)(async () => {
// Pre-generate certificates
await generateCertificates();
});
(0, vitest_1.beforeEach)(() => {
http_1.default.globalAgent = defaultHttpAgent;
https_1.default.globalAgent = defaultHttpsAgent;
});
(0, vitest_1.afterEach)(() => {
for (const localProxyServer of localProxyServers) {
localProxyServer.stop();
}
localProxyServers = [];
for (const localHttpServer of localHttpServers) {
localHttpServer.stop();
}
localHttpServers = [];
for (const localHttpsServer of localHttpsServers) {
localHttpsServer.stop();
}
localHttpsServers = [];
// Reset NODE_TLS_REJECT_UNAUTHORIZED to original value
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = defaultNodeTlsRejectUnauthorized;
});
const createHttpResponseResolver = (resolve) => {
return (response) => {
let body = '';
response.on('data', (data) => {
body += data;
});
response.on('end', () => {
if (!response.headers) {
throw new Error('response.headers is not defined');
}
if (!response.statusCode) {
throw new Error('response.statusCode is not defined');
}
resolve({
body,
headers: response.headers,
statusCode: response.statusCode,
});
});
};
};
// Create a local HTTPS server for CONNECT tunnel targets
const createHttpsServer = async () => {
const port = await getNextPort();
const certs = await generateCertificates();
const localHttpsServer = await new Promise((resolve) => {
const httpsServer = https_1.default.createServer({
cert: certs.cert,
key: certs.key,
}, (request, response) => {
response.writeHead(200, { 'content-type': 'text/plain' });
response.end('OK');
});
httpsServer.listen(port, '127.0.0.1', () => {
resolve({
port,
stop: () => {
httpsServer.close();
},
url: 'https://127.0.0.1:' + port,
});
});
});
localHttpsServers.push(localHttpsServer);
return localHttpsServer;
};
// Create a simple HTTP proxy server that can handle both HTTP requests and HTTPS CONNECT tunneling
const createProxyServer = async (rules) => {
const port = await getNextPort();
// Create an HTTPS server that the proxy will tunnel to for CONNECT requests
const httpsServer = await createHttpsServer();
const localProxyServer = await new Promise((resolve) => {
const proxyServer = http_1.default.createServer((request, response) => {
// Handle regular HTTP proxy requests
if (rules === null || rules === void 0 ? void 0 : rules.beforeSendRequest) {
const result = rules.beforeSendRequest({
requestOptions: {
headers: request.headers,
},
});
response.writeHead(result.response.statusCode, result.response.header);
response.end(result.response.body);
}
else {
// Default response
response.writeHead(200, { 'content-type': 'text/plain' });
response.end('OK');
}
});
// Handle CONNECT requests for HTTPS tunneling
proxyServer.on('connect', (request, clientSocket, head) => {
// Call onConnect hook if provided
if (rules === null || rules === void 0 ? void 0 : rules.onConnect) {
rules.onConnect(request);
}
// Connect to the local HTTPS server instead of the requested host
const serverSocket = net_1.default.connect(httpsServer.port, '127.0.0.1', () => {
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
serverSocket.write(head);
serverSocket.pipe(clientSocket);
clientSocket.pipe(serverSocket);
});
serverSocket.on('error', () => {
clientSocket.end('HTTP/1.1 500 Internal Server Error\r\n\r\n');
});
clientSocket.on('error', () => {
serverSocket.end();
});
});
proxyServer.listen(port, () => {
resolve({
httpsServer,
port,
stop: () => {
proxyServer.close();
},
url: 'http://127.0.0.1:' + port,
});
});
});
localProxyServers.push(localProxyServer);
return localProxyServer;
};
const createHttpServer = async () => {
const port = await getNextPort();
const localHttpServer = await new Promise((resolve) => {
const httpServer = http_1.default.createServer((request, response) => {
response.end('DIRECT');
});
httpServer.listen(port, () => {
resolve({
stop: () => {
httpServer.close();
},
url: 'http://127.0.0.1:' + port,
});
});
});
localHttpServers.push(localHttpServer);
return localHttpServer;
};
(0, vitest_1.test)('proxies HTTP request', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await new Promise((resolve) => {
http_1.default.get('http://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('proxies HTTP request with proxy-authorization header', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const beforeSendRequest = (0, sinon_1.stub)().callsFake(() => {
return {
response: {
body: 'OK',
header: { 'content-type': 'text/plain' },
statusCode: 200,
},
};
});
const proxyServer = await createProxyServer({
beforeSendRequest,
});
globalProxyAgent.HTTP_PROXY = 'http://foo@127.0.0.1:' + proxyServer.port;
const response = await new Promise((resolve) => {
http_1.default.get('http://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
(0, vitest_1.expect)(beforeSendRequest.firstCall.args[0].requestOptions.headers['proxy-authorization']).toBe('Basic Zm9v');
});
(0, vitest_1.test)('Test reject unauthorized variable when NODE_TLS_REJECT_UNAUTHORIZED = undefined', async () => {
// oxlint-disable-next-line node/no-process-env
const { NODE_TLS_REJECT_UNAUTHORIZED, ...restEnvironments } = process.env; // oxlint-disable-line @typescript-eslint/no-unused-vars
// oxlint-disable-next-line node/no-process-env
process.env = restEnvironments;
// oxlint-disable-next-line node/no-process-env
process.env.GLOBAL_AGENT_FORCE_GLOBAL_AGENT = 'true';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.getRejectUnauthorized()).toBe(true);
const response = await new Promise((resolve) => {
http_1.default.get('http://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('Test reject unauthorized variable when NODE_TLS_REJECT_UNAUTHORIZED = null', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 'null';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.getRejectUnauthorized()).toBe(false);
});
(0, vitest_1.test)('Test reject unauthorized variable when NODE_TLS_REJECT_UNAUTHORIZED = 1', async () => {
// @ts-expect-error it is expected as we wanted to set process variable with int
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 1;
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.getRejectUnauthorized()).toBe(true);
});
(0, vitest_1.test)('Test reject unauthorized variable when NODE_TLS_REJECT_UNAUTHORIZED = 0', async () => {
// @ts-expect-error it is expected as we wanted to set process variable with int
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.getRejectUnauthorized()).toBe(false);
});
(0, vitest_1.test)('Test reject unauthorized variable when NODE_TLS_REJECT_UNAUTHORIZED = true', async () => {
// @ts-expect-error it is expected as we wanted to set process variable with boolean
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = true;
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.getRejectUnauthorized()).toBe(true);
});
(0, vitest_1.test)('Test reject unauthorized variable when NODE_TLS_REJECT_UNAUTHORIZED = false', async () => {
// @ts-expect-error it is expected as we wanted to set process variable with boolean
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = false;
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.getRejectUnauthorized()).toBe(false);
});
(0, vitest_1.test)('Test reject unauthorized variable when NODE_TLS_REJECT_UNAUTHORIZED = yes', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 'yes';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.getRejectUnauthorized()).toBe(true);
});
(0, vitest_1.test)('Test reject unauthorized variable when NODE_TLS_REJECT_UNAUTHORIZED = no', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = 'no';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.getRejectUnauthorized()).toBe(false);
});
(0, vitest_1.test)('Test addCACertificates and clearCACertificates methods', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.ca).toBe(undefined);
globalAgent.addCACertificates(['test-ca-certficate1', 'test-ca-certficate2']);
globalAgent.addCACertificates(['test-ca-certficate3']);
const result = ['test-ca-certficate1', 'test-ca-certficate2', 'test-ca-certficate3'];
(0, vitest_1.expect)(globalAgent.ca.length).toBe(result.length);
(0, vitest_1.expect)(JSON.stringify(globalAgent.ca)).toBe(JSON.stringify(result));
globalAgent.clearCACertificates();
(0, vitest_1.expect)(globalAgent.ca).toBe(undefined);
});
(0, vitest_1.test)('Test addCACertificates when passed ca is a string', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.ca).toBe(undefined);
globalAgent.addCACertificates('test-ca-certficate1');
globalAgent.addCACertificates('test-ca-certficate2');
(0, vitest_1.expect)(globalAgent.ca).toBe('test-ca-certficate1test-ca-certficate2');
const response = await new Promise((resolve) => {
https_1.default.get('https://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('Test addCACertificates when input ca is a string and existing ca is array', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)({ ca: ['test-ca'] });
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.ca.length).toBe(1);
globalAgent.addCACertificates('test-ca-certficate1');
(0, vitest_1.expect)(globalAgent.ca.length).toBe(1);
(0, vitest_1.expect)(JSON.stringify(globalAgent.ca)).toBe(JSON.stringify(['test-ca']));
const response = await new Promise((resolve) => {
https_1.default.get('https://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('Test addCACertificates when input ca array is null or undefined', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.ca).toBe(undefined);
globalAgent.addCACertificates(undefined);
globalAgent.addCACertificates(null);
(0, vitest_1.expect)(globalAgent.ca).toBe(undefined);
const response = await new Promise((resolve) => {
https_1.default.get('https://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('Test initializing ca certificate property while creating global proxy agent', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)({ ca: ['test-ca'] });
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const globalAgent = https_1.default.globalAgent;
(0, vitest_1.expect)(globalAgent.ca.length).toBe(1);
globalAgent.addCACertificates(['test-ca1']);
(0, vitest_1.expect)(globalAgent.ca.length).toBe(2);
(0, vitest_1.expect)(globalAgent.ca[0]).toBe('test-ca');
(0, vitest_1.expect)(globalAgent.ca[1]).toBe('test-ca1');
const response = await new Promise((resolve) => {
https_1.default.get('https://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('proxies HTTPS request', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await new Promise((resolve) => {
https_1.default.get('https://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('proxies HTTPS request with proxy-authorization header', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const onConnect = (0, sinon_1.stub)();
const proxyServer = await createProxyServer({
onConnect,
});
globalProxyAgent.HTTP_PROXY = 'http://foo@127.0.0.1:' + proxyServer.port;
const response = await new Promise((resolve) => {
https_1.default.get('https://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
(0, vitest_1.expect)(onConnect.firstCall.args[0].headers['proxy-authorization']).toBe('Basic Zm9v');
});
(0, vitest_1.test)('does not produce unhandled rejection when cannot connect to proxy', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const port = await getNextPort();
globalProxyAgent.HTTP_PROXY = 'http://127.0.0.1:' + port;
await (0, vitest_1.expect)((0, got_1.default)('http://127.0.0.1')).rejects.toThrow();
});
(0, vitest_1.test)('proxies HTTPS request with dedicated proxy', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTPS_PROXY = proxyServer.url;
const response = await new Promise((resolve) => {
https_1.default.get('https://127.0.0.1', createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('ignores dedicated HTTPS proxy for HTTP urls', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
globalProxyAgent.HTTPS_PROXY = 'http://example.org';
const response = await new Promise((resolve) => {
http_1.default.get('http://127.0.0.1', {}, createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('forwards requests matching NO_PROXY', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
const httpServer = await createHttpServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
globalProxyAgent.NO_PROXY = '127.0.0.1';
const response = await new Promise((resolve) => {
http_1.default.get(httpServer.url, createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('DIRECT');
});
(0, vitest_1.test)('forwards requests that go to a socket', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
// not relevant as traffic shouldn't go through proxy
globalProxyAgent.HTTP_PROXY = 'localhost:10324';
const server = http_1.default.createServer((request, serverResponse) => {
serverResponse.writeHead(200);
serverResponse.write('OK');
serverResponse.end();
});
server.listen('/tmp/test.sock');
const response = await new Promise((resolve) => {
http_1.default.get({
path: '/endpoint',
socketPath: '/tmp/test.sock',
}, createHttpResponseResolver(resolve));
});
server.close();
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('proxies HTTP request (using http.get(host))', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await new Promise((resolve) => {
http_1.default.get({
host: '127.0.0.1',
}, createHttpResponseResolver(resolve));
});
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('proxies HTTP request (using got)', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await (0, got_1.default)('http://127.0.0.1');
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('proxies HTTPS request (using got)', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await (0, got_1.default)('https://127.0.0.1');
(0, vitest_1.expect)(response.body).toBe('OK');
});
(0, vitest_1.test)('proxies HTTP request (using axios)', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await axios_1.default.get('http://127.0.0.1');
(0, vitest_1.expect)(response.data).toBe('OK');
});
(0, vitest_1.test)('proxies HTTPS request (using axios)', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await axios_1.default.get('https://127.0.0.1');
(0, vitest_1.expect)(response.data).toBe('OK');
});
(0, vitest_1.test)('proxies HTTP request (using request)', async () => {
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await new Promise((resolve) => {
(0, request_1.default)('http://127.0.0.1', (error, requestResponse, body) => {
(0, vitest_1.expect)(error).toBe(null);
resolve(body);
});
});
(0, vitest_1.expect)(response).toBe('OK');
});
(0, vitest_1.test)('proxies HTTPS request (using request)', async () => {
// oxlint-disable-next-line node/no-process-env
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const globalProxyAgent = (0, createGlobalProxyAgent_1.default)();
const proxyServer = await createProxyServer();
globalProxyAgent.HTTP_PROXY = proxyServer.url;
const response = await new Promise((resolve) => {
(0, request_1.default)('https://127.0.0.1', (error, requestResponse, body) => {
(0, vitest_1.expect)(error).toBe(null);
resolve(body);
});
});
(0, vitest_1.expect)(response).toBe('OK');
});
@@ -0,0 +1,7 @@
type ProxyController = {
HTTP_PROXY: string | null;
HTTPS_PROXY: string | null;
NO_PROXY: string | null;
};
declare const _default: () => ProxyController;
export default _default;
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Logger_1 = require("../Logger");
const log = Logger_1.logger.child({
namespace: 'createProxyController',
});
const KNOWN_PROPERTY_NAMES = [
'HTTP_PROXY',
'HTTPS_PROXY',
'NO_PROXY',
];
exports.default = () => {
// oxlint-disable-next-line fp/no-proxy
return new Proxy({
HTTP_PROXY: null,
HTTPS_PROXY: null,
NO_PROXY: null,
}, {
set: (subject, name, value) => {
if (typeof name !== 'string') {
throw new TypeError('Unexpected object member.');
}
if (!KNOWN_PROPERTY_NAMES.includes(name)) {
throw new Error('Cannot set an unmapped property "' + name + '".');
}
// @ts-expect-error string cannot be used to index an object
subject[name] = value;
log.info({
change: {
name,
value,
},
newConfiguration: subject,
}, 'configuration changed');
return true;
},
});
};
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,29 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
const createProxyController_1 = __importDefault(require("./createProxyController"));
(0, vitest_1.test)('sets HTTP_PROXY', () => {
const globalAgentGlobal = (0, createProxyController_1.default)();
globalAgentGlobal.HTTP_PROXY = 'http://127.0.0.1';
(0, vitest_1.expect)(globalAgentGlobal.HTTP_PROXY).toBe('http://127.0.0.1');
});
(0, vitest_1.test)('sets HTTPS_PROXY', () => {
const globalAgentGlobal = (0, createProxyController_1.default)();
globalAgentGlobal.HTTPS_PROXY = 'http://127.0.0.1';
(0, vitest_1.expect)(globalAgentGlobal.HTTPS_PROXY).toBe('http://127.0.0.1');
});
(0, vitest_1.test)('sets NO_PROXY', () => {
const globalAgentGlobal = (0, createProxyController_1.default)();
globalAgentGlobal.NO_PROXY = '*';
(0, vitest_1.expect)(globalAgentGlobal.NO_PROXY).toBe('*');
});
(0, vitest_1.test)('throws an error if unknown property is set', () => {
const globalAgentGlobal = (0, createProxyController_1.default)();
(0, vitest_1.expect)(() => {
// @ts-expect-error expected unknown property.
globalAgentGlobal.FOO = 'BAR';
}).toThrow('Cannot set an unmapped property "FOO".');
});
+2
View File
@@ -0,0 +1,2 @@
export { default as createGlobalProxyAgent, } from './createGlobalProxyAgent';
export { default as createProxyController, } from './createProxyController';
+10
View File
@@ -0,0 +1,10 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createProxyController = exports.createGlobalProxyAgent = void 0;
var createGlobalProxyAgent_1 = require("./createGlobalProxyAgent");
Object.defineProperty(exports, "createGlobalProxyAgent", { enumerable: true, get: function () { return __importDefault(createGlobalProxyAgent_1).default; } });
var createProxyController_1 = require("./createProxyController");
Object.defineProperty(exports, "createProxyController", { enumerable: true, get: function () { return __importDefault(createProxyController_1).default; } });
+3
View File
@@ -0,0 +1,3 @@
export { bootstrap, } from './routines';
export { createGlobalProxyAgent, } from './factories';
export type { Logger, } from './Logger';
+7
View File
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createGlobalProxyAgent = exports.bootstrap = void 0;
var routines_1 = require("./routines");
Object.defineProperty(exports, "bootstrap", { enumerable: true, get: function () { return routines_1.bootstrap; } });
var factories_1 = require("./factories");
Object.defineProperty(exports, "createGlobalProxyAgent", { enumerable: true, get: function () { return factories_1.createGlobalProxyAgent; } });
+3
View File
@@ -0,0 +1,3 @@
import type { ProxyAgentConfigurationInputType } from '../types';
declare const _default: (configurationInput?: ProxyAgentConfigurationInputType) => boolean;
export default _default;
+20
View File
@@ -0,0 +1,20 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const globalthis_1 = __importDefault(require("globalthis"));
const Logger_1 = require("../Logger");
const factories_1 = require("../factories");
const globalThis = (0, globalthis_1.default)();
const log = Logger_1.logger.child({
namespace: 'bootstrap',
});
exports.default = (configurationInput) => {
if (globalThis.GLOBAL_AGENT) {
log.warn('found globalThis.GLOBAL_AGENT; second attempt to bootstrap global-agent was ignored');
return false;
}
globalThis.GLOBAL_AGENT = (0, factories_1.createGlobalProxyAgent)(configurationInput);
return true;
};
+1
View File
@@ -0,0 +1 @@
export { default as bootstrap, } from './bootstrap';
+8
View File
@@ -0,0 +1,8 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.bootstrap = void 0;
var bootstrap_1 = require("./bootstrap");
Object.defineProperty(exports, "bootstrap", { enumerable: true, get: function () { return __importDefault(bootstrap_1).default; } });
+57
View File
@@ -0,0 +1,57 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import type { Agent as HttpAgent } from 'http';
import type { Agent as HttpsAgent } from 'https';
import type { Socket } from 'net';
import type { TLSSocket } from 'tls';
import type { Logger } from './Logger';
export type ProxyConfigurationType = {
authorization: string | null;
hostname: string;
port: number;
};
export type TlsConfigurationType = {
ca?: string[] | string;
cert?: string;
ciphers?: string;
clientCertEngine?: string;
crl?: string;
dhparam?: string;
ecdhCurve?: string;
honorCipherOrder?: boolean;
key?: string;
passphrase?: string;
pfx?: string;
rejectUnauthorized?: boolean;
secureOptions?: number;
secureProtocol?: string;
servername?: string;
sessionIdContext?: string;
};
export type ConnectionConfigurationType = {
host: string;
port: number;
tls?: TlsConfigurationType;
proxy: ProxyConfigurationType;
};
export type ConnectionCallbackType = (error: Error | null, socket?: Socket | TLSSocket) => void;
export type AgentType = HttpAgent | HttpsAgent;
export type IsProxyConfiguredMethodType = () => boolean;
export type MustUrlUseProxyMethodType = (url: string) => boolean;
export type GetUrlProxyMethodType = (url: string) => ProxyConfigurationType;
export type ProtocolType = 'http:' | 'https:';
export type ProxyAgentConfigurationInputType = {
environmentVariableNamespace?: string;
forceGlobalAgent?: boolean;
socketConnectionTimeout?: number;
ca?: string[] | string;
logger?: Logger;
};
export type ProxyAgentConfigurationType = {
environmentVariableNamespace: string;
forceGlobalAgent: boolean;
socketConnectionTimeout: number;
ca?: string[] | string;
};
+2
View File
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
/// <reference types="node" />
/// <reference types="node" />
import http from 'http';
import https from 'https';
type AgentType = http.Agent | https.Agent;
declare const _default: (originalMethod: Function, agent: AgentType, forceGlobalAgent: boolean) => (...args: any[]) => any;
export default _default;
@@ -0,0 +1,50 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_1 = __importDefault(require("http"));
const https_1 = __importDefault(require("https"));
exports.default = (originalMethod, agent, forceGlobalAgent) => {
return (...args) => {
let url;
let options;
let callback;
if (typeof args[0] === 'string' || args[0] instanceof URL) {
url = args[0];
if (typeof args[1] === 'function') {
options = {};
callback = args[1];
}
else {
options = {
...args[1],
};
callback = args[2];
}
}
else {
options = {
...args[0],
};
callback = args[1];
}
if (forceGlobalAgent) {
options.agent = agent;
}
else {
if (!options.agent) {
options.agent = agent;
}
if (options.agent === http_1.default.globalAgent || options.agent === https_1.default.globalAgent) {
options.agent = agent;
}
}
if (url) {
return originalMethod(url, options, callback);
}
else {
return originalMethod(options, callback);
}
};
};
+3
View File
@@ -0,0 +1,3 @@
export { default as bindHttpMethod, } from './bindHttpMethod';
export { default as isUrlMatchingNoProxy, } from './isUrlMatchingNoProxy';
export { default as parseProxyUrl, } from './parseProxyUrl';
+12
View File
@@ -0,0 +1,12 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseProxyUrl = exports.isUrlMatchingNoProxy = exports.bindHttpMethod = void 0;
var bindHttpMethod_1 = require("./bindHttpMethod");
Object.defineProperty(exports, "bindHttpMethod", { enumerable: true, get: function () { return __importDefault(bindHttpMethod_1).default; } });
var isUrlMatchingNoProxy_1 = require("./isUrlMatchingNoProxy");
Object.defineProperty(exports, "isUrlMatchingNoProxy", { enumerable: true, get: function () { return __importDefault(isUrlMatchingNoProxy_1).default; } });
var parseProxyUrl_1 = require("./parseProxyUrl");
Object.defineProperty(exports, "parseProxyUrl", { enumerable: true, get: function () { return __importDefault(parseProxyUrl_1).default; } });
@@ -0,0 +1,2 @@
declare const _default: (subjectUrl: string, noProxy: string) => boolean;
export default _default;
@@ -0,0 +1,27 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const matcher_1 = __importDefault(require("matcher"));
const errors_1 = require("../errors");
exports.default = (subjectUrl, noProxy) => {
const subjectUrlTokens = new URL(subjectUrl);
const rules = noProxy.split(/[\s,]+/).filter(Boolean);
for (const rule of rules) {
const ruleMatch = rule
.replace(/^(?<leadingDot>\.)/, '*')
.match(/^(?<hostname>.+?)(?::(?<port>\d+))?$/);
if (!ruleMatch || !ruleMatch.groups) {
throw new errors_1.UnexpectedStateError('Invalid NO_PROXY pattern.');
}
if (!ruleMatch.groups.hostname) {
throw new errors_1.UnexpectedStateError('NO_PROXY entry pattern must include hostname. Use * to match any hostname.');
}
const hostnameIsMatch = matcher_1.default.isMatch(subjectUrlTokens.hostname, ruleMatch.groups.hostname);
if (hostnameIsMatch && (!ruleMatch.groups || !ruleMatch.groups.port || subjectUrlTokens.port && subjectUrlTokens.port === ruleMatch.groups.port)) {
return true;
}
}
return false;
};
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,61 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
const isUrlMatchingNoProxy_1 = __importDefault(require("./isUrlMatchingNoProxy"));
(0, vitest_1.test)('returns `true` if hosts match', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', 'foo.com')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts match (IP)', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://127.0.0.1/', '127.0.0.1')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts match (using asterisk wildcard)', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://bar.foo.com/', '*.foo.com')).toBe(true);
});
(0, vitest_1.test)('returns `true` if domain matches (using dot wildcard)', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', '.foo.com')).toBe(true);
});
(0, vitest_1.test)('returns `true` if subdomain matches (using dot wildcard)', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://bar.foo.com/', '.foo.com')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts match (*) and ports match', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com:8080/', '*:8080')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts and ports match', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com:8080/', 'foo.com:8080')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts match and NO_PROXY does not define port', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com:8080/', 'foo.com')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts (IP) and ports match', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://127.0.0.1:8080/', '127.0.0.1:8080')).toBe(true);
});
(0, vitest_1.test)('returns `false` if hosts match and ports do not match (diffferent port)', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com:8080/', 'foo.com:8000')).toBe(false);
});
(0, vitest_1.test)('returns `false` if hosts match and ports do not match (port not present subject)', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', 'foo.com:8000')).toBe(false);
});
(0, vitest_1.test)('returns `true` if hosts match and ports do not match (port not present NO_PROXY)', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com:8000/', 'foo.com')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts match in one of multiple rules separated with a comma', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', 'bar.org,foo.com,baz.io')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts match in one of multiple rules separated with a comma and a space', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', 'bar.org, foo.com, baz.io')).toBe(true);
});
(0, vitest_1.test)('returns `true` if hosts match in one of multiple rules separated with a space', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', 'bar.org foo.com baz.io')).toBe(true);
});
(0, vitest_1.test)('handles trailing newline in NO_PROXY', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', 'foo.com\n')).toBe(true);
});
(0, vitest_1.test)('handles trailing whitespace in NO_PROXY', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', 'foo.com ')).toBe(true);
});
(0, vitest_1.test)('handles leading whitespace in NO_PROXY', () => {
(0, vitest_1.expect)((0, isUrlMatchingNoProxy_1.default)('http://foo.com/', ' foo.com')).toBe(true);
});
@@ -0,0 +1 @@
export declare const parseBoolean: (value: any) => boolean;
+16
View File
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseBoolean = void 0;
const parseBoolean = function (value) {
switch (Object.prototype.toString.call(value)) {
case '[object String]':
return ['true', 't', 'yes', 'y', 'on', '1'].includes(value.trim().toLowerCase());
case '[object Number]':
return value.valueOf() === 1;
case '[object Boolean]':
return value.valueOf();
default:
return false;
}
};
exports.parseBoolean = parseBoolean;
@@ -0,0 +1,6 @@
declare const _default: (url: string) => {
authorization: string | null;
hostname: string;
port: number;
};
export default _default;
+31
View File
@@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("../errors");
exports.default = (url) => {
const urlTokens = new URL(url);
if (urlTokens.search !== '') {
throw new errors_1.UnexpectedStateError('Unsupported `GLOBAL_AGENT.HTTP_PROXY` configuration value: URL must not have query.');
}
if (urlTokens.hash !== '') {
throw new errors_1.UnexpectedStateError('Unsupported `GLOBAL_AGENT.HTTP_PROXY` configuration value: URL must not have hash.');
}
if (urlTokens.protocol !== 'http:') {
throw new errors_1.UnexpectedStateError('Unsupported `GLOBAL_AGENT.HTTP_PROXY` configuration value: URL protocol must be "http:".');
}
let port = 80;
if (urlTokens.port) {
port = Number.parseInt(urlTokens.port, 10);
}
let authorization = null;
if (urlTokens.username && urlTokens.password) {
authorization = urlTokens.username + ':' + urlTokens.password;
}
else if (urlTokens.username) {
authorization = urlTokens.username;
}
return {
authorization,
hostname: urlTokens.hostname,
port,
};
};
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,31 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
const parseProxyUrl_1 = __importDefault(require("./parseProxyUrl"));
(0, vitest_1.test)('extracts hostname', () => {
(0, vitest_1.expect)((0, parseProxyUrl_1.default)('http://0.0.0.0').hostname).toBe('0.0.0.0');
});
(0, vitest_1.test)('extracts port', () => {
(0, vitest_1.expect)((0, parseProxyUrl_1.default)('http://0.0.0.0:3000').port).toBe(3000);
});
(0, vitest_1.test)('extracts authorization', () => {
(0, vitest_1.expect)((0, parseProxyUrl_1.default)('http://foo:bar@0.0.0.0').authorization).toBe('foo:bar');
});
(0, vitest_1.test)('throws an error if protocol is not "http:"', () => {
(0, vitest_1.expect)(() => {
(0, parseProxyUrl_1.default)('https://0.0.0.0:3000');
}).toThrow('Unsupported `GLOBAL_AGENT.HTTP_PROXY` configuration value: URL protocol must be "http:".');
});
(0, vitest_1.test)('throws an error if query is present', () => {
(0, vitest_1.expect)(() => {
(0, parseProxyUrl_1.default)('http://0.0.0.0:3000/?foo=bar');
}).toThrow('Unsupported `GLOBAL_AGENT.HTTP_PROXY` configuration value: URL must not have query.');
});
(0, vitest_1.test)('throws an error if hash is present', () => {
(0, vitest_1.expect)(() => {
(0, parseProxyUrl_1.default)('http://0.0.0.0:3000/#foo');
}).toThrow('Unsupported `GLOBAL_AGENT.HTTP_PROXY` configuration value: URL must not have hash.');
});
+76
View File
@@ -0,0 +1,76 @@
{
"author": {
"email": "gajus@gajus.com",
"name": "Gajus Kuizinas",
"url": "https://gajus.com"
},
"dependencies": {
"globalthis": "^1.0.2",
"matcher": "^4.0.0",
"semver": "^7.3.5",
"serialize-error": "^8.1.0"
},
"description": "Global HTTP/HTTPS proxy configurable using environment variables.",
"devDependencies": {
"@babel/cli": "^7.14.8",
"@babel/core": "^7.14.8",
"@babel/node": "^7.14.7",
"@babel/plugin-transform-flow-strip-types": "^7.14.5",
"@babel/preset-env": "^7.14.8",
"@babel/register": "^7.14.5",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/github": "^12.0.3",
"@semantic-release/npm": "^13.1.3",
"@types/globalthis": "^1.0.1",
"@types/pem": "^1.14.4",
"@types/request": "^2.48.6",
"@types/semver": "^7.3.8",
"@types/sinon": "^10.0.2",
"axios": "^0.21.1",
"babel-plugin-transform-export-default-name": "^2.1.0",
"del-cli": "^4.0.1",
"get-port": "^5.1.1",
"got": "^11.8.2",
"husky": "^7.0.1",
"oxlint": "^1.43.0",
"pem": "^1.14.4",
"request": "^2.88.2",
"semantic-release": "^25.0.3",
"sinon": "^11.1.2",
"ts-node": "^10.1.0",
"typescript": "^4.3.5",
"vitest": "^4.0.18"
},
"files": [
"dist"
],
"engines": {
"node": ">=10.0"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint && npm run test && npm run build"
}
},
"keywords": [
"http",
"global",
"proxy",
"agent"
],
"license": "BSD-3-Clause",
"main": "./dist/index.js",
"name": "global-agent",
"repository": {
"type": "git",
"url": "https://github.com/gajus/global-agent"
},
"scripts": {
"build": "del-cli ./dist && tsc",
"lint": "oxlint ./src && tsc",
"test": "vitest run",
"create-readme": "gitdown ./.README/README.md --output-file ./README.md"
},
"typings": "./dist/src/index.d.ts",
"version": "4.1.3"
}