From b4af1aded77e65fd2c78db6a577888f192a69586 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 11 Sep 2026 13:04:18 +0200 Subject: [PATCH] feat(core): Accept `CollectBehavior` shorthand for `dataCollection.httpHeaders` The docs and the v10.57 changelog advertise `httpHeaders: { deny: [...] }`, but the resolver only understood the `{ request, response }` shape, so a shorthand silently fell through to the defaults. Normalize a boolean or allow/deny object into both directions while keeping `{}` and directional objects on the existing merge path. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 1 + packages/core/src/index.ts | 1 + packages/core/src/types/datacollection.ts | 19 ++++--- .../resolveDataCollectionOptions.ts | 31 +++++++++-- .../resolveDataCollectionOptions.test.ts | 53 +++++++++++++++++++ 5 files changed, 94 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38fd95259776..d65296eaca89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Work in this release was contributed by @psh4607, @thijsw, @trinitiwowka, @nehaprasad-dev, @JealousGx, @Jxxunnn, @eddie333016, @davidmurdoch, @yashschandra, @atharv-sys32, @AG0708, @birkskyum, @mkly, @mcbbugu, @suhailopensource, @zkasuran, @mohd-akram, @RealBhupesh, @halillusion, @psang39, and @hafzism. Thank you for your contributions! +- feat(core): Accept a `CollectBehavior` shorthand for `dataCollection.httpHeaders`. Passing `true`, `false`, `{ allow: [...] }` or `{ deny: [...] }` now applies to both request and response headers; `{ request, response }` still controls each direction independently. - feat(langchain)!: Emit `gen_ai.pipeline.name` instead of `langchain.chain.name` on LangChain chain spans. The attribute is omitted when the chain is unnamed. - feat(deno)!: Rename several default integrations to match the other SDKs ([#22404](https://github.com/getsentry/sentry-javascript/pull/22404)). The `deno*Integration` exports are kept as deprecated aliases. If you were relying on the names (for example, to disable them), then note that these have changed: - `DenoAmqplib` => `Amqplib` diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0eef1ba9f64c..efe39a7e1cf3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -401,6 +401,7 @@ export type { CollectBehavior, DataCollection, HttpBodyCollectionTarget, + HttpHeadersCollection, ResolvedDataCollection, } from './types/datacollection'; export type { ClientOptions, CoreOptions as Options } from './types/options'; diff --git a/packages/core/src/types/datacollection.ts b/packages/core/src/types/datacollection.ts index db4f97a82a77..005e0a9fdd9d 100644 --- a/packages/core/src/types/datacollection.ts +++ b/packages/core/src/types/datacollection.ts @@ -10,6 +10,14 @@ export type CollectBehavior = boolean | { allow: string[] } | { deny: string[] } export type HttpBodyCollectionTarget = 'incomingRequest' | 'outgoingRequest' | 'incomingResponse' | 'outgoingResponse'; +/** + * Controls HTTP header collection per direction. + */ +export interface HttpHeadersCollection { + request?: CollectBehavior; + response?: CollectBehavior; +} + /** * Controls what data the SDK collects and sends to Sentry. * @@ -30,12 +38,11 @@ export interface DataCollection { /** * Controls HTTP header collection for requests and responses. + * + * Accepts a `CollectBehavior` applied to both directions, or `{ request, response }` to control each independently. * @default { request: true, response: true } */ - httpHeaders?: { - request?: CollectBehavior; - response?: CollectBehavior; - }; + httpHeaders?: CollectBehavior | HttpHeadersCollection; /** * Which HTTP body types to collect. An omitted value collects all body types valid for the @@ -118,8 +125,8 @@ export interface DataCollection { /** * Fully resolved `DataCollection` with all defaults applied. */ -export type ResolvedDataCollection = Required & { - httpHeaders: Required>; +export type ResolvedDataCollection = Required> & { + httpHeaders: Required; graphQL: Required>; genAI: Required>; }; diff --git a/packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts b/packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts index 5e8fee59912f..cb4ab2437799 100644 --- a/packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts +++ b/packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts @@ -1,4 +1,9 @@ -import type { DataCollection, ResolvedDataCollection } from '../../types/datacollection'; +import type { + CollectBehavior, + DataCollection, + HttpHeadersCollection, + ResolvedDataCollection, +} from '../../types/datacollection'; const DEFAULTS: ResolvedDataCollection = { userInfo: true, @@ -14,6 +19,25 @@ const DEFAULTS: ResolvedDataCollection = { frameContextLines: 5, }; +function isCollectBehavior(value: CollectBehavior | HttpHeadersCollection): value is CollectBehavior { + return typeof value === 'boolean' || 'allow' in value || 'deny' in value; +} + +function resolveHttpHeaders(httpHeaders: DataCollection['httpHeaders']): ResolvedDataCollection['httpHeaders'] { + if (httpHeaders === undefined) { + return { ...DEFAULTS.httpHeaders }; + } + + if (isCollectBehavior(httpHeaders)) { + return { request: httpHeaders, response: httpHeaders }; + } + + return { + request: httpHeaders.request ?? DEFAULTS.httpHeaders.request, + response: httpHeaders.response ?? DEFAULTS.httpHeaders.response, + }; +} + /** * Resolves the effective `DataCollection` configuration from client options. * @@ -27,10 +51,7 @@ export function resolveDataCollectionOptions(options: { dataCollection?: DataCol return { userInfo: dc.userInfo ?? DEFAULTS.userInfo, cookies: dc.cookies ?? DEFAULTS.cookies, - httpHeaders: { - request: dc.httpHeaders?.request ?? DEFAULTS.httpHeaders.request, - response: dc.httpHeaders?.response ?? DEFAULTS.httpHeaders.response, - }, + httpHeaders: resolveHttpHeaders(dc.httpHeaders), httpBodies: dc.httpBodies ?? DEFAULTS.httpBodies, urlQueryParams: dc.urlQueryParams ?? DEFAULTS.urlQueryParams, graphQL: { diff --git a/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts b/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts index e9fe9da40133..133ae35799dc 100644 --- a/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts +++ b/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts @@ -69,6 +69,59 @@ describe('resolveDataCollectionOptions', () => { expect(result.httpHeaders.response).toBe(true); }); + it('merges nested httpHeaders partially for the response direction', () => { + const result = resolveDataCollectionOptions({ + dataCollection: { + httpHeaders: { response: { allow: ['content-type'] } }, + }, + }); + + expect(result.httpHeaders).toEqual({ request: true, response: { allow: ['content-type'] } }); + }); + + it('resolves independent request and response header settings', () => { + const result = resolveDataCollectionOptions({ + dataCollection: { + httpHeaders: { request: { allow: ['x-request-id'] }, response: false }, + }, + }); + + expect(result.httpHeaders).toEqual({ request: { allow: ['x-request-id'] }, response: false }); + }); + + it('treats an empty httpHeaders object as directional config with defaults', () => { + expect(resolveDataCollectionOptions({ dataCollection: { httpHeaders: {} } }).httpHeaders).toEqual({ + request: true, + response: true, + }); + }); + + it('applies boolean httpHeaders shorthand to both directions', () => { + expect(resolveDataCollectionOptions({ dataCollection: { httpHeaders: false } }).httpHeaders).toEqual({ + request: false, + response: false, + }); + + expect(resolveDataCollectionOptions({ dataCollection: { httpHeaders: true } }).httpHeaders).toEqual({ + request: true, + response: true, + }); + }); + + it('applies allow/deny httpHeaders shorthand to both directions', () => { + const deny = { deny: ['forwarded', '-ip'] }; + expect(resolveDataCollectionOptions({ dataCollection: { httpHeaders: deny } }).httpHeaders).toEqual({ + request: deny, + response: deny, + }); + + const allow = { allow: ['content-type'] }; + expect(resolveDataCollectionOptions({ dataCollection: { httpHeaders: allow } }).httpHeaders).toEqual({ + request: allow, + response: allow, + }); + }); + it('merges nested genAI partially', () => { const result = resolveDataCollectionOptions({ dataCollection: {