From 5faa33a8301ad41c7775f6c2ed8a5739a0f43dd5 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 30 Apr 2024 10:18:26 +0200 Subject: [PATCH 01/26] test(browser): Add test for captureConsole (#11831) Adding tests for https://github.com/getsentry/sentry-javascript/issues/11829. backport of https://github.com/getsentry/sentry-javascript/pull/11830 --- .../integrations/captureConsole/init.js | 10 ++ .../integrations/captureConsole/subject.js | 8 ++ .../integrations/captureConsole/test.ts | 117 ++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 dev-packages/browser-integration-tests/suites/integrations/captureConsole/init.js create mode 100644 dev-packages/browser-integration-tests/suites/integrations/captureConsole/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/integrations/captureConsole/test.ts diff --git a/dev-packages/browser-integration-tests/suites/integrations/captureConsole/init.js b/dev-packages/browser-integration-tests/suites/integrations/captureConsole/init.js new file mode 100644 index 000000000000..5d73c7da769c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/captureConsole/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; +import { captureConsoleIntegration } from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [captureConsoleIntegration()], + autoSessionTracking: false, +}); diff --git a/dev-packages/browser-integration-tests/suites/integrations/captureConsole/subject.js b/dev-packages/browser-integration-tests/suites/integrations/captureConsole/subject.js new file mode 100644 index 000000000000..54f94f5ca4b3 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/captureConsole/subject.js @@ -0,0 +1,8 @@ +console.log('console log'); +console.warn('console warn'); +console.error('console error'); +console.info('console info'); +console.trace('console trace'); + +console.error(new Error('console error with error object')); +console.trace(new Error('console trace with error object')); diff --git a/dev-packages/browser-integration-tests/suites/integrations/captureConsole/test.ts b/dev-packages/browser-integration-tests/suites/integrations/captureConsole/test.ts new file mode 100644 index 000000000000..c704725822e7 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/integrations/captureConsole/test.ts @@ -0,0 +1,117 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../utils/fixtures'; +import { getMultipleSentryEnvelopeRequests } from '../../../utils/helpers'; + +sentryTest('it captures console messages correctly', async ({ getLocalTestUrl, page }) => { + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const [, events] = await Promise.all([page.goto(url), getMultipleSentryEnvelopeRequests(page, 7)]); + + expect(events).toHaveLength(7); + + const logEvent = events.find(event => event.message === 'console log'); + const warnEvent = events.find(event => event.message === 'console warn'); + const infoEvent = events.find(event => event.message === 'console info'); + const errorEvent = events.find(event => event.message === 'console error'); + const traceEvent = events.find(event => event.message === 'console trace'); + const errorWithErrorEvent = events.find( + event => event.exception && event.exception.values?.[0].value === 'console error with error object', + ); + const traceWithErrorEvent = events.find( + event => event.exception && event.exception.values?.[0].value === 'console trace with error object', + ); + + expect(logEvent).toEqual( + expect.objectContaining({ + level: 'log', + logger: 'console', + extra: { + arguments: ['console log'], + }, + }), + ); + expect(logEvent?.exception).toBeUndefined(); + expect(warnEvent).toEqual( + expect.objectContaining({ + level: 'warning', + logger: 'console', + extra: { + arguments: ['console warn'], + }, + }), + ); + expect(warnEvent?.exception).toBeUndefined(); + expect(infoEvent).toEqual( + expect.objectContaining({ + level: 'info', + logger: 'console', + extra: { + arguments: ['console info'], + }, + }), + ); + expect(infoEvent?.exception).toBeUndefined(); + expect(errorEvent).toEqual( + expect.objectContaining({ + level: 'error', + logger: 'console', + extra: { + arguments: ['console error'], + }, + }), + ); + expect(errorEvent?.exception).toBeUndefined(); + expect(traceEvent).toEqual( + expect.objectContaining({ + level: 'log', + logger: 'console', + extra: { + arguments: ['console trace'], + }, + }), + ); + expect(traceEvent?.exception).toBeUndefined(); + expect(errorWithErrorEvent).toEqual( + expect.objectContaining({ + level: 'error', + logger: 'console', + extra: { + arguments: [ + { + message: 'console error with error object', + name: 'Error', + stack: expect.any(String), + }, + ], + }, + exception: expect.any(Object), + }), + ); + expect(errorWithErrorEvent?.exception?.values?.[0].value).toBe('console error with error object'); + expect(traceWithErrorEvent).toEqual( + expect.objectContaining({ + level: 'log', + logger: 'console', + extra: { + arguments: [ + { + message: 'console trace with error object', + name: 'Error', + stack: expect.any(String), + }, + ], + }, + }), + ); + expect(traceWithErrorEvent?.exception?.values?.[0].value).toBe('console trace with error object'); +}); From 064c5ae052f3cad90e0004d1f26308a4df2b26c6 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 30 Apr 2024 14:04:56 +0200 Subject: [PATCH 02/26] feat(ember): Update ember dependencies (#11753) This updates the versions of some dependencies for the Ember SDK: * `ember-auto-import` is bumped to `^2.4.3` * `ember-cli-babel` is bumped to `^8.2.0` * `ember-cli-typescript` is bumped to `^5.3.0` Closes https://github.com/getsentry/sentry-javascript/issues/11730 --- MIGRATION.md | 9 + packages/ember/package.json | 16 +- .../tests/dummy/app/controllers/index.ts | 5 +- .../dummy/app/initializers/deprecation.ts | 14 - .../unit/instrument-route-performance-test.ts | 4 +- yarn.lock | 1016 +++++++++++++++-- 6 files changed, 912 insertions(+), 152 deletions(-) delete mode 100644 packages/ember/tests/dummy/app/initializers/deprecation.ts diff --git a/MIGRATION.md b/MIGRATION.md index 25227a752e77..082dede43593 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -959,12 +959,21 @@ replacement API. Removed top-level exports: `InitSentryForEmber`, `StartTransactionFunction` - [Removal of `InitSentryForEmber` export](./MIGRATION.md#removal-of-initsentryforember-export) +- [Updated Ember Dependencies](./MIGRATION.md#updated-ember-dependencies) #### Removal of `InitSentryForEmber` export The `InitSentryForEmber` export has been removed. Instead, you should use the `Sentry.init` method to initialize the SDK. +#### Updated Ember Dependencies + +The following dependencies that the SDK uses have been bumped to a more recent version: + +- `ember-auto-import` is bumped to `^2.4.3` +- `ember-cli-babel` is bumped to `^8.2.0` +- `ember-cli-typescript` is bumped to `^5.3.0` + ### Svelte SDK Removed top-level exports: `componentTrackingPreprocessor` diff --git a/packages/ember/package.json b/packages/ember/package.json index 33bd40f846aa..3f1e8dadf437 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -25,26 +25,27 @@ "lint:ts": "tsc", "fix": "eslint . --format stylish --fix", "start": "ember serve", - "test": "ember b --prod && ember test", + "test": "ember b --prod && yarn ember test", "test:all": "ember try:each", "prepack": "ember ts:precompile", "postpack": "ember ts:clean" }, "dependencies": { - "@embroider/macros": "^1.9.0", + "@babel/core": "^7.24.4", + "@embroider/macros": "^1.16.0", "@sentry/browser": "8.0.0-beta.5", "@sentry/core": "8.0.0-beta.5", "@sentry/types": "8.0.0-beta.5", "@sentry/utils": "8.0.0-beta.5", - "ember-auto-import": "^1.12.1 || ^2.4.3", - "ember-cli-babel": "^7.26.11", + "ember-auto-import": "^2.7.2", + "ember-cli-babel": "^8.2.0", "ember-cli-htmlbars": "^6.1.1", - "ember-cli-typescript": "^5.1.1" + "ember-cli-typescript": "^5.3.0" }, "devDependencies": { "@ember/optional-features": "~1.3.0", "@ember/test-helpers": "2.9.4", - "@embroider/test-setup": "~1.8.3", + "@embroider/test-setup": "~4.0.0", "@glimmer/component": "~1.1.2", "@glimmer/tracking": "~1.1.2", "@types/ember": "~3.16.5", @@ -62,15 +63,14 @@ "ember-cli-terser": "~4.0.2", "ember-cli-typescript-blueprints": "~3.0.0", "ember-disable-prototype-extensions": "~1.1.3", + "ember-maybe-import-regenerator": "1.0.0", "ember-load-initializers": "~2.1.1", - "ember-maybe-import-regenerator": "~1.0.0", "ember-qunit": "~6.0.0", "ember-resolver": "11.0.0", "ember-sinon-qunit": "7.1.4", "ember-source": "~4.8.0", "ember-source-channel-url": "~2.0.1", "ember-template-lint": "~4.16.1", - "ember-test-selectors": "~6.0.0", "ember-try": "~2.0.0", "ember-window-mock": "~0.8.1", "eslint-plugin-ember": "11.9.0", diff --git a/packages/ember/tests/dummy/app/controllers/index.ts b/packages/ember/tests/dummy/app/controllers/index.ts index bd28b635e1a1..c49ff8d94147 100644 --- a/packages/ember/tests/dummy/app/controllers/index.ts +++ b/packages/ember/tests/dummy/app/controllers/index.ts @@ -1,5 +1,4 @@ import Controller from '@ember/controller'; -import EmberError from '@ember/error'; import { action } from '@ember/object'; import { scheduleOnce } from '@ember/runloop'; import { tracked } from '@glimmer/tracking'; @@ -16,13 +15,13 @@ export default class IndexController extends Controller { @action public createEmberError(): void { - throw new EmberError('Whoops, looks like you have an EmberError'); + throw new Error('Whoops, looks like you have an EmberError'); } @action public createCaughtEmberError(): void { try { - throw new EmberError('Looks like you have a caught EmberError'); + throw new Error('Looks like you have a caught EmberError'); } catch (e) { // do nothing } diff --git a/packages/ember/tests/dummy/app/initializers/deprecation.ts b/packages/ember/tests/dummy/app/initializers/deprecation.ts deleted file mode 100644 index 190b1a932397..000000000000 --- a/packages/ember/tests/dummy/app/initializers/deprecation.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { registerDeprecationHandler } from '@ember/debug'; - -export function initialize(): void { - registerDeprecationHandler((message, options, next) => { - if (options && options.until && options.until !== '3.0.0') { - return; - } else { - // @ts-expect-error this is fine - next(message, options); - } - }); -} - -export default { initialize }; diff --git a/packages/ember/tests/unit/instrument-route-performance-test.ts b/packages/ember/tests/unit/instrument-route-performance-test.ts index 6ab88a646f23..f35da9f6bba2 100644 --- a/packages/ember/tests/unit/instrument-route-performance-test.ts +++ b/packages/ember/tests/unit/instrument-route-performance-test.ts @@ -17,7 +17,7 @@ module('Unit | Utility | instrument-route-performance', function (hooks) { const setupController = sinon.spy(); class DummyRoute extends Route { - public beforeModel(...args: unknown[]): unknown { + public beforeModel(...args: unknown[]): ReturnType { return beforeModel.call(this, ...args); } @@ -25,7 +25,7 @@ module('Unit | Utility | instrument-route-performance', function (hooks) { return model.call(this, ...args); } - public afterModel(...args: unknown[]): unknown { + public afterModel(...args: unknown[]): ReturnType { return afterModel.call(this, ...args); } diff --git a/yarn.lock b/yarn.lock index 236f8b18b110..b4ea8976fe76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1080,6 +1080,14 @@ "@babel/highlight" "^7.24.1" picocolors "^1.0.0" +"@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + "@babel/compat-data@^7.13.0", "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.4", "@babel/compat-data@^7.20.0": version "7.20.1" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" @@ -1095,6 +1103,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.14.tgz#4106fc8b755f3e3ee0a0a7c27dde5de1d2b2baf8" integrity sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw== +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" + integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== + "@babel/compat-data@^7.22.9": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0" @@ -1231,6 +1244,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.24.0", "@babel/core@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" + integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.4" + "@babel/parser" "^7.24.4" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@7.18.12": version "7.18.12" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.12.tgz#fa58daa303757bd6f5e4bbca91b342040463d9f4" @@ -1279,6 +1313,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.4.tgz#1fc55532b88adf952025d5d2d1e71f946cb1c498" + integrity sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@7.18.6", "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -1301,6 +1345,13 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== + dependencies: + "@babel/types" "^7.22.15" + "@babel/helper-compilation-targets@^7.12.0", "@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3", "@babel/helper-compilation-targets@^7.20.0": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" @@ -1311,7 +1362,7 @@ browserslist "^4.21.3" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.18.2", "@babel/helper-compilation-targets@^7.23.6": +"@babel/helper-compilation-targets@^7.18.2", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== @@ -1357,6 +1408,21 @@ "@babel/helper-replace-supers" "^7.18.9" "@babel/helper-split-export-declaration" "^7.18.6" +"@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz#c806f73788a6800a5cfbbc04d2df7ee4d927cce3" + integrity sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0": version "7.19.0" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" @@ -1365,7 +1431,7 @@ "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" -"@babel/helper-create-regexp-features-plugin@^7.22.5": +"@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== @@ -1400,6 +1466,17 @@ resolve "^1.14.2" semver "^6.1.2" +"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" + integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + "@babel/helper-environment-visitor@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" @@ -1425,7 +1502,7 @@ "@babel/template" "^7.18.10" "@babel/types" "^7.19.0" -"@babel/helper-function-name@^7.23.0": +"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== @@ -1475,6 +1552,13 @@ dependencies: "@babel/types" "^7.22.15" +"@babel/helper-module-imports@^7.24.1": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + "@babel/helper-module-transforms@^7.18.0", "@babel/helper-module-transforms@^7.18.9", "@babel/helper-module-transforms@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" @@ -1555,6 +1639,15 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" +"@babel/helper-remap-async-to-generator@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" + "@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9", "@babel/helper-replace-supers@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" @@ -1672,6 +1765,15 @@ "@babel/traverse" "^7.19.0" "@babel/types" "^7.19.0" +"@babel/helper-wrap-function@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" + integrity sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw== + dependencies: + "@babel/helper-function-name" "^7.22.5" + "@babel/template" "^7.22.15" + "@babel/types" "^7.22.19" + "@babel/helpers@^7.18.2": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.9.tgz#c3e20bbe7f7a7e10cb9b178384b4affdf5995c7d" @@ -1717,6 +1819,15 @@ "@babel/traverse" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/helpers@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6" + integrity sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" @@ -1754,6 +1865,16 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.4", "@babel/parser@^7.18.10", "@babel/parser@^7.20.2", "@babel/parser@^7.4.5", "@babel/parser@^7.7.0": version "7.20.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2" @@ -1789,6 +1910,19 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== +"@babel/parser@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" + integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" + integrity sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -1796,6 +1930,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf" + integrity sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" @@ -1805,6 +1946,23 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-proposal-optional-chaining" "^7.18.9" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz#da8261f2697f0f41b0855b91d3a20a1fbfd271d3" + integrity sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz#1181d9685984c91d657b8ddf14f0487a6bab2988" + integrity sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-proposal-async-generator-functions@7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952" @@ -1863,6 +2021,15 @@ "@babel/helper-split-export-declaration" "^7.18.6" "@babel/plugin-syntax-decorators" "^7.19.0" +"@babel/plugin-proposal-decorators@^7.20.13": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.1.tgz#bab2b9e174a2680f0a80f341f3ec70f809f8bb4b" + integrity sha512-zPEvzFijn+hRvJuX2Vu3KbEBN39LN3f7tW3MQO2LsIs57B26KU+kUc82BdAktS1VCM6libzh45eKGI65lg0cpA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-decorators" "^7.24.1" + "@babel/plugin-proposal-dynamic-import@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" @@ -1950,7 +2117,7 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-private-methods@^7.16.5", "@babel/plugin-proposal-private-methods@^7.18.6": +"@babel/plugin-proposal-private-methods@^7.16.5", "@babel/plugin-proposal-private-methods@^7.16.7", "@babel/plugin-proposal-private-methods@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== @@ -1958,6 +2125,11 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + "@babel/plugin-proposal-private-property-in-object@^7.16.5", "@babel/plugin-proposal-private-property-in-object@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" @@ -1968,6 +2140,16 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" +"@babel/plugin-proposal-private-property-in-object@^7.20.5": + version "7.21.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c" + integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" @@ -2011,6 +2193,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" +"@babel/plugin-syntax-decorators@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.1.tgz#71d9ad06063a6ac5430db126b5df48c70ee885fa" + integrity sha512-05RJdO/cCrtVWuAaSn1tS3bH8jbsJa/Y1uD186u6J4C/1mnHFxseeuWpsqr9anvo7TUulev7tm7GDwRV+VuhDw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -2032,7 +2221,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-import-assertions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz#db3aad724153a00eaac115a3fb898de544e34971" + integrity sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-import-attributes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz#c66b966c63b714c4eec508fcf5763b1f2d381093" + integrity sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -2116,6 +2319,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-syntax-typescript@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" + integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-arrow-functions@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" @@ -2123,6 +2341,23 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-arrow-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz#2bf263617060c9cc45bcdbf492b8cc805082bf27" + integrity sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-async-generator-functions@^7.24.3": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz#8fa7ae481b100768cc9842c8617808c5352b8b89" + integrity sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-transform-async-to-generator@7.18.6", "@babel/plugin-transform-async-to-generator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" @@ -2132,6 +2367,15 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-remap-async-to-generator" "^7.18.6" +"@babel/plugin-transform-async-to-generator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz#0e220703b89f2216800ce7b1c53cb0cf521c37f4" + integrity sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw== + dependencies: + "@babel/helper-module-imports" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-transform-block-scoped-functions@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" @@ -2139,6 +2383,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-block-scoped-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz#1c94799e20fcd5c4d4589523bbc57b7692979380" + integrity sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-block-scoping@^7.16.0", "@babel/plugin-transform-block-scoping@^7.19.4": version "7.19.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz#315d70f68ce64426db379a3d830e7ac30be02e9b" @@ -2153,7 +2404,31 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-classes@^7.18.9": +"@babel/plugin-transform-block-scoping@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz#28f5c010b66fbb8ccdeef853bef1935c434d7012" + integrity sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-class-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29" + integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-class-static-block@^7.16.7", "@babel/plugin-transform-class-static-block@^7.22.11", "@babel/plugin-transform-class-static-block@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4" + integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.4" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.18.9", "@babel/plugin-transform-classes@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz#5bc8fc160ed96378184bc10042af47f50884dcb1" integrity sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q== @@ -2189,7 +2464,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-destructuring@^7.18.9": +"@babel/plugin-transform-computed-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz#bc7e787f8e021eccfb677af5f13c29a9934ed8a7" + integrity sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/template" "^7.24.0" + +"@babel/plugin-transform-destructuring@^7.18.9", "@babel/plugin-transform-destructuring@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz#b1e8243af4a0206841973786292b8c8dd8447345" integrity sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw== @@ -2211,6 +2494,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-dotall-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz#d56913d2f12795cc9930801b84c6f8c47513ac13" + integrity sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-duplicate-keys@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" @@ -2218,6 +2509,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-duplicate-keys@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz#5347a797fe82b8d09749d10e9f5b83665adbca88" + integrity sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-dynamic-import@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz#2a5a49959201970dd09a5fca856cb651e44439dd" + integrity sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" @@ -2226,6 +2532,22 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-exponentiation-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz#6650ebeb5bd5c012d5f5f90a26613a08162e8ba4" + integrity sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-export-namespace-from@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz#f033541fc036e3efb2dcb58eedafd4f6b8078acd" + integrity sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-transform-for-of@^7.18.8": version "7.18.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" @@ -2233,6 +2555,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-for-of@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz#67448446b67ab6c091360ce3717e7d3a59e202fd" + integrity sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" @@ -2242,6 +2572,23 @@ "@babel/helper-function-name" "^7.18.9" "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-function-name@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz#8cba6f7730626cc4dfe4ca2fa516215a0592b361" + integrity sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA== + dependencies: + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-json-strings@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz#08e6369b62ab3e8a7b61089151b161180c8299f7" + integrity sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-transform-literals@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" @@ -2249,6 +2596,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz#0a1982297af83e6b3c94972686067df588c5c096" + integrity sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-logical-assignment-operators@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz#719d8aded1aa94b8fb34e3a785ae8518e24cfa40" + integrity sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-transform-member-expression-literals@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" @@ -2256,6 +2618,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-member-expression-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz#896d23601c92f437af8b01371ad34beb75df4489" + integrity sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-modules-amd@^7.13.0", "@babel/plugin-transform-modules-amd@^7.18.6": version "7.19.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" @@ -2264,6 +2633,14 @@ "@babel/helper-module-transforms" "^7.19.6" "@babel/helper-plugin-utils" "^7.19.0" +"@babel/plugin-transform-modules-amd@^7.20.11", "@babel/plugin-transform-modules-amd@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz#b6d829ed15258536977e9c7cc6437814871ffa39" + integrity sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-modules-commonjs@^7.18.6": version "7.19.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" @@ -2273,7 +2650,16 @@ "@babel/helper-plugin-utils" "^7.19.0" "@babel/helper-simple-access" "^7.19.4" -"@babel/plugin-transform-modules-systemjs@^7.18.9": +"@babel/plugin-transform-modules-commonjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" + integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-modules-systemjs@^7.18.9", "@babel/plugin-transform-modules-systemjs@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz#2b9625a3d4e445babac9788daec39094e6b11e3e" integrity sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA== @@ -2301,7 +2687,15 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": +"@babel/plugin-transform-modules-umd@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz#69220c66653a19cf2c0872b9c762b9a48b8bebef" + integrity sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6", "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== @@ -2324,6 +2718,39 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-new-target@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz#29c59988fa3d0157de1c871a28cd83096363cc34" + integrity sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988" + integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz#5bc019ce5b3435c1cadf37215e55e433d674d4e8" + integrity sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz#5a3ce73caf0e7871a02e1c31e8b473093af241ff" + integrity sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA== + dependencies: + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.24.1" + "@babel/plugin-transform-object-super@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" @@ -2332,6 +2759,31 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" +"@babel/plugin-transform-object-super@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz#e71d6ab13483cca89ed95a474f542bbfc20a0520" + integrity sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-replace-supers" "^7.24.1" + +"@babel/plugin-transform-optional-catch-binding@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz#92a3d0efe847ba722f1a4508669b23134669e2da" + integrity sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz#26e588acbedce1ab3519ac40cc748e380c5291e6" + integrity sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-transform-parameters@^7.18.8": version "7.18.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a" @@ -2346,6 +2798,31 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-transform-parameters@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz#983c15d114da190506c75b616ceb0f817afcc510" + integrity sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-private-methods@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a" + integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-private-property-in-object@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz#756443d400274f8fb7896742962cc1b9f25c1f6a" + integrity sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-transform-property-literals@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" @@ -2353,6 +2830,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-property-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz#d6a9aeab96f03749f4eebeb0b6ea8e90ec958825" + integrity sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-react-jsx@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.15.tgz#7e6266d88705d7c49f11c98db8b9464531289cd6" @@ -2372,6 +2856,14 @@ "@babel/helper-plugin-utils" "^7.18.6" regenerator-transform "^0.15.0" +"@babel/plugin-transform-regenerator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz#625b7545bae52363bdc1fbbdc7252b5046409c8c" + integrity sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + regenerator-transform "^0.15.2" + "@babel/plugin-transform-reserved-words@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" @@ -2379,6 +2871,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-reserved-words@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz#8de729f5ecbaaf5cf83b67de13bad38a21be57c1" + integrity sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-runtime@7.18.10": version "7.18.10" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f" @@ -2410,7 +2909,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.18.9": +"@babel/plugin-transform-shorthand-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz#ba9a09144cf55d35ec6b93a32253becad8ee5b55" + integrity sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-spread@^7.18.9", "@babel/plugin-transform-spread@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz#a1acf9152cbf690e4da0ba10790b3ac7d2b2b391" integrity sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g== @@ -2433,6 +2939,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-sticky-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz#f03e672912c6e203ed8d6e0271d9c2113dc031b9" + integrity sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-template-literals@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" @@ -2440,6 +2953,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-template-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz#15e2166873a30d8617e3e2ccadb86643d327aab7" + integrity sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-typeof-symbol@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" @@ -2447,6 +2967,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-typeof-symbol@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz#6831f78647080dec044f7e9f68003d99424f94c7" + integrity sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-typescript@^7.13.0", "@babel/plugin-transform-typescript@^7.16.7", "@babel/plugin-transform-typescript@^7.16.8": version "7.19.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.3.tgz#4f1db1e0fe278b42ddbc19ec2f6cd2f8262e35d6" @@ -2456,6 +2983,16 @@ "@babel/helper-plugin-utils" "^7.19.0" "@babel/plugin-syntax-typescript" "^7.18.6" +"@babel/plugin-transform-typescript@^7.20.13": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.4.tgz#03e0492537a4b953e491f53f2bc88245574ebd15" + integrity sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.4" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-typescript" "^7.24.1" + "@babel/plugin-transform-typescript@~7.4.0": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.4.5.tgz#ab3351ba35307b79981993536c93ff8be050ba28" @@ -2480,6 +3017,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" +"@babel/plugin-transform-unicode-escapes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz#fb3fa16676549ac7c7449db9b342614985c2a3a4" + integrity sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-property-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz#56704fd4d99da81e5e9f0c0c93cabd91dbc4889e" + integrity sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" @@ -2488,6 +3040,22 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-unicode-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz#57c3c191d68f998ac46b708380c1ce4d13536385" + integrity sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-sets-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz#c1ea175b02afcffc9cf57a9c4658326625165b7f" + integrity sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/polyfill@^7.11.5": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96" @@ -2658,6 +3226,102 @@ core-js-compat "^3.25.1" semver "^6.3.0" +"@babel/preset-env@^7.20.2": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.4.tgz#46dbbcd608771373b88f956ffb67d471dce0d23b" + integrity sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A== + dependencies: + "@babel/compat-data" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.4" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.24.1" + "@babel/plugin-syntax-import-attributes" "^7.24.1" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.1" + "@babel/plugin-transform-async-generator-functions" "^7.24.3" + "@babel/plugin-transform-async-to-generator" "^7.24.1" + "@babel/plugin-transform-block-scoped-functions" "^7.24.1" + "@babel/plugin-transform-block-scoping" "^7.24.4" + "@babel/plugin-transform-class-properties" "^7.24.1" + "@babel/plugin-transform-class-static-block" "^7.24.4" + "@babel/plugin-transform-classes" "^7.24.1" + "@babel/plugin-transform-computed-properties" "^7.24.1" + "@babel/plugin-transform-destructuring" "^7.24.1" + "@babel/plugin-transform-dotall-regex" "^7.24.1" + "@babel/plugin-transform-duplicate-keys" "^7.24.1" + "@babel/plugin-transform-dynamic-import" "^7.24.1" + "@babel/plugin-transform-exponentiation-operator" "^7.24.1" + "@babel/plugin-transform-export-namespace-from" "^7.24.1" + "@babel/plugin-transform-for-of" "^7.24.1" + "@babel/plugin-transform-function-name" "^7.24.1" + "@babel/plugin-transform-json-strings" "^7.24.1" + "@babel/plugin-transform-literals" "^7.24.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.1" + "@babel/plugin-transform-member-expression-literals" "^7.24.1" + "@babel/plugin-transform-modules-amd" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-modules-systemjs" "^7.24.1" + "@babel/plugin-transform-modules-umd" "^7.24.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.24.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1" + "@babel/plugin-transform-numeric-separator" "^7.24.1" + "@babel/plugin-transform-object-rest-spread" "^7.24.1" + "@babel/plugin-transform-object-super" "^7.24.1" + "@babel/plugin-transform-optional-catch-binding" "^7.24.1" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + "@babel/plugin-transform-parameters" "^7.24.1" + "@babel/plugin-transform-private-methods" "^7.24.1" + "@babel/plugin-transform-private-property-in-object" "^7.24.1" + "@babel/plugin-transform-property-literals" "^7.24.1" + "@babel/plugin-transform-regenerator" "^7.24.1" + "@babel/plugin-transform-reserved-words" "^7.24.1" + "@babel/plugin-transform-shorthand-properties" "^7.24.1" + "@babel/plugin-transform-spread" "^7.24.1" + "@babel/plugin-transform-sticky-regex" "^7.24.1" + "@babel/plugin-transform-template-literals" "^7.24.1" + "@babel/plugin-transform-typeof-symbol" "^7.24.1" + "@babel/plugin-transform-unicode-escapes" "^7.24.1" + "@babel/plugin-transform-unicode-property-regex" "^7.24.1" + "@babel/plugin-transform-unicode-regex" "^7.24.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.1" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.31.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + "@babel/preset-modules@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" @@ -2849,7 +3513,7 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@babel/types@^7.24.0": +"@babel/types@^7.22.19", "@babel/types@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== @@ -3144,67 +3808,25 @@ ember-cli-version-checker "^5.1.2" semver "^7.3.5" -"@embroider/macros@^1.0.0", "@embroider/macros@^1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.9.0.tgz#0df2a56fdd93f11fddea450b6ca83cc2119b5008" - integrity sha512-12ElrRT+mX3aSixGHjHnfsnyoH1hw5nM+P+Ax0ITZdp6TaAvWZ8dENnVHltdnv4ssHiX0EsVEXmqbIIdMN4nLA== +"@embroider/macros@^1.0.0", "@embroider/macros@^1.10.0", "@embroider/macros@^1.15.0", "@embroider/macros@^1.16.0": + version "1.16.0" + resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.16.0.tgz#4d7ffe3496f6052a6f7abe2d1235be44c5f10720" + integrity sha512-k36Zt+RPGZiMR6lFqrb/fJmMCCG7He0ww7O1w72eT/QVlvlJ2d7T1/0yvG+ZThHGpvX1FQMKQYJkREfG2DJF6w== dependencies: - "@embroider/shared-internals" "1.8.3" - assert-never "^1.2.1" - babel-import-util "^1.1.0" - ember-cli-babel "^7.26.6" - find-up "^5.0.0" - lodash "^4.17.21" - resolve "^1.20.0" - semver "^7.3.2" - -"@embroider/macros@^1.10.0", "@embroider/macros@^1.11.0": - version "1.13.1" - resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.13.1.tgz#aee17e5af0e0086bd36873bdb4e49ea346bab3fa" - integrity sha512-4htraP/rNIht8uCxXoc59Bw2EsBFfc4YUQD9XSpzJ4xUr1V0GQf9wL/noeSuYSxIhwRfZOErnJhsdyf1hH+I/A== - dependencies: - "@embroider/shared-internals" "2.4.0" + "@babel/core" "^7.24.0" + "@embroider/shared-internals" "2.6.0" assert-never "^1.2.1" babel-import-util "^2.0.0" - ember-cli-babel "^7.26.6" + ember-cli-babel "^8.2.0" find-up "^5.0.0" lodash "^4.17.21" resolve "^1.20.0" semver "^7.3.2" -"@embroider/shared-internals@1.8.3": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-1.8.3.tgz#52d868dc80016e9fe983552c0e516f437bf9b9f9" - integrity sha512-N5Gho6Qk8z5u+mxLCcMYAoQMbN4MmH+z2jXwQHVs859bxuZTxwF6kKtsybDAASCtd2YGxEmzcc1Ja/wM28824w== - dependencies: - babel-import-util "^1.1.0" - ember-rfc176-data "^0.3.17" - fs-extra "^9.1.0" - js-string-escape "^1.0.1" - lodash "^4.17.21" - resolve-package-path "^4.0.1" - semver "^7.3.5" - typescript-memoize "^1.0.1" - -"@embroider/shared-internals@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.4.0.tgz#0e9fdb0b2df9bad45fab8c54cbb70d8a2cbf01fc" - integrity sha512-pFE05ebenWMC9XAPRjadYCXXb6VmqjkhYN5uqkhPo+VUmMHnx7sZYYxqGjxfVuhC/ghS/BNlOffOCXDOoE7k7g== - dependencies: - babel-import-util "^2.0.0" - debug "^4.3.2" - ember-rfc176-data "^0.3.17" - fs-extra "^9.1.0" - js-string-escape "^1.0.1" - lodash "^4.17.21" - resolve-package-path "^4.0.1" - semver "^7.3.5" - typescript-memoize "^1.0.1" - -"@embroider/shared-internals@^2.0.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.3.0.tgz#97215f6263a4013fbdb3d1e4890cc069f2d9df12" - integrity sha512-5h7hUcci10ixXecJj/peqNQJO8kWe4b4tRx7mZjf7I6+P6zDcdVk3QxQZ+/gwrG6cbEfpLzEGKIEiLjZvPtqIA== +"@embroider/shared-internals@2.6.0", "@embroider/shared-internals@^2.0.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.6.0.tgz#851fd8d051fd4f7f93b2b7130e2ae5cdd537c5d6" + integrity sha512-A2BYQkhotdKOXuTaxvo9dqOIMbk+2LqFyqvfaaePkZcFJvtCkvTaD31/sSzqvRF6rdeBHjdMwU9Z2baPZ55fEQ== dependencies: babel-import-util "^2.0.0" debug "^4.3.2" @@ -3212,26 +3834,28 @@ fs-extra "^9.1.0" js-string-escape "^1.0.1" lodash "^4.17.21" + minimatch "^3.0.4" resolve-package-path "^4.0.1" semver "^7.3.5" typescript-memoize "^1.0.1" -"@embroider/test-setup@~1.8.3": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@embroider/test-setup/-/test-setup-1.8.3.tgz#445b9fe5a363ce50367ac2114750597f98d7806d" - integrity sha512-BCCbBG7UWkCw+cQ401Ip6LnqTRaQDeKImxR+e7Q4oP6H4EBj7p4iGR1z6fhMy4NNyXKPB6jk3bGa9bTiiNoEAw== +"@embroider/test-setup@~4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@embroider/test-setup/-/test-setup-4.0.0.tgz#080dd40314a53cc6f6fcffed41cd24ee0cb48b3d" + integrity sha512-1S3Ebk0CEh3XDqD93AWSwQZBCk+oGv03gtkaGgdgyXGIR7jrVyDgEnEuslN/hJ0cuU8TqhiXrzHMw7bJwIGhWw== dependencies: lodash "^4.17.21" resolve "^1.20.0" "@embroider/util@^1.9.0": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@embroider/util/-/util-1.11.1.tgz#622390932542e6b7f8d5d28e956891306e664eb3" - integrity sha512-IqzlEQahM2cfLvo4PULA2WyvROqr9jRmeSv0GGZzpitWCh6l4FDwweOLSArdlKSXdQxHkKhwBMCi//7DhKjRlg== + version "1.13.0" + resolved "https://registry.yarnpkg.com/@embroider/util/-/util-1.13.0.tgz#075c0be2955505677e7fd834847b1e7b30b5e11a" + integrity sha512-29NeyZ8jvcQXCZThaARpbU9nBNMXj/5dCuQmFmxyEC2AcHFzBBhhL0ebv6VI2e3f44g+pAFbCMbN434VBh2xqQ== dependencies: - "@embroider/macros" "^1.11.0" + "@babel/core" "^7.24.0" + "@embroider/macros" "^1.15.0" broccoli-funnel "^3.0.5" - ember-cli-babel "^7.26.11" + ember-cli-babel "^8.2.0" "@esbuild/aix-ppc64@0.20.0": version "0.20.0" @@ -10149,11 +10773,6 @@ babel-import-util@^0.2.0: resolved "https://registry.yarnpkg.com/babel-import-util/-/babel-import-util-0.2.0.tgz#b468bb679919601a3570f9e317536c54f2862e23" integrity sha512-CtWYYHU/MgK88rxMrLfkD356dApswtR/kWZ/c6JifG1m10e7tBBrs/366dFzWMAoqYmG5/JSh+94tUSpIwh+ag== -babel-import-util@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/babel-import-util/-/babel-import-util-1.2.2.tgz#1027560e143a4a68b1758e71d4fadc661614e495" - integrity sha512-8HgkHWt5WawRFukO30TuaL9EiDUOdvyKtDwLma4uBNeUSDbOO0/hiPfavrOWxSS6J6TKXfukWHZ3wiqZhJ8ONQ== - babel-import-util@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/babel-import-util/-/babel-import-util-2.0.0.tgz#99a2e7424bcde01898bc61bb19700ff4c74379a3" @@ -10301,6 +10920,17 @@ babel-plugin-module-resolver@^4.1.0: reselect "^4.0.0" resolve "^1.13.1" +babel-plugin-module-resolver@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz#cdeac5d4aaa3b08dd1ac23ddbf516660ed2d293e" + integrity sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg== + dependencies: + find-babel-config "^2.1.1" + glob "^9.3.3" + pkg-up "^3.1.0" + reselect "^4.1.7" + resolve "^1.22.8" + babel-plugin-polyfill-corejs2@^0.1.4: version "0.1.10" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.10.tgz#a2c5c245f56c0cac3dbddbf0726a46b24f0f81d1" @@ -10319,6 +10949,15 @@ babel-plugin-polyfill-corejs2@^0.3.2, babel-plugin-polyfill-corejs2@^0.3.3: "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.11" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" + integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.2" + semver "^6.3.1" + babel-plugin-polyfill-corejs3@^0.1.3: version "0.1.7" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz#80449d9d6f2274912e05d9e182b54816904befd0" @@ -10327,6 +10966,14 @@ babel-plugin-polyfill-corejs3@^0.1.3: "@babel/helper-define-polyfill-provider" "^0.1.5" core-js-compat "^3.8.1" +babel-plugin-polyfill-corejs3@^0.10.4: + version "0.10.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" + integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + core-js-compat "^3.36.1" + babel-plugin-polyfill-corejs3@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" @@ -10357,6 +11004,13 @@ babel-plugin-polyfill-regenerator@^0.4.0, babel-plugin-polyfill-regenerator@^0.4 dependencies: "@babel/helper-define-polyfill-provider" "^0.3.3" +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" + integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + babel-plugin-syntax-dynamic-import@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" @@ -10755,6 +11409,20 @@ broccoli-babel-transpiler@^7.8.0, broccoli-babel-transpiler@^7.8.1: rsvp "^4.8.4" workerpool "^3.1.1" +broccoli-babel-transpiler@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-8.0.0.tgz#07576728a95b840a99d5f0f9b07b71a737f69319" + integrity sha512-3HEp3flvasUKJGWERcrPgM1SWvHJ0O/fmbEtY9L4kDyMSnqjY6hTYvNvgWCIgbwXAYAUlZP0vjAQsmyLNGLwFw== + dependencies: + broccoli-persistent-filter "^3.0.0" + clone "^2.1.2" + hash-for-dep "^1.4.7" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.9" + json-stable-stringify "^1.0.1" + rsvp "^4.8.4" + workerpool "^6.0.2" + broccoli-builder@^0.18.14: version "0.18.14" resolved "https://registry.yarnpkg.com/broccoli-builder/-/broccoli-builder-0.18.14.tgz#4b79e2f844de11a4e1b816c3f49c6df4776c312d" @@ -11013,6 +11681,23 @@ broccoli-persistent-filter@^2.2.1, broccoli-persistent-filter@^2.3.0: sync-disk-cache "^1.3.3" walk-sync "^1.0.0" +broccoli-persistent-filter@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-3.1.3.tgz#aca815bf3e3b0247bd0a7b567fdb0d0e08c99cc2" + integrity sha512-Q+8iezprZzL9voaBsDY3rQVl7c7H5h+bvv8SpzCZXPZgfBFCbx7KFQ2c3rZR6lW5k4Kwoqt7jG+rZMUg67Gwxw== + dependencies: + async-disk-cache "^2.0.0" + async-promise-queue "^1.0.3" + broccoli-plugin "^4.0.3" + fs-tree-diff "^2.0.0" + hash-for-dep "^1.5.0" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.7" + promise-map-series "^0.2.1" + rimraf "^3.0.0" + symlink-or-copy "^1.0.1" + sync-disk-cache "^2.0.0" + broccoli-persistent-filter@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-3.1.2.tgz#41da6b9577be09a170ecde185f2c5a6099f99c4e" @@ -11625,30 +12310,10 @@ can-symlink@^1.0.0: dependencies: tmp "0.0.28" -caniuse-lite@^1.0.30001400: - version "1.0.30001422" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001422.tgz#f2d7c6202c49a8359e6e35add894d88ef93edba1" - integrity sha512-hSesn02u1QacQHhaxl/kNMZwqVG35Sz/8DgvmgedxSH8z9UUpcDYSPYgsj3x5dQNRcNp6BwpSfQfVzYUTm+fog== - -caniuse-lite@^1.0.30001406: - version "1.0.30001597" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz#8be94a8c1d679de23b22fbd944232aa1321639e6" - integrity sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w== - -caniuse-lite@^1.0.30001541: - version "1.0.30001546" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz#10fdad03436cfe3cc632d3af7a99a0fb497407f0" - integrity sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw== - -caniuse-lite@^1.0.30001587: - version "1.0.30001589" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz#7ad6dba4c9bf6561aec8291976402339dc157dfb" - integrity sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg== - -caniuse-lite@^1.0.30001591: - version "1.0.30001599" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce" - integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA== +caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001541, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001591: + version "1.0.30001614" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz#f894b4209376a0bf923d67d9c361d96b1dfebe39" + integrity sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog== capture-exit@^2.0.0: version "2.0.0" @@ -12594,6 +13259,13 @@ core-js-compat@^3.25.1, core-js-compat@^3.8.1: dependencies: browserslist "^4.21.4" +core-js-compat@^3.31.0, core-js-compat@^3.36.1: + version "3.37.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.0.tgz#d9570e544163779bb4dff1031c7972f44918dc73" + integrity sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA== + dependencies: + browserslist "^4.23.0" + core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" @@ -13625,7 +14297,7 @@ elliptic@^6.5.3, elliptic@^6.5.4: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -"ember-auto-import@^1.12.1 || ^2.4.3", ember-auto-import@^2.4.1, ember-auto-import@^2.4.2, ember-auto-import@^2.6.1: +ember-auto-import@^2.4.1, ember-auto-import@^2.4.2, ember-auto-import@^2.6.1: version "2.6.3" resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.6.3.tgz#f18d1b93dd10b08ba5496518436f9d56dd4e000a" integrity sha512-uLhrRDJYWCRvQ4JQ1e64XlSrqAKSd6PXaJ9ZsZI6Tlms9T4DtQFxNXasqji2ZRJBVrxEoLCRYX3RTldsQ0vNGQ== @@ -13662,12 +14334,52 @@ elliptic@^6.5.3, elliptic@^6.5.4: typescript-memoize "^1.0.0-alpha.3" walk-sync "^3.0.0" +ember-auto-import@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.7.2.tgz#5e74b6a8839fab25e23af6cff6f74b1b424d8f25" + integrity sha512-pkWIljmJClYL17YBk8FjO7NrZPQoY9v0b+FooJvaHf/xlDQIBYVP7OaDHbNuNbpj7+wAwSDAnnwxjCoLsmm4cw== + dependencies: + "@babel/core" "^7.16.7" + "@babel/plugin-proposal-class-properties" "^7.16.7" + "@babel/plugin-proposal-decorators" "^7.16.7" + "@babel/plugin-proposal-private-methods" "^7.16.7" + "@babel/plugin-transform-class-static-block" "^7.16.7" + "@babel/preset-env" "^7.16.7" + "@embroider/macros" "^1.0.0" + "@embroider/shared-internals" "^2.0.0" + babel-loader "^8.0.6" + babel-plugin-ember-modules-api-polyfill "^3.5.0" + babel-plugin-ember-template-compilation "^2.0.1" + babel-plugin-htmlbars-inline-precompile "^5.2.1" + babel-plugin-syntax-dynamic-import "^6.18.0" + broccoli-debug "^0.6.4" + broccoli-funnel "^3.0.8" + broccoli-merge-trees "^4.2.0" + broccoli-plugin "^4.0.0" + broccoli-source "^3.0.0" + css-loader "^5.2.0" + debug "^4.3.1" + fs-extra "^10.0.0" + fs-tree-diff "^2.0.0" + handlebars "^4.3.1" + js-string-escape "^1.0.1" + lodash "^4.17.19" + mini-css-extract-plugin "^2.5.2" + minimatch "^3.0.0" + parse5 "^6.0.1" + resolve "^1.20.0" + resolve-package-path "^4.0.3" + semver "^7.3.4" + style-loader "^2.0.0" + typescript-memoize "^1.0.0-alpha.3" + walk-sync "^3.0.0" + ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.1.tgz#5016b80cdef37036c4282eef2d863e1d73576879" integrity sha512-sKvOiPNHr5F/60NLd7SFzMpYPte/nnGkq/tMIfXejfKHIhaiIkYFqX8Z9UFTKWLLn+V7NOaby6niNPZUdvKCRw== -ember-cli-babel@^7.0.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.13.2, ember-cli-babel@^7.22.1, ember-cli-babel@^7.23.0, ember-cli-babel@^7.26.11, ember-cli-babel@^7.26.4, ember-cli-babel@^7.26.6, ember-cli-babel@^7.7.3: +ember-cli-babel@^7.0.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.13.2, ember-cli-babel@^7.22.1, ember-cli-babel@^7.23.0, ember-cli-babel@^7.26.11, ember-cli-babel@^7.26.6, ember-cli-babel@^7.7.3: version "7.26.11" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.26.11.tgz#50da0fe4dcd99aada499843940fec75076249a9f" integrity sha512-JJYeYjiz/JTn34q7F5DSOjkkZqy8qwFOOxXfE6pe9yEJqWGu4qErKxlz8I22JoVEQ/aBUO+OcKTpmctvykM9YA== @@ -13703,6 +14415,39 @@ ember-cli-babel@^7.0.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.13.2, ember- rimraf "^3.0.1" semver "^5.5.0" +ember-cli-babel@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-8.2.0.tgz#91e14c22ac22956177002385947724174553d41c" + integrity sha512-8H4+jQElCDo6tA7CamksE66NqBXWs7VNpS3a738L9pZCjg2kXIX4zoyHzkORUqCtr0Au7YsCnrlAMi1v2ALo7A== + dependencies: + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/plugin-proposal-class-properties" "^7.16.5" + "@babel/plugin-proposal-decorators" "^7.20.13" + "@babel/plugin-proposal-private-methods" "^7.16.5" + "@babel/plugin-proposal-private-property-in-object" "^7.20.5" + "@babel/plugin-transform-class-static-block" "^7.22.11" + "@babel/plugin-transform-modules-amd" "^7.20.11" + "@babel/plugin-transform-runtime" "^7.13.9" + "@babel/plugin-transform-typescript" "^7.20.13" + "@babel/preset-env" "^7.20.2" + "@babel/runtime" "7.12.18" + amd-name-resolver "^1.3.1" + babel-plugin-debug-macros "^0.3.4" + babel-plugin-ember-data-packages-polyfill "^0.1.2" + babel-plugin-ember-modules-api-polyfill "^3.5.0" + babel-plugin-module-resolver "^5.0.0" + broccoli-babel-transpiler "^8.0.0" + broccoli-debug "^0.6.4" + broccoli-funnel "^3.0.8" + broccoli-source "^3.0.1" + calculate-cache-key-for-tree "^2.0.0" + clone "^2.1.2" + ember-cli-babel-plugin-helpers "^1.1.1" + ember-cli-version-checker "^5.1.2" + ensure-posix-path "^1.0.2" + resolve-package-path "^4.0.3" + semver "^7.3.8" + ember-cli-dependency-checker@~3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/ember-cli-dependency-checker/-/ember-cli-dependency-checker-3.3.1.tgz#16b44d7a1a1e946f59859fad97f32e616d78323a" @@ -13876,10 +14621,10 @@ ember-cli-typescript@^2.0.2: stagehand "^1.0.0" walk-sync "^1.0.0" -ember-cli-typescript@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/ember-cli-typescript/-/ember-cli-typescript-5.1.1.tgz#cf561026f3e7bd05312c1c212acffa1c30d5fa0c" - integrity sha512-DbzATYWY8nbXwSxXqtK8YlqGJTcyFyL+eg6IGCc2ur0AMnq/H+o6Z9np9eGoq1sI+HwX7vBkOVoD3k0WurAwXg== +ember-cli-typescript@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/ember-cli-typescript/-/ember-cli-typescript-5.2.1.tgz#553030f1ce3e8958b8e4fc34909acd1218cb35f2" + integrity sha512-qqp5TAIuPHxHiGXJKL+78Euyhy0zSKQMovPh8sJpN/ZBYx0H90pONufHR3anaMcp1snVfx4B+mb9+7ijOik8ZA== dependencies: ansi-to-html "^0.6.15" broccoli-stew "^3.0.0" @@ -13892,10 +14637,10 @@ ember-cli-typescript@^5.1.1: stagehand "^1.0.0" walk-sync "^2.2.0" -ember-cli-typescript@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/ember-cli-typescript/-/ember-cli-typescript-5.2.1.tgz#553030f1ce3e8958b8e4fc34909acd1218cb35f2" - integrity sha512-qqp5TAIuPHxHiGXJKL+78Euyhy0zSKQMovPh8sJpN/ZBYx0H90pONufHR3anaMcp1snVfx4B+mb9+7ijOik8ZA== +ember-cli-typescript@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ember-cli-typescript/-/ember-cli-typescript-5.3.0.tgz#c0f726c61e4309aa9ff49b388219c6729ea986cd" + integrity sha512-gFA+ZwmsvvFwo2Jz/B9GMduEn+fPoGb69qWGP0Tp3+Tu5xypDtIKVSZ5086I3Cr19cLXD4HkrOR3YQvdUKzAkQ== dependencies: ansi-to-html "^0.6.15" broccoli-stew "^3.0.0" @@ -14072,7 +14817,7 @@ ember-load-initializers@~2.1.1: ember-cli-babel "^7.13.0" ember-cli-typescript "^2.0.2" -ember-maybe-import-regenerator@~1.0.0: +ember-maybe-import-regenerator@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ember-maybe-import-regenerator/-/ember-maybe-import-regenerator-1.0.0.tgz#c05453dfd3b65dbec2b569612b01ae70b672dd7e" integrity sha512-wtjgjEV0Hk4fgiAwFjOfPrGWfmFrbRW3zgNZO4oA3H5FlbMssMvWuR8blQ3QSWYHODVK9r+ThsRAs8lG4kbxqA== @@ -14262,15 +15007,6 @@ ember-template-recast@^6.1.4: tmp "^0.2.1" workerpool "^6.4.0" -ember-test-selectors@~6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/ember-test-selectors/-/ember-test-selectors-6.0.0.tgz#ba9bb19550d9dec6e4037d86d2b13c2cfd329341" - integrity sha512-PgYcI9PeNvtKaF0QncxfbS68olMYM1idwuI8v/WxsjOGqUx5bmsu6V17vy/d9hX4mwmjgsBhEghrVasGSuaIgw== - dependencies: - calculate-cache-key-for-tree "^2.0.0" - ember-cli-babel "^7.26.4" - ember-cli-version-checker "^5.1.2" - ember-try-config@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/ember-try-config/-/ember-try-config-4.0.0.tgz#8dbdcc071e7acbcb34750b4ed7faf1ab009766f1" @@ -15934,6 +16670,14 @@ find-babel-config@^1.1.0, find-babel-config@^1.2.0: json5 "^0.5.1" path-exists "^3.0.0" +find-babel-config@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-2.1.1.tgz#93703fc8e068db5e4c57592900c5715dd04b7e5b" + integrity sha512-5Ji+EAysHGe1OipH7GN4qDjok5Z1uw5KAwDCbicU/4wyTZY7CqOCzcWbG7J5ad9mazq67k89fXlbc1MuIfl9uA== + dependencies: + json5 "^2.2.3" + path-exists "^4.0.0" + find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -16767,7 +17511,7 @@ glob@^8.0.0: minimatch "^5.0.1" once "^1.3.0" -glob@^9.2.0, glob@^9.3.2: +glob@^9.2.0, glob@^9.3.2, glob@^9.3.3: version "9.3.5" resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== @@ -25254,7 +25998,12 @@ regenerator-runtime@0.13.9: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4: +regenerator-runtime@^0.13.2: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regenerator-runtime@^0.13.4: version "0.13.10" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== @@ -25266,6 +26015,13 @@ regenerator-transform@^0.15.0: dependencies: "@babel/runtime" "^7.8.4" +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -25546,6 +26302,11 @@ reselect@^4.0.0: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== +reselect@^4.1.7: + version "4.1.8" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" + integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -25655,7 +26416,7 @@ resolve@1.22.1, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.1 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@1.22.8: +resolve@1.22.8, resolve@^1.22.8: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -29924,6 +30685,11 @@ workerpool@^3.1.1: object-assign "4.1.1" rsvp "^4.8.4" +workerpool@^6.0.2: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== + workerpool@^6.1.5, workerpool@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" From 7fef87ee42377a004a41a1675fa0843429d3f718 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 30 Apr 2024 14:13:58 +0200 Subject: [PATCH 03/26] ref: Add geo location types (#11847) Adds types for `geo` in the user object https://develop.sentry.dev/sdk/event-payloads/user/ --- packages/types/src/user.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/types/src/user.ts b/packages/types/src/user.ts index ba8e32a3d469..f559c5029825 100644 --- a/packages/types/src/user.ts +++ b/packages/types/src/user.ts @@ -7,4 +7,11 @@ export interface User { ip_address?: string; email?: string; username?: string; + geo?: GeoLocation; +} + +export interface GeoLocation { + country_code?: string; + region?: string; + city?: string; } From 944cd9de94bff4542694f71ed8eb0ccf8395c18e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Tue, 30 Apr 2024 14:40:43 +0200 Subject: [PATCH 04/26] ci: Skip cloudflare-astro E2E tests for dependabot (#11853) They fail because a secret is missing, we can skip them for dependabot. See: https://github.com/orgs/community/discussions/26253 --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7609e25a856f..a7115aa7a15b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -989,6 +989,8 @@ jobs: strategy: fail-fast: false matrix: + is_dependabot: + - ${{ github.actor == 'dependabot[bot]' }} test-application: [ 'angular-17', @@ -1042,6 +1044,10 @@ jobs: - test-application: 'nextjs-app-dir' build-command: 'test:build-13' label: 'nextjs-app-dir (next@13)' + exclude: + - is_dependabot: true + test-application: 'cloudflare-astro' + steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 From ffcdc5c11d4a7bc2b68fa112682ace6854685749 Mon Sep 17 00:00:00 2001 From: Catherine Lee <55311782+c298lee@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:05:46 -0400 Subject: [PATCH 05/26] feat(feedback): Have screenshot by default (#11839) Defaults to having screenshots. --- packages/feedback/src/core/integration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts index 7d4d9199bc80..c2b367632b45 100644 --- a/packages/feedback/src/core/integration.ts +++ b/packages/feedback/src/core/integration.ts @@ -66,7 +66,7 @@ export const buildFeedbackIntegration = ({ autoInject = true, showEmail = true, showName = true, - showScreenshot = false, + showScreenshot = true, useSentryUser = { email: 'email', name: 'username', From 3c95ac9c3e2083363ace0008f463ab435aac1e07 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Tue, 30 Apr 2024 11:21:57 -0700 Subject: [PATCH 06/26] feat(fedback): Convert CDN bundles to use async feedback for lower bundle sizes (#11791) It's more aggressive to start with the async loading strategy in the CDN bundle, but could be safer because if we want to change from async->sync it would not really be a majr version change. Whereas going from sync->async would be more of a big feature, riskier, and could demand a version bump or something. --- packages/browser/src/{feedback.ts => feedbackSync.ts} | 2 +- packages/browser/src/index.bundle.feedback.ts | 9 +++++++-- packages/browser/src/index.bundle.replay.ts | 6 +++++- .../src/index.bundle.tracing.replay.feedback.ts | 7 ++++--- packages/browser/src/index.bundle.tracing.replay.ts | 4 ++-- packages/browser/src/index.bundle.tracing.ts | 6 +++++- packages/browser/src/index.bundle.ts | 1 + packages/browser/src/index.ts | 5 +++-- .../browser/test/unit/index.bundle.feedback.test.ts | 5 +++-- packages/browser/test/unit/index.bundle.replay.test.ts | 8 +++++--- packages/browser/test/unit/index.bundle.test.ts | 10 ++++++++-- .../unit/index.bundle.tracing.replay.feedback.test.ts | 8 +++++--- .../test/unit/index.bundle.tracing.replay.test.ts | 7 +++---- .../browser/test/unit/index.bundle.tracing.test.ts | 5 +++-- 14 files changed, 55 insertions(+), 28 deletions(-) rename packages/browser/src/{feedback.ts => feedbackSync.ts} (86%) diff --git a/packages/browser/src/feedback.ts b/packages/browser/src/feedbackSync.ts similarity index 86% rename from packages/browser/src/feedback.ts rename to packages/browser/src/feedbackSync.ts index 2e959da9817d..b99c9a4b752f 100644 --- a/packages/browser/src/feedback.ts +++ b/packages/browser/src/feedbackSync.ts @@ -6,7 +6,7 @@ import { import { lazyLoadIntegration } from './utils/lazyLoadIntegration'; /** Add a widget to capture user feedback to your application. */ -export const feedbackIntegration = buildFeedbackIntegration({ +export const feedbackSyncIntegration = buildFeedbackIntegration({ lazyLoadIntegration, getModalIntegration: () => feedbackModalIntegration, getScreenshotIntegration: () => feedbackScreenshotIntegration, diff --git a/packages/browser/src/index.bundle.feedback.ts b/packages/browser/src/index.bundle.feedback.ts index eacbd0fb89c4..eecad6304ebf 100644 --- a/packages/browser/src/index.bundle.feedback.ts +++ b/packages/browser/src/index.bundle.feedback.ts @@ -1,8 +1,13 @@ import { browserTracingIntegrationShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; +import { feedbackAsyncIntegration } from './feedbackAsync'; export * from './index.bundle.base'; -export { feedbackIntegration } from './feedback'; export { getFeedback } from '@sentry-internal/feedback'; -export { browserTracingIntegrationShim as browserTracingIntegration, replayIntegrationShim as replayIntegration }; +export { + browserTracingIntegrationShim as browserTracingIntegration, + feedbackAsyncIntegration as feedbackAsyncIntegration, + feedbackAsyncIntegration as feedbackIntegration, + replayIntegrationShim as replayIntegration, +}; diff --git a/packages/browser/src/index.bundle.replay.ts b/packages/browser/src/index.bundle.replay.ts index c2d267a5227d..1a538a97162f 100644 --- a/packages/browser/src/index.bundle.replay.ts +++ b/packages/browser/src/index.bundle.replay.ts @@ -4,4 +4,8 @@ export * from './index.bundle.base'; export { replayIntegration } from '@sentry-internal/replay'; -export { browserTracingIntegrationShim as browserTracingIntegration, feedbackIntegrationShim as feedbackIntegration }; +export { + browserTracingIntegrationShim as browserTracingIntegration, + feedbackIntegrationShim as feedbackAsyncIntegration, + feedbackIntegrationShim as feedbackIntegration, +}; diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 8e31dba14027..194a387bf4c4 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -15,13 +15,14 @@ export { setMeasurement, } from '@sentry/core'; -export { feedbackIntegration } from './feedback'; -export { getFeedback } from '@sentry-internal/feedback'; - export { browserTracingIntegration, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; +import { feedbackAsyncIntegration } from './feedbackAsync'; +export { getFeedback } from '@sentry-internal/feedback'; +export { feedbackAsyncIntegration as feedbackAsyncIntegration, feedbackAsyncIntegration as feedbackIntegration }; + export { replayIntegration } from '@sentry-internal/replay'; diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index f103fa297ace..3b8a51e661dc 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -1,4 +1,3 @@ -import { feedbackIntegrationShim } from '@sentry-internal/integration-shims'; import { registerSpanErrorInstrumentation } from '@sentry/core'; registerSpanErrorInstrumentation(); @@ -22,6 +21,7 @@ export { startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; -export { feedbackIntegrationShim as feedbackIntegration }; +import { feedbackIntegrationShim } from '@sentry-internal/integration-shims'; +export { feedbackIntegrationShim as feedbackAsyncIntegration, feedbackIntegrationShim as feedbackIntegration }; export { replayIntegration } from '@sentry-internal/replay'; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index 9af45dfa8572..e93bf68994e3 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -22,4 +22,8 @@ export { startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; -export { feedbackIntegrationShim as feedbackIntegration, replayIntegrationShim as replayIntegration }; +export { + feedbackIntegrationShim as feedbackAsyncIntegration, + feedbackIntegrationShim as feedbackIntegration, + replayIntegrationShim as replayIntegration, +}; diff --git a/packages/browser/src/index.bundle.ts b/packages/browser/src/index.bundle.ts index f24e98b4f6db..5004b376cd46 100644 --- a/packages/browser/src/index.bundle.ts +++ b/packages/browser/src/index.bundle.ts @@ -8,6 +8,7 @@ export * from './index.bundle.base'; export { browserTracingIntegrationShim as browserTracingIntegration, + feedbackIntegrationShim as feedbackAsyncIntegration, feedbackIntegrationShim as feedbackIntegration, replayIntegrationShim as replayIntegration, }; diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index d5e905ff568c..23b042ca8e09 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -30,8 +30,9 @@ export type { export { replayCanvasIntegration } from '@sentry-internal/replay-canvas'; -export { feedbackIntegration } from './feedback'; -export { feedbackAsyncIntegration } from './feedbackAsync'; +import { feedbackAsyncIntegration } from './feedbackAsync'; +import { feedbackSyncIntegration } from './feedbackSync'; +export { feedbackAsyncIntegration, feedbackSyncIntegration, feedbackSyncIntegration as feedbackIntegration }; export { getFeedback, sendFeedback, diff --git a/packages/browser/test/unit/index.bundle.feedback.test.ts b/packages/browser/test/unit/index.bundle.feedback.test.ts index bd231a95fe05..99516cca295e 100644 --- a/packages/browser/test/unit/index.bundle.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.feedback.test.ts @@ -1,12 +1,13 @@ import { browserTracingIntegrationShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; -import { feedbackIntegration } from '../../src'; +import { feedbackAsyncIntegration } from '../../src'; import * as FeedbackBundle from '../../src/index.bundle.feedback'; describe('index.bundle.feedback', () => { it('has correct exports', () => { expect(FeedbackBundle.browserTracingIntegration).toBe(browserTracingIntegrationShim); + expect(FeedbackBundle.feedbackAsyncIntegration).toBe(feedbackAsyncIntegration); + expect(FeedbackBundle.feedbackIntegration).toBe(feedbackAsyncIntegration); expect(FeedbackBundle.replayIntegration).toBe(replayIntegrationShim); - expect(FeedbackBundle.feedbackIntegration).toBe(feedbackIntegration); }); }); diff --git a/packages/browser/test/unit/index.bundle.replay.test.ts b/packages/browser/test/unit/index.bundle.replay.test.ts index 3fec5d2bb6ab..0fdbf95fe3e4 100644 --- a/packages/browser/test/unit/index.bundle.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.replay.test.ts @@ -1,11 +1,13 @@ -import { feedbackIntegrationShim } from '@sentry-internal/integration-shims'; -import { replayIntegration } from '@sentry/browser'; +import { browserTracingIntegrationShim, feedbackIntegrationShim } from '@sentry-internal/integration-shims'; +import { replayIntegration } from '../../src'; import * as ReplayBundle from '../../src/index.bundle.replay'; describe('index.bundle.replay', () => { it('has correct exports', () => { - expect(ReplayBundle.replayIntegration).toBe(replayIntegration); + expect(ReplayBundle.browserTracingIntegration).toBe(browserTracingIntegrationShim); + expect(ReplayBundle.feedbackAsyncIntegration).toBe(feedbackIntegrationShim); expect(ReplayBundle.feedbackIntegration).toBe(feedbackIntegrationShim); + expect(ReplayBundle.replayIntegration).toBe(replayIntegration); }); }); diff --git a/packages/browser/test/unit/index.bundle.test.ts b/packages/browser/test/unit/index.bundle.test.ts index 1d459ddfd731..1535d74d6b6a 100644 --- a/packages/browser/test/unit/index.bundle.test.ts +++ b/packages/browser/test/unit/index.bundle.test.ts @@ -1,10 +1,16 @@ -import { feedbackIntegrationShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; +import { + browserTracingIntegrationShim, + feedbackIntegrationShim, + replayIntegrationShim, +} from '@sentry-internal/integration-shims'; import * as Bundle from '../../src/index.bundle'; describe('index.bundle', () => { it('has correct exports', () => { - expect(Bundle.replayIntegration).toBe(replayIntegrationShim); + expect(Bundle.browserTracingIntegration).toBe(browserTracingIntegrationShim); + expect(Bundle.feedbackAsyncIntegration).toBe(feedbackIntegrationShim); expect(Bundle.feedbackIntegration).toBe(feedbackIntegrationShim); + expect(Bundle.replayIntegration).toBe(replayIntegrationShim); }); }); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts index 72418b639241..2d62f247a1da 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts @@ -1,10 +1,12 @@ -import { browserTracingIntegration, feedbackIntegration, replayIntegration } from '../../src'; +import { browserTracingIntegration, feedbackAsyncIntegration, replayIntegration } from '../../src'; + import * as TracingReplayFeedbackBundle from '../../src/index.bundle.tracing.replay.feedback'; describe('index.bundle.tracing.replay.feedback', () => { it('has correct exports', () => { - expect(TracingReplayFeedbackBundle.replayIntegration).toBe(replayIntegration); expect(TracingReplayFeedbackBundle.browserTracingIntegration).toBe(browserTracingIntegration); - expect(TracingReplayFeedbackBundle.feedbackIntegration).toBe(feedbackIntegration); + expect(TracingReplayFeedbackBundle.feedbackAsyncIntegration).toBe(feedbackAsyncIntegration); + expect(TracingReplayFeedbackBundle.feedbackIntegration).toBe(feedbackAsyncIntegration); + expect(TracingReplayFeedbackBundle.replayIntegration).toBe(replayIntegration); }); }); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts index 5f5f1e649951..2cd3f5dca0f0 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts @@ -1,14 +1,13 @@ import { feedbackIntegrationShim } from '@sentry-internal/integration-shims'; - import { browserTracingIntegration, replayIntegration } from '../../src'; + import * as TracingReplayBundle from '../../src/index.bundle.tracing.replay'; describe('index.bundle.tracing.replay', () => { it('has correct exports', () => { - expect(TracingReplayBundle.replayIntegration).toBe(replayIntegration); - expect(TracingReplayBundle.browserTracingIntegration).toBe(browserTracingIntegration); - + expect(TracingReplayBundle.feedbackAsyncIntegration).toBe(feedbackIntegrationShim); expect(TracingReplayBundle.feedbackIntegration).toBe(feedbackIntegrationShim); + expect(TracingReplayBundle.replayIntegration).toBe(replayIntegration); }); }); diff --git a/packages/browser/test/unit/index.bundle.tracing.test.ts b/packages/browser/test/unit/index.bundle.tracing.test.ts index 065654e054b9..942d185b2e91 100644 --- a/packages/browser/test/unit/index.bundle.tracing.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.test.ts @@ -1,12 +1,13 @@ import { feedbackIntegrationShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; - import { browserTracingIntegration } from '../../src'; + import * as TracingBundle from '../../src/index.bundle.tracing'; describe('index.bundle.tracing', () => { it('has correct exports', () => { - expect(TracingBundle.replayIntegration).toBe(replayIntegrationShim); expect(TracingBundle.browserTracingIntegration).toBe(browserTracingIntegration); + expect(TracingBundle.feedbackAsyncIntegration).toBe(feedbackIntegrationShim); expect(TracingBundle.feedbackIntegration).toBe(feedbackIntegrationShim); + expect(TracingBundle.replayIntegration).toBe(replayIntegrationShim); }); }); From 7e6c23e3973788db6bd525b20ddb1e0d22cbd741 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Wed, 1 May 2024 13:15:29 -0700 Subject: [PATCH 07/26] fix(feedback): Be consistent about whether screenshot should and can render (#11859) This fixes the conditions for loading and rendering the screenshot integration.... also improves the conditions for not rendering it if we're on a mobile device. - We should check the local`options.showScreenshot` instead of the closed-over `showScreenshot` because the options might have changed for this instance of the widget - We should combine `options.showScreenshot` (the desire) with `isScreenshotSupported()` (the possibility) to set the right expectation --- packages/feedback/src/core/integration.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts index c2b367632b45..fe5eceee4508 100644 --- a/packages/feedback/src/core/integration.ts +++ b/packages/feedback/src/core/integration.ts @@ -176,9 +176,10 @@ export const buildFeedbackIntegration = ({ }; const _loadAndRenderDialog = async (options: FeedbackInternalOptions): Promise => { + const screenshotRequired = options.showScreenshot && isScreenshotSupported(); const [modalIntegration, screenshotIntegration] = await Promise.all([ _findIntegration('FeedbackModal', getModalIntegration, 'feedbackModalIntegration'), - showScreenshot && isScreenshotSupported() + screenshotRequired ? _findIntegration( 'FeedbackScreenshot', getScreenshotIntegration, @@ -186,15 +187,22 @@ export const buildFeedbackIntegration = ({ ) : undefined, ]); - if (!modalIntegration || (showScreenshot && !screenshotIntegration)) { + if (!modalIntegration) { // TODO: Let the end-user retry async loading - // Include more verbose logs so developers can understand the options (like preloading). - throw new Error('Missing feedback helper integration!'); + DEBUG_BUILD && + logger.error( + '[Feedback] Missing feedback modal integration. Try using `feedbackSyncIntegration` in your `Sentry.init`.', + ); + throw new Error('[Feedback] Missing feedback modal integration!'); + } + if (screenshotRequired && !screenshotIntegration) { + DEBUG_BUILD && + logger.error('[Feedback] Missing feedback screenshot integration. Proceeding without screenshots.'); } return modalIntegration.createDialog({ options, - screenshotIntegration: showScreenshot ? screenshotIntegration : undefined, + screenshotIntegration: screenshotRequired ? screenshotIntegration : undefined, sendFeedback, shadow: _createShadow(options), }); From eadcac5b883ea652b5fac0daa77c9129cc696f42 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Thu, 2 May 2024 00:14:56 -0700 Subject: [PATCH 08/26] feat(integrations): Add zod integration (#11144) This adds a [Zod](https://github.com/colinhacks/zod) integration to sentry that adds better support for ZodError issues. Currently, the ZodError message is a formatted json string that gets truncated and the full list of issues are lost. - Adds the full list of issues to `extras['zoderror.issues']`. - Replaces the error message with a simple string. before ![image](https://github.com/getsentry/sentry-javascript/assets/1400464/835f4388-398b-42bf-9c6c-dae111207de8) ![image](https://github.com/getsentry/sentry-javascript/assets/1400464/1647b16d-3990-4726-805f-93ad863f71ea) after ![image](https://github.com/getsentry/sentry-javascript/assets/1400464/561751c3-1455-41f5-b700-8116daae419f) ![image](https://github.com/getsentry/sentry-javascript/assets/1400464/3c6df13a-6c0e-46fd-9631-80345743c061) ![image](https://github.com/getsentry/sentry-javascript/assets/1400464/1556cad3-2b78-42af-be1c-c8cb9d79fb4a) --------- Co-authored-by: Francesco Novy --- packages/aws-serverless/src/index.ts | 1 + packages/browser/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/core/src/index.ts | 1 + packages/core/src/integrations/zoderrors.ts | 119 ++++++++++++++++++ .../test/lib/integrations/zoderrrors.test.ts | 100 +++++++++++++++ packages/deno/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + packages/vercel-edge/src/index.ts | 1 + 10 files changed, 227 insertions(+) create mode 100644 packages/core/src/integrations/zoderrors.ts create mode 100644 packages/core/test/lib/integrations/zoderrrors.test.ts diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index c892e8fd373c..dcf248f05112 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -97,6 +97,7 @@ export { spanToTraceHeader, trpcMiddleware, addOpenTelemetryInstrumentation, + zodErrorsIntegration, } from '@sentry/node'; export { diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 23b042ca8e09..04a98fbacb02 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -64,6 +64,7 @@ export { setHttpStatus, makeMultiplexedTransport, moduleMetadataIntegration, + zodErrorsIntegration, } from '@sentry/core'; export type { Span } from '@sentry/types'; export { makeBrowserOfflineTransport } from './transports/offline'; diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 329af504f73f..47b40a9e19d7 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -118,6 +118,7 @@ export { spanToTraceHeader, trpcMiddleware, addOpenTelemetryInstrumentation, + zodErrorsIntegration, } from '@sentry/node'; export { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index df889035b7c4..c2c210262483 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -91,6 +91,7 @@ export { dedupeIntegration } from './integrations/dedupe'; export { extraErrorDataIntegration } from './integrations/extraerrordata'; export { rewriteFramesIntegration } from './integrations/rewriteframes'; export { sessionTimingIntegration } from './integrations/sessiontiming'; +export { zodErrorsIntegration } from './integrations/zoderrors'; export { metrics } from './metrics/exports'; export type { MetricData } from './metrics/exports'; export { metricsDefault } from './metrics/exports-default'; diff --git a/packages/core/src/integrations/zoderrors.ts b/packages/core/src/integrations/zoderrors.ts new file mode 100644 index 000000000000..14a7da84d384 --- /dev/null +++ b/packages/core/src/integrations/zoderrors.ts @@ -0,0 +1,119 @@ +import type { IntegrationFn } from '@sentry/types'; +import type { Event, EventHint } from '@sentry/types'; +import { isError, truncate } from '@sentry/utils'; +import { defineIntegration } from '../integration'; + +interface ZodErrorsOptions { + key?: string; + limit?: number; +} + +const DEFAULT_LIMIT = 10; +const INTEGRATION_NAME = 'ZodErrors'; + +// Simplified ZodIssue type definition +interface ZodIssue { + path: (string | number)[]; + message?: string; + expected?: string | number; + received?: string | number; + unionErrors?: unknown[]; + keys?: unknown[]; +} + +interface ZodError extends Error { + issues: ZodIssue[]; + + get errors(): ZodError['issues']; +} + +function originalExceptionIsZodError(originalException: unknown): originalException is ZodError { + return ( + isError(originalException) && + originalException.name === 'ZodError' && + Array.isArray((originalException as ZodError).errors) + ); +} + +type SingleLevelZodIssue = { + [P in keyof T]: T[P] extends string | number | undefined + ? T[P] + : T[P] extends unknown[] + ? string | undefined + : unknown; +}; + +/** + * Formats child objects or arrays to a string + * That is preserved when sent to Sentry + */ +function formatIssueTitle(issue: ZodIssue): SingleLevelZodIssue { + return { + ...issue, + path: 'path' in issue && Array.isArray(issue.path) ? issue.path.join('.') : undefined, + keys: 'keys' in issue ? JSON.stringify(issue.keys) : undefined, + unionErrors: 'unionErrors' in issue ? JSON.stringify(issue.unionErrors) : undefined, + }; +} + +/** + * Zod error message is a stringified version of ZodError.issues + * This doesn't display well in the Sentry UI. Replace it with something shorter. + */ +function formatIssueMessage(zodError: ZodError): string { + const errorKeyMap = new Set(); + for (const iss of zodError.issues) { + if (iss.path) errorKeyMap.add(iss.path[0]); + } + const errorKeys = Array.from(errorKeyMap); + + return `Failed to validate keys: ${truncate(errorKeys.join(', '), 100)}`; +} + +/** + * Applies ZodError issues to an event extras and replaces the error message + */ +export function applyZodErrorsToEvent(limit: number, event: Event, hint?: EventHint): Event { + if ( + !event.exception || + !event.exception.values || + !hint || + !hint.originalException || + !originalExceptionIsZodError(hint.originalException) || + hint.originalException.issues.length === 0 + ) { + return event; + } + + return { + ...event, + exception: { + ...event.exception, + values: [ + { + ...event.exception.values[0], + value: formatIssueMessage(hint.originalException), + }, + ...event.exception.values.slice(1), + ], + }, + extra: { + ...event.extra, + 'zoderror.issues': hint.originalException.errors.slice(0, limit).map(formatIssueTitle), + }, + }; +} + +const _zodErrorsIntegration = ((options: ZodErrorsOptions = {}) => { + const limit = options.limit || DEFAULT_LIMIT; + + return { + name: INTEGRATION_NAME, + processEvent(originalEvent, hint) { + const processedEvent = applyZodErrorsToEvent(limit, originalEvent, hint); + return processedEvent; + }, + }; +}) satisfies IntegrationFn; + +export const zodErrorsIntegration = defineIntegration(_zodErrorsIntegration); diff --git a/packages/core/test/lib/integrations/zoderrrors.test.ts b/packages/core/test/lib/integrations/zoderrrors.test.ts new file mode 100644 index 000000000000..2eca38d5d2ab --- /dev/null +++ b/packages/core/test/lib/integrations/zoderrrors.test.ts @@ -0,0 +1,100 @@ +import type { Event, EventHint } from '@sentry/types'; + +import { applyZodErrorsToEvent } from '../../../src/integrations/zoderrors'; + +// Simplified type definition +interface ZodIssue { + code: string; + path: (string | number)[]; + expected?: string | number; + received?: string | number; + keys?: string[]; + message?: string; +} + +class ZodError extends Error { + issues: ZodIssue[] = []; + + // https://github.com/colinhacks/zod/blob/8910033b861c842df59919e7d45e7f51cf8b76a2/src/ZodError.ts#L199C1-L211C4 + constructor(issues: ZodIssue[]) { + super(); + + const actualProto = new.target.prototype; + if (Object.setPrototypeOf) { + Object.setPrototypeOf(this, actualProto); + } else { + (this as any).__proto__ = actualProto; + } + + this.name = 'ZodError'; + this.issues = issues; + } + + get errors() { + return this.issues; + } + + static create = (issues: ZodIssue[]) => { + const error = new ZodError(issues); + return error; + }; +} + +describe('applyZodErrorsToEvent()', () => { + test('should not do anything if exception is not a ZodError', () => { + const event: Event = {}; + const eventHint: EventHint = { originalException: new Error() }; + applyZodErrorsToEvent(100, event, eventHint); + + // no changes + expect(event).toStrictEqual({}); + }); + + test('should add ZodError issues to extras and format message', () => { + const issues = [ + { + code: 'invalid_type', + expected: 'string', + received: 'number', + path: ['names', 1], + keys: ['extra'], + message: 'Invalid input: expected string, received number', + }, + ] satisfies ZodIssue[]; + const originalException = ZodError.create(issues); + + const event: Event = { + exception: { + values: [ + { + type: 'Error', + value: originalException.message, + }, + ], + }, + }; + + const eventHint: EventHint = { originalException }; + const processedEvent = applyZodErrorsToEvent(100, event, eventHint); + + expect(processedEvent.exception).toStrictEqual({ + values: [ + { + type: 'Error', + value: 'Failed to validate keys: names', + }, + ], + }); + + expect(processedEvent.extra).toStrictEqual({ + 'zoderror.issues': [ + { + ...issues[0], + path: issues[0].path.join('.'), + keys: JSON.stringify(issues[0].keys), + unionErrors: undefined, + }, + ], + }); + }); +}); diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index f25c14864d84..2e5e0cefa657 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -67,6 +67,7 @@ export { extraErrorDataIntegration, rewriteFramesIntegration, sessionTimingIntegration, + zodErrorsIntegration, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index 0bfd796bb297..eb1e47b2fd05 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -97,6 +97,7 @@ export { spanToTraceHeader, trpcMiddleware, addOpenTelemetryInstrumentation, + zodErrorsIntegration, } from '@sentry/node'; export { diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index a44561e969c9..fd71dc874974 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -109,6 +109,7 @@ export { spanToJSON, spanToTraceHeader, trpcMiddleware, + zodErrorsIntegration, } from '@sentry/core'; export type { diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 963497cc619a..9c5a0705426e 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -64,6 +64,7 @@ export { inboundFiltersIntegration, linkedErrorsIntegration, requestDataIntegration, + zodErrorsIntegration, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, From a3cac7b49568545868167e0e474a474518ac8eac Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 2 May 2024 10:28:18 +0200 Subject: [PATCH 09/26] fix(react): Fix react router v4/v5 instrumentation (#11855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turns out we were not correctly setting parametrized route names for react router v4/v5 😬 We had no proper test covering this. So I added a new E2E test `react-router-5` that actually checks that parametrization etc. works as expected. While at it, I also updated the react-router-6 E2E test to actually check these things as well - previously, we mostly only checked that _anything_ was sent to sentry, but didn't look at the content we sent. Fixes https://github.com/getsentry/sentry-javascript/issues/11815 --- .github/workflows/build.yml | 1 + .../react-router-5/.gitignore | 29 +++++++ .../test-applications/react-router-5/.npmrc | 2 + .../react-router-5/package.json | 60 ++++++++++++++ .../react-router-5/playwright.config.ts | 82 +++++++++++++++++++ .../react-router-5/public/index.html | 24 ++++++ .../react-router-5/src/globals.d.ts | 5 ++ .../react-router-5/src/index.tsx | 42 ++++++++++ .../react-router-5/src/pages/Index.tsx | 25 ++++++ .../react-router-5/src/pages/User.tsx | 8 ++ .../react-router-5/src/react-app-env.d.ts | 1 + .../react-router-5/start-event-proxy.ts | 6 ++ .../react-router-5/tests/errors.test.ts | 59 +++++++++++++ .../react-router-5/tests/transactions.test.ts | 56 +++++++++++++ .../react-router-5/tsconfig.json | 25 ++++++ packages/react/src/reactrouter.tsx | 9 +- 16 files changed, 430 insertions(+), 4 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/package.json create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/playwright.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/public/index.html create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/src/globals.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/src/index.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/src/pages/Index.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/src/pages/User.tsx create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/src/react-app-env.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/start-event-proxy.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/tests/transactions.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-router-5/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7115aa7a15b..1027efa79677 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1008,6 +1008,7 @@ jobs: 'nextjs-14', 'react-create-hash-router', 'react-router-6-use-routes', + 'react-router-5', 'standard-frontend-react', 'svelte-5', 'sveltekit', diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/.gitignore b/dev-packages/e2e-tests/test-applications/react-router-5/.gitignore new file mode 100644 index 000000000000..84634c973eeb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/.gitignore @@ -0,0 +1,29 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +/test-results/ +/playwright-report/ +/playwright/.cache/ + +!*.d.ts diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/.npmrc b/dev-packages/e2e-tests/test-applications/react-router-5/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/.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/react-router-5/package.json b/dev-packages/e2e-tests/test-applications/react-router-5/package.json new file mode 100644 index 000000000000..921f67e212b3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/package.json @@ -0,0 +1,60 @@ +{ + "name": "react-router-5-e2e-test-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@sentry/react": "latest || *", + "@testing-library/jest-dom": "5.14.1", + "@testing-library/react": "13.0.0", + "@testing-library/user-event": "13.2.1", + "history": "4.9.0", + "@types/history": "4.7.11", + "@types/jest": "27.0.1", + "@types/node": "16.7.13", + "@types/react": "18.0.0", + "@types/react-dom": "18.0.0", + "@types/react-router": "5.1.20", + "@types/react-router-dom": "5.3.3", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-router-dom": "5.3.4", + "react-scripts": "5.0.1", + "typescript": "4.9.5", + "web-vitals": "2.1.0" + }, + "scripts": { + "build": "react-scripts build", + "start": "serve -s build", + "test": "playwright test", + "clean": "npx rimraf node_modules,pnpm-lock.yaml", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:assert": "pnpm test" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@playwright/test": "^1.43.1", + "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server", + "ts-node": "^10.9.2", + "serve": "14.0.1" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/playwright.config.ts b/dev-packages/e2e-tests/test-applications/react-router-5/playwright.config.ts new file mode 100644 index 000000000000..abee4975ed4e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/playwright.config.ts @@ -0,0 +1,82 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +const reactPort = 3030; +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: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: 0, + /* Opt out of parallel tests on CI. */ + workers: 1, + /* 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, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + baseURL: `http://localhost:${reactPort}`, + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + // For now we only test Chrome! + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // }, + // }, + ], + + /* Run your local dev server before starting the tests */ + + webServer: [ + { + command: 'pnpm ts-node-script start-event-proxy.ts', + port: eventProxyPort, + }, + { + command: 'pnpm start', + port: reactPort, + env: { + PORT: `${reactPort}`, + }, + }, + ], +}; + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/public/index.html b/dev-packages/e2e-tests/test-applications/react-router-5/public/index.html new file mode 100644 index 000000000000..39da76522bea --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/public/index.html @@ -0,0 +1,24 @@ + + + + + + + + React App + + + +
+ + + diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/src/globals.d.ts b/dev-packages/e2e-tests/test-applications/react-router-5/src/globals.d.ts new file mode 100644 index 000000000000..ffa61ca49acc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/src/globals.d.ts @@ -0,0 +1,5 @@ +interface Window { + recordedTransactions?: string[]; + capturedExceptionId?: string; + sentryReplayId?: string; +} diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-5/src/index.tsx new file mode 100644 index 000000000000..315ba07ad8c4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/src/index.tsx @@ -0,0 +1,42 @@ +import * as Sentry from '@sentry/react'; +import { createBrowserHistory } from 'history'; +// biome-ignore lint/nursery/noUnusedImports: +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { Route, Router, Switch } from 'react-router-dom'; +import Index from './pages/Index'; +import User from './pages/User'; + +const replay = Sentry.replayIntegration(); + +const history = createBrowserHistory(); + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: + process.env.REACT_APP_E2E_TEST_DSN || + 'https://3b6c388182fb435097f41d181be2b2ba@o4504321058471936.ingest.sentry.io/4504321066008576', + integrations: [Sentry.reactRouterV5BrowserTracingIntegration({ history }), replay], + // We recommend adjusting this value in production, or using tracesSampler + // for finer control + tracesSampleRate: 1.0, + release: 'e2e-test', + tunnel: 'http://localhost:3031/', // proxy server + + // Always capture replays, so we can test this properly + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.0, +}); + +// Create Custom Sentry Route component +export const SentryRoute = Sentry.withSentryRouting(Route); + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + + + + , +); diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/src/pages/Index.tsx b/dev-packages/e2e-tests/test-applications/react-router-5/src/pages/Index.tsx new file mode 100644 index 000000000000..7789a2773224 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/src/pages/Index.tsx @@ -0,0 +1,25 @@ +import * as Sentry from '@sentry/react'; +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; +import { Link } from 'react-router-dom'; + +const Index = () => { + return ( + <> + { + const eventId = Sentry.captureException(new Error('I am an error!')); + window.capturedExceptionId = eventId; + }} + /> + + navigate + + + ); +}; + +export default Index; diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/src/pages/User.tsx b/dev-packages/e2e-tests/test-applications/react-router-5/src/pages/User.tsx new file mode 100644 index 000000000000..3b41552d35d3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/src/pages/User.tsx @@ -0,0 +1,8 @@ +// biome-ignore lint/nursery/noUnusedImports: Need React import for JSX +import * as React from 'react'; + +const User = (params: { id: string }) => { + return

Show user details for {params.id}

; +}; + +export default User; diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/src/react-app-env.d.ts b/dev-packages/e2e-tests/test-applications/react-router-5/src/react-app-env.d.ts new file mode 100644 index 000000000000..6431bc5fc6b2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/react-router-5/start-event-proxy.ts new file mode 100644 index 000000000000..4b18df9aacaf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/start-event-proxy.ts @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'react-router-5', +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/react-router-5/tests/errors.test.ts new file mode 100644 index 000000000000..bcfafe8b6624 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/tests/errors.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/event-proxy-server'; + +test('Sends correct error event', async ({ page }) => { + const errorEventPromise = waitForError('react-router-5', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.request).toEqual({ + headers: expect.any(Object), + url: 'http://localhost:3030/', + }); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + +test('Sets correct transactionName', async ({ page }) => { + const transactionPromise = waitForTransaction('react-router-5', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const errorEventPromise = waitForError('react-router-5', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + const transactionEvent = await transactionPromise; + + // Only capture error once transaction was sent + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: transactionEvent.contexts?.trace?.trace_id, + span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-router-5/tests/transactions.test.ts new file mode 100644 index 000000000000..e13c4702dc55 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/tests/transactions.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/event-proxy-server'; + +test('sends a pageload transaction with a parameterized URL', async ({ page }) => { + const transactionPromise = waitForTransaction('react-router-5', 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.react.reactrouter_v5', + }, + }, + transaction: '/', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction with a parameterized URL', async ({ page }) => { + page.on('console', msg => console.log(msg.text())); + const pageloadTxnPromise = waitForTransaction('react-router-5', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('react-router-5', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await pageloadTxnPromise; + + const linkElement = page.locator('id=navigation'); + + const [_, navigationTxn] = await Promise.all([linkElement.click(), navigationTxnPromise]); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.react.reactrouter_v5', + }, + }, + transaction: '/user/:id', + transaction_info: { + source: 'route', + }, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-5/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-router-5/tsconfig.json new file mode 100644 index 000000000000..c137d51512ef --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-5/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2018", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + }, + "include": ["src", "tests"], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } +} diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index 3fe40df8ece8..3adf59326012 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -224,14 +224,15 @@ function computeRootMatch(pathname: string): Match { export function withSentryRouting

, R extends React.ComponentType

>(Route: R): R { const componentDisplayName = (Route as any).displayName || (Route as any).name; - const activeRootSpan = getActiveRootSpan(); - const WrappedRoute: React.FC

= (props: P) => { if (props && props.computedMatch && props.computedMatch.isExact) { - getCurrentScope().setTransactionName(props.computedMatch.path); + const route = props.computedMatch.path; + const activeRootSpan = getActiveRootSpan(); + + getCurrentScope().setTransactionName(route); if (activeRootSpan) { - activeRootSpan.updateName(props.computedMatch.path); + activeRootSpan.updateName(route); activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); } } From ae1705420fbdb3994d55d2ec1db18e145251827a Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 2 May 2024 11:27:15 +0200 Subject: [PATCH 10/26] test(react): Improve react router v6 E2E tests (#11866) This updates the react router v6 E2E test to actually check what data we send. --- .../react-router-6-use-routes/package.json | 4 +- .../playwright.config.ts | 24 ++++++-- .../react-router-6-use-routes/src/index.tsx | 2 + .../start-event-proxy.ts | 6 ++ ...ur-test.spec.ts => behaviour-test.test.ts} | 2 +- .../tests/errors.test.ts | 59 +++++++++++++++++++ .../tests/fixtures/ReplayRecordingData.ts | 1 + .../tests/transactions.test.ts | 56 ++++++++++++++++++ .../react-router-6-use-routes/tsconfig.json | 7 ++- 9 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6-use-routes/start-event-proxy.ts rename dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/{behaviour-test.spec.ts => behaviour-test.test.ts} (99%) create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/transactions.test.ts diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json index a0e7c162f0f0..6a602f574863 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/package.json @@ -47,7 +47,9 @@ ] }, "devDependencies": { - "@playwright/test": "1.26.1", + "@playwright/test": "^1.43.1", + "@sentry-internal/event-proxy-server": "link:../../../event-proxy-server", + "ts-node": "^10.9.2", "axios": "1.6.0", "serve": "14.0.1" }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/playwright.config.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/playwright.config.ts index 5f93f826ebf0..abee4975ed4e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/playwright.config.ts @@ -1,6 +1,9 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; +const reactPort = 3030; +const eventProxyPort = 3031; + /** * See https://playwright.dev/docs/test-configuration. */ @@ -32,6 +35,8 @@ const config: PlaywrightTestConfig = { /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + baseURL: `http://localhost:${reactPort}`, }, /* Configure projects for major browsers */ @@ -58,13 +63,20 @@ const config: PlaywrightTestConfig = { ], /* Run your local dev server before starting the tests */ - webServer: { - command: 'pnpm start', - port: 3030, - env: { - PORT: '3030', + + webServer: [ + { + command: 'pnpm ts-node-script start-event-proxy.ts', + port: eventProxyPort, }, - }, + { + command: 'pnpm start', + port: reactPort, + env: { + PORT: `${reactPort}`, + }, + }, + ], }; export default config; diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx index bf68d694d0f7..6340fca3f04b 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/src/index.tsx @@ -35,6 +35,8 @@ Sentry.init({ // Always capture replays, so we can test this properly replaysSessionSampleRate: 1.0, replaysOnErrorSampleRate: 0.0, + + tunnel: 'http://localhost:3031', // proxy server }); Object.defineProperty(window, 'sentryReplayId', { diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/start-event-proxy.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/start-event-proxy.ts new file mode 100644 index 000000000000..a836ebb7baa6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/start-event-proxy.ts @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/event-proxy-server'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'react-router-6-use-routes', +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.spec.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts similarity index 99% rename from dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.spec.ts rename to dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts index 20f1c75ac222..1729850e778a 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.spec.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/behaviour-test.test.ts @@ -110,7 +110,7 @@ test('Sends a navigation transaction to Sentry', async ({ page }) => { await page.goto('/'); // Give pageload transaction time to finish - page.waitForTimeout(4000); + await page.waitForTimeout(4000); const linkElement = page.locator('id=navigation'); await linkElement.click(); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/errors.test.ts new file mode 100644 index 000000000000..baecddb9b96d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/errors.test.ts @@ -0,0 +1,59 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/event-proxy-server'; + +test('Sends correct error event', async ({ page }) => { + const errorEventPromise = waitForError('react-router-6-use-routes', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.request).toEqual({ + headers: expect.any(Object), + url: 'http://localhost:3030/', + }); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + +test('Sets correct transactionName', async ({ page }) => { + const transactionPromise = waitForTransaction('react-router-6-use-routes', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const errorEventPromise = waitForError('react-router-6-use-routes', event => { + return !event.type && event.exception?.values?.[0]?.value === 'I am an error!'; + }); + + await page.goto('/'); + const transactionEvent = await transactionPromise; + + // Only capture error once transaction was sent + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('I am an error!'); + + expect(errorEvent.transaction).toEqual('/'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: transactionEvent.contexts?.trace?.trace_id, + span_id: expect.not.stringContaining(transactionEvent.contexts?.trace?.span_id || ''), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts index 0b454ba12214..554fac59f88e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/fixtures/ReplayRecordingData.ts @@ -175,6 +175,7 @@ export const ReplayRecordingData = [ decodedBodySize: expect.any(Number), encodedBodySize: expect.any(Number), size: expect.any(Number), + statusCode: 200, }, }, }, diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/transactions.test.ts new file mode 100644 index 000000000000..75b42ebe6c0a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tests/transactions.test.ts @@ -0,0 +1,56 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/event-proxy-server'; + +test('sends a pageload transaction with a parameterized URL', async ({ page }) => { + const transactionPromise = waitForTransaction('react-router-6-use-routes', 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.react.reactrouter_v6', + }, + }, + transaction: '/', + transaction_info: { + source: 'route', + }, + }); +}); + +test('sends a navigation transaction with a parameterized URL', async ({ page }) => { + page.on('console', msg => console.log(msg.text())); + const pageloadTxnPromise = waitForTransaction('react-router-6-use-routes', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + const navigationTxnPromise = waitForTransaction('react-router-6-use-routes', async transactionEvent => { + return !!transactionEvent?.transaction && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await pageloadTxnPromise; + + const linkElement = page.locator('id=navigation'); + + const [_, navigationTxn] = await Promise.all([linkElement.click(), navigationTxnPromise]); + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.react.reactrouter_v6', + }, + }, + transaction: '/user/:id', + transaction_info: { + source: 'route', + }, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tsconfig.json b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tsconfig.json index 4cc95dc2689a..c137d51512ef 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tsconfig.json +++ b/dev-packages/e2e-tests/test-applications/react-router-6-use-routes/tsconfig.json @@ -16,5 +16,10 @@ "noEmit": true, "jsx": "react" }, - "include": ["src", "tests"] + "include": ["src", "tests"], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } } From 85da8cf872457f9ee87b9f80a3f9dd740ecc04ec Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 2 May 2024 13:48:20 +0200 Subject: [PATCH 11/26] feat(nextjs): Add transaction name to scope of server component (#11850) ref https://github.com/getsentry/sentry-javascript/pull/11782 --------- Co-authored-by: Francesco Novy --- packages/nextjs/src/common/types.ts | 2 +- .../common/wrapServerComponentWithSentry.ts | 83 ++++++++++--------- .../src/config/loaders/wrappingLoader.ts | 4 +- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/packages/nextjs/src/common/types.ts b/packages/nextjs/src/common/types.ts index 9e74983b078e..c182e80c2d20 100644 --- a/packages/nextjs/src/common/types.ts +++ b/packages/nextjs/src/common/types.ts @@ -5,7 +5,7 @@ import type { RequestAsyncStorage } from '../config/templates/requestAsyncStorag export type ServerComponentContext = { componentRoute: string; - componentType: string; + componentType: 'Page' | 'Layout' | 'Head' | 'Not-found' | 'Loading' | 'Unknown'; headers?: WebFetchHeaders; }; diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index 7785cb0df2fa..1234ea448a3d 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -3,10 +3,10 @@ import { SPAN_STATUS_ERROR, SPAN_STATUS_OK, captureException, - getCurrentScope, handleCallbackErrors, startSpanManual, withIsolationScope, + withScope, } from '@sentry/core'; import { propagationContextFromHeaders, winterCGHeadersToDict } from '@sentry/utils'; @@ -55,47 +55,50 @@ export function wrapServerComponentWithSentry any> const propagationContext = commonObjectToPropagationContext(context.headers, incomingPropagationContext); return withIsolationScope(isolationScope, () => { - isolationScope.setTransactionName(`${componentType} Server Component (${componentRoute})`); - getCurrentScope().setPropagationContext(propagationContext); - return startSpanManual( - { - op: 'function.nextjs', - name: `${componentType} Server Component (${componentRoute})`, - forceTransaction: true, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - }, - }, - span => { - return handleCallbackErrors( - () => originalFunction.apply(thisArg, args), - error => { - if (isNotFoundNavigationError(error)) { - // We don't want to report "not-found"s - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); - } else if (isRedirectNavigationError(error)) { - // We don't want to report redirects - span.setStatus({ code: SPAN_STATUS_OK }); - } else { - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - captureException(error, { - mechanism: { - handled: false, - }, - }); - } - }, - () => { - span.end(); + return withScope(scope => { + scope.setTransactionName(`${componentType} Server Component (${componentRoute})`); - // flushQueue should not throw - // eslint-disable-next-line @typescript-eslint/no-floating-promises - flushQueue(); + scope.setPropagationContext(propagationContext); + return startSpanManual( + { + op: 'function.nextjs', + name: `${componentType} Server Component (${componentRoute})`, + forceTransaction: true, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', }, - ); - }, - ); + }, + span => { + return handleCallbackErrors( + () => originalFunction.apply(thisArg, args), + error => { + if (isNotFoundNavigationError(error)) { + // We don't want to report "not-found"s + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); + } else if (isRedirectNavigationError(error)) { + // We don't want to report redirects + span.setStatus({ code: SPAN_STATUS_OK }); + } else { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + captureException(error, { + mechanism: { + handled: false, + }, + }); + } + }, + () => { + span.end(); + + // flushQueue should not throw + // eslint-disable-next-line @typescript-eslint/no-floating-promises + flushQueue(); + }, + ); + }, + ); + }); }); }); }, diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index 8689082de95b..a0d953d8315b 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -6,7 +6,7 @@ import * as chalk from 'chalk'; import type { RollupBuild, RollupError } from 'rollup'; import { rollup } from 'rollup'; -import type { VercelCronsConfig } from '../../common/types'; +import type { ServerComponentContext, VercelCronsConfig } from '../../common/types'; import type { LoaderThis } from './types'; // Just a simple placeholder to make referencing module consistent @@ -185,7 +185,7 @@ export default function wrappingLoader( .match(/\/?([^/]+)\.(?:js|ts|jsx|tsx)$/); if (componentTypeMatch && componentTypeMatch[1]) { - let componentType; + let componentType: ServerComponentContext['componentType']; switch (componentTypeMatch[1]) { case 'page': componentType = 'Page'; From ac59e7e1d2d8afd758720a26b9c0aba5c11dd4f4 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 2 May 2024 14:08:47 +0200 Subject: [PATCH 12/26] feat(ioredis): Add integration for `ioredis` (#11856) Integration for Node `integrations: [Sentry.experimental_redisIntegration()]` --------- Co-authored-by: Francesco Novy --- MIGRATION.md | 1 + .../node-integration-tests/package.json | 1 + .../suites/tracing/redis/docker-compose.yml | 9 ++ .../suites/tracing/redis/scenario-ioredis.js | 37 +++++++ .../suites/tracing/redis/test.ts | 40 +++++++ packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/bun/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/package.json | 1 + packages/node/src/index.ts | 1 + .../node/src/integrations/tracing/index.ts | 2 + .../node/src/integrations/tracing/redis.ts | 25 +++++ packages/remix/src/index.server.ts | 1 + yarn.lock | 100 +++++++++++++++++- 15 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis/docker-compose.yml create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis/scenario-ioredis.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/redis/test.ts create mode 100644 packages/node/src/integrations/tracing/redis.ts diff --git a/MIGRATION.md b/MIGRATION.md index 082dede43593..0e9af3dc7fc3 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -351,6 +351,7 @@ We now support the following integrations out of the box without extra configura - `mongooseIntegration`: Automatically instruments Mongoose - `mysqlIntegration`: Automatically instruments MySQL - `mysql2Integration`: Automatically instruments MySQL2 +- `redisIntegration`: Automatically instruments Redis (supported clients: ioredis) - `nestIntegration`: Automatically instruments Nest.js - `postgresIntegration`: Automatically instruments PostgreSQL - `prismaIntegration`: Automatically instruments Prisma diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index c9a81caff541..42dcaba9679f 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -44,6 +44,7 @@ "express": "^4.17.3", "graphql": "^16.3.0", "http-terminator": "^3.2.0", + "ioredis": "^5.4.1", "mongodb": "^3.7.3", "mongodb-memory-server-global": "^7.6.3", "mongoose": "^5.13.22", diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/docker-compose.yml b/dev-packages/node-integration-tests/suites/tracing/redis/docker-compose.yml new file mode 100644 index 000000000000..164d5977e33d --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3.9' + +services: + db: + image: redis:latest + restart: always + container_name: integration-tests-redis + ports: + - '6379:6379' diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/scenario-ioredis.js b/dev-packages/node-integration-tests/suites/tracing/redis/scenario-ioredis.js new file mode 100644 index 000000000000..52df06b2a386 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis/scenario-ioredis.js @@ -0,0 +1,37 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const Redis = require('ioredis'); + +const redis = new Redis({ port: 6379 }); + +async function run() { + await Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + async () => { + try { + await redis.set('test-key', 'test-value'); + + await redis.get('test-key'); + } finally { + await redis.disconnect(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/redis/test.ts b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts new file mode 100644 index 000000000000..fd441201cebc --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/redis/test.ts @@ -0,0 +1,40 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +describe('redis auto instrumentation', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('should auto-instrument `ioredis` package when using redis.set() and redis.get()', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'set test-key [1 other arguments]', + op: 'db', + data: expect.objectContaining({ + 'db.system': 'redis', + 'net.peer.name': 'localhost', + 'net.peer.port': 6379, + 'db.statement': 'set test-key [1 other arguments]', + }), + }), + expect.objectContaining({ + description: 'get test-key', + op: 'db', + data: expect.objectContaining({ + 'db.system': 'redis', + 'net.peer.name': 'localhost', + 'net.peer.port': 6379, + 'db.statement': 'get test-key', + }), + }), + ]), + }; + + createRunner(__dirname, 'scenario-ioredis.js') + .withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port=6379'] }) + .expect({ transaction: EXPECTED_TRANSACTION }) + .start(done); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index a200ca559f7a..71df2903e9eb 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -80,6 +80,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index dcf248f05112..28fa4229ec39 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -85,6 +85,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 47b40a9e19d7..b61dea4a4d77 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -106,6 +106,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index eb1e47b2fd05..c480589a5ef1 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -85,6 +85,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/packages/node/package.json b/packages/node/package.json index 2a21d2b2d162..b9c75f3b9a1b 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -63,6 +63,7 @@ "@opentelemetry/instrumentation-graphql": "0.39.0", "@opentelemetry/instrumentation-hapi": "0.36.0", "@opentelemetry/instrumentation-http": "0.48.0", + "@opentelemetry/instrumentation-ioredis": "0.40.0", "@opentelemetry/instrumentation-koa": "0.39.0", "@opentelemetry/instrumentation-mongodb": "0.39.0", "@opentelemetry/instrumentation-mongoose": "0.37.0", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index fd71dc874974..60b152f346e8 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -17,6 +17,7 @@ export { mongoIntegration } from './integrations/tracing/mongo'; export { mongooseIntegration } from './integrations/tracing/mongoose'; export { mysqlIntegration } from './integrations/tracing/mysql'; export { mysql2Integration } from './integrations/tracing/mysql2'; +export { redisIntegration } from './integrations/tracing/redis'; export { nestIntegration, setupNestErrorHandler } from './integrations/tracing/nest'; export { postgresIntegration } from './integrations/tracing/postgres'; export { prismaIntegration } from './integrations/tracing/prisma'; diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 446a23e91a3b..ec71ec7b8b60 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -12,6 +12,7 @@ import { mysqlIntegration } from './mysql'; import { mysql2Integration } from './mysql2'; import { nestIntegration } from './nest'; import { postgresIntegration } from './postgres'; +import { redisIntegration } from './redis'; /** * With OTEL, all performance integrations will be added, as OTEL only initializes them when the patched package is actually required. @@ -25,6 +26,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { mongooseIntegration(), mysqlIntegration(), mysql2Integration(), + redisIntegration(), postgresIntegration(), // For now, we do not include prisma by default because it has ESM issues // See https://github.com/prisma/prisma/issues/23410 diff --git a/packages/node/src/integrations/tracing/redis.ts b/packages/node/src/integrations/tracing/redis.ts new file mode 100644 index 000000000000..8e1eebb83b6a --- /dev/null +++ b/packages/node/src/integrations/tracing/redis.ts @@ -0,0 +1,25 @@ +import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis'; +import { defineIntegration } from '@sentry/core'; +import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; +import type { IntegrationFn } from '@sentry/types'; + +const _redisIntegration = (() => { + return { + name: 'Redis', + setupOnce() { + addOpenTelemetryInstrumentation([ + new IORedisInstrumentation({}), + // todo: implement them gradually + // new LegacyRedisInstrumentation({}), + // new RedisInstrumentation({}), + ]); + }, + }; +}) satisfies IntegrationFn; + +/** + * Redis integration for "ioredis" + * + * Capture tracing data for redis and ioredis. + */ +export const redisIntegration = defineIntegration(_redisIntegration); diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index fdb9878206f6..d528db5802f0 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -86,6 +86,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, diff --git a/yarn.lock b/yarn.lock index b4ea8976fe76..174a823c5361 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5077,6 +5077,11 @@ resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -6114,6 +6119,13 @@ dependencies: "@opentelemetry/api" "^1.0.0" +"@opentelemetry/api-logs@0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.51.0.tgz#71f296661d2215167c748ca044ff184a65d9426b" + integrity sha512-m/jtfBPEIXS1asltl8fPQtO3Sb1qMpuL61unQajUmM8zIxeMF1AlqzWXM3QedcYgTTFiJCew5uJjyhpmqhc0+g== + dependencies: + "@opentelemetry/api" "^1.0.0" + "@opentelemetry/api@1.8.0", "@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.6.0", "@opentelemetry/api@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" @@ -6235,6 +6247,15 @@ "@opentelemetry/semantic-conventions" "1.21.0" semver "^7.5.2" +"@opentelemetry/instrumentation-ioredis@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.40.0.tgz#3a747dc44c6244d7f4c8cc98a6b75b9856241eaf" + integrity sha512-Jv/fH7KhpWe4KBirsiqeUJIYrsdR2iu2l4nWhfOlRvaZ+zYIiLEzTQR6QhBbyRoAbU4OuYJzjWusOmmpGBnwng== + dependencies: + "@opentelemetry/instrumentation" "^0.51.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.0.0" + "@opentelemetry/instrumentation-koa@0.39.0": version "0.39.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.39.0.tgz#9c01d40a444e592a95b6e39ba0bbe94e096bfc31" @@ -6301,6 +6322,24 @@ "@types/pg" "8.6.1" "@types/pg-pool" "2.0.4" +"@opentelemetry/instrumentation-redis-4@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.39.0.tgz#9f9950bca3eb7e2f1cfc53a003c4eef64d8846bc" + integrity sha512-Zpfqfi83KeKgVQ0C2083GZPon3ZPYQ5E59v9FAbhubtOoUb9Rh7n111YD8FPW3sgx6JKp1odXmBmfQhWCaTOpQ== + dependencies: + "@opentelemetry/instrumentation" "^0.51.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.22.0" + +"@opentelemetry/instrumentation-redis@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.39.0.tgz#66a9d94a726deb0ef9c340ba504764ae457676a1" + integrity sha512-yjHWwufY7kfKtf20rliqlETgP32X3ZynGAfoP59NXSSHwTCZS7QMn+S+Hb0iLjwbca/iTM/BooiVFrB943kMrw== + dependencies: + "@opentelemetry/instrumentation" "^0.51.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.22.0" + "@opentelemetry/instrumentation@0.48.0", "@opentelemetry/instrumentation@^0.48.0": version "0.48.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.48.0.tgz#a6dee936e973f1270c464657a55bb570807194aa" @@ -6335,6 +6374,18 @@ semver "^7.5.2" shimmer "^1.2.1" +"@opentelemetry/instrumentation@^0.51.0": + version "0.51.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.51.0.tgz#93dbe96c87da539081d0ccd07475cfc0b0c61233" + integrity sha512-Eg/+Od5bEvzpvZQGhvMyKIkrzB9S7jW+6z9LHEI2VXhl/GrqQ3oBqlzJt4tA6pGtxRmqQWKWGM1wAbwDdW/gUA== + dependencies: + "@opentelemetry/api-logs" "0.51.0" + "@types/shimmer" "^1.0.2" + import-in-the-middle "1.7.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + "@opentelemetry/propagation-utils@^0.30.8": version "0.30.8" resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.8.tgz#5ae1468250e4f225be98b70aed994586248e2de3" @@ -6347,6 +6398,11 @@ dependencies: "@opentelemetry/core" "^1.0.0" +"@opentelemetry/redis-common@^0.36.2": + version "0.36.2" + 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": version "1.23.0" resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.23.0.tgz#4c71430f3e20c4d88b67ef5629759fae108485e5" @@ -12736,6 +12792,11 @@ clsx@^2.0.0: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + cmd-shim@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d" @@ -18886,6 +18947,21 @@ invariant@^2.2.1, invariant@^2.2.2: dependencies: loose-envify "^1.0.0" +ioredis@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.1.tgz#1c56b70b759f01465913887375ed809134296f40" + integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -20915,6 +20991,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + lodash.defaultsdeep@^4.6.1: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" @@ -20943,7 +21024,7 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== -lodash.isarguments@^3.0.0: +lodash.isarguments@^3.0.0, lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= @@ -25963,6 +26044,18 @@ redeyed@~1.0.0: dependencies: esprima "~3.0.0" +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + redux@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" @@ -27830,6 +27923,11 @@ stagehand@^1.0.0: dependencies: debug "^4.1.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" From 08a3b7c6ec9bd4d19ebe49ecfaca11a7c997e356 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 2 May 2024 14:26:40 +0200 Subject: [PATCH 13/26] feat(nextjs): Set transaction names on scope for route handlers and generation functions (#11869) for server components: https://github.com/getsentry/sentry-javascript/pull/11850 --- .../wrapGenerationFunctionWithSentry.ts | 88 ++++++++--------- .../src/common/wrapRouteHandlerWithSentry.ts | 96 ++++++++++--------- 2 files changed, 94 insertions(+), 90 deletions(-) diff --git a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts index 5a320ac4556a..f3998b693b38 100644 --- a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts +++ b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts @@ -4,10 +4,10 @@ import { SPAN_STATUS_OK, captureException, getClient, - getCurrentScope, handleCallbackErrors, startSpanManual, withIsolationScope, + withScope, } from '@sentry/core'; import type { WebFetchHeaders } from '@sentry/types'; import { propagationContextFromHeaders, winterCGHeadersToDict } from '@sentry/utils'; @@ -59,51 +59,53 @@ export function wrapGenerationFunctionWithSentry a const propagationContext = commonObjectToPropagationContext(headers, incomingPropagationContext); return withIsolationScope(isolationScope, () => { - isolationScope.setTransactionName(`${componentType}.${generationFunctionIdentifier} (${componentRoute})`); - isolationScope.setSDKProcessingMetadata({ - request: { - headers: headers ? winterCGHeadersToDict(headers) : undefined, - }, - }); + return withScope(scope => { + scope.setTransactionName(`${componentType}.${generationFunctionIdentifier} (${componentRoute})`); + isolationScope.setSDKProcessingMetadata({ + request: { + headers: headers ? winterCGHeadersToDict(headers) : undefined, + }, + }); - getCurrentScope().setExtra('route_data', data); - getCurrentScope().setPropagationContext(propagationContext); + scope.setExtra('route_data', data); + scope.setPropagationContext(propagationContext); - return startSpanManual( - { - op: 'function.nextjs', - name: `${componentType}.${generationFunctionIdentifier} (${componentRoute})`, - forceTransaction: true, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - }, - }, - span => { - return handleCallbackErrors( - () => originalFunction.apply(thisArg, args), - err => { - if (isNotFoundNavigationError(err)) { - // We don't want to report "not-found"s - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); - } else if (isRedirectNavigationError(err)) { - // We don't want to report redirects - span.setStatus({ code: SPAN_STATUS_OK }); - } else { - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - captureException(err, { - mechanism: { - handled: false, - }, - }); - } - }, - () => { - span.end(); + return startSpanManual( + { + op: 'function.nextjs', + name: `${componentType}.${generationFunctionIdentifier} (${componentRoute})`, + forceTransaction: true, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', }, - ); - }, - ); + }, + span => { + return handleCallbackErrors( + () => originalFunction.apply(thisArg, args), + err => { + if (isNotFoundNavigationError(err)) { + // We don't want to report "not-found"s + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); + } else if (isRedirectNavigationError(err)) { + // We don't want to report redirects + span.setStatus({ code: SPAN_STATUS_OK }); + } else { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + captureException(err, { + mechanism: { + handled: false, + }, + }); + } + }, + () => { + span.end(); + }, + ); + }, + ); + }); }); }); }, diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index b5da2743d97d..be378dc8cd5e 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -4,11 +4,11 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SPAN_STATUS_ERROR, captureException, - getCurrentScope, handleCallbackErrors, setHttpStatus, startSpan, withIsolationScope, + withScope, } from '@sentry/core'; import { propagationContextFromHeaders, winterCGHeadersToDict } from '@sentry/utils'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; @@ -51,57 +51,59 @@ export function wrapRouteHandlerWithSentry any>( const propagationContext = commonObjectToPropagationContext(headers, incomingPropagationContext); - return withIsolationScope(isolationScope, async () => { - isolationScope.setTransactionName(`${method} ${parameterizedRoute}`); - getCurrentScope().setPropagationContext(propagationContext); - try { - return startSpan( - { - name: `${method} ${parameterizedRoute}`, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - }, - forceTransaction: true, - }, - async span => { - const response: Response = await handleCallbackErrors( - () => originalFunction.apply(thisArg, args), - error => { - // Next.js throws errors when calling `redirect()`. We don't wanna report these. - if (isRedirectNavigationError(error)) { - // Don't do anything - } else if (isNotFoundNavigationError(error) && span) { - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); - } else { - captureException(error, { - mechanism: { - handled: false, - }, - }); - } + return withIsolationScope(isolationScope, () => { + return withScope(async scope => { + scope.setTransactionName(`${method} ${parameterizedRoute}`); + scope.setPropagationContext(propagationContext); + try { + return startSpan( + { + name: `${method} ${parameterizedRoute}`, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', }, - ); + forceTransaction: true, + }, + async span => { + const response: Response = await handleCallbackErrors( + () => originalFunction.apply(thisArg, args), + error => { + // Next.js throws errors when calling `redirect()`. We don't wanna report these. + if (isRedirectNavigationError(error)) { + // Don't do anything + } else if (isNotFoundNavigationError(error) && span) { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); + } else { + captureException(error, { + mechanism: { + handled: false, + }, + }); + } + }, + ); - try { - if (span && response.status) { - setHttpStatus(span, response.status); + try { + if (span && response.status) { + setHttpStatus(span, response.status); + } + } catch { + // best effort - response may be undefined? } - } catch { - // best effort - response may be undefined? - } - return response; - }, - ); - } finally { - if (!platformSupportsStreaming() || process.env.NEXT_RUNTIME === 'edge') { - // 1. Edge transport requires manual flushing - // 2. Lambdas require manual flushing to prevent execution freeze before the event is sent - await flushQueue(); + return response; + }, + ); + } finally { + if (!platformSupportsStreaming() || process.env.NEXT_RUNTIME === 'edge') { + // 1. Edge transport requires manual flushing + // 2. Lambdas require manual flushing to prevent execution freeze before the event is sent + await flushQueue(); + } } - } + }); }); }); }, From 1c4e7768f3d1a12335a6284d52f7e8dde54937f8 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 2 May 2024 15:12:23 +0100 Subject: [PATCH 14/26] feat: Add `tunnel` support to multiplexed transport (#11806) --- packages/core/src/baseclient.ts | 1 + packages/core/src/transports/multiplexed.ts | 44 ++++++++++++++----- .../test/lib/transports/multiplexed.test.ts | 31 ++++++++++--- packages/types/src/transport.ts | 6 +++ 4 files changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 981566469115..455cba2d9771 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -141,6 +141,7 @@ export abstract class BaseClient implements Client { options._metadata ? options._metadata.sdk : undefined, ); this._transport = options.transport({ + tunnel: this._options.tunnel, recordDroppedEvent: this.recordDroppedEvent.bind(this), ...options.transportOptions, url, diff --git a/packages/core/src/transports/multiplexed.ts b/packages/core/src/transports/multiplexed.ts index faeb5a0fbd81..d740f1fde3ec 100644 --- a/packages/core/src/transports/multiplexed.ts +++ b/packages/core/src/transports/multiplexed.ts @@ -7,7 +7,7 @@ import type { Transport, TransportMakeRequestResponse, } from '@sentry/types'; -import { dsnFromString, forEachEnvelopeItem } from '@sentry/utils'; +import { createEnvelope, dsnFromString, forEachEnvelopeItem } from '@sentry/utils'; import { getEnvelopeEndpointWithUrlEncodedAuth } from '../api'; @@ -57,6 +57,7 @@ function makeOverrideReleaseTransport( const transport = createTransport(options); return { + ...transport, send: async (envelope: Envelope): Promise => { const event = eventFromEnvelope(envelope, ['event', 'transaction', 'profile', 'replay_event']); @@ -65,11 +66,23 @@ function makeOverrideReleaseTransport( } return transport.send(envelope); }, - flush: timeout => transport.flush(timeout), }; }; } +/** Overrides the DSN in the envelope header */ +function overrideDsn(envelope: Envelope, dsn: string): Envelope { + return createEnvelope( + dsn + ? { + ...envelope[0], + dsn, + } + : envelope[0], + envelope[1], + ); +} + /** * Creates a transport that can send events to different DSNs depending on the envelope contents. */ @@ -79,26 +92,30 @@ export function makeMultiplexedTransport( ): (options: TO) => Transport { return options => { const fallbackTransport = createTransport(options); - const otherTransports: Record = {}; + const otherTransports: Map = new Map(); - function getTransport(dsn: string, release: string | undefined): Transport | undefined { + function getTransport(dsn: string, release: string | undefined): [string, Transport] | undefined { // We create a transport for every unique dsn/release combination as there may be code from multiple releases in // use at the same time const key = release ? `${dsn}:${release}` : dsn; - if (!otherTransports[key]) { + let transport = otherTransports.get(key); + + if (!transport) { const validatedDsn = dsnFromString(dsn); if (!validatedDsn) { return undefined; } - const url = getEnvelopeEndpointWithUrlEncodedAuth(validatedDsn); + const url = getEnvelopeEndpointWithUrlEncodedAuth(validatedDsn, options.tunnel); - otherTransports[key] = release + transport = release ? makeOverrideReleaseTransport(createTransport, release)({ ...options, url }) : createTransport({ ...options, url }); + + otherTransports.set(key, transport); } - return otherTransports[key]; + return [dsn, transport]; } async function send(envelope: Envelope): Promise { @@ -115,20 +132,23 @@ export function makeMultiplexedTransport( return getTransport(result.dsn, result.release); } }) - .filter((t): t is Transport => !!t); + .filter((t): t is [string, Transport] => !!t); // If we have no transports to send to, use the fallback transport if (transports.length === 0) { - transports.push(fallbackTransport); + // Don't override the DSN in the header for the fallback transport. '' is falsy + transports.push(['', fallbackTransport]); } - const results = await Promise.all(transports.map(transport => transport.send(envelope))); + const results = await Promise.all( + transports.map(([dsn, transport]) => transport.send(overrideDsn(envelope, dsn))), + ); return results[0]; } async function flush(timeout: number | undefined): Promise { - const allTransports = [...Object.keys(otherTransports).map(dsn => otherTransports[dsn]), fallbackTransport]; + const allTransports = [...otherTransports.values(), fallbackTransport]; const results = await Promise.all(allTransports.map(transport => transport.flush(timeout))); return results.every(r => r); } diff --git a/packages/core/test/lib/transports/multiplexed.test.ts b/packages/core/test/lib/transports/multiplexed.test.ts index c2d0d2f1d318..59e71e31304f 100644 --- a/packages/core/test/lib/transports/multiplexed.test.ts +++ b/packages/core/test/lib/transports/multiplexed.test.ts @@ -1,6 +1,7 @@ import type { BaseTransportOptions, ClientReport, + Envelope, EventEnvelope, EventItem, TransactionEvent, @@ -47,7 +48,7 @@ const CLIENT_REPORT_ENVELOPE = createClientReportEnvelope( 123456, ); -type Assertion = (url: string, release: string | undefined, body: string | Uint8Array) => void; +type Assertion = (url: string, release: string | undefined, body: Envelope) => void; const createTestTransport = (...assertions: Assertion[]): ((options: BaseTransportOptions) => Transport) => { return (options: BaseTransportOptions) => @@ -60,7 +61,7 @@ const createTestTransport = (...assertions: Assertion[]): ((options: BaseTranspo const event = eventFromEnvelope(parseEnvelope(request.body), ['event']); - assertion(options.url, event?.release, request.body); + assertion(options.url, event?.release, parseEnvelope(request.body)); resolve({ statusCode: 200 }); }); }); @@ -105,11 +106,12 @@ describe('makeMultiplexedTransport', () => { }); it('DSN can be overridden via match callback', async () => { - expect.assertions(1); + expect.assertions(2); const makeTransport = makeMultiplexedTransport( - createTestTransport(url => { + createTestTransport((url, _, env) => { expect(url).toBe(DSN2_URL); + expect(env[0].dsn).toBe(DSN2); }), () => [DSN2], ); @@ -119,12 +121,13 @@ describe('makeMultiplexedTransport', () => { }); it('DSN and release can be overridden via match callback', async () => { - expect.assertions(2); + expect.assertions(3); const makeTransport = makeMultiplexedTransport( - createTestTransport((url, release) => { + createTestTransport((url, release, env) => { expect(url).toBe(DSN2_URL); expect(release).toBe('something@1.0.0'); + expect(env[0].dsn).toBe(DSN2); }), () => [{ dsn: DSN2, release: 'something@1.0.0' }], ); @@ -133,6 +136,22 @@ describe('makeMultiplexedTransport', () => { await transport.send(ERROR_ENVELOPE); }); + it('URL can be overridden by tunnel option', async () => { + expect.assertions(3); + + const makeTransport = makeMultiplexedTransport( + createTestTransport((url, release, env) => { + expect(url).toBe('http://google.com'); + expect(release).toBe('something@1.0.0'); + expect(env[0].dsn).toBe(DSN2); + }), + () => [{ dsn: DSN2, release: 'something@1.0.0' }], + ); + + const transport = makeTransport({ url: DSN1_URL, ...transportOptions, tunnel: 'http://google.com' }); + await transport.send(ERROR_ENVELOPE); + }); + it('match callback can return multiple DSNs', async () => { expect.assertions(2); diff --git a/packages/types/src/transport.ts b/packages/types/src/transport.ts index 07186b141420..39741bf111de 100644 --- a/packages/types/src/transport.ts +++ b/packages/types/src/transport.ts @@ -15,6 +15,12 @@ export type TransportMakeRequestResponse = { }; export interface InternalBaseTransportOptions { + /** + * @ignore + * Users should pass the tunnel property via the init/client options. + * This is only used by the SDK to pass the tunnel to the transport. + */ + tunnel?: string; bufferSize?: number; recordDroppedEvent: Client['recordDroppedEvent']; } From dc7b1f55c77de23cd02f305130985881d291c14b Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 2 May 2024 16:36:40 +0200 Subject: [PATCH 15/26] fix(node): Fix nest.js error handler (#11874) This was brought up here https://github.com/getsentry/sentry-javascript/discussions/5578#discussioncomment-9284317, our error handler implementation was too naive, and our tests not ideal - the tests only checked that stuff is sent to sentry (which it was!) but not that the page otherwise worked. Now, I updated the test to ensure this works as expected. With this PR, the signature for `Sentry.setupNestErrorHandler()` changes ( breaking change, but sadly required at this point). You have to pass in an exception filter, which we'll extend to _also_ send exceptions to Sentry: ```js import { BaseExceptionFilter, HttpAdapterHost } from '@nestjs/core'; const { httpAdapter } = app1.get(HttpAdapterHost); Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); ``` This is a bit more involved, but also allows you to use a custom filter if needed (we can extend _any_ exception filter). --- .../node-nestjs-app/src/main.ts | 6 ++-- .../node-nestjs-app/tests/errors.test.ts | 9 ++++-- .../nestjs-errors-no-express/scenario.ts | 5 +-- .../suites/tracing/nestjs-errors/scenario.ts | 5 +-- .../tracing/nestjs-no-express/scenario.ts | 5 +-- .../node/src/integrations/tracing/nest.ts | 32 +++++++++++++------ 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/main.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/main.ts index f852b29c8e06..b168cc19e7c3 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-app/src/main.ts @@ -1,4 +1,4 @@ -import { NestFactory } from '@nestjs/core'; +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; import * as Sentry from '@sentry/node'; import { AppModule1, AppModule2 } from './app.module'; @@ -15,7 +15,9 @@ async function bootstrap() { }); const app1 = await NestFactory.create(AppModule1); - Sentry.setupNestErrorHandler(app1); + + const { httpAdapter } = app1.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); await app1.listen(app1Port); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/errors.test.ts index ba7b0cf1849b..7ad93315a84f 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-app/tests/errors.test.ts @@ -47,9 +47,12 @@ test('Sends exception to Sentry', async ({ baseURL }) => { }); try { - axios.get(`${baseURL}/test-exception/123`); - } catch { - // this results in an error, but we don't care - we want to check the error event + await axios.get(`${baseURL}/test-exception/123`); + // Should never be reached! + expect(false).toBe(true); + } catch (error) { + expect(error).toBeInstanceOf(AxiosError); + expect(error.response?.status).toBe(500); } const errorEvent = await errorEventPromise; diff --git a/dev-packages/node-integration-tests/suites/tracing/nestjs-errors-no-express/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/nestjs-errors-no-express/scenario.ts index bcf4c3adb432..295eba00fd9a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/nestjs-errors-no-express/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/nestjs-errors-no-express/scenario.ts @@ -15,7 +15,7 @@ Sentry.init({ }); import { Controller, Get, Injectable, Module, Param } from '@nestjs/common'; -import { NestFactory } from '@nestjs/core'; +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; const port = 3480; @@ -49,7 +49,8 @@ class AppModule {} async function init(): Promise { const app = await NestFactory.create(AppModule); - Sentry.setupNestErrorHandler(app); + const { httpAdapter } = app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); await app.listen(port); sendPortToRunner(port); } diff --git a/dev-packages/node-integration-tests/suites/tracing/nestjs-errors/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/nestjs-errors/scenario.ts index 8a00c25fab7a..09a59eb8c7c7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/nestjs-errors/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/nestjs-errors/scenario.ts @@ -13,7 +13,7 @@ Sentry.init({ }); import { Controller, Get, Injectable, Module, Param } from '@nestjs/common'; -import { NestFactory } from '@nestjs/core'; +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; const port = 3460; @@ -47,7 +47,8 @@ class AppModule {} async function init(): Promise { const app = await NestFactory.create(AppModule); - Sentry.setupNestErrorHandler(app); + const { httpAdapter } = app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); await app.listen(port); sendPortToRunner(port); } diff --git a/dev-packages/node-integration-tests/suites/tracing/nestjs-no-express/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/nestjs-no-express/scenario.ts index cffc8de263d2..209f517193dc 100644 --- a/dev-packages/node-integration-tests/suites/tracing/nestjs-no-express/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/nestjs-no-express/scenario.ts @@ -15,7 +15,7 @@ Sentry.init({ }); import { Controller, Get, Injectable, Module, Param } from '@nestjs/common'; -import { NestFactory } from '@nestjs/core'; +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; const port = 3470; @@ -49,7 +49,8 @@ class AppModule {} async function init(): Promise { const app = await NestFactory.create(AppModule); - Sentry.setupNestErrorHandler(app); + const { httpAdapter } = app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); await app.listen(port); sendPortToRunner(port); } diff --git a/packages/node/src/integrations/tracing/nest.ts b/packages/node/src/integrations/tracing/nest.ts index b0b143774d2d..220b60d68a59 100644 --- a/packages/node/src/integrations/tracing/nest.ts +++ b/packages/node/src/integrations/tracing/nest.ts @@ -17,8 +17,14 @@ interface MinimalNestJsExecutionContext { }; }; } + +interface NestJsErrorFilter { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + catch(exception: any, host: any): void; +} + interface MinimalNestJsApp { - useGlobalFilters: (arg0: { catch(exception: unknown): void }) => void; + useGlobalFilters: (arg0: NestJsErrorFilter) => void; useGlobalInterceptors: (interceptor: { intercept: (context: MinimalNestJsExecutionContext, next: { handle: () => void }) => void; }) => void; @@ -40,16 +46,10 @@ const _nestIntegration = (() => { */ export const nestIntegration = defineIntegration(_nestIntegration); -const SentryNestExceptionFilter = { - catch(exception: unknown) { - captureException(exception); - }, -}; - /** * Setup an error handler for Nest. */ -export function setupNestErrorHandler(app: MinimalNestJsApp): void { +export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsErrorFilter): void { app.useGlobalInterceptors({ intercept(context, next) { if (getIsolationScope() === getDefaultIsolationScope()) { @@ -65,5 +65,19 @@ export function setupNestErrorHandler(app: MinimalNestJsApp): void { }, }); - app.useGlobalFilters(SentryNestExceptionFilter); + const wrappedFilter = new Proxy(baseFilter, { + get(target, prop, receiver) { + if (prop === 'catch') { + const originalCatch = Reflect.get(target, prop, receiver); + + return (exception: unknown, host: unknown) => { + captureException(exception); + return originalCatch.apply(target, [exception, host]); + }; + } + return Reflect.get(target, prop, receiver); + }, + }); + + app.useGlobalFilters(wrappedFilter); } From 53cb600251fd842cac198d8b48e1fc6f6c439238 Mon Sep 17 00:00:00 2001 From: Ryan Albrecht Date: Thu, 2 May 2024 08:17:34 -0700 Subject: [PATCH 16/26] ci: Put CODEOWNERS in the correct folder (#11861) Per the docs: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-file-location > file called CODEOWNERS in the .github/, root, or docs/ directory of the repository, in the branch where you'd like to add the code owners. But also, in our case: > If CODEOWNERS files exist in more than one of those locations, GitHub will search for them in that order and use the first one it finds. So we just need the one file. --- .github/CODEOWNERS | 6 +++++- CODEOWNERS | 5 ----- 2 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8b137891791f..3bb7aa3860ff 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,5 @@ - +packages/replay-internal @getsentry/replay-sdk-web +packages/replay-worker @getsentry/replay-sdk-web +packages/replay-canvas @getsentry/replay-sdk-web +packages/feedback @getsentry/feedback-sdk +dev-packages/browser-integration-tests/suites/replay @getsentry/replay-sdk-web diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index 6ae2679deafa..000000000000 --- a/CODEOWNERS +++ /dev/null @@ -1,5 +0,0 @@ -packages/replay @getsentry/replay-sdk-web -packages/replay-worker @getsentry/replay-sdk-web -packages/replay-canvas @getsentry/replay-sdk-web -packages/feedback @getsentry/replay-sdk-web -dev-packages/browser-integration-tests/suites/replay @getsentry/replay-sdk-web From a6915fcff314d7953d95c5daab59bb6fa939b8e9 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 2 May 2024 17:49:26 +0200 Subject: [PATCH 17/26] build(yarn): Update lockfile (#11873) --- yarn.lock | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/yarn.lock b/yarn.lock index 174a823c5361..a4155d7f446a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6322,24 +6322,6 @@ "@types/pg" "8.6.1" "@types/pg-pool" "2.0.4" -"@opentelemetry/instrumentation-redis-4@0.39.0": - version "0.39.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.39.0.tgz#9f9950bca3eb7e2f1cfc53a003c4eef64d8846bc" - integrity sha512-Zpfqfi83KeKgVQ0C2083GZPon3ZPYQ5E59v9FAbhubtOoUb9Rh7n111YD8FPW3sgx6JKp1odXmBmfQhWCaTOpQ== - dependencies: - "@opentelemetry/instrumentation" "^0.51.0" - "@opentelemetry/redis-common" "^0.36.2" - "@opentelemetry/semantic-conventions" "^1.22.0" - -"@opentelemetry/instrumentation-redis@0.39.0": - version "0.39.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.39.0.tgz#66a9d94a726deb0ef9c340ba504764ae457676a1" - integrity sha512-yjHWwufY7kfKtf20rliqlETgP32X3ZynGAfoP59NXSSHwTCZS7QMn+S+Hb0iLjwbca/iTM/BooiVFrB943kMrw== - dependencies: - "@opentelemetry/instrumentation" "^0.51.0" - "@opentelemetry/redis-common" "^0.36.2" - "@opentelemetry/semantic-conventions" "^1.22.0" - "@opentelemetry/instrumentation@0.48.0", "@opentelemetry/instrumentation@^0.48.0": version "0.48.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.48.0.tgz#a6dee936e973f1270c464657a55bb570807194aa" From 3355a053ff26be0b2aace5b12e4b9dcd1fb12dea Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 2 May 2024 18:23:03 +0200 Subject: [PATCH 18/26] feat(node): Support Node 22 (#11871) Port from v7: https://github.com/getsentry/sentry-javascript/pull/11754 --------- Co-authored-by: Abhijeet Prasad Co-authored-by: JonasBa --- .github/workflows/build.yml | 37 ++++++++++++++----- packages/profiling-node/README.md | 2 +- packages/profiling-node/binding.gyp | 12 +++++- .../profiling-node/bindings/cpu_profiler.cc | 3 ++ packages/profiling-node/package.json | 4 +- packages/profiling-node/src/cpu_profiler.ts | 21 +++++++++++ yarn.lock | 10 ++--- 7 files changed, 71 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1027efa79677..c44609b1e2c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -509,7 +509,7 @@ jobs: strategy: fail-fast: false matrix: - node: [14, 16, 18, 20, 21] + node: [14, 16, 18, 20, 22] steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -569,7 +569,7 @@ jobs: strategy: fail-fast: false matrix: - node: [14, 16, 18, 20, 21] + node: [14, 16, 18, 20, 22] steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v4 @@ -832,12 +832,12 @@ jobs: strategy: fail-fast: false matrix: - node: [14, 16, 18, 20, 21] + node: [14, 16, 18, 20, 22] typescript: - false include: # Only check typescript for latest version (to streamline CI) - - node: 20 + - node: 22 typescript: '3.8' steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) @@ -874,7 +874,7 @@ jobs: strategy: fail-fast: false matrix: - node: [18, 20, 21] + node: [18, 20, 22] remix: [1, 2] # Remix v2 only supports Node 18+, so run Node 14, 16 tests separately include: @@ -1294,6 +1294,8 @@ jobs: node: 18 - os: ubuntu-20.04 node: 20 + - os: ubuntu-20.04 + node: 22 # x64 musl - os: ubuntu-20.04 @@ -1305,6 +1307,9 @@ jobs: - os: ubuntu-20.04 container: node:20-alpine3.17 node: 20 + - os: ubuntu-20.04 + container: node:22-alpine3.18 + node: 22 # arm64 glibc - os: ubuntu-20.04 @@ -1316,6 +1321,9 @@ jobs: - os: ubuntu-20.04 arch: arm64 node: 20 + - os: ubuntu-20.04 + arch: arm64 + node: 22 # arm64 musl - os: ubuntu-20.04 @@ -1330,6 +1338,10 @@ jobs: arch: arm64 container: node:20-alpine3.17 node: 20 + - os: ubuntu-20.04 + arch: arm64 + container: node:22-alpine3.18 + node: 22 # macos x64 - os: macos-11 @@ -1341,35 +1353,42 @@ jobs: - os: macos-11 node: 20 arch: x64 + - os: macos-11 + node: 22 + arch: x64 # macos arm64 - os: macos-12 arch: arm64 node: 16 target_platform: darwin - - os: macos-12 arch: arm64 node: 18 target_platform: darwin - - os: macos-12 arch: arm64 node: 20 target_platform: darwin + - os: macos-12 + arch: arm64 + node: 22 + target_platform: darwin # windows x64 - os: windows-2022 node: 16 arch: x64 - - os: windows-2022 node: 18 arch: x64 - - os: windows-2022 node: 20 arch: x64 + - os: windows-2022 + node: 22 + arch: x64 + steps: - name: Setup (alpine) if: contains(matrix.container, 'alpine') diff --git a/packages/profiling-node/README.md b/packages/profiling-node/README.md index f2c9920f0ff1..7203752643ed 100644 --- a/packages/profiling-node/README.md +++ b/packages/profiling-node/README.md @@ -66,7 +66,7 @@ npm i -g windows-build-tools ### Prebuilt binaries -We currently ship prebuilt binaries for a few of the most common platforms and node versions (v16-20). +We currently ship prebuilt binaries for a few of the most common platforms and node versions (v16-22). - macOS x64 - Linux ARM64 (musl) diff --git a/packages/profiling-node/binding.gyp b/packages/profiling-node/binding.gyp index fd2322db4e94..1c1aad075e39 100644 --- a/packages/profiling-node/binding.gyp +++ b/packages/profiling-node/binding.gyp @@ -6,5 +6,15 @@ # Silence gcc8 deprecation warning https://github.com/nodejs/nan/issues/807#issuecomment-455750192 "cflags": ["-Wno-cast-function-type"] }, - ] + ], + 'conditions': [ + [ 'OS=="win"', { + 'defines': [ + # Stop from defining macros that conflict with + # std::min() and std::max(). We don't use (much) + # but we still inherit it from uv.h. + 'NOMINMAX', + ] + }], + ], } diff --git a/packages/profiling-node/bindings/cpu_profiler.cc b/packages/profiling-node/bindings/cpu_profiler.cc index f269990f425b..64a82ba61910 100644 --- a/packages/profiling-node/bindings/cpu_profiler.cc +++ b/packages/profiling-node/bindings/cpu_profiler.cc @@ -1,3 +1,6 @@ +#ifndef NOMINMAX +#define NOMINMAX +#endif #include #include diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index 0353281a8699..794fd0a8b6e7 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -82,11 +82,11 @@ "@sentry/types": "8.0.0-beta.5", "@sentry/utils": "8.0.0-beta.5", "detect-libc": "^2.0.2", - "node-abi": "^3.52.0" + "node-abi": "^3.61.0" }, "devDependencies": { "@types/node": "16.18.70", - "@types/node-abi": "^3.0.0", + "@types/node-abi": "^3.0.3", "clang-format": "^1.8.0", "cross-env": "^7.0.3", "node-gyp": "^9.4.1", diff --git a/packages/profiling-node/src/cpu_profiler.ts b/packages/profiling-node/src/cpu_profiler.ts index f2769fe5be45..b31e6f4d25d0 100644 --- a/packages/profiling-node/src/cpu_profiler.ts +++ b/packages/profiling-node/src/cpu_profiler.ts @@ -47,6 +47,9 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { if (abi === '115') { return require('../sentry_cpu_profiler-darwin-x64-115.node'); } + if (abi === '127') { + return require('../sentry_cpu_profiler-darwin-x64-127.node'); + } } if (arch === 'arm64') { @@ -59,6 +62,9 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { if (abi === '115') { return require('../sentry_cpu_profiler-darwin-arm64-115.node'); } + if (abi === '127') { + return require('../sentry_cpu_profiler-darwin-arm64-127.node'); + } } } @@ -73,6 +79,9 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { if (abi === '115') { return require('../sentry_cpu_profiler-win32-x64-115.node'); } + if (abi === '127') { + return require('../sentry_cpu_profiler-win32-x64-127.node'); + } } } @@ -88,6 +97,9 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { if (abi === '115') { return require('../sentry_cpu_profiler-linux-x64-musl-115.node'); } + if (abi === '127') { + return require('../sentry_cpu_profiler-linux-x64-musl-127.node'); + } } if (stdlib === 'glibc') { if (abi === '93') { @@ -99,6 +111,9 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { if (abi === '115') { return require('../sentry_cpu_profiler-linux-x64-glibc-115.node'); } + if (abi === '127') { + return require('../sentry_cpu_profiler-linux-x64-glibc-127.node'); + } } } if (arch === 'arm64') { @@ -112,6 +127,9 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { if (abi === '115') { return require('../sentry_cpu_profiler-linux-arm64-musl-115.node'); } + if (abi === '127') { + return require('../sentry_cpu_profiler-linux-arm64-musl-127.node'); + } } if (stdlib === 'glibc') { if (abi === '93') { @@ -123,6 +141,9 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { if (abi === '115') { return require('../sentry_cpu_profiler-linux-arm64-glibc-115.node'); } + if (abi === '127') { + return require('../sentry_cpu_profiler-linux-arm64-glibc-127.node'); + } } } } diff --git a/yarn.lock b/yarn.lock index a4155d7f446a..6ef60581df1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8602,7 +8602,7 @@ dependencies: "@types/unist" "^2" -"@types/node-abi@^3.0.0": +"@types/node-abi@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/node-abi/-/node-abi-3.0.3.tgz#a8334d75fe45ccd4cdb2a6c1ae82540a7a76828c" integrity sha512-5oos6sivyXcDEuVC5oX3+wLwfgrGZu4NIOn826PGAjPCHsqp2zSPTGU7H1Tv+GZBOiDUY3nBXY1MdaofSEt4fw== @@ -22942,10 +22942,10 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" -node-abi@^3.52.0: - version "3.54.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.54.0.tgz#f6386f7548817acac6434c6cba02999c9aebcc69" - integrity sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA== +node-abi@^3.61.0: + version "3.61.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.61.0.tgz#9248f8b8e35dbae2fafeecd6240c5a017ea23f3f" + integrity sha512-dYDO1rxzvMXjEMi37PBeFuYgwh3QZpsw/jt+qOmnRSwiV4z4c+OLoRlTa3V8ID4TrkSQpzCVc9OI2sstFaINfQ== dependencies: semver "^7.3.5" From 85c754af1f486aa9cb5bda5705532f716a6a6436 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 3 May 2024 09:52:14 +0200 Subject: [PATCH 19/26] docs: Clarify Node.js specific imports in Next.js register hook migration guide (#11878) --- MIGRATION.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 0e9af3dc7fc3..a5f64a76f181 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -833,6 +833,24 @@ The following is an example of how to initialize the serverside SDK in a Next.js } ``` + If you need to import a Node.js specific integration (like for example `@sentry/profiling-node`), you will have to + import the package using a dynamic import to prevent Next.js from bundling Node.js APIs into bundles for other + runtime environments (like the Browser or the Edge runtime). You can do so as follows: + + ```ts + import * as Sentry from '@sentry/nextjs'; + + export async function register() { + if (process.env.NEXT_RUNTIME === 'nodejs') { + const { nodeProfilingIntegration } = await import('@sentry/profiling-node'); + Sentry.init({ + dsn: 'YOUR_DSN', + integrations: [nodeProfilingIntegration()], + }); + } + } + ``` + Note that you can initialize the SDK differently depending on which server runtime is being used. If you are using a From 56197af54b8d502716ce2c0cbfe9aa7bf2250d42 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 May 2024 09:52:23 +0200 Subject: [PATCH 20/26] feat(feedback): Add `captureFeedback` method (#11428) This PR adds a new `captureFeedback` method which is exported from all SDKs. This method can be used to capture a (new) user feedback from any package. We follow the same semantics as for other capture methods like `captureException()` or `captureMessage()`: The method returns a string, which is the event id. We do not wait for sending to be successful or not, we just try to send it and return. You can both set an `associatedEventId` (which replaces the event_id of the "old" `captureUserFeedback`), and also pass attachments to send along (which for now are sent as a separate envelope). For usage in the modal UI, there is still `sendFeedback` which continues to return a promise that resolves with the event ID, or rejects with a meaningful error message if sending fails. This also deprecates `captureUserFeedback()`, which is only exported in browser. We cannot remove this yet because `captureFeedback` will only work on newer self-hosted instances, so not all users can easily update. We can/should remove this in v9. Includes https://github.com/getsentry/sentry-javascript/pull/11626 Fixes 49946 --- .../suites/feedback/captureFeedback/test.ts | 4 + .../hasSampling/test.ts | 4 + .../suites/tracing/trace-lifetime/init.js | 2 +- .../tracing/trace-lifetime/navigation/test.ts | 51 +- .../trace-lifetime/pageload-meta/test.ts | 41 +- .../tracing/trace-lifetime/pageload/test.ts | 41 +- packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/browser/src/client.ts | 2 + packages/browser/src/exports.ts | 1 + packages/browser/src/index.bundle.feedback.ts | 2 + .../index.bundle.tracing.replay.feedback.ts | 1 + packages/browser/src/index.ts | 1 + packages/browser/src/sdk.ts | 3 + packages/bun/src/index.ts | 1 + packages/core/src/baseclient.ts | 5 +- packages/core/src/envelope.ts | 31 -- packages/core/src/feedback.ts | 38 ++ packages/core/src/index.ts | 3 +- packages/core/test/lib/feedback.test.ts | 451 ++++++++++++++++++ packages/core/test/mocks/client.ts | 7 +- packages/deno/src/index.ts | 1 + .../feedback/src/core/sendFeedback.test.ts | 392 ++++++++++++++- packages/feedback/src/core/sendFeedback.ts | 109 ++--- .../feedback/src/modal/components/Form.tsx | 13 +- .../feedback/src/util/prepareFeedbackEvent.ts | 43 -- packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + packages/remix/src/index.server.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + packages/types/src/feedback/sendFeedback.ts | 14 +- packages/vercel-edge/src/index.ts | 1 + 32 files changed, 1094 insertions(+), 174 deletions(-) create mode 100644 packages/core/src/feedback.ts create mode 100644 packages/core/test/lib/feedback.test.ts delete mode 100644 packages/feedback/src/util/prepareFeedbackEvent.ts diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts index b58efe858f25..01e56a9c1946 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts @@ -53,6 +53,10 @@ sentryTest('should capture feedback', async ({ getLocalTestPath, page }) => { source: 'widget', url: expect.stringContaining('/dist/index.html'), }, + trace: { + trace_id: expect.stringMatching(/\w{32}/), + span_id: expect.stringMatching(/\w{16}/), + }, }, level: 'info', timestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts index 6768bf838e75..3c46d1c79964 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts @@ -89,6 +89,10 @@ sentryTest('should capture feedback', async ({ forceFlushReplay, getLocalTestPat source: 'widget', url: expect.stringContaining('/dist/index.html'), }, + trace: { + trace_id: expect.stringMatching(/\w{32}/), + span_id: expect.stringMatching(/\w{16}/), + }, }, level: 'info', timestamp: expect.any(Number), diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/init.js index 7cd076a052e5..9c34f4d99f69 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration()], + integrations: [Sentry.browserTracingIntegration(), Sentry.feedbackIntegration()], tracePropagationTargets: ['http://example.com'], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts index c3e4bf936288..a05f0da3d91d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts @@ -2,6 +2,7 @@ import { expect } from '@playwright/test'; import type { Event, SpanEnvelope } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import type { EventAndTraceHeader } from '../../../../utils/helpers'; +import { shouldSkipFeedbackTest } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, @@ -134,7 +135,7 @@ sentryTest('error during navigation has new navigation traceId', async ({ getLoc const url = await getLocalTestUrl({ testDir: __dirname }); - // ensure navigation transaction is finished + // ensure pageload transaction is finished await getFirstSentryEnvelopeRequest(page, url); const envelopeRequestsPromise = getMultipleSentryEnvelopeRequests( @@ -202,7 +203,7 @@ sentryTest( }); }); - // ensure navigation transaction is finished + // ensure pageload transaction is finished await getFirstSentryEnvelopeRequest(page, url); const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( @@ -276,7 +277,7 @@ sentryTest( }); }); - // ensure navigation transaction is finished + // ensure pageload transaction is finished await getFirstSentryEnvelopeRequest(page, url); const navigationEventPromise = getFirstSentryEnvelopeRequest( @@ -456,3 +457,47 @@ sentryTest( ); }, ); + +sentryTest( + 'user feedback event after navigation has navigation traceId in headers', + async ({ getLocalTestPath, page }) => { + if (shouldSkipTracingTest() || shouldSkipFeedbackTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestPath({ testDir: __dirname }); + + // ensure pageload transaction is finished + await getFirstSentryEnvelopeRequest(page, url); + + const navigationEvent = await getFirstSentryEnvelopeRequest(page, `${url}#foo`); + + const navigationTraceContext = navigationEvent.contexts?.trace; + expect(navigationTraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), + span_id: expect.stringMatching(/^[0-9a-f]{16}$/), + }); + expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + + const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); + + await page.getByText('Report a Bug').click(); + expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.locator('[data-sentry-feedback] .btn--primary').click(); + + const feedbackEvent = await feedbackEventPromise; + + expect(feedbackEvent.type).toEqual('feedback'); + + const feedbackTraceContext = feedbackEvent.contexts?.trace; + + expect(feedbackTraceContext).toMatchObject({ + trace_id: navigationTraceContext?.trace_id, + span_id: expect.stringMatching(/^[0-9a-f]{16}$/), + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts index f206e9292b9f..78582a8369c1 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts @@ -1,7 +1,8 @@ import { expect } from '@playwright/test'; -import type { SpanEnvelope } from '@sentry/types'; +import type { Event, SpanEnvelope } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import type { EventAndTraceHeader } from '../../../../utils/helpers'; +import { shouldSkipFeedbackTest } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, @@ -429,3 +430,41 @@ sentryTest( expect(headers['baggage']).toBe(META_TAG_BAGGAGE); }, ); + +sentryTest('user feedback event after pageload has pageload traceId in headers', async ({ getLocalTestPath, page }) => { + if (shouldSkipTracingTest() || shouldSkipFeedbackTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestPath({ testDir: __dirname }); + + const pageloadEvent = await getFirstSentryEnvelopeRequest(page, url); + const pageloadTraceContext = pageloadEvent.contexts?.trace; + + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: META_TAG_TRACE_ID, + parent_span_id: META_TAG_PARENT_SPAN_ID, + span_id: expect.stringMatching(/^[0-9a-f]{16}$/), + }); + + const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); + + await page.getByText('Report a Bug').click(); + expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.locator('[data-sentry-feedback] .btn--primary').click(); + + const feedbackEvent = await feedbackEventPromise; + const feedbackTraceContext = feedbackEvent.contexts?.trace; + + expect(feedbackEvent.type).toEqual('feedback'); + + expect(feedbackTraceContext).toMatchObject({ + trace_id: META_TAG_TRACE_ID, + parent_span_id: META_TAG_PARENT_SPAN_ID, + span_id: expect.stringMatching(/^[0-9a-f]{16}$/), + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts index 4bfe8c26d5cb..3b6a007c54fb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts @@ -1,7 +1,8 @@ import { expect } from '@playwright/test'; -import type { SpanEnvelope } from '@sentry/types'; +import type { Event, SpanEnvelope } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import type { EventAndTraceHeader } from '../../../../utils/helpers'; +import { shouldSkipFeedbackTest } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, @@ -431,3 +432,41 @@ sentryTest( ); }, ); + +sentryTest('user feedback event after pageload has pageload traceId in headers', async ({ getLocalTestPath, page }) => { + if (shouldSkipTracingTest() || shouldSkipFeedbackTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestPath({ testDir: __dirname }); + + const pageloadEvent = await getFirstSentryEnvelopeRequest(page, url); + const pageloadTraceContext = pageloadEvent.contexts?.trace; + + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), + span_id: expect.stringMatching(/^[0-9a-f]{16}$/), + }); + expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + + const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); + + await page.getByText('Report a Bug').click(); + expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.locator('[data-sentry-feedback] .btn--primary').click(); + + const feedbackEvent = await feedbackEventPromise; + + expect(feedbackEvent.type).toEqual('feedback'); + + const feedbackTraceContext = feedbackEvent.contexts?.trace; + + expect(feedbackTraceContext).toMatchObject({ + trace_id: pageloadTraceContext?.trace_id, + span_id: expect.stringMatching(/^[0-9a-f]{16}$/), + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 71df2903e9eb..28b75ee5145a 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -14,6 +14,7 @@ export { captureEvent, captureMessage, captureCheckIn, + captureFeedback, withMonitor, createTransport, // eslint-disable-next-line deprecation/deprecation diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 28fa4229ec39..5d166fce43c8 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -6,6 +6,7 @@ export { captureEvent, captureMessage, captureCheckIn, + captureFeedback, startSession, captureSession, endSession, diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index 78ab902e01a1..c301df98f7f6 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -91,6 +91,8 @@ export class BrowserClient extends BaseClient { /** * Sends user feedback to Sentry. + * + * @deprecated Use `captureFeedback` instead. */ public captureUserFeedback(feedback: UserFeedback): void { if (!this._isEnabled()) { diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 58391f1432c0..3da765aaa0f7 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -88,6 +88,7 @@ export { init, onLoad, showReportDialog, + // eslint-disable-next-line deprecation/deprecation captureUserFeedback, } from './sdk'; diff --git a/packages/browser/src/index.bundle.feedback.ts b/packages/browser/src/index.bundle.feedback.ts index eecad6304ebf..957583d79eeb 100644 --- a/packages/browser/src/index.bundle.feedback.ts +++ b/packages/browser/src/index.bundle.feedback.ts @@ -11,3 +11,5 @@ export { feedbackAsyncIntegration as feedbackIntegration, replayIntegrationShim as replayIntegration, }; + +export { captureFeedback } from '@sentry/core'; diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 194a387bf4c4..de8453db8784 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -13,6 +13,7 @@ export { withActiveSpan, getSpanDescendants, setMeasurement, + captureFeedback, } from '@sentry/core'; export { diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 04a98fbacb02..86e6ea20fe81 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -10,6 +10,7 @@ export { extraErrorDataIntegration, rewriteFramesIntegration, sessionTimingIntegration, + captureFeedback, } from '@sentry/core'; export { diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index d64981cb0c7a..422acf8c3150 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -295,10 +295,13 @@ function startSessionTracking(): void { /** * Captures user feedback and sends it to Sentry. + * + * @deprecated Use `captureFeedback` instead. */ export function captureUserFeedback(feedback: UserFeedback): void { const client = getClient(); if (client) { + // eslint-disable-next-line deprecation/deprecation client.captureUserFeedback(feedback); } } diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index b61dea4a4d77..f0ab0d24e724 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -26,6 +26,7 @@ export { captureEvent, captureMessage, captureCheckIn, + captureFeedback, startSession, captureSession, endSession, diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 455cba2d9771..68a113e5ff2c 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -35,6 +35,7 @@ import { addItemToEnvelope, checkOrSetAlreadyCaught, createAttachmentEnvelopeItem, + dropUndefinedKeys, isParameterizedString, isPlainObject, isPrimitive, @@ -663,11 +664,11 @@ export abstract class BaseClient implements Client { if (!trace && propagationContext) { const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext; evt.contexts = { - trace: { + trace: dropUndefinedKeys({ trace_id, span_id: spanId, parent_span_id: parentSpanId, - }, + }), ...evt.contexts, }; diff --git a/packages/core/src/envelope.ts b/packages/core/src/envelope.ts index 11d38b2040e6..2aef194b069e 100644 --- a/packages/core/src/envelope.ts +++ b/packages/core/src/envelope.ts @@ -1,6 +1,4 @@ import type { - Attachment, - AttachmentItem, DsnComponents, DynamicSamplingContext, Event, @@ -15,7 +13,6 @@ import type { SpanEnvelope, } from '@sentry/types'; import { - createAttachmentEnvelopeItem, createEnvelope, createEventEnvelopeHeaders, dsnToString, @@ -95,34 +92,6 @@ export function createEventEnvelope( return createEnvelope(envelopeHeaders, [eventItem]); } -/** - * Create an Envelope from an event. - */ -export function createAttachmentEnvelope( - event: Event, - attachments: Attachment[], - dsn?: DsnComponents, - metadata?: SdkMetadata, - tunnel?: string, -): EventEnvelope { - const sdkInfo = getSdkMetadataForEnvelopeHeader(metadata); - enhanceEventWithSdkInfo(event, metadata && metadata.sdk); - - const envelopeHeaders = createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn); - - // Prevent this data (which, if it exists, was used in earlier steps in the processing pipeline) from being sent to - // sentry. (Note: Our use of this property comes and goes with whatever we might be debugging, whatever hacks we may - // have temporarily added, etc. Even if we don't happen to be using it at some point in the future, let's not get rid - // of this `delete`, lest we miss putting it back in the next time the property is in use.) - delete event.sdkProcessingMetadata; - - const attachmentItems: AttachmentItem[] = []; - for (const attachment of attachments || []) { - attachmentItems.push(createAttachmentEnvelopeItem(attachment)); - } - return createEnvelope(envelopeHeaders, attachmentItems); -} - /** * Create envelope from Span item. */ diff --git a/packages/core/src/feedback.ts b/packages/core/src/feedback.ts new file mode 100644 index 000000000000..ae3abc7ca50f --- /dev/null +++ b/packages/core/src/feedback.ts @@ -0,0 +1,38 @@ +import type { EventHint, FeedbackEvent, SendFeedbackParams } from '@sentry/types'; +import { dropUndefinedKeys } from '@sentry/utils'; +import { getClient, getCurrentScope } from './currentScopes'; + +/** + * Send user feedback to Sentry. + */ +export function captureFeedback( + feedbackParams: SendFeedbackParams, + hint: EventHint & { includeReplay?: boolean } = {}, +): string { + const { message, name, email, url, source, associatedEventId } = feedbackParams; + + const client = getClient(); + + const feedbackEvent: FeedbackEvent = { + contexts: { + feedback: dropUndefinedKeys({ + contact_email: email, + name, + message, + url, + source, + associated_event_id: associatedEventId, + }), + }, + type: 'feedback', + level: 'info', + }; + + if (client) { + client.emit('beforeSendFeedback', feedbackEvent, hint); + } + + const eventId = getCurrentScope().captureEvent(feedbackEvent, hint); + + return eventId; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c2c210262483..743a2e9d9bd8 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -8,7 +8,7 @@ export type { IntegrationIndex } from './integration'; export * from './tracing'; export * from './semanticAttributes'; -export { createEventEnvelope, createSessionEnvelope, createAttachmentEnvelope, createSpanEnvelope } from './envelope'; +export { createEventEnvelope, createSessionEnvelope, createSpanEnvelope } from './envelope'; export { captureCheckIn, withMonitor, @@ -99,6 +99,7 @@ export { BrowserMetricsAggregator } from './metrics/browser-aggregator'; export { getMetricSummaryJsonForSpan } from './metrics/metric-summary'; export { addTracingHeadersToFetchRequest, instrumentFetchRequest } from './fetch'; export { trpcMiddleware } from './trpc'; +export { captureFeedback } from './feedback'; // eslint-disable-next-line deprecation/deprecation export { getCurrentHubShim, getCurrentHub } from './getCurrentHubShim'; diff --git a/packages/core/test/lib/feedback.test.ts b/packages/core/test/lib/feedback.test.ts new file mode 100644 index 000000000000..faa17a8c51ea --- /dev/null +++ b/packages/core/test/lib/feedback.test.ts @@ -0,0 +1,451 @@ +import type { Span } from '@sentry/types'; +import { addBreadcrumb, getCurrentScope, setCurrentClient, startSpan, withIsolationScope, withScope } from '../../src'; +import { captureFeedback } from '../../src/feedback'; +import { TestClient, getDefaultTestClientOptions } from '../mocks/client'; + +describe('captureFeedback', () => { + beforeEach(() => { + getCurrentScope().setClient(undefined); + getCurrentScope().clear(); + }); + + test('it works without a client', () => { + const res = captureFeedback({ + message: 'test', + }); + + expect(typeof res).toBe('string'); + }); + + test('it works with minimal options', async () => { + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://dsn@ingest.f00.f00/1', + enableSend: true, + }), + ); + setCurrentClient(client); + client.init(); + + const mockTransport = jest.spyOn(client.getTransport()!, 'send'); + + const eventId = captureFeedback({ + message: 'test', + }); + + await client.flush(); + + expect(typeof eventId).toBe('string'); + + expect(mockTransport).toHaveBeenCalledWith([ + { + event_id: eventId, + sent_at: expect.any(String), + trace: { + environment: 'production', + public_key: 'dsn', + trace_id: expect.any(String), + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: undefined, + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + feedback: { + message: 'test', + }, + }, + level: 'info', + environment: 'production', + event_id: eventId, + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + ], + ]); + }); + + test('it works with full options', async () => { + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://dsn@ingest.f00.f00/1', + enableSend: true, + }), + ); + setCurrentClient(client); + client.init(); + + const mockTransport = jest.spyOn(client.getTransport()!, 'send'); + + const eventId = captureFeedback({ + name: 'doe', + email: 're@example.org', + message: 'mi', + url: 'http://example.com/', + source: 'custom-source', + associatedEventId: '1234', + }); + + await client.flush(); + + expect(typeof eventId).toBe('string'); + + expect(mockTransport).toHaveBeenCalledWith([ + { + event_id: eventId, + sent_at: expect.any(String), + trace: { + environment: 'production', + public_key: 'dsn', + trace_id: expect.any(String), + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: undefined, + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + feedback: { + name: 'doe', + contact_email: 're@example.org', + message: 'mi', + url: 'http://example.com/', + source: 'custom-source', + associated_event_id: '1234', + }, + }, + level: 'info', + environment: 'production', + event_id: eventId, + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + ], + ]); + }); + + test('it captures attachments', async () => { + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://dsn@ingest.f00.f00/1', + enableSend: true, + }), + ); + setCurrentClient(client); + client.init(); + + const mockTransport = jest.spyOn(client.getTransport()!, 'send'); + + const attachment1 = new Uint8Array([1, 2, 3, 4, 5]); + const attachment2 = new Uint8Array([6, 7, 8, 9]); + + const eventId = captureFeedback( + { + message: 'test', + }, + { + attachments: [ + { + data: attachment1, + filename: 'test-file.txt', + }, + { + data: attachment2, + filename: 'test-file2.txt', + }, + ], + }, + ); + + await client.flush(); + + expect(typeof eventId).toBe('string'); + + expect(mockTransport).toHaveBeenCalledTimes(1); + + const [feedbackEnvelope] = mockTransport.mock.calls; + + expect(feedbackEnvelope).toHaveLength(1); + expect(feedbackEnvelope[0]).toEqual([ + { + event_id: eventId, + sent_at: expect.any(String), + trace: { + environment: 'production', + public_key: 'dsn', + trace_id: expect.any(String), + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: undefined, + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + feedback: { + message: 'test', + }, + }, + level: 'info', + environment: 'production', + event_id: eventId, + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + [ + { + type: 'attachment', + length: 5, + filename: 'test-file.txt', + }, + attachment1, + ], + [ + { + type: 'attachment', + length: 4, + filename: 'test-file2.txt', + }, + attachment2, + ], + ], + ]); + }); + + test('it captures DSC from scope', async () => { + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://dsn@ingest.f00.f00/1', + enableSend: true, + }), + ); + setCurrentClient(client); + client.init(); + + const mockTransport = jest.spyOn(client.getTransport()!, 'send'); + + const traceId = '4C79F60C11214EB38604F4AE0781BFB2'; + const spanId = 'FA90FDEAD5F74052'; + const dsc = { + trace_id: traceId, + span_id: spanId, + sampled: 'true', + }; + + getCurrentScope().setPropagationContext({ + traceId, + spanId, + dsc, + }); + + const eventId = captureFeedback({ + message: 'test', + }); + + await client.flush(); + + expect(typeof eventId).toBe('string'); + + expect(mockTransport).toHaveBeenCalledWith([ + { + event_id: eventId, + sent_at: expect.any(String), + trace: { + trace_id: traceId, + span_id: spanId, + sampled: 'true', + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: undefined, + contexts: { + trace: { + trace_id: traceId, + span_id: spanId, + }, + feedback: { + message: 'test', + }, + }, + level: 'info', + environment: 'production', + event_id: eventId, + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + ], + ]); + }); + + test('it captures data from active span', async () => { + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://dsn@ingest.f00.f00/1', + enableSend: true, + enableTracing: true, + // We don't care about transactions here... + beforeSendTransaction() { + return null; + }, + }), + ); + setCurrentClient(client); + client.init(); + + const mockTransport = jest.spyOn(client.getTransport()!, 'send'); + + let span: Span | undefined; + const eventId = startSpan({ name: 'test-span' }, _span => { + span = _span; + return captureFeedback({ + message: 'test', + }); + }); + + await client.flush(); + + expect(typeof eventId).toBe('string'); + expect(span).toBeDefined(); + + const { spanId, traceId } = span!.spanContext(); + + expect(mockTransport).toHaveBeenCalledWith([ + { + event_id: eventId, + sent_at: expect.any(String), + trace: { + trace_id: traceId, + environment: 'production', + public_key: 'dsn', + sampled: 'true', + sample_rate: '1', + transaction: 'test-span', + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: undefined, + contexts: { + trace: { + trace_id: traceId, + span_id: spanId, + }, + feedback: { + message: 'test', + }, + }, + level: 'info', + environment: 'production', + event_id: eventId, + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + ], + ]); + }); + + it('applies scope data to feedback', async () => { + const client = new TestClient( + getDefaultTestClientOptions({ + dsn: 'https://dsn@ingest.f00.f00/1', + enableSend: true, + enableTracing: true, + // We don't care about transactions here... + beforeSendTransaction() { + return null; + }, + }), + ); + setCurrentClient(client); + client.init(); + + const mockTransport = jest.spyOn(client.getTransport()!, 'send'); + + withIsolationScope(isolationScope => { + isolationScope.setTag('test-1', 'tag'); + isolationScope.setExtra('test-1', 'extra'); + + return withScope(scope => { + scope.setTag('test-2', 'tag'); + scope.setExtra('test-2', 'extra'); + + addBreadcrumb({ message: 'test breadcrumb', timestamp: 12345 }); + + captureFeedback({ + name: 'doe', + email: 're@example.org', + message: 'mi', + }); + }); + }); + + expect(mockTransport).toHaveBeenCalledWith([ + { + event_id: expect.any(String), + sent_at: expect.any(String), + trace: { + trace_id: expect.any(String), + environment: 'production', + public_key: 'dsn', + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: [{ message: 'test breadcrumb', timestamp: 12345 }], + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + feedback: { + contact_email: 're@example.org', + message: 'mi', + name: 'doe', + }, + }, + extra: { + 'test-1': 'extra', + 'test-2': 'extra', + }, + tags: { + 'test-1': 'tag', + 'test-2': 'tag', + }, + level: 'info', + environment: 'production', + event_id: expect.any(String), + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + ], + ]); + }); +}); diff --git a/packages/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index 473028ea4b12..8063dab46592 100644 --- a/packages/core/test/mocks/client.ts +++ b/packages/core/test/mocks/client.ts @@ -77,13 +77,14 @@ export class TestClient extends BaseClient { public sendEvent(event: Event, hint?: EventHint): void { this.event = event; - // In real life, this will get deleted as part of envelope creation. - delete event.sdkProcessingMetadata; - if (this._options.enableSend) { super.sendEvent(event, hint); return; } + + // In real life, this will get deleted as part of envelope creation. + delete event.sdkProcessingMetadata; + TestClient.sendEventCalled && TestClient.sendEventCalled(event); } diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 2e5e0cefa657..be740ac22665 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -26,6 +26,7 @@ export { captureException, captureEvent, captureMessage, + captureFeedback, close, createTransport, continueTrace, diff --git a/packages/feedback/src/core/sendFeedback.test.ts b/packages/feedback/src/core/sendFeedback.test.ts index 1b0ad480ca4e..e87573533952 100644 --- a/packages/feedback/src/core/sendFeedback.test.ts +++ b/packages/feedback/src/core/sendFeedback.test.ts @@ -1,31 +1,184 @@ -import { getClient } from '@sentry/core'; +import { + addBreadcrumb, + getClient, + getCurrentScope, + getIsolationScope, + startSpan, + withIsolationScope, + withScope, +} from '@sentry/core'; import { mockSdk } from './mockSdk'; import { sendFeedback } from './sendFeedback'; +import { TextDecoder, TextEncoder } from 'util'; +const patchedEncoder = (!global.window.TextEncoder && (global.window.TextEncoder = TextEncoder)) || true; +// @ts-expect-error patch the encoder on the window, else importing JSDOM fails (deleted in afterAll) +const patchedDecoder = (!global.window.TextDecoder && (global.window.TextDecoder = TextDecoder)) || true; + describe('sendFeedback', () => { - it('sends feedback', async () => { + beforeEach(() => { + getIsolationScope().clear(); + getCurrentScope().clear(); + jest.clearAllMocks(); + }); + + afterAll(() => { + // @ts-expect-error patch the encoder on the window, else importing JSDOM fails + patchedEncoder && delete global.window.TextEncoder; + // @ts-expect-error patch the encoder on the window, else importing JSDOM fails + patchedDecoder && delete global.window.TextDecoder; + }); + + it('sends feedback with minimal options', async () => { + mockSdk(); + const mockTransport = jest.spyOn(getClient()!.getTransport()!, 'send'); + + const promise = sendFeedback({ + message: 'mi', + }); + + expect(promise).toBeInstanceOf(Promise); + + const eventId = await promise; + + expect(typeof eventId).toEqual('string'); + + expect(mockTransport).toHaveBeenCalledWith([ + { + event_id: expect.any(String), + sent_at: expect.any(String), + trace: { + trace_id: expect.any(String), + environment: 'production', + public_key: 'dsn', + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: undefined, + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + feedback: { + message: 'mi', + source: 'api', + url: 'http://localhost/', + }, + }, + level: 'info', + environment: 'production', + event_id: expect.any(String), + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + ], + ]); + }); + + it('sends feedback with full options', async () => { mockSdk(); const mockTransport = jest.spyOn(getClient()!.getTransport()!, 'send'); - await sendFeedback({ + const promise = sendFeedback({ name: 'doe', email: 're@example.org', message: 'mi', + url: 'http://example.com/', + source: 'custom-source', + associatedEventId: '1234', + }); + + expect(promise).toBeInstanceOf(Promise); + + const eventId = await promise; + + expect(typeof eventId).toEqual('string'); + + expect(mockTransport).toHaveBeenCalledWith([ + { + event_id: expect.any(String), + sent_at: expect.any(String), + trace: { + trace_id: expect.any(String), + environment: 'production', + public_key: 'dsn', + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: undefined, + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + feedback: { + name: 'doe', + contact_email: 're@example.org', + message: 'mi', + url: 'http://example.com/', + source: 'custom-source', + associated_event_id: '1234', + }, + }, + level: 'info', + environment: 'production', + event_id: expect.any(String), + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + ], + ]); + }); + + it('applies active span data to feedback', async () => { + mockSdk({ sentryOptions: { enableTracing: true } }); + const mockTransport = jest.spyOn(getClient()!.getTransport()!, 'send'); + + await startSpan({ name: 'test span' }, () => { + return sendFeedback({ + name: 'doe', + email: 're@example.org', + message: 'mi', + }); }); + expect(mockTransport).toHaveBeenCalledWith([ - { event_id: expect.any(String), sent_at: expect.any(String) }, + { + event_id: expect.any(String), + sent_at: expect.any(String), + trace: { + trace_id: expect.any(String), + environment: 'production', + public_key: 'dsn', + sample_rate: '1', + sampled: 'true', + transaction: 'test span', + }, + }, [ [ { type: 'feedback' }, { breadcrumbs: undefined, contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, feedback: { contact_email: 're@example.org', message: 'mi', name: 'doe', - replay_id: undefined, source: 'api', url: 'http://localhost/', }, @@ -33,7 +186,6 @@ describe('sendFeedback', () => { level: 'info', environment: 'production', event_id: expect.any(String), - platform: 'javascript', timestamp: expect.any(Number), type: 'feedback', }, @@ -41,4 +193,232 @@ describe('sendFeedback', () => { ], ]); }); + + it('applies scope data to feedback', async () => { + mockSdk({ sentryOptions: { enableTracing: true } }); + const mockTransport = jest.spyOn(getClient()!.getTransport()!, 'send'); + + await withIsolationScope(isolationScope => { + isolationScope.setTag('test-1', 'tag'); + isolationScope.setExtra('test-1', 'extra'); + + return withScope(scope => { + scope.setTag('test-2', 'tag'); + scope.setExtra('test-2', 'extra'); + + addBreadcrumb({ message: 'test breadcrumb', timestamp: 12345 }); + + return sendFeedback({ + name: 'doe', + email: 're@example.org', + message: 'mi', + }); + }); + }); + + expect(mockTransport).toHaveBeenCalledWith([ + { + event_id: expect.any(String), + sent_at: expect.any(String), + trace: { + trace_id: expect.any(String), + environment: 'production', + public_key: 'dsn', + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: [{ message: 'test breadcrumb', timestamp: 12345 }], + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + feedback: { + contact_email: 're@example.org', + message: 'mi', + name: 'doe', + source: 'api', + url: 'http://localhost/', + }, + }, + extra: { + 'test-1': 'extra', + 'test-2': 'extra', + }, + tags: { + 'test-1': 'tag', + 'test-2': 'tag', + }, + level: 'info', + environment: 'production', + event_id: expect.any(String), + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + ], + ]); + }); + + it('handles 400 transport error', async () => { + mockSdk(); + jest.spyOn(getClient()!.getTransport()!, 'send').mockImplementation(() => { + return Promise.resolve({ statusCode: 400 }); + }); + + await expect( + sendFeedback({ + name: 'doe', + email: 're@example.org', + message: 'mi', + }), + ).rejects.toMatch('Unable to send Feedback. Invalid response from server.'); + }); + + it('handles 0 transport error', async () => { + mockSdk(); + jest.spyOn(getClient()!.getTransport()!, 'send').mockImplementation(() => { + return Promise.resolve({ statusCode: 0 }); + }); + + await expect( + sendFeedback({ + name: 'doe', + email: 're@example.org', + message: 'mi', + }), + ).rejects.toMatch( + 'Unable to send Feedback. This is because of network issues, or because you are using an ad-blocker.', + ); + }); + + it('handles 200 transport response', async () => { + mockSdk(); + jest.spyOn(getClient()!.getTransport()!, 'send').mockImplementation(() => { + return Promise.resolve({ statusCode: 200 }); + }); + + await expect( + sendFeedback({ + name: 'doe', + email: 're@example.org', + message: 'mi', + }), + ).resolves.toEqual(expect.any(String)); + }); + + it('handles timeout', async () => { + jest.useFakeTimers(); + + mockSdk(); + jest.spyOn(getClient()!.getTransport()!, 'send').mockImplementation(() => { + return new Promise(resolve => setTimeout(resolve, 10_000)); + }); + + const promise = sendFeedback({ + name: 'doe', + email: 're@example.org', + message: 'mi', + }); + + jest.advanceTimersByTime(5_000); + + await expect(promise).rejects.toMatch('Unable to determine if Feedback was correctly sent.'); + + jest.useRealTimers(); + }); + + it('sends attachments', async () => { + mockSdk(); + const mockTransport = jest.spyOn(getClient()!.getTransport()!, 'send'); + + const attachment1 = new Uint8Array([1, 2, 3, 4, 5]); + const attachment2 = new Uint8Array([6, 7, 8, 9]); + + const promise = sendFeedback( + { + name: 'doe', + email: 're@example.org', + message: 'mi', + }, + { + attachments: [ + { + data: attachment1, + filename: 'test-file.txt', + }, + { + data: attachment2, + filename: 'test-file2.txt', + }, + ], + }, + ); + + expect(promise).toBeInstanceOf(Promise); + + const eventId = await promise; + + expect(typeof eventId).toEqual('string'); + expect(mockTransport).toHaveBeenCalledTimes(1); + + const [feedbackEnvelope] = mockTransport.mock.calls; + + expect(feedbackEnvelope[0]).toEqual([ + { + event_id: eventId, + sent_at: expect.any(String), + trace: { + trace_id: expect.any(String), + environment: 'production', + public_key: 'dsn', + }, + }, + [ + [ + { type: 'feedback' }, + { + breadcrumbs: undefined, + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + feedback: { + contact_email: 're@example.org', + message: 'mi', + name: 'doe', + source: 'api', + url: 'http://localhost/', + }, + }, + level: 'info', + environment: 'production', + event_id: eventId, + timestamp: expect.any(Number), + type: 'feedback', + }, + ], + [ + { + type: 'attachment', + length: 5, + filename: 'test-file.txt', + }, + attachment1, + ], + [ + { + type: 'attachment', + length: 4, + filename: 'test-file2.txt', + }, + attachment2, + ], + ], + ]); + }); }); diff --git a/packages/feedback/src/core/sendFeedback.ts b/packages/feedback/src/core/sendFeedback.ts index 5aa795fb16ca..3848eb9cbc33 100644 --- a/packages/feedback/src/core/sendFeedback.ts +++ b/packages/feedback/src/core/sendFeedback.ts @@ -1,100 +1,65 @@ -import { createAttachmentEnvelope, createEventEnvelope, getClient, withScope } from '@sentry/core'; -import type { FeedbackEvent, SendFeedback, SendFeedbackParams } from '@sentry/types'; +import { captureFeedback } from '@sentry/core'; +import { getClient } from '@sentry/core'; +import type { EventHint, SendFeedback, SendFeedbackParams, TransportMakeRequestResponse } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { getLocationHref } from '@sentry/utils'; -import { FEEDBACK_API_SOURCE, FEEDBACK_WIDGET_SOURCE } from '../constants'; -import { prepareFeedbackEvent } from '../util/prepareFeedbackEvent'; +import { FEEDBACK_API_SOURCE } from '../constants'; /** * Public API to send a Feedback item to Sentry */ export const sendFeedback: SendFeedback = ( - { name, email, message, attachments, source = FEEDBACK_API_SOURCE, url = getLocationHref() }: SendFeedbackParams, - { includeReplay = true } = {}, -) => { - if (!message) { + options: SendFeedbackParams, + hint: EventHint & { includeReplay?: boolean } = { includeReplay: true }, +): Promise => { + if (!options.message) { throw new Error('Unable to submit feedback with empty message'); } + // We want to wait for the feedback to be sent (or not) const client = getClient(); - const transport = client && client.getTransport(); - const dsn = client && client.getDsn(); - if (!client || !transport || !dsn) { - throw new Error('Invalid Sentry client'); + if (!client) { + throw new Error('No client setup, cannot send feedback.'); } - const baseEvent: FeedbackEvent = { - contexts: { - feedback: { - contact_email: email, - name, - message, - url, - source, - }, + const eventId = captureFeedback( + { + source: FEEDBACK_API_SOURCE, + url: getLocationHref(), + ...options, }, - type: 'feedback', - }; + hint, + ); - return withScope(async scope => { - // No use for breadcrumbs in feedback - scope.clearBreadcrumbs(); + // We want to wait for the feedback to be sent (or not) + return new Promise((resolve, reject) => { + // After 5s, we want to clear anyhow + const timeout = setTimeout(() => reject('Unable to determine if Feedback was correctly sent.'), 5_000); - if ([FEEDBACK_API_SOURCE, FEEDBACK_WIDGET_SOURCE].includes(String(source))) { - scope.setLevel('info'); - } - - const feedbackEvent = await prepareFeedbackEvent({ - scope, - client, - event: baseEvent, - }); - - if (client.emit) { - client.emit('beforeSendFeedback', feedbackEvent, { includeReplay: Boolean(includeReplay) }); - } - - try { - const response = await transport.send( - createEventEnvelope(feedbackEvent, dsn, client.getOptions()._metadata, client.getOptions().tunnel), - ); - - if (attachments && attachments.length) { - // TODO: https://docs.sentry.io/platforms/javascript/enriching-events/attachments/ - await transport.send( - createAttachmentEnvelope( - feedbackEvent, - attachments, - dsn, - client.getOptions()._metadata, - client.getOptions().tunnel, - ), - ); + client.on('afterSendEvent', (event: Event, response: TransportMakeRequestResponse) => { + if (event.event_id !== eventId) { + return; } + clearTimeout(timeout); + // Require valid status codes, otherwise can assume feedback was not sent successfully - if (typeof response.statusCode === 'number' && (response.statusCode < 200 || response.statusCode >= 300)) { + if ( + response && + typeof response.statusCode === 'number' && + (response.statusCode < 200 || response.statusCode >= 300) + ) { if (response.statusCode === 0) { - throw new Error( + return reject( 'Unable to send Feedback. This is because of network issues, or because you are using an ad-blocker.', ); } - throw new Error('Unable to send Feedback. Invalid response from server.'); + return reject('Unable to send Feedback. Invalid response from server.'); } - return response; - } catch (err) { - const error = new Error('Unable to send Feedback'); - - try { - // In case browsers don't allow this property to be writable - // @ts-expect-error This needs lib es2022 and newer - error.cause = err; - } catch { - // nothing to do - } - throw error; - } + resolve(eventId); + }); }); }; diff --git a/packages/feedback/src/modal/components/Form.tsx b/packages/feedback/src/modal/components/Form.tsx index f40693d8f2af..3a723bc61d1b 100644 --- a/packages/feedback/src/modal/components/Form.tsx +++ b/packages/feedback/src/modal/components/Form.tsx @@ -112,17 +112,28 @@ export function Form({ } const formData = new FormData(e.target); const attachment = await (screenshotInput && showScreenshotInput ? screenshotInput.value() : undefined); + const data: FeedbackFormData = { name: retrieveStringValue(formData, 'name'), email: retrieveStringValue(formData, 'email'), message: retrieveStringValue(formData, 'message'), attachments: attachment ? [attachment] : undefined, }; + if (!hasAllRequiredFields(data)) { return; } + try { - await onSubmit({ ...data, source: FEEDBACK_WIDGET_SOURCE }); + await onSubmit( + { + name: data.name, + email: data.email, + message: data.message, + source: FEEDBACK_WIDGET_SOURCE, + }, + { attachments: data.attachments }, + ); onSubmitSuccess(data); } catch (error) { DEBUG_BUILD && logger.error(error); diff --git a/packages/feedback/src/util/prepareFeedbackEvent.ts b/packages/feedback/src/util/prepareFeedbackEvent.ts deleted file mode 100644 index 0d66d251c6ff..000000000000 --- a/packages/feedback/src/util/prepareFeedbackEvent.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { getIsolationScope, prepareEvent } from '@sentry/core'; -import type { Client, FeedbackEvent, Scope } from '@sentry/types'; - -interface PrepareFeedbackEventParams { - client: Client; - event: FeedbackEvent; - scope: Scope; -} -/** - * Prepare a feedback event & enrich it with the SDK metadata. - */ -export async function prepareFeedbackEvent({ - client, - scope, - event, -}: PrepareFeedbackEventParams): Promise { - const eventHint = {}; - if (client.emit) { - client.emit('preprocessEvent', event, eventHint); - } - - const preparedEvent = (await prepareEvent( - client.getOptions(), - event, - eventHint, - scope, - client, - getIsolationScope(), - )) as FeedbackEvent | null; - - if (preparedEvent === null) { - // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions - client.recordDroppedEvent('event_processor', 'feedback', event); - throw new Error('Unable to prepare event'); - } - - // This normally happens in browser client "_prepareEvent" - // but since we do not use this private method from the client, but rather the plain import - // we need to do this manually. - preparedEvent.platform = preparedEvent.platform || 'javascript'; - - return preparedEvent; -} diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index c480589a5ef1..d6f774d633f0 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -6,6 +6,7 @@ export { captureEvent, captureMessage, captureCheckIn, + captureFeedback, startSession, captureSession, endSession, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 60b152f346e8..c209a5d2ab8b 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -90,6 +90,7 @@ export { captureException, captureEvent, captureMessage, + captureFeedback, captureConsoleIntegration, debugIntegration, dedupeIntegration, diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index d528db5802f0..e8b25aa2936f 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -18,6 +18,7 @@ export { captureException, captureEvent, captureMessage, + captureFeedback, createTransport, // eslint-disable-next-line deprecation/deprecation getCurrentHub, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 07c297de608b..29f7dfd313a5 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -11,6 +11,7 @@ export { captureEvent, captureMessage, captureCheckIn, + captureFeedback, withMonitor, createTransport, getClient, diff --git a/packages/types/src/feedback/sendFeedback.ts b/packages/types/src/feedback/sendFeedback.ts index 612e0cff001d..a284e82f107b 100644 --- a/packages/types/src/feedback/sendFeedback.ts +++ b/packages/types/src/feedback/sendFeedback.ts @@ -1,6 +1,4 @@ -import type { Attachment } from '../attachment'; -import type { Event } from '../event'; -import type { TransportMakeRequestResponse } from '../transport'; +import type { Event, EventHint } from '../event'; import type { User } from '../user'; /** @@ -19,6 +17,7 @@ interface FeedbackContext extends Record { name?: string; replay_id?: string; url?: string; + associated_event_id?: string; } /** @@ -36,19 +35,16 @@ export interface SendFeedbackParams { message: string; name?: string; email?: string; - attachments?: Attachment[]; url?: string; source?: string; + associatedEventId?: string; } -interface SendFeedbackOptions { +interface SendFeedbackOptions extends EventHint { /** * Should include replay with the feedback? */ includeReplay?: boolean; } -export type SendFeedback = ( - params: SendFeedbackParams, - options?: SendFeedbackOptions, -) => Promise; +export type SendFeedback = (params: SendFeedbackParams, options?: SendFeedbackOptions) => Promise; diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 9c5a0705426e..031787bebe22 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -27,6 +27,7 @@ export { captureException, captureEvent, captureMessage, + captureFeedback, close, createTransport, flush, From fd10a2b1d39f5079f732546d978fea9f50521612 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 May 2024 09:57:57 +0200 Subject: [PATCH 21/26] fix(nestjs): Ensure Nest.js interceptor works with non-http context (#11880) Fixes https://github.com/getsentry/sentry-javascript/issues/11877 --- packages/node/src/integrations/tracing/nest.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/node/src/integrations/tracing/nest.ts b/packages/node/src/integrations/tracing/nest.ts index 220b60d68a59..a831a6771179 100644 --- a/packages/node/src/integrations/tracing/nest.ts +++ b/packages/node/src/integrations/tracing/nest.ts @@ -5,6 +5,8 @@ import type { IntegrationFn } from '@sentry/types'; import { logger } from '@sentry/utils'; interface MinimalNestJsExecutionContext { + getType: () => string; + switchToHttp: () => { // minimal request object // according to official types, all properties are required but @@ -57,10 +59,13 @@ export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsE return next.handle(); } - const req = context.switchToHttp().getRequest(); - if (req.route) { - getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`); + if (context.getType() === 'http') { + const req = context.switchToHttp().getRequest(); + if (req.route) { + getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`); + } } + return next.handle(); }, }); From 6f44e8ad0dd36a707ece605b3894a6572f78f700 Mon Sep 17 00:00:00 2001 From: Artur Androsovych Date: Fri, 3 May 2024 09:18:19 +0100 Subject: [PATCH 22/26] fix(angular): Run tracing calls outside Angular (#11748) This commit updates all tracing functionality to run outside the Angular zone. Before this change, it hindered server-side rendering and hydration, causing instability in the app. The app achieves stability when there are no micro/macro tasks running. As a result, the HTML can now be serialized and sent to the client. --- packages/angular/src/tracing.ts | 94 ++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 42 deletions(-) diff --git a/packages/angular/src/tracing.ts b/packages/angular/src/tracing.ts index 59b9653e02ec..7f97e7c32679 100644 --- a/packages/angular/src/tracing.ts +++ b/packages/angular/src/tracing.ts @@ -89,12 +89,14 @@ export class TraceService implements OnDestroy { if (client) { // see comment in `_isPageloadOngoing` for rationale if (!this._isPageloadOngoing()) { - startBrowserTracingNavigationSpan(client, { - name: strippedUrl, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.angular', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - }, + runOutsideAngular(() => { + startBrowserTracingNavigationSpan(client, { + name: strippedUrl, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.angular', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + }, + }); }); } else { // The first time we end up here, we set the pageload flag to false @@ -104,18 +106,20 @@ export class TraceService implements OnDestroy { } this._routingSpan = - startInactiveSpan({ - name: `${navigationEvent.url}`, - op: ANGULAR_ROUTING_OP, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', - url: strippedUrl, - ...(navigationEvent.navigationTrigger && { - navigationTrigger: navigationEvent.navigationTrigger, - }), - }, - }) || null; + runOutsideAngular(() => + startInactiveSpan({ + name: `${navigationEvent.url}`, + op: ANGULAR_ROUTING_OP, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', + url: strippedUrl, + ...(navigationEvent.navigationTrigger && { + navigationTrigger: navigationEvent.navigationTrigger, + }), + }, + }), + ) || null; return; } @@ -252,11 +256,13 @@ export class TraceDirective implements OnInit, AfterViewInit { } if (getActiveSpan()) { - this._tracingSpan = startInactiveSpan({ - name: `<${this.componentName}>`, - op: ANGULAR_INIT_OP, - attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive' }, - }); + this._tracingSpan = runOutsideAngular(() => + startInactiveSpan({ + name: `<${this.componentName}>`, + op: ANGULAR_INIT_OP, + attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_directive' }, + }), + ); } } @@ -266,7 +272,7 @@ export class TraceDirective implements OnInit, AfterViewInit { */ public ngAfterViewInit(): void { if (this._tracingSpan) { - this._tracingSpan.end(); + runOutsideAngular(() => this._tracingSpan!.end()); } } } @@ -298,14 +304,16 @@ export function TraceClass(options?: TraceClassOptions): ClassDecorator { const originalOnInit = target.prototype.ngOnInit; // eslint-disable-next-line @typescript-eslint/no-explicit-any target.prototype.ngOnInit = function (...args: any[]): ReturnType { - tracingSpan = startInactiveSpan({ - onlyIfParent: true, - name: `<${options && options.name ? options.name : 'unnamed'}>`, - op: ANGULAR_INIT_OP, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_class_decorator', - }, - }); + tracingSpan = runOutsideAngular(() => + startInactiveSpan({ + onlyIfParent: true, + name: `<${options && options.name ? options.name : 'unnamed'}>`, + op: ANGULAR_INIT_OP, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_class_decorator', + }, + }), + ); if (originalOnInit) { return originalOnInit.apply(this, args); @@ -316,7 +324,7 @@ export function TraceClass(options?: TraceClassOptions): ClassDecorator { // eslint-disable-next-line @typescript-eslint/no-explicit-any target.prototype.ngAfterViewInit = function (...args: any[]): ReturnType { if (tracingSpan) { - tracingSpan.end(); + runOutsideAngular(() => tracingSpan.end()); } if (originalAfterViewInit) { return originalAfterViewInit.apply(this, args); @@ -344,15 +352,17 @@ export function TraceMethod(options?: TraceMethodOptions): MethodDecorator { descriptor.value = function (...args: any[]): ReturnType { const now = timestampInSeconds(); - startInactiveSpan({ - onlyIfParent: true, - name: `<${options && options.name ? options.name : 'unnamed'}>`, - op: `${ANGULAR_OP}.${String(propertyKey)}`, - startTime: now, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator', - }, - }).end(now); + runOutsideAngular(() => { + startInactiveSpan({ + onlyIfParent: true, + name: `<${options && options.name ? options.name : 'unnamed'}>`, + op: `${ANGULAR_OP}.${String(propertyKey)}`, + startTime: now, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.angular.trace_method_decorator', + }, + }).end(now); + }); if (originalMethod) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access From d2b7df487c0f82dfcd1ff11d7166cf4db91eb22e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 May 2024 10:34:16 +0200 Subject: [PATCH 23/26] feat(browser): Disable standalone `http.client` spans (#11879) Since there seem to be issues around this in the backend/UI today, we decided to not send these for now. We may revisit this later... I left the code to send this in for now, so we can revert this more easily if needed. --- .../request/fetch-standalone-span/init.js | 17 -- .../request/fetch-standalone-span/subject.js | 1 - .../request/fetch-standalone-span/test.ts | 96 ----------- .../request/xhr-standalone-span/init.js | 17 -- .../request/xhr-standalone-span/subject.js | 3 - .../request/xhr-standalone-span/test.ts | 95 ----------- .../tracing/trace-lifetime/navigation/test.ts | 151 +----------------- .../trace-lifetime/pageload-meta/test.ts | 136 +--------------- .../tracing/trace-lifetime/pageload/test.ts | 141 +--------------- packages/browser/src/tracing/request.ts | 6 - packages/core/src/fetch.ts | 7 +- 11 files changed, 4 insertions(+), 666 deletions(-) delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/init.js deleted file mode 100644 index d4ad9bb47ce9..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/init.js +++ /dev/null @@ -1,17 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - // disable auto span creation - integrations: [ - Sentry.browserTracingIntegration({ - instrumentPageLoad: false, - instrumentNavigation: false, - }), - ], - tracePropagationTargets: ['http://example.com'], - tracesSampleRate: 1, - autoSessionTracking: false, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/subject.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/subject.js deleted file mode 100644 index ab34b15730e4..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/subject.js +++ /dev/null @@ -1 +0,0 @@ -fetch('http://example.com/0'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/test.ts deleted file mode 100644 index ef59fbc810dd..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-standalone-span/test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { SpanEnvelope } from '@sentry/types'; -import { sentryTest } from '../../../../utils/fixtures'; -import { - getFirstSentryEnvelopeRequest, - properFullEnvelopeRequestParser, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; - -import { expect } from '@playwright/test'; - -sentryTest( - "should create standalone span for fetch requests if there's no active span and should attach tracing headers", - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - let sentryTraceHeader = ''; - let baggageHeader = ''; - - await page.route('http://example.com/**', route => { - sentryTraceHeader = route.request().headers()['sentry-trace']; - baggageHeader = route.request().headers()['baggage']; - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spanEnvelopePromise = getFirstSentryEnvelopeRequest( - page, - undefined, - properFullEnvelopeRequestParser, - ); - - await page.goto(url); - - const spanEnvelope = await spanEnvelopePromise; - - const spanEnvelopeHeaders = spanEnvelope[0]; - const spanEnvelopeItem = spanEnvelope[1][0][1]; - - const traceId = spanEnvelopeHeaders.trace!.trace_id; - const spanId = spanEnvelopeItem.span_id; - - expect(traceId).toMatch(/[a-f0-9]{32}/); - expect(spanId).toMatch(/[a-f0-9]{16}/); - - expect(spanEnvelopeHeaders).toEqual({ - sent_at: expect.any(String), - trace: { - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: traceId, - transaction: 'GET http://example.com/0', - }, - }); - - expect(spanEnvelopeItem).toEqual({ - data: expect.objectContaining({ - 'http.method': 'GET', - 'http.response.status_code': 200, - 'http.response_content_length': expect.any(Number), - 'http.url': 'http://example.com/0', - 'sentry.op': 'http.client', - 'sentry.origin': 'auto.http.browser', - 'sentry.sample_rate': 1, - 'sentry.source': 'custom', - 'server.address': 'example.com', - type: 'fetch', - url: 'http://example.com/0', - }), - description: 'GET http://example.com/0', - op: 'http.client', - origin: 'auto.http.browser', - status: 'ok', - trace_id: traceId, - span_id: spanId, - segment_id: spanId, - is_segment: true, - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - }); - - // the standalone span was sampled, so we propagate the positive sampling decision - expect(sentryTraceHeader).toBe(`${traceId}-${spanId}-1`); - expect(baggageHeader).toBe( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${traceId},sentry-sample_rate=1,sentry-transaction=GET%20http%3A%2F%2Fexample.com%2F0,sentry-sampled=true`, - ); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/init.js deleted file mode 100644 index d4ad9bb47ce9..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/init.js +++ /dev/null @@ -1,17 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - // disable auto span creation - integrations: [ - Sentry.browserTracingIntegration({ - instrumentPageLoad: false, - instrumentNavigation: false, - }), - ], - tracePropagationTargets: ['http://example.com'], - tracesSampleRate: 1, - autoSessionTracking: false, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/subject.js b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/subject.js deleted file mode 100644 index a487cfac3676..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/subject.js +++ /dev/null @@ -1,3 +0,0 @@ -const xhr_1 = new XMLHttpRequest(); -xhr_1.open('GET', 'http://example.com/0'); -xhr_1.send(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/test.ts deleted file mode 100644 index 46c1c5d616a3..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/request/xhr-standalone-span/test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { expect } from '@playwright/test'; - -import type { SpanEnvelope } from '@sentry/types'; -import { sentryTest } from '../../../../utils/fixtures'; -import { - getFirstSentryEnvelopeRequest, - properFullEnvelopeRequestParser, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; - -sentryTest( - "should create standalone span for XHR requests if there's no active span and should attach tracing headers", - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - let sentryTraceHeader = ''; - let baggageHeader = ''; - - await page.route('http://example.com/**', route => { - sentryTraceHeader = route.request().headers()['sentry-trace']; - baggageHeader = route.request().headers()['baggage']; - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const spanEnvelopePromise = getFirstSentryEnvelopeRequest( - page, - undefined, - properFullEnvelopeRequestParser, - ); - - await page.goto(url); - - const spanEnvelope = await spanEnvelopePromise; - - const spanEnvelopeHeaders = spanEnvelope[0]; - const spanEnvelopeItem = spanEnvelope[1][0][1]; - - const traceId = spanEnvelopeHeaders.trace!.trace_id; - const spanId = spanEnvelopeItem.span_id; - - expect(traceId).toMatch(/[a-f0-9]{32}/); - expect(spanId).toMatch(/[a-f0-9]{16}/); - - expect(spanEnvelopeHeaders).toEqual({ - sent_at: expect.any(String), - trace: { - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: traceId, - transaction: 'GET http://example.com/0', - }, - }); - - expect(spanEnvelopeItem).toEqual({ - data: { - 'http.method': 'GET', - 'http.response.status_code': 200, - 'http.url': 'http://example.com/0', - 'sentry.op': 'http.client', - 'sentry.origin': 'auto.http.browser', - 'sentry.sample_rate': 1, - 'sentry.source': 'custom', - 'server.address': 'example.com', - type: 'xhr', - url: 'http://example.com/0', - }, - description: 'GET http://example.com/0', - op: 'http.client', - origin: 'auto.http.browser', - status: 'ok', - trace_id: traceId, - span_id: spanId, - segment_id: spanId, - is_segment: true, - start_timestamp: expect.any(Number), - timestamp: expect.any(Number), - }); - - // the standalone span was sampled, so we propagate the positive sampling decision - expect(sentryTraceHeader).toBe(`${traceId}-${spanId}-1`); - expect(baggageHeader).toBe( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${traceId},sentry-sample_rate=1,sentry-transaction=GET%20http%3A%2F%2Fexample.com%2F0,sentry-sampled=true`, - ); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts index a05f0da3d91d..461fdc052f5e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event, SpanEnvelope } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { shouldSkipFeedbackTest } from '../../../../utils/helpers'; @@ -7,7 +7,6 @@ import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, - properFullEnvelopeRequestParser, shouldSkipTracingTest, } from '../../../../utils/helpers'; @@ -186,80 +185,6 @@ sentryTest('error during navigation has new navigation traceId', async ({ getLoc }); }); -sentryTest( - 'outgoing fetch request after navigation has navigation traceId in headers and standalone span', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://example.com/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - // ensure pageload transaction is finished - await getFirstSentryEnvelopeRequest(page, url); - - const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, - ); - - const navigationTraceContext = navigationEvent.contexts?.trace; - - expect(navigationEvent.type).toEqual('transaction'); - const navigationTraceId = navigationTraceContext?.trace_id; - - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); - - expect(navigationTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceId, - }); - - const spanEnvelopePromise = getFirstSentryEnvelopeRequest( - page, - undefined, - properFullEnvelopeRequestParser, - ); - const requestPromise = page.waitForRequest('http://example.com/*'); - await page.locator('#fetchBtn').click(); - const [request, spanEnvelope] = await Promise.all([requestPromise, spanEnvelopePromise]); - const headers = request.headers(); - - const spanEnvelopeTraceHeader = spanEnvelope[0].trace; - const spanEnvelopeItem = spanEnvelope[1][0][1]; - - expect(spanEnvelopeTraceHeader).toEqual(navigationTraceHeader); - - expect(spanEnvelopeItem).toMatchObject({ - trace_id: navigationTraceContext?.trace_id, - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - - // sampling decision and DSC are continued from navigation span, even after it ended - expect(headers['sentry-trace']).toEqual(`${navigationTraceId}-${spanEnvelopeItem.span_id}-1`); - expect(headers['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sample_rate=1,sentry-sampled=true`, - ); - }, -); - sentryTest( 'outgoing fetch request during navigation has navigation traceId in headers', async ({ getLocalTestUrl, page }) => { @@ -322,80 +247,6 @@ sentryTest( }, ); -sentryTest( - 'outgoing XHR request after navigation has navigation traceId in headers and in span envelope', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://example.com/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - // ensure navigation transaction is finished - await getFirstSentryEnvelopeRequest(page, url); - - const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, - ); - - const navigationTraceContext = navigationEvent.contexts?.trace; - expect(navigationEvent.type).toEqual('transaction'); - - const navigationTraceId = navigationTraceContext?.trace_id; - - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); - - expect(navigationTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceContext?.trace_id, - }); - - const xhrPromise = page.waitForRequest('http://example.com/*'); - const spanEnvelopePromise = getFirstSentryEnvelopeRequest( - page, - undefined, - properFullEnvelopeRequestParser, - ); - await page.locator('#xhrBtn').click(); - const [request, spanEnvelope] = await Promise.all([xhrPromise, spanEnvelopePromise]); - const headers = request.headers(); - - const spanEnvelopeTraceHeader = spanEnvelope[0].trace; - const spanEnvelopeItem = spanEnvelope[1][0][1]; - - expect(spanEnvelopeTraceHeader).toEqual(navigationTraceHeader); - - expect(spanEnvelopeItem).toMatchObject({ - trace_id: navigationTraceContext?.trace_id, - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - - // sampling decision and DSC are continued from navigation span, even after it ended - expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`)); - expect(headers['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sample_rate=1,sentry-sampled=true`, - ); - }, -); - sentryTest( 'outgoing XHR request during navigation has navigation traceId in headers', async ({ getLocalTestUrl, page }) => { diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts index 78582a8369c1..cfc0fcf8855d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event, SpanEnvelope } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { shouldSkipFeedbackTest } from '../../../../utils/helpers'; @@ -7,7 +7,6 @@ import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, - properFullEnvelopeRequestParser, shouldSkipTracingTest, } from '../../../../utils/helpers'; @@ -192,73 +191,6 @@ sentryTest('error during tag pageload has pageload traceId', async ({ get }); }); -sentryTest( - 'outgoing fetch request after tag pageload has pageload traceId in headers and span envelope', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://example.com/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadEvent?.contexts?.trace).toMatchObject({ - op: 'pageload', - trace_id: META_TAG_TRACE_ID, - parent_span_id: META_TAG_PARENT_SPAN_ID, - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - - expect(pageloadTraceHeader).toEqual({ - environment: 'prod', - release: '1.0.0', - sample_rate: '0.2', - sampled: 'true', - transaction: 'my-transaction', - public_key: 'public', - trace_id: META_TAG_TRACE_ID, - }); - - const requestPromise = page.waitForRequest('http://example.com/*'); - const spanEnvelopePromise = getFirstSentryEnvelopeRequest( - page, - undefined, - properFullEnvelopeRequestParser, - ); - await page.locator('#fetchBtn').click(); - const [request, spanEnvelope] = await Promise.all([requestPromise, spanEnvelopePromise]); - const headers = request.headers(); - - const spanEnvelopeTraceHeader = spanEnvelope[0].trace; - const spanEnvelopeItem = spanEnvelope[1][0][1]; - - expect(spanEnvelopeTraceHeader).toEqual(pageloadTraceHeader); - - expect(spanEnvelopeItem).toMatchObject({ - parent_span_id: META_TAG_PARENT_SPAN_ID, - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - trace_id: META_TAG_TRACE_ID, - }); - - // sampling decision is propagated from meta tag's sentry-trace sampled flag - expect(headers['sentry-trace']).toMatch(new RegExp(`^${META_TAG_TRACE_ID}-[0-9a-f]{16}-1$`)); - expect(headers['baggage']).toBe(META_TAG_BAGGAGE); - }, -); - sentryTest( 'outgoing fetch request during tag pageload has pageload traceId in headers', async ({ getLocalTestUrl, page }) => { @@ -312,72 +244,6 @@ sentryTest( }, ); -sentryTest( - 'outgoing XHR request after tag pageload has pageload traceId in headers', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - await page.route('http://example.com/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadEvent?.contexts?.trace).toMatchObject({ - op: 'pageload', - trace_id: META_TAG_TRACE_ID, - parent_span_id: META_TAG_PARENT_SPAN_ID, - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - expect(pageloadTraceHeader).toEqual({ - environment: 'prod', - release: '1.0.0', - sample_rate: '0.2', - sampled: 'true', - transaction: 'my-transaction', - public_key: 'public', - trace_id: META_TAG_TRACE_ID, - }); - - const requestPromise = page.waitForRequest('http://example.com/*'); - const spanEnvelopePromise = getFirstSentryEnvelopeRequest( - page, - undefined, - properFullEnvelopeRequestParser, - ); - await page.locator('#xhrBtn').click(); - const [request, spanEnvelope] = await Promise.all([requestPromise, spanEnvelopePromise]); - const headers = request.headers(); - - const spanEnvelopeTraceHeader = spanEnvelope[0].trace; - const spanEnvelopeItem = spanEnvelope[1][0][1]; - - expect(spanEnvelopeTraceHeader).toEqual(pageloadTraceHeader); - - expect(spanEnvelopeItem).toMatchObject({ - parent_span_id: META_TAG_PARENT_SPAN_ID, - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - trace_id: META_TAG_TRACE_ID, - }); - - // sampling decision is propagated from meta tag's sentry-trace sampled flag - expect(headers['sentry-trace']).toMatch(new RegExp(`^${META_TAG_TRACE_ID}-[0-9a-f]{16}-1$`)); - expect(headers['baggage']).toBe(META_TAG_BAGGAGE); - }, -); - sentryTest( 'outgoing XHR request during tag pageload has pageload traceId in headers', async ({ getLocalTestUrl, page }) => { diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts index 3b6a007c54fb..38665b37c5c3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { Event, SpanEnvelope } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { shouldSkipFeedbackTest } from '../../../../utils/helpers'; @@ -7,7 +7,6 @@ import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, - properFullEnvelopeRequestParser, shouldSkipTracingTest, } from '../../../../utils/helpers'; @@ -183,75 +182,6 @@ sentryTest('error during pageload has pageload traceId', async ({ getLocalTestUr }); }); -sentryTest( - 'outgoing fetch request after pageload has pageload traceId in headers and span envelope', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://example.com/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const pageloadTraceId = pageloadTraceContext?.trace_id; - - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); - - expect(pageloadTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: pageloadTraceId, - }); - - const requestPromise = page.waitForRequest('http://example.com/*'); - const spanEnvelopePromise = getFirstSentryEnvelopeRequest( - page, - undefined, - properFullEnvelopeRequestParser, - ); - await page.locator('#fetchBtn').click(); - const [request, spanEnvelope] = await Promise.all([requestPromise, spanEnvelopePromise]); - const headers = request.headers(); - - const spanEnvelopeTraceHeader = spanEnvelope[0].trace; - const spanEnvelopeItem = spanEnvelope[1][0][1]; - - expect(spanEnvelopeTraceHeader).toEqual(pageloadTraceHeader); - - expect(spanEnvelopeItem).toMatchObject({ - trace_id: pageloadTraceId, - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - - // sampling decision and DSC are continued from the pageload span even after it ended - expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}-1$`)); - expect(headers['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sample_rate=1,sentry-sampled=true`, - ); - }, -); - sentryTest( 'outgoing fetch request during pageload has pageload traceId in headers', async ({ getLocalTestUrl, page }) => { @@ -308,75 +238,6 @@ sentryTest( }, ); -sentryTest( - 'outgoing XHR request after pageload has pageload traceId in headers and span envelope', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://example.com/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const pageloadTraceId = pageloadTraceContext?.trace_id; - - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[0-9a-f]{32}$/), - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); - - expect(pageloadTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: pageloadTraceId, - }); - - const requestPromise = page.waitForRequest('http://example.com/*'); - const spanEnvelopePromise = getFirstSentryEnvelopeRequest( - page, - undefined, - properFullEnvelopeRequestParser, - ); - await page.locator('#xhrBtn').click(); - const [request, spanEnvelope] = await Promise.all([requestPromise, spanEnvelopePromise]); - const headers = request.headers(); - - const spanEnvelopeTraceHeader = spanEnvelope[0].trace; - const spanEnvelopeItem = spanEnvelope[1][0][1]; - - expect(spanEnvelopeTraceHeader).toEqual(pageloadTraceHeader); - - expect(spanEnvelopeItem).toMatchObject({ - trace_id: pageloadTraceId, - span_id: expect.stringMatching(/^[0-9a-f]{16}$/), - }); - - // sampling decision and DSC are continued from the pageload span even after it ended - expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}-1$`)); - expect(headers['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sample_rate=1,sentry-sampled=true`, - ); - }, -); - sentryTest( 'outgoing XHR request during pageload has pageload traceId in headers', async ({ getLocalTestUrl, page }) => { diff --git a/packages/browser/src/tracing/request.ts b/packages/browser/src/tracing/request.ts index 909acb647f17..673cff9e5474 100644 --- a/packages/browser/src/tracing/request.ts +++ b/packages/browser/src/tracing/request.ts @@ -7,7 +7,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, - getActiveSpan, getClient, getCurrentScope, getDynamicSamplingContextFromClient, @@ -322,8 +321,6 @@ export function xhrCallback( return undefined; } - const hasParent = !!getActiveSpan(); - const fullUrl = getFullURL(sentryXhrData.url); const host = fullUrl ? parseUrl(fullUrl).host : undefined; @@ -339,9 +336,6 @@ export function xhrCallback( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.browser', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', }, - experimental: { - standalone: !hasParent, - }, }) : new SentryNonRecordingSpan(); diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 5766c7d1d7bd..9a8ff09f1f50 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -17,7 +17,7 @@ import { } from './tracing'; import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; import { hasTracingEnabled } from './utils/hasTracingEnabled'; -import { getActiveSpan, spanToTraceHeader } from './utils/spanUtils'; +import { spanToTraceHeader } from './utils/spanUtils'; type PolymorphicRequestHeaders = | Record @@ -67,8 +67,6 @@ export function instrumentFetchRequest( const { method, url } = handlerData.fetchData; - const hasParent = !!getActiveSpan(); - const fullUrl = getFullURL(url); const host = fullUrl ? parseUrl(fullUrl).host : undefined; @@ -84,9 +82,6 @@ export function instrumentFetchRequest( [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin, [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client', }, - experimental: { - standalone: !hasParent, - }, }) : new SentryNonRecordingSpan(); From d06e2901cfed0f00557a36b49314f0a1e0b0bf92 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 May 2024 11:13:10 +0200 Subject: [PATCH 24/26] feat: Export `spanToBaggageHeader` utility (#11881) So we have a proper usage path without installing utils & core... --- MIGRATION.md | 2 +- packages/aws-serverless/src/index.ts | 1 + packages/browser/src/exports.ts | 1 + packages/bun/src/index.ts | 1 + packages/core/src/index.ts | 1 + packages/core/src/utils/spanUtils.ts | 10 ++++++++++ packages/deno/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + packages/node/src/index.ts | 1 + packages/remix/src/index.server.ts | 1 + packages/sveltekit/src/server/index.ts | 1 + packages/vercel-edge/src/index.ts | 1 + 12 files changed, 21 insertions(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index a5f64a76f181..ef1f17dcfe7d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1721,7 +1721,7 @@ In v8, the Span class is heavily reworked. The following properties & methods ar - `span.traceId`: Use `span.spanContext().traceId` instead. - `span.name`: Use `spanToJSON(span).description` instead. - `span.description`: Use `spanToJSON(span).description` instead. -- `span.getDynamicSamplingContext`: Use `getDynamicSamplingContextFromSpan` utility function instead. +- `span.getDynamicSamplingContext`: Use `spanToBaggageHeader(span)` utility function instead. - `span.tags`: Set tags on the surrounding scope instead, or use attributes. - `span.data`: Use `spanToJSON(span).data` instead. - `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope. diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 5d166fce43c8..ade19b700fcd 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -97,6 +97,7 @@ export { initOpenTelemetry, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, trpcMiddleware, addOpenTelemetryInstrumentation, zodErrorsIntegration, diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 3da765aaa0f7..1b5a9d294144 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -57,6 +57,7 @@ export { endSession, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, } from '@sentry/core'; export { diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index f0ab0d24e724..3f0be5d7a914 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -118,6 +118,7 @@ export { initOpenTelemetry, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, trpcMiddleware, addOpenTelemetryInstrumentation, zodErrorsIntegration, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 743a2e9d9bd8..75f8ceea4b6e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -67,6 +67,7 @@ export { handleCallbackErrors } from './utils/handleCallbackErrors'; export { parameterize } from './utils/parameterize'; export { spanToTraceHeader, + spanToBaggageHeader, spanToJSON, spanIsSampled, spanToTraceContext, diff --git a/packages/core/src/utils/spanUtils.ts b/packages/core/src/utils/spanUtils.ts index 55b3df65aa2b..8b83effd81f6 100644 --- a/packages/core/src/utils/spanUtils.ts +++ b/packages/core/src/utils/spanUtils.ts @@ -12,6 +12,7 @@ import type { import { addNonEnumerableProperty, dropUndefinedKeys, + dynamicSamplingContextToSentryBaggageHeader, generateSentryTraceHeader, timestampInSeconds, } from '@sentry/utils'; @@ -21,6 +22,7 @@ import { getCurrentScope } from '../currentScopes'; import { getMetricSummaryJsonForSpan, updateMetricSummaryOnSpan } from '../metrics/metric-summary'; import type { MetricType } from '../metrics/types'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes'; +import { getDynamicSamplingContextFromSpan } from '../tracing'; import type { SentrySpan } from '../tracing/sentrySpan'; import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from '../tracing/spanstatus'; import { _getSpanForScope } from './spanOnScope'; @@ -68,6 +70,14 @@ export function spanToTraceHeader(span: Span): string { return generateSentryTraceHeader(traceId, spanId, sampled); } +/** + * Convert a Span to a baggage header. + */ +export function spanToBaggageHeader(span: Span): string | undefined { + const dsc = getDynamicSamplingContextFromSpan(span); + return dynamicSamplingContextToSentryBaggageHeader(dsc); +} + /** * Convert a span time input intp a timestamp in seconds. */ diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index be740ac22665..a46c052ec2a9 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -78,6 +78,7 @@ export { endSession, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, } from '@sentry/core'; export { DenoClient } from './client'; diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index d6f774d633f0..79c39e74e870 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -97,6 +97,7 @@ export { initOpenTelemetry, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, trpcMiddleware, addOpenTelemetryInstrumentation, zodErrorsIntegration, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index c209a5d2ab8b..f31c38048aa6 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -110,6 +110,7 @@ export { getRootSpan, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, trpcMiddleware, zodErrorsIntegration, } from '@sentry/core'; diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index e8b25aa2936f..ff00f7a80e58 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -99,6 +99,7 @@ export { trpcMiddleware, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, addOpenTelemetryInstrumentation, } from '@sentry/node'; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 29f7dfd313a5..8f74221a1c64 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -72,6 +72,7 @@ export { trpcMiddleware, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, addOpenTelemetryInstrumentation, } from '@sentry/node'; diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 031787bebe22..8fbcebf40e8c 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -73,6 +73,7 @@ export { trpcMiddleware, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, } from '@sentry/core'; export { VercelEdgeClient } from './client'; From 5102de0ee7e5a70de74b34a25fd9c324cf81df84 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 3 May 2024 11:13:37 +0200 Subject: [PATCH 25/26] feat(nextjs): Be smarter in warning about old ways of init configuration (#11882) In theory it is possible that people still have files like `sentry.server.config.ts` and reference them from `instrumentation.ts`. In that case we do not want to show the warning about the old way. --- packages/nextjs/src/config/webpack.ts | 30 ++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index f1228d3ec936..a9e98cbb7d71 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -495,15 +495,39 @@ async function addSentryToClientEntryProperty( } /** - * Searches for old `sentry.(server|edge).config.ts` files and warns if it finds any. + * Searches for old `sentry.(server|edge).config.ts` files and Next.js instrumentation hooks and warns if there are "old" + * config files and no signs of them inside the instrumentation hook. * * @param projectDir The root directory of the project, where config files would be located * @param platform Either "server" or "edge", so that we know which file to look for */ function warnAboutDeprecatedConfigFiles(projectDir: string, platform: 'server' | 'edge'): void { - const possibilities = [`sentry.${platform}.config.ts`, `sentry.${platform}.config.js`]; + const hasInstrumentationHookWithIndicationsOfSentry = [ + ['src', 'instrumentation.ts'], + ['src', 'instrumentation.js'], + ['instrumentation.ts'], + ['instrumentation.js'], + ].some(potentialInstrumentationHookPathSegments => { + try { + const instrumentationHookContent = fs.readFileSync( + path.resolve(projectDir, ...potentialInstrumentationHookPathSegments), + { encoding: 'utf-8' }, + ); - for (const filename of possibilities) { + return ( + instrumentationHookContent.includes('@sentry/') || + instrumentationHookContent.match(/sentry\.(server|edge)\.config\.(ts|js)/) + ); + } catch (e) { + return false; + } + }); + + if (hasInstrumentationHookWithIndicationsOfSentry) { + return; + } + + for (const filename of [`sentry.${platform}.config.ts`, `sentry.${platform}.config.js`]) { if (fs.existsSync(path.resolve(projectDir, filename))) { // eslint-disable-next-line no-console console.warn( From cce2404a740336e9c71fbae588644c75f364bf5b Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 3 May 2024 11:15:32 +0200 Subject: [PATCH 26/26] meta(changelog): Update changelog for v8.0.0-beta.6 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd140416d447..c28f5dc0efe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,30 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.0.0-beta.6 + +This beta release contains various bugfixes and improvements for the v8 beta cycle. + +- feat: Add `tunnel` support to multiplexed transport (#11806) +- feat: Export `spanToBaggageHeader` utility (#11881) +- feat(browser): Disable standalone `http.client` spans (#11879) +- feat(ember): Update ember dependencies (#11753) +- feat(fedback): Convert CDN bundles to use async feedback for lower bundle sizes (#11791) +- feat(feedback): Add `captureFeedback` method (#11428) +- feat(feedback): Have screenshot by default (#11839) +- feat(integrations): Add zod integration (#11144) +- feat(ioredis): Add integration for `ioredis` (#11856) +- feat(nextjs): Add transaction name to scope of server component (#11850) +- feat(nextjs): Be smarter in warning about old ways of init configuration (#11882) +- feat(nextjs): Set transaction names on scope for route handlers and generation functions (#11869) +- feat(node): Support Node 22 (#11871) +- fix(angular): Run tracing calls outside Angular (#11748) +- fix(feedback): Be consistent about whether screenshot should and can render (#11859) +- fix(nestjs): Ensure Nest.js interceptor works with non-http context (#11880) +- fix(node): Fix nest.js error handler (#11874) +- fix(react): Fix react router v4/v5 instrumentation (#11855) +- ref: Add geo location types (#11847) + ## 8.0.0-beta.5 This beta release contains various bugfixes and improvements for the v8 beta cycle.