From fb1e8a235f212ff96e67de0fbff923e61e211171 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 1 Apr 2026 18:56:31 +0200 Subject: [PATCH 1/4] feat(core): Export a reusable function to add tracing headers --- packages/core/src/fetch.ts | 24 ++++++++------ packages/core/src/index.ts | 2 +- packages/core/test/lib/fetch.test.ts | 48 ++++++++++++++-------------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 4a26a5f06d80..07435810a798 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -21,6 +21,7 @@ import { type PolymorphicRequestHeaders = | Record | Array<[string, string]> + | Iterable> // the below is not precisely the Header type used in Request, but it'll pass duck-typing | { append: (key: string, value: string) => void; @@ -124,7 +125,7 @@ export function instrumentFetchRequest( // Examples: users re-using same options object for multiple fetch calls, frozen objects const options: { [key: string]: unknown } = { ...(handlerData.args[1] || {}) }; - const headers = _addTracingHeadersToFetchRequest( + const headers = getTracingHeadersForFetchRequest( request, options, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), @@ -176,17 +177,22 @@ export function _callOnRequestSpanEnd( } /** - * Adds sentry-trace and baggage headers to the various forms of fetch headers. - * exported only for testing purposes + * Builds merged fetch headers that include `sentry-trace` and `baggage` (and optionally `traceparent`) + * for the given request and init, without mutating the original request or options. + * Returns `undefined` when there is no `sentry-trace` value to attach. * - * When we determine if we should add a baggage header, there are 3 cases: - * 1. No previous baggage header -> add baggage - * 2. Previous baggage header has no sentry baggage values -> add our baggage - * 3. Previous baggage header has sentry baggage values -> do nothing (might have been added manually by users) + * @internal Exported for cross-package instrumentation (for example Cloudflare Workers fetcher bindings) + * and unit tests + * @hidden + * + * Baggage handling: + * 1. No previous baggage header → include Sentry baggage + * 2. Previous baggage has no Sentry entries → merge Sentry baggage in + * 3. Previous baggage already has Sentry entries → leave as-is (may be user-defined) */ // eslint-disable-next-line complexity -- yup it's this complicated :( -export function _addTracingHeadersToFetchRequest( - request: string | Request, +export function getTracingHeadersForFetchRequest( + request: string | URL | Request, fetchOptionsObj: { headers?: | { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7018ff63fa8a..cd4651ea4713 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -142,7 +142,7 @@ export { profiler } from './profiling'; // eslint thinks the entire function is deprecated (while only one overload is actually deprecated) // Therefore: // eslint-disable-next-line deprecation/deprecation -export { instrumentFetchRequest } from './fetch'; +export { instrumentFetchRequest, getTracingHeadersForFetchRequest } from './fetch'; export { trpcMiddleware } from './trpc'; export { wrapMcpServerWithSentry } from './integrations/mcp-server'; export { captureFeedback } from './feedback'; diff --git a/packages/core/test/lib/fetch.test.ts b/packages/core/test/lib/fetch.test.ts index 86087bcd167b..605e001ee560 100644 --- a/packages/core/test/lib/fetch.test.ts +++ b/packages/core/test/lib/fetch.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { HandlerDataFetch } from '../../src'; -import { _addTracingHeadersToFetchRequest, instrumentFetchRequest } from '../../src/fetch'; +import { getTracingHeadersForFetchRequest, instrumentFetchRequest } from '../../src/fetch'; import type { Span } from '../../src/types-hoist/span'; const { DEFAULT_SENTRY_TRACE, DEFAULT_BAGGAGE, hasSpansEnabled } = vi.hoisted(() => ({ @@ -31,7 +31,7 @@ vi.mock('../../src/utils/hasSpansEnabled', () => { }; }); -describe('_addTracingHeadersToFetchRequest', () => { +describe('getTracingHeadersForFetchRequest', () => { beforeEach(() => { vi.clearAllMocks(); hasSpansEnabled.mockReturnValue(false); @@ -47,7 +47,7 @@ describe('_addTracingHeadersToFetchRequest', () => { options: { headers: {} }, }, ])('attaches sentry headers (options: $options)', ({ options }) => { - expect(_addTracingHeadersToFetchRequest('/api/test', options)).toEqual({ + expect(getTracingHeadersForFetchRequest('/api/test', options)).toEqual({ 'sentry-trace': DEFAULT_SENTRY_TRACE, baggage: DEFAULT_BAGGAGE, }); @@ -56,7 +56,7 @@ describe('_addTracingHeadersToFetchRequest', () => { describe('and request headers are set in options', () => { it('attaches sentry headers to headers object', () => { - expect(_addTracingHeadersToFetchRequest('/api/test', { headers: { 'custom-header': 'custom-value' } })).toEqual( + expect(getTracingHeadersForFetchRequest('/api/test', { headers: { 'custom-header': 'custom-value' } })).toEqual( { 'sentry-trace': DEFAULT_SENTRY_TRACE, baggage: DEFAULT_BAGGAGE, @@ -66,7 +66,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }); it('attaches sentry headers to a Headers instance', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: new Headers({ 'custom-header': 'custom-value' }), }); @@ -81,7 +81,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }); it('attaches sentry headers to headers array', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: [['custom-header', 'custom-value']], }); @@ -96,7 +96,7 @@ describe('_addTracingHeadersToFetchRequest', () => { describe('and 3rd party baggage header is set', () => { it('adds additional sentry baggage values to Headers instance', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: new Headers({ baggage: 'custom-baggage=1,someVal=bar', }), @@ -112,7 +112,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }); it('adds additional sentry baggage values to headers array', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: [['baggage', 'custom-baggage=1,someVal=bar']], }); @@ -126,7 +126,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }); it('adds additional sentry baggage values to headers object', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: { baggage: 'custom-baggage=1,someVal=bar', }, @@ -141,7 +141,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }); it('adds additional sentry baggage values to headers object with arrays', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: { baggage: ['custom-baggage=1,someVal=bar', 'other-vendor-key=value'], }, @@ -158,7 +158,7 @@ describe('_addTracingHeadersToFetchRequest', () => { describe('and Sentry values are already set', () => { it('does not override them (Headers instance)', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: new Headers({ 'sentry-trace': CUSTOM_SENTRY_TRACE, baggage: CUSTOM_BAGGAGE, @@ -177,7 +177,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }); it('does not override them (headers array)', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: [ ['sentry-trace', CUSTOM_SENTRY_TRACE], ['baggage', CUSTOM_BAGGAGE], @@ -195,7 +195,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }); it('does not override them (headers object)', () => { - const returnedHeaders = _addTracingHeadersToFetchRequest('/api/test', { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { headers: { 'sentry-trace': CUSTOM_SENTRY_TRACE, baggage: CUSTOM_BAGGAGE, @@ -218,7 +218,7 @@ describe('_addTracingHeadersToFetchRequest', () => { describe('and no request headers are set', () => { it('attaches sentry headers', () => { const request = new Request('http://locahlost:3000/api/test'); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -236,7 +236,7 @@ describe('_addTracingHeadersToFetchRequest', () => { headers: new Headers({ 'custom-header': 'custom-value' }), }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -253,7 +253,7 @@ describe('_addTracingHeadersToFetchRequest', () => { headers: { 'custom-header': 'custom-value' }, }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -270,7 +270,7 @@ describe('_addTracingHeadersToFetchRequest', () => { headers: [['custom-header', 'custom-value']], }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -292,7 +292,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }), }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -309,7 +309,7 @@ describe('_addTracingHeadersToFetchRequest', () => { headers: [['baggage', 'custom-baggage=1,someVal=bar']], }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -327,7 +327,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }, }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -345,7 +345,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }, }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -367,7 +367,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }), }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -388,7 +388,7 @@ describe('_addTracingHeadersToFetchRequest', () => { ], }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -409,7 +409,7 @@ describe('_addTracingHeadersToFetchRequest', () => { }, }); - const returnedHeaders = _addTracingHeadersToFetchRequest(request, {}); + const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); From 1622527173888568358d7ae021438dec449ce8f3 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Tue, 7 Apr 2026 09:48:05 +0200 Subject: [PATCH 2/4] fixup! feat(core): Export a reusable function to add tracing headers --- packages/core/src/fetch.ts | 16 ++++++++++++++-- packages/core/test/lib/fetch.test.ts | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 07435810a798..22bf440e4a4e 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -240,7 +240,7 @@ export function getTracingHeadersForFetchRequest( } return newHeaders; - } else if (Array.isArray(originalHeaders)) { + } else if (isHeadersInitTupleArray(originalHeaders)) { const newHeaders = [...originalHeaders]; if (!originalHeaders.find(header => header[0] === 'sentry-trace')) { @@ -261,7 +261,7 @@ export function getTracingHeadersForFetchRequest( newHeaders.push(['baggage', baggage]); } - return newHeaders as PolymorphicRequestHeaders; + return newHeaders; } else { const existingSentryTraceHeader = 'sentry-trace' in originalHeaders ? originalHeaders['sentry-trace'] : undefined; const existingTraceparentHeader = 'traceparent' in originalHeaders ? originalHeaders.traceparent : undefined; @@ -327,6 +327,18 @@ function isHeaders(headers: unknown): headers is Headers { return typeof Headers !== 'undefined' && isInstanceOf(headers, Headers); } +/** `HeadersInit` array form: each entry is a [name, value] pair of strings. */ +function isHeadersInitTupleArray(headers: unknown): headers is [string, string][] { + if (!Array.isArray(headers)) { + return false; + } + + return headers.every( + (item): item is [string, string] => + Array.isArray(item) && item.length === 2 && typeof item[0] === 'string' && typeof item[1] === 'string', + ); +} + function getSpanStartOptions( url: string, method: string, diff --git a/packages/core/test/lib/fetch.test.ts b/packages/core/test/lib/fetch.test.ts index 605e001ee560..c0b766913c65 100644 --- a/packages/core/test/lib/fetch.test.ts +++ b/packages/core/test/lib/fetch.test.ts @@ -92,6 +92,21 @@ describe('getTracingHeadersForFetchRequest', () => { ['baggage', DEFAULT_BAGGAGE], ]); }); + + it('treats array with non-tuple items as headers object', () => { + const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + headers: ['not-a-tuple', 'also-not-a-tuple'], + }); + + // Falls through to the else branch (headers object handling) + // since the array items are not [string, string] tuples + expect(returnedHeaders).toEqual({ + '0': 'not-a-tuple', + '1': 'also-not-a-tuple', + 'sentry-trace': DEFAULT_SENTRY_TRACE, + baggage: DEFAULT_BAGGAGE, + }); + }); }); describe('and 3rd party baggage header is set', () => { From 4e235e3a7b10ea18905a3169e6f7e29b9e211b84 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 8 Apr 2026 15:17:17 +0100 Subject: [PATCH 3/4] fixup! feat(core): Export a reusable function to add tracing headers --- packages/core/src/fetch.ts | 4 +- packages/core/src/index.ts | 2 +- packages/core/test/lib/fetch.test.ts | 62 ++++++++++++++-------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 22bf440e4a4e..99b3c5a968ae 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -125,7 +125,7 @@ export function instrumentFetchRequest( // Examples: users re-using same options object for multiple fetch calls, frozen objects const options: { [key: string]: unknown } = { ...(handlerData.args[1] || {}) }; - const headers = getTracingHeadersForFetchRequest( + const headers = _INTERNAL_getTracingHeadersForFetchRequest( request, options, // If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), @@ -191,7 +191,7 @@ export function _callOnRequestSpanEnd( * 3. Previous baggage already has Sentry entries → leave as-is (may be user-defined) */ // eslint-disable-next-line complexity -- yup it's this complicated :( -export function getTracingHeadersForFetchRequest( +export function _INTERNAL_getTracingHeadersForFetchRequest( request: string | URL | Request, fetchOptionsObj: { headers?: diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cd4651ea4713..a7d8bf64ed88 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -142,7 +142,7 @@ export { profiler } from './profiling'; // eslint thinks the entire function is deprecated (while only one overload is actually deprecated) // Therefore: // eslint-disable-next-line deprecation/deprecation -export { instrumentFetchRequest, getTracingHeadersForFetchRequest } from './fetch'; +export { instrumentFetchRequest, _INTERNAL_getTracingHeadersForFetchRequest } from './fetch'; export { trpcMiddleware } from './trpc'; export { wrapMcpServerWithSentry } from './integrations/mcp-server'; export { captureFeedback } from './feedback'; diff --git a/packages/core/test/lib/fetch.test.ts b/packages/core/test/lib/fetch.test.ts index c0b766913c65..d0274b8efe82 100644 --- a/packages/core/test/lib/fetch.test.ts +++ b/packages/core/test/lib/fetch.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { HandlerDataFetch } from '../../src'; -import { getTracingHeadersForFetchRequest, instrumentFetchRequest } from '../../src/fetch'; +import { _INTERNAL_getTracingHeadersForFetchRequest, instrumentFetchRequest } from '../../src/fetch'; import type { Span } from '../../src/types-hoist/span'; const { DEFAULT_SENTRY_TRACE, DEFAULT_BAGGAGE, hasSpansEnabled } = vi.hoisted(() => ({ @@ -31,7 +31,7 @@ vi.mock('../../src/utils/hasSpansEnabled', () => { }; }); -describe('getTracingHeadersForFetchRequest', () => { +describe('_INTERNAL_getTracingHeadersForFetchRequest', () => { beforeEach(() => { vi.clearAllMocks(); hasSpansEnabled.mockReturnValue(false); @@ -47,7 +47,7 @@ describe('getTracingHeadersForFetchRequest', () => { options: { headers: {} }, }, ])('attaches sentry headers (options: $options)', ({ options }) => { - expect(getTracingHeadersForFetchRequest('/api/test', options)).toEqual({ + expect(_INTERNAL_getTracingHeadersForFetchRequest('/api/test', options)).toEqual({ 'sentry-trace': DEFAULT_SENTRY_TRACE, baggage: DEFAULT_BAGGAGE, }); @@ -56,17 +56,17 @@ describe('getTracingHeadersForFetchRequest', () => { describe('and request headers are set in options', () => { it('attaches sentry headers to headers object', () => { - expect(getTracingHeadersForFetchRequest('/api/test', { headers: { 'custom-header': 'custom-value' } })).toEqual( - { - 'sentry-trace': DEFAULT_SENTRY_TRACE, - baggage: DEFAULT_BAGGAGE, - 'custom-header': 'custom-value', - }, - ); + expect( + _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: { 'custom-header': 'custom-value' } }), + ).toEqual({ + 'sentry-trace': DEFAULT_SENTRY_TRACE, + baggage: DEFAULT_BAGGAGE, + 'custom-header': 'custom-value', + }); }); it('attaches sentry headers to a Headers instance', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: new Headers({ 'custom-header': 'custom-value' }), }); @@ -81,7 +81,7 @@ describe('getTracingHeadersForFetchRequest', () => { }); it('attaches sentry headers to headers array', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: [['custom-header', 'custom-value']], }); @@ -94,7 +94,7 @@ describe('getTracingHeadersForFetchRequest', () => { }); it('treats array with non-tuple items as headers object', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: ['not-a-tuple', 'also-not-a-tuple'], }); @@ -111,7 +111,7 @@ describe('getTracingHeadersForFetchRequest', () => { describe('and 3rd party baggage header is set', () => { it('adds additional sentry baggage values to Headers instance', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: new Headers({ baggage: 'custom-baggage=1,someVal=bar', }), @@ -127,7 +127,7 @@ describe('getTracingHeadersForFetchRequest', () => { }); it('adds additional sentry baggage values to headers array', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: [['baggage', 'custom-baggage=1,someVal=bar']], }); @@ -141,7 +141,7 @@ describe('getTracingHeadersForFetchRequest', () => { }); it('adds additional sentry baggage values to headers object', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: { baggage: 'custom-baggage=1,someVal=bar', }, @@ -156,7 +156,7 @@ describe('getTracingHeadersForFetchRequest', () => { }); it('adds additional sentry baggage values to headers object with arrays', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: { baggage: ['custom-baggage=1,someVal=bar', 'other-vendor-key=value'], }, @@ -173,7 +173,7 @@ describe('getTracingHeadersForFetchRequest', () => { describe('and Sentry values are already set', () => { it('does not override them (Headers instance)', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: new Headers({ 'sentry-trace': CUSTOM_SENTRY_TRACE, baggage: CUSTOM_BAGGAGE, @@ -192,7 +192,7 @@ describe('getTracingHeadersForFetchRequest', () => { }); it('does not override them (headers array)', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: [ ['sentry-trace', CUSTOM_SENTRY_TRACE], ['baggage', CUSTOM_BAGGAGE], @@ -210,7 +210,7 @@ describe('getTracingHeadersForFetchRequest', () => { }); it('does not override them (headers object)', () => { - const returnedHeaders = getTracingHeadersForFetchRequest('/api/test', { + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest('/api/test', { headers: { 'sentry-trace': CUSTOM_SENTRY_TRACE, baggage: CUSTOM_BAGGAGE, @@ -233,7 +233,7 @@ describe('getTracingHeadersForFetchRequest', () => { describe('and no request headers are set', () => { it('attaches sentry headers', () => { const request = new Request('http://locahlost:3000/api/test'); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -251,7 +251,7 @@ describe('getTracingHeadersForFetchRequest', () => { headers: new Headers({ 'custom-header': 'custom-value' }), }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -268,7 +268,7 @@ describe('getTracingHeadersForFetchRequest', () => { headers: { 'custom-header': 'custom-value' }, }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -285,7 +285,7 @@ describe('getTracingHeadersForFetchRequest', () => { headers: [['custom-header', 'custom-value']], }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -307,7 +307,7 @@ describe('getTracingHeadersForFetchRequest', () => { }), }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -324,7 +324,7 @@ describe('getTracingHeadersForFetchRequest', () => { headers: [['baggage', 'custom-baggage=1,someVal=bar']], }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -342,7 +342,7 @@ describe('getTracingHeadersForFetchRequest', () => { }, }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -360,7 +360,7 @@ describe('getTracingHeadersForFetchRequest', () => { }, }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -382,7 +382,7 @@ describe('getTracingHeadersForFetchRequest', () => { }), }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -403,7 +403,7 @@ describe('getTracingHeadersForFetchRequest', () => { ], }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); @@ -424,7 +424,7 @@ describe('getTracingHeadersForFetchRequest', () => { }, }); - const returnedHeaders = getTracingHeadersForFetchRequest(request, {}); + const returnedHeaders = _INTERNAL_getTracingHeadersForFetchRequest(request, {}); expect(returnedHeaders).toBeInstanceOf(Headers); From 3d15dcfbfc1a59e247cabe02e4dd2e2ca90bb85d Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Thu, 9 Apr 2026 12:51:25 +0200 Subject: [PATCH 4/4] feat: Loosen types and header check --- packages/core/src/fetch.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 99b3c5a968ae..0de9685528c3 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -19,9 +19,9 @@ import { } from './utils/url'; type PolymorphicRequestHeaders = - | Record - | Array<[string, string]> - | Iterable> + | Record + | Array<[string, unknown]> + | Iterable> // the below is not precisely the Header type used in Request, but it'll pass duck-typing | { append: (key: string, value: string) => void; @@ -183,7 +183,6 @@ export function _callOnRequestSpanEnd( * * @internal Exported for cross-package instrumentation (for example Cloudflare Workers fetcher bindings) * and unit tests - * @hidden * * Baggage handling: * 1. No previous baggage header → include Sentry baggage @@ -243,16 +242,17 @@ export function _INTERNAL_getTracingHeadersForFetchRequest( } else if (isHeadersInitTupleArray(originalHeaders)) { const newHeaders = [...originalHeaders]; - if (!originalHeaders.find(header => header[0] === 'sentry-trace')) { + if (!newHeaders.find(header => header[0] === 'sentry-trace')) { newHeaders.push(['sentry-trace', sentryTrace]); } - if (propagateTraceparent && traceparent && !originalHeaders.find(header => header[0] === 'traceparent')) { + if (propagateTraceparent && traceparent && !newHeaders.find(header => header[0] === 'traceparent')) { newHeaders.push(['traceparent', traceparent]); } const prevBaggageHeaderWithSentryValues = originalHeaders.find( - header => header[0] === 'baggage' && baggageHeaderHasSentryBaggageValues(header[1]), + header => + header[0] === 'baggage' && typeof header[1] === 'string' && baggageHeaderHasSentryBaggageValues(header[1]), ); if (baggage && !prevBaggageHeaderWithSentryValues) { @@ -319,7 +319,11 @@ function endSpan(span: Span, handlerData: HandlerDataFetch): void { span.end(); } -function baggageHeaderHasSentryBaggageValues(baggageHeader: string): boolean { +function baggageHeaderHasSentryBaggageValues(baggageHeader: unknown): boolean { + if (typeof baggageHeader !== 'string') { + return false; + } + return baggageHeader.split(',').some(baggageEntry => baggageEntry.trim().startsWith(SENTRY_BAGGAGE_KEY_PREFIX)); } @@ -328,14 +332,13 @@ function isHeaders(headers: unknown): headers is Headers { } /** `HeadersInit` array form: each entry is a [name, value] pair of strings. */ -function isHeadersInitTupleArray(headers: unknown): headers is [string, string][] { +function isHeadersInitTupleArray(headers: unknown): headers is [string, unknown][] { if (!Array.isArray(headers)) { return false; } return headers.every( - (item): item is [string, string] => - Array.isArray(item) && item.length === 2 && typeof item[0] === 'string' && typeof item[1] === 'string', + (item): item is [string, unknown] => Array.isArray(item) && item.length === 2 && typeof item[0] === 'string', ); }