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/.github/workflows/build.yml b/.github/workflows/build.yml index 7609e25a856f..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: @@ -989,6 +989,8 @@ jobs: strategy: fail-fast: false matrix: + is_dependabot: + - ${{ github.actor == 'dependabot[bot]' }} test-application: [ 'angular-17', @@ -1006,6 +1008,7 @@ jobs: 'nextjs-14', 'react-create-hash-router', 'react-router-6-use-routes', + 'react-router-5', 'standard-frontend-react', 'svelte-5', 'sveltekit', @@ -1042,6 +1045,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 @@ -1287,6 +1294,8 @@ jobs: node: 18 - os: ubuntu-20.04 node: 20 + - os: ubuntu-20.04 + node: 22 # x64 musl - os: ubuntu-20.04 @@ -1298,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 @@ -1309,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 @@ -1323,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 @@ -1334,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/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. 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 diff --git a/MIGRATION.md b/MIGRATION.md index 25227a752e77..ef1f17dcfe7d 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 @@ -832,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 @@ -959,12 +978,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` @@ -1693,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/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/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'); +}); 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/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..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,12 +1,12 @@ 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'; import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, - properFullEnvelopeRequestParser, shouldSkipTracingTest, } from '../../../../utils/helpers'; @@ -134,7 +134,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( @@ -186,7 +186,7 @@ sentryTest('error during navigation has new navigation traceId', async ({ getLoc }); sentryTest( - 'outgoing fetch request after navigation has navigation traceId in headers and standalone span', + 'outgoing fetch request during navigation has navigation traceId in headers', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -202,20 +202,25 @@ sentryTest( }); }); - // ensure navigation transaction is finished + // ensure pageload transaction is finished await getFirstSentryEnvelopeRequest(page, url); - const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( + const navigationEventPromise = getFirstSentryEnvelopeRequest( page, - `${url}#foo`, + undefined, eventAndTraceHeaderRequestParser, ); + const requestPromise = page.waitForRequest('http://example.com/*'); + await page.goto(`${url}#foo`); + await page.locator('#fetchBtn').click(); + const [[navigationEvent, navigationTraceHeader], request] = await Promise.all([ + navigationEventPromise, + requestPromise, + ]); 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}$/), @@ -228,31 +233,14 @@ sentryTest( public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: navigationTraceId, + trace_id: navigationTraceContext?.trace_id, }); - 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`); + // sampling decision is propagated from active span sampling decision + const navigationTraceId = navigationTraceContext?.trace_id; + 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`, ); @@ -260,7 +248,7 @@ sentryTest( ); sentryTest( - 'outgoing fetch request during navigation has navigation traceId in headers', + 'outgoing XHR request during navigation has navigation traceId in headers', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -277,7 +265,7 @@ sentryTest( }); // ensure navigation transaction is finished - await getFirstSentryEnvelopeRequest(page, url); + await getFirstSentryEnvelopeRequest(page, url); const navigationEventPromise = getFirstSentryEnvelopeRequest( page, @@ -286,7 +274,7 @@ sentryTest( ); const requestPromise = page.waitForRequest('http://example.com/*'); await page.goto(`${url}#foo`); - await page.locator('#fetchBtn').click(); + await page.locator('#xhrBtn').click(); const [[navigationEvent, navigationTraceHeader], request] = await Promise.all([ navigationEventPromise, requestPromise, @@ -322,36 +310,20 @@ sentryTest( ); sentryTest( - 'outgoing XHR request after navigation has navigation traceId in headers and in span envelope', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { + 'user feedback event after navigation has navigation traceId in headers', + async ({ getLocalTestPath, page }) => { + if (shouldSkipTracingTest() || shouldSkipFeedbackTest()) { sentryTest.skip(); } - const url = await getLocalTestUrl({ testDir: __dirname }); + const url = await getLocalTestPath({ 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); + // ensure pageload transaction is finished + await getFirstSentryEnvelopeRequest(page, url); - const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, - ); + const navigationEvent = await getFirstSentryEnvelopeRequest(page, `${url}#foo`); 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}$/), @@ -359,100 +331,24 @@ sentryTest( }); 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 }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); + const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); - await page.route('http://example.com/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); + 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(); - // ensure navigation transaction is finished - await getFirstSentryEnvelopeRequest(page, url); - - const navigationEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); - const requestPromise = page.waitForRequest('http://example.com/*'); - await page.goto(`${url}#foo`); - await page.locator('#xhrBtn').click(); - const [[navigationEvent, navigationTraceHeader], request] = await Promise.all([ - navigationEventPromise, - requestPromise, - ]); + const feedbackEvent = await feedbackEventPromise; - const navigationTraceContext = navigationEvent.contexts?.trace; + expect(feedbackEvent.type).toEqual('feedback'); - expect(navigationEvent.type).toEqual('transaction'); - 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 feedbackTraceContext = feedbackEvent.contexts?.trace; - expect(navigationTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', + expect(feedbackTraceContext).toMatchObject({ trace_id: navigationTraceContext?.trace_id, + span_id: expect.stringMatching(/^[0-9a-f]{16}$/), }); - - const headers = request.headers(); - - // sampling decision is propagated from active span sampling decision - const navigationTraceId = navigationTraceContext?.trace_id; - 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`, - ); }, ); 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..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,12 +1,12 @@ import { expect } from '@playwright/test'; -import type { 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'; import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, - properFullEnvelopeRequestParser, shouldSkipTracingTest, } from '../../../../utils/helpers'; @@ -191,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 }) => { @@ -311,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 }) => { @@ -429,3 +296,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..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,12 +1,12 @@ import { expect } from '@playwright/test'; -import type { 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'; import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, - properFullEnvelopeRequestParser, shouldSkipTracingTest, } from '../../../../utils/helpers'; @@ -182,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 }) => { @@ -307,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 }) => { @@ -431,3 +293,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/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/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/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" + } + } } 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/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/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/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 diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index a200ca559f7a..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 @@ -80,6 +81,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 c892e8fd373c..ade19b700fcd 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, @@ -85,6 +86,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, @@ -95,8 +97,10 @@ export { initOpenTelemetry, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, trpcMiddleware, addOpenTelemetryInstrumentation, + zodErrorsIntegration, } from '@sentry/node'; export { 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..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 { @@ -88,6 +89,7 @@ export { init, onLoad, showReportDialog, + // eslint-disable-next-line deprecation/deprecation captureUserFeedback, } from './sdk'; 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..957583d79eeb 100644 --- a/packages/browser/src/index.bundle.feedback.ts +++ b/packages/browser/src/index.bundle.feedback.ts @@ -1,8 +1,15 @@ 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, +}; + +export { captureFeedback } from '@sentry/core'; 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..de8453db8784 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -13,15 +13,17 @@ export { withActiveSpan, getSpanDescendants, setMeasurement, + captureFeedback, } 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..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 { @@ -30,8 +31,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, @@ -63,6 +65,7 @@ export { setHttpStatus, makeMultiplexedTransport, moduleMetadataIntegration, + zodErrorsIntegration, } from '@sentry/core'; export type { Span } from '@sentry/types'; export { makeBrowserOfflineTransport } from './transports/offline'; 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/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/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); }); }); diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 329af504f73f..3f0be5d7a914 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, @@ -106,6 +107,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, @@ -116,8 +118,10 @@ export { initOpenTelemetry, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, trpcMiddleware, addOpenTelemetryInstrumentation, + zodErrorsIntegration, } from '@sentry/node'; export { diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 981566469115..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, @@ -141,6 +142,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, @@ -662,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/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(); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index df889035b7c4..75f8ceea4b6e 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, @@ -67,6 +67,7 @@ export { handleCallbackErrors } from './utils/handleCallbackErrors'; export { parameterize } from './utils/parameterize'; export { spanToTraceHeader, + spanToBaggageHeader, spanToJSON, spanIsSampled, spanToTraceContext, @@ -91,6 +92,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'; @@ -98,6 +100,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/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/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/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/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/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/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/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 f25c14864d84..a46c052ec2a9 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, @@ -67,6 +68,7 @@ export { extraErrorDataIntegration, rewriteFramesIntegration, sessionTimingIntegration, + zodErrorsIntegration, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, @@ -76,6 +78,7 @@ export { endSession, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, } from '@sentry/core'; export { DenoClient } from './client'; 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/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts index 7d4d9199bc80..fe5eceee4508 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', @@ -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), }); 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 0bfd796bb297..79c39e74e870 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, @@ -85,6 +86,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, @@ -95,8 +97,10 @@ export { initOpenTelemetry, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, trpcMiddleware, addOpenTelemetryInstrumentation, + zodErrorsIntegration, } from '@sentry/node'; export { 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/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(); + } } - } + }); }); }); }, 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'; 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( 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 a44561e969c9..f31c38048aa6 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'; @@ -89,6 +90,7 @@ export { captureException, captureEvent, captureMessage, + captureFeedback, captureConsoleIntegration, debugIntegration, dedupeIntegration, @@ -108,7 +110,9 @@ export { getRootSpan, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, trpcMiddleware, + zodErrorsIntegration, } from '@sentry/core'; export type { 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/nest.ts b/packages/node/src/integrations/tracing/nest.ts index b0b143774d2d..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 @@ -17,8 +19,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 +48,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()) { @@ -57,13 +59,30 @@ export function setupNestErrorHandler(app: MinimalNestJsApp): void { 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(); }, }); - 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); } 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/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/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'); } } diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index fdb9878206f6..ff00f7a80e58 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, @@ -86,6 +87,7 @@ export { mongooseIntegration, mysqlIntegration, mysql2Integration, + redisIntegration, nestIntegration, setupNestErrorHandler, postgresIntegration, @@ -97,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 07c297de608b..8f74221a1c64 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, @@ -71,6 +72,7 @@ export { trpcMiddleware, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, addOpenTelemetryInstrumentation, } from '@sentry/node'; 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/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']; } 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; } diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 963497cc619a..8fbcebf40e8c 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, @@ -64,6 +65,7 @@ export { inboundFiltersIntegration, linkedErrorsIntegration, requestDataIntegration, + zodErrorsIntegration, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, @@ -71,6 +73,7 @@ export { trpcMiddleware, spanToJSON, spanToTraceHeader, + spanToBaggageHeader, } from '@sentry/core'; export { VercelEdgeClient } from './client'; diff --git a/yarn.lock b/yarn.lock index 236f8b18b110..6ef60581df1f 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" @@ -2408,9 +2907,16 @@ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@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.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== - 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== +"@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" "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" @@ -4453,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" @@ -5490,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" @@ -5611,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" @@ -5711,6 +6356,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" @@ -5723,6 +6380,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" @@ -7940,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== @@ -10149,11 +10811,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 +10958,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 +10987,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 +11004,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 +11042,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 +11447,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 +11719,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 +12348,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" @@ -12071,6 +12774,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" @@ -12594,6 +13302,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 +14340,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 +14377,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 +14458,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 +14664,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 +14680,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 +14860,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 +15050,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 +16713,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 +17554,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== @@ -18142,6 +18929,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" @@ -20171,6 +20973,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" @@ -20199,7 +21006,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= @@ -22135,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" @@ -25219,6 +26026,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" @@ -25254,7 +26073,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 +26090,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 +26377,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 +26491,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== @@ -27069,6 +27905,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" @@ -29924,6 +30765,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"