File Explorer

/proc/4/root/var/lang/lib/node_modules/npm/node_modules/@npmcli/agent/lib

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

agents.js7.6 KB · 259 lines
'use strict' const net = require('net')const tls = require('tls')const { once } = require('events')const timers = require('timers/promises')const { normalizeOptions, cacheOptions } = require('./options')const { getProxy, getProxyAgent, proxyCache } = require('./proxy.js')const Errors = require('./errors.js')const { Agent: AgentBase } = require('agent-base') module.exports = class Agent extends AgentBase {  #options  #timeouts  #proxy  #noProxy  #ProxyAgent   constructor (options = {}) {    const { timeouts, proxy, noProxy, ...normalizedOptions } = normalizeOptions(options)     super(normalizedOptions)     this.#options = normalizedOptions    this.#timeouts = timeouts     if (proxy) {      this.#proxy = new URL(proxy)      this.#noProxy = noProxy      this.#ProxyAgent = getProxyAgent(proxy)    }  }   get proxy () {    return this.#proxy ? { url: this.#proxy } : {}  }   #getProxy (options) {    if (!this.#proxy) {      return    }     const proxy = getProxy(`${options.protocol}//${options.host}:${options.port}`, {      proxy: this.#proxy,      noProxy: this.#noProxy,    })     if (!proxy) {      return    }     const cacheKey = cacheOptions({      ...options,      ...this.#options,      timeouts: this.#timeouts,      proxy,    })     if (proxyCache.has(cacheKey)) {      return proxyCache.get(cacheKey)    }     let ProxyAgent = this.#ProxyAgent    if (Array.isArray(ProxyAgent)) {      ProxyAgent = this.isSecureEndpoint(options) ? ProxyAgent[1] : ProxyAgent[0]    }     const proxyAgent = new ProxyAgent(proxy, {      ...this.#options,      socketOptions: { family: this.#options.family },    })    proxyCache.set(cacheKey, proxyAgent)     return proxyAgent  }   // takes an array of promises and races them against the connection timeout  // which will throw the necessary error if it is hit. This will return the  // result of the promise race.  async #timeoutConnection ({ promises, options, timeout }, ac = new AbortController()) {    if (timeout) {      const connectionTimeout = timers.setTimeout(timeout, null, { signal: ac.signal })        .then(() => {          throw new Errors.ConnectionTimeoutError(`${options.host}:${options.port}`)        }).catch((err) => {          if (err.name === 'AbortError') {            return          }          throw err        })      promises.push(connectionTimeout)    }     let result    try {      result = await Promise.race(promises)      ac.abort()    } catch (err) {      ac.abort()      throw err    }    return result  }   async connect (request, options) {    // if the connection does not have its own lookup function    // set, then use the one from our options    options.lookup ??= this.#options.lookup     let socket    let timeout = this.#timeouts.connection    const isSecureEndpoint = this.isSecureEndpoint(options)     const proxy = this.#getProxy(options)    if (proxy) {      // some of the proxies will wait for the socket to fully connect before      // returning so we have to await this while also racing it against the      // connection timeout.      const start = Date.now()      socket = await this.#timeoutConnection({        options,        timeout,        promises: [proxy.connect(request, options)],      })      // see how much time proxy.connect took and subtract it from      // the timeout      if (timeout) {        timeout = timeout - (Date.now() - start)      }    } else {      socket = (isSecureEndpoint ? tls : net).connect(options)    }     socket.setKeepAlive(this.keepAlive, this.keepAliveMsecs)    socket.setNoDelay(this.keepAlive)     const abortController = new AbortController()    const { signal } = abortController     const connectPromise = socket[isSecureEndpoint ? 'secureConnecting' : 'connecting']      ? once(socket, isSecureEndpoint ? 'secureConnect' : 'connect', { signal })      : Promise.resolve()     await this.#timeoutConnection({      options,      timeout,      promises: [        connectPromise,        once(socket, 'error', { signal }).then((err) => {          throw err[0]        }),      ],    }, abortController)     if (this.#timeouts.idle) {      socket.setTimeout(this.#timeouts.idle, () => {        socket.destroy(new Errors.IdleTimeoutError(`${options.host}:${options.port}`))      })    }     return socket  }   addRequest (request, options) {    const proxy = this.#getProxy(options)    // it would be better to call proxy.addRequest here but this causes the    // http-proxy-agent to call its super.addRequest which causes the request    // to be added to the agent twice. since we only support 3 agents    // currently (see the required agents in proxy.js) we have manually    // checked that the only public methods we need to call are called in the    // next block. this could change in the future and presumably we would get    // failing tests until we have properly called the necessary methods on    // each of our proxy agents    if (proxy?.setRequestProps) {      proxy.setRequestProps(request, options)    }     request.setHeader('connection', this.keepAlive ? 'keep-alive' : 'close')     if (this.#timeouts.response) {      let responseTimeout      request.once('finish', () => {        setTimeout(() => {          request.destroy(new Errors.ResponseTimeoutError(request, this.#proxy))        }, this.#timeouts.response)      })      request.once('response', () => {        clearTimeout(responseTimeout)      })    }     if (this.#timeouts.transfer) {      let transferTimeout      request.once('response', (res) => {        setTimeout(() => {          res.destroy(new Errors.TransferTimeoutError(request, this.#proxy))        }, this.#timeouts.transfer)        res.once('close', () => {          clearTimeout(transferTimeout)        })      })    }     return super.addRequest(request, options)  }   // When connect() rejects, agent-base removes only its placeholder socket, so Node never drains this.requests[name] and requests queued past maxSockets hang forever.  // On a failure we dispatch the next queued request ourselves.  // See npm/cli#9386 and TooTallNate/proxy-agents#427.  createSocket (req, options, cb) {    super.createSocket(req, options, (err, socket) => {      if (err) {        this.#drainPendingRequests(req, options)      }      cb(err, socket)    })  }   // Dispatch the next request queued behind maxSockets, reusing the slot the failed connection freed.  #drainPendingRequests (failedReq, options) {    const name = this.getName(options)    const queue = this.requests[name]    if (!queue || queue.length === 0) {      return    }     // Node's removeSocket() picks a queued request without shifting it off, so drop the failed one to avoid dispatching it twice.    const failedIndex = queue.indexOf(failedReq)    if (failedIndex !== -1) {      queue.splice(failedIndex, 1)    }    if (queue.length === 0) {      delete this.requests[name]      return    }     // Safety belt: only dispatch if a socket slot is genuinely free.    const socketCount = this.sockets[name] ? this.sockets[name].length : 0    if (socketCount >= this.maxSockets || this.totalSocketCount >= this.maxTotalSockets) {      return    }     const nextReq = queue.shift()    if (queue.length === 0) {      delete this.requests[name]    }     // All queued requests share this origin, so the failed request's options suit the next one.    // createSocket() recurses here if this connection also fails, draining the whole queue.    this.createSocket(nextReq, options, (err, socket) => {      if (err) {        nextReq.onSocket(null, err)      } else {        nextReq.onSocket(socket)      }    })  }}