Skip to content

Commit 304d5ac

Browse files
committed
feat(core): Send user-agent header with envelope requests
1 parent 6229224 commit 304d5ac

File tree

21 files changed

+181
-26
lines changed

21 files changed

+181
-26
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
transportOptions: {
8+
headers: {
9+
'x-custom-header': 'custom-value',
10+
},
11+
},
12+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Sentry.captureException(new Error('test'));
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { expect } from '@playwright/test';
2+
import { SDK_VERSION } from '@sentry/browser';
3+
import { sentryTest } from '../../../utils/fixtures';
4+
5+
sentryTest('adds X-Sentry-User-Agent header to envelope requests', async ({ getLocalTestUrl, page }) => {
6+
const url = await getLocalTestUrl({ testDir: __dirname });
7+
8+
const requestHeadersPromise = new Promise<Record<string, string>>(resolve => {
9+
page.route('https://dsn.ingest.sentry.io/**/*', (route, request) => {
10+
resolve(request.headers());
11+
return route.fulfill({
12+
status: 200,
13+
body: JSON.stringify({}),
14+
});
15+
});
16+
});
17+
18+
await page.goto(url);
19+
20+
const requestHeaders = await requestHeadersPromise;
21+
22+
expect(requestHeaders).toMatchObject({
23+
// this is the browser's user-agent header (which we don't modify)
24+
'user-agent': expect.any(String),
25+
26+
// this is the SDK's user-agent header (in browser)
27+
'x-sentry-user-agent': `sentry.javascript.browser/${SDK_VERSION}`,
28+
29+
// this is a custom header users add via `transportOptions.headers`
30+
'x-custom-header': 'custom-value',
31+
});
32+
});

dev-packages/e2e-tests/test-applications/cloudflare-workers/tests/index.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForError } from '@sentry-internal/test-utils';
2+
import { waitForError, waitForRequest } from '@sentry-internal/test-utils';
3+
import { SDK_VERSION } from '@sentry/cloudflare';
34
import { WebSocket } from 'ws';
45

56
test('Index page', async ({ baseURL }) => {
@@ -69,3 +70,15 @@ test('Websocket.webSocketClose', async ({ baseURL }) => {
6970
expect(event.exception?.values?.[0]?.value).toBe('Should be recorded in Sentry: webSocketClose');
7071
expect(event.exception?.values?.[0]?.mechanism?.type).toBe('auto.faas.cloudflare.durable_object');
7172
});
73+
74+
test('sends user-agent header with SDK name and version in envelope requests', async ({ baseURL }) => {
75+
const requestPromise = waitForRequest('cloudflare-workers', () => true);
76+
77+
await fetch(`${baseURL}/throwException`);
78+
79+
const request = await requestPromise;
80+
81+
expect(request.rawProxyRequestHeaders).toMatchObject({
82+
'user-agent': `sentry.javascript.cloudflare/${SDK_VERSION}`,
83+
});
84+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForRequest } from '@sentry-internal/test-utils';
3+
import { SDK_VERSION } from '@sentry/node';
4+
5+
test('sends user-agent header with SDK name and version in envelope requests', async ({ baseURL }) => {
6+
const requestPromise = waitForRequest('node-express', () => true);
7+
8+
await fetch(`${baseURL}/test-exception/123`);
9+
10+
const request = await requestPromise;
11+
12+
expect(request.rawProxyRequestHeaders).toMatchObject({
13+
'user-agent': `sentry.javascript.node/${SDK_VERSION}`,
14+
});
15+
});

dev-packages/test-utils/src/event-proxy-server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface EventProxyServerOptions {
2424
interface SentryRequestCallbackData {
2525
envelope: Envelope;
2626
rawProxyRequestBody: string;
27+
rawProxyRequestHeaders: Record<string, string | string[] | undefined>;
2728
rawSentryResponseBody: string;
2829
sentryResponseStatusCode?: number;
2930
}
@@ -182,6 +183,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P
182183
const data: SentryRequestCallbackData = {
183184
envelope: parseEnvelope(proxyRequestBody),
184185
rawProxyRequestBody: proxyRequestBody,
186+
rawProxyRequestHeaders: proxyRequest.headers,
185187
rawSentryResponseBody: '',
186188
sentryResponseStatusCode: 200,
187189
};

packages/browser/src/client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
_INTERNAL_flushLogsBuffer,
1414
_INTERNAL_flushMetricsBuffer,
1515
addAutoIpAddressToSession,
16+
addUserAgentToTransportHeaders,
1617
applySdkMetadata,
1718
Client,
1819
getSDKSource,
@@ -97,6 +98,8 @@ export class BrowserClient extends Client<BrowserClientOptions> {
9798
const sdkSource = WINDOW.SENTRY_SDK_SOURCE || getSDKSource();
9899
applySdkMetadata(opts, 'browser', ['browser'], sdkSource);
99100

101+
addUserAgentToTransportHeaders(opts, 'x-Sentry-User-Agent');
102+
100103
// Only allow IP inferral by Relay if sendDefaultPii is true
101104
if (opts._metadata?.sdk) {
102105
opts._metadata.sdk.settings = {

packages/browser/src/transports/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,4 @@ import type { BaseTransportOptions } from '@sentry/core';
33
export interface BrowserTransportOptions extends BaseTransportOptions {
44
/** Fetch API init parameters. Used by the FetchTransport */
55
fetchOptions?: RequestInit;
6-
/** Custom headers for the transport. Used by the XHRTransport and FetchTransport */
7-
headers?: { [key: string]: string };
86
}

packages/browser/test/client.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import * as sentryCore from '@sentry/core';
6-
import { Scope } from '@sentry/core';
6+
import { Scope, SDK_VERSION } from '@sentry/core';
77
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
88
import { applyDefaultOptions, BrowserClient } from '../src/client';
99
import { WINDOW } from '../src/helpers';
@@ -289,3 +289,33 @@ describe('SDK metadata', () => {
289289
});
290290
});
291291
});
292+
293+
describe('user agent header', () => {
294+
it('adds X-Sentry-User-Agent header to transport options', () => {
295+
const options = getDefaultBrowserClientOptions({});
296+
const client = new BrowserClient(options);
297+
298+
expect(client.getOptions().transportOptions?.headers).toEqual({
299+
'x-Sentry-User-Agent': `sentry.javascript.browser/${SDK_VERSION}`,
300+
});
301+
});
302+
303+
it('respects user-passed headers', () => {
304+
const options = getDefaultBrowserClientOptions({
305+
transportOptions: {
306+
headers: {
307+
'x-custom-header': 'custom-value',
308+
'x-Sentry-User-Agent': 'custom-user-agent',
309+
'user-agent': 'custom-user-agent-2',
310+
},
311+
},
312+
});
313+
const client = new BrowserClient(options);
314+
315+
expect(client.getOptions().transportOptions?.headers).toEqual({
316+
'x-custom-header': 'custom-value',
317+
'x-Sentry-User-Agent': 'custom-user-agent',
318+
'user-agent': 'custom-user-agent-2',
319+
});
320+
});
321+
});

packages/bun/src/transports/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/core';
22
import { createTransport, suppressTracing } from '@sentry/core';
33

4-
export interface BunTransportOptions extends BaseTransportOptions {
5-
/** Custom headers for the transport. Used by the XHRTransport and FetchTransport */
6-
headers?: { [key: string]: string };
7-
}
8-
94
/**
105
* Creates a Transport that uses the Fetch API to send events to Sentry.
116
*/
12-
export function makeFetchTransport(options: BunTransportOptions): Transport {
7+
export function makeFetchTransport(options: BaseTransportOptions): Transport {
138
function makeRequest(request: TransportRequest): PromiseLike<TransportMakeRequestResponse> {
149
const requestOptions: RequestInit = {
1510
body: request.body,

0 commit comments

Comments
 (0)