diff --git a/CHANGELOG.md b/CHANGELOG.md
index 319465691cd5..ca5dd9918a40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,13 +4,59 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
+## 10.15.0
+
+### Important Changes
+
+- **feat(cloudflare): Add honoIntegration with error-filtering function ([#17743](https://github.com/getsentry/sentry-javascript/pull/17743))**
+
+ This release adds a `honoIntegration` to `@sentry/cloudflare`, which exposes a `shouldHandleError` function that lets you define which errors in `onError` should be captured.
+ By default, Sentry captures exceptions with `error.status >= 500 || error.status <= 299`.
+
+ The integration is added by default, and it's possible to modify this behavior like this:
+
+ ```js
+ integrations: [
+ honoIntegration({
+ shouldHandleError: (err) => true; // always capture exceptions in onError
+ })
+ ]
+ ```
+
+- **feat(node): Add instrumentation for hono handler ([#17428](https://github.com/getsentry/sentry-javascript/pull/17428))**
+
+This PR enhances the Hono integration by adding comprehensive handler instrumentation, error handling capabilities.
+
+- **feat(aws): Enable Lambda extension by default when using the Lamba layer ([#17684](https://github.com/getsentry/sentry-javascript/pull/17684))**
+
+- **feat(browser): Add `setActiveSpanInBrowser` to set an active span in the browser ([#17714](https://github.com/getsentry/sentry-javascript/pull/17714))**
+
+This PR adds a feature to the browser SDKs only: Making an inactive span active. We do this to enable use cases where having a span only being active in the callback is not practical.
+
+### Other Changes
+
+- fix(browser): Improve handling of `0` and `undefined` resource timing values ([#17751](https://github.com/getsentry/sentry-javascript/pull/17751))
+- ref(nextjs): Display build compatibility warning for webpack ([#17746](https://github.com/getsentry/sentry-javascript/pull/17746))
+
+
+ Internal Changes
+
+- docs: Reword changelog for google gen ai instrumentation ([#17753](https://github.com/getsentry/sentry-javascript/pull/17753))
+- build: Add `@typescript-eslint/no-unnecessary-type-assertion` rule ([#17728](https://github.com/getsentry/sentry-javascript/pull/17728))
+- build: Update TS target to `es2020` everywhere ([#17709](https://github.com/getsentry/sentry-javascript/pull/17709))
+- chore: Add external contributor to CHANGELOG.md ([#17745](https://github.com/getsentry/sentry-javascript/pull/17745))
+
+
+
+Work in this release was contributed by @Karibash. Thank you for your contribution!
+
## 10.14.0
### Important Changes
- **feat(cloudflare,vercel-edge): Add support for Google Gen AI instrumentation ([#17723](https://github.com/getsentry/sentry-javascript/pull/17723))**
- The SDK now automatically instruments Google's Generative AI operations in Cloudflare Workers and Vercel Edge Runtime environments, providing insights into your AI operations.
+ The SDK now supports manually instrumenting Google's Generative AI operations in Cloudflare Workers and Vercel Edge Runtime environments, providing insights into your AI operations. You can use `const wrappedClient = Sentry.instrumentGoogleGenAIClient(genAiClient)` to get an instrumented client.
### Other Changes
diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json
index d086c88c2383..0f06b5b9cb90 100644
--- a/dev-packages/browser-integration-tests/package.json
+++ b/dev-packages/browser-integration-tests/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/browser-integration-tests",
- "version": "10.14.0",
+ "version": "10.15.0",
"main": "index.js",
"license": "MIT",
"engines": {
@@ -43,7 +43,7 @@
"@babel/preset-typescript": "^7.16.7",
"@playwright/test": "~1.53.2",
"@sentry-internal/rrweb": "2.34.0",
- "@sentry/browser": "10.14.0",
+ "@sentry/browser": "10.15.0",
"@supabase/supabase-js": "2.49.3",
"axios": "1.8.2",
"babel-loader": "^8.2.2",
diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts
index bcce3b5c4000..6a5024caee03 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts
@@ -23,7 +23,7 @@ sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestUrl
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts
index c2ba9222a108..fc125817725e 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts
@@ -23,7 +23,7 @@ sentryTest('captures Breadcrumb for basic GET request that uses request object',
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts
index 317230a631d4..6658c2628f60 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts
@@ -23,7 +23,7 @@ sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestUrl, pag
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/statusCode/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/statusCode/test.ts
index ba957aec42cc..03b9b636d9e9 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/statusCode/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/statusCode/test.ts
@@ -19,7 +19,7 @@ sentryTest('captures Breadcrumb with log level for 4xx response code', async ({
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -56,7 +56,7 @@ sentryTest('captures Breadcrumb with log level for 5xx response code', async ({
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts
index 3c3b41e07ea2..02275b0adf2a 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts
@@ -24,7 +24,7 @@ sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestUrl
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts
index f9c018d2ec70..7de33d189f37 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts
@@ -23,7 +23,7 @@ sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestUrl, pag
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/statusCode/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/statusCode/test.ts
index 905d00cb85cd..d17613cc1da1 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/statusCode/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/statusCode/test.ts
@@ -19,7 +19,7 @@ sentryTest('captures Breadcrumb with log level for 4xx response code', async ({
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -56,7 +56,7 @@ sentryTest('captures Breadcrumb with log level for 5xx response code', async ({
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts
index 826e6cef48f5..f3fd78bc0b94 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts
@@ -86,7 +86,7 @@ sentryTest('should update breadcrumbs for GraphQL fetch requests', async ({ getL
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts
index 5089401441f2..ca9704cc48fe 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts
@@ -86,7 +86,7 @@ sentryTest('should update breadcrumbs for GraphQL XHR requests', async ({ getLoc
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/integrations/webWorker/test.ts b/dev-packages/browser-integration-tests/suites/integrations/webWorker/test.ts
index cc5a8b3c7cf0..bb5adf0ac70a 100644
--- a/dev-packages/browser-integration-tests/suites/integrations/webWorker/test.ts
+++ b/dev-packages/browser-integration-tests/suites/integrations/webWorker/test.ts
@@ -4,7 +4,7 @@ import { sentryTest } from '../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
sentryTest('Assigns web worker debug IDs when using webWorkerIntegration', async ({ getLocalTestUrl, page }) => {
- const bundle = process.env.PW_BUNDLE as string | undefined;
+ const bundle = process.env.PW_BUNDLE;
if (bundle != null && !bundle.includes('esm') && !bundle.includes('cjs')) {
sentryTest.skip();
}
diff --git a/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureException/test.ts b/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureException/test.ts
index faeff404df6b..99023e472526 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureException/test.ts
+++ b/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureException/test.ts
@@ -1,17 +1,17 @@
import { expect } from '@playwright/test';
-import type { Event, FeedbackEvent } from '@sentry/core';
+import type { Event } from '@sentry/core';
import { sentryTest } from '../../../../utils/fixtures';
import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers';
sentryTest('capture user feedback when captureException is called', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
- const data = (await getMultipleSentryEnvelopeRequests(page, 2, { url })) as (Event | FeedbackEvent)[];
+ const data = await getMultipleSentryEnvelopeRequests(page, 2, { url });
expect(data).toHaveLength(2);
- const errorEvent = ('exception' in data[0] ? data[0] : data[1]) as Event;
- const feedback = ('exception' in data[0] ? data[1] : data[0]) as FeedbackEvent;
+ const errorEvent = 'exception' in data[0] ? data[0] : data[1];
+ const feedback = 'exception' in data[0] ? data[1] : data[0];
expect(feedback.contexts).toEqual(
expect.objectContaining({
diff --git a/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureMessage/test.ts b/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureMessage/test.ts
index 4cf2e4165b55..fab1207d9246 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureMessage/test.ts
+++ b/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureMessage/test.ts
@@ -1,17 +1,17 @@
import { expect } from '@playwright/test';
-import type { Event, FeedbackEvent } from '@sentry/core';
+import type { Event } from '@sentry/core';
import { sentryTest } from '../../../../utils/fixtures';
import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers';
sentryTest('capture user feedback when captureMessage is called', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
- const data = (await getMultipleSentryEnvelopeRequests(page, 2, { url })) as (Event | FeedbackEvent)[];
+ const data = await getMultipleSentryEnvelopeRequests(page, 2, { url });
expect(data).toHaveLength(2);
- const errorEvent = ('exception' in data[0] ? data[0] : data[1]) as Event;
- const feedback = ('exception' in data[0] ? data[1] : data[0]) as FeedbackEvent;
+ const errorEvent = 'exception' in data[0] ? data[0] : data[1];
+ const feedback = 'exception' in data[0] ? data[1] : data[0];
expect(feedback.contexts).toEqual(
expect.objectContaining({
diff --git a/dev-packages/browser-integration-tests/suites/public-api/diagnoseSdkConnectivity/test.ts b/dev-packages/browser-integration-tests/suites/public-api/diagnoseSdkConnectivity/test.ts
index 294e60b34bfd..c18d75464223 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/diagnoseSdkConnectivity/test.ts
+++ b/dev-packages/browser-integration-tests/suites/public-api/diagnoseSdkConnectivity/test.ts
@@ -3,7 +3,7 @@ import { sentryTest } from '../../../utils/fixtures';
import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers';
sentryTest('makes a call to sentry.io to diagnose SDK connectivity', async ({ getLocalTestUrl, page }) => {
- const bundle = process.env.PW_BUNDLE as string | undefined;
+ const bundle = process.env.PW_BUNDLE;
if (shouldSkipTracingTest() || !!bundle) {
// the CDN bundle doesn't export diagnoseSdkConnectivity. So skipping the test for bundles.
sentryTest.skip();
diff --git a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/requestAnimationFrame/callback/test.ts b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/requestAnimationFrame/callback/test.ts
index 71f2b8d4ab3b..0d50e0705e1b 100644
--- a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/requestAnimationFrame/callback/test.ts
+++ b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/requestAnimationFrame/callback/test.ts
@@ -13,7 +13,7 @@ sentryTest(
const outsideCtx = window as any;
requestAnimationFrame(function () {
// @ts-expect-error re-assigning this
- resolve({ outsideCtx, requestAnimationFrameCtx: this as any });
+ resolve({ outsideCtx, requestAnimationFrameCtx: this });
});
});
})) as any;
diff --git a/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts b/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts
index f9a6d1041615..ef0882e0206b 100644
--- a/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts
@@ -48,7 +48,7 @@ sentryTest(
const req0 = await transactionReq;
- const envHeader = envelopeRequestParser(req0, 0) as EventEnvelopeHeaders;
+ const envHeader = envelopeRequestParser(req0, 0);
const replay = await getReplaySnapshot(page);
expect(replay.session?.id).toBeDefined();
@@ -96,7 +96,7 @@ sentryTest(
const req0 = await transactionReq;
- const envHeader = envelopeRequestParser(req0, 0) as EventEnvelopeHeaders;
+ const envHeader = envelopeRequestParser(req0, 0);
const replay = await getReplaySnapshot(page);
expect(replay.session?.id).toBeDefined();
@@ -148,7 +148,7 @@ sentryTest(
const req0 = await transactionReq;
- const envHeader = envelopeRequestParser(req0, 0) as EventEnvelopeHeaders;
+ const envHeader = envelopeRequestParser(req0, 0);
const replay = await getReplaySnapshot(page);
expect(replay.session?.id).toBeDefined();
@@ -191,7 +191,7 @@ sentryTest(
const req0 = await transactionReq;
- const envHeader = envelopeRequestParser(req0, 0) as EventEnvelopeHeaders;
+ const envHeader = envelopeRequestParser(req0, 0);
const replay = await getReplaySnapshot(page);
@@ -235,7 +235,7 @@ sentryTest('should add replay_id to error DSC while replay is active', async ({
await page.evaluate('window._triggerError(1)');
- const error1Header = envelopeRequestParser(await error1Req, 0) as EventEnvelopeHeaders;
+ const error1Header = envelopeRequestParser(await error1Req, 0);
const replay = await getReplaySnapshot(page);
expect(replay.session?.id).toBeDefined();
@@ -260,7 +260,7 @@ sentryTest('should add replay_id to error DSC while replay is active', async ({
await page.waitForFunction('!window.Replay.getReplayId();');
await page.evaluate('window._triggerError(2)');
- const error2Header = envelopeRequestParser(await error2Req, 0) as EventEnvelopeHeaders;
+ const error2Header = envelopeRequestParser(await error2Req, 0);
expect(error2Header.trace).toBeDefined();
expect(error2Header.trace).toEqual({
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 e63e2fb285e0..22c756e05bf4 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
@@ -44,7 +44,7 @@ sentryTest('captures text request body', async ({ getLocalTestUrl, page, browser
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -114,7 +114,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestUrl, page, browser
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -188,7 +188,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestUrl, page, bro
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -258,7 +258,7 @@ sentryTest('captures text request body when matching relative URL', async ({ get
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -326,7 +326,7 @@ sentryTest('does not capture request body when URL does not match', async ({ get
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
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 01afceca077c..bdb2c6d05110 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
@@ -46,7 +46,7 @@ sentryTest('handles empty/missing request headers', async ({ getLocalTestUrl, pa
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -118,7 +118,7 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestUrl, page, b
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -198,7 +198,7 @@ sentryTest('captures request headers on Request', async ({ getLocalTestUrl, page
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -277,7 +277,7 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -353,7 +353,7 @@ sentryTest('does not captures request headers if URL does not match', async ({ g
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
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 5671bdaabc1a..2790f26ac476 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
@@ -45,7 +45,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -124,7 +124,7 @@ sentryTest('captures request size from non-text request body', async ({ getLocal
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
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 4f5926f742ef..a64f1667ec95 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
@@ -44,7 +44,7 @@ sentryTest('captures text response body', async ({ getLocalTestUrl, page, browse
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -116,7 +116,7 @@ sentryTest('captures JSON response body', async ({ getLocalTestUrl, page, browse
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -188,7 +188,7 @@ sentryTest('captures non-text response body', async ({ getLocalTestUrl, page, br
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -260,7 +260,7 @@ sentryTest.skip('does not capture response body when URL does not match', async
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
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 86913966aaa1..74f677280657 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
@@ -43,7 +43,7 @@ sentryTest('handles empty headers', async ({ getLocalTestUrl, page, browserName
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -111,7 +111,7 @@ sentryTest('captures response headers', async ({ getLocalTestUrl, page }) => {
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -185,7 +185,7 @@ sentryTest('does not capture response headers if URL does not match', async ({ g
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
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 58375d604dd2..65fcd44d74ad 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
@@ -49,7 +49,7 @@ sentryTest('captures response size from Content-Length header if available', asy
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -131,7 +131,7 @@ sentryTest('captures response size without Content-Length header', async ({ getL
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
@@ -213,7 +213,7 @@ sentryTest('captures response size from non-text response body', async ({ getLoc
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'fetch',
type: 'http',
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 df63c10084b4..715fddecfd34 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
@@ -61,5 +61,5 @@ sentryTest('captures correct timestamps', async ({ getLocalTestUrl, page, browse
expect(endTimestamp).toEqual(expect.any(Number));
expect(endTimestamp).toBeGreaterThan(startTimestamp);
- expect(eventData!.breadcrumbs![0].timestamp).toBeGreaterThan(startTimestamp);
+ expect(eventData.breadcrumbs![0].timestamp).toBeGreaterThan(startTimestamp);
});
diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts
index 6b0e6e88f3e1..90e6ae4ec154 100644
--- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts
@@ -47,7 +47,7 @@ sentryTest('captures text request body', async ({ getLocalTestUrl, page, browser
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -119,7 +119,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestUrl, page, browser
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -195,7 +195,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestUrl, page, bro
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -267,7 +267,7 @@ sentryTest('captures text request body when matching relative URL', async ({ get
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -339,7 +339,7 @@ sentryTest('does not capture request body when URL does not match', async ({ get
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts
index 12f3a59f5fb1..2f40dc9bb469 100644
--- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts
@@ -51,7 +51,7 @@ sentryTest('captures request headers', async ({ getLocalTestUrl, page, browserNa
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -129,7 +129,7 @@ sentryTest('does not capture request headers if URL does not match', async ({ ge
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts
index 2fe599bbe831..48f2f30ebb79 100644
--- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts
@@ -49,7 +49,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -131,7 +131,7 @@ sentryTest('captures request size from non-text request body', async ({ getLocal
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts
index 4764d32b16df..f74842f38883 100644
--- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts
@@ -51,7 +51,7 @@ sentryTest('captures text response body', async ({ getLocalTestUrl, page, browse
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -127,7 +127,7 @@ sentryTest('captures JSON response body', async ({ getLocalTestUrl, page, browse
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -205,7 +205,7 @@ sentryTest('captures JSON response body when responseType=json', async ({ getLoc
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -281,7 +281,7 @@ sentryTest('captures non-text response body', async ({ getLocalTestUrl, page, br
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -357,7 +357,7 @@ sentryTest('does not capture response body when URL does not match', async ({ ge
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts
index f4b444455b46..d728c6995592 100644
--- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts
@@ -54,7 +54,7 @@ sentryTest('captures response headers', async ({ getLocalTestUrl, page, browserN
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -136,7 +136,7 @@ sentryTest(
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts
index 5f14ac8b17cd..a3c3ba9746fe 100644
--- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts
@@ -52,7 +52,7 @@ sentryTest(
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
@@ -223,7 +223,7 @@ sentryTest('captures response size for non-string bodies', async ({ getLocalTest
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData?.breadcrumbs?.length).toBe(1);
- expect(eventData!.breadcrumbs![0]).toEqual({
+ expect(eventData.breadcrumbs![0]).toEqual({
timestamp: expect.any(Number),
category: 'xhr',
type: 'http',
diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts
index ef4b9aee3c26..40c9462fff21 100644
--- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts
+++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts
@@ -68,5 +68,5 @@ sentryTest('captures correct timestamps', async ({ getLocalTestUrl, page, browse
expect(endTimestamp).toEqual(expect.any(Number));
expect(endTimestamp).toBeGreaterThan(startTimestamp);
- expect(eventData!.breadcrumbs![0].timestamp).toBeGreaterThan(startTimestamp);
+ expect(eventData.breadcrumbs![0].timestamp).toBeGreaterThan(startTimestamp);
});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts
index e17cbbbda691..d5dabb5d0ca5 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts
@@ -28,7 +28,7 @@ sentryTest(
const imageFastSpan = elementTimingSpans?.find(({ description }) => description === 'element[image-fast]');
const imageFastRenderTime = imageFastSpan?.data['element.render_time'];
const imageFastLoadTime = imageFastSpan?.data['element.load_time'];
- const duration = imageFastSpan!.timestamp! - imageFastSpan!.start_timestamp!;
+ const duration = imageFastSpan!.timestamp! - imageFastSpan!.start_timestamp;
expect(imageFastSpan).toBeDefined();
expect(imageFastSpan?.data).toEqual({
@@ -58,7 +58,7 @@ sentryTest(
const text1Span = elementTimingSpans?.find(({ data }) => data?.['element.identifier'] === 'text1');
const text1RenderTime = text1Span?.data['element.render_time'];
const text1LoadTime = text1Span?.data['element.load_time'];
- const text1Duration = text1Span!.timestamp! - text1Span!.start_timestamp!;
+ const text1Duration = text1Span!.timestamp! - text1Span!.start_timestamp;
expect(text1Span).toBeDefined();
expect(text1Span?.data).toEqual({
'sentry.op': 'ui.elementtiming',
@@ -109,7 +109,7 @@ sentryTest(
});
const imageSlowRenderTime = imageSlowSpan?.data['element.render_time'];
const imageSlowLoadTime = imageSlowSpan?.data['element.load_time'];
- const imageSlowDuration = imageSlowSpan!.timestamp! - imageSlowSpan!.start_timestamp!;
+ const imageSlowDuration = imageSlowSpan!.timestamp! - imageSlowSpan!.start_timestamp;
expect(imageSlowRenderTime).toBeGreaterThan(1400);
expect(imageSlowRenderTime).toBeLessThan(2000);
expect(imageSlowLoadTime).toBeGreaterThan(1400);
@@ -137,7 +137,7 @@ sentryTest(
});
const lazyImageRenderTime = lazyImageSpan?.data['element.render_time'];
const lazyImageLoadTime = lazyImageSpan?.data['element.load_time'];
- const lazyImageDuration = lazyImageSpan!.timestamp! - lazyImageSpan!.start_timestamp!;
+ const lazyImageDuration = lazyImageSpan!.timestamp! - lazyImageSpan!.start_timestamp;
expect(lazyImageRenderTime).toBeGreaterThan(1000);
expect(lazyImageRenderTime).toBeLessThan(1500);
expect(lazyImageLoadTime).toBeGreaterThan(1000);
@@ -155,7 +155,7 @@ sentryTest(
});
const lazyTextRenderTime = lazyTextSpan?.data['element.render_time'];
const lazyTextLoadTime = lazyTextSpan?.data['element.load_time'];
- const lazyTextDuration = lazyTextSpan!.timestamp! - lazyTextSpan!.start_timestamp!;
+ const lazyTextDuration = lazyTextSpan!.timestamp! - lazyTextSpan!.start_timestamp;
expect(lazyTextRenderTime).toBeGreaterThan(1000);
expect(lazyTextRenderTime).toBeLessThan(1500);
expect(lazyTextLoadTime).toBe(0);
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js
new file mode 100644
index 000000000000..7c200c542c56
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js
@@ -0,0 +1,8 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ tracesSampleRate: 1,
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/subject.js
new file mode 100644
index 000000000000..0ce39588eb1b
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/subject.js
@@ -0,0 +1,14 @@
+const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' });
+Sentry.setActiveSpanInBrowser(checkoutSpan);
+
+Sentry.startSpan({ name: 'checkout-step-1' }, () => {
+ Sentry.startSpan({ name: 'checkout-step-1-1' }, () => {
+ // ... `
+ });
+});
+
+Sentry.startSpan({ name: 'checkout-step-2' }, () => {
+ // ... `
+});
+
+checkoutSpan.end();
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts
new file mode 100644
index 000000000000..080ebbfa6e84
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts
@@ -0,0 +1,35 @@
+import { expect } from '@playwright/test';
+import { sentryTest } from '../../../../utils/fixtures';
+import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers';
+
+sentryTest('sets an inactive span active and adds child spans to it', async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipTracingTest()) {
+ sentryTest.skip();
+ }
+
+ const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow');
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+ await page.goto(url);
+
+ const checkoutEvent = envelopeRequestParser(await req);
+ const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id;
+ expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/);
+
+ expect(checkoutEvent.spans).toHaveLength(3);
+
+ const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1');
+ const checkoutStep11 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1-1');
+ const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2');
+
+ expect(checkoutStep1).toBeDefined();
+ expect(checkoutStep11).toBeDefined();
+ expect(checkoutStep2).toBeDefined();
+
+ expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId);
+ expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId);
+
+ // despite 1-1 being called within 1, it's still parented to the root span
+ // due to this being default behaviour in browser environments
+ expect(checkoutStep11?.parent_span_id).toBe(checkoutSpanId);
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js
new file mode 100644
index 000000000000..375a16cbf005
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js
@@ -0,0 +1,9 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ tracesSampleRate: 1,
+ parentSpanIsAlwaysRootSpan: false,
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/subject.js
new file mode 100644
index 000000000000..dc601cbf4d30
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/subject.js
@@ -0,0 +1,22 @@
+const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' });
+Sentry.setActiveSpanInBrowser(checkoutSpan);
+
+Sentry.startSpan({ name: 'checkout-step-1' }, () => {});
+
+const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' });
+Sentry.setActiveSpanInBrowser(checkoutStep2);
+
+Sentry.startSpan({ name: 'checkout-step-2-1' }, () => {
+ // ... `
+});
+checkoutStep2.end();
+
+Sentry.startSpan({ name: 'checkout-step-3' }, () => {});
+
+checkoutSpan.end();
+
+Sentry.startSpan({ name: 'post-checkout' }, () => {
+ Sentry.startSpan({ name: 'post-checkout-1' }, () => {
+ // ... `
+ });
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts
new file mode 100644
index 000000000000..2270b470123b
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts
@@ -0,0 +1,57 @@
+import { expect } from '@playwright/test';
+import { sentryTest } from '../../../../utils/fixtures';
+import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers';
+
+sentryTest(
+ 'nested calls to setActiveSpanInBrowser with parentSpanIsAlwaysRootSpan=false result in correct parenting',
+ async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipTracingTest()) {
+ sentryTest.skip();
+ }
+
+ const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow');
+ const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout');
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+ await page.goto(url);
+
+ const checkoutEvent = envelopeRequestParser(await req);
+ const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq);
+
+ const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id;
+ const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id;
+
+ expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/);
+ expect(postCheckoutSpanId).toMatch(/[a-f0-9]{16}/);
+
+ expect(checkoutEvent.spans).toHaveLength(4);
+ expect(postCheckoutEvent.spans).toHaveLength(1);
+
+ const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1');
+ const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2');
+ const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1');
+ const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3');
+
+ expect(checkoutStep1).toBeDefined();
+ expect(checkoutStep2).toBeDefined();
+ expect(checkoutStep21).toBeDefined();
+ expect(checkoutStep3).toBeDefined();
+
+ expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId);
+ expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId);
+
+ // with parentSpanIsAlwaysRootSpan=false, 2-1 is parented to 2 because
+ // 2 was the active span when 2-1 was started
+ expect(checkoutStep21?.parent_span_id).toBe(checkoutStep2?.span_id);
+
+ // since the parent of three is `checkoutSpan`, we correctly reset
+ // the active span to `checkoutSpan` after 2 ended
+ expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId);
+
+ // post-checkout trace is started as a new trace because ending checkoutSpan removes the active
+ // span on the scope
+ const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1');
+ expect(postCheckoutStep1).toBeDefined();
+ expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId);
+ },
+);
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js
new file mode 100644
index 000000000000..7c200c542c56
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js
@@ -0,0 +1,8 @@
+import * as Sentry from '@sentry/browser';
+
+window.Sentry = Sentry;
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ tracesSampleRate: 1,
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/subject.js
new file mode 100644
index 000000000000..dc601cbf4d30
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/subject.js
@@ -0,0 +1,22 @@
+const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' });
+Sentry.setActiveSpanInBrowser(checkoutSpan);
+
+Sentry.startSpan({ name: 'checkout-step-1' }, () => {});
+
+const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' });
+Sentry.setActiveSpanInBrowser(checkoutStep2);
+
+Sentry.startSpan({ name: 'checkout-step-2-1' }, () => {
+ // ... `
+});
+checkoutStep2.end();
+
+Sentry.startSpan({ name: 'checkout-step-3' }, () => {});
+
+checkoutSpan.end();
+
+Sentry.startSpan({ name: 'post-checkout' }, () => {
+ Sentry.startSpan({ name: 'post-checkout-1' }, () => {
+ // ... `
+ });
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts
new file mode 100644
index 000000000000..094bb0ed3dd8
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts
@@ -0,0 +1,52 @@
+import { expect } from '@playwright/test';
+import { sentryTest } from '../../../../utils/fixtures';
+import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers';
+
+sentryTest(
+ 'nested calls to setActiveSpanInBrowser still parent to root span by default',
+ async ({ getLocalTestUrl, page }) => {
+ if (shouldSkipTracingTest()) {
+ sentryTest.skip();
+ }
+
+ const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow');
+ const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout');
+
+ const url = await getLocalTestUrl({ testDir: __dirname });
+ await page.goto(url);
+
+ const checkoutEvent = envelopeRequestParser(await req);
+ const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq);
+
+ const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id;
+ const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id;
+
+ expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/);
+ expect(postCheckoutSpanId).toMatch(/[a-f0-9]{16}/);
+
+ expect(checkoutEvent.spans).toHaveLength(4);
+ expect(postCheckoutEvent.spans).toHaveLength(1);
+
+ const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1');
+ const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2');
+ const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1');
+ const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3');
+
+ expect(checkoutStep1).toBeDefined();
+ expect(checkoutStep2).toBeDefined();
+ expect(checkoutStep21).toBeDefined();
+ expect(checkoutStep3).toBeDefined();
+
+ expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId);
+ expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId);
+ expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId);
+
+ // despite 2-1 being called within 2 AND setting 2 as active span, it's still parented to the
+ // root span due to this being default behaviour in browser environments
+ expect(checkoutStep21?.parent_span_id).toBe(checkoutSpanId);
+
+ const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1');
+ expect(postCheckoutStep1).toBeDefined();
+ expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId);
+ },
+);
diff --git a/dev-packages/browser-integration-tests/suites/transport/offline/queued/test.ts b/dev-packages/browser-integration-tests/suites/transport/offline/queued/test.ts
index 13694a383c54..5df36411011c 100644
--- a/dev-packages/browser-integration-tests/suites/transport/offline/queued/test.ts
+++ b/dev-packages/browser-integration-tests/suites/transport/offline/queued/test.ts
@@ -37,7 +37,7 @@ sentryTest('should queue and retry events when they fail to send', async ({ getL
const eventData = await getMultipleSentryEnvelopeRequests(page, 3, { url, timeout: 10_000 });
// Filter out any client reports
- const events = eventData.filter(e => !('discarded_events' in e)) as Event[];
+ const events = eventData.filter(e => !('discarded_events' in e));
expect(events).toHaveLength(2);
diff --git a/dev-packages/browser-integration-tests/tsconfig.json b/dev-packages/browser-integration-tests/tsconfig.json
index ecc3b11d5e32..05605881f5d6 100644
--- a/dev-packages/browser-integration-tests/tsconfig.json
+++ b/dev-packages/browser-integration-tests/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
- "lib": ["dom", "es2019"],
+ "lib": ["dom", "ES2020"],
"moduleResolution": "node",
"noEmit": true,
"strict": true,
diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts
index b3f55b4550a0..dd75d2f6ee86 100644
--- a/dev-packages/browser-integration-tests/utils/helpers.ts
+++ b/dev-packages/browser-integration-tests/utils/helpers.ts
@@ -310,7 +310,7 @@ export async function waitForSession(page: Page): Promise {
* @returns `true` if we should skip the tracing test
*/
export function shouldSkipTracingTest(): boolean {
- const bundle = process.env.PW_BUNDLE as string | undefined;
+ const bundle = process.env.PW_BUNDLE;
return bundle != null && !bundle.includes('tracing') && !bundle.includes('esm') && !bundle.includes('cjs');
}
@@ -330,7 +330,7 @@ export function shouldSkipFeedbackTest(): boolean {
* @returns `true` if we should skip the feature flags test
*/
export function shouldSkipFeatureFlagsTest(): boolean {
- const bundle = process.env.PW_BUNDLE as string | undefined;
+ const bundle = process.env.PW_BUNDLE;
return bundle != null && !bundle.includes('esm') && !bundle.includes('cjs');
}
@@ -423,7 +423,7 @@ export async function getMultipleSentryEnvelopeRequests(
},
requestParser: (req: Request) => T = envelopeRequestParser as (req: Request) => T,
): Promise {
- return getMultipleRequests(page, count, envelopeUrlRegex, requestParser, options) as Promise;
+ return getMultipleRequests(page, count, envelopeUrlRegex, requestParser, options);
}
/**
diff --git a/dev-packages/browser-integration-tests/utils/replayHelpers.ts b/dev-packages/browser-integration-tests/utils/replayHelpers.ts
index f1f18967e69b..86a9e5ba3d78 100644
--- a/dev-packages/browser-integration-tests/utils/replayHelpers.ts
+++ b/dev-packages/browser-integration-tests/utils/replayHelpers.ts
@@ -431,7 +431,7 @@ export const replayEnvelopeParser = (request: Request | null): unknown[] => {
* @returns `true` if we should skip the replay test
*/
export function shouldSkipReplayTest(): boolean {
- const bundle = process.env.PW_BUNDLE as string | undefined;
+ const bundle = process.env.PW_BUNDLE;
return bundle != null && !bundle.includes('replay') && !bundle.includes('esm') && !bundle.includes('cjs');
}
diff --git a/dev-packages/browser-integration-tests/utils/wasmHelpers.ts b/dev-packages/browser-integration-tests/utils/wasmHelpers.ts
index cb8cf1bcb4b3..5287f5db0436 100644
--- a/dev-packages/browser-integration-tests/utils/wasmHelpers.ts
+++ b/dev-packages/browser-integration-tests/utils/wasmHelpers.ts
@@ -10,6 +10,6 @@ export function shouldSkipWASMTests(browser: string): boolean {
if (browser === 'webkit') {
return true;
}
- const bundle = process.env.PW_BUNDLE as string | undefined;
+ const bundle = process.env.PW_BUNDLE;
return bundle != null;
}
diff --git a/dev-packages/bundle-analyzer-scenarios/package.json b/dev-packages/bundle-analyzer-scenarios/package.json
index a6979802028c..aefad1717477 100644
--- a/dev-packages/bundle-analyzer-scenarios/package.json
+++ b/dev-packages/bundle-analyzer-scenarios/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/bundle-analyzer-scenarios",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Scenarios to test bundle analysis with",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/dev-packages/bundle-analyzer-scenarios",
diff --git a/dev-packages/clear-cache-gh-action/package.json b/dev-packages/clear-cache-gh-action/package.json
index eb05510731c0..f235d00a367d 100644
--- a/dev-packages/clear-cache-gh-action/package.json
+++ b/dev-packages/clear-cache-gh-action/package.json
@@ -1,7 +1,7 @@
{
"name": "@sentry-internal/clear-cache-gh-action",
"description": "An internal Github Action to clear GitHub caches.",
- "version": "10.14.0",
+ "version": "10.15.0",
"license": "MIT",
"engines": {
"node": ">=18"
diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json
index 23b9f99da2f8..ebab25a00609 100644
--- a/dev-packages/cloudflare-integration-tests/package.json
+++ b/dev-packages/cloudflare-integration-tests/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/cloudflare-integration-tests",
- "version": "10.14.0",
+ "version": "10.15.0",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -13,11 +13,11 @@
"test:watch": "yarn test --watch"
},
"dependencies": {
- "@sentry/cloudflare": "10.14.0"
+ "@sentry/cloudflare": "10.15.0"
},
"devDependencies": {
- "@cloudflare/workers-types": "^4.20250708.0",
- "@sentry-internal/test-utils": "10.14.0",
+ "@cloudflare/workers-types": "^4.20250922.0",
+ "@sentry-internal/test-utils": "10.15.0",
"vitest": "^3.2.4",
"wrangler": "4.22.0"
},
diff --git a/dev-packages/cloudflare-integration-tests/tsconfig.json b/dev-packages/cloudflare-integration-tests/tsconfig.json
index 07f106daf370..b93dc5f57c50 100644
--- a/dev-packages/cloudflare-integration-tests/tsconfig.json
+++ b/dev-packages/cloudflare-integration-tests/tsconfig.json
@@ -7,6 +7,7 @@
// Although this seems wrong to include `DOM` here, it's necessary to make
// global fetch available in tests in lower Node versions.
"lib": ["ES2020"],
- "esModuleInterop": true
+ "esModuleInterop": true,
+ "types": ["@cloudflare/workers-types"]
}
}
diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json
index 7a36e57d65b2..da52e7ebf9a2 100644
--- a/dev-packages/e2e-tests/package.json
+++ b/dev-packages/e2e-tests/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/e2e-tests",
- "version": "10.14.0",
+ "version": "10.15.0",
"license": "MIT",
"private": true,
"scripts": {
diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs
deleted file mode 100644
index d4cd56b78c90..000000000000
--- a/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as Sentry from '@sentry/aws-serverless';
-
-Sentry.init({
- dsn: process.env.SENTRY_DSN,
- tracesSampleRate: 1,
- debug: true,
- _experiments: {
- enableLambdaExtension: true,
- },
-});
-
-export const handler = async (event, context) => {
- Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => {
- return 'Hello, world!';
- });
-};
diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts
index 6ec76124140d..bb7ae03a96e7 100644
--- a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts
+++ b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts
@@ -242,52 +242,4 @@ test.describe('Lambda layer', () => {
}),
);
});
-
- test('experimental extension works', async ({ lambdaClient }) => {
- const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
- return transactionEvent?.transaction === 'LayerExperimentalExtension';
- });
-
- await lambdaClient.send(
- new InvokeCommand({
- FunctionName: 'LayerExperimentalExtension',
- Payload: JSON.stringify({}),
- }),
- );
-
- const transactionEvent = await transactionEventPromise;
-
- expect(transactionEvent.transaction).toEqual('LayerExperimentalExtension');
- expect(transactionEvent.contexts?.trace).toEqual({
- data: {
- 'sentry.sample_rate': 1,
- 'sentry.source': 'custom',
- 'sentry.origin': 'auto.otel.aws-lambda',
- 'sentry.op': 'function.aws.lambda',
- 'cloud.account.id': '012345678912',
- 'faas.execution': expect.any(String),
- 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:LayerExperimentalExtension',
- 'faas.coldstart': true,
- 'otel.kind': 'SERVER',
- },
- op: 'function.aws.lambda',
- origin: 'auto.otel.aws-lambda',
- span_id: expect.stringMatching(/[a-f0-9]{16}/),
- status: 'ok',
- trace_id: expect.stringMatching(/[a-f0-9]{32}/),
- });
-
- expect(transactionEvent.spans).toHaveLength(1);
-
- expect(transactionEvent.spans).toContainEqual(
- expect.objectContaining({
- data: expect.objectContaining({
- 'sentry.op': 'test',
- 'sentry.origin': 'manual',
- }),
- description: 'manual-span',
- op: 'test',
- }),
- );
- });
});
diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json
index d38eb397cab2..a4a4f7b99266 100644
--- a/dev-packages/external-contributor-gh-action/package.json
+++ b/dev-packages/external-contributor-gh-action/package.json
@@ -1,7 +1,7 @@
{
"name": "@sentry-internal/external-contributor-gh-action",
"description": "An internal Github Action to add external contributors to the CHANGELOG.md file.",
- "version": "10.14.0",
+ "version": "10.15.0",
"license": "MIT",
"engines": {
"node": ">=18"
diff --git a/dev-packages/node-core-integration-tests/package.json b/dev-packages/node-core-integration-tests/package.json
index e227071a7082..5a33b7c710d2 100644
--- a/dev-packages/node-core-integration-tests/package.json
+++ b/dev-packages/node-core-integration-tests/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/node-core-integration-tests",
- "version": "10.14.0",
+ "version": "10.15.0",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -34,8 +34,8 @@
"@opentelemetry/resources": "^2.1.0",
"@opentelemetry/sdk-trace-base": "^2.1.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
- "@sentry/core": "10.14.0",
- "@sentry/node-core": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node-core": "10.15.0",
"body-parser": "^1.20.3",
"cors": "^2.8.5",
"cron": "^3.1.6",
diff --git a/dev-packages/node-core-integration-tests/tsconfig.json b/dev-packages/node-core-integration-tests/tsconfig.json
index 1cd6c0aec734..a554f62a0fc6 100644
--- a/dev-packages/node-core-integration-tests/tsconfig.json
+++ b/dev-packages/node-core-integration-tests/tsconfig.json
@@ -6,7 +6,7 @@
"compilerOptions": {
// Although this seems wrong to include `DOM` here, it's necessary to make
// global fetch available in tests in lower Node versions.
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
// package-specific options
"esModuleInterop": true,
"types": ["node"]
diff --git a/dev-packages/node-core-integration-tests/tsconfig.test.json b/dev-packages/node-core-integration-tests/tsconfig.test.json
index 45a6e39b0054..89f44d610021 100644
--- a/dev-packages/node-core-integration-tests/tsconfig.test.json
+++ b/dev-packages/node-core-integration-tests/tsconfig.test.json
@@ -6,7 +6,7 @@
"compilerOptions": {
// Although this seems wrong to include `DOM` here, it's necessary to make
// global fetch available in tests in lower Node versions.
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
// should include all types from `./tsconfig.json` plus types for all test frameworks used
"types": ["node"]
diff --git a/dev-packages/node-core-integration-tests/utils/runner.ts b/dev-packages/node-core-integration-tests/utils/runner.ts
index 273680607ffb..da6184dcbb42 100644
--- a/dev-packages/node-core-integration-tests/utils/runner.ts
+++ b/dev-packages/node-core-integration-tests/utils/runner.ts
@@ -427,7 +427,7 @@ export function createRunner(...paths: string[]) {
? runDockerCompose(dockerOptions)
: Promise.resolve(undefined);
- const startup = Promise.all([dockerStartup, serverStartup]) as Promise<[DockerStartup, ServerStartup]>;
+ const startup = Promise.all([dockerStartup, serverStartup]);
startup
.then(([dockerChild, [mockServerPort, mockServerClose]]) => {
diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json
index bf183115df5f..b9069e7b6c58 100644
--- a/dev-packages/node-integration-tests/package.json
+++ b/dev-packages/node-integration-tests/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/node-integration-tests",
- "version": "10.14.0",
+ "version": "10.15.0",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -27,13 +27,14 @@
"@aws-sdk/client-s3": "^3.552.0",
"@google/genai": "^1.20.0",
"@hapi/hapi": "^21.3.10",
+ "@hono/node-server": "^1.19.4",
"@nestjs/common": "11.1.3",
"@nestjs/core": "11.1.3",
"@nestjs/platform-express": "11.1.3",
"@prisma/client": "6.15.0",
- "@sentry/aws-serverless": "10.14.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
+ "@sentry/aws-serverless": "10.15.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
"@types/mongodb": "^3.6.20",
"@types/mysql": "^2.15.21",
"@types/pg": "^8.6.5",
@@ -49,6 +50,7 @@
"express": "^4.21.1",
"generic-pool": "^3.9.0",
"graphql": "^16.3.0",
+ "hono": "^4.9.8",
"http-terminator": "^3.2.0",
"ioredis": "^5.4.1",
"kafkajs": "2.2.4",
@@ -75,7 +77,7 @@
"yargs": "^16.2.0"
},
"devDependencies": {
- "@sentry-internal/test-utils": "10.14.0",
+ "@sentry-internal/test-utils": "10.15.0",
"@types/amqplib": "^0.10.5",
"@types/node-cron": "^3.0.11",
"@types/node-schedule": "^2.1.7",
diff --git a/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts
index f9952ce43a9f..0f5bf73d7e5b 100644
--- a/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts
+++ b/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts
@@ -36,7 +36,7 @@ app.get('/test/init', (_req, res) => {
transport: loggingTransport,
});
// Set this on initial scope, to ensure it can be inherited
- initialCurrentScope.setClient(Sentry.getClient()!);
+ initialCurrentScope.setClient(Sentry.getClient());
Sentry.addBreadcrumb({ message: 'init breadcrumb' });
Sentry.setTag('init', 'tag');
diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs
new file mode 100644
index 000000000000..46a27dd03b74
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs
@@ -0,0 +1,9 @@
+import * as Sentry from '@sentry/node';
+import { loggingTransport } from '@sentry-internal/node-integration-tests';
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ tracesSampleRate: 1.0,
+ transport: loggingTransport,
+});
diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs
new file mode 100644
index 000000000000..7ef73c3d22c3
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs
@@ -0,0 +1,196 @@
+import { serve } from '@hono/node-server';
+import * as Sentry from '@sentry/node';
+import { sendPortToRunner } from '@sentry-internal/node-core-integration-tests';
+import { Hono } from 'hono';
+import { HTTPException } from 'hono/http-exception';
+
+const app = new Hono();
+
+Sentry.setupHonoErrorHandler(app);
+
+// Global middleware to capture all requests
+app.use(async function global(c, next) {
+ await next();
+});
+
+const basePaths = ['/sync', '/async'];
+const methods = ['get', 'post', 'put', 'delete', 'patch'];
+
+basePaths.forEach(basePath => {
+ // Sub-path middleware to capture all requests under the basePath
+ app.use(`${basePath}/*`, async function base(c, next) {
+ await next();
+ });
+
+ const baseApp = new Hono();
+ methods.forEach(method => {
+ baseApp[method]('/', c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ });
+
+ baseApp[method](
+ '/middleware',
+ // anonymous middleware
+ async (c, next) => {
+ await next();
+ },
+ c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ },
+ );
+
+ // anonymous middleware
+ baseApp[method]('/middleware/separately', async (c, next) => {
+ await next();
+ });
+
+ baseApp[method]('/middleware/separately', async c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ });
+
+ baseApp.all('/all', c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ });
+
+ baseApp.all(
+ '/all/middleware',
+ // anonymous middleware
+ async (c, next) => {
+ await next();
+ },
+ c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ },
+ );
+
+ // anonymous middleware
+ baseApp.all('/all/middleware/separately', async (c, next) => {
+ await next();
+ });
+
+ baseApp.all('/all/middleware/separately', async c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ });
+
+ baseApp.on(method, '/on', c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ });
+
+ baseApp.on(
+ method,
+ '/on/middleware',
+ // anonymous middleware
+ async (c, next) => {
+ await next();
+ },
+ c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ },
+ );
+
+ // anonymous middleware
+ baseApp.on(method, '/on/middleware/separately', async (c, next) => {
+ await next();
+ });
+
+ baseApp.on(method, '/on/middleware/separately', async c => {
+ const response = c.text('response 200');
+ if (basePath === '/sync') return response;
+ return Promise.resolve(response);
+ });
+
+ baseApp[method]('/401', () => {
+ const response = new HTTPException(401, { message: 'response 401' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp.all('/all/401', () => {
+ const response = new HTTPException(401, { message: 'response 401' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp.on(method, '/on/401', () => {
+ const response = new HTTPException(401, { message: 'response 401' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp[method]('/402', () => {
+ const response = new HTTPException(402, { message: 'response 402' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp.all('/all/402', () => {
+ const response = new HTTPException(402, { message: 'response 402' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp.on(method, '/on/402', () => {
+ const response = new HTTPException(402, { message: 'response 402' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp[method]('/403', () => {
+ const response = new HTTPException(403, { message: 'response 403' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp.all('/all/403', () => {
+ const response = new HTTPException(403, { message: 'response 403' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp.on(method, '/on/403', () => {
+ const response = new HTTPException(403, { message: 'response 403' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp[method]('/500', () => {
+ const response = new HTTPException(500, { message: 'response 500' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp.all('/all/500', () => {
+ const response = new HTTPException(500, { message: 'response 500' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+
+ baseApp.on(method, '/on/500', () => {
+ const response = new HTTPException(500, { message: 'response 500' });
+ if (basePath === '/sync') throw response;
+ return Promise.reject(response);
+ });
+ });
+
+ app.route(basePath, baseApp);
+});
+
+const port = 8787;
+serve({ fetch: app.fetch, port });
+sendPortToRunner(port);
diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/test.ts b/dev-packages/node-integration-tests/suites/tracing/hono/test.ts
new file mode 100644
index 000000000000..67d0ff8b56fb
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing/hono/test.ts
@@ -0,0 +1,251 @@
+import { afterAll, describe, expect } from 'vitest';
+import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
+
+describe('hono tracing', () => {
+ afterAll(() => {
+ cleanupChildProcesses();
+ });
+
+ createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
+ describe.each(['/sync', '/async'] as const)('when using %s route', route => {
+ describe.each(['get', 'post', 'put', 'delete', 'patch'] as const)('when using %s method', method => {
+ describe.each(['/', '/all', '/on'])('when using %s path', path => {
+ test('should handle transaction', async () => {
+ const runner = createRunner()
+ .expect({
+ transaction: {
+ transaction: `${method.toUpperCase()} ${route}${path === '/' ? '' : path}`,
+ spans: expect.arrayContaining([
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'sentryRequestMiddleware',
+ 'hono.type': 'middleware',
+ }),
+ description: 'sentryRequestMiddleware',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'sentryErrorMiddleware',
+ 'hono.type': 'middleware',
+ }),
+ description: 'sentryErrorMiddleware',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'global',
+ 'hono.type': 'middleware',
+ }),
+ description: 'global',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'base',
+ 'hono.type': 'middleware',
+ }),
+ description: 'base',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': `${route}${path === '/' ? '' : path}`,
+ 'hono.type': 'request_handler',
+ }),
+ description: `${route}${path === '/' ? '' : path}`,
+ op: 'request_handler.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ ]),
+ },
+ })
+ .start();
+ runner.makeRequest(method, `${route}${path === '/' ? '' : path}`);
+ await runner.completed();
+ });
+
+ test('should handle transaction with anonymous middleware', async () => {
+ const runner = createRunner()
+ .expect({
+ transaction: {
+ transaction: `${method.toUpperCase()} ${route}${path === '/' ? '' : path}/middleware`,
+ spans: expect.arrayContaining([
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'sentryRequestMiddleware',
+ 'hono.type': 'middleware',
+ }),
+ description: 'sentryRequestMiddleware',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'sentryErrorMiddleware',
+ 'hono.type': 'middleware',
+ }),
+ description: 'sentryErrorMiddleware',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'global',
+ 'hono.type': 'middleware',
+ }),
+ description: 'global',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'base',
+ 'hono.type': 'middleware',
+ }),
+ description: 'base',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'anonymous',
+ 'hono.type': 'middleware',
+ }),
+ description: 'anonymous',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': `${route}${path === '/' ? '' : path}/middleware`,
+ 'hono.type': 'request_handler',
+ }),
+ description: `${route}${path === '/' ? '' : path}/middleware`,
+ op: 'request_handler.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ ]),
+ },
+ })
+ .start();
+ runner.makeRequest(method, `${route}${path === '/' ? '' : path}/middleware`);
+ await runner.completed();
+ });
+
+ test('should handle transaction with separate middleware', async () => {
+ const runner = createRunner()
+ .expect({
+ transaction: {
+ transaction: `${method.toUpperCase()} ${route}${path === '/' ? '' : path}/middleware/separately`,
+ spans: expect.arrayContaining([
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'sentryRequestMiddleware',
+ 'hono.type': 'middleware',
+ }),
+ description: 'sentryRequestMiddleware',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'sentryErrorMiddleware',
+ 'hono.type': 'middleware',
+ }),
+ description: 'sentryErrorMiddleware',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'global',
+ 'hono.type': 'middleware',
+ }),
+ description: 'global',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'base',
+ 'hono.type': 'middleware',
+ }),
+ description: 'base',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': 'anonymous',
+ 'hono.type': 'middleware',
+ }),
+ description: 'anonymous',
+ op: 'middleware.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'hono.name': `${route}${path === '/' ? '' : path}/middleware/separately`,
+ 'hono.type': 'request_handler',
+ }),
+ description: `${route}${path === '/' ? '' : path}/middleware/separately`,
+ op: 'request_handler.hono',
+ origin: 'auto.http.otel.hono',
+ }),
+ ]),
+ },
+ })
+ .start();
+ runner.makeRequest(method, `${route}${path === '/' ? '' : path}/middleware/separately`);
+ await runner.completed();
+ });
+
+ test('should handle returned errors for %s path', async () => {
+ const runner = createRunner()
+ .ignore('transaction')
+ .expect({
+ event: {
+ exception: {
+ values: [
+ {
+ mechanism: {
+ type: 'auto.middleware.hono',
+ handled: false,
+ },
+ type: 'Error',
+ value: 'response 500',
+ },
+ ],
+ },
+ },
+ })
+ .start();
+ runner.makeRequest(method, `${route}${path === '/' ? '' : path}/500`, { expectError: true });
+ await runner.completed();
+ });
+
+ test.each(['/401', '/402', '/403', '/does-not-exist'])(
+ 'should ignores error %s path by default',
+ async (subPath: string) => {
+ const runner = createRunner()
+ .expect({
+ transaction: {
+ transaction: `${method.toUpperCase()} ${route}`,
+ },
+ })
+ .start();
+ runner.makeRequest(method, `${route}${path === '/' ? '' : path}${subPath}`, { expectError: true });
+ runner.makeRequest(method, route);
+ await runner.completed();
+ },
+ );
+ });
+ });
+ });
+ });
+});
diff --git a/dev-packages/node-integration-tests/tsconfig.json b/dev-packages/node-integration-tests/tsconfig.json
index 1cd6c0aec734..a554f62a0fc6 100644
--- a/dev-packages/node-integration-tests/tsconfig.json
+++ b/dev-packages/node-integration-tests/tsconfig.json
@@ -6,7 +6,7 @@
"compilerOptions": {
// Although this seems wrong to include `DOM` here, it's necessary to make
// global fetch available in tests in lower Node versions.
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
// package-specific options
"esModuleInterop": true,
"types": ["node"]
diff --git a/dev-packages/node-integration-tests/tsconfig.test.json b/dev-packages/node-integration-tests/tsconfig.test.json
index 45a6e39b0054..89f44d610021 100644
--- a/dev-packages/node-integration-tests/tsconfig.test.json
+++ b/dev-packages/node-integration-tests/tsconfig.test.json
@@ -6,7 +6,7 @@
"compilerOptions": {
// Although this seems wrong to include `DOM` here, it's necessary to make
// global fetch available in tests in lower Node versions.
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
// should include all types from `./tsconfig.json` plus types for all test frameworks used
"types": ["node"]
diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts
index dfe27dc5a4a5..b0c6467fd75a 100644
--- a/dev-packages/node-integration-tests/utils/runner.ts
+++ b/dev-packages/node-integration-tests/utils/runner.ts
@@ -167,7 +167,7 @@ type StartResult = {
getLogs(): string[];
getPort(): number | undefined;
makeRequest(
- method: 'get' | 'post',
+ method: 'get' | 'post' | 'put' | 'delete' | 'patch',
path: string,
options?: { headers?: Record; data?: BodyInit; expectError?: boolean },
): Promise;
@@ -538,7 +538,7 @@ export function createRunner(...paths: string[]) {
? runDockerCompose(dockerOptions)
: Promise.resolve(undefined);
- const startup = Promise.all([dockerStartup, serverStartup]) as Promise<[DockerStartup, ServerStartup]>;
+ const startup = Promise.all([dockerStartup, serverStartup]);
startup
.then(([dockerChild, [mockServerPort, mockServerClose]]) => {
@@ -655,7 +655,7 @@ export function createRunner(...paths: string[]) {
return scenarioServerPort;
},
makeRequest: async function (
- method: 'get' | 'post',
+ method: 'get' | 'post' | 'put' | 'delete' | 'patch',
path: string,
options: { headers?: Record; data?: BodyInit; expectError?: boolean } = {},
): Promise {
@@ -674,7 +674,7 @@ export function createRunner(...paths: string[]) {
if (process.env.DEBUG) log('making request', method, url, headers, body);
try {
- const res = await fetch(url, { headers, method, body });
+ const res = await fetch(url, { headers, method: method.toUpperCase(), body });
if (!res.ok) {
if (!expectError) {
diff --git a/dev-packages/node-overhead-gh-action/package.json b/dev-packages/node-overhead-gh-action/package.json
index 032732e056db..b1bd26279098 100644
--- a/dev-packages/node-overhead-gh-action/package.json
+++ b/dev-packages/node-overhead-gh-action/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/node-overhead-gh-action",
- "version": "10.14.0",
+ "version": "10.15.0",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -23,7 +23,7 @@
"fix": "eslint . --format stylish --fix"
},
"dependencies": {
- "@sentry/node": "10.14.0",
+ "@sentry/node": "10.15.0",
"express": "^4.21.1",
"mysql2": "^3.14.4"
},
diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json
index 4ec829a47fbc..470dedbd5f42 100644
--- a/dev-packages/rollup-utils/package.json
+++ b/dev-packages/rollup-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/rollup-utils",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Rollup utilities used at Sentry for the Sentry JavaScript SDK",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/rollup-utils",
diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json
index e8e817899a16..98953335f288 100644
--- a/dev-packages/size-limit-gh-action/package.json
+++ b/dev-packages/size-limit-gh-action/package.json
@@ -1,7 +1,7 @@
{
"name": "@sentry-internal/size-limit-gh-action",
"description": "An internal Github Action to compare the current size of a PR against the one on develop.",
- "version": "10.14.0",
+ "version": "10.15.0",
"license": "MIT",
"engines": {
"node": ">=18"
diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json
index 291aebab0629..df1cd72d4bff 100644
--- a/dev-packages/test-utils/package.json
+++ b/dev-packages/test-utils/package.json
@@ -1,6 +1,6 @@
{
"private": true,
- "version": "10.14.0",
+ "version": "10.15.0",
"name": "@sentry-internal/test-utils",
"author": "Sentry",
"license": "MIT",
@@ -48,7 +48,7 @@
},
"devDependencies": {
"@playwright/test": "~1.53.2",
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"volta": {
"extends": "../../package.json"
diff --git a/lerna.json b/lerna.json
index ac571e2f4218..ec2646c4569c 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
- "version": "10.14.0",
+ "version": "10.15.0",
"npmClient": "yarn"
}
diff --git a/packages/angular/package.json b/packages/angular/package.json
index 0c63bb990142..8fe9acac76a8 100644
--- a/packages/angular/package.json
+++ b/packages/angular/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/angular",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Angular",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular",
@@ -21,8 +21,8 @@
"rxjs": "^6.5.5 || ^7.x"
},
"dependencies": {
- "@sentry/browser": "10.14.0",
- "@sentry/core": "10.14.0",
+ "@sentry/browser": "10.15.0",
+ "@sentry/core": "10.15.0",
"tslib": "^2.4.1"
},
"devDependencies": {
diff --git a/packages/angular/tsconfig.ngc.json b/packages/angular/tsconfig.ngc.json
index df29c7ffdfee..2e86b8e3a6b4 100644
--- a/packages/angular/tsconfig.ngc.json
+++ b/packages/angular/tsconfig.ngc.json
@@ -5,9 +5,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
- "target": "es2018",
+ "target": "es2020",
"declarationMap": false,
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
"baseUrl": "./"
},
"angularCompilerOptions": {
diff --git a/packages/astro/package.json b/packages/astro/package.json
index b09f89be12a2..e66449a303ca 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/astro",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Astro",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro",
@@ -56,9 +56,9 @@
"astro": ">=3.x || >=4.0.0-beta || >=5.x"
},
"dependencies": {
- "@sentry/browser": "10.14.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
+ "@sentry/browser": "10.15.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
"@sentry/vite-plugin": "^4.1.0"
},
"devDependencies": {
diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts
index de4079c4b5c4..d39cb5e4484d 100644
--- a/packages/astro/src/index.server.ts
+++ b/packages/astro/src/index.server.ts
@@ -64,6 +64,7 @@ export {
winterCGHeadersToDict,
graphqlIntegration,
hapiIntegration,
+ honoIntegration,
httpIntegration,
// eslint-disable-next-line deprecation/deprecation
inboundFiltersIntegration,
@@ -116,6 +117,7 @@ export {
setupConnectErrorHandler,
setupExpressErrorHandler,
setupHapiErrorHandler,
+ setupHonoErrorHandler,
setupKoaErrorHandler,
setUser,
spanToBaggageHeader,
diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts
index 10e314268264..020365dda627 100644
--- a/packages/astro/test/server/middleware.test.ts
+++ b/packages/astro/test/server/middleware.test.ts
@@ -200,9 +200,9 @@ describe('sentryMiddleware', () => {
// @ts-expect-error, a partial ctx object is fine here
const resultFromNext = await middleware(ctx, next);
+ expect(resultFromNext).toBeDefined();
expect(resultFromNext?.headers.get('content-type')).toEqual('text/html');
-
- await expect(() => resultFromNext!.text()).rejects.toThrowError();
+ await expect(() => resultFromNext?.text()).rejects.toThrowError();
expect(captureExceptionSpy).toHaveBeenCalledWith(error, {
mechanism: { handled: false, type: 'auto.middleware.astro' },
diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json
index 560626ed6578..f5361a8428a3 100644
--- a/packages/aws-serverless/package.json
+++ b/packages/aws-serverless/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/aws-serverless",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for AWS Lambda and AWS Serverless Environments",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/aws-serverless",
@@ -69,8 +69,8 @@
"@opentelemetry/instrumentation": "^0.204.0",
"@opentelemetry/instrumentation-aws-sdk": "0.59.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
"@types/aws-lambda": "^8.10.62"
},
"devDependencies": {
diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts
index 0cbe5879b02e..cfab7b72754b 100644
--- a/packages/aws-serverless/src/index.ts
+++ b/packages/aws-serverless/src/index.ts
@@ -111,6 +111,8 @@ export {
createSentryWinstonTransport,
hapiIntegration,
setupHapiErrorHandler,
+ honoIntegration,
+ setupHonoErrorHandler,
spotlightIntegration,
initOpenTelemetry,
spanToJSON,
diff --git a/packages/aws-serverless/src/init.ts b/packages/aws-serverless/src/init.ts
index 9de744bedf34..6640db8ec5fa 100644
--- a/packages/aws-serverless/src/init.ts
+++ b/packages/aws-serverless/src/init.ts
@@ -15,12 +15,10 @@ export function getDefaultIntegrations(_options: Options): Integration[] {
}
export interface AwsServerlessOptions extends NodeOptions {
- _experiments?: NodeOptions['_experiments'] & {
- /**
- * If proxying Sentry events through the Sentry Lambda extension should be enabled.
- */
- enableLambdaExtension?: boolean;
- };
+ /**
+ * If Sentry events should be proxied through the Lambda extension when using the Lambda layer. Defaults to `true` when using the Lambda layer.
+ */
+ useLayerExtension?: boolean;
}
/**
@@ -29,14 +27,14 @@ export interface AwsServerlessOptions extends NodeOptions {
* @param options Configuration options for the SDK, @see {@link AWSLambdaOptions}.
*/
export function init(options: AwsServerlessOptions = {}): NodeClient | undefined {
+ const sdkSource = getSDKSource();
const opts = {
defaultIntegrations: getDefaultIntegrations(options),
+ useLayerExtension: sdkSource === 'aws-lambda-layer' && !options.tunnel,
...options,
};
- const sdkSource = getSDKSource();
-
- if (opts._experiments?.enableLambdaExtension) {
+ if (opts.useLayerExtension) {
if (sdkSource === 'aws-lambda-layer') {
if (!opts.tunnel) {
DEBUG_BUILD && debug.log('Proxying Sentry events through the Sentry Lambda extension');
diff --git a/packages/aws-serverless/test/init.test.ts b/packages/aws-serverless/test/init.test.ts
index b4aa7ddc0d2b..576257e3f3e4 100644
--- a/packages/aws-serverless/test/init.test.ts
+++ b/packages/aws-serverless/test/init.test.ts
@@ -18,14 +18,12 @@ const mockGetSDKSource = vi.mocked(getSDKSource);
const mockInitWithoutDefaultIntegrations = vi.mocked(initWithoutDefaultIntegrations);
describe('init', () => {
- describe('experimental Lambda extension support', () => {
+ describe('Lambda extension setup', () => {
test('should preserve user-provided tunnel option when Lambda extension is enabled', () => {
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {
tunnel: 'https://custom-tunnel.example.com',
- _experiments: {
- enableLambdaExtension: true,
- },
+ useLayerExtension: true,
};
init(options);
@@ -40,9 +38,7 @@ describe('init', () => {
test('should set default tunnel when Lambda extension is enabled and SDK source is aws-lambda-layer', () => {
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {
- _experiments: {
- enableLambdaExtension: true,
- },
+ useLayerExtension: true,
};
init(options);
@@ -57,9 +53,7 @@ describe('init', () => {
test('should not set tunnel when Lambda extension is disabled', () => {
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {
- _experiments: {
- enableLambdaExtension: false,
- },
+ useLayerExtension: false,
};
init(options);
@@ -74,9 +68,7 @@ describe('init', () => {
test('should not set tunnel when SDK source is not aws-lambda-layer even with Lambda extension enabled', () => {
mockGetSDKSource.mockReturnValue('npm');
const options: AwsServerlessOptions = {
- _experiments: {
- enableLambdaExtension: true,
- },
+ useLayerExtension: true,
};
init(options);
@@ -88,17 +80,52 @@ describe('init', () => {
);
});
- test('should not set tunnel when no experiments are provided', () => {
+ test('should default useLayerExtension to true when SDK source is aws-lambda-layer', () => {
mockGetSDKSource.mockReturnValue('aws-lambda-layer');
const options: AwsServerlessOptions = {};
init(options);
+ expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
+ expect.objectContaining({
+ useLayerExtension: true,
+ tunnel: 'http://localhost:9000/envelope',
+ }),
+ );
+ });
+
+ test('should default useLayerExtension to false when SDK source is not aws-lambda-layer', () => {
+ mockGetSDKSource.mockReturnValue('npm');
+ const options: AwsServerlessOptions = {};
+
+ init(options);
+
+ expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
+ expect.objectContaining({
+ useLayerExtension: false,
+ }),
+ );
expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
expect.not.objectContaining({
tunnel: expect.any(String),
}),
);
});
+
+ test('should default useLayerExtension to false when tunnel is provided even when SDK source is aws-lambda-layer', () => {
+ mockGetSDKSource.mockReturnValue('aws-lambda-layer');
+ const options: AwsServerlessOptions = {
+ tunnel: 'https://custom-tunnel.example.com',
+ };
+
+ init(options);
+
+ expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith(
+ expect.objectContaining({
+ useLayerExtension: false,
+ tunnel: 'https://custom-tunnel.example.com',
+ }),
+ );
+ });
});
});
diff --git a/packages/aws-serverless/test/sdk.test.ts b/packages/aws-serverless/test/sdk.test.ts
index d2047274d744..a3aef6a10b5d 100644
--- a/packages/aws-serverless/test/sdk.test.ts
+++ b/packages/aws-serverless/test/sdk.test.ts
@@ -19,7 +19,7 @@ const mockScope = {
vi.mock('@sentry/node', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node');
+ const original = await vi.importActual('@sentry/node');
return {
...original,
initWithoutDefaultIntegrations: (options: unknown) => {
@@ -459,7 +459,7 @@ describe('AWSLambda', () => {
const streamError = new Error('stream error');
const streamingHandler = vi.fn(async (_event, responseStream, _context) => {
// Simulate stream error by calling the error listener
- const errorListener = (responseStream.on as any).mock.calls.find((call: any[]) => call[0] === 'error')?.[1];
+ const errorListener = responseStream.on.mock.calls.find((call: any[]) => call[0] === 'error')?.[1];
if (errorListener) {
errorListener(streamError);
}
diff --git a/packages/aws-serverless/test/utils.test.ts b/packages/aws-serverless/test/utils.test.ts
index 8fba98d0add6..2f3516ce8e35 100644
--- a/packages/aws-serverless/test/utils.test.ts
+++ b/packages/aws-serverless/test/utils.test.ts
@@ -4,7 +4,7 @@ import { eventContextExtractor, getAwsTraceData } from '../src/utils';
const mockExtractContext = vi.fn();
vi.mock('@opentelemetry/api', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const actualApi = (await vi.importActual('@opentelemetry/api')) as typeof import('@opentelemetry/api');
+ const actualApi = await vi.importActual('@opentelemetry/api');
return {
...actualApi,
propagation: {
diff --git a/packages/aws-serverless/tsconfig.json b/packages/aws-serverless/tsconfig.json
index fd68e15254db..ea4b60f7b1ae 100644
--- a/packages/aws-serverless/tsconfig.json
+++ b/packages/aws-serverless/tsconfig.json
@@ -5,7 +5,7 @@
"compilerOptions": {
// package-specific options
- "target": "ES2018",
+ "target": "es2020",
"resolveJsonModule": true
}
}
diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json
index ac311da388b2..dfa69ad9ef25 100644
--- a/packages/browser-utils/package.json
+++ b/packages/browser-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/browser-utils",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Browser Utilities for all Sentry JavaScript SDKs",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser-utils",
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts
index 8b1592408e8a..2c61408c1d76 100644
--- a/packages/browser-utils/src/metrics/browserMetrics.ts
+++ b/packages/browser-utils/src/metrics/browserMetrics.ts
@@ -473,8 +473,8 @@ export function _addMeasureSpans(
// Measurements from third parties can be off, which would create invalid spans, dropping transactions in the process.
if (measureStartTimestamp <= measureEndTimestamp) {
startAndEndSpan(span, measureStartTimestamp, measureEndTimestamp, {
- name: entry.name as string,
- op: entry.entryType as string,
+ name: entry.name,
+ op: entry.entryType,
attributes,
});
}
@@ -595,9 +595,9 @@ function _getEndPropertyNameForNavigationTiming(event: StartEventName): EndEvent
/** Create request and response related spans */
function _addRequest(span: Span, entry: PerformanceNavigationTiming, timeOrigin: number): void {
- const requestStartTimestamp = timeOrigin + msToSec(entry.requestStart as number);
- const responseEndTimestamp = timeOrigin + msToSec(entry.responseEnd as number);
- const responseStartTimestamp = timeOrigin + msToSec(entry.responseStart as number);
+ const requestStartTimestamp = timeOrigin + msToSec(entry.requestStart);
+ const responseEndTimestamp = timeOrigin + msToSec(entry.responseEnd);
+ const responseStartTimestamp = timeOrigin + msToSec(entry.responseStart);
if (entry.responseEnd) {
// It is possible that we are collecting these metrics when the page hasn't finished loading yet, for example when the HTML slowly streams in.
// In this case, ie. when the document request hasn't finished yet, `entry.responseEnd` will be 0.
diff --git a/packages/browser-utils/src/metrics/resourceTiming.ts b/packages/browser-utils/src/metrics/resourceTiming.ts
index fe613355c55d..5a711d307cf3 100644
--- a/packages/browser-utils/src/metrics/resourceTiming.ts
+++ b/packages/browser-utils/src/metrics/resourceTiming.ts
@@ -2,8 +2,10 @@ import type { SpanAttributes } from '@sentry/core';
import { browserPerformanceTimeOrigin } from '@sentry/core';
import { extractNetworkProtocol, getBrowserPerformanceAPI } from './utils';
-function getAbsoluteTime(time = 0): number {
- return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000;
+function getAbsoluteTime(time: number | undefined): number | undefined {
+ // falsy values should be preserved so that we can later on drop undefined values and
+ // preserve 0 vals for cross-origin resources without proper `Timing-Allow-Origin` header.
+ return time ? ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000 : time;
}
/**
@@ -30,7 +32,7 @@ export function resourceTimingToSpanAttributes(resourceTiming: PerformanceResour
return timingSpanData;
}
- return {
+ return dropUndefinedKeysFromObject({
...timingSpanData,
'http.request.redirect_start': getAbsoluteTime(resourceTiming.redirectStart),
@@ -55,6 +57,16 @@ export function resourceTimingToSpanAttributes(resourceTiming: PerformanceResour
// For TTFB we actually want the relative time from timeOrigin to responseStart
// This way, TTFB always measures the "first page load" experience.
// see: https://web.dev/articles/ttfb#measure-resource-requests
- 'http.request.time_to_first_byte': (resourceTiming.responseStart ?? 0) / 1000,
- };
+ 'http.request.time_to_first_byte':
+ resourceTiming.responseStart != null ? resourceTiming.responseStart / 1000 : undefined,
+ });
+}
+
+/**
+ * Remove properties with `undefined` as value from an object.
+ * In contrast to `dropUndefinedKeys` in core this funciton only works on first-level
+ * key-value objects and does not recursively go into object properties or arrays.
+ */
+function dropUndefinedKeysFromObject(attrs: T): Partial {
+ return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value != null)) as Partial;
}
diff --git a/packages/browser-utils/src/metrics/web-vitals/getCLS.ts b/packages/browser-utils/src/metrics/web-vitals/getCLS.ts
index 1b4d50a7c44e..c40f993f8ca8 100644
--- a/packages/browser-utils/src/metrics/web-vitals/getCLS.ts
+++ b/packages/browser-utils/src/metrics/web-vitals/getCLS.ts
@@ -74,7 +74,7 @@ export const onCLS = (onReport: (metric: CLSMetric) => void, opts: ReportOpts =
const po = observe('layout-shift', handleEntries);
if (po) {
- report = bindReporter(onReport, metric, CLSThresholds, opts!.reportAllChanges);
+ report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges);
WINDOW.document?.addEventListener('visibilitychange', () => {
if (WINDOW.document?.visibilityState === 'hidden') {
diff --git a/packages/browser-utils/src/metrics/web-vitals/getLCP.ts b/packages/browser-utils/src/metrics/web-vitals/getLCP.ts
index 0f2f821d9bcc..6eafee698673 100644
--- a/packages/browser-utils/src/metrics/web-vitals/getLCP.ts
+++ b/packages/browser-utils/src/metrics/web-vitals/getLCP.ts
@@ -52,7 +52,7 @@ export const onLCP = (onReport: (metric: LCPMetric) => void, opts: ReportOpts =
const handleEntries = (entries: LCPMetric['entries']) => {
// If reportAllChanges is set then call this function for each entry,
// otherwise only consider the last one.
- if (!opts!.reportAllChanges) {
+ if (!opts.reportAllChanges) {
// eslint-disable-next-line no-param-reassign
entries = entries.slice(-1);
}
diff --git a/packages/browser-utils/src/metrics/web-vitals/lib/InteractionManager.ts b/packages/browser-utils/src/metrics/web-vitals/lib/InteractionManager.ts
index 033cdb2cb836..d4aea1683606 100644
--- a/packages/browser-utils/src/metrics/web-vitals/lib/InteractionManager.ts
+++ b/packages/browser-utils/src/metrics/web-vitals/lib/InteractionManager.ts
@@ -104,7 +104,7 @@ export class InteractionManager {
// The least-long of the 10 longest interactions.
const minLongestInteraction = this._longestInteractionList.at(-1);
- let interaction = this._longestInteractionMap.get(entry.interactionId!);
+ let interaction = this._longestInteractionMap.get(entry.interactionId);
// Only process the entry if it's possibly one of the ten longest,
// or if it's part of an existing interaction.
@@ -126,7 +126,7 @@ export class InteractionManager {
}
} else {
interaction = {
- id: entry.interactionId!,
+ id: entry.interactionId,
entries: [entry],
_latency: entry.duration,
};
diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts
index c717bb81ca0b..c734ec326b47 100644
--- a/packages/browser-utils/test/browser/browserMetrics.test.ts
+++ b/packages/browser-utils/test/browser/browserMetrics.test.ts
@@ -266,6 +266,18 @@ describe('_addResourceSpans', () => {
decodedBodySize: 593,
renderBlockingStatus: 'non-blocking',
nextHopProtocol: 'http/1.1',
+ connectStart: 1000,
+ connectEnd: 1001,
+ redirectStart: 1002,
+ redirectEnd: 1003,
+ fetchStart: 1004,
+ domainLookupStart: 1005,
+ domainLookupEnd: 1006,
+ requestStart: 1007,
+ responseStart: 1008,
+ responseEnd: 1009,
+ secureConnectionStart: 1005,
+ workerStart: 1006,
});
const timeOrigin = 100;
@@ -305,7 +317,7 @@ describe('_addResourceSpans', () => {
'http.request.response_end': expect.any(Number),
'http.request.response_start': expect.any(Number),
'http.request.secure_connection_start': expect.any(Number),
- 'http.request.time_to_first_byte': 0,
+ 'http.request.time_to_first_byte': 1.008,
'http.request.worker_start': expect.any(Number),
},
}),
@@ -492,6 +504,18 @@ describe('_addResourceSpans', () => {
encodedBodySize: null,
decodedBodySize: null,
nextHopProtocol: 'h3',
+ connectStart: 1000,
+ connectEnd: 1001,
+ redirectStart: 1002,
+ redirectEnd: 1003,
+ fetchStart: 1004,
+ domainLookupStart: 1005,
+ domainLookupEnd: 1006,
+ requestStart: 1007,
+ responseStart: 1008,
+ responseEnd: 1009,
+ secureConnectionStart: 1005,
+ workerStart: 1006,
} as unknown as PerformanceResourceTiming;
_addResourceSpans(span, entry, resourceEntryName, 100, 23, 345);
@@ -518,7 +542,7 @@ describe('_addResourceSpans', () => {
'http.request.response_end': expect.any(Number),
'http.request.response_start': expect.any(Number),
'http.request.secure_connection_start': expect.any(Number),
- 'http.request.time_to_first_byte': 0,
+ 'http.request.time_to_first_byte': 1.008,
'http.request.worker_start': expect.any(Number),
},
description: '/assets/to/css',
diff --git a/packages/browser-utils/test/metrics/resourceTiming.test.ts b/packages/browser-utils/test/metrics/resourceTiming.test.ts
index 881a7075441e..5e35097423d1 100644
--- a/packages/browser-utils/test/metrics/resourceTiming.test.ts
+++ b/packages/browser-utils/test/metrics/resourceTiming.test.ts
@@ -26,7 +26,7 @@ describe('resourceTimingToSpanAttributes', () => {
duration: 200,
initiatorType: 'fetch',
nextHopProtocol: 'h2',
- workerStart: 0,
+ workerStart: 1,
redirectStart: 10,
redirectEnd: 20,
fetchStart: 25,
@@ -276,6 +276,13 @@ describe('resourceTimingToSpanAttributes', () => {
});
it('handles zero timing values', () => {
+ /**
+ * Most resource timing entries have a 0 value if the resource was requested from
+ * a cross-origin source which does not return a matching `Timing-Allow-Origin` header.
+ *
+ * see: https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Resource_timing#cross-origin_timing_information
+ */
+
extractNetworkProtocolSpy.mockReturnValue({
name: '',
version: 'unknown',
@@ -284,15 +291,17 @@ describe('resourceTimingToSpanAttributes', () => {
const mockResourceTiming = createMockResourceTiming({
nextHopProtocol: '',
redirectStart: 0,
- fetchStart: 0,
+ redirectEnd: 0,
+ workerStart: 0,
+ fetchStart: 1000100, // fetchStart is not restricted by `Timing-Allow-Origin` header
domainLookupStart: 0,
domainLookupEnd: 0,
connectStart: 0,
- secureConnectionStart: 0,
connectEnd: 0,
+ secureConnectionStart: 0,
requestStart: 0,
responseStart: 0,
- responseEnd: 0,
+ responseEnd: 1000200, // responseEnd is not restricted by `Timing-Allow-Origin` header
});
const result = resourceTimingToSpanAttributes(mockResourceTiming);
@@ -300,18 +309,18 @@ describe('resourceTimingToSpanAttributes', () => {
expect(result).toEqual({
'network.protocol.version': 'unknown',
'network.protocol.name': '',
- 'http.request.redirect_start': 1000, // (1000000 + 0) / 1000
- 'http.request.redirect_end': 1000.02,
- 'http.request.worker_start': 1000,
- 'http.request.fetch_start': 1000,
- 'http.request.domain_lookup_start': 1000,
- 'http.request.domain_lookup_end': 1000,
- 'http.request.connect_start': 1000,
- 'http.request.secure_connection_start': 1000,
- 'http.request.connection_end': 1000,
- 'http.request.request_start': 1000,
- 'http.request.response_start': 1000,
- 'http.request.response_end': 1000,
+ 'http.request.redirect_start': 0,
+ 'http.request.redirect_end': 0,
+ 'http.request.worker_start': 0,
+ 'http.request.fetch_start': 2000.1,
+ 'http.request.domain_lookup_start': 0,
+ 'http.request.domain_lookup_end': 0,
+ 'http.request.connect_start': 0,
+ 'http.request.secure_connection_start': 0,
+ 'http.request.connection_end': 0,
+ 'http.request.request_start': 0,
+ 'http.request.response_start': 0,
+ 'http.request.response_end': 2000.2,
'http.request.time_to_first_byte': 0,
});
});
@@ -343,7 +352,7 @@ describe('resourceTimingToSpanAttributes', () => {
'network.protocol.name': 'http',
'http.request.redirect_start': 1000.005,
'http.request.redirect_end': 1000.02,
- 'http.request.worker_start': 1000,
+ 'http.request.worker_start': 1000.001,
'http.request.fetch_start': 1000.01,
'http.request.domain_lookup_start': 1000.015,
'http.request.domain_lookup_end': 1000.02,
@@ -470,7 +479,7 @@ describe('resourceTimingToSpanAttributes', () => {
});
describe('edge cases', () => {
- it('handles undefined timing values', () => {
+ it("doesn't include undefined timing values", () => {
browserPerformanceTimeOriginSpy.mockReturnValue(1000000);
extractNetworkProtocolSpy.mockReturnValue({
@@ -481,6 +490,7 @@ describe('resourceTimingToSpanAttributes', () => {
const mockResourceTiming = createMockResourceTiming({
nextHopProtocol: '',
redirectStart: undefined as any,
+ redirectEnd: undefined as any,
fetchStart: undefined as any,
workerStart: undefined as any,
domainLookupStart: undefined as any,
@@ -498,19 +508,6 @@ describe('resourceTimingToSpanAttributes', () => {
expect(result).toEqual({
'network.protocol.version': 'unknown',
'network.protocol.name': '',
- 'http.request.redirect_start': 1000, // (1000000 + 0) / 1000
- 'http.request.redirect_end': 1000.02,
- 'http.request.worker_start': 1000,
- 'http.request.fetch_start': 1000,
- 'http.request.domain_lookup_start': 1000,
- 'http.request.domain_lookup_end': 1000,
- 'http.request.connect_start': 1000,
- 'http.request.secure_connection_start': 1000,
- 'http.request.connection_end': 1000,
- 'http.request.request_start': 1000,
- 'http.request.response_start': 1000,
- 'http.request.response_end': 1000,
- 'http.request.time_to_first_byte': 0,
});
});
@@ -534,6 +531,7 @@ describe('resourceTimingToSpanAttributes', () => {
requestStart: 999999,
responseStart: 999999,
responseEnd: 999999,
+ workerStart: 999999,
});
const result = resourceTimingToSpanAttributes(mockResourceTiming);
@@ -543,7 +541,7 @@ describe('resourceTimingToSpanAttributes', () => {
'network.protocol.name': '',
'http.request.redirect_start': 1999.999, // (1000000 + 999999) / 1000
'http.request.redirect_end': 1000.02,
- 'http.request.worker_start': 1000,
+ 'http.request.worker_start': 1999.999,
'http.request.fetch_start': 1999.999,
'http.request.domain_lookup_start': 1999.999,
'http.request.domain_lookup_end': 1999.999,
diff --git a/packages/browser-utils/tsconfig.json b/packages/browser-utils/tsconfig.json
index 36891917c5cc..fd54f069790c 100644
--- a/packages/browser-utils/tsconfig.json
+++ b/packages/browser-utils/tsconfig.json
@@ -4,6 +4,6 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"]
+ "lib": ["DOM", "es2020"]
}
}
diff --git a/packages/browser/package.json b/packages/browser/package.json
index dea278d94fce..8521b2370858 100644
--- a/packages/browser/package.json
+++ b/packages/browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/browser",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for browsers",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser",
@@ -39,14 +39,14 @@
"access": "public"
},
"dependencies": {
- "@sentry-internal/browser-utils": "10.14.0",
- "@sentry-internal/feedback": "10.14.0",
- "@sentry-internal/replay": "10.14.0",
- "@sentry-internal/replay-canvas": "10.14.0",
- "@sentry/core": "10.14.0"
+ "@sentry-internal/browser-utils": "10.15.0",
+ "@sentry-internal/feedback": "10.15.0",
+ "@sentry-internal/replay": "10.15.0",
+ "@sentry-internal/replay-canvas": "10.15.0",
+ "@sentry/core": "10.15.0"
},
"devDependencies": {
- "@sentry-internal/integration-shims": "10.14.0",
+ "@sentry-internal/integration-shims": "10.15.0",
"fake-indexeddb": "^4.0.1"
},
"scripts": {
diff --git a/packages/browser/src/eventbuilder.ts b/packages/browser/src/eventbuilder.ts
index 67cb4d2578cd..cc0be3378b8d 100644
--- a/packages/browser/src/eventbuilder.ts
+++ b/packages/browser/src/eventbuilder.ts
@@ -309,7 +309,7 @@ export function eventFromUnknownInput(
// If it's a plain object or an instance of `Event` (the built-in JS kind, not this SDK's `Event` type), serialize
// it manually. This will allow us to group events based on top-level keys which is much better than creating a new
// group on any key/value change.
- const objectException = exception as Record;
+ const objectException = exception;
event = eventFromPlainObject(stackParser, objectException, syntheticException, isUnhandledRejection);
addExceptionMechanism(event, {
synthetic: true,
diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts
index fc805c82a4e5..7aa4b3ae778c 100644
--- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts
+++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts
@@ -22,6 +22,7 @@ export {
startBrowserTracingNavigationSpan,
startBrowserTracingPageLoadSpan,
} from './tracing/browserTracingIntegration';
+export { setActiveSpanInBrowser } from './tracing/setActiveSpan';
export { reportPageLoaded } from './tracing/reportPageLoaded';
diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts
index f77d2774c36e..3dc858d69cb5 100644
--- a/packages/browser/src/index.bundle.tracing.replay.ts
+++ b/packages/browser/src/index.bundle.tracing.replay.ts
@@ -22,8 +22,8 @@ export {
startBrowserTracingNavigationSpan,
startBrowserTracingPageLoadSpan,
} from './tracing/browserTracingIntegration';
-
export { reportPageLoaded } from './tracing/reportPageLoaded';
+export { setActiveSpanInBrowser } from './tracing/setActiveSpan';
export { feedbackIntegrationShim as feedbackAsyncIntegration, feedbackIntegrationShim as feedbackIntegration };
diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts
index c32e806f1de8..62259b92ce7e 100644
--- a/packages/browser/src/index.bundle.tracing.ts
+++ b/packages/browser/src/index.bundle.tracing.ts
@@ -22,6 +22,7 @@ export {
startBrowserTracingNavigationSpan,
startBrowserTracingPageLoadSpan,
} from './tracing/browserTracingIntegration';
+export { setActiveSpanInBrowser } from './tracing/setActiveSpan';
export { reportPageLoaded } from './tracing/reportPageLoaded';
diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts
index f2a3e7dc179c..5e9924fe6da5 100644
--- a/packages/browser/src/index.ts
+++ b/packages/browser/src/index.ts
@@ -40,6 +40,8 @@ export {
startBrowserTracingPageLoadSpan,
} from './tracing/browserTracingIntegration';
export { reportPageLoaded } from './tracing/reportPageLoaded';
+export { setActiveSpanInBrowser } from './tracing/setActiveSpan';
+
export type { RequestInstrumentationOptions } from './tracing/request';
export {
registerSpanErrorInstrumentation,
diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts
index 2aa29731a9b0..fa815db7c15e 100644
--- a/packages/browser/src/integrations/globalhandlers.ts
+++ b/packages/browser/src/integrations/globalhandlers.ts
@@ -86,7 +86,7 @@ function _installGlobalOnUnhandledRejectionHandler(client: Client): void {
return;
}
- const error = _getUnhandledRejectionError(e as unknown);
+ const error = _getUnhandledRejectionError(e);
const event = isPrimitive(error)
? _eventFromRejectionWithPrimitive(error)
diff --git a/packages/browser/src/tracing/setActiveSpan.ts b/packages/browser/src/tracing/setActiveSpan.ts
new file mode 100644
index 000000000000..5e3b537d4b6d
--- /dev/null
+++ b/packages/browser/src/tracing/setActiveSpan.ts
@@ -0,0 +1,64 @@
+import type { Span } from '@sentry/core';
+import { _INTERNAL_setSpanForScope, getActiveSpan, getCurrentScope } from '@sentry/core';
+
+/**
+ * Sets an inactive span active on the current scope.
+ *
+ * This is useful in browser applications, if you want to create a span that cannot be finished
+ * within its callback. Any spans started while the given span is active, will be children of the span.
+ *
+ * If there already was an active span on the scope prior to calling this function, it is replaced
+ * with the given span and restored after the span ended. Otherwise, the span will simply be
+ * removed, resulting in no active span on the scope.
+ *
+ * IMPORTANT: This function can ONLY be used in the browser! Calling this function in a server
+ * environment (for example in a server-side rendered component) will result in undefined behaviour
+ * and is not supported.
+ * You MUST call `span.end()` manually, otherwise the span will never be finished.
+ *
+ * @example
+ * ```js
+ * let checkoutSpan;
+ *
+ * on('checkoutStarted', () => {
+ * checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' });
+ * Sentry.setActiveSpanInBrowser(checkoutSpan);
+ * })
+ *
+ * // during this time, any spans started will be children of `checkoutSpan`:
+ * Sentry.startSpan({ name: 'checkout-step-1' }, () => {
+ * // ... `
+ * })
+ *
+ * on('checkoutCompleted', () => {
+ * checkoutSpan?.end();
+ * })
+ * ```
+ *
+ * @param span - the span to set active
+ */
+export function setActiveSpanInBrowser(span: Span): void {
+ const maybePreviousActiveSpan = getActiveSpan();
+
+ // If the span is already active, there's no need to double-patch or set it again.
+ // This also guards against users (for whatever reason) calling setActiveSpanInBrowser on SDK-started
+ // idle spans like pageload or navigation spans. These will already be handled correctly by the SDK.
+ // For nested situations, we have to double-patch to ensure we restore the correct previous span (see tests)
+ if (maybePreviousActiveSpan === span) {
+ return;
+ }
+
+ const scope = getCurrentScope();
+
+ // Putting a small patch onto the span.end method to ensure we
+ // remove the span from the scope when it ends.
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ span.end = new Proxy(span.end, {
+ apply(target, thisArg, args: Parameters) {
+ _INTERNAL_setSpanForScope(scope, maybePreviousActiveSpan);
+ return Reflect.apply(target, thisArg, args);
+ },
+ });
+
+ _INTERNAL_setSpanForScope(scope, span);
+}
diff --git a/packages/browser/test/integrations/webWorker.test.ts b/packages/browser/test/integrations/webWorker.test.ts
index eacd2b53344d..4dfea4983949 100644
--- a/packages/browser/test/integrations/webWorker.test.ts
+++ b/packages/browser/test/integrations/webWorker.test.ts
@@ -117,7 +117,7 @@ describe('webWorkerIntegration', () => {
// Extract the message handler from the addEventListener call
expect(mockWorker.addEventListener.mock.calls).toBeDefined();
- messageHandler = mockWorker.addEventListener.mock.calls![0]![1];
+ messageHandler = mockWorker.addEventListener.mock.calls[0]![1];
});
it('ignores non-Sentry messages', () => {
diff --git a/packages/browser/test/tracing/browserTracingIntegration.test.ts b/packages/browser/test/tracing/browserTracingIntegration.test.ts
index 7e573cae1866..e3f1060655c2 100644
--- a/packages/browser/test/tracing/browserTracingIntegration.test.ts
+++ b/packages/browser/test/tracing/browserTracingIntegration.test.ts
@@ -873,7 +873,7 @@ describe('browserTracingIntegration', () => {
const idleSpan = getActiveSpan()!;
expect(idleSpan).toBeDefined();
- const dynamicSamplingContext = getDynamicSamplingContextFromSpan(idleSpan!);
+ const dynamicSamplingContext = getDynamicSamplingContextFromSpan(idleSpan);
const propagationContext = getCurrentScope().getPropagationContext();
// Span is correct
@@ -1010,7 +1010,7 @@ describe('browserTracingIntegration', () => {
const idleSpan = getActiveSpan()!;
expect(idleSpan).toBeDefined();
- const dynamicSamplingContext = getDynamicSamplingContextFromSpan(idleSpan!);
+ const dynamicSamplingContext = getDynamicSamplingContextFromSpan(idleSpan);
const propagationContext = getCurrentScope().getPropagationContext();
// Span is correct
diff --git a/packages/browser/test/tracing/setActiveSpan.test.ts b/packages/browser/test/tracing/setActiveSpan.test.ts
new file mode 100644
index 000000000000..d3c7ea79cf67
--- /dev/null
+++ b/packages/browser/test/tracing/setActiveSpan.test.ts
@@ -0,0 +1,90 @@
+import { getActiveSpan, SentrySpan } from '@sentry/core';
+import { describe, expect, it } from 'vitest';
+import { setActiveSpanInBrowser } from '../../src';
+
+describe('setActiveSpanInBrowser', () => {
+ it('sets the passed span active the current scope', () => {
+ const span = new SentrySpan({ name: 'test' });
+ setActiveSpanInBrowser(span);
+ expect(getActiveSpan()).toBe(span);
+
+ span.end();
+ expect(getActiveSpan()).toBeUndefined();
+ });
+
+ it('handles multiple calls to setActiveSpanInBrowser', () => {
+ const span = new SentrySpan({ name: 'test' });
+ setActiveSpanInBrowser(span);
+ setActiveSpanInBrowser(span);
+ setActiveSpanInBrowser(span);
+ expect(getActiveSpan()).toBe(span);
+
+ span.end();
+ expect(getActiveSpan()).toBeUndefined();
+ });
+
+ it('handles changing active span while span is running', () => {
+ const span = new SentrySpan({ name: 'test' });
+ setActiveSpanInBrowser(span);
+
+ expect(getActiveSpan()).toBe(span);
+
+ const span2 = new SentrySpan({ name: 'test2' });
+ setActiveSpanInBrowser(span2);
+ expect(getActiveSpan()).toBe(span2);
+
+ span2.end();
+ expect(getActiveSpan()).toBe(span);
+
+ span.end();
+ expect(getActiveSpan()).toBeUndefined();
+ });
+
+ it('handles multiple span.end calls', () => {
+ const span = new SentrySpan({ name: 'test' });
+ setActiveSpanInBrowser(span);
+ setActiveSpanInBrowser(span);
+
+ expect(getActiveSpan()).toBe(span);
+
+ const span2 = new SentrySpan({ name: 'test2' });
+ setActiveSpanInBrowser(span2);
+ expect(getActiveSpan()).toBe(span2);
+
+ span2.end();
+ span2.end();
+ span2.end();
+ expect(getActiveSpan()).toBe(span);
+
+ span.end();
+ span.end();
+ expect(getActiveSpan()).toBeUndefined();
+ });
+
+ it('handles nested activation of the same span', () => {
+ const span1 = new SentrySpan({ name: 'test1', sampled: true });
+ const span2 = new SentrySpan({ name: 'test2', sampled: true });
+ expect(span1.isRecording()).toBe(true);
+ expect(span2.isRecording()).toBe(true);
+
+ setActiveSpanInBrowser(span1);
+ expect(getActiveSpan()).toBe(span1);
+
+ setActiveSpanInBrowser(span2);
+ expect(getActiveSpan()).toBe(span2);
+
+ setActiveSpanInBrowser(span1);
+ expect(getActiveSpan()).toBe(span1);
+
+ span2.end();
+ expect(getActiveSpan()).toBe(span1);
+ expect(span2.isRecording()).toBe(false);
+ expect(span1.isRecording()).toBe(true);
+
+ span1.end();
+ expect(getActiveSpan()).toBeUndefined();
+
+ expect(span1.isRecording()).toBe(false);
+ expect(span2.isRecording()).toBe(false);
+ });
+});
diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json
index 1ac927bde013..b80e9ddbfaa5 100644
--- a/packages/browser/tsconfig.json
+++ b/packages/browser/tsconfig.json
@@ -4,6 +4,6 @@
"include": ["src/**/*", "test/loader.js"],
"compilerOptions": {
- "lib": ["DOM", "ES2018", "WebWorker"]
+ "lib": ["DOM", "es2020", "WebWorker"]
}
}
diff --git a/packages/bun/package.json b/packages/bun/package.json
index b254ed09fe94..ffac9e99ef2f 100644
--- a/packages/bun/package.json
+++ b/packages/bun/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/bun",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for bun",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bun",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0"
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0"
},
"devDependencies": {
"bun-types": "^1.2.9"
diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts
index b1c4854e5026..68a1e2b6d6ff 100644
--- a/packages/bun/src/index.ts
+++ b/packages/bun/src/index.ts
@@ -130,6 +130,8 @@ export {
prismaIntegration,
hapiIntegration,
setupHapiErrorHandler,
+ honoIntegration,
+ setupHonoErrorHandler,
spotlightIntegration,
initOpenTelemetry,
spanToJSON,
diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json
index 50727b5e9937..eebf9ef1ef1f 100644
--- a/packages/cloudflare/package.json
+++ b/packages/cloudflare/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/cloudflare",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Cloudflare Workers and Pages",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/cloudflare",
@@ -50,7 +50,7 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"peerDependencies": {
"@cloudflare/workers-types": "^4.x"
@@ -61,7 +61,7 @@
}
},
"devDependencies": {
- "@cloudflare/workers-types": "4.20250620.0",
+ "@cloudflare/workers-types": "4.20250922.0",
"@types/node": "^18.19.1",
"wrangler": "4.22.0"
},
diff --git a/packages/cloudflare/src/handler.ts b/packages/cloudflare/src/handler.ts
index a6e5983902c6..969cb6be72ee 100644
--- a/packages/cloudflare/src/handler.ts
+++ b/packages/cloudflare/src/handler.ts
@@ -10,6 +10,7 @@ import {
import { setAsyncLocalStorageAsyncContextStrategy } from './async';
import type { CloudflareOptions } from './client';
import { isInstrumented, markAsInstrumented } from './instrument';
+import { getHonoIntegration } from './integrations/hono';
import { getFinalOptions } from './options';
import { wrapRequestHandler } from './request';
import { addCloudResourceContext } from './scope-utils';
@@ -48,7 +49,7 @@ export function withSentry | undefined {
+ return getClient()?.getIntegrationByName(INTEGRATION_NAME);
+}
+
+function isHonoError(err: unknown): err is HonoError {
+ if (err instanceof Error) {
+ return true;
+ }
+ return typeof err === 'object' && err !== null && 'status' in (err as Record);
+}
+
+const _honoIntegration = ((options: Partial = {}) => {
+ return {
+ name: INTEGRATION_NAME,
+ handleHonoException(err: HonoError): void {
+ const shouldHandleError = options.shouldHandleError || defaultShouldHandleError;
+
+ if (!isHonoError(err)) {
+ DEBUG_BUILD && debug.log("[Hono] Won't capture exception in `onError` because it's not a Hono error.", err);
+ return;
+ }
+
+ if (shouldHandleError(err)) {
+ captureException(err, { mechanism: { handled: false, type: 'auto.faas.hono.error_handler' } });
+ } else {
+ DEBUG_BUILD && debug.log('[Hono] Not capturing exception because `shouldHandleError` returned `false`.', err);
+ }
+ },
+ };
+}) satisfies IntegrationFn;
+
+/**
+ * Automatically captures exceptions caught with the `onError` handler in Hono.
+ *
+ * The integration is enabled by default.
+ *
+ * @example
+ * integrations: [
+ * honoIntegration({
+ * shouldHandleError: (err) => true; // always capture exceptions in onError
+ * })
+ * ]
+ */
+export const honoIntegration = defineIntegration(_honoIntegration);
+
+/**
+ * Default function to determine if an error should be sent to Sentry
+ *
+ * 3xx and 4xx errors are not sent by default.
+ */
+function defaultShouldHandleError(error: HonoError): boolean {
+ const statusCode = error?.status;
+ // 3xx and 4xx errors are not sent by default.
+ return statusCode ? statusCode >= 500 || statusCode <= 299 : true;
+}
diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts
index 9d4fb8d749ae..238cc13253a5 100644
--- a/packages/cloudflare/src/sdk.ts
+++ b/packages/cloudflare/src/sdk.ts
@@ -14,6 +14,7 @@ import type { CloudflareClientOptions, CloudflareOptions } from './client';
import { CloudflareClient } from './client';
import { makeFlushLock } from './flush';
import { fetchIntegration } from './integrations/fetch';
+import { honoIntegration } from './integrations/hono';
import { setupOpenTelemetryTracer } from './opentelemetry/tracer';
import { makeCloudflareTransport } from './transport';
import { defaultStackParser } from './vendor/stacktrace';
@@ -31,6 +32,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[
functionToStringIntegration(),
linkedErrorsIntegration(),
fetchIntegration(),
+ honoIntegration(),
// TODO(v11): the `include` object should be defined directly in the integration based on `sendDefaultPii`
requestDataIntegration(sendDefaultPii ? undefined : { include: { cookies: false } }),
consoleIntegration(),
diff --git a/packages/cloudflare/src/workflows.ts b/packages/cloudflare/src/workflows.ts
index 336df2abe301..16327ea71ccf 100644
--- a/packages/cloudflare/src/workflows.ts
+++ b/packages/cloudflare/src/workflows.ts
@@ -192,5 +192,5 @@ export function instrumentWorkflowWithSentry<
},
});
},
- }) as C;
+ });
}
diff --git a/packages/cloudflare/test/durableobject.test.ts b/packages/cloudflare/test/durableobject.test.ts
index ce794dc7fb69..4d9e2a20fe97 100644
--- a/packages/cloudflare/test/durableobject.test.ts
+++ b/packages/cloudflare/test/durableobject.test.ts
@@ -23,10 +23,7 @@ describe('instrumentDurableObjectWithSentry', () => {
return 'sync-result';
}
};
- const obj = Reflect.construct(
- instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any),
- [],
- ) as any;
+ const obj = Reflect.construct(instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any), []);
expect(obj.method).toBe(obj.method);
const result = obj.method();
@@ -40,10 +37,7 @@ describe('instrumentDurableObjectWithSentry', () => {
return 'async-result';
}
};
- const obj = Reflect.construct(
- instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any),
- [],
- ) as any;
+ const obj = Reflect.construct(instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any), []);
expect(obj.asyncMethod).toBe(obj.asyncMethod);
const result = obj.asyncMethod();
@@ -74,13 +68,13 @@ describe('instrumentDurableObjectWithSentry', () => {
const instance1 = Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), [
mockContext,
mockEnv,
- ]) as any;
+ ]);
instance1.method();
const instance2 = Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), [
mockContext,
mockEnv,
- ]) as any;
+ ]);
instance2.method();
expect(initCore).nthCalledWith(1, expect.any(Function), expect.objectContaining({ orgId: 1 }));
@@ -156,7 +150,7 @@ describe('instrumentDurableObjectWithSentry', () => {
};
const options = vi.fn().mockReturnValue({});
const instrumented = instrumentDurableObjectWithSentry(options, testClass as any);
- const obj = Reflect.construct(instrumented, []) as any;
+ const obj = Reflect.construct(instrumented, []);
expect(isInstrumented(obj.prototypeMethod)).toBeFalsy();
});
@@ -169,7 +163,7 @@ describe('instrumentDurableObjectWithSentry', () => {
};
const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: false });
const instrumented = instrumentDurableObjectWithSentry(options, testClass as any);
- const obj = Reflect.construct(instrumented, []) as any;
+ const obj = Reflect.construct(instrumented, []);
expect(isInstrumented(obj.prototypeMethod)).toBeFalsy();
});
@@ -185,7 +179,7 @@ describe('instrumentDurableObjectWithSentry', () => {
};
const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: true });
const instrumented = instrumentDurableObjectWithSentry(options, testClass as any);
- const obj = Reflect.construct(instrumented, []) as any;
+ const obj = Reflect.construct(instrumented, []);
expect(isInstrumented(obj.methodOne)).toBeTruthy();
expect(isInstrumented(obj.methodTwo)).toBeTruthy();
@@ -205,7 +199,7 @@ describe('instrumentDurableObjectWithSentry', () => {
};
const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: ['methodOne', 'methodThree'] });
const instrumented = instrumentDurableObjectWithSentry(options, testClass as any);
- const obj = Reflect.construct(instrumented, []) as any;
+ const obj = Reflect.construct(instrumented, []);
expect(isInstrumented(obj.methodOne)).toBeTruthy();
expect(isInstrumented(obj.methodTwo)).toBeFalsy();
@@ -224,7 +218,7 @@ describe('instrumentDurableObjectWithSentry', () => {
};
const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: false });
const instrumented = instrumentDurableObjectWithSentry(options, testClass as any);
- const obj = Reflect.construct(instrumented, []) as any;
+ const obj = Reflect.construct(instrumented, []);
// Instance methods should still be instrumented
expect(isInstrumented(obj.propertyFunction)).toBeTruthy();
diff --git a/packages/cloudflare/test/handler.test.ts b/packages/cloudflare/test/handler.test.ts
index 97e93199ea31..7768689ffc48 100644
--- a/packages/cloudflare/test/handler.test.ts
+++ b/packages/cloudflare/test/handler.test.ts
@@ -14,6 +14,7 @@ import { beforeEach, describe, expect, onTestFinished, test, vi } from 'vitest';
import { CloudflareClient } from '../src/client';
import { withSentry } from '../src/handler';
import { markAsInstrumented } from '../src/instrument';
+import * as HonoIntegration from '../src/integrations/hono';
// Custom type for hono-like apps (cloudflare handlers) that include errorHandler and onError
type HonoLikeApp = ExportedHandler<
@@ -1081,10 +1082,12 @@ describe('withSentry', () => {
});
describe('hono errorHandler', () => {
- test('captures errors handled by the errorHandler', async () => {
- const captureExceptionSpy = vi.spyOn(SentryCore, 'captureException');
+ test('calls Hono Integration to handle error captured by the errorHandler', async () => {
const error = new Error('test hono error');
+ const handleHonoException = vi.fn();
+ vi.spyOn(HonoIntegration, 'getHonoIntegration').mockReturnValue({ handleHonoException } as any);
+
const honoApp = {
fetch(_request, _env, _context) {
return new Response('test');
@@ -1100,10 +1103,8 @@ describe('withSentry', () => {
// simulates hono's error handling
const errorHandlerResponse = honoApp.errorHandler?.(error);
- expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
- expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, {
- mechanism: { handled: false, type: 'auto.faas.cloudflare.error_handler' },
- });
+ expect(handleHonoException).toHaveBeenCalledTimes(1);
+ expect(handleHonoException).toHaveBeenLastCalledWith(error);
expect(errorHandlerResponse?.status).toBe(500);
});
diff --git a/packages/cloudflare/test/integrations/fetch.test.ts b/packages/cloudflare/test/integrations/fetch.test.ts
index 03cdbb9bf5a5..11dbaa18916b 100644
--- a/packages/cloudflare/test/integrations/fetch.test.ts
+++ b/packages/cloudflare/test/integrations/fetch.test.ts
@@ -7,7 +7,7 @@ import { fetchIntegration } from '../../src/integrations/fetch';
class FakeClient extends CloudflareClient {
public getIntegrationByName(name: string): T | undefined {
- return name === 'Fetch' ? (fetchIntegration() as Integration as T) : undefined;
+ return name === 'Fetch' ? (fetchIntegration() as T) : undefined;
}
}
diff --git a/packages/cloudflare/test/integrations/hono.test.ts b/packages/cloudflare/test/integrations/hono.test.ts
new file mode 100644
index 000000000000..f1b273b3ed2b
--- /dev/null
+++ b/packages/cloudflare/test/integrations/hono.test.ts
@@ -0,0 +1,94 @@
+import * as sentryCore from '@sentry/core';
+import { type Client, createStackParser } from '@sentry/core';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { CloudflareClient } from '../../src/client';
+import { honoIntegration } from '../../src/integrations/hono';
+
+class FakeClient extends CloudflareClient {
+ public getIntegrationByName(name: string) {
+ return name === 'Hono' ? (honoIntegration() as any) : undefined;
+ }
+}
+
+type MockHonoIntegrationType = { handleHonoException: (err: Error) => void };
+
+describe('Hono integration', () => {
+ let client: FakeClient;
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ client = new FakeClient({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ integrations: [],
+ transport: () => ({ send: () => Promise.resolve({}), flush: () => Promise.resolve(true) }),
+ stackParser: createStackParser(),
+ });
+
+ vi.spyOn(sentryCore, 'getClient').mockImplementation(() => client as Client);
+ });
+
+ it('captures in errorHandler when onError exists', () => {
+ const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
+ const integration = honoIntegration();
+ integration.setupOnce?.();
+
+ const error = new Error('hono boom');
+ // simulate withSentry wrapping of errorHandler calling back into integration
+ (integration as unknown as MockHonoIntegrationType).handleHonoException(error);
+
+ expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
+ expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, {
+ mechanism: { handled: false, type: 'auto.faas.hono.error_handler' },
+ });
+ });
+
+ it('does not capture for 4xx status', () => {
+ const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
+ const integration = honoIntegration();
+ integration.setupOnce?.();
+
+ (integration as unknown as MockHonoIntegrationType).handleHonoException(
+ Object.assign(new Error('client err'), { status: 404 }),
+ );
+ expect(captureExceptionSpy).not.toHaveBeenCalled();
+ });
+
+ it('does not capture for 3xx status', () => {
+ const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
+ const integration = honoIntegration();
+ integration.setupOnce?.();
+
+ (integration as unknown as MockHonoIntegrationType).handleHonoException(
+ Object.assign(new Error('redirect'), { status: 302 }),
+ );
+ expect(captureExceptionSpy).not.toHaveBeenCalled();
+ });
+
+ it('captures for 5xx status', () => {
+ const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
+ const integration = honoIntegration();
+ integration.setupOnce?.();
+
+ const err = Object.assign(new Error('server err'), { status: 500 });
+ (integration as unknown as MockHonoIntegrationType).handleHonoException(err);
+ expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
+ });
+
+ it('captures if no status is present on Error', () => {
+ const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
+ const integration = honoIntegration();
+ integration.setupOnce?.();
+
+ (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('no status'));
+ expect(captureExceptionSpy).toHaveBeenCalledTimes(1);
+ });
+
+ it('supports custom shouldHandleError option', () => {
+ const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException');
+ const integration = honoIntegration({ shouldHandleError: () => false });
+ integration.setupOnce?.();
+
+ (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('blocked'));
+ expect(captureExceptionSpy).not.toHaveBeenCalled();
+ });
+});
diff --git a/packages/cloudflare/test/workflow.test.ts b/packages/cloudflare/test/workflow.test.ts
index 2ca2ccd28e46..fa922d7233e0 100644
--- a/packages/cloudflare/test/workflow.test.ts
+++ b/packages/cloudflare/test/workflow.test.ts
@@ -449,7 +449,7 @@ describe.skipIf(NODE_MAJOR_VERSION < 20)('workflows', () => {
expect(mockContext.waitUntil).toHaveBeenCalledTimes(2);
expect(mockTransport.send).toHaveBeenCalledTimes(1);
- const sendArg = mockTransport.send.mock.calls[0]![0] as any;
+ const sendArg = mockTransport.send.mock.calls[0]![0];
const items = sendArg[1] as any[];
const rootSpanItem = items.find(i => i[0].type === 'transaction');
expect(rootSpanItem).toBeDefined();
diff --git a/packages/cloudflare/tsconfig.test.json b/packages/cloudflare/tsconfig.test.json
index 42d9d0df227e..00cada2d8bcf 100644
--- a/packages/cloudflare/tsconfig.test.json
+++ b/packages/cloudflare/tsconfig.test.json
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig.json",
- "include": ["test/**/*"],
+ "include": ["test/**/*", "vite.config.ts"],
"compilerOptions": {
// other package-specific, test-specific options
diff --git a/packages/cloudflare/vite.config.ts b/packages/cloudflare/vite.config.ts
new file mode 100644
index 000000000000..b2150cd225a4
--- /dev/null
+++ b/packages/cloudflare/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from 'vitest/config';
+import baseConfig from '../../vite/vite.config';
+
+export default defineConfig({
+ ...baseConfig,
+});
diff --git a/packages/core/package.json b/packages/core/package.json
index 6ae5b8797175..9c11c0774988 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/core",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Base implementation for all Sentry JavaScript SDKs",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/core",
diff --git a/packages/core/src/asyncContext/stackStrategy.ts b/packages/core/src/asyncContext/stackStrategy.ts
index 845605be731d..87dc534fc636 100644
--- a/packages/core/src/asyncContext/stackStrategy.ts
+++ b/packages/core/src/asyncContext/stackStrategy.ts
@@ -135,7 +135,7 @@ function withScope(callback: (scope: Scope) => T): T {
}
function withSetScope(scope: Scope, callback: (scope: Scope) => T): T {
- const stack = getAsyncContextStack() as AsyncContextStack;
+ const stack = getAsyncContextStack();
return stack.withScope(() => {
stack.getStackTop().scope = scope;
return callback(scope);
diff --git a/packages/core/src/breadcrumbs.ts b/packages/core/src/breadcrumbs.ts
index 99deafdd8d2d..d6e15fcd0b02 100644
--- a/packages/core/src/breadcrumbs.ts
+++ b/packages/core/src/breadcrumbs.ts
@@ -28,7 +28,7 @@ export function addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): vo
const timestamp = dateTimestampInSeconds();
const mergedBreadcrumb = { timestamp, ...breadcrumb };
const finalBreadcrumb = beforeBreadcrumb
- ? (consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint)) as Breadcrumb | null)
+ ? consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint))
: mergedBreadcrumb;
if (finalBreadcrumb === null) return;
diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts
index 501c29b4ea10..9ab62ec732da 100644
--- a/packages/core/src/fetch.ts
+++ b/packages/core/src/fetch.ts
@@ -248,7 +248,7 @@ export function _addTracingHeadersToFetchRequest(
baggage: string | undefined;
traceparent?: string;
} = {
- ...(originalHeaders as Exclude),
+ ...originalHeaders,
'sentry-trace': (existingSentryTraceHeader as string | undefined) ?? sentryTrace,
baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined,
};
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 8a5566948f6e..e0daefd54d76 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -81,6 +81,7 @@ export {
spanTimeInputToSeconds,
updateSpanName,
} from './utils/spanUtils';
+export { _setSpanForScope as _INTERNAL_setSpanForScope } from './utils/spanOnScope';
export { parseSampleRate } from './utils/parseSampleRate';
export { applySdkMetadata } from './utils/sdkMetadata';
export { getTraceData } from './utils/traceData';
diff --git a/packages/core/src/integrations/mcp-server/index.ts b/packages/core/src/integrations/mcp-server/index.ts
index 1e16eaf202f3..a1eb8815805a 100644
--- a/packages/core/src/integrations/mcp-server/index.ts
+++ b/packages/core/src/integrations/mcp-server/index.ts
@@ -64,5 +64,5 @@ export function wrapMcpServerWithSentry(mcpServerInstance: S):
wrapAllMCPHandlers(serverInstance);
wrappedMcpServerInstances.add(mcpServerInstance);
- return mcpServerInstance as S;
+ return mcpServerInstance;
}
diff --git a/packages/core/src/integrations/mcp-server/methodConfig.ts b/packages/core/src/integrations/mcp-server/methodConfig.ts
index a652cd8cc41a..b57c0f254b63 100644
--- a/packages/core/src/integrations/mcp-server/methodConfig.ts
+++ b/packages/core/src/integrations/mcp-server/methodConfig.ts
@@ -56,7 +56,7 @@ export function extractTargetInfo(
target?: string;
attributes: Record;
} {
- const config = METHOD_CONFIGS[method as keyof typeof METHOD_CONFIGS];
+ const config = METHOD_CONFIGS[method];
if (!config) {
return { attributes: {} };
}
@@ -80,7 +80,7 @@ export function extractTargetInfo(
*/
export function getRequestArguments(method: string, params: Record): Record {
const args: Record = {};
- const config = METHOD_CONFIGS[method as keyof typeof METHOD_CONFIGS];
+ const config = METHOD_CONFIGS[method];
if (!config) {
return args;
diff --git a/packages/core/src/integrations/mcp-server/spans.ts b/packages/core/src/integrations/mcp-server/spans.ts
index 9a527046b6f2..fdd4c107ee30 100644
--- a/packages/core/src/integrations/mcp-server/spans.ts
+++ b/packages/core/src/integrations/mcp-server/spans.ts
@@ -78,7 +78,7 @@ function buildSentryAttributes(type: McpSpanConfig['type']): Record | undefined;
+ const params = message.params;
// Determine span name based on type and OTEL conventions
let spanName: string;
@@ -172,7 +172,7 @@ export function buildMcpServerSpanConfig(
attributes: Record;
} {
const { method } = jsonRpcMessage;
- const params = jsonRpcMessage.params as Record | undefined;
+ const params = jsonRpcMessage.params;
const targetInfo = extractTargetInfo(method, params || {});
const spanName = createSpanName(method, targetInfo.target);
diff --git a/packages/core/src/integrations/supabase.ts b/packages/core/src/integrations/supabase.ts
index 39dedadc9e04..61005fdad805 100644
--- a/packages/core/src/integrations/supabase.ts
+++ b/packages/core/src/integrations/supabase.ts
@@ -302,12 +302,12 @@ function instrumentSupabaseAuthClient(supabaseClientInstance: SupabaseClientInst
}
function instrumentSupabaseClientConstructor(SupabaseClient: unknown): void {
- if (isInstrumented((SupabaseClient as unknown as SupabaseClientConstructor).prototype.from)) {
+ if (isInstrumented((SupabaseClient as SupabaseClientConstructor).prototype.from)) {
return;
}
- (SupabaseClient as unknown as SupabaseClientConstructor).prototype.from = new Proxy(
- (SupabaseClient as unknown as SupabaseClientConstructor).prototype.from,
+ (SupabaseClient as SupabaseClientConstructor).prototype.from = new Proxy(
+ (SupabaseClient as SupabaseClientConstructor).prototype.from,
{
apply(target, thisArg, argumentsList) {
const rv = Reflect.apply(target, thisArg, argumentsList);
@@ -320,7 +320,7 @@ function instrumentSupabaseClientConstructor(SupabaseClient: unknown): void {
},
);
- markAsInstrumented((SupabaseClient as unknown as SupabaseClientConstructor).prototype.from);
+ markAsInstrumented((SupabaseClient as SupabaseClientConstructor).prototype.from);
}
function instrumentPostgRESTFilterBuilder(PostgRESTFilterBuilder: PostgRESTFilterBuilder['constructor']): void {
diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts
index af1a1f2b8ffc..9bd98b9741c6 100644
--- a/packages/core/src/tracing/sentrySpan.ts
+++ b/packages/core/src/tracing/sentrySpan.ts
@@ -24,7 +24,6 @@ import type {
} from '../types-hoist/span';
import type { SpanStatus } from '../types-hoist/spanStatus';
import type { TimedEvent } from '../types-hoist/timedEvent';
-import type { TransactionSource } from '../types-hoist/transaction';
import { debug } from '../utils/debug-logger';
import { generateSpanId, generateTraceId } from '../utils/propagationContext';
import {
@@ -347,7 +346,7 @@ export class SentrySpan implements Span {
const spans = finishedSpans.map(span => spanToJSON(span)).filter(isFullFinishedSpan);
- const source = this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] as TransactionSource | undefined;
+ const source = this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
// remove internal root span attributes we don't need to send.
/* eslint-disable @typescript-eslint/no-dynamic-delete */
diff --git a/packages/core/src/utils/anthropic-ai/index.ts b/packages/core/src/utils/anthropic-ai/index.ts
index f24707c4cc92..8cc44c188671 100644
--- a/packages/core/src/utils/anthropic-ai/index.ts
+++ b/packages/core/src/utils/anthropic-ai/index.ts
@@ -302,7 +302,7 @@ function createDeepProxy(target: T, currentPath = '', options:
}
if (value && typeof value === 'object') {
- return createDeepProxy(value as object, methodPath, options);
+ return createDeepProxy(value, methodPath, options);
}
return value;
diff --git a/packages/core/src/utils/debug-logger.ts b/packages/core/src/utils/debug-logger.ts
index 6fc9c7a8c865..bbc524729674 100644
--- a/packages/core/src/utils/debug-logger.ts
+++ b/packages/core/src/utils/debug-logger.ts
@@ -47,7 +47,7 @@ export function consoleSandbox(callback: () => T): T {
return callback();
}
- const console = GLOBAL_OBJ.console as Console;
+ const console = GLOBAL_OBJ.console;
const wrappedFuncs: Partial void>> = {};
const wrappedLevels = Object.keys(originalConsoleMethods) as ConsoleLevel[];
diff --git a/packages/core/src/utils/eventbuilder.ts b/packages/core/src/utils/eventbuilder.ts
index 24889187712f..203d95ccc3a8 100644
--- a/packages/core/src/utils/eventbuilder.ts
+++ b/packages/core/src/utils/eventbuilder.ts
@@ -102,7 +102,7 @@ function getException(
if (isPlainObject(exception)) {
const normalizeDepth = client?.getOptions().normalizeDepth;
- const extras = { ['__serialized__']: normalizeToSize(exception as Record, normalizeDepth) };
+ const extras = { ['__serialized__']: normalizeToSize(exception, normalizeDepth) };
const errorFromProp = getErrorPropertyFromObject(exception);
if (errorFromProp) {
diff --git a/packages/core/src/utils/featureFlags.ts b/packages/core/src/utils/featureFlags.ts
index 3e7be22704b6..4fa3cdc5ac8d 100644
--- a/packages/core/src/utils/featureFlags.ts
+++ b/packages/core/src/utils/featureFlags.ts
@@ -63,7 +63,7 @@ export function _INTERNAL_insertFlagToScope(
if (!scopeContexts.flags) {
scopeContexts.flags = { values: [] };
}
- const flags = scopeContexts.flags.values as FeatureFlag[];
+ const flags = scopeContexts.flags.values;
_INTERNAL_insertToFlagBuffer(flags, name, value, maxSize);
}
diff --git a/packages/core/src/utils/google-genai/index.ts b/packages/core/src/utils/google-genai/index.ts
index 13079cb34b49..58d7e2e6b5e6 100644
--- a/packages/core/src/utils/google-genai/index.ts
+++ b/packages/core/src/utils/google-genai/index.ts
@@ -278,7 +278,7 @@ function createDeepProxy(target: T, currentPath = '', options:
return value;
},
- }) as T;
+ });
}
/**
diff --git a/packages/core/src/utils/openai/index.ts b/packages/core/src/utils/openai/index.ts
index d296df840112..4ecfad625062 100644
--- a/packages/core/src/utils/openai/index.ts
+++ b/packages/core/src/utils/openai/index.ts
@@ -201,7 +201,7 @@ function addRequestAttributes(span: Span, params: Record): void
function getOptionsFromIntegration(): OpenAiOptions {
const scope = getCurrentScope();
const client = scope.getClient();
- const integration = client?.getIntegrationByName(OPENAI_INTEGRATION_NAME) as OpenAiIntegration | undefined;
+ const integration = client?.getIntegrationByName(OPENAI_INTEGRATION_NAME);
const shouldRecordInputsAndOutputs = integration ? Boolean(client?.getOptions().sendDefaultPii) : false;
return {
@@ -324,7 +324,7 @@ function createDeepProxy(target: T, currentPath = '', options?
}
if (value && typeof value === 'object') {
- return createDeepProxy(value as object, methodPath, options);
+ return createDeepProxy(value, methodPath, options);
}
return value;
diff --git a/packages/core/test/lib/integrations/captureconsole.test.ts b/packages/core/test/lib/integrations/captureconsole.test.ts
index 8da7c6570d74..2ca059be4f86 100644
--- a/packages/core/test/lib/integrations/captureconsole.test.ts
+++ b/packages/core/test/lib/integrations/captureconsole.test.ts
@@ -134,7 +134,7 @@ describe('CaptureConsole setup', () => {
expect(mockScope.addEventProcessor).toHaveBeenCalledTimes(1);
- const addedEventProcessor = (mockScope.addEventProcessor as Mock).mock.calls[0]?.[0];
+ const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0]?.[0];
const someEvent: Event = {};
addedEventProcessor(someEvent);
@@ -315,7 +315,7 @@ describe('CaptureConsole setup', () => {
const someError = new Error('some error');
GLOBAL_OBJ.console.error(someError);
- const addedEventProcessor = (mockScope.addEventProcessor as Mock).mock.calls[0]?.[0];
+ const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0]?.[0];
const someEvent: Event = {
exception: {
values: [{}],
@@ -339,7 +339,7 @@ describe('CaptureConsole setup', () => {
const someError = new Error('some error');
GLOBAL_OBJ.console.error(someError);
- const addedEventProcessor = (mockScope.addEventProcessor as Mock).mock.calls[0]?.[0];
+ const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0]?.[0];
const someEvent: Event = {
exception: {
values: [{}],
@@ -363,7 +363,7 @@ describe('CaptureConsole setup', () => {
const someError = new Error('some error');
GLOBAL_OBJ.console.error(someError);
- const addedEventProcessor = (mockScope.addEventProcessor as Mock).mock.calls[0]?.[0];
+ const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0]?.[0];
const someEvent: Event = {
exception: {
values: [{}],
diff --git a/packages/core/test/lib/tracing/idleSpan.test.ts b/packages/core/test/lib/tracing/idleSpan.test.ts
index 677428c941cd..3e930872945d 100644
--- a/packages/core/test/lib/tracing/idleSpan.test.ts
+++ b/packages/core/test/lib/tracing/idleSpan.test.ts
@@ -47,7 +47,7 @@ describe('startIdleSpan', () => {
expect(getActiveSpan()).toBe(idleSpan);
- idleSpan!.end();
+ idleSpan.end();
vi.runAllTimers();
expect(getActiveSpan()).toBe(undefined);
@@ -142,7 +142,7 @@ describe('startIdleSpan', () => {
vi.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout + 1);
vi.runOnlyPendingTimers();
- expect(spanToJSON(idleSpan!).data).toEqual(
+ expect(spanToJSON(idleSpan).data).toEqual(
expect.objectContaining({
foo: 'bar',
}),
@@ -693,7 +693,7 @@ describe('startIdleSpan', () => {
vi.runAllTimers();
- expect(spanToJSON(idleSpan!).timestamp).toBe(1100);
+ expect(spanToJSON(idleSpan).timestamp).toBe(1100);
});
it('trims end to final timeout', () => {
@@ -713,7 +713,7 @@ describe('startIdleSpan', () => {
vi.runAllTimers();
- expect(spanToJSON(idleSpan!).timestamp).toBe(1030);
+ expect(spanToJSON(idleSpan).timestamp).toBe(1030);
});
it('keeps lower span endTime than highest child span end', () => {
@@ -733,8 +733,8 @@ describe('startIdleSpan', () => {
vi.runAllTimers();
- expect(spanToJSON(idleSpan!).timestamp).toBeLessThan(999_999_999);
- expect(spanToJSON(idleSpan!).timestamp).toBeGreaterThan(1060);
+ expect(spanToJSON(idleSpan).timestamp).toBeLessThan(999_999_999);
+ expect(spanToJSON(idleSpan).timestamp).toBeGreaterThan(1060);
});
});
});
diff --git a/packages/core/tsconfig.test.json b/packages/core/tsconfig.test.json
index 0db9ad3bf16c..5a80d11f7055 100644
--- a/packages/core/tsconfig.test.json
+++ b/packages/core/tsconfig.test.json
@@ -4,7 +4,7 @@
"include": ["test/**/*", "vite.config.ts"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
"module": "ESNext", // support dynamic import()
// should include all types from `./tsconfig.json` plus types for all test frameworks used
"types": ["node"]
diff --git a/packages/deno/package.json b/packages/deno/package.json
index 3ea2d201df79..0ba4798f1b06 100644
--- a/packages/deno/package.json
+++ b/packages/deno/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/deno",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Deno",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/deno",
@@ -25,7 +25,7 @@
],
"dependencies": {
"@opentelemetry/api": "^1.9.0",
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"scripts": {
"deno-types": "node ./scripts/download-deno-types.mjs",
diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts
index 4e7e599fb4d3..6a2bc6cd95aa 100644
--- a/packages/deno/src/integrations/normalizepaths.ts
+++ b/packages/deno/src/integrations/normalizepaths.ts
@@ -18,7 +18,7 @@ function appRootFromErrorStack(error: Error): string | undefined {
.replace(/\\/g, '/') // replace all `\` instances with `/`
.split('/')
.filter(seg => seg !== ''), // remove empty segments
- ) as string[][];
+ );
const firstPath = paths[0];
diff --git a/packages/ember/package.json b/packages/ember/package.json
index 1a41eefe4992..9ccd4e089364 100644
--- a/packages/ember/package.json
+++ b/packages/ember/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/ember",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Ember.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember",
@@ -32,8 +32,8 @@
"dependencies": {
"@babel/core": "^7.27.7",
"@embroider/macros": "^1.16.0",
- "@sentry/browser": "10.14.0",
- "@sentry/core": "10.14.0",
+ "@sentry/browser": "10.15.0",
+ "@sentry/core": "10.15.0",
"ember-auto-import": "^2.7.2",
"ember-cli-babel": "^8.2.0",
"ember-cli-htmlbars": "^6.1.1",
diff --git a/packages/ember/tests/acceptance/sentry-replay-test.ts b/packages/ember/tests/acceptance/sentry-replay-test.ts
index 87b8d849b799..ef9d318b2c6a 100644
--- a/packages/ember/tests/acceptance/sentry-replay-test.ts
+++ b/packages/ember/tests/acceptance/sentry-replay-test.ts
@@ -16,7 +16,7 @@ module('Acceptance | Sentry Session Replay', function (hooks) {
Sentry.getClient()?.getIntegrationByName>('Replay');
assert.ok(integration);
- const replay = integration!['_replay'] as ReturnType['_replay'];
+ const replay = integration!['_replay'];
assert.true(replay.isEnabled());
assert.false(replay.isPaused());
diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json
index f4e0e3a95b91..fc45bc2cf6a8 100644
--- a/packages/eslint-config-sdk/package.json
+++ b/packages/eslint-config-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/eslint-config-sdk",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK eslint config",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-config-sdk",
@@ -22,8 +22,8 @@
"access": "public"
},
"dependencies": {
- "@sentry-internal/eslint-plugin-sdk": "10.14.0",
- "@sentry-internal/typescript": "10.14.0",
+ "@sentry-internal/eslint-plugin-sdk": "10.15.0",
+ "@sentry-internal/typescript": "10.15.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"eslint-config-prettier": "^6.11.0",
diff --git a/packages/eslint-config-sdk/src/base.js b/packages/eslint-config-sdk/src/base.js
index 52334507ac8b..5b77056bfbb2 100644
--- a/packages/eslint-config-sdk/src/base.js
+++ b/packages/eslint-config-sdk/src/base.js
@@ -59,6 +59,9 @@ module.exports = {
// We want to use optional chaining, where possible, to safe bytes
'@typescript-eslint/prefer-optional-chain': 'error',
+ // Disallow unnecessary type assertions/conversions
+ '@typescript-eslint/no-unnecessary-type-assertion': 'error',
+
// Private and protected members of a class should be prefixed with a leading underscore.
// typeLike declarations (class, interface, typeAlias, enum, typeParameter) should be
// PascalCase.
diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json
index 856691120543..2e24087f70cb 100644
--- a/packages/eslint-plugin-sdk/package.json
+++ b/packages/eslint-plugin-sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/eslint-plugin-sdk",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK eslint plugin",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-plugin-sdk",
diff --git a/packages/feedback/package.json b/packages/feedback/package.json
index 9b81c34cb0c1..37bcbeb66e04 100644
--- a/packages/feedback/package.json
+++ b/packages/feedback/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/feedback",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Sentry SDK integration for user feedback",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback",
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"devDependencies": {
"preact": "^10.19.4"
diff --git a/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts
index 87ab62cfec1b..9ce0feac921f 100644
--- a/packages/feedback/src/core/integration.ts
+++ b/packages/feedback/src/core/integration.ts
@@ -186,7 +186,7 @@ export const buildFeedbackIntegration = ({
_shadow = host.attachShadow({ mode: 'open' });
_shadow.appendChild(createMainStyles(options));
}
- return _shadow as ShadowRoot;
+ return _shadow;
};
const _loadAndRenderDialog = async (
diff --git a/packages/feedback/src/modal/integration.tsx b/packages/feedback/src/modal/integration.tsx
index 5bdf4362509d..0e3f8a637149 100644
--- a/packages/feedback/src/modal/integration.tsx
+++ b/packages/feedback/src/modal/integration.tsx
@@ -24,7 +24,7 @@ export const feedbackModalIntegration = ((): FeedbackModalIntegration => {
name: 'FeedbackModal',
setupOnce() {},
createDialog: ({ options, screenshotIntegration, sendFeedback, shadow }) => {
- const shadowRoot = shadow as unknown as ShadowRoot;
+ const shadowRoot = shadow as ShadowRoot;
const userKey = options.useSentryUser;
const user = getUser();
diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json
index 779ab0cfa5dd..58b73d1bff96 100644
--- a/packages/gatsby/package.json
+++ b/packages/gatsby/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/gatsby",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Gatsby.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby",
@@ -45,8 +45,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "10.14.0",
- "@sentry/react": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/react": "10.15.0",
"@sentry/webpack-plugin": "^4.1.1"
},
"peerDependencies": {
diff --git a/packages/gatsby/tsconfig.json b/packages/gatsby/tsconfig.json
index 77d5f63b9345..756f55907955 100644
--- a/packages/gatsby/tsconfig.json
+++ b/packages/gatsby/tsconfig.json
@@ -4,7 +4,7 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
// package-specific options
"jsx": "react"
}
diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json
index 3fb1deb94d8a..778a465c4dad 100644
--- a/packages/google-cloud-serverless/package.json
+++ b/packages/google-cloud-serverless/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/google-cloud-serverless",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Google Cloud Functions",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/google-cloud-serverless",
@@ -48,8 +48,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
"@types/express": "^4.17.14"
},
"devDependencies": {
diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts
index fc0fe353b919..ac0f41079017 100644
--- a/packages/google-cloud-serverless/src/index.ts
+++ b/packages/google-cloud-serverless/src/index.ts
@@ -109,6 +109,8 @@ export {
prismaIntegration,
hapiIntegration,
setupHapiErrorHandler,
+ honoIntegration,
+ setupHonoErrorHandler,
spotlightIntegration,
initOpenTelemetry,
spanToJSON,
diff --git a/packages/google-cloud-serverless/test/gcpfunction/cloud_event.test.ts b/packages/google-cloud-serverless/test/gcpfunction/cloud_event.test.ts
index 0cb6d502e934..8e7d80ee95b0 100644
--- a/packages/google-cloud-serverless/test/gcpfunction/cloud_event.test.ts
+++ b/packages/google-cloud-serverless/test/gcpfunction/cloud_event.test.ts
@@ -17,7 +17,7 @@ const mockSpan = {
vi.mock('@sentry/node', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node');
+ const original = await vi.importActual('@sentry/node');
return {
...original,
startSpanManual: (...args: unknown[]) => {
diff --git a/packages/google-cloud-serverless/test/gcpfunction/events.test.ts b/packages/google-cloud-serverless/test/gcpfunction/events.test.ts
index 7cb41a3c36e9..0dc7198ccdcf 100644
--- a/packages/google-cloud-serverless/test/gcpfunction/events.test.ts
+++ b/packages/google-cloud-serverless/test/gcpfunction/events.test.ts
@@ -18,7 +18,7 @@ const mockSpan = {
vi.mock('@sentry/node', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node');
+ const original = await vi.importActual('@sentry/node');
return {
...original,
startSpanManual: (...args: unknown[]) => {
diff --git a/packages/google-cloud-serverless/test/gcpfunction/http.test.ts b/packages/google-cloud-serverless/test/gcpfunction/http.test.ts
index 73cd262144e0..ce327c79cd54 100644
--- a/packages/google-cloud-serverless/test/gcpfunction/http.test.ts
+++ b/packages/google-cloud-serverless/test/gcpfunction/http.test.ts
@@ -20,7 +20,7 @@ const mockSpan = {
vi.mock('@sentry/node', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node');
+ const original = await vi.importActual('@sentry/node');
return {
...original,
init: (options: unknown) => {
diff --git a/packages/google-cloud-serverless/test/integrations/google-cloud-http.test.ts b/packages/google-cloud-serverless/test/integrations/google-cloud-http.test.ts
index 72a96e0b8e86..ce9ccee0a4ea 100644
--- a/packages/google-cloud-serverless/test/integrations/google-cloud-http.test.ts
+++ b/packages/google-cloud-serverless/test/integrations/google-cloud-http.test.ts
@@ -14,7 +14,7 @@ const mockStartInactiveSpan = vi.fn(spanArgs => ({ ...spanArgs }));
vi.mock('@sentry/node', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node');
+ const original = await vi.importActual('@sentry/node');
return {
...original,
startInactiveSpan: (ctx: unknown) => {
diff --git a/packages/google-cloud-serverless/test/sdk.test.ts b/packages/google-cloud-serverless/test/sdk.test.ts
index 9759ac2a5a43..a98cd8af47e6 100644
--- a/packages/google-cloud-serverless/test/sdk.test.ts
+++ b/packages/google-cloud-serverless/test/sdk.test.ts
@@ -5,7 +5,7 @@ const mockInit = vi.fn();
vi.mock('@sentry/node', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node');
+ const original = await vi.importActual('@sentry/node');
return {
...original,
init: (options: unknown) => {
diff --git a/packages/google-cloud-serverless/tsconfig.json b/packages/google-cloud-serverless/tsconfig.json
index a2731860dfa0..483551a2af71 100644
--- a/packages/google-cloud-serverless/tsconfig.json
+++ b/packages/google-cloud-serverless/tsconfig.json
@@ -5,7 +5,7 @@
"compilerOptions": {
// package-specific options
- "target": "ES2018",
+ "target": "es2020",
"resolveJsonModule": true
}
}
diff --git a/packages/integration-shims/package.json b/packages/integration-shims/package.json
index e5f4518944d9..bfa19a207fda 100644
--- a/packages/integration-shims/package.json
+++ b/packages/integration-shims/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/integration-shims",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Shims for integrations in Sentry SDK.",
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
@@ -56,7 +56,7 @@
"url": "https://github.com/getsentry/sentry-javascript/issues"
},
"dependencies": {
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"engines": {
"node": ">=18"
diff --git a/packages/integration-shims/src/Feedback.ts b/packages/integration-shims/src/Feedback.ts
index 327b2d40401f..dd56bd273717 100644
--- a/packages/integration-shims/src/Feedback.ts
+++ b/packages/integration-shims/src/Feedback.ts
@@ -22,10 +22,10 @@ export const feedbackIntegrationShim = Object.assign(
return {
name: 'Feedback',
- ...(FEEDBACK_INTEGRATION_METHODS.reduce((acc, method) => {
+ ...FEEDBACK_INTEGRATION_METHODS.reduce((acc, method) => {
acc[method] = FAKE_FUNCTION;
return acc;
- }, {} as FeedbackSpecificMethods) as FeedbackSpecificMethods),
+ }, {} as FeedbackSpecificMethods),
};
},
{
diff --git a/packages/integration-shims/src/Replay.ts b/packages/integration-shims/src/Replay.ts
index 6774b9308c23..eee5cfbb2ef7 100644
--- a/packages/integration-shims/src/Replay.ts
+++ b/packages/integration-shims/src/Replay.ts
@@ -21,9 +21,9 @@ export function replayIntegrationShim(_options: unknown): ReplayIntegration {
return {
name: 'Replay',
- ...(REPLAY_INTEGRATION_METHODS.reduce((acc, method) => {
+ ...REPLAY_INTEGRATION_METHODS.reduce((acc, method) => {
acc[method] = FAKE_FUNCTION;
return acc;
- }, {} as ReplaySpecificMethods) as ReplaySpecificMethods),
+ }, {} as ReplaySpecificMethods),
};
}
diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json
index aaf0e7bb3565..b94a67225206 100644
--- a/packages/nestjs/package.json
+++ b/packages/nestjs/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/nestjs",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for NestJS",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs",
@@ -49,8 +49,8 @@
"@opentelemetry/instrumentation": "^0.204.0",
"@opentelemetry/instrumentation-nestjs-core": "0.50.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0"
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0"
},
"devDependencies": {
"@nestjs/common": "^10.0.0",
diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts
index cc6d65514b77..2d4255df9b3f 100644
--- a/packages/nestjs/src/setup.ts
+++ b/packages/nestjs/src/setup.ts
@@ -54,7 +54,7 @@ class SentryTracingInterceptor implements NestInterceptor {
}
if (context.getType() === 'http') {
- const req = context.switchToHttp().getRequest() as FastifyRequest | ExpressRequest;
+ const req = context.switchToHttp().getRequest();
if ('routeOptions' in req && req.routeOptions?.url) {
// fastify case
getIsolationScope().setTransactionName(`${(req.method || 'GET').toUpperCase()} ${req.routeOptions.url}`);
diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index 403715a4157e..6abe5bb008c3 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/nextjs",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Next.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs",
@@ -79,13 +79,13 @@
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
"@rollup/plugin-commonjs": "28.0.1",
- "@sentry-internal/browser-utils": "10.14.0",
+ "@sentry-internal/browser-utils": "10.15.0",
"@sentry/bundler-plugin-core": "^4.3.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
- "@sentry/opentelemetry": "10.14.0",
- "@sentry/react": "10.14.0",
- "@sentry/vercel-edge": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
+ "@sentry/opentelemetry": "10.15.0",
+ "@sentry/react": "10.15.0",
+ "@sentry/vercel-edge": "10.15.0",
"@sentry/webpack-plugin": "^4.3.0",
"chalk": "3.0.0",
"resolve": "1.22.8",
diff --git a/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts b/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts
index d8e931e06451..3a05c97d5f5c 100644
--- a/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts
+++ b/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts
@@ -41,7 +41,7 @@ function wrapHandler(handler: T, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | '
// We try-catch here just in case the API around `requestAsyncStorage` changes unexpectedly since it is not public API
try {
- const requestAsyncStore = requestAsyncStorage?.getStore() as ReturnType;
+ const requestAsyncStore = requestAsyncStorage?.getStore();
headers = requestAsyncStore?.headers;
} catch {
/** empty */
diff --git a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts
index f29df45d3542..7a1320c3517f 100644
--- a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts
+++ b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts
@@ -46,7 +46,7 @@ if (typeof serverComponent === 'function') {
// We try-catch here just in `requestAsyncStorage` is undefined since it may not be defined
try {
- const requestAsyncStore = requestAsyncStorage?.getStore() as ReturnType;
+ const requestAsyncStore = requestAsyncStorage?.getStore();
sentryTraceHeader = requestAsyncStore?.headers.get('sentry-trace') ?? undefined;
baggageHeader = requestAsyncStore?.headers.get('baggage') ?? undefined;
headers = requestAsyncStore?.headers;
diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts
index 201c27dc5d0a..ddf761998e50 100644
--- a/packages/nextjs/src/config/withSentryConfig.ts
+++ b/packages/nextjs/src/config/withSentryConfig.ts
@@ -275,6 +275,18 @@ function getFinalConfigObject(
}
}
+ // webpack case
+ if (
+ userSentryOptions.useRunAfterProductionCompileHook &&
+ !supportsProductionCompileHook(nextJsVersion ?? '') &&
+ !isTurbopack
+ ) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.',
+ );
+ }
+
// If not explicitly set, turbopack uses the runAfterProductionCompile hook (as there are no alternatives), webpack does not.
const shouldUseRunAfterProductionCompileHook =
userSentryOptions?.useRunAfterProductionCompileHook ?? (isTurbopack ? true : false);
diff --git a/packages/nextjs/test/config/mocks.ts b/packages/nextjs/test/config/mocks.ts
index 2aa174877beb..0e9c48146585 100644
--- a/packages/nextjs/test/config/mocks.ts
+++ b/packages/nextjs/test/config/mocks.ts
@@ -16,7 +16,7 @@ vi.mock('os');
// function also lets us restore the original when we do want to test
// `getUserConfigFile()`.
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
-const fsReal = (await vi.importActual('fs')) as typeof import('fs');
+const fsReal = await vi.importActual('fs');
export const realExistsSync = fsReal.existsSync;
export const mockExistsSync = (path: fs.PathLike): ReturnType => {
if (
@@ -37,7 +37,7 @@ export const exitsSync = vi.spyOn(fs, 'existsSync').mockImplementation(mockExist
// Make it so that all temporary folders, either created directly by tests or by the code they're testing, will go into
// one spot that we know about, which we can then clean up when we're done
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
-const osReal = (await vi.importActual('os')) as typeof import('os');
+const osReal = await vi.importActual('os');
const realTmpdir = osReal.tmpdir;
// Including the random number ensures that even if multiple test files using these mocks are running at once, they have
diff --git a/packages/nextjs/test/config/withSentryConfig.test.ts b/packages/nextjs/test/config/withSentryConfig.test.ts
index 8fc0a81e3dd4..b437e73dfe75 100644
--- a/packages/nextjs/test/config/withSentryConfig.test.ts
+++ b/packages/nextjs/test/config/withSentryConfig.test.ts
@@ -1173,4 +1173,277 @@ describe('withSentryConfig', () => {
consoleWarnSpy.mockRestore();
});
});
+
+ describe('useRunAfterProductionCompileHook warning logic', () => {
+ const originalTurbopack = process.env.TURBOPACK;
+
+ afterEach(() => {
+ vi.restoreAllMocks();
+ process.env.TURBOPACK = originalTurbopack;
+ });
+
+ it('warns when useRunAfterProductionCompileHook is enabled with unsupported Next.js version in webpack mode', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: true,
+ };
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.',
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('does not warn when useRunAfterProductionCompileHook is enabled with supported Next.js version in webpack mode', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); // Supported version
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true);
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: true,
+ };
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).not.toHaveBeenCalledWith(
+ expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'),
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('does not warn when useRunAfterProductionCompileHook is disabled with unsupported Next.js version in webpack mode', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: false,
+ };
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).not.toHaveBeenCalledWith(
+ expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'),
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('does not warn when useRunAfterProductionCompileHook is undefined with unsupported Next.js version in webpack mode', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {}; // useRunAfterProductionCompileHook is undefined
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).not.toHaveBeenCalledWith(
+ expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'),
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('does not warn when useRunAfterProductionCompileHook is enabled with unsupported Next.js version in turbopack mode', () => {
+ process.env.TURBOPACK = '1'; // Ensure turbopack mode
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: true,
+ };
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ // Should not warn about useRunAfterProductionCompileHook incompatibility in turbopack mode
+ // (though it may warn about turbopack version compatibility)
+ expect(consoleWarnSpy).not.toHaveBeenCalledWith(
+ expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'),
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('warns with different unsupported Next.js versions', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: true,
+ };
+
+ // Test with 15.3.9
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.9');
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.',
+ );
+
+ consoleWarnSpy.mockClear();
+
+ // Test with 14.2.0
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('14.2.0');
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.',
+ );
+
+ consoleWarnSpy.mockClear();
+
+ // Test with canary version that's unsupported
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0-canary.42');
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.',
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('does not warn with supported Next.js versions', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: true,
+ };
+
+ // Test with 15.4.1
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1');
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true);
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).not.toHaveBeenCalledWith(
+ expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'),
+ );
+
+ consoleWarnSpy.mockClear();
+
+ // Test with 15.5.0
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.5.0');
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true);
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).not.toHaveBeenCalledWith(
+ expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'),
+ );
+
+ consoleWarnSpy.mockClear();
+
+ // Test with 16.0.0
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('16.0.0');
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true);
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).not.toHaveBeenCalledWith(
+ expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'),
+ );
+
+ consoleWarnSpy.mockClear();
+
+ // Test with supported canary version
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1-canary.1');
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true);
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).not.toHaveBeenCalledWith(
+ expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'),
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('handles edge case when Next.js version is undefined', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue(undefined);
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: true,
+ };
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.',
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('handles edge case when Next.js version is empty string', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('');
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: true,
+ };
+
+ materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.',
+ );
+
+ consoleWarnSpy.mockRestore();
+ });
+
+ it('works correctly with other sentry options present', () => {
+ delete process.env.TURBOPACK; // Ensure webpack mode
+ vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version
+ vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false);
+ const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
+
+ const sentryOptions = {
+ useRunAfterProductionCompileHook: true,
+ debug: true,
+ sourcemaps: {
+ disable: false,
+ },
+ tunnelRoute: '/tunnel',
+ };
+
+ const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions);
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.',
+ );
+
+ // Ensure other functionality still works (tunnel route creates rewrites function)
+ expect(finalConfig.rewrites).toBeInstanceOf(Function);
+ // Release name should be set (from git or environment)
+ expect(finalConfig.env).toHaveProperty('_sentryRelease');
+
+ consoleWarnSpy.mockRestore();
+ });
+ });
});
diff --git a/packages/nextjs/tsconfig.test.json b/packages/nextjs/tsconfig.test.json
index ecd411a65dc3..633c4212a0e9 100644
--- a/packages/nextjs/tsconfig.test.json
+++ b/packages/nextjs/tsconfig.test.json
@@ -9,7 +9,7 @@
// require for top-level await
"module": "Node16",
- "target": "es2017",
+ "target": "es2020",
// other package-specific, test-specific options
"lib": ["DOM", "ESNext"]
diff --git a/packages/node-core/package.json b/packages/node-core/package.json
index 830f660bbf87..c0a1fee20d36 100644
--- a/packages/node-core/package.json
+++ b/packages/node-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/node-core",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Sentry Node-Core SDK",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node-core",
@@ -66,8 +66,8 @@
"@opentelemetry/semantic-conventions": "^1.37.0"
},
"dependencies": {
- "@sentry/core": "10.14.0",
- "@sentry/opentelemetry": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/opentelemetry": "10.15.0",
"import-in-the-middle": "^1.14.2"
},
"devDependencies": {
diff --git a/packages/node-core/src/integrations/context.ts b/packages/node-core/src/integrations/context.ts
index aca94a70db7a..cad8a1c4a443 100644
--- a/packages/node-core/src/integrations/context.ts
+++ b/packages/node-core/src/integrations/context.ts
@@ -389,7 +389,7 @@ async function getLinuxInfo(): Promise {
// usually quite small, this should not allocate too much memory and we only
// hold on to it for a very short amount of time.
const distroPath = join('/etc', distroFile.name);
- const contents = ((await readFileAsync(distroPath, { encoding: 'utf-8' })) as string).toLowerCase();
+ const contents = (await readFileAsync(distroPath, { encoding: 'utf-8' })).toLowerCase();
// Some Linux distributions store their release information in the same file
// (e.g. RHEL and Centos). In those cases, we scan the file for an
diff --git a/packages/node-core/src/integrations/http/incoming-requests.ts b/packages/node-core/src/integrations/http/incoming-requests.ts
index 57588d0ac16e..e2de19f77582 100644
--- a/packages/node-core/src/integrations/http/incoming-requests.ts
+++ b/packages/node-core/src/integrations/http/incoming-requests.ts
@@ -489,7 +489,7 @@ function getContentLength(headers: IncomingHttpHeaders): number | null {
const contentLengthHeader = headers['content-length'];
if (contentLengthHeader === undefined) return null;
- const contentLength = parseInt(contentLengthHeader as string, 10);
+ const contentLength = parseInt(contentLengthHeader, 10);
if (isNaN(contentLength)) return null;
return contentLength;
diff --git a/packages/node-core/src/utils/ensureIsWrapped.ts b/packages/node-core/src/utils/ensureIsWrapped.ts
index a73c087ebaf2..921d01da8207 100644
--- a/packages/node-core/src/utils/ensureIsWrapped.ts
+++ b/packages/node-core/src/utils/ensureIsWrapped.ts
@@ -9,7 +9,7 @@ import { isCjs } from './detection';
*/
export function ensureIsWrapped(
maybeWrappedFunction: unknown,
- name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa',
+ name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa' | 'hono',
): void {
const clientOptions = getClient()?.getOptions();
if (
diff --git a/packages/node-core/test/integrations/context.test.ts b/packages/node-core/test/integrations/context.test.ts
index a22bfcf6ec34..c6ca3ad6a4da 100644
--- a/packages/node-core/test/integrations/context.test.ts
+++ b/packages/node-core/test/integrations/context.test.ts
@@ -5,7 +5,7 @@ import { conditionalTest } from '../helpers/conditional';
vi.mock('node:os', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('node:os')) as typeof import('node:os');
+ const original = await vi.importActual('node:os');
return {
...original,
uptime: original.uptime,
diff --git a/packages/node-core/test/integrations/contextlines.test.ts b/packages/node-core/test/integrations/contextlines.test.ts
index 2965acded28f..332cd9b2e51e 100644
--- a/packages/node-core/test/integrations/contextlines.test.ts
+++ b/packages/node-core/test/integrations/contextlines.test.ts
@@ -13,7 +13,7 @@ import { getError } from '../helpers/error';
vi.mock('node:fs', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('node:fs')) as typeof import('node:fs');
+ const original = await vi.importActual('node:fs');
return {
...original,
createReadStream: original.createReadStream,
diff --git a/packages/node-core/test/integrations/spotlight.test.ts b/packages/node-core/test/integrations/spotlight.test.ts
index 19d52ebcdb21..6d8e6b0bcfc2 100644
--- a/packages/node-core/test/integrations/spotlight.test.ts
+++ b/packages/node-core/test/integrations/spotlight.test.ts
@@ -8,7 +8,7 @@ import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOpti
vi.mock('node:http', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('node:http')) as typeof import('node:http');
+ const original = await vi.importActual('node:http');
return {
...original,
request: original.request,
diff --git a/packages/node-core/test/sdk/init.test.ts b/packages/node-core/test/sdk/init.test.ts
index c0c574ab2b35..4262bffb7cda 100644
--- a/packages/node-core/test/sdk/init.test.ts
+++ b/packages/node-core/test/sdk/init.test.ts
@@ -101,7 +101,7 @@ describe('init()', () => {
expect(mockDefaultIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1);
expect(mockDefaultIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(0);
- expect(newIntegration.setupOnce as Mock).toHaveBeenCalledTimes(1);
+ expect(newIntegration.setupOnce).toHaveBeenCalledTimes(1);
});
});
diff --git a/packages/node-core/test/transports/http.test.ts b/packages/node-core/test/transports/http.test.ts
index 0f01fdc5639a..e33bcb542bbd 100644
--- a/packages/node-core/test/transports/http.test.ts
+++ b/packages/node-core/test/transports/http.test.ts
@@ -14,7 +14,7 @@ import { makeNodeTransport } from '../../src/transports';
vi.mock('@sentry/core', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const actualCore = (await vi.importActual('@sentry/core')) as typeof import('@sentry/core');
+ const actualCore = await vi.importActual('@sentry/core');
return {
...actualCore,
createTransport: vi.fn().mockImplementation(actualCore.createTransport),
@@ -23,7 +23,7 @@ vi.mock('@sentry/core', async () => {
vi.mock('node:http', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const original = (await vi.importActual('node:http')) as typeof import('node:http');
+ const original = await vi.importActual('node:http');
return {
...original,
request: original.request,
diff --git a/packages/node-core/test/transports/https.test.ts b/packages/node-core/test/transports/https.test.ts
index bb4147a20133..ec9fc0aa8204 100644
--- a/packages/node-core/test/transports/https.test.ts
+++ b/packages/node-core/test/transports/https.test.ts
@@ -10,7 +10,7 @@ import testServerCerts from './test-server-certs';
vi.mock('@sentry/core', async () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const actualCore = (await vi.importActual('@sentry/core')) as typeof import('@sentry/core');
+ const actualCore = await vi.importActual('@sentry/core');
return {
...actualCore,
createTransport: vi.fn().mockImplementation(actualCore.createTransport),
diff --git a/packages/node-core/tsconfig.json b/packages/node-core/tsconfig.json
index b9683a850600..64d6f3a1b9e0 100644
--- a/packages/node-core/tsconfig.json
+++ b/packages/node-core/tsconfig.json
@@ -4,7 +4,7 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["es2018", "es2020.string"],
+ "lib": ["es2020"],
"module": "Node16"
}
}
diff --git a/packages/node-native/package.json b/packages/node-native/package.json
index f310c434f6cb..6c7ba47deaba 100644
--- a/packages/node-native/package.json
+++ b/packages/node-native/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/node-native",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Native Tools for the Official Sentry Node.js SDK",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node-native",
@@ -64,8 +64,8 @@
},
"dependencies": {
"@sentry-internal/node-native-stacktrace": "^0.2.2",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0"
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0"
},
"devDependencies": {
"@types/node": "^18.19.1"
diff --git a/packages/node-native/tsconfig.json b/packages/node-native/tsconfig.json
index 29acbf3f36e9..b1109af19fdf 100644
--- a/packages/node-native/tsconfig.json
+++ b/packages/node-native/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "esnext",
- "lib": ["es2018"],
+ "lib": ["es2020"],
"outDir": "build",
"types": ["node"]
},
diff --git a/packages/node/package.json b/packages/node/package.json
index 93b5ea7fbf62..6bb808447779 100644
--- a/packages/node/package.json
+++ b/packages/node/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/node",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Sentry Node SDK using OpenTelemetry for performance instrumentation",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node",
@@ -95,9 +95,9 @@
"@opentelemetry/sdk-trace-base": "^2.1.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
"@prisma/instrumentation": "6.15.0",
- "@sentry/core": "10.14.0",
- "@sentry/node-core": "10.14.0",
- "@sentry/opentelemetry": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node-core": "10.15.0",
+ "@sentry/opentelemetry": "10.15.0",
"import-in-the-middle": "^1.14.2",
"minimatch": "^9.0.0"
},
diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts
index 853ec8dbac2f..67e00660c2a1 100644
--- a/packages/node/src/index.ts
+++ b/packages/node/src/index.ts
@@ -15,6 +15,7 @@ export { postgresIntegration } from './integrations/tracing/postgres';
export { postgresJsIntegration } from './integrations/tracing/postgresjs';
export { prismaIntegration } from './integrations/tracing/prisma';
export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/hapi';
+export { honoIntegration, setupHonoErrorHandler } from './integrations/tracing/hono';
export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa';
export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect';
export { knexIntegration } from './integrations/tracing/knex';
diff --git a/packages/node/src/integrations/tracing/fastify/index.ts b/packages/node/src/integrations/tracing/fastify/index.ts
index 084c50a6957e..0ad069ea1f4e 100644
--- a/packages/node/src/integrations/tracing/fastify/index.ts
+++ b/packages/node/src/integrations/tracing/fastify/index.ts
@@ -101,7 +101,7 @@ function getFastifyIntegration(): ReturnType | undef
if (!client) {
return undefined;
} else {
- return client.getIntegrationByName(INTEGRATION_NAME) as ReturnType | undefined;
+ return client.getIntegrationByName(INTEGRATION_NAME);
}
}
diff --git a/packages/node/src/integrations/tracing/firebase/firebase.ts b/packages/node/src/integrations/tracing/firebase/firebase.ts
index 9f2abbfe31fd..649a7089289b 100644
--- a/packages/node/src/integrations/tracing/firebase/firebase.ts
+++ b/packages/node/src/integrations/tracing/firebase/firebase.ts
@@ -1,4 +1,3 @@
-import type { Span } from '@opentelemetry/api';
import type { IntegrationFn } from '@sentry/core';
import { defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core';
import { addOriginToSpan, generateInstrumentOnce } from '@sentry/node-core';
@@ -8,7 +7,7 @@ const INTEGRATION_NAME = 'Firebase';
const config: FirebaseInstrumentationConfig = {
firestoreSpanCreationHook: span => {
- addOriginToSpan(span as Span, 'auto.firebase.otel.firestore');
+ addOriginToSpan(span, 'auto.firebase.otel.firestore');
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'db.query');
},
diff --git a/packages/node/src/integrations/tracing/google-genai/instrumentation.ts b/packages/node/src/integrations/tracing/google-genai/instrumentation.ts
index cfdb68973be6..e604e0c99cd3 100644
--- a/packages/node/src/integrations/tracing/google-genai/instrumentation.ts
+++ b/packages/node/src/integrations/tracing/google-genai/instrumentation.ts
@@ -69,7 +69,7 @@ export class SentryGoogleGenAiInstrumentation extends InstrumentationBase new HonoInstrumentation());
+function addHonoSpanAttributes(span: Span): void {
+ const attributes = spanToJSON(span).data;
+ const type = attributes[AttributeNames.HONO_TYPE];
+ if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] || !type) {
+ return;
+ }
+
+ span.setAttributes({
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.hono',
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.hono`,
+ });
+
+ const name = attributes[AttributeNames.HONO_NAME];
+ if (typeof name === 'string') {
+ span.updateName(name);
+ }
+
+ if (getIsolationScope() === getDefaultIsolationScope()) {
+ DEBUG_BUILD && debug.warn('Isolation scope is default isolation scope - skipping setting transactionName');
+ return;
+ }
+
+ const route = attributes[ATTR_HTTP_ROUTE];
+ const method = attributes[ATTR_HTTP_REQUEST_METHOD];
+ if (typeof route === 'string' && typeof method === 'string') {
+ getIsolationScope().setTransactionName(`${method} ${route}`);
+ }
+}
+
+export const instrumentHono = generateInstrumentOnce(
+ INTEGRATION_NAME,
+ () =>
+ new HonoInstrumentation({
+ responseHook: span => {
+ addHonoSpanAttributes(span);
+ },
+ }),
+);
const _honoIntegration = (() => {
return {
@@ -33,3 +84,67 @@ const _honoIntegration = (() => {
* ```
*/
export const honoIntegration = defineIntegration(_honoIntegration);
+
+interface HonoHandlerOptions {
+ /**
+ * Callback method deciding whether error should be captured and sent to Sentry
+ * @param error Captured Hono error
+ */
+ shouldHandleError: (context: Context) => boolean;
+}
+
+function honoRequestHandler(): MiddlewareHandler {
+ return async function sentryRequestMiddleware(context: Context, next: Next): Promise {
+ const normalizedRequest = httpRequestToRequestData(context.req);
+ getIsolationScope().setSDKProcessingMetadata({ normalizedRequest });
+ await next();
+ };
+}
+
+function defaultShouldHandleError(context: Context): boolean {
+ const statusCode = context.res.status;
+ return statusCode >= 500;
+}
+
+function honoErrorHandler(options?: Partial): MiddlewareHandler {
+ return async function sentryErrorMiddleware(context: Context, next: Next): Promise {
+ await next();
+
+ const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError;
+ if (shouldHandleError(context)) {
+ (context.res as { sentry?: string }).sentry = captureException(context.error, {
+ mechanism: {
+ type: 'auto.middleware.hono',
+ handled: false,
+ },
+ });
+ }
+ };
+}
+
+/**
+ * Add a Hono error handler to capture errors to Sentry.
+ *
+ * @param app The Hono instances
+ * @param options Configuration options for the handler
+ *
+ * @example
+ * ```javascript
+ * const Sentry = require('@sentry/node');
+ * const { Hono } = require("hono");
+ *
+ * const app = new Hono();
+ *
+ * Sentry.setupHonoErrorHandler(app);
+ *
+ * // Add your routes, etc.
+ * ```
+ */
+export function setupHonoErrorHandler(
+ app: { use: MiddlewareHandlerInterface },
+ options?: Partial,
+): void {
+ app.use(honoRequestHandler());
+ app.use(honoErrorHandler(options));
+ ensureIsWrapped(app.use, 'hono');
+}
diff --git a/packages/node/src/integrations/tracing/hono/instrumentation.ts b/packages/node/src/integrations/tracing/hono/instrumentation.ts
index 81e062560051..9a55eaac2776 100644
--- a/packages/node/src/integrations/tracing/hono/instrumentation.ts
+++ b/packages/node/src/integrations/tracing/hono/instrumentation.ts
@@ -1,15 +1,39 @@
+import type { Span } from '@opentelemetry/api';
+import { context, SpanStatusCode, trace } from '@opentelemetry/api';
+import type { InstrumentationConfig } from '@opentelemetry/instrumentation';
import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
-import type { HandlerInterface, Hono, HonoInstance, MiddlewareHandlerInterface, OnHandlerInterface } from './types';
+import { isThenable } from '@sentry/core';
+import { AttributeNames, HonoTypes } from './constants';
+import type {
+ Context,
+ Handler,
+ HandlerInterface,
+ Hono,
+ HonoInstance,
+ MiddlewareHandler,
+ MiddlewareHandlerInterface,
+ Next,
+ OnHandlerInterface,
+} from './types';
const PACKAGE_NAME = '@sentry/instrumentation-hono';
const PACKAGE_VERSION = '0.0.1';
+export interface HonoResponseHookFunction {
+ (span: Span): void;
+}
+
+export interface HonoInstrumentationConfig extends InstrumentationConfig {
+ /** Function for adding custom span attributes from the response */
+ responseHook?: HonoResponseHookFunction;
+}
+
/**
* Hono instrumentation for OpenTelemetry
*/
-export class HonoInstrumentation extends InstrumentationBase {
- public constructor() {
- super(PACKAGE_NAME, PACKAGE_VERSION, {});
+export class HonoInstrumentation extends InstrumentationBase {
+ public constructor(config: HonoInstrumentationConfig = {}) {
+ super(PACKAGE_NAME, PACKAGE_VERSION, config);
}
/**
@@ -28,7 +52,7 @@ export class HonoInstrumentation extends InstrumentationBase {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const instrumentation = this;
- moduleExports.Hono = class HonoWrapper extends moduleExports.Hono {
+ class WrappedHono extends moduleExports.Hono {
public constructor(...args: unknown[]) {
super(...args);
@@ -42,7 +66,15 @@ export class HonoInstrumentation extends InstrumentationBase {
instrumentation._wrap(this, 'on', instrumentation._patchOnHandler());
instrumentation._wrap(this, 'use', instrumentation._patchMiddlewareHandler());
}
- };
+ }
+
+ try {
+ moduleExports.Hono = WrappedHono;
+ } catch {
+ // This is a workaround for environments where direct assignment is not allowed.
+ return { ...moduleExports, Hono: WrappedHono };
+ }
+
return moduleExports;
}
@@ -50,10 +82,28 @@ export class HonoInstrumentation extends InstrumentationBase {
* Patches the route handler to instrument it.
*/
private _patchHandler(): (original: HandlerInterface) => HandlerInterface {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const instrumentation = this;
+
return function (original: HandlerInterface) {
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) {
- // TODO: Add OpenTelemetry tracing logic here
- return original.apply(this, args);
+ if (typeof args[0] === 'string') {
+ const path = args[0];
+ if (args.length === 1) {
+ return original.apply(this, [path]);
+ }
+
+ const handlers = args.slice(1);
+ return original.apply(this, [
+ path,
+ ...handlers.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)),
+ ]);
+ }
+
+ return original.apply(
+ this,
+ args.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)),
+ );
};
};
}
@@ -62,10 +112,16 @@ export class HonoInstrumentation extends InstrumentationBase {
* Patches the 'on' handler to instrument it.
*/
private _patchOnHandler(): (original: OnHandlerInterface) => OnHandlerInterface {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const instrumentation = this;
+
return function (original: OnHandlerInterface) {
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) {
- // TODO: Add OpenTelemetry tracing logic here
- return original.apply(this, args);
+ const handlers = args.slice(2);
+ return original.apply(this, [
+ ...args.slice(0, 2),
+ ...handlers.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)),
+ ]);
};
};
}
@@ -74,11 +130,124 @@ export class HonoInstrumentation extends InstrumentationBase {
* Patches the middleware handler to instrument it.
*/
private _patchMiddlewareHandler(): (original: MiddlewareHandlerInterface) => MiddlewareHandlerInterface {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const instrumentation = this;
+
return function (original: MiddlewareHandlerInterface) {
return function wrappedHandler(this: HonoInstance, ...args: unknown[]) {
- // TODO: Add OpenTelemetry tracing logic here
- return original.apply(this, args);
+ if (typeof args[0] === 'string') {
+ const path = args[0];
+ if (args.length === 1) {
+ return original.apply(this, [path]);
+ }
+
+ const handlers = args.slice(1);
+ return original.apply(this, [
+ path,
+ ...handlers.map(handler => instrumentation._wrapHandler(handler as MiddlewareHandler)),
+ ]);
+ }
+
+ return original.apply(
+ this,
+ args.map(handler => instrumentation._wrapHandler(handler as MiddlewareHandler)),
+ );
};
};
}
+
+ /**
+ * Wraps a handler or middleware handler to apply instrumentation.
+ */
+ private _wrapHandler(handler: Handler | MiddlewareHandler): Handler | MiddlewareHandler {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const instrumentation = this;
+
+ return function (this: unknown, c: Context, next: Next) {
+ if (!instrumentation.isEnabled()) {
+ return handler.apply(this, [c, next]);
+ }
+
+ const path = c.req.path;
+ const span = instrumentation.tracer.startSpan(path);
+
+ return context.with(trace.setSpan(context.active(), span), () => {
+ return instrumentation._safeExecute(
+ () => {
+ const result = handler.apply(this, [c, next]);
+ if (isThenable(result)) {
+ return result.then(result => {
+ const type = instrumentation._determineHandlerType(result);
+ span.setAttributes({
+ [AttributeNames.HONO_TYPE]: type,
+ [AttributeNames.HONO_NAME]: type === HonoTypes.REQUEST_HANDLER ? path : handler.name || 'anonymous',
+ });
+ instrumentation.getConfig().responseHook?.(span);
+ return result;
+ });
+ } else {
+ const type = instrumentation._determineHandlerType(result);
+ span.setAttributes({
+ [AttributeNames.HONO_TYPE]: type,
+ [AttributeNames.HONO_NAME]: type === HonoTypes.REQUEST_HANDLER ? path : handler.name || 'anonymous',
+ });
+ instrumentation.getConfig().responseHook?.(span);
+ return result;
+ }
+ },
+ () => span.end(),
+ error => {
+ instrumentation._handleError(span, error);
+ span.end();
+ },
+ );
+ });
+ };
+ }
+
+ /**
+ * Safely executes a function and handles errors.
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private _safeExecute(execute: () => any, onSuccess: () => void, onFailure: (error: unknown) => void): () => any {
+ try {
+ const result = execute();
+
+ if (isThenable(result)) {
+ result.then(
+ () => onSuccess(),
+ (error: unknown) => onFailure(error),
+ );
+ } else {
+ onSuccess();
+ }
+
+ return result;
+ } catch (error: unknown) {
+ onFailure(error);
+ throw error;
+ }
+ }
+
+ /**
+ * Determines the handler type based on the result.
+ * @param result
+ * @private
+ */
+ private _determineHandlerType(result: unknown): HonoTypes {
+ return result === undefined ? HonoTypes.MIDDLEWARE : HonoTypes.REQUEST_HANDLER;
+ }
+
+ /**
+ * Handles errors by setting the span status and recording the exception.
+ */
+ private _handleError(span: Span, error: unknown): void {
+ if (error instanceof Error) {
+ span.setStatus({
+ code: SpanStatusCode.ERROR,
+ message: error.message,
+ });
+ span.recordException(error);
+ }
+ }
}
diff --git a/packages/node/src/integrations/tracing/hono/types.ts b/packages/node/src/integrations/tracing/hono/types.ts
index 3d7e057859f1..9873f80afa66 100644
--- a/packages/node/src/integrations/tracing/hono/types.ts
+++ b/packages/node/src/integrations/tracing/hono/types.ts
@@ -1,11 +1,14 @@
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/request.ts#L30
export type HonoRequest = {
path: string;
+ method: string;
};
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/context.ts#L291
export type Context = {
req: HonoRequest;
+ res: Response;
+ error: Error | undefined;
};
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L36C1-L36C39
@@ -15,7 +18,7 @@ export type Next = () => Promise;
export type Handler = (c: Context, next: Next) => Promise | Response;
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L80
-export type MiddlewareHandler = (c: Context, next: Next) => Promise | Response | void;
+export type MiddlewareHandler = (c: Context, next: Next) => Promise;
// Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L109
export type HandlerInterface = {
diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts
index e4dd84fc266e..dd9d9ac8df2b 100644
--- a/packages/node/src/integrations/tracing/index.ts
+++ b/packages/node/src/integrations/tracing/index.ts
@@ -10,6 +10,7 @@ import { genericPoolIntegration, instrumentGenericPool } from './genericPool';
import { googleGenAIIntegration, instrumentGoogleGenAI } from './google-genai';
import { graphqlIntegration, instrumentGraphql } from './graphql';
import { hapiIntegration, instrumentHapi } from './hapi';
+import { honoIntegration, instrumentHono } from './hono';
import { instrumentKafka, kafkaIntegration } from './kafka';
import { instrumentKoa, koaIntegration } from './koa';
import { instrumentLruMemoizer, lruMemoizerIntegration } from './lrumemoizer';
@@ -33,6 +34,7 @@ export function getAutoPerformanceIntegrations(): Integration[] {
expressIntegration(),
fastifyIntegration(),
graphqlIntegration(),
+ honoIntegration(),
mongoIntegration(),
mongooseIntegration(),
mysqlIntegration(),
@@ -70,6 +72,7 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) =>
instrumentFastify,
instrumentFastifyV3,
instrumentHapi,
+ instrumentHono,
instrumentKafka,
instrumentKoa,
instrumentLruMemoizer,
diff --git a/packages/node/src/utils/redisCache.ts b/packages/node/src/utils/redisCache.ts
index b9d6cb814856..476a257fbc6d 100644
--- a/packages/node/src/utils/redisCache.ts
+++ b/packages/node/src/utils/redisCache.ts
@@ -103,9 +103,9 @@ function flatten(input: NestedArray): T[] {
const flattenHelper = (input: NestedArray): void => {
input.forEach((el: T | NestedArray) => {
if (Array.isArray(el)) {
- flattenHelper(el as NestedArray);
+ flattenHelper(el);
} else {
- result.push(el as T);
+ result.push(el);
}
});
};
diff --git a/packages/node/test/sdk/init.test.ts b/packages/node/test/sdk/init.test.ts
index 422bbdc924f4..da6680fc622f 100644
--- a/packages/node/test/sdk/init.test.ts
+++ b/packages/node/test/sdk/init.test.ts
@@ -111,7 +111,7 @@ describe('init()', () => {
expect(mockDefaultIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1);
expect(mockDefaultIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(0);
- expect(newIntegration.setupOnce as Mock).toHaveBeenCalledTimes(1);
+ expect(newIntegration.setupOnce).toHaveBeenCalledTimes(1);
expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0);
});
@@ -133,7 +133,7 @@ describe('init()', () => {
expect(mockIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1);
expect(mockIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(1);
- expect(autoPerformanceIntegration.setupOnce as Mock).toHaveBeenCalledTimes(1);
+ expect(autoPerformanceIntegration.setupOnce).toHaveBeenCalledTimes(1);
expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(1);
const client = getClient();
diff --git a/packages/node/tsconfig.json b/packages/node/tsconfig.json
index b9683a850600..64d6f3a1b9e0 100644
--- a/packages/node/tsconfig.json
+++ b/packages/node/tsconfig.json
@@ -4,7 +4,7 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["es2018", "es2020.string"],
+ "lib": ["es2020"],
"module": "Node16"
}
}
diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json
index e157ff0352de..07f00fd58f3e 100644
--- a/packages/nuxt/package.json
+++ b/packages/nuxt/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/nuxt",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Nuxt",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nuxt",
@@ -47,13 +47,13 @@
},
"dependencies": {
"@nuxt/kit": "^3.13.2",
- "@sentry/browser": "10.14.0",
- "@sentry/cloudflare": "10.14.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
+ "@sentry/browser": "10.15.0",
+ "@sentry/cloudflare": "10.15.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
"@sentry/rollup-plugin": "^4.3.0",
"@sentry/vite-plugin": "^4.3.0",
- "@sentry/vue": "10.14.0"
+ "@sentry/vue": "10.15.0"
},
"devDependencies": {
"@nuxt/module-builder": "^0.8.4",
diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json
index 1dd7a9cc168b..2b4657ca1d35 100644
--- a/packages/opentelemetry/package.json
+++ b/packages/opentelemetry/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/opentelemetry",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry utilities for OpenTelemetry",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/opentelemetry",
@@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.9.0",
diff --git a/packages/pino-transport/package.json b/packages/pino-transport/package.json
index 616c506ec089..bd19200b4a0c 100644
--- a/packages/pino-transport/package.json
+++ b/packages/pino-transport/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/pino-transport",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Pino transport for Sentry SDK",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/pino-transport",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
"pino-abstract-transport": "^2.0.0"
},
"peerDependencies": {
diff --git a/packages/pino-transport/tsconfig.json b/packages/pino-transport/tsconfig.json
index b9683a850600..64d6f3a1b9e0 100644
--- a/packages/pino-transport/tsconfig.json
+++ b/packages/pino-transport/tsconfig.json
@@ -4,7 +4,7 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["es2018", "es2020.string"],
+ "lib": ["es2020"],
"module": "Node16"
}
}
diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json
index deb257ed7372..c4c3e86387de 100644
--- a/packages/profiling-node/package.json
+++ b/packages/profiling-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/profiling-node",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Node.js Profiling",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/profiling-node",
@@ -63,8 +63,8 @@
},
"dependencies": {
"@sentry-internal/node-cpu-profiler": "^2.2.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0"
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0"
},
"devDependencies": {
"@types/node": "^18.19.1"
diff --git a/packages/profiling-node/tsconfig.json b/packages/profiling-node/tsconfig.json
index 29acbf3f36e9..b1109af19fdf 100644
--- a/packages/profiling-node/tsconfig.json
+++ b/packages/profiling-node/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "esnext",
- "lib": ["es2018"],
+ "lib": ["es2020"],
"outDir": "build",
"types": ["node"]
},
diff --git a/packages/react-router/package.json b/packages/react-router/package.json
index 52d1b7a66166..b9d339c865db 100644
--- a/packages/react-router/package.json
+++ b/packages/react-router/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/react-router",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for React Router (Framework)",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react-router",
@@ -49,11 +49,11 @@
"@opentelemetry/core": "^2.1.0",
"@opentelemetry/instrumentation": "^0.204.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
- "@sentry/browser": "10.14.0",
+ "@sentry/browser": "10.15.0",
"@sentry/cli": "^2.53.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
- "@sentry/react": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
+ "@sentry/react": "10.15.0",
"@sentry/vite-plugin": "^4.1.0",
"glob": "11.0.1"
},
diff --git a/packages/react-router/test/server/lowQualityTransactionsFilterIntegration.test.ts b/packages/react-router/test/server/lowQualityTransactionsFilterIntegration.test.ts
index 4f43b48dcad9..7edd75c9e996 100644
--- a/packages/react-router/test/server/lowQualityTransactionsFilterIntegration.test.ts
+++ b/packages/react-router/test/server/lowQualityTransactionsFilterIntegration.test.ts
@@ -1,4 +1,4 @@
-import type { Event, EventType, Integration } from '@sentry/core';
+import type { Event, EventType } from '@sentry/core';
import * as SentryCore from '@sentry/core';
import * as SentryNode from '@sentry/node';
import { afterEach, describe, expect, it, vi } from 'vitest';
@@ -20,7 +20,7 @@ describe('Low Quality Transactions Filter Integration', () => {
['@id/ requests', 'GET /@id/some-id'],
['manifest requests', 'GET /__manifest?p=%2Fperformance%2Fserver-action'],
])('%s', (description, transaction) => {
- const integration = lowQualityTransactionsFilterIntegration({ debug: true }) as Integration;
+ const integration = lowQualityTransactionsFilterIntegration({ debug: true });
const event = {
type: 'transaction' as EventType,
transaction,
@@ -40,7 +40,7 @@ describe('Low Quality Transactions Filter Integration', () => {
['API endpoints', 'POST /data'],
['app routes', 'GET /projects/123'],
])('%s', (description, transaction) => {
- const integration = lowQualityTransactionsFilterIntegration({}) as Integration;
+ const integration = lowQualityTransactionsFilterIntegration({});
const event = {
type: 'transaction' as EventType,
transaction,
@@ -53,7 +53,7 @@ describe('Low Quality Transactions Filter Integration', () => {
});
it('does not affect non-transaction events', () => {
- const integration = lowQualityTransactionsFilterIntegration({}) as Integration;
+ const integration = lowQualityTransactionsFilterIntegration({});
const event = {
type: 'error' as EventType,
transaction: 'GET /node_modules/some-package/index.js',
diff --git a/packages/react/package.json b/packages/react/package.json
index 13607540006f..e7e2856e09a1 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/react",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for React.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "10.14.0",
- "@sentry/core": "10.14.0",
+ "@sentry/browser": "10.15.0",
+ "@sentry/core": "10.15.0",
"hoist-non-react-statics": "^3.3.2"
},
"peerDependencies": {
diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json
index 67260f799e86..41ff3c42258e 100644
--- a/packages/react/tsconfig.json
+++ b/packages/react/tsconfig.json
@@ -4,7 +4,7 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
// package-specific options
"esModuleInterop": true,
"jsx": "react"
diff --git a/packages/remix/package.json b/packages/remix/package.json
index 3bc1c9942dbd..6b2ca7fe93db 100644
--- a/packages/remix/package.json
+++ b/packages/remix/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/remix",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Remix",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/remix",
@@ -69,9 +69,9 @@
"@opentelemetry/semantic-conventions": "^1.37.0",
"@remix-run/router": "1.x",
"@sentry/cli": "^2.53.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
- "@sentry/react": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
+ "@sentry/react": "10.15.0",
"glob": "^10.3.4",
"yargs": "^17.6.0"
},
diff --git a/packages/remix/src/server/sdk.ts b/packages/remix/src/server/sdk.ts
index 145cb8a66b47..f191a336cfbb 100644
--- a/packages/remix/src/server/sdk.ts
+++ b/packages/remix/src/server/sdk.ts
@@ -18,7 +18,7 @@ export function getRemixDefaultIntegrations(options: RemixOptions): Integration[
...getDefaultNodeIntegrations(options as NodeOptions).filter(integration => integration.name !== 'Http'),
httpIntegration(),
remixIntegration(),
- ].filter(int => int) as Integration[];
+ ].filter(int => int);
}
/** Initializes Sentry Remix SDK on Node. */
diff --git a/packages/remix/test/integration/tsconfig.json b/packages/remix/test/integration/tsconfig.json
index 1ab42867a9dc..e269d26457d3 100644
--- a/packages/remix/test/integration/tsconfig.json
+++ b/packages/remix/test/integration/tsconfig.json
@@ -1,13 +1,13 @@
{
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "lib": ["DOM", "DOM.Iterable", "ES2020"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"resolveJsonModule": true,
- "target": "ES2019",
+ "target": "es2020",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
diff --git a/packages/remix/tsconfig.test.json b/packages/remix/tsconfig.test.json
index bc444a444917..f62d7ff34d09 100644
--- a/packages/remix/tsconfig.test.json
+++ b/packages/remix/tsconfig.test.json
@@ -4,11 +4,11 @@
"include": ["test/**/*", "vitest.config.ts", "vitest.config.unit.ts"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
"types": ["node"],
// Required for top-level await in tests
"module": "Node16",
- "target": "ES2017",
+ "target": "es2020",
"esModuleInterop": true
}
diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json
index 4c45f539b637..dc758918406e 100644
--- a/packages/replay-canvas/package.json
+++ b/packages/replay-canvas/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/replay-canvas",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Replay canvas integration",
"main": "build/npm/cjs/index.js",
"module": "build/npm/esm/index.js",
@@ -69,8 +69,8 @@
"@sentry-internal/rrweb": "2.37.0"
},
"dependencies": {
- "@sentry-internal/replay": "10.14.0",
- "@sentry/core": "10.14.0"
+ "@sentry-internal/replay": "10.15.0",
+ "@sentry/core": "10.15.0"
},
"engines": {
"node": ">=18"
diff --git a/packages/replay-canvas/tsconfig.json b/packages/replay-canvas/tsconfig.json
index cd1b8207ea06..4f9aae5f3994 100644
--- a/packages/replay-canvas/tsconfig.json
+++ b/packages/replay-canvas/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
"module": "esnext"
},
"include": ["src/**/*.ts"]
diff --git a/packages/replay-canvas/tsconfig.test.json b/packages/replay-canvas/tsconfig.test.json
index f4e8a1624f08..ccc44803a599 100644
--- a/packages/replay-canvas/tsconfig.test.json
+++ b/packages/replay-canvas/tsconfig.test.json
@@ -4,7 +4,7 @@
"include": ["test/**/*.ts", "vite.config.ts"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
"types": ["node"],
"esModuleInterop": true,
"allowJs": true,
diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json
index ec588fca16d8..53f31f9053dd 100644
--- a/packages/replay-internal/package.json
+++ b/packages/replay-internal/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/replay",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "User replays for Sentry",
"main": "build/npm/cjs/index.js",
"module": "build/npm/esm/index.js",
@@ -81,7 +81,7 @@
"homepage": "https://docs.sentry.io/platforms/javascript/session-replay/",
"devDependencies": {
"@babel/core": "^7.27.7",
- "@sentry-internal/replay-worker": "10.14.0",
+ "@sentry-internal/replay-worker": "10.15.0",
"@sentry-internal/rrweb": "2.37.0",
"@sentry-internal/rrweb-snapshot": "2.37.0",
"fflate": "0.8.2",
@@ -90,8 +90,8 @@
"node-fetch": "^2.6.7"
},
"dependencies": {
- "@sentry-internal/browser-utils": "10.14.0",
- "@sentry/core": "10.14.0"
+ "@sentry-internal/browser-utils": "10.15.0",
+ "@sentry/core": "10.15.0"
},
"engines": {
"node": ">=18"
diff --git a/packages/replay-internal/test/unit/session/createSession.test.ts b/packages/replay-internal/test/unit/session/createSession.test.ts
index 53fbbbdb59e5..a30f13401502 100644
--- a/packages/replay-internal/test/unit/session/createSession.test.ts
+++ b/packages/replay-internal/test/unit/session/createSession.test.ts
@@ -12,7 +12,7 @@ vi.mock('./../../../src/session/saveSession');
vi.mock('@sentry/core', async () => {
return {
- ...((await vi.importActual('@sentry/core')) as { string: unknown }),
+ ...(await vi.importActual('@sentry/core')),
uuid4: vi.fn(() => 'test_session_id'),
};
});
diff --git a/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts b/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts
index b59c14aa4e4d..273d401a7afc 100644
--- a/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts
+++ b/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts
@@ -14,7 +14,7 @@ import type { SessionOptions } from '../../../src/types';
vi.mock('@sentry/core', async () => {
return {
- ...((await vi.importActual('@sentry/core')) as { string: unknown }),
+ ...(await vi.importActual('@sentry/core')),
uuid4: vi.fn(() => 'test_session_uuid'),
};
});
diff --git a/packages/replay-internal/tsconfig.json b/packages/replay-internal/tsconfig.json
index cd1b8207ea06..4f9aae5f3994 100644
--- a/packages/replay-internal/tsconfig.json
+++ b/packages/replay-internal/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
"module": "esnext"
},
"include": ["src/**/*.ts"]
diff --git a/packages/replay-worker/package.json b/packages/replay-worker/package.json
index ed2d7147241f..a09417250bd6 100644
--- a/packages/replay-worker/package.json
+++ b/packages/replay-worker/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/replay-worker",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Worker for @sentry-internal/replay",
"main": "build/esm/index.js",
"module": "build/esm/index.js",
diff --git a/packages/replay-worker/tsconfig.json b/packages/replay-worker/tsconfig.json
index f3db9c003516..24cce469ccc3 100644
--- a/packages/replay-worker/tsconfig.json
+++ b/packages/replay-worker/tsconfig.json
@@ -4,7 +4,7 @@
"module": "esnext",
"lib": ["webworker", "scripthost"],
"esModuleInterop": true,
- "target": "es2018",
+ "target": "es2020",
"strictPropertyInitialization": false
},
"include": ["src/**/*.ts"]
diff --git a/packages/solid/package.json b/packages/solid/package.json
index eced885b8818..6986e039feef 100644
--- a/packages/solid/package.json
+++ b/packages/solid/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/solid",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Solid",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solid",
@@ -44,8 +44,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "10.14.0",
- "@sentry/core": "10.14.0"
+ "@sentry/browser": "10.15.0",
+ "@sentry/core": "10.15.0"
},
"peerDependencies": {
"@solidjs/router": "^0.13.4",
diff --git a/packages/solid/tsconfig.json b/packages/solid/tsconfig.json
index 36891917c5cc..fd54f069790c 100644
--- a/packages/solid/tsconfig.json
+++ b/packages/solid/tsconfig.json
@@ -4,6 +4,6 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"]
+ "lib": ["DOM", "es2020"]
}
}
diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json
index e37c661d3778..2dc2a2f2186d 100644
--- a/packages/solidstart/package.json
+++ b/packages/solidstart/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/solidstart",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Solid Start",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solidstart",
@@ -66,9 +66,9 @@
}
},
"dependencies": {
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
- "@sentry/solid": "10.14.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
+ "@sentry/solid": "10.15.0",
"@sentry/vite-plugin": "^4.1.0"
},
"devDependencies": {
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index b9f5b03c9c5c..4a2b89c34ee8 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/svelte",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Svelte",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/svelte",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "10.14.0",
- "@sentry/core": "10.14.0",
+ "@sentry/browser": "10.15.0",
+ "@sentry/core": "10.15.0",
"magic-string": "^0.30.0"
},
"peerDependencies": {
diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json
index 36891917c5cc..fd54f069790c 100644
--- a/packages/svelte/tsconfig.json
+++ b/packages/svelte/tsconfig.json
@@ -4,6 +4,6 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"]
+ "lib": ["DOM", "es2020"]
}
}
diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json
index f33163768057..9e2fbe3d0026 100644
--- a/packages/sveltekit/package.json
+++ b/packages/sveltekit/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/sveltekit",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for SvelteKit",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/sveltekit",
@@ -48,10 +48,10 @@
},
"dependencies": {
"@babel/parser": "7.26.9",
- "@sentry/cloudflare": "10.14.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
- "@sentry/svelte": "10.14.0",
+ "@sentry/cloudflare": "10.15.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
+ "@sentry/svelte": "10.15.0",
"@sentry/vite-plugin": "^4.1.0",
"magic-string": "0.30.7",
"recast": "0.23.11",
diff --git a/packages/sveltekit/test/server-common/serverRoute.test.ts b/packages/sveltekit/test/server-common/serverRoute.test.ts
index 2ec3ef2cf2d2..1edfb6da6ebd 100644
--- a/packages/sveltekit/test/server-common/serverRoute.test.ts
+++ b/packages/sveltekit/test/server-common/serverRoute.test.ts
@@ -31,7 +31,7 @@ describe('wrapServerRouteWithSentry', () => {
it('assigns the route id as name if available', () => {
const wrappedRouteHandler = wrapServerRouteWithSentry(originalRouteHandler);
- wrappedRouteHandler(getRequestEventMock() as RequestEvent);
+ wrappedRouteHandler(getRequestEventMock());
expect(startSpanSpy).toHaveBeenCalledWith(
{
@@ -81,7 +81,7 @@ describe('wrapServerRouteWithSentry', () => {
});
await expect(async () => {
- await wrappedRouteHandler(getRequestEventMock() as RequestEvent);
+ await wrappedRouteHandler(getRequestEventMock());
}).rejects.toThrowError('Server Route Error');
expect(captureExceptionSpy).toHaveBeenCalledWith(error, {
@@ -95,7 +95,7 @@ describe('wrapServerRouteWithSentry', () => {
});
await expect(async () => {
- await wrappedRouteHandler(getRequestEventMock() as RequestEvent);
+ await wrappedRouteHandler(getRequestEventMock());
}).rejects.toThrow();
expect(captureExceptionSpy).toHaveBeenCalledWith(
@@ -115,7 +115,7 @@ describe('wrapServerRouteWithSentry', () => {
});
await expect(async () => {
- await wrappedRouteHandler(getRequestEventMock() as RequestEvent);
+ await wrappedRouteHandler(getRequestEventMock());
}).rejects.toThrow();
expect(captureExceptionSpy).not.toHaveBeenCalled();
@@ -127,7 +127,7 @@ describe('wrapServerRouteWithSentry', () => {
});
await expect(async () => {
- await wrappedRouteHandler(getRequestEventMock() as RequestEvent);
+ await wrappedRouteHandler(getRequestEventMock());
}).rejects.toThrow();
expect(captureExceptionSpy).not.toHaveBeenCalled();
diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json
index 36b4f1e55146..7aecb67afcdf 100644
--- a/packages/tanstackstart-react/package.json
+++ b/packages/tanstackstart-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/tanstackstart-react",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for TanStack Start React",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/tanstackstart-react",
@@ -52,10 +52,10 @@
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
- "@sentry-internal/browser-utils": "10.14.0",
- "@sentry/core": "10.14.0",
- "@sentry/node": "10.14.0",
- "@sentry/react": "10.14.0"
+ "@sentry-internal/browser-utils": "10.15.0",
+ "@sentry/core": "10.15.0",
+ "@sentry/node": "10.15.0",
+ "@sentry/react": "10.15.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
diff --git a/packages/tanstackstart-react/tsconfig.json b/packages/tanstackstart-react/tsconfig.json
index 20cf507e5203..ff4cadba841a 100644
--- a/packages/tanstackstart-react/tsconfig.json
+++ b/packages/tanstackstart-react/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["es2018", "es2020.string"],
+ "lib": ["es2020"],
"module": "Node16"
}
}
diff --git a/packages/tanstackstart/package.json b/packages/tanstackstart/package.json
index 70d1bbe48d33..fff1c3c27661 100644
--- a/packages/tanstackstart/package.json
+++ b/packages/tanstackstart/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/tanstackstart",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Utilities for the Sentry TanStack Start SDKs",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/tanstackstart",
diff --git a/packages/tanstackstart/tsconfig.json b/packages/tanstackstart/tsconfig.json
index 20cf507e5203..ff4cadba841a 100644
--- a/packages/tanstackstart/tsconfig.json
+++ b/packages/tanstackstart/tsconfig.json
@@ -2,7 +2,7 @@
"extends": "../../tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["es2018", "es2020.string"],
+ "lib": ["es2020"],
"module": "Node16"
}
}
diff --git a/packages/types/package.json b/packages/types/package.json
index 2e63198d01a3..3b255cad4906 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/types",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Types for all Sentry JavaScript SDKs",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/types",
@@ -57,7 +57,7 @@
"yalc:publish": "yalc publish --push --sig"
},
"dependencies": {
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"volta": {
"extends": "../../package.json"
diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json
index 89a9b9e0e2fe..e712bc6f77a7 100644
--- a/packages/types/tsconfig.json
+++ b/packages/types/tsconfig.json
@@ -4,6 +4,6 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["es2018"]
+ "lib": ["es2020"]
}
}
diff --git a/packages/typescript/package.json b/packages/typescript/package.json
index ffce0a43a4e0..4eb309ad5eb1 100644
--- a/packages/typescript/package.json
+++ b/packages/typescript/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry-internal/typescript",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Typescript configuration used at Sentry",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/typescript",
diff --git a/packages/typescript/tsconfig.json b/packages/typescript/tsconfig.json
index 1f6d405fb00b..8dd1e93e2c6e 100644
--- a/packages/typescript/tsconfig.json
+++ b/packages/typescript/tsconfig.json
@@ -7,7 +7,7 @@
"importHelpers": true,
"inlineSources": true,
"isolatedModules": true,
- "lib": ["ES2018"],
+ "lib": ["es2020"],
"moduleResolution": "node",
"noErrorTruncation": true,
"noFallthroughCasesInSwitch": true,
@@ -18,7 +18,7 @@
"sourceMap": true,
"strict": true,
"strictBindCallApply": false,
- "target": "es2018",
+ "target": "es2020",
"noUncheckedIndexedAccess": true
}
}
diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json
index d262d5d479df..034e50082396 100644
--- a/packages/vercel-edge/package.json
+++ b/packages/vercel-edge/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/vercel-edge",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for the Vercel Edge Runtime",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vercel-edge",
@@ -41,14 +41,14 @@
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/resources": "^2.1.0",
- "@sentry/core": "10.14.0"
+ "@sentry/core": "10.15.0"
},
"devDependencies": {
"@edge-runtime/types": "3.0.1",
"@opentelemetry/core": "^2.1.0",
"@opentelemetry/sdk-trace-base": "^2.1.0",
"@opentelemetry/semantic-conventions": "^1.37.0",
- "@sentry/opentelemetry": "10.14.0"
+ "@sentry/opentelemetry": "10.15.0"
},
"scripts": {
"build": "run-p build:transpile build:types",
diff --git a/packages/vercel-edge/test/wintercg-fetch.test.ts b/packages/vercel-edge/test/wintercg-fetch.test.ts
index cb5506a71e05..a413cc7e93da 100644
--- a/packages/vercel-edge/test/wintercg-fetch.test.ts
+++ b/packages/vercel-edge/test/wintercg-fetch.test.ts
@@ -8,7 +8,7 @@ import { winterCGFetchIntegration } from '../src/integrations/wintercg-fetch';
class FakeClient extends VercelEdgeClient {
public getIntegrationByName(name: string): T | undefined {
- return name === 'WinterCGFetch' ? (winterCGFetchIntegration() as Integration as T) : undefined;
+ return name === 'WinterCGFetch' ? (winterCGFetchIntegration() as T) : undefined;
}
}
diff --git a/packages/vercel-edge/tsconfig.json b/packages/vercel-edge/tsconfig.json
index fc8caeb42e65..685e6c58c06b 100644
--- a/packages/vercel-edge/tsconfig.json
+++ b/packages/vercel-edge/tsconfig.json
@@ -7,7 +7,7 @@
// Note: using `dom` here is inaccurate for the vercel-edge runtime, but needed
// because @edge-runtime/types does not type things like fetch or RequestInit
// ref: https://github.com/vercel/edge-runtime/issues/506
- "lib": ["DOM", "ES2018"],
+ "lib": ["DOM", "es2020"],
"types": ["@edge-runtime/types"]
}
}
diff --git a/packages/vue/package.json b/packages/vue/package.json
index a74318e92b9a..b75bcca15e80 100644
--- a/packages/vue/package.json
+++ b/packages/vue/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/vue",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Official Sentry SDK for Vue.js",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vue",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "10.14.0",
- "@sentry/core": "10.14.0"
+ "@sentry/browser": "10.15.0",
+ "@sentry/core": "10.15.0"
},
"peerDependencies": {
"pinia": "2.x || 3.x",
diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json
index 36891917c5cc..fd54f069790c 100644
--- a/packages/vue/tsconfig.json
+++ b/packages/vue/tsconfig.json
@@ -4,6 +4,6 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"]
+ "lib": ["DOM", "es2020"]
}
}
diff --git a/packages/wasm/package.json b/packages/wasm/package.json
index 2133a44da38b..f5276e2c8dad 100644
--- a/packages/wasm/package.json
+++ b/packages/wasm/package.json
@@ -1,6 +1,6 @@
{
"name": "@sentry/wasm",
- "version": "10.14.0",
+ "version": "10.15.0",
"description": "Support for WASM.",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/wasm",
@@ -39,8 +39,8 @@
"access": "public"
},
"dependencies": {
- "@sentry/browser": "10.14.0",
- "@sentry/core": "10.14.0"
+ "@sentry/browser": "10.15.0",
+ "@sentry/core": "10.15.0"
},
"scripts": {
"build": "run-p build:transpile build:bundle build:types",
diff --git a/packages/wasm/tsconfig.json b/packages/wasm/tsconfig.json
index 36891917c5cc..fd54f069790c 100644
--- a/packages/wasm/tsconfig.json
+++ b/packages/wasm/tsconfig.json
@@ -4,6 +4,6 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["DOM", "ES2018"]
+ "lib": ["DOM", "es2020"]
}
}
diff --git a/yarn.lock b/yarn.lock
index 46e96703fc7e..299b1415d89a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2701,15 +2701,10 @@
resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250617.0.tgz#6b4397fcf01c7b8a547152761cc3bcd63e173a58"
integrity sha512-XWM/6sagDrO0CYDKhXhPjM23qusvIN1ju9ZEml6gOQs8tNOFnq6Cn6X9FAmnyapRFCGUSEC3HZYJAm7zwVKaMA==
-"@cloudflare/workers-types@4.20250620.0":
- version "4.20250620.0"
- resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20250620.0.tgz#a22e635a631212963b84e315191614b20c4ad317"
- integrity sha512-EVvRB/DJEm6jhdKg+A4Qm4y/ry1cIvylSgSO3/f/Bv161vldDRxaXM2YoQQWFhLOJOw0qtrHsKOD51KYxV1XCw==
-
-"@cloudflare/workers-types@^4.20250708.0":
- version "4.20250726.0"
- resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20250726.0.tgz#2bcd78bc5e26aa222d4a8f8cf9edb8f5f3427bb3"
- integrity sha512-NtM1yVBKJFX4LgSoZkVU0EDhWWvSb1vt6REO+uMYZRgx1HAfQz9GDN6bBB0B+fm2ZIxzt6FzlDbmrXpGJ2M/4Q==
+"@cloudflare/workers-types@4.20250922.0", "@cloudflare/workers-types@^4.20250922.0":
+ version "4.20250922.0"
+ resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20250922.0.tgz#a159fbf3bb785fa85b473ecfaa8c501525827885"
+ integrity sha512-BaqlKnVc0Xzqm9xt3TC4v0yB9EHy5vVqpiWz+DAsbEmdcpUbqdBschvI9502p6FgFbZElD7XcxTEeViXLsoO0A==
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
@@ -4596,6 +4591,11 @@
"@hapi/bourne" "^3.0.0"
"@hapi/hoek" "^11.0.2"
+"@hono/node-server@^1.19.4":
+ version "1.19.4"
+ resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.4.tgz#2721cda094f7c080ee985494ac3e074f16c503eb"
+ integrity sha512-AWKQZ/YkHUBSHeL/5Ld8FWgUs6wFf4TxGYxqp9wLZxRdFuHBpXmgOq+CuDoL4vllkZLzovCf5HBJnypiy3EtHA==
+
"@humanwhocodes/config-array@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9"
@@ -18604,6 +18604,11 @@ homedir-polyfill@^1.0.1:
dependencies:
parse-passwd "^1.0.0"
+hono@^4.9.8:
+ version "4.9.8"
+ resolved "https://registry.yarnpkg.com/hono/-/hono-4.9.8.tgz#1710981135ec775fe26fab5ea6535b403e92bcc3"
+ integrity sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg==
+
hookable@^5.5.3:
version "5.5.3"
resolved "https://registry.yarnpkg.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d"