TooTallNate / proxy-agents

Node.js HTTP Proxy Agents Monorepo
https://proxy-agents.n8.io
917 stars 238 forks source link

Support global registration like `global-agent` #314

Open segevfiner opened 4 months ago

segevfiner commented 4 months ago

The package global-agent supports registering a proxy agent globally, by registering under the globalAgent property and an additional hack for packages that don't use it. It would be nice to have this functionality available in this package as it has a more robust proxy agent.

segevfiner commented 4 months ago

So it goes something like this based on the code in global-agent:

import http from 'http';
import https from 'https';
import semver from 'semver';

export default function bindHttpMethod(
  originalMethod: (...args: unknown[]) => unknown,
  agent: AgentType,
  forceGlobalAgent: boolean
) {
  return (...args: unknown[]) => {
    let url;
    let options: Record<string, unknown>;
    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] as object),
        };
        callback = args[2];
      }
    } else {
      options = {
        ...(args[0] as object),
      };
      callback = args[1];
    }

    if (forceGlobalAgent) {
      options.agent = agent;
    } else {
      if (!options.agent) {
        options.agent = agent;
      }

      if (options.agent === http.globalAgent || options.agent === https.globalAgent) {
        options.agent = agent;
      }
    }

    if (url) {
      return originalMethod(url, options, callback);
    } else {
      return originalMethod(options, callback);
    }
  };
}

const httpGet = http.get;
const httpRequest = http.request;
const httpsGet = https.get;
const httpsRequest = https.request;

export function enableGlobalProxyAgent(proxyAgent: ProxyAgent, configuration?: { forceGlobalAgent?: boolean }): void {
  // Overriding globalAgent was added in v11.7.
  // @see https://nodejs.org/uk/blog/release/v11.7.0/
  if (semver.gte(process.version, 'v11.7.0')) {
    // @see https://github.com/facebook/flow/issues/7670
    // @ts-ignore Node.js version compatibility
    http.globalAgent = proxyAgent;

    // @ts-ignore Node.js version compatibility
    https.globalAgent = proxyAgent;
  }

  // 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 (semver.gte(process.version, 'v10.0.0')) {
    // @ts-expect-error seems like we are using wrong type for httpAgent
    http.get = bindHttpMethod(httpGet, proxyAgent, configuration?.forceGlobalAgent ?? true);

    // @ts-expect-error seems like we are using wrong type for httpAgent
    http.request = bindHttpMethod(httpRequest, proxyAgent, configuration?.forceGlobalAgent ?? true);

    // @ts-expect-error seems like we are using wrong type for httpAgent
    https.get = bindHttpMethod(httpsGet, proxyAgent, configuration?.forceGlobalAgent ?? true);

    // @ts-expect-error seems like we are using wrong type for httpAgent
    https.request = bindHttpMethod(httpsRequest, proxyAgent, configuration?.forceGlobalAgent ?? true);
  } else {
    // eslint-disable-next-line no-console
    console.warn('attempt to initialize global proxy-agent in unsupported Node.js version was ignored');
  }
}

@TooTallNate What do you think?