From 5e84d3938eed0b2c0b24292fb250a32de43af297 Mon Sep 17 00:00:00 2001 From: Yannis Gerbessiotis Date: Fri, 23 Jan 2026 13:58:04 +0200 Subject: [PATCH] Fix trace header injection for calls using URL-first signature --- src/initializers/riviere.ts | 31 ++++++++++---- test/initializers/riviere.test.ts | 68 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/src/initializers/riviere.ts b/src/initializers/riviere.ts index d5154af..a7621ba 100644 --- a/src/initializers/riviere.ts +++ b/src/initializers/riviere.ts @@ -78,19 +78,34 @@ const init = (config, orkaOptions) => { }); const handler = { apply: (target, thisArg, argumentsList) => { + const args = [...argumentsList]; try { - const [requestArgs = {}] = argumentsList || []; - requestArgs.headers = requestArgs.headers || {}; - const traceHeaderName = config.traceHeaderName && config.traceHeaderName.toLowerCase(); - const traceId = getRequestContext()?.get('requestId') || getRequestContext()?.get('correlationId'); - appendHeadersFromStore(requestArgs, getRequestContext(), config); - if (!requestArgs.headers[traceHeaderName] && traceId) { - requestArgs.headers[traceHeaderName] = traceId; + // http.request supports two signatures: + // - http.request(options[, callback]) + // - http.request(url[, options][, callback]) + // Find the options object based on the first argument type + let optionsIndex = 0; + const firstArg = args[0]; + if (typeof firstArg === 'string' || firstArg instanceof URL) { + optionsIndex = 1; + if (!args[1] || typeof args[1] === 'function') { + args.splice(1, 0, {}); + } + } + const requestArgs = args[optionsIndex] || {}; + if (typeof requestArgs === 'object' && requestArgs !== null) { + requestArgs.headers = requestArgs.headers || {}; + const traceHeaderName = config.traceHeaderName && config.traceHeaderName.toLowerCase(); + const traceId = getRequestContext()?.get('requestId') || getRequestContext()?.get('correlationId'); + appendHeadersFromStore(requestArgs, getRequestContext(), config); + if (!requestArgs.headers[traceHeaderName] && traceId) { + requestArgs.headers[traceHeaderName] = traceId; + } } } catch (e) { getLogger('orka.riviere').error(e); } - return target.apply(thisArg, argumentsList); + return target.apply(thisArg, args); } }; http.request = new Proxy(http.request, handler); diff --git a/test/initializers/riviere.test.ts b/test/initializers/riviere.test.ts index 8d73df8..cc999f8 100644 --- a/test/initializers/riviere.test.ts +++ b/test/initializers/riviere.test.ts @@ -1,6 +1,7 @@ import * as riviere from '@workablehr/riviere'; import * as log4js from 'log4js'; import * as sinon from 'sinon'; +import * as https from 'https'; const sandbox = sinon.createSandbox(); @@ -95,4 +96,71 @@ describe('riviere', () => { ); }); }); + + context('when proxying https.request', () => { + let originalRequest; + let errorLoggerStub; + + const baseConfig = { + riviere: { + enabled: true, + outbound: { + enabled: true, + request: { enabled: true } + }, + inbound: { + request: { enabled: true } + } + }, + traceHeaderName: 'x-request-id' + }; + + beforeEach(() => { + originalRequest = https.request; + errorLoggerStub = sandbox.stub(log4js.getLogger('orka.riviere').constructor.prototype, 'error'); + }); + + afterEach(() => { + (https as any).request = originalRequest; + }); + + it('should handle URL string as first argument without error', () => { + orkaRiviereInitializer.default(baseConfig, orkaOptions); + + const req = https.request('https://example.com/api/test', { method: 'GET' }, () => { /* noop */ }); + req.on('error', () => { /* expected - we're destroying the socket */ }); + req.destroy(); + + const headerError = errorLoggerStub.args.find( + args => args[0]?.message?.includes('Cannot create property') + ); + (headerError === undefined).should.be.true(); + }); + + it('should handle URL object as first argument without error', () => { + orkaRiviereInitializer.default(baseConfig, orkaOptions); + + const req = https.request(new URL('https://example.com/api/test'), { method: 'GET' }, () => { /* noop */ }); + req.on('error', () => { /* expected - we're destroying the socket */ }); + req.destroy(); + + const headerError = errorLoggerStub.args.find( + args => args[0]?.message?.includes('Cannot create property') + ); + (headerError === undefined).should.be.true(); + }); + + it('should handle options object as first argument (original behavior)', () => { + orkaRiviereInitializer.default(baseConfig, orkaOptions); + + const req = https.request({ hostname: 'example.com', path: '/api/test', method: 'GET' }, () => { /* noop */ }); + req.on('error', () => { /* expected - we're destroying the socket */ }); + req.destroy(); + + const headerError = errorLoggerStub.args.find( + args => args[0]?.message?.includes('Cannot create property') + ); + (headerError === undefined).should.be.true(); + }); + }); });