From cf0c6cb6c64ad7bba3904bbe025863770a4d8594 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 2 Mar 2026 13:14:22 -0500 Subject: [PATCH 1/2] feat: expose agent config in node sdk --- lib/core/http.js | 12 ++++-- lib/index.js | 24 +++++++++++- lib/types.d.ts | 2 + tests/core/http.test.js | 84 +++++++++++++++++++++++++++++++++++++++++ tests/sdk.test.js | 54 ++++++++++++++++++++++++++ 5 files changed, 172 insertions(+), 4 deletions(-) diff --git a/lib/core/http.js b/lib/core/http.js index 52531fe..1910aee 100644 --- a/lib/core/http.js +++ b/lib/core/http.js @@ -6,8 +6,9 @@ const axios = require('axios'); * @param {string} appUuid * @param {string} apiKey * @param {import('../types').HttpConfig} config + * @param {{ httpAgent?: import('http').Agent, httpsAgent?: import('https').Agent }} [agents] */ -module.exports = (appUuid, apiKey, config) => { +module.exports = (appUuid, apiKey, config, { httpAgent, httpsAgent } = {}) => { const request = ( method, path, @@ -28,7 +29,7 @@ module.exports = (appUuid, apiKey, config) => { headers['api-key'] = apiKey; } - return axios({ + const requestConfig = { url: path.startsWith('https://') || path.startsWith('http://') ? path @@ -38,7 +39,12 @@ module.exports = (appUuid, apiKey, config) => { data, validateStatus: (_) => true, responseType, - }); + }; + + if (httpAgent) requestConfig.httpAgent = httpAgent; + if (httpsAgent) requestConfig.httpsAgent = httpsAgent; + + return axios(requestConfig); }; const get = (path, headers) => request('GET', path, headers); diff --git a/lib/index.js b/lib/index.js index 54d57bb..07b18d6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,5 @@ const crypto = require('crypto'); +const http = require('http'); const https = require('https'); const retry = require('async-retry'); const { Buffer } = require('buffer'); @@ -75,9 +76,30 @@ class EvervaultClient { curve = options.curve; } + if ( + options.httpAgent != null && + !(options.httpAgent instanceof http.Agent) + ) { + throw new errors.EvervaultError( + 'options.httpAgent must be an instance of http.Agent' + ); + } + + if ( + options.httpsAgent != null && + !(options.httpsAgent instanceof https.Agent) + ) { + throw new errors.EvervaultError( + 'options.httpsAgent must be an instance of https.Agent' + ); + } + this.curve = curve; this.retry = options.retry; - this.http = Http(appId, apiKey, this.config.http); + this.http = Http(appId, apiKey, this.config.http, { + httpAgent: options.httpAgent, + httpsAgent: options.httpsAgent, + }); this.crypto = Crypto(this.config.encryption[curve]); this.httpsHelper = httpsHelper; this.apiKey = apiKey; diff --git a/lib/types.d.ts b/lib/types.d.ts index 7f86138..240a07e 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -44,6 +44,8 @@ export interface SdkOptions { curve?: SupportedCurve; retry?: boolean; enableOutboundRelay?: boolean; + httpAgent?: import('http').Agent; + httpsAgent?: import('https').Agent; } export interface PCRs { diff --git a/tests/core/http.test.js b/tests/core/http.test.js index 0fef8d6..2329a7d 100644 --- a/tests/core/http.test.js +++ b/tests/core/http.test.js @@ -1,3 +1,5 @@ +const http = require('http'); +const https = require('https'); const { expect } = require('chai'); const { errors } = require('../../lib/utils'); const nock = require('nock'); @@ -593,6 +595,88 @@ describe('Http Module', () => { }); }); + describe('agent forwarding', () => { + const rewire = require('rewire'); + + /** + * Creates a rewired http module with a stub axios and optional agents, + * returning both the client and a getter for the last captured axios config. + */ + const buildClientWithAxiosStub = (agents = {}) => { + const httpModule = rewire('../../lib/core/http'); + let capturedConfig; + const axiosStub = (cfg) => { + capturedConfig = cfg; + return Promise.resolve({ + status: 200, + data: {}, + headers: { 'x-poll-interval': '5' }, + }); + }; + httpModule.__set__('axios', axiosStub); + const client = httpModule(testAppId, testApiKey, testValidConfig, agents); + return { + client, + getCapturedConfig: () => capturedConfig, + }; + }; + + context('when httpAgent is provided', () => { + it('sets httpAgent on the axios request config and does not set httpsAgent', async () => { + const agent = new http.Agent(); + const { client, getCapturedConfig } = buildClientWithAxiosStub({ + httpAgent: agent, + }); + + await client.getRelayOutboundConfig(); + + expect(getCapturedConfig().httpAgent).to.equal(agent); + expect(getCapturedConfig().httpsAgent).to.be.undefined; + }); + }); + + context('when httpsAgent is provided', () => { + it('sets httpsAgent on the axios request config and does not set httpAgent', async () => { + const agent = new https.Agent(); + const { client, getCapturedConfig } = buildClientWithAxiosStub({ + httpsAgent: agent, + }); + + await client.getRelayOutboundConfig(); + + expect(getCapturedConfig().httpsAgent).to.equal(agent); + expect(getCapturedConfig().httpAgent).to.be.undefined; + }); + }); + + context('when both agents are provided', () => { + it('sets both httpAgent and httpsAgent on the axios request config', async () => { + const httpAgentInstance = new http.Agent(); + const httpsAgentInstance = new https.Agent(); + const { client, getCapturedConfig } = buildClientWithAxiosStub({ + httpAgent: httpAgentInstance, + httpsAgent: httpsAgentInstance, + }); + + await client.getRelayOutboundConfig(); + + expect(getCapturedConfig().httpAgent).to.equal(httpAgentInstance); + expect(getCapturedConfig().httpsAgent).to.equal(httpsAgentInstance); + }); + }); + + context('when no agents are provided', () => { + it('does not set httpAgent or httpsAgent on the axios request config', async () => { + const { client, getCapturedConfig } = buildClientWithAxiosStub(); + + await client.getRelayOutboundConfig(); + + expect(getCapturedConfig().httpAgent).to.be.undefined; + expect(getCapturedConfig().httpsAgent).to.be.undefined; + }); + }); + }); + describe('getRelayOutboundConfig', () => { context('Given an api key', () => { context('Request is successful', () => { diff --git a/tests/sdk.test.js b/tests/sdk.test.js index 88f2d31..2519655 100644 --- a/tests/sdk.test.js +++ b/tests/sdk.test.js @@ -1,3 +1,5 @@ +const http = require('http'); +const https = require('https'); const chai = require('chai'); chai.use(require('sinon-chai')); const { expect } = chai; @@ -25,6 +27,58 @@ describe('evervault client', () => { const test = () => new Evervault(testAppId, ''); expect(test).to.throw(errors.EvervaultError); }); + + context('agent validation', () => { + it('should throw if httpAgent is not an instance of http.Agent', () => { + const Evervault = require('../lib'); + const test = () => + new Evervault(testAppId, testApiKey, { httpAgent: { fake: true } }); + expect(test).to.throw( + errors.EvervaultError, + 'options.httpAgent must be an instance of http.Agent' + ); + }); + + it('should throw if httpsAgent is not an instance of https.Agent', () => { + const Evervault = require('../lib'); + const test = () => + new Evervault(testAppId, testApiKey, { + httpsAgent: { fake: true }, + }); + expect(test).to.throw( + errors.EvervaultError, + 'options.httpsAgent must be an instance of https.Agent' + ); + }); + + it('should accept a valid http.Agent without throwing', () => { + const Evervault = require('../lib'); + const test = () => + new Evervault(testAppId, testApiKey, { + httpAgent: new http.Agent(), + }); + expect(test).to.not.throw(); + }); + + it('should accept a valid https.Agent without throwing', () => { + const Evervault = require('../lib'); + const test = () => + new Evervault(testAppId, testApiKey, { + httpsAgent: new https.Agent(), + }); + expect(test).to.not.throw(); + }); + + it('should accept both a valid http.Agent and https.Agent without throwing', () => { + const Evervault = require('../lib'); + const test = () => + new Evervault(testAppId, testApiKey, { + httpAgent: new http.Agent(), + httpsAgent: new https.Agent(), + }); + expect(test).to.not.throw(); + }); + }); }); context('calling methods that require api keys', () => { From 9db608e41170099a8df760fa1e9a3ebcb0bf6130 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 2 Mar 2026 13:21:11 -0500 Subject: [PATCH 2/2] chore: add changeset --- .changeset/strong-singers-visit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/strong-singers-visit.md diff --git a/.changeset/strong-singers-visit.md b/.changeset/strong-singers-visit.md new file mode 100644 index 0000000..b445f1e --- /dev/null +++ b/.changeset/strong-singers-visit.md @@ -0,0 +1,5 @@ +--- +'@evervault/sdk': minor +--- + +Introduce agent parameters to SDK constructor to allow greater networking control from the Node SDK