From 2212e66a98a184421654997f17aeb394939bcd58 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Thu, 11 Apr 2024 13:49:49 +0000 Subject: [PATCH 1/6] fix(browser): Wait until response has finished streaming before calling fetch handlers --- packages/utils/src/instrument/fetch.ts | 40 ++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/utils/src/instrument/fetch.ts b/packages/utils/src/instrument/fetch.ts index 9c663f88baf0..2606641ee379 100644 --- a/packages/utils/src/instrument/fetch.ts +++ b/packages/utils/src/instrument/fetch.ts @@ -47,13 +47,41 @@ function instrumentFetch(): void { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return originalFetch.apply(GLOBAL_OBJ, args).then( (response: Response) => { - const finishedHandlerData: HandlerDataFetch = { - ...handlerData, - endTimestamp: Date.now(), - response, - }; + const clonedResponse = response.clone(); + + if (clonedResponse.body) { + const responseReader = clonedResponse.body.getReader(); + + // eslint-disable-next-line no-inner-declarations + function consumeChunks({ done }: { done: boolean }): Promise { + if (!done) { + return responseReader.read().then(consumeChunks); + } else { + return Promise.resolve(); + } + } + + responseReader + .read() + .then(consumeChunks) + .then(() => { + triggerHandlers('fetch', { + ...handlerData, + endTimestamp: Date.now(), + response, + }); + }) + .catch(() => { + // noop + }); + } else { + triggerHandlers('fetch', { + ...handlerData, + endTimestamp: Date.now(), + response, + }); + } - triggerHandlers('fetch', finishedHandlerData); return response; }, (error: Error) => { From 125f991011e6c1f8515f967d9a8d3557ded0c744 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Thu, 11 Apr 2024 13:56:03 +0000 Subject: [PATCH 2/6] try catch clone failure --- packages/utils/src/instrument/fetch.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/instrument/fetch.ts b/packages/utils/src/instrument/fetch.ts index 2606641ee379..85c4fd6f89e1 100644 --- a/packages/utils/src/instrument/fetch.ts +++ b/packages/utils/src/instrument/fetch.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { HandlerDataFetch } from '@sentry/types'; +import { DEBUG_BUILD } from '../debug-build'; +import { logger } from '../logger'; import { fill } from '../object'; import { supportsNativeFetch } from '../supports'; import { GLOBAL_OBJ } from '../worldwide'; @@ -47,9 +49,15 @@ function instrumentFetch(): void { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return originalFetch.apply(GLOBAL_OBJ, args).then( (response: Response) => { - const clonedResponse = response.clone(); + let clonedResponse; + try { + clonedResponse = response.clone(); + } catch (e) { + // noop + DEBUG_BUILD && logger.warn('Failed to clone response.'); + } - if (clonedResponse.body) { + if (clonedResponse && clonedResponse.body) { const responseReader = clonedResponse.body.getReader(); // eslint-disable-next-line no-inner-declarations From a6d06805a9ab4ac648fb09edc20cffcb260c4732 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 12 Apr 2024 07:57:23 +0000 Subject: [PATCH 3/6] tests --- .../Breadcrumbs/fetch/get/subject.js | 8 +-- .../Breadcrumbs/fetch/post/subject.js | 8 +-- .../fetch/captureRequestBody/test.ts | 50 +++++++++++------- .../fetch/captureRequestHeaders/test.ts | 52 +++++++++++-------- .../fetch/captureRequestSize/test.ts | 20 ++++--- .../fetch/captureResponseBody/test.ts | 40 ++++++++------ .../fetch/captureResponseHeaders/test.ts | 30 ++++++----- .../fetch/captureResponseSize/test.ts | 30 ++++++----- .../fetch/captureTimestamps/test.ts | 10 ++-- .../nextjs-app-dir/middleware.ts | 2 +- 10 files changed, 149 insertions(+), 101 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js index f6e1e21e4611..1ba7e011aca2 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/subject.js @@ -1,3 +1,5 @@ -fetch('http://localhost:7654/foo').then(() => { - Sentry.captureException('test error'); -}); +fetch('http://localhost:7654/foo') + .then(res => res.text()) + .then(() => { + Sentry.captureException('test error'); + }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js index ea1bf44bc905..948b569e3c03 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/subject.js @@ -6,6 +6,8 @@ fetch('http://localhost:7654/foo', { 'Content-Type': 'application/json', Cache: 'no-cache', }, -}).then(() => { - Sentry.captureException('test error'); -}); +}) + .then(res => res.text()) + .then(() => { + Sentry.captureException('test error'); + }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts index bd8050b740aa..c54b88df1f8c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts @@ -42,10 +42,12 @@ sentryTest('captures text request body', async ({ getLocalTestPath, page, browse fetch('http://localhost:7654/foo', { method: 'POST', body: 'input body', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -122,10 +124,12 @@ sentryTest('captures JSON request body', async ({ getLocalTestPath, page, browse fetch('http://localhost:7654/foo', { method: 'POST', body: '{"foo":"bar"}', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -206,10 +210,12 @@ sentryTest('captures non-text request body', async ({ getLocalTestPath, page, br fetch('http://localhost:7654/foo', { method: 'POST', body: body, - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -286,10 +292,12 @@ sentryTest('captures text request body when matching relative URL', async ({ get fetch('/foo', { method: 'POST', body: 'input body', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -364,10 +372,12 @@ sentryTest('does not capture request body when URL does not match', async ({ get fetch('http://localhost:7654/bar', { method: 'POST', body: 'input body', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts index 0b0b37fb1cf6..2ae0f97e48eb 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts @@ -38,14 +38,14 @@ sentryTest.skip('handles empty/missing request headers', async ({ getLocalTestPa await page.goto(url); await page.evaluate(() => { - /* eslint-disable */ fetch('http://localhost:7654/foo', { method: 'POST', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); - /* eslint-enable */ + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); }); const request = await requestPromise; @@ -121,10 +121,12 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestPath, page, 'X-Custom-Header': 'foo', 'X-Test-Header': 'test-value', }, - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -206,10 +208,12 @@ sentryTest('captures request headers on Request', async ({ getLocalTestPath, pag }, }); /* eslint-disable */ - fetch(request).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + fetch(request) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -291,10 +295,12 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest fetch('http://localhost:7654/foo', { method: 'POST', headers, - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -375,10 +381,12 @@ sentryTest('does not captures request headers if URL does not match', async ({ g 'X-Custom-Header': 'foo', 'X-Test-Header': 'test-value', }, - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts index 52857d17479d..7e3d0da80d30 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts @@ -42,10 +42,12 @@ sentryTest.skip('captures request body size when body is sent', async ({ getLoca fetch('http://localhost:7654/foo', { method: 'POST', body: '{"foo":"bar"}', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -129,10 +131,12 @@ sentryTest('captures request size from non-text request body', async ({ getLocal fetch('http://localhost:7654/foo', { method: 'POST', body: blob, - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts index 5e58b63218ef..8861f75db2ca 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts @@ -42,10 +42,12 @@ sentryTest('captures text response body', async ({ getLocalTestPath, page, brows /* eslint-disable */ fetch('http://localhost:7654/foo', { method: 'POST', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -124,10 +126,12 @@ sentryTest('captures JSON response body', async ({ getLocalTestPath, page, brows /* eslint-disable */ fetch('http://localhost:7654/foo', { method: 'POST', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -206,10 +210,12 @@ sentryTest('captures non-text response body', async ({ getLocalTestPath, page, b /* eslint-disable */ fetch('http://localhost:7654/foo', { method: 'POST', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -288,10 +294,12 @@ sentryTest.skip('does not capture response body when URL does not match', async /* eslint-disable */ fetch('http://localhost:7654/bar', { method: 'POST', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts index 8f098627c120..0614aec77d00 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts @@ -38,10 +38,12 @@ sentryTest('handles empty headers', async ({ getLocalTestPath, page, browserName await page.goto(url); await page.evaluate(() => { - fetch('http://localhost:7654/foo').then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + fetch('http://localhost:7654/foo') + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); }); const request = await requestPromise; @@ -112,10 +114,12 @@ sentryTest('captures response headers', async ({ getLocalTestPath, page }) => { await page.goto(url); await page.evaluate(() => { - fetch('http://localhost:7654/foo').then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + fetch('http://localhost:7654/foo') + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); }); const request = await requestPromise; @@ -192,10 +196,12 @@ sentryTest('does not capture response headers if URL does not match', async ({ g await page.goto(url); await page.evaluate(() => { - fetch('http://localhost:7654/bar').then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + fetch('http://localhost:7654/bar') + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); }); const request = await requestPromise; diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts index ad3aafe34562..0dc4b7392b8d 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts @@ -45,10 +45,12 @@ sentryTest('captures response size from Content-Length header if available', asy await page.evaluate(() => { /* eslint-disable */ - fetch('http://localhost:7654/foo').then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + fetch('http://localhost:7654/foo') + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -135,10 +137,12 @@ sentryTest('captures response size without Content-Length header', async ({ getL await page.evaluate(() => { /* eslint-disable */ - fetch('http://localhost:7654/foo').then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + fetch('http://localhost:7654/foo') + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); @@ -224,10 +228,12 @@ sentryTest('captures response size from non-text response body', async ({ getLoc /* eslint-disable */ fetch('http://localhost:7654/foo', { method: 'POST', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts index cce931062770..6e3413b1e54f 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts @@ -42,10 +42,12 @@ sentryTest('captures correct timestamps', async ({ getLocalTestPath, page, brows fetch('http://localhost:7654/foo', { method: 'POST', body: '{"foo":"bar"}', - }).then(() => { - // @ts-expect-error Sentry is a global - Sentry.captureException('test error'); - }); + }) + .then(res => res.text()) + .then(() => { + // @ts-expect-error Sentry is a global + Sentry.captureException('test error'); + }); /* eslint-enable */ }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts index 6096fcfb1493..c46af6d229b5 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts @@ -12,7 +12,7 @@ export async function middleware(request: NextRequest) { } if (request.headers.has('x-should-make-request')) { - await fetch('http://localhost:3030/'); + await fetch('http://localhost:3030/').then(res => res.text()); } return NextResponse.next(); From fdf13f752e0567e21df363cb43099b39709d792a Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 12 Apr 2024 08:45:04 +0000 Subject: [PATCH 4/6] clone asap --- packages/utils/src/instrument/fetch.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/utils/src/instrument/fetch.ts b/packages/utils/src/instrument/fetch.ts index 85c4fd6f89e1..533b40dbc941 100644 --- a/packages/utils/src/instrument/fetch.ts +++ b/packages/utils/src/instrument/fetch.ts @@ -49,16 +49,23 @@ function instrumentFetch(): void { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return originalFetch.apply(GLOBAL_OBJ, args).then( (response: Response) => { - let clonedResponse; + // We need to immediately clone the response, so that if the user reads the body before we call the handlers, + // we cannot clone the response inside the handlers since it would throw. The Replay integration for instance + // needs to clone the response body inside a handler to collect response size and breadcrumbs. + // If the cloning fails for whatever reason, we still pass the original response because it could be used for + // status. + let responseForHandlers = response; + let clonedResponseForResolving; try { - clonedResponse = response.clone(); + responseForHandlers = response.clone(); + clonedResponseForResolving = response.clone(); } catch (e) { // noop - DEBUG_BUILD && logger.warn('Failed to clone response.'); + DEBUG_BUILD && logger.warn('Failed to clone response body.'); } - if (clonedResponse && clonedResponse.body) { - const responseReader = clonedResponse.body.getReader(); + if (clonedResponseForResolving && clonedResponseForResolving.body) { + const responseReader = clonedResponseForResolving.body.getReader(); // eslint-disable-next-line no-inner-declarations function consumeChunks({ done }: { done: boolean }): Promise { @@ -76,7 +83,7 @@ function instrumentFetch(): void { triggerHandlers('fetch', { ...handlerData, endTimestamp: Date.now(), - response, + response: responseForHandlers, }); }) .catch(() => { @@ -86,7 +93,7 @@ function instrumentFetch(): void { triggerHandlers('fetch', { ...handlerData, endTimestamp: Date.now(), - response, + response: responseForHandlers, }); } From 7467562842a122ce49cf7ed560a4519edadce460 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 12 Apr 2024 08:54:25 +0000 Subject: [PATCH 5/6] overlooked test --- .../Breadcrumbs/fetch/getWithRequestObj/subject.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/subject.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/subject.js index 0ca20f1b5acb..6c3eaf0ddf25 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/subject.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/subject.js @@ -1,3 +1,5 @@ -fetch(new Request('http://localhost:7654/foo')).then(() => { - Sentry.captureException('test error'); -}); +fetch(new Request('http://localhost:7654/foo')) + .then(res => res.text()) + .then(() => { + Sentry.captureException('test error'); + }); From 5d4d9771b8c422a3e099b4d7254728e17d0a42b3 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 12 Apr 2024 12:21:51 +0000 Subject: [PATCH 6/6] Try delaying for a tick --- packages/utils/src/instrument/fetch.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/instrument/fetch.ts b/packages/utils/src/instrument/fetch.ts index 533b40dbc941..9a0a959c4c3a 100644 --- a/packages/utils/src/instrument/fetch.ts +++ b/packages/utils/src/instrument/fetch.ts @@ -97,7 +97,11 @@ function instrumentFetch(): void { }); } - return response; + return new Promise(resolve => { + setTimeout(() => { + resolve(response); + }, 0); + }); }, (error: Error) => { const erroredHandlerData: HandlerDataFetch = {