Skip to content

Commit 57b0656

Browse files
authored
ref(browser): Improve fetchTransport error handling (#17661)
This changes our fetch transport a bit, making use of async functions and slightly adjusting error handling: 1. Technically there was a bug if fetch is not available, as we would keep increasing the `pendingBodySize`/`pendingCount` and never decrease it. 2. We had some dedicated error messages for edge cases that are IMHO not necessary - we can safe a few bytes by just using default error messages there. 3. No need to use sync promise here, all of this is async anyhow. Extracted this out of #17641
1 parent 41cec5b commit 57b0656

File tree

2 files changed

+36
-25
lines changed

2 files changed

+36
-25
lines changed

packages/browser/src/transports/fetch.ts

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/core';
2-
import { createTransport, rejectedSyncPromise } from '@sentry/core';
2+
import { createTransport } from '@sentry/core';
33
import { clearCachedImplementation, getNativeImplementation } from '@sentry-internal/browser-utils';
44
import type { WINDOW } from '../helpers';
55
import type { BrowserTransportOptions } from './types';
@@ -9,12 +9,12 @@ import type { BrowserTransportOptions } from './types';
99
*/
1010
export function makeFetchTransport(
1111
options: BrowserTransportOptions,
12-
nativeFetch: typeof WINDOW.fetch | undefined = getNativeImplementation('fetch'),
12+
nativeFetch: typeof WINDOW.fetch = getNativeImplementation('fetch'),
1313
): Transport {
1414
let pendingBodySize = 0;
1515
let pendingCount = 0;
1616

17-
function makeRequest(request: TransportRequest): PromiseLike<TransportMakeRequestResponse> {
17+
async function makeRequest(request: TransportRequest): Promise<TransportMakeRequestResponse> {
1818
const requestSize = request.body.length;
1919
pendingBodySize += requestSize;
2020
pendingCount++;
@@ -39,29 +39,23 @@ export function makeFetchTransport(
3939
...options.fetchOptions,
4040
};
4141

42-
if (!nativeFetch) {
43-
clearCachedImplementation('fetch');
44-
return rejectedSyncPromise('No fetch implementation available');
45-
}
46-
4742
try {
48-
// Note: We do not need to suppress tracing here, becasue we are using the native fetch, instead of our wrapped one.
49-
return nativeFetch(options.url, requestOptions).then(response => {
50-
pendingBodySize -= requestSize;
51-
pendingCount--;
52-
return {
53-
statusCode: response.status,
54-
headers: {
55-
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
56-
'retry-after': response.headers.get('Retry-After'),
57-
},
58-
};
59-
});
43+
// Note: We do not need to suppress tracing here, because we are using the native fetch, instead of our wrapped one.
44+
const response = await nativeFetch(options.url, requestOptions);
45+
46+
return {
47+
statusCode: response.status,
48+
headers: {
49+
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
50+
'retry-after': response.headers.get('Retry-After'),
51+
},
52+
};
6053
} catch (e) {
6154
clearCachedImplementation('fetch');
55+
throw e;
56+
} finally {
6257
pendingBodySize -= requestSize;
6358
pendingCount--;
64-
return rejectedSyncPromise(e);
6559
}
6660
}
6761

packages/browser/test/transports/fetch.test.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { EventEnvelope, EventItem } from '@sentry/core';
22
import { createEnvelope, serializeEnvelope } from '@sentry/core';
33
import type { Mock } from 'vitest';
4-
import { describe, expect, it, vi } from 'vitest';
4+
import { afterEach, describe, expect, it, vi } from 'vitest';
55
import { makeFetchTransport } from '../../src/transports/fetch';
66
import type { BrowserTransportOptions } from '../../src/transports/types';
77

@@ -29,7 +29,11 @@ class Headers {
2929
}
3030
}
3131

32-
describe('NewFetchTransport', () => {
32+
describe('fetchTransport', () => {
33+
afterEach(() => {
34+
vi.resetAllMocks();
35+
});
36+
3337
it('calls fetch with the given URL', async () => {
3438
const mockFetch = vi.fn(() =>
3539
Promise.resolve({
@@ -102,15 +106,28 @@ describe('NewFetchTransport', () => {
102106
});
103107
});
104108

105-
it('handles when `getNativetypeof window.fetchementation` is undefined', async () => {
109+
it('handles when native fetch implementation returns undefined', async () => {
106110
const mockFetch = vi.fn(() => undefined) as unknown as typeof window.fetch;
107111
const transport = makeFetchTransport(DEFAULT_FETCH_TRANSPORT_OPTIONS, mockFetch);
108112

109113
expect(mockFetch).toHaveBeenCalledTimes(0);
110-
await expect(() => transport.send(ERROR_ENVELOPE)).not.toThrow();
114+
await expect(() => transport.send(ERROR_ENVELOPE)).rejects.toThrow(
115+
"Cannot read properties of undefined (reading 'status')",
116+
);
111117
expect(mockFetch).toHaveBeenCalledTimes(1);
112118
});
113119

120+
it('handles when native fetch implementation is undefined', async () => {
121+
vi.mock('@sentry-internal/browser-utils', async importOriginal => ({
122+
...(await importOriginal()),
123+
getNativeImplementation: () => undefined,
124+
}));
125+
126+
const transport = makeFetchTransport(DEFAULT_FETCH_TRANSPORT_OPTIONS);
127+
128+
await expect(() => transport.send(ERROR_ENVELOPE)).rejects.toThrow('nativeFetch is not a function');
129+
});
130+
114131
it('correctly sets keepalive flag', async () => {
115132
const mockFetch = vi.fn(() =>
116133
Promise.resolve({

0 commit comments

Comments
 (0)