Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
agents.js207 linesDownload Raw Back to lib
1'use strict'
2
3const net = require('net')
4const tls = require('tls')
5const { once } = require('events')
6const timers = require('timers/promises')
7const { normalizeOptions, cacheOptions } = require('./options')
8const { getProxy, getProxyAgent, proxyCache } = require('./proxy.js')
9const Errors = require('./errors.js')
10const { Agent: AgentBase } = require('agent-base')
11
12module.exports = class Agent extends AgentBase {
13  #options
14  #timeouts
15  #proxy
16  #noProxy
17  #ProxyAgent
18
19  constructor (options = {}) {
20    const { timeouts, proxy, noProxy, ...normalizedOptions } = normalizeOptions(options)
21
22    super(normalizedOptions)
23
24    this.#options = normalizedOptions
25    this.#timeouts = timeouts
26
27    if (proxy) {
28      this.#proxy = new URL(proxy)
29      this.#noProxy = noProxy
30      this.#ProxyAgent = getProxyAgent(proxy)
31    }
32  }
33
34  get proxy () {
35    return this.#proxy ? { url: this.#proxy } : {}
36  }
37
38  #getProxy (options) {
39    if (!this.#proxy) {
40      return
41    }
42
43    const proxy = getProxy(`${options.protocol}//${options.host}:${options.port}`, {
44      proxy: this.#proxy,
45      noProxy: this.#noProxy,
46    })
47
48    if (!proxy) {
49      return
50    }
51
52    const cacheKey = cacheOptions({
53      ...options,
54      ...this.#options,
55      timeouts: this.#timeouts,
56      proxy,
57    })
58
59    if (proxyCache.has(cacheKey)) {
60      return proxyCache.get(cacheKey)
61    }
62
63    let ProxyAgent = this.#ProxyAgent
64    if (Array.isArray(ProxyAgent)) {
65      ProxyAgent = this.isSecureEndpoint(options) ? ProxyAgent[1] : ProxyAgent[0]
66    }
67
68    const proxyAgent = new ProxyAgent(proxy, {
69      ...this.#options,
70      socketOptions: { family: this.#options.family },
71    })
72    proxyCache.set(cacheKey, proxyAgent)
73
74    return proxyAgent
75  }
76
77  // takes an array of promises and races them against the connection timeout
78  // which will throw the necessary error if it is hit. This will return the
79  // result of the promise race.
80  async #timeoutConnection ({ promises, options, timeout }, ac = new AbortController()) {
81    if (timeout) {
82      const connectionTimeout = timers.setTimeout(timeout, null, { signal: ac.signal })
83        .then(() => {
84          throw new Errors.ConnectionTimeoutError(`${options.host}:${options.port}`)
85        }).catch((err) => {
86          if (err.name === 'AbortError') {
87            return
88          }
89          throw err
90        })
91      promises.push(connectionTimeout)
92    }
93
94    let result
95    try {
96      result = await Promise.race(promises)
97      ac.abort()
98    } catch (err) {
99      ac.abort()
100      throw err
101    }
102    return result
103  }
104
105  async connect (request, options) {
106    // if the connection does not have its own lookup function
107    // set, then use the one from our options
108    options.lookup ??= this.#options.lookup
109
110    let socket
111    let timeout = this.#timeouts.connection
112    const isSecureEndpoint = this.isSecureEndpoint(options)
113
114    const proxy = this.#getProxy(options)
115    if (proxy) {
116      // some of the proxies will wait for the socket to fully connect before
117      // returning so we have to await this while also racing it against the
118      // connection timeout.
119      const start = Date.now()
120      socket = await this.#timeoutConnection({
121        options,
122        timeout,
123        promises: [proxy.connect(request, options)],
124      })
125      // see how much time proxy.connect took and subtract it from
126      // the timeout
127      if (timeout) {
128        timeout = timeout - (Date.now() - start)
129      }
130    } else {
131      socket = (isSecureEndpoint ? tls : net).connect(options)
132    }
133
134    socket.setKeepAlive(this.keepAlive, this.keepAliveMsecs)
135    socket.setNoDelay(this.keepAlive)
136
137    const abortController = new AbortController()
138    const { signal } = abortController
139
140    const connectPromise = socket[isSecureEndpoint ? 'secureConnecting' : 'connecting']
141      ? once(socket, isSecureEndpoint ? 'secureConnect' : 'connect', { signal })
142      : Promise.resolve()
143
144    await this.#timeoutConnection({
145      options,
146      timeout,
147      promises: [
148        connectPromise,
149        once(socket, 'error', { signal }).then((err) => {
150          throw err[0]
151        }),
152      ],
153    }, abortController)
154
155    if (this.#timeouts.idle) {
156      socket.setTimeout(this.#timeouts.idle, () => {
157        socket.destroy(new Errors.IdleTimeoutError(`${options.host}:${options.port}`))
158      })
159    }
160
161    return socket
162  }
163
164  addRequest (request, options) {
165    const proxy = this.#getProxy(options)
166    // it would be better to call proxy.addRequest here but this causes the
167    // http-proxy-agent to call its super.addRequest which causes the request
168    // to be added to the agent twice. since we only support 3 agents
169    // currently (see the required agents in proxy.js) we have manually
170    // checked that the only public methods we need to call are called in the
171    // next block. this could change in the future and presumably we would get
172    // failing tests until we have properly called the necessary methods on
173    // each of our proxy agents
174    if (proxy?.setRequestProps) {
175      proxy.setRequestProps(request, options)
176    }
177
178    request.setHeader('connection', this.keepAlive ? 'keep-alive' : 'close')
179
180    if (this.#timeouts.response) {
181      let responseTimeout
182      request.once('finish', () => {
183        setTimeout(() => {
184          request.destroy(new Errors.ResponseTimeoutError(request, this.#proxy))
185        }, this.#timeouts.response)
186      })
187      request.once('response', () => {
188        clearTimeout(responseTimeout)
189      })
190    }
191
192    if (this.#timeouts.transfer) {
193      let transferTimeout
194      request.once('response', (res) => {
195        setTimeout(() => {
196          res.destroy(new Errors.TransferTimeoutError(request, this.#proxy))
197        }, this.#timeouts.transfer)
198        res.once('close', () => {
199          clearTimeout(transferTimeout)
200        })
201      })
202    }
203
204    return super.addRequest(request, options)
205  }
206}
207 
codekingpro/portable-devtools · Team Ai