diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bcbef8e06a8e..9ef18fe35116 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -994,6 +994,7 @@ jobs:
test-application:
[
'angular-17',
+ 'angular-18',
'cloudflare-astro',
'node-express',
'create-react-app',
@@ -1007,6 +1008,7 @@ jobs:
'node-express-esm-without-loader',
'nextjs-app-dir',
'nextjs-14',
+ 'nextjs-15',
'react-create-hash-router',
'react-router-6-use-routes',
'react-router-5',
diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml
index 2771e84ece6b..25004eee8438 100644
--- a/.github/workflows/canary.yml
+++ b/.github/workflows/canary.yml
@@ -83,6 +83,12 @@ jobs:
- test-application: 'nextjs-14'
build-command: 'test:build-latest'
label: 'nextjs-14 (latest)'
+ - test-application: 'nextjs-15'
+ build-command: 'test:build-canary'
+ label: 'nextjs-15 (canary)'
+ - test-application: 'nextjs-15'
+ build-command: 'test:build-latest'
+ label: 'nextjs-15 (latest)'
- test-application: 'react-create-hash-router'
build-command: 'test:build-canary'
label: 'react-create-hash-router (canary)'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4a883477367..4ebdb34852e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,23 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
+## 8.3.1
+
+### Important Changes
+
+- **feat(nextjs): Trace pageloads in App Router (#12157)**
+
+If you are using Next.js version `14.3.0-canary.64` or above, the Sentry Next.js SDK will now trace clientside pageloads
+with React Server Components. This means, that client-side errors like
+`Error: An error occurred in the Server Components render.`, which previously didn't give you much information on how
+that error was caused, can now be traced back to a specific error in a server component.
+
+### Other Changes
+
+- feat(angular): Add Support for Angular 18 (#12183)
+- feat(deps): Bump @opentelemetry/instrumentation-aws-lambda from 0.41.0 to 0.41.1 (#12078)
+- fix(metrics): Ensure string values are interpreted for metrics (#12165)
+
## 8.3.0
### Important Changes
diff --git a/dev-packages/browser-integration-tests/suites/metrics/init.js b/dev-packages/browser-integration-tests/suites/metrics/init.js
index 8a5032c05eef..97182a9af6e7 100644
--- a/dev-packages/browser-integration-tests/suites/metrics/init.js
+++ b/dev-packages/browser-integration-tests/suites/metrics/init.js
@@ -7,10 +7,11 @@ Sentry.init({
});
Sentry.metrics.increment('increment');
-Sentry.metrics.increment('increment');
+Sentry.metrics.increment('increment', 2);
+Sentry.metrics.increment('increment', '3');
Sentry.metrics.distribution('distribution', 42);
-Sentry.metrics.distribution('distribution', 45);
+Sentry.metrics.distribution('distribution', '45');
Sentry.metrics.gauge('gauge', 5);
-Sentry.metrics.gauge('gauge', 15);
+Sentry.metrics.gauge('gauge', '15');
Sentry.metrics.set('set', 'nope');
Sentry.metrics.set('set', 'another');
diff --git a/dev-packages/browser-integration-tests/suites/metrics/test.ts b/dev-packages/browser-integration-tests/suites/metrics/test.ts
index d73235d876c8..5c6ff8bb13a4 100644
--- a/dev-packages/browser-integration-tests/suites/metrics/test.ts
+++ b/dev-packages/browser-integration-tests/suites/metrics/test.ts
@@ -12,6 +12,6 @@ sentryTest('collects metrics', async ({ getLocalTestUrl, page }) => {
const normalisedStatsdString = statsdString.replace(/T\d+\n?/g, 'T000000');
expect(normalisedStatsdString).toEqual(
- 'increment@none:2|c|T000000distribution@none:42:45|d|T000000gauge@none:15:5:15:20:2|g|T000000set@none:3387254:3443787523|s|T000000',
+ 'increment@none:6|c|T000000distribution@none:42:45|d|T000000gauge@none:15:5:15:20:2|g|T000000set@none:3387254:3443787523|s|T000000',
);
});
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/.editorconfig b/dev-packages/e2e-tests/test-applications/angular-18/.editorconfig
new file mode 100644
index 000000000000..59d9a3a3e73f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/.editorconfig
@@ -0,0 +1,16 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.ts]
+quote_type = single
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/.gitignore b/dev-packages/e2e-tests/test-applications/angular-18/.gitignore
new file mode 100644
index 000000000000..0711527ef9d5
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/.gitignore
@@ -0,0 +1,42 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# Compiled output
+/dist
+/tmp
+/out-tsc
+/bazel-out
+
+# Node
+/node_modules
+npm-debug.log
+yarn-error.log
+
+# IDEs and editors
+.idea/
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# Visual Studio Code
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# Miscellaneous
+/.angular/cache
+.sass-cache/
+/connect.lock
+/coverage
+/libpeerconnection.log
+testem.log
+/typings
+
+# System files
+.DS_Store
+Thumbs.db
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/.npmrc b/dev-packages/e2e-tests/test-applications/angular-18/.npmrc
new file mode 100644
index 000000000000..070f80f05092
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/.npmrc
@@ -0,0 +1,2 @@
+@sentry:registry=http://127.0.0.1:4873
+@sentry-internal:registry=http://127.0.0.1:4873
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/README.md b/dev-packages/e2e-tests/test-applications/angular-18/README.md
new file mode 100644
index 000000000000..5a2f2e67c868
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/README.md
@@ -0,0 +1,3 @@
+# Angular 18
+
+E2E test app for Angular 18 and `@sentry/angular`.
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/angular.json b/dev-packages/e2e-tests/test-applications/angular-18/angular.json
new file mode 100644
index 000000000000..cb3c0b70cec6
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/angular.json
@@ -0,0 +1,95 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "angular-18": {
+ "projectType": "application",
+ "schematics": {},
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:application",
+ "options": {
+ "outputPath": "dist/angular-18",
+ "index": "src/index.html",
+ "browser": "src/main.ts",
+ "polyfills": [
+ "zone.js"
+ ],
+ "tsConfig": "tsconfig.app.json",
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kb",
+ "maximumError": "1mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "2kb",
+ "maximumError": "4kb"
+ }
+ ],
+ "outputHashing": "all"
+ },
+ "development": {
+ "optimization": false,
+ "extractLicenses": false,
+ "sourceMap": true
+ }
+ },
+ "defaultConfiguration": "production"
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "configurations": {
+ "production": {
+ "buildTarget": "angular-18:build:production"
+ },
+ "development": {
+ "buildTarget": "angular-18:build:development"
+ }
+ },
+ "defaultConfiguration": "development"
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "buildTarget": "angular-18:build"
+ }
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "polyfills": [
+ "zone.js",
+ "zone.js/testing"
+ ],
+ "tsConfig": "tsconfig.spec.json",
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": []
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/package.json b/dev-packages/e2e-tests/test-applications/angular-18/package.json
new file mode 100644
index 000000000000..1c2bf8b723cb
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/package.json
@@ -0,0 +1,51 @@
+{
+ "name": "angular-18",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "ng",
+ "dev": "ng serve",
+ "proxy": "node start-event-proxy.mjs",
+ "preview": "http-server dist/angular-18/browser --port 8080",
+ "build": "ng build",
+ "watch": "ng build --watch --configuration development",
+ "test": "playwright test",
+ "test:build": "pnpm install && npx playwright install && pnpm build",
+ "test:assert": "playwright test",
+ "clean": "npx rimraf .angular node_modules pnpm-lock.yaml dist"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^18.0.0",
+ "@angular/common": "^18.0.0",
+ "@angular/compiler": "^18.0.0",
+ "@angular/core": "^18.0.0",
+ "@angular/forms": "^18.0.0",
+ "@angular/platform-browser": "^18.0.0",
+ "@angular/platform-browser-dynamic": "^18.0.0",
+ "@angular/router": "^18.0.0",
+ "@sentry/angular": "* || latest",
+ "rxjs": "~7.8.0",
+ "tslib": "^2.3.0",
+ "zone.js": "~0.14.3"
+ },
+ "devDependencies": {
+ "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server",
+ "@angular-devkit/build-angular": "^18.0.0",
+ "@angular/cli": "^18.0.0",
+ "@angular/compiler-cli": "^18.0.0",
+ "@playwright/test": "^1.41.1",
+ "@types/jasmine": "~5.1.0",
+ "http-server": "^14.1.1",
+ "jasmine-core": "~5.1.0",
+ "karma": "~6.4.0",
+ "karma-chrome-launcher": "~3.2.0",
+ "karma-coverage": "~2.2.0",
+ "karma-jasmine": "~5.1.0",
+ "karma-jasmine-html-reporter": "~2.1.0",
+ "typescript": "~5.4.5",
+ "wait-port": "1.0.4"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts b/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts
new file mode 100644
index 000000000000..df7c2d9758e9
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/playwright.config.ts
@@ -0,0 +1,77 @@
+import type { PlaywrightTestConfig } from '@playwright/test';
+import { devices } from '@playwright/test';
+
+// Fix urls not resolving to localhost on Node v17+
+// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575
+import { setDefaultResultOrder } from 'dns';
+setDefaultResultOrder('ipv4first');
+
+const testEnv = process.env['TEST_ENV'] || 'production';
+
+if (!testEnv) {
+ throw new Error('No test env defined');
+}
+
+const angularPort = 8080;
+const eventProxyPort = 3031;
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+const config: PlaywrightTestConfig = {
+ testDir: './tests',
+ /* Maximum time one test can run for. */
+ timeout: 150_000,
+ expect: {
+ /**
+ * Maximum time expect() should wait for the condition to be met.
+ * For example in `await expect(locator).toHaveText();`
+ */
+ timeout: 10000,
+ },
+ fullyParallel: false,
+ workers: 1,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* `next dev` is incredibly buggy with the app dir */
+ retries: testEnv === 'development' ? 3 : 0,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'list',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
+ actionTimeout: 0,
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: `http://localhost:${angularPort}`,
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: {
+ ...devices['Desktop Chrome'],
+ },
+ },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ webServer: [
+ {
+ command: 'node start-event-proxy.mjs',
+ port: eventProxyPort,
+ },
+ {
+ command:
+ testEnv === 'development'
+ ? `pnpm wait-port ${eventProxyPort} && pnpm preview -p ${angularPort}`
+ : `pnpm wait-port ${eventProxyPort} && pnpm preview -p ${angularPort}`,
+ port: angularPort,
+ },
+ ],
+};
+
+export default config;
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.component.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.component.ts
new file mode 100644
index 000000000000..ab3efd7e16f3
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [RouterOutlet],
+ template: ``,
+})
+export class AppComponent {
+ title = 'angular-18';
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.config.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.config.ts
new file mode 100644
index 000000000000..8267759c8ba1
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.config.ts
@@ -0,0 +1,25 @@
+import { APP_INITIALIZER, ApplicationConfig, ErrorHandler } from '@angular/core';
+import { Router, provideRouter } from '@angular/router';
+
+import { TraceService, createErrorHandler } from '@sentry/angular';
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideRouter(routes),
+ {
+ provide: ErrorHandler,
+ useValue: createErrorHandler(),
+ },
+ {
+ provide: TraceService,
+ deps: [Router],
+ },
+ {
+ provide: APP_INITIALIZER,
+ useFactory: () => () => {},
+ deps: [TraceService],
+ multi: true,
+ },
+ ],
+};
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.routes.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.routes.ts
new file mode 100644
index 000000000000..24bf8b769051
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/app.routes.ts
@@ -0,0 +1,42 @@
+import { Routes } from '@angular/router';
+import { cancelGuard } from './cancel-guard.guard';
+import { CancelComponent } from './cancel/cancel.components';
+import { ComponentTrackingComponent } from './component-tracking/component-tracking.components';
+import { HomeComponent } from './home/home.component';
+import { UserComponent } from './user/user.component';
+
+export const routes: Routes = [
+ {
+ path: 'users/:id',
+ component: UserComponent,
+ },
+ {
+ path: 'home',
+ component: HomeComponent,
+ },
+ {
+ path: 'cancel',
+ component: CancelComponent,
+ canActivate: [cancelGuard],
+ },
+ {
+ path: 'component-tracking',
+ component: ComponentTrackingComponent,
+ },
+ {
+ path: 'redirect1',
+ redirectTo: '/redirect2',
+ },
+ {
+ path: 'redirect2',
+ redirectTo: '/redirect3',
+ },
+ {
+ path: 'redirect3',
+ redirectTo: '/users/456',
+ },
+ {
+ path: '**',
+ redirectTo: 'home',
+ },
+];
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel-guard.guard.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel-guard.guard.ts
new file mode 100644
index 000000000000..16ec4a2ab164
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel-guard.guard.ts
@@ -0,0 +1,5 @@
+import { ActivatedRouteSnapshot, CanActivateFn, RouterStateSnapshot } from '@angular/router';
+
+export const cancelGuard: CanActivateFn = (_next: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
+ return false;
+};
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel/cancel.components.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel/cancel.components.ts
new file mode 100644
index 000000000000..b6ee1876e035
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/cancel/cancel.components.ts
@@ -0,0 +1,8 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-cancel',
+ standalone: true,
+ template: `
`,
+})
+export class CancelComponent {}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/component-tracking/component-tracking.components.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/component-tracking/component-tracking.components.ts
new file mode 100644
index 000000000000..d437a1d43fdd
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/component-tracking/component-tracking.components.ts
@@ -0,0 +1,18 @@
+import { AfterViewInit, Component, OnInit } from '@angular/core';
+import { TraceClass, TraceMethod, TraceModule } from '@sentry/angular';
+import { SampleComponent } from '../sample-component/sample-component.components';
+
+@Component({
+ selector: 'app-cancel',
+ standalone: true,
+ imports: [TraceModule, SampleComponent],
+ template: ``,
+})
+@TraceClass({ name: 'ComponentTrackingComponent' })
+export class ComponentTrackingComponent implements OnInit, AfterViewInit {
+ @TraceMethod({ name: 'ngOnInit' })
+ ngOnInit() {}
+
+ @TraceMethod()
+ ngAfterViewInit() {}
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/home/home.component.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/home/home.component.ts
new file mode 100644
index 000000000000..9f36814d6c03
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/home/home.component.ts
@@ -0,0 +1,26 @@
+import { Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+
+@Component({
+ selector: 'app-home',
+ standalone: true,
+ imports: [RouterLink],
+ template: `
+
+ Welcome to Sentry's Angular 18 E2E test app
+
+
+
+`,
+})
+export class HomeComponent {
+ throwError() {
+ throw new Error('Error thrown from Angular 18 E2E test app');
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/sample-component/sample-component.components.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/sample-component/sample-component.components.ts
new file mode 100644
index 000000000000..da09425c7565
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/sample-component/sample-component.components.ts
@@ -0,0 +1,12 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'app-sample-component',
+ standalone: true,
+ template: `Component
`,
+})
+export class SampleComponent implements OnInit {
+ ngOnInit() {
+ console.log('SampleComponent');
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/app/user/user.component.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/app/user/user.component.ts
new file mode 100644
index 000000000000..db02568d395f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/app/user/user.component.ts
@@ -0,0 +1,25 @@
+import { AsyncPipe } from '@angular/common';
+import { Component } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Observable, map } from 'rxjs';
+
+@Component({
+ selector: 'app-user',
+ standalone: true,
+ imports: [AsyncPipe],
+ template: `
+ Hello User {{ userId$ | async }}
+
+ `,
+})
+export class UserComponent {
+ public userId$: Observable;
+
+ constructor(private route: ActivatedRoute) {
+ this.userId$ = this.route.paramMap.pipe(map(params => params.get('id') || 'UNKNOWN USER'));
+ }
+
+ throwError() {
+ throw new Error('Error thrown from user page');
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/favicon.ico b/dev-packages/e2e-tests/test-applications/angular-18/src/favicon.ico
new file mode 100644
index 000000000000..57614f9c9675
Binary files /dev/null and b/dev-packages/e2e-tests/test-applications/angular-18/src/favicon.ico differ
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/index.html b/dev-packages/e2e-tests/test-applications/angular-18/src/index.html
new file mode 100644
index 000000000000..075475aa383e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ Angular 18
+
+
+
+
+
+
+
+
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/main.ts b/dev-packages/e2e-tests/test-applications/angular-18/src/main.ts
new file mode 100644
index 000000000000..947f40691b05
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/main.ts
@@ -0,0 +1,15 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+import { appConfig } from './app/app.config';
+
+import * as Sentry from '@sentry/angular';
+
+Sentry.init({
+ dsn: 'https://3b6c388182fb435097f41d181be2b2ba@o4504321058471936.ingest.sentry.io/4504321066008576',
+ tracesSampleRate: 1.0,
+ integrations: [Sentry.browserTracingIntegration({})],
+ tunnel: `http://localhost:3031/`, // proxy server
+ debug: true,
+});
+
+bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err));
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/src/styles.css b/dev-packages/e2e-tests/test-applications/angular-18/src/styles.css
new file mode 100644
index 000000000000..90d4ee0072ce
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/src/styles.css
@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/angular-18/start-event-proxy.mjs
new file mode 100644
index 000000000000..696f1807d8f1
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/start-event-proxy.mjs
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from '@sentry-internal/event-proxy-server';
+
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'angular-18',
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/angular-18/tests/errors.test.ts
new file mode 100644
index 000000000000..a255d2130dda
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/tests/errors.test.ts
@@ -0,0 +1,65 @@
+import { expect, test } from '@playwright/test';
+import { waitForError, waitForTransaction } from '@sentry-internal/event-proxy-server';
+
+test('sends an error', async ({ page }) => {
+ const errorPromise = waitForError('angular-18', async errorEvent => {
+ return !errorEvent.type;
+ });
+
+ await page.goto(`/`);
+
+ await page.locator('#errorBtn').click();
+
+ const error = await errorPromise;
+
+ expect(error).toMatchObject({
+ exception: {
+ values: [
+ {
+ type: 'Error',
+ value: 'Error thrown from Angular 18 E2E test app',
+ mechanism: {
+ type: 'angular',
+ handled: false,
+ },
+ },
+ ],
+ },
+ transaction: '/home/',
+ });
+});
+
+test('assigns the correct transaction value after a navigation', async ({ page }) => {
+ const pageloadTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ const errorPromise = waitForError('angular-18', async errorEvent => {
+ return !errorEvent.type;
+ });
+
+ await page.goto(`/`);
+ await pageloadTxnPromise;
+
+ await page.waitForTimeout(5000);
+
+ await page.locator('#navLink').click();
+
+ const [_, error] = await Promise.all([page.locator('#userErrorBtn').click(), errorPromise]);
+
+ expect(error).toMatchObject({
+ exception: {
+ values: [
+ {
+ type: 'Error',
+ value: 'Error thrown from user page',
+ mechanism: {
+ type: 'angular',
+ handled: false,
+ },
+ },
+ ],
+ },
+ transaction: '/users/:id/',
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/angular-18/tests/performance.test.ts
new file mode 100644
index 000000000000..12f0fbd41133
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/tests/performance.test.ts
@@ -0,0 +1,313 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/event-proxy-server';
+import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
+
+test('sends a pageload transaction with a parameterized URL', async ({ page }) => {
+ const transactionPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ await page.goto(`/`);
+
+ const rootSpan = await transactionPromise;
+
+ expect(rootSpan).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'pageload',
+ origin: 'auto.pageload.angular',
+ },
+ },
+ transaction: '/home/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+});
+
+test('sends a navigation transaction with a parameterized URL', async ({ page }) => {
+ const pageloadTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+ await pageloadTxnPromise;
+
+ await page.waitForTimeout(5000);
+
+ const [_, navigationTxn] = await Promise.all([page.locator('#navLink').click(), navigationTxnPromise]);
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ },
+ },
+ transaction: '/users/:id/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+});
+
+test('sends a navigation transaction even if the pageload span is still active', async ({ page }) => {
+ const pageloadTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload';
+ });
+
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, pageloadTxn, navigationTxn] = await Promise.all([
+ page.locator('#navLink').click(),
+ pageloadTxnPromise,
+ navigationTxnPromise,
+ ]);
+
+ expect(pageloadTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'pageload',
+ origin: 'auto.pageload.angular',
+ },
+ },
+ transaction: '/home/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ origin: 'auto.navigation.angular',
+ },
+ },
+ transaction: '/users/:id/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+});
+
+test('groups redirects within one navigation root span', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#redirectLink').click(), navigationTxnPromise]);
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ origin: 'auto.navigation.angular',
+ },
+ },
+ transaction: '/users/:id/',
+ transaction_info: {
+ source: 'route',
+ },
+ });
+
+ const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+ expect(routingSpan).toBeDefined();
+ expect(routingSpan?.description).toBe('/redirect1');
+});
+
+test.describe('finish routing span', () => {
+ test('finishes routing span on navigation cancel', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#cancelLink').click(), navigationTxnPromise]);
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ origin: 'auto.navigation.angular',
+ },
+ },
+ transaction: '/cancel',
+ transaction_info: {
+ source: 'url',
+ },
+ });
+
+ const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+ expect(routingSpan).toBeDefined();
+ expect(routingSpan?.description).toBe('/cancel');
+ });
+
+ test('finishes routing span on navigation error', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#nonExistentLink').click(), navigationTxnPromise]);
+
+ const nonExistentRoute = '/non-existent';
+
+ expect(navigationTxn).toMatchObject({
+ contexts: {
+ trace: {
+ op: 'navigation',
+ origin: 'auto.navigation.angular',
+ },
+ },
+ transaction: nonExistentRoute,
+ transaction_info: {
+ source: 'url',
+ },
+ });
+
+ const routingSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.routing');
+
+ expect(routingSpan).toBeDefined();
+ expect(routingSpan?.description).toBe(nonExistentRoute);
+ });
+});
+
+test.describe('TraceDirective', () => {
+ test('creates a child tracingSpan with component name as span name on ngOnInit', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+ const traceDirectiveSpan = navigationTxn.spans?.find(
+ span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_directive',
+ );
+
+ expect(traceDirectiveSpan).toBeDefined();
+ expect(traceDirectiveSpan).toEqual(
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive',
+ },
+ description: '',
+ op: 'ui.angular.init',
+ origin: 'auto.ui.angular.trace_directive',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ );
+ });
+});
+
+test.describe('TraceClass Decorator', () => {
+ test('adds init span for decorated class', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+ const classDecoratorSpan = navigationTxn.spans?.find(
+ span => span?.data && span?.data[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.ui.angular.trace_class_decorator',
+ );
+
+ expect(classDecoratorSpan).toBeDefined();
+ expect(classDecoratorSpan).toEqual(
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.init',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_class_decorator',
+ },
+ description: '',
+ op: 'ui.angular.init',
+ origin: 'auto.ui.angular.trace_class_decorator',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ );
+ });
+});
+
+test.describe('TraceMethod Decorator', () => {
+ test('adds name to span description of decorated method `ngOnInit`', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+ const ngInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngOnInit');
+
+ expect(ngInitSpan).toBeDefined();
+ expect(ngInitSpan).toEqual(
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngOnInit',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
+ },
+ description: '',
+ op: 'ui.angular.ngOnInit',
+ origin: 'auto.ui.angular.trace_method_decorator',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ );
+ });
+
+ test('adds fallback name to span description of decorated method `ngAfterViewInit`', async ({ page }) => {
+ const navigationTxnPromise = waitForTransaction('angular-18', async transactionEvent => {
+ return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation';
+ });
+
+ await page.goto(`/`);
+
+ // immediately navigate to a different route
+ const [_, navigationTxn] = await Promise.all([page.locator('#componentTracking').click(), navigationTxnPromise]);
+
+ const ngAfterViewInitSpan = navigationTxn.spans?.find(span => span.op === 'ui.angular.ngAfterViewInit');
+
+ expect(ngAfterViewInitSpan).toBeDefined();
+ expect(ngAfterViewInitSpan).toEqual(
+ expect.objectContaining({
+ data: {
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.angular.ngAfterViewInit',
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator',
+ },
+ description: '',
+ op: 'ui.angular.ngAfterViewInit',
+ origin: 'auto.ui.angular.trace_method_decorator',
+ start_timestamp: expect.any(Number),
+ timestamp: expect.any(Number),
+ }),
+ );
+ });
+});
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.app.json b/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.app.json
new file mode 100644
index 000000000000..374cc9d294aa
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.app.json
@@ -0,0 +1,14 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "files": [
+ "src/main.ts"
+ ],
+ "include": [
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.json b/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.json
new file mode 100644
index 000000000000..f37b67ff0277
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/angular-18/tsconfig.json
@@ -0,0 +1,33 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "outDir": "./dist/out-tsc",
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "sourceMap": true,
+ "declaration": false,
+ "experimentalDecorators": true,
+ "moduleResolution": "node",
+ "importHelpers": true,
+ "target": "ES2022",
+ "module": "ES2022",
+ "useDefineForClassFields": false,
+ "lib": [
+ "ES2022",
+ "dom"
+ ]
+ },
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json
index c597d55a1f17..b5025e7b1232 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-14/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-14/package.json
@@ -8,7 +8,7 @@
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "TEST_ENV=development playwright test",
"test:build": "pnpm install && npx playwright install && pnpm build",
- "test:build-canary": "pnpm install && pnpm add next@canary && npx playwright install && pnpm build",
+ "test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build",
"test:build-latest": "pnpm install && pnpm add next@latest && npx playwright install && pnpm build",
"test:assert": "pnpm test:prod && pnpm test:dev"
},
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-15/.gitignore
new file mode 100644
index 000000000000..e799cc33c4e7
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/.gitignore
@@ -0,0 +1,45 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+!*.d.ts
+
+# Sentry
+.sentryclirc
+
+.vscode
+
+test-results
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/.npmrc b/dev-packages/e2e-tests/test-applications/nextjs-15/.npmrc
new file mode 100644
index 000000000000..070f80f05092
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/.npmrc
@@ -0,0 +1,2 @@
+@sentry:registry=http://127.0.0.1:4873
+@sentry-internal:registry=http://127.0.0.1:4873
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-15/app/layout.tsx
new file mode 100644
index 000000000000..c8f9cee0b787
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/app/layout.tsx
@@ -0,0 +1,7 @@
+export default function Layout({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/app/pageload-tracing/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-15/app/pageload-tracing/layout.tsx
new file mode 100644
index 000000000000..1f0cbe478f88
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/app/pageload-tracing/layout.tsx
@@ -0,0 +1,8 @@
+import { PropsWithChildren } from 'react';
+
+export const dynamic = 'force-dynamic';
+
+export default async function Layout({ children }: PropsWithChildren) {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ return <>{children}>;
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/app/pageload-tracing/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-15/app/pageload-tracing/page.tsx
new file mode 100644
index 000000000000..4d2763b992b5
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/app/pageload-tracing/page.tsx
@@ -0,0 +1,14 @@
+export const dynamic = 'force-dynamic';
+
+export default async function Page() {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ return I am page 2
;
+}
+
+export async function generateMetadata() {
+ (await fetch('http://example.com/')).text();
+
+ return {
+ title: 'my title',
+ };
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/globals.d.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/globals.d.ts
new file mode 100644
index 000000000000..109dbcd55648
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/globals.d.ts
@@ -0,0 +1,4 @@
+interface Window {
+ recordedTransactions?: string[];
+ capturedExceptionId?: string;
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/instrumentation.ts
new file mode 100644
index 000000000000..7b89a972e157
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/instrumentation.ts
@@ -0,0 +1,9 @@
+export async function register() {
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
+ await import('./sentry.server.config');
+ }
+
+ if (process.env.NEXT_RUNTIME === 'edge') {
+ await import('./sentry.edge.config');
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/next-env.d.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/next-env.d.ts
new file mode 100644
index 000000000000..4f11a03dc6cc
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/next.config.js b/dev-packages/e2e-tests/test-applications/nextjs-15/next.config.js
new file mode 100644
index 000000000000..1098c2ce5a4f
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/next.config.js
@@ -0,0 +1,8 @@
+const { withSentryConfig } = require('@sentry/nextjs');
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+module.exports = withSentryConfig(nextConfig, {
+ silent: true,
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json
new file mode 100644
index 000000000000..dc1d111ee393
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "create-next-app",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
+ "clean": "npx rimraf node_modules pnpm-lock.yaml",
+ "test:prod": "TEST_ENV=production playwright test",
+ "test:dev": "TEST_ENV=development playwright test",
+ "test:build": "pnpm install && npx playwright install && pnpm build",
+ "test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build",
+ "test:build-latest": "pnpm install && pnpm add next@latest && npx playwright install && pnpm build",
+ "test:assert": "pnpm test:prod && pnpm test:dev"
+ },
+ "dependencies": {
+ "@playwright/test": "^1.27.1",
+ "@sentry/nextjs": "latest || *",
+ "@types/node": "18.11.17",
+ "@types/react": "18.0.26",
+ "@types/react-dom": "18.0.9",
+ "next": "14.3.0-canary.73",
+ "react": "beta",
+ "react-dom": "beta",
+ "typescript": "4.9.5",
+ "wait-port": "1.0.4"
+ },
+ "devDependencies": {
+ "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server",
+ "@sentry-internal/feedback": "latest || *",
+ "@sentry-internal/replay-canvas": "latest || *",
+ "@sentry-internal/browser-utils": "latest || *",
+ "@sentry/browser": "latest || *",
+ "@sentry/core": "latest || *",
+ "@sentry/nextjs": "latest || *",
+ "@sentry/node": "latest || *",
+ "@sentry/opentelemetry": "latest || *",
+ "@sentry/react": "latest || *",
+ "@sentry-internal/replay": "latest || *",
+ "@sentry/types": "latest || *",
+ "@sentry/utils": "latest || *",
+ "@sentry/vercel-edge": "latest || *"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.ts
new file mode 100644
index 000000000000..0709f27158b4
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/playwright.config.ts
@@ -0,0 +1,80 @@
+import os from 'os';
+import type { PlaywrightTestConfig } from '@playwright/test';
+import { devices } from '@playwright/test';
+
+// Fix urls not resolving to localhost on Node v17+
+// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575
+import { setDefaultResultOrder } from 'dns';
+setDefaultResultOrder('ipv4first');
+
+const testEnv = process.env.TEST_ENV;
+
+if (!testEnv) {
+ throw new Error('No test env defined');
+}
+
+const nextPort = 3030;
+const eventProxyPort = 3031;
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+const config: PlaywrightTestConfig = {
+ testDir: './tests',
+ /* Maximum time one test can run for. */
+ timeout: 30_000,
+ expect: {
+ /**
+ * Maximum time expect() should wait for the condition to be met.
+ * For example in `await expect(locator).toHaveText();`
+ */
+ timeout: 10000,
+ },
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Defaults to half the number of CPUs. The tests are not really CPU-bound but rather I/O-bound with all the polling we do so we increase the concurrency to the CPU count. */
+ workers: os.cpus().length,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: !!process.env.CI,
+ /* `next dev` is incredibly buggy with the app dir */
+ retries: testEnv === 'development' ? 3 : 0,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: 'list',
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
+ actionTimeout: 0,
+ /* Base URL to use in actions like `await page.goto('/')`. */
+ baseURL: `http://localhost:${nextPort}`,
+ trace: 'retain-on-failure',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: {
+ ...devices['Desktop Chrome'],
+ },
+ },
+ ],
+
+ /* Run your local dev server before starting the tests */
+ webServer: [
+ {
+ command: 'node start-event-proxy.mjs',
+ port: eventProxyPort,
+ },
+ {
+ command:
+ testEnv === 'development'
+ ? `pnpm wait-port ${eventProxyPort} && pnpm next dev -p ${nextPort}`
+ : `pnpm wait-port ${eventProxyPort} && pnpm next start -p ${nextPort}`,
+ port: nextPort,
+ stdout: 'pipe',
+ stderr: 'pipe',
+ },
+ ],
+};
+
+export default config;
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.client.config.ts
new file mode 100644
index 000000000000..85bd765c9c44
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.client.config.ts
@@ -0,0 +1,9 @@
+import * as Sentry from '@sentry/nextjs';
+
+Sentry.init({
+ environment: 'qa', // dynamic sampling bias to keep transactions
+ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
+ tunnel: `http://localhost:3031/`, // proxy server
+ tracesSampleRate: 1.0,
+ sendDefaultPii: true,
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.edge.config.ts
new file mode 100644
index 000000000000..067d2ead0b8b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.edge.config.ts
@@ -0,0 +1,13 @@
+import * as Sentry from '@sentry/nextjs';
+
+Sentry.init({
+ environment: 'qa', // dynamic sampling bias to keep transactions
+ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
+ tunnel: `http://localhost:3031/`, // proxy server
+ tracesSampleRate: 1.0,
+ sendDefaultPii: true,
+ transportOptions: {
+ // We are doing a lot of events at once in this test
+ bufferSize: 1000,
+ },
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.server.config.ts
new file mode 100644
index 000000000000..067d2ead0b8b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.server.config.ts
@@ -0,0 +1,13 @@
+import * as Sentry from '@sentry/nextjs';
+
+Sentry.init({
+ environment: 'qa', // dynamic sampling bias to keep transactions
+ dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
+ tunnel: `http://localhost:3031/`, // proxy server
+ tracesSampleRate: 1.0,
+ sendDefaultPii: true,
+ transportOptions: {
+ // We are doing a lot of events at once in this test
+ bufferSize: 1000,
+ },
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-15/start-event-proxy.mjs
new file mode 100644
index 000000000000..56744b35c7e6
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/start-event-proxy.mjs
@@ -0,0 +1,6 @@
+import { startEventProxyServer } from '@sentry-internal/event-proxy-server';
+
+startEventProxyServer({
+ port: 3031,
+ proxyServerName: 'nextjs-15',
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts
new file mode 100644
index 000000000000..7893633d3b48
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/pageload-tracing.test.ts
@@ -0,0 +1,37 @@
+import { expect, test } from '@playwright/test';
+import { waitForTransaction } from '@sentry-internal/event-proxy-server';
+
+test('all server component transactions should be attached to the pageload request span', async ({ page }) => {
+ const pageServerComponentTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
+ return transactionEvent?.transaction === 'Page Server Component (/pageload-tracing)';
+ });
+
+ const layoutServerComponentTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
+ return transactionEvent?.transaction === 'Layout Server Component (/pageload-tracing)';
+ });
+
+ const metadataTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
+ return transactionEvent?.transaction === 'Page.generateMetadata (/pageload-tracing)';
+ });
+
+ const pageloadTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
+ return transactionEvent?.transaction === '/pageload-tracing';
+ });
+
+ await page.goto(`/pageload-tracing`);
+
+ const [pageServerComponentTransaction, layoutServerComponentTransaction, metadataTransaction, pageloadTransaction] =
+ await Promise.all([
+ pageServerComponentTransactionPromise,
+ layoutServerComponentTransactionPromise,
+ metadataTransactionPromise,
+ pageloadTransactionPromise,
+ ]);
+
+ const pageloadTraceId = pageloadTransaction.contexts?.trace?.trace_id;
+
+ expect(pageloadTraceId).toBeTruthy();
+ expect(pageServerComponentTransaction.contexts?.trace?.trace_id).toBe(pageloadTraceId);
+ expect(layoutServerComponentTransaction.contexts?.trace?.trace_id).toBe(pageloadTraceId);
+ expect(metadataTransaction.contexts?.trace?.trace_id).toBe(pageloadTraceId);
+});
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-15/tsconfig.json
new file mode 100644
index 000000000000..ef9e351d7a7b
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "es2018",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "incremental": true
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js", ".next/types/**/*.ts"],
+ "exclude": ["node_modules", "playwright.config.ts"]
+}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json
index c9f87f9cce33..925ee97e79f7 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json
+++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/package.json
@@ -9,7 +9,7 @@
"test:dev": "TEST_ENV=development playwright test",
"test:build": "pnpm install && npx playwright install && pnpm build",
"test:test-build": "pnpm ts-node --script-mode assert-build.ts",
- "test:build-canary": "pnpm install && pnpm add next@canary && npx playwright install && pnpm build",
+ "test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build",
"test:build-latest": "pnpm install && pnpm add next@latest && npx playwright install && pnpm build",
"test:build-13": "pnpm install && pnpm add next@13.4.19 && npx playwright install && pnpm build",
"test:assert": "pnpm test:test-build && pnpm test:prod && pnpm test:dev"
diff --git a/packages/angular/package.json b/packages/angular/package.json
index 5984a62c62f3..d0a363fc8d45 100644
--- a/packages/angular/package.json
+++ b/packages/angular/package.json
@@ -15,9 +15,9 @@
"access": "public"
},
"peerDependencies": {
- "@angular/common": ">= 14.x <= 17.x",
- "@angular/core": ">= 14.x <= 17.x",
- "@angular/router": ">= 14.x <= 17.x",
+ "@angular/common": ">= 14.x <= 18.x",
+ "@angular/core": ">= 14.x <= 18.x",
+ "@angular/router": ">= 14.x <= 18.x",
"rxjs": "^6.5.5 || ^7.x"
},
"dependencies": {
diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json
index eea668a450ae..f979ac407ec5 100644
--- a/packages/aws-serverless/package.json
+++ b/packages/aws-serverless/package.json
@@ -63,7 +63,7 @@
"access": "public"
},
"dependencies": {
- "@opentelemetry/instrumentation-aws-lambda": "0.41.0",
+ "@opentelemetry/instrumentation-aws-lambda": "0.41.1",
"@opentelemetry/instrumentation-aws-sdk": "0.41.0",
"@sentry/core": "8.3.0",
"@sentry/node": "8.3.0",
diff --git a/packages/browser-utils/src/getNativeImplementation.ts b/packages/browser-utils/src/getNativeImplementation.ts
index e06fb69f561e..6da58bfba623 100644
--- a/packages/browser-utils/src/getNativeImplementation.ts
+++ b/packages/browser-utils/src/getNativeImplementation.ts
@@ -1,4 +1,4 @@
-import { logger } from '@sentry/utils';
+import { isNativeFunction, logger } from '@sentry/utils';
import { DEBUG_BUILD } from './debug-build';
import { WINDOW } from './types';
@@ -15,14 +15,6 @@ interface CacheableImplementations {
const cachedImplementations: Partial = {};
-/**
- * isNative checks if the given function is a native implementation
- */
-// eslint-disable-next-line @typescript-eslint/ban-types
-function isNative(func: Function): boolean {
- return func && /^function\s+\w+\(\)\s+\{\s+\[native code\]\s+\}$/.test(func.toString());
-}
-
/**
* Get the native implementation of a browser function.
*
@@ -43,7 +35,7 @@ export function getNativeImplementation a
const { requestAsyncStorage, componentRoute, componentType, generationFunctionIdentifier } = context;
return new Proxy(generationFunction, {
apply: (originalFunction, thisArg, args) => {
+ const requestTraceId = getActiveSpan()?.spanContext().traceId;
return escapeNextjsTracing(() => {
let headers: WebFetchHeaders | undefined = undefined;
// We try-catch here just in case anything goes wrong with the async storage here goes wrong since it is Next.js internal API
@@ -50,23 +52,30 @@ export function wrapGenerationFunctionWithSentry a
data = { params, searchParams };
}
- const incomingPropagationContext = propagationContextFromHeaders(
- headers?.get('sentry-trace') ?? undefined,
- headers?.get('baggage'),
- );
+ const headersDict = headers ? winterCGHeadersToDict(headers) : undefined;
const isolationScope = commonObjectToIsolationScope(headers);
- const propagationContext = commonObjectToPropagationContext(headers, incomingPropagationContext);
return withIsolationScope(isolationScope, () => {
return withScope(scope => {
scope.setTransactionName(`${componentType}.${generationFunctionIdentifier} (${componentRoute})`);
+
isolationScope.setSDKProcessingMetadata({
request: {
- headers: headers ? winterCGHeadersToDict(headers) : undefined,
+ headers: headersDict,
},
});
+ const propagationContext = commonObjectToPropagationContext(
+ headers,
+ headersDict?.['sentry-trace']
+ ? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
+ : {
+ traceId: requestTraceId || uuid4(),
+ spanId: uuid4().substring(16),
+ },
+ );
+
scope.setExtra('route_data', data);
scope.setPropagationContext(propagationContext);
diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts
index 1234ea448a3d..fe185679528d 100644
--- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts
+++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts
@@ -3,12 +3,13 @@ import {
SPAN_STATUS_ERROR,
SPAN_STATUS_OK,
captureException,
+ getActiveSpan,
handleCallbackErrors,
startSpanManual,
withIsolationScope,
withScope,
} from '@sentry/core';
-import { propagationContextFromHeaders, winterCGHeadersToDict } from '@sentry/utils';
+import { propagationContextFromHeaders, uuid4, winterCGHeadersToDict } from '@sentry/utils';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils';
@@ -34,30 +35,32 @@ export function wrapServerComponentWithSentry any>
// hook. 🤯
return new Proxy(appDirComponent, {
apply: (originalFunction, thisArg, args) => {
+ const requestTraceId = getActiveSpan()?.spanContext().traceId;
return escapeNextjsTracing(() => {
const isolationScope = commonObjectToIsolationScope(context.headers);
- const completeHeadersDict: Record = context.headers
- ? winterCGHeadersToDict(context.headers)
- : {};
+ const headersDict = context.headers ? winterCGHeadersToDict(context.headers) : undefined;
isolationScope.setSDKProcessingMetadata({
request: {
- headers: completeHeadersDict,
+ headers: headersDict,
},
});
- const incomingPropagationContext = propagationContextFromHeaders(
- completeHeadersDict['sentry-trace'],
- completeHeadersDict['baggage'],
- );
-
- const propagationContext = commonObjectToPropagationContext(context.headers, incomingPropagationContext);
-
return withIsolationScope(isolationScope, () => {
return withScope(scope => {
scope.setTransactionName(`${componentType} Server Component (${componentRoute})`);
+ const propagationContext = commonObjectToPropagationContext(
+ context.headers,
+ headersDict?.['sentry-trace']
+ ? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
+ : {
+ traceId: requestTraceId || uuid4(),
+ spanId: uuid4().substring(16),
+ },
+ );
+
scope.setPropagationContext(propagationContext);
return startSpanManual(
{
diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts
index f748c5beb115..57601279997c 100644
--- a/packages/nextjs/src/config/types.ts
+++ b/packages/nextjs/src/config/types.ts
@@ -46,6 +46,7 @@ export type NextConfigObject = {
// Next.js experimental options
experimental?: {
instrumentationHook?: boolean;
+ clientTraceMetadata?: string[];
};
};
diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts
index f9c815fe6efb..32a4a885f9d7 100644
--- a/packages/nextjs/src/config/withSentryConfig.ts
+++ b/packages/nextjs/src/config/withSentryConfig.ts
@@ -1,5 +1,7 @@
-import { isThenable } from '@sentry/utils';
+import { isThenable, parseSemver } from '@sentry/utils';
+import * as fs from 'fs';
+import { sync as resolveSync } from 'resolve';
import type {
ExportedNextConfig as NextConfig,
NextConfigFunction,
@@ -82,6 +84,24 @@ function getFinalConfigObject(
...incomingUserNextConfigObject.experimental,
};
+ // Add the `clientTraceMetadata` experimental option based on Next.js version. The option got introduced in Next.js version 15.0.0 (actually 14.3.0-canary.64).
+ // Adding the option on lower versions will cause Next.js to print nasty warnings we wouldn't confront our users with.
+ const nextJsVersion = getNextjsVersion();
+ if (nextJsVersion) {
+ const { major, minor } = parseSemver(nextJsVersion);
+ if (major && minor && (major >= 15 || (major === 14 && minor >= 3))) {
+ incomingUserNextConfigObject.experimental = {
+ clientTraceMetadata: ['baggage', 'sentry-trace'],
+ ...incomingUserNextConfigObject.experimental,
+ };
+ }
+ } else {
+ // eslint-disable-next-line no-console
+ console.log(
+ "[@sentry/nextjs] The Sentry SDK was not able to determine your Next.js version. If you are using Next.js 15 or greater, please add `experimental.clientTraceMetadata: ['sentry-trace', 'baggage']` to your Next.js config to enable pageload tracing for App Router.",
+ );
+ }
+
return {
...incomingUserNextConfigObject,
webpack: constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions),
@@ -163,3 +183,27 @@ function setUpTunnelRewriteRules(userNextConfig: NextConfigObject, tunnelPath: s
}
};
}
+
+function getNextjsVersion(): string | undefined {
+ const nextjsPackageJsonPath = resolveNextjsPackageJson();
+ if (nextjsPackageJsonPath) {
+ try {
+ const nextjsPackageJson: { version: string } = JSON.parse(
+ fs.readFileSync(nextjsPackageJsonPath, { encoding: 'utf-8' }),
+ );
+ return nextjsPackageJson.version;
+ } catch {
+ // noop
+ }
+ }
+
+ return undefined;
+}
+
+function resolveNextjsPackageJson(): string | undefined {
+ try {
+ return resolveSync('next/package.json', { basedir: process.cwd() });
+ } catch {
+ return undefined;
+ }
+}
diff --git a/packages/opentelemetry/src/contextManager.ts b/packages/opentelemetry/src/contextManager.ts
index 43568fb34398..46561e3242fd 100644
--- a/packages/opentelemetry/src/contextManager.ts
+++ b/packages/opentelemetry/src/contextManager.ts
@@ -11,7 +11,7 @@ import { getScopesFromContext, setContextOnScope, setScopesOnContext } from './u
import { setIsSetup } from './utils/setupCheck';
/**
- * Wrap an OpenTelemetry ContextManager in a way that ensures the context is kept in sync with the Sentry Hub.
+ * Wrap an OpenTelemetry ContextManager in a way that ensures the context is kept in sync with the Sentry Scope.
*
* Usage:
* import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
@@ -23,7 +23,7 @@ export function wrapContextManagerClass ReturnType>(
context: Context,
diff --git a/packages/utils/src/supports.ts b/packages/utils/src/supports.ts
index 2931e9b72c4c..d1ae4b5b96d4 100644
--- a/packages/utils/src/supports.ts
+++ b/packages/utils/src/supports.ts
@@ -76,12 +76,13 @@ export function supportsFetch(): boolean {
return false;
}
}
+
/**
- * isNativeFetch checks if the given function is a native implementation of fetch()
+ * isNative checks if the given function is a native implementation
*/
// eslint-disable-next-line @typescript-eslint/ban-types
-export function isNativeFetch(func: Function): boolean {
- return func && /^function fetch\(\)\s+\{\s+\[native code\]\s+\}$/.test(func.toString());
+export function isNativeFunction(func: Function): boolean {
+ return func && /^function\s+\w+\(\)\s+\{\s+\[native code\]\s+\}$/.test(func.toString());
}
/**
@@ -101,7 +102,7 @@ export function supportsNativeFetch(): boolean {
// Fast path to avoid DOM I/O
// eslint-disable-next-line @typescript-eslint/unbound-method
- if (isNativeFetch(WINDOW.fetch)) {
+ if (isNativeFunction(WINDOW.fetch)) {
return true;
}
@@ -117,7 +118,7 @@ export function supportsNativeFetch(): boolean {
doc.head.appendChild(sandbox);
if (sandbox.contentWindow && sandbox.contentWindow.fetch) {
// eslint-disable-next-line @typescript-eslint/unbound-method
- result = isNativeFetch(sandbox.contentWindow.fetch);
+ result = isNativeFunction(sandbox.contentWindow.fetch);
}
doc.head.removeChild(sandbox);
} catch (err) {
diff --git a/yarn.lock b/yarn.lock
index 9486c769c2c7..176a6abb92d7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6177,15 +6177,15 @@
"@opentelemetry/context-base" "^0.12.0"
semver "^7.1.3"
-"@opentelemetry/instrumentation-aws-lambda@0.41.0":
- version "0.41.0"
- resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.41.0.tgz#91d50792dc77087f1f81c5c3882c237f5734aae8"
- integrity sha512-TeK7ZGtmEDqkfuwyAvlexnG11e7kEux0PncShqdyst2h1k1nVKmwnY/woPCUcTyU08PX6fa9YEyJ9E+G6wZacQ==
+"@opentelemetry/instrumentation-aws-lambda@0.41.1":
+ version "0.41.1"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-lambda/-/instrumentation-aws-lambda-0.41.1.tgz#dc40aefa4e697be5cf411be6f71c16ba4121beda"
+ integrity sha512-/BLG+0DQr2tCILFGJKJH2Fg6eyjhqOlVflYpNddUEXnzyQ/PAhTdgirkqbICFgeSW2XYcEY9zXpuRldrVNw9cA==
dependencies:
"@opentelemetry/instrumentation" "^0.51.0"
"@opentelemetry/propagator-aws-xray" "^1.3.1"
"@opentelemetry/resources" "^1.8.0"
- "@opentelemetry/semantic-conventions" "^1.0.0"
+ "@opentelemetry/semantic-conventions" "^1.22.0"
"@types/aws-lambda" "8.10.122"
"@opentelemetry/instrumentation-aws-sdk@0.41.0":
@@ -6367,7 +6367,7 @@
resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz#906ac8e4d804d4109f3ebd5c224ac988276fdc47"
integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==
-"@opentelemetry/resources@1.23.0", "@opentelemetry/resources@^1.23.0", "@opentelemetry/resources@^1.8.0":
+"@opentelemetry/resources@1.23.0":
version "1.23.0"
resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.23.0.tgz#4c71430f3e20c4d88b67ef5629759fae108485e5"
integrity sha512-iPRLfVfcEQynYGo7e4Di+ti+YQTAY0h5mQEUJcHlU9JOqpb4x965O6PZ+wMcwYVY63G96KtdS86YCM1BF1vQZg==
@@ -6375,7 +6375,7 @@
"@opentelemetry/core" "1.23.0"
"@opentelemetry/semantic-conventions" "1.23.0"
-"@opentelemetry/resources@1.24.1":
+"@opentelemetry/resources@1.24.1", "@opentelemetry/resources@^1.23.0", "@opentelemetry/resources@^1.8.0":
version "1.24.1"
resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.24.1.tgz#5e2cb84814824f3b1e1017e6caeeee8402e0ad6e"
integrity sha512-cyv0MwAaPF7O86x5hk3NNgenMObeejZFLJJDVuSeSMIsknlsj3oOZzRv3qSzlwYomXsICfBeFFlxwHQte5mGXQ==
@@ -7819,16 +7819,11 @@
resolved "https://registry.yarnpkg.com/@types/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#5433a141730f8e1d7a8e7486458ceb8144ee5edc"
integrity sha512-JOvNJUU/zjfJWcA1aHDnCKHwQjZ7VQ3UNfbcMKXrkQKKyMkJHrQ9vpSVMhgsztrtsbIRJKazMDvg2QggFVwJqw==
-"@types/aws-lambda@8.10.122":
+"@types/aws-lambda@8.10.122", "@types/aws-lambda@^8.10.62":
version "8.10.122"
resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.122.tgz#206c8d71b09325d26a458dba27db842afdc54df1"
integrity sha512-vBkIh9AY22kVOCEKo5CJlyCgmSWvasC+SWUxL/x/vOwRobMpI/HG1xp/Ae3AqmSiZeLUbOhW0FCD3ZjqqUxmXw==
-"@types/aws-lambda@^8.10.62":
- version "8.10.73"
- resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.73.tgz#77773c9accb2cec26fcb7c6b510a555805604a53"
- integrity sha512-P+a6TRQbRnVQOIjWkmw6F23wiJcF+4Uniasbzx7NAXjLQCVGx/Z4VoMfit81/pxlmcXNxAMGuYPugn6CrJLilQ==
-
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
version "7.1.19"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460"