|
| 1 | +/** |
| 2 | + * This code was originally forked from https://github.com/TooTallNate/proxy-agents/tree/b133295fd16f6475578b6b15bd9b4e33ecb0d0b7 |
| 3 | + * With the following licence: |
| 4 | + * |
| 5 | + * (The MIT License) |
| 6 | + * |
| 7 | + * Copyright (c) 2013 Nathan Rajlich <[email protected]>* |
| 8 | + * |
| 9 | + * Permission is hereby granted, free of charge, to any person obtaining |
| 10 | + * a copy of this software and associated documentation files (the |
| 11 | + * 'Software'), to deal in the Software without restriction, including |
| 12 | + * without limitation the rights to use, copy, modify, merge, publish, |
| 13 | + * distribute, sublicense, and/or sell copies of the Software, and to |
| 14 | + * permit persons to whom the Software is furnished to do so, subject to |
| 15 | + * the following conditions:* |
| 16 | + * |
| 17 | + * The above copyright notice and this permission notice shall be |
| 18 | + * included in all copies or substantial portions of the Software.* |
| 19 | + * |
| 20 | + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
| 21 | + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 22 | + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| 23 | + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| 24 | + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| 25 | + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| 26 | + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 27 | + */ |
| 28 | + |
| 29 | +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ |
| 30 | +/* eslint-disable @typescript-eslint/member-ordering */ |
| 31 | +/* eslint-disable jsdoc/require-jsdoc */ |
| 32 | +import * as http from 'http'; |
| 33 | +import type * as net from 'net'; |
| 34 | +import type { Duplex } from 'stream'; |
| 35 | +import type * as tls from 'tls'; |
| 36 | + |
| 37 | +export * from './helpers'; |
| 38 | + |
| 39 | +interface HttpConnectOpts extends net.TcpNetConnectOpts { |
| 40 | + secureEndpoint: false; |
| 41 | + protocol?: string; |
| 42 | +} |
| 43 | + |
| 44 | +interface HttpsConnectOpts extends tls.ConnectionOptions { |
| 45 | + secureEndpoint: true; |
| 46 | + protocol?: string; |
| 47 | + port: number; |
| 48 | +} |
| 49 | + |
| 50 | +export type AgentConnectOpts = HttpConnectOpts | HttpsConnectOpts; |
| 51 | + |
| 52 | +const INTERNAL = Symbol('AgentBaseInternalState'); |
| 53 | + |
| 54 | +interface InternalState { |
| 55 | + defaultPort?: number; |
| 56 | + protocol?: string; |
| 57 | + currentSocket?: Duplex; |
| 58 | +} |
| 59 | + |
| 60 | +export abstract class Agent extends http.Agent { |
| 61 | + private [INTERNAL]: InternalState; |
| 62 | + |
| 63 | + // Set by `http.Agent` - missing from `@types/node` |
| 64 | + options!: Partial<net.TcpNetConnectOpts & tls.ConnectionOptions>; |
| 65 | + keepAlive!: boolean; |
| 66 | + |
| 67 | + constructor(opts?: http.AgentOptions) { |
| 68 | + super(opts); |
| 69 | + this[INTERNAL] = {}; |
| 70 | + } |
| 71 | + |
| 72 | + abstract connect( |
| 73 | + req: http.ClientRequest, |
| 74 | + options: AgentConnectOpts, |
| 75 | + ): Promise<Duplex | http.Agent> | Duplex | http.Agent; |
| 76 | + |
| 77 | + /** |
| 78 | + * Determine whether this is an `http` or `https` request. |
| 79 | + */ |
| 80 | + isSecureEndpoint(options?: AgentConnectOpts): boolean { |
| 81 | + if (options) { |
| 82 | + // First check the `secureEndpoint` property explicitly, since this |
| 83 | + // means that a parent `Agent` is "passing through" to this instance. |
| 84 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access |
| 85 | + if (typeof (options as any).secureEndpoint === 'boolean') { |
| 86 | + return options.secureEndpoint; |
| 87 | + } |
| 88 | + |
| 89 | + // If no explicit `secure` endpoint, check if `protocol` property is |
| 90 | + // set. This will usually be the case since using a full string URL |
| 91 | + // or `URL` instance should be the most common usage. |
| 92 | + if (typeof options.protocol === 'string') { |
| 93 | + return options.protocol === 'https:'; |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + // Finally, if no `protocol` property was set, then fall back to |
| 98 | + // checking the stack trace of the current call stack, and try to |
| 99 | + // detect the "https" module. |
| 100 | + const { stack } = new Error(); |
| 101 | + if (typeof stack !== 'string') return false; |
| 102 | + return stack.split('\n').some(l => l.indexOf('(https.js:') !== -1 || l.indexOf('node:https:') !== -1); |
| 103 | + } |
| 104 | + |
| 105 | + createSocket(req: http.ClientRequest, options: AgentConnectOpts, cb: (err: Error | null, s?: Duplex) => void): void { |
| 106 | + const connectOpts = { |
| 107 | + ...options, |
| 108 | + secureEndpoint: this.isSecureEndpoint(options), |
| 109 | + }; |
| 110 | + Promise.resolve() |
| 111 | + .then(() => this.connect(req, connectOpts)) |
| 112 | + .then(socket => { |
| 113 | + if (socket instanceof http.Agent) { |
| 114 | + // @ts-expect-error `addRequest()` isn't defined in `@types/node` |
| 115 | + return socket.addRequest(req, connectOpts); |
| 116 | + } |
| 117 | + this[INTERNAL].currentSocket = socket; |
| 118 | + // @ts-expect-error `createSocket()` isn't defined in `@types/node` |
| 119 | + super.createSocket(req, options, cb); |
| 120 | + }, cb); |
| 121 | + } |
| 122 | + |
| 123 | + createConnection(): Duplex { |
| 124 | + const socket = this[INTERNAL].currentSocket; |
| 125 | + this[INTERNAL].currentSocket = undefined; |
| 126 | + if (!socket) { |
| 127 | + throw new Error('No socket was returned in the `connect()` function'); |
| 128 | + } |
| 129 | + return socket; |
| 130 | + } |
| 131 | + |
| 132 | + get defaultPort(): number { |
| 133 | + return this[INTERNAL].defaultPort ?? (this.protocol === 'https:' ? 443 : 80); |
| 134 | + } |
| 135 | + |
| 136 | + set defaultPort(v: number) { |
| 137 | + if (this[INTERNAL]) { |
| 138 | + this[INTERNAL].defaultPort = v; |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + get protocol(): string { |
| 143 | + return this[INTERNAL].protocol ?? (this.isSecureEndpoint() ? 'https:' : 'http:'); |
| 144 | + } |
| 145 | + |
| 146 | + set protocol(v: string) { |
| 147 | + if (this[INTERNAL]) { |
| 148 | + this[INTERNAL].protocol = v; |
| 149 | + } |
| 150 | + } |
| 151 | +} |
0 commit comments