diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28161bfb0ecc..ab411b85a506 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,35 @@
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
+## 7.96.0
+
+### Important Changes
+
+#### Deprecations
+
+This release includes some deprecations for integrations in `@sentry/browser` and frontend framework SDKs
+(`@sentry/react`, `@sentry/vue`, etc.). Please take a look at our
+[migration guide](https://github.com/getsentry/sentry-javascript/blob/develop/MIGRATION.md) for more details.
+
+- feat(browser): Export functional integrations & deprecate classes (#10267)
+
+#### Web Vitals Fix for LCP and CLS
+
+This release fixes an issue with the Web Vitals integration where LCP and CLS were not being captured correctly,
+increasing capture rate by 10-30% for some apps. LCP and CLS capturing issues were introduced with version `7.75.0`.
+
+- fix(tracing): Ensure web vitals are correctly stopped/captured (#10323)
+
+### Other Changes
+
+- feat(react): Add `stripBasename` option for React Router 6. (#10314)
+- fix(node): Fix `node-cron` types and add test (#10315)
+- fix(node): Fix downleveled types entry point (#10321)
+- fix(node): LocalVariables integration should use setupOnce (#10307)
+- fix(replay): Fix type for options of replayIntegration (#10325)
+
+Work in this release contributed by @Shubhdeep12. Thank you for your contribution!
+
## 7.95.0
### Important Changes
diff --git a/MIGRATION.md b/MIGRATION.md
index 0253907e3278..4c0ea3eddc91 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -34,17 +34,31 @@ integrations from the `Integrations.XXX` hash, is deprecated in favor of using t
The following list shows how integrations should be migrated:
-| Old | New | Packages |
-| ------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------- |
-| `new InboundFilters()` | `inboundFiltersIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
-| `new FunctionToString()` | `functionToStringIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
-| `new LinkedErrors()` | `linkedErrorsIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
-| `new ModuleMetadata()` | `moduleMetadataIntegration()` | `@sentry/core`, `@sentry/browser` |
-| `new RequestData()` | `requestDataIntegration()` | `@sentry/core`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
-| `new Wasm() ` | `wasmIntegration()` | `@sentry/wasm` |
-| `new Replay()` | `replayIntegration()` | `@sentry/browser` |
-| `new ReplayCanvas()` | `replayCanvasIntegration()` | `@sentry/browser` |
-| `new Feedback()` | `feedbackIntegration()` | `@sentry/browser` |
+| Old | New | Packages |
+| ------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------- |
+| `new InboundFilters()` | `inboundFiltersIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
+| `new FunctionToString()` | `functionToStringIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
+| `new LinkedErrors()` | `linkedErrorsIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
+| `new ModuleMetadata()` | `moduleMetadataIntegration()` | `@sentry/core`, `@sentry/browser` |
+| `new RequestData()` | `requestDataIntegration()` | `@sentry/core`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
+| `new Wasm() ` | `wasmIntegration()` | `@sentry/wasm` |
+| `new Replay()` | `replayIntegration()` | `@sentry/browser` |
+| `new ReplayCanvas()` | `replayCanvasIntegration()` | `@sentry/browser` |
+| `new Feedback()` | `feedbackIntegration()` | `@sentry/browser` |
+| `new CaptureConsole()` | `captureConsoleIntegration()` | `@sentry/integrations` |
+| `new Debug()` | `debugIntegration()` | `@sentry/integrations` |
+| `new Dedupe()` | `dedupeIntegration()` | `@sentry/browser`, `@sentry/integrations`, `@sentry/deno` |
+| `new ExtraErrorData()` | `extraErrorDataIntegration()` | `@sentry/integrations` |
+| `new ReportingObserver()` | `reportingObserverIntegration()` | `@sentry/integrations` |
+| `new RewriteFrames()` | `rewriteFramesIntegration()` | `@sentry/integrations` |
+| `new SessionTiming()` | `sessionTimingIntegration()` | `@sentry/integrations` |
+| `new HttpClient()` | `httpClientIntegration()` | `@sentry/integrations` |
+| `new ContextLines()` | `contextLinesIntegration()` | `@sentry/browser` |
+| `new Breadcrumbs()` | `breadcrumbsIntegration()` | `@sentry/browser`, `@sentry/deno` |
+| `new GlobalHandlers()` | `globalHandlersIntegration()` | `@sentry/browser` |
+| `new HttpContext()` | `httpContextIntegration()` | `@sentry/browser` |
+| `new TryCatch()` | `browserApiErrorsIntegration()` | `@sentry/browser`, `@sentry/deno` |
+| `new VueIntegration()` | `vueIntegration()` | `@sentry/vue` |
## Deprecate `hub.bindClient()` and `makeMain()`
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/assets/sentry-logo-600x179.png b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/assets/sentry-logo-600x179.png
new file mode 100644
index 000000000000..353b7233d6bf
Binary files /dev/null and b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/assets/sentry-logo-600x179.png differ
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/subject.js b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/subject.js
new file mode 100644
index 000000000000..d0f8df871ee3
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/subject.js
@@ -0,0 +1,18 @@
+import { addLcpInstrumentationHandler } from '@sentry-internal/tracing';
+
+addLcpInstrumentationHandler(({ metric }) => {
+ const entry = metric.entries[metric.entries.length - 1];
+ window._LCP = entry.size;
+});
+
+addLcpInstrumentationHandler(({ metric }) => {
+ const entry = metric.entries[metric.entries.length - 1];
+ window._LCP2 = entry.size;
+});
+
+window.ADD_HANDLER = () => {
+ addLcpInstrumentationHandler(({ metric }) => {
+ const entry = metric.entries[metric.entries.length - 1];
+ window._LCP3 = entry.size;
+ });
+};
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/template.html b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/template.html
new file mode 100644
index 000000000000..caf4b8f2deab
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/template.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/test.ts
new file mode 100644
index 000000000000..23cd29099a0f
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/handlers-lcp/test.ts
@@ -0,0 +1,51 @@
+import type { Route } from '@playwright/test';
+import { expect } from '@playwright/test';
+import type { Event } from '@sentry/types';
+
+import { sentryTest } from '../../../../utils/fixtures';
+import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers';
+
+const bundle = process.env.PW_BUNDLE || '';
+
+sentryTest(
+ 'should capture metrics for LCP instrumentation handlers',
+ async ({ browserName, getLocalTestPath, page }) => {
+ // This uses a utility that is not exported in CDN bundles
+ if (shouldSkipTracingTest() || browserName !== 'chromium' || bundle.startsWith('bundle')) {
+ sentryTest.skip();
+ }
+
+ await page.route('**/path/to/image.png', (route: Route) =>
+ route.fulfill({ path: `${__dirname}/assets/sentry-logo-600x179.png` }),
+ );
+
+ const url = await getLocalTestPath({ testDir: __dirname });
+ const [eventData] = await Promise.all([
+ getFirstSentryEnvelopeRequest(page),
+ page.goto(url),
+ page.click('button'),
+ ]);
+
+ expect(eventData.measurements).toBeDefined();
+ expect(eventData.measurements?.lcp?.value).toBeDefined();
+
+ expect(eventData.tags?.['lcp.element']).toBe('body > img');
+ expect(eventData.tags?.['lcp.size']).toBe(107400);
+ expect(eventData.tags?.['lcp.url']).toBe('https://example.com/path/to/image.png');
+
+ const lcp = await (await page.waitForFunction('window._LCP')).jsonValue();
+ const lcp2 = await (await page.waitForFunction('window._LCP2')).jsonValue();
+ const lcp3 = await page.evaluate('window._LCP3');
+
+ expect(lcp).toEqual(107400);
+ expect(lcp2).toEqual(107400);
+ // this has not been triggered yet
+ expect(lcp3).toEqual(undefined);
+
+ // Adding a handler after LCP is completed still triggers the handler
+ await page.evaluate('window.ADD_HANDLER()');
+ const lcp3_2 = await (await page.waitForFunction('window._LCP3')).jsonValue();
+
+ expect(lcp3_2).toEqual(107400);
+ },
+);
diff --git a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts
index 698cb83b5fb4..0b454ba12214 100644
--- a/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts
+++ b/dev-packages/e2e-tests/test-applications/react-create-hash-router/tests/fixtures/ReplayRecordingData.ts
@@ -215,7 +215,11 @@ export const ReplayRecordingData = [
description: 'largest-contentful-paint',
startTimestamp: expect.any(Number),
endTimestamp: expect.any(Number),
- data: { value: expect.any(Number), size: expect.any(Number) },
+ data: {
+ value: expect.any(Number),
+ size: expect.any(Number),
+ nodeId: 16,
+ },
},
},
},
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 698cb83b5fb4..0b454ba12214 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
@@ -215,7 +215,11 @@ export const ReplayRecordingData = [
description: 'largest-contentful-paint',
startTimestamp: expect.any(Number),
endTimestamp: expect.any(Number),
- data: { value: expect.any(Number), size: expect.any(Number) },
+ data: {
+ value: expect.any(Number),
+ size: expect.any(Number),
+ nodeId: 16,
+ },
},
},
},
diff --git a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts b/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts
index 698cb83b5fb4..0b454ba12214 100644
--- a/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts
+++ b/dev-packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts
@@ -215,7 +215,11 @@ export const ReplayRecordingData = [
description: 'largest-contentful-paint',
startTimestamp: expect.any(Number),
endTimestamp: expect.any(Number),
- data: { value: expect.any(Number), size: expect.any(Number) },
+ data: {
+ value: expect.any(Number),
+ size: expect.any(Number),
+ nodeId: 16,
+ },
},
},
},
diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json
index f36333c4daa8..10596903e2b2 100644
--- a/dev-packages/node-integration-tests/package.json
+++ b/dev-packages/node-integration-tests/package.json
@@ -36,10 +36,12 @@
"apollo-server": "^3.11.1",
"axios": "^0.27.2",
"cors": "^2.8.5",
+ "cron": "^3.1.6",
"express": "^4.17.3",
"graphql": "^16.3.0",
"http-terminator": "^3.2.0",
"mongodb": "^3.7.3",
+ "mongoose": "^5.13.22",
"mongodb-memory-server-global": "^7.6.3",
"mysql": "^2.18.1",
"nock": "^13.1.0",
diff --git a/dev-packages/node-integration-tests/suites/cron/node-cron/scenario.ts b/dev-packages/node-integration-tests/suites/cron/node-cron/scenario.ts
new file mode 100644
index 000000000000..71b005d4dfd6
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/cron/node-cron/scenario.ts
@@ -0,0 +1,9 @@
+import * as Sentry from '@sentry/node';
+import { CronJob } from 'cron';
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const CronJobWithCheckIn = Sentry.cron.instrumentCron(CronJob, 'my-cron-job');
+
+setTimeout(() => {
+ process.exit(0);
+}, 1_000);
diff --git a/dev-packages/node-integration-tests/suites/cron/node-cron/test.ts b/dev-packages/node-integration-tests/suites/cron/node-cron/test.ts
new file mode 100644
index 000000000000..b768599cd215
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/cron/node-cron/test.ts
@@ -0,0 +1,9 @@
+import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
+
+afterAll(() => {
+ cleanupChildProcesses();
+});
+
+test('node-cron types should match', done => {
+ createRunner(__dirname, 'scenario.ts').ensureNoErrorOutput().start(done);
+});
diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario.js
new file mode 100644
index 000000000000..42bb8edacb8f
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/scenario.js
@@ -0,0 +1,52 @@
+const Sentry = require('@sentry/node-experimental');
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+
+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);
+
+async function run() {
+ const { ApolloServer, gql } = require('apollo-server');
+
+ await Sentry.startSpan(
+ {
+ name: 'Test Transaction',
+ op: 'transaction',
+ },
+ async span => {
+ const typeDefs = gql`type Query { hello: String }`;
+
+ const resolvers = {
+ Query: {
+ hello: () => {
+ return 'Hello world!';
+ },
+ },
+ };
+
+ const server = new ApolloServer({
+ typeDefs,
+ resolvers,
+ });
+
+ // Ref: https://www.apollographql.com/docs/apollo-server/testing/testing/#testing-using-executeoperation
+ await server.executeOperation({
+ query: '{hello}',
+ });
+
+ setTimeout(() => {
+ span.end();
+ server.stop();
+ }, 500);
+ },
+ );
+}
+
+// eslint-disable-next-line @typescript-eslint/no-floating-promises
+run();
diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/test.ts
new file mode 100644
index 000000000000..dc7c304484f9
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing-experimental/apollo-graphql/test.ts
@@ -0,0 +1,38 @@
+import { conditionalTest } from '../../../utils';
+import { createRunner } from '../../../utils/runner';
+
+conditionalTest({ min: 14 })('GraphQL/Apollo Tests', () => {
+ const EXPECTED_TRANSACTION = {
+ transaction: 'Test Transaction',
+ spans: expect.arrayContaining([
+ expect.objectContaining({
+ data: {
+ 'graphql.operation.type': 'query',
+ 'graphql.source': '{hello}',
+ 'otel.kind': 'INTERNAL',
+ 'sentry.origin': 'auto.graphql.otel.graphql',
+ },
+ description: 'query',
+ status: 'ok',
+ origin: 'auto.graphql.otel.graphql',
+ }),
+ expect.objectContaining({
+ data: {
+ 'graphql.field.name': 'hello',
+ 'graphql.field.path': 'hello',
+ 'graphql.field.type': 'String',
+ 'graphql.source': 'hello',
+ 'otel.kind': 'INTERNAL',
+ 'sentry.origin': 'manual',
+ },
+ description: 'graphql.resolve',
+ status: 'ok',
+ origin: 'manual',
+ }),
+ ]),
+ };
+
+ test('CJS - should instrument GraphQL queries used from Apollo Server.', done => {
+ createRunner(__dirname, 'scenario.js').expect({ transaction: EXPECTED_TRANSACTION }).start(done);
+ });
+});
diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js
new file mode 100644
index 000000000000..47a67e4aaf78
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/scenario.js
@@ -0,0 +1,50 @@
+const { loggingTransport } = require('@sentry-internal/node-integration-tests');
+const Sentry = require('@sentry/node-experimental');
+
+Sentry.init({
+ dsn: 'https://public@dsn.ingest.sentry.io/1337',
+ release: '1.0',
+ debug: true,
+ tracesSampleRate: 1.0,
+ transport: loggingTransport,
+});
+
+// Stop the process from exiting before the transaction is sent
+setInterval(() => {}, 1000);
+
+// Must be required after Sentry is initialized
+const mongoose = require('mongoose');
+
+async function run() {
+ await mongoose.connect(process.env.MONGO_URL || '');
+
+ const Schema = mongoose.Schema;
+
+ const BlogPostSchema = new Schema({
+ title: String,
+ body: String,
+ date: Date,
+ });
+
+ const BlogPost = mongoose.model('BlogPost', BlogPostSchema);
+
+ await Sentry.startSpan(
+ {
+ name: 'Test Transaction',
+ op: 'transaction',
+ },
+ async () => {
+ const post = new BlogPost();
+ post.title = 'Test';
+ post.body = 'Test body';
+ post.date = new Date();
+
+ await post.save();
+
+ await BlogPost.findOne({});
+ },
+ );
+}
+
+// eslint-disable-next-line @typescript-eslint/no-floating-promises
+run();
diff --git a/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/test.ts b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/test.ts
new file mode 100644
index 000000000000..1a246d8ec5b9
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/tracing-experimental/mongoose/test.ts
@@ -0,0 +1,54 @@
+import { MongoMemoryServer } from 'mongodb-memory-server-global';
+
+import { conditionalTest } from '../../../utils';
+import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
+
+jest.setTimeout(20000);
+
+conditionalTest({ min: 14 })('Mongoose experimental Test', () => {
+ let mongoServer: MongoMemoryServer;
+
+ beforeAll(async () => {
+ mongoServer = await MongoMemoryServer.create();
+ process.env.MONGO_URL = mongoServer.getUri();
+ }, 10000);
+
+ afterAll(async () => {
+ if (mongoServer) {
+ await mongoServer.stop();
+ }
+ cleanupChildProcesses();
+ });
+
+ const EXPECTED_TRANSACTION = {
+ transaction: 'Test Transaction',
+ spans: expect.arrayContaining([
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'db.mongodb.collection': 'blogposts',
+ 'db.name': 'test',
+ 'db.operation': 'save',
+ 'db.system': 'mongoose',
+ }),
+ description: 'mongoose.BlogPost.save',
+ op: 'db',
+ origin: 'auto.db.otel.mongoose',
+ }),
+ expect.objectContaining({
+ data: expect.objectContaining({
+ 'db.mongodb.collection': 'blogposts',
+ 'db.name': 'test',
+ 'db.operation': 'findOne',
+ 'db.system': 'mongoose',
+ }),
+ description: 'mongoose.BlogPost.findOne',
+ op: 'db',
+ origin: 'auto.db.otel.mongoose',
+ }),
+ ]),
+ };
+
+ test('CJS - should auto-instrument `mongoose` package.', done => {
+ createRunner(__dirname, 'scenario.js').expect({ transaction: EXPECTED_TRANSACTION }).start(done);
+ });
+});
diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts
index 1831761c3181..31969452ba74 100644
--- a/dev-packages/node-integration-tests/utils/runner.ts
+++ b/dev-packages/node-integration-tests/utils/runner.ts
@@ -70,6 +70,7 @@ export function createRunner(...paths: string[]) {
const flags: string[] = [];
const ignored: EnvelopeItemType[] = [];
let withSentryServer = false;
+ let ensureNoErrorOutput = false;
if (testPath.endsWith('.ts')) {
flags.push('-r', 'ts-node/register');
@@ -92,6 +93,10 @@ export function createRunner(...paths: string[]) {
ignored.push(...types);
return this;
},
+ ensureNoErrorOutput: function () {
+ ensureNoErrorOutput = true;
+ return this;
+ },
start: function (done?: (e?: unknown) => void) {
const expectedEnvelopeCount = expectedEnvelopes.length;
@@ -190,8 +195,19 @@ export function createRunner(...paths: string[]) {
CHILD_PROCESSES.add(child);
+ if (ensureNoErrorOutput) {
+ child.stderr.on('data', (data: Buffer) => {
+ const output = data.toString();
+ complete(new Error(`Expected no error output but got: '${output}'`));
+ });
+ }
+
child.on('close', () => {
hasExited = true;
+
+ if (ensureNoErrorOutput) {
+ complete();
+ }
});
// Pass error to done to end the test quickly
diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts
index 146d6a6e23b0..026321e8ab3d 100644
--- a/packages/astro/src/index.types.ts
+++ b/packages/astro/src/index.types.ts
@@ -14,8 +14,11 @@ import sentryAstro from './index.server';
export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): void;
// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere.
+// eslint-disable-next-line deprecation/deprecation
export declare const Integrations: typeof clientSdk.Integrations & typeof serverSdk.Integrations;
+export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
+
export declare const defaultIntegrations: Integration[];
export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts
index 5d012bdea2b7..e9e947de8559 100644
--- a/packages/browser/src/exports.ts
+++ b/packages/browser/src/exports.ts
@@ -102,4 +102,13 @@ export {
// eslint-disable-next-line deprecation/deprecation
wrap,
} from './sdk';
+
+export { breadcrumbsIntegration } from './integrations/breadcrumbs';
+export { dedupeIntegration } from './integrations/dedupe';
+export { globalHandlersIntegration } from './integrations/globalhandlers';
+export { httpContextIntegration } from './integrations/httpcontext';
+export { linkedErrorsIntegration } from './integrations/linkederrors';
+export { browserApiErrorsIntegration } from './integrations/trycatch';
+
+// eslint-disable-next-line deprecation/deprecation
export { GlobalHandlers, TryCatch, Breadcrumbs, LinkedErrors, HttpContext, Dedupe } from './integrations';
diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts
index 6c91e141149a..19c377fc5931 100644
--- a/packages/browser/src/index.ts
+++ b/packages/browser/src/index.ts
@@ -12,6 +12,7 @@ if (WINDOW.Sentry && WINDOW.Sentry.Integrations) {
windowIntegrations = WINDOW.Sentry.Integrations;
}
+/** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */
const INTEGRATIONS = {
...windowIntegrations,
// eslint-disable-next-line deprecation/deprecation
@@ -19,6 +20,7 @@ const INTEGRATIONS = {
...BrowserIntegrations,
};
+// eslint-disable-next-line deprecation/deprecation
export { INTEGRATIONS as Integrations };
export {
diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts
index 485b19d3b711..1cbd8321346e 100644
--- a/packages/browser/src/integrations/breadcrumbs.ts
+++ b/packages/browser/src/integrations/breadcrumbs.ts
@@ -1,5 +1,5 @@
/* eslint-disable max-lines */
-import { addBreadcrumb, convertIntegrationFnToClass, getClient } from '@sentry/core';
+import { addBreadcrumb, convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
import type {
Client,
Event as SentryEvent,
@@ -57,7 +57,7 @@ const MAX_ALLOWED_STRING_LENGTH = 1024;
const INTEGRATION_NAME = 'Breadcrumbs';
-const breadcrumbsIntegration = ((options: Partial = {}) => {
+const _breadcrumbsIntegration = ((options: Partial = {}) => {
const _options = {
console: true,
dom: true,
@@ -95,8 +95,12 @@ const breadcrumbsIntegration = ((options: Partial = {}) => {
};
}) satisfies IntegrationFn;
+export const breadcrumbsIntegration = defineIntegration(_breadcrumbsIntegration);
+
/**
* Default Breadcrumbs instrumentations
+ *
+ * @deprecated Use `breadcrumbsIntegration()` instead.
*/
// eslint-disable-next-line deprecation/deprecation
export const Breadcrumbs = convertIntegrationFnToClass(INTEGRATION_NAME, breadcrumbsIntegration) as IntegrationClass<
diff --git a/packages/browser/src/integrations/dedupe.ts b/packages/browser/src/integrations/dedupe.ts
index 394d5c5ae1e3..f4ace6011b19 100644
--- a/packages/browser/src/integrations/dedupe.ts
+++ b/packages/browser/src/integrations/dedupe.ts
@@ -1,4 +1,4 @@
-import { convertIntegrationFnToClass } from '@sentry/core';
+import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types';
import { logger } from '@sentry/utils';
@@ -6,7 +6,7 @@ import { DEBUG_BUILD } from '../debug-build';
const INTEGRATION_NAME = 'Dedupe';
-const dedupeIntegration = (() => {
+const _dedupeIntegration = (() => {
let previousEvent: Event | undefined;
return {
@@ -33,7 +33,12 @@ const dedupeIntegration = (() => {
};
}) satisfies IntegrationFn;
-/** Deduplication filter */
+export const dedupeIntegration = defineIntegration(_dedupeIntegration);
+
+/**
+ * Deduplication filter.
+ * @deprecated Use `dedupeIntegration()` instead.
+ */
// eslint-disable-next-line deprecation/deprecation
export const Dedupe = convertIntegrationFnToClass(INTEGRATION_NAME, dedupeIntegration) as IntegrationClass<
Integration & { processEvent: (event: Event) => Event }
diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts
index 01ef16a02851..c1096e7c2132 100644
--- a/packages/browser/src/integrations/globalhandlers.ts
+++ b/packages/browser/src/integrations/globalhandlers.ts
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
-import { captureEvent, convertIntegrationFnToClass, getClient } from '@sentry/core';
+import { captureEvent, convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
import type {
Client,
Event,
@@ -30,7 +30,7 @@ type GlobalHandlersIntegrations = Record = {}) => {
+const _globalHandlersIntegration = ((options: Partial = {}) => {
const _options = {
onerror: true,
onunhandledrejection: true,
@@ -55,7 +55,12 @@ const globalHandlersIntegration = ((options: Partial
};
}) satisfies IntegrationFn;
-/** Global handlers */
+export const globalHandlersIntegration = defineIntegration(_globalHandlersIntegration);
+
+/**
+ * Global handlers.
+ * @deprecated Use `globalHandlersIntegration()` instead.
+ */
// eslint-disable-next-line deprecation/deprecation
export const GlobalHandlers = convertIntegrationFnToClass(
INTEGRATION_NAME,
diff --git a/packages/browser/src/integrations/httpcontext.ts b/packages/browser/src/integrations/httpcontext.ts
index 569eeffab45c..1a5e95f2eee3 100644
--- a/packages/browser/src/integrations/httpcontext.ts
+++ b/packages/browser/src/integrations/httpcontext.ts
@@ -1,11 +1,11 @@
-import { convertIntegrationFnToClass } from '@sentry/core';
+import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
import { WINDOW } from '../helpers';
const INTEGRATION_NAME = 'HttpContext';
-const httpContextIntegration = (() => {
+const _httpContextIntegration = (() => {
return {
name: INTEGRATION_NAME,
// TODO v8: Remove this
@@ -33,7 +33,12 @@ const httpContextIntegration = (() => {
};
}) satisfies IntegrationFn;
-/** HttpContext integration collects information about HTTP request headers */
+export const httpContextIntegration = defineIntegration(_httpContextIntegration);
+
+/**
+ * HttpContext integration collects information about HTTP request headers.
+ * @deprecated Use `httpContextIntegration()` instead.
+ */
// eslint-disable-next-line deprecation/deprecation
export const HttpContext = convertIntegrationFnToClass(INTEGRATION_NAME, httpContextIntegration) as IntegrationClass<
Integration & { preprocessEvent: (event: Event) => void }
diff --git a/packages/browser/src/integrations/index.ts b/packages/browser/src/integrations/index.ts
index e029422f363c..17ac85b31232 100644
--- a/packages/browser/src/integrations/index.ts
+++ b/packages/browser/src/integrations/index.ts
@@ -1,3 +1,4 @@
+/* eslint-disable deprecation/deprecation */
export { GlobalHandlers } from './globalhandlers';
export { TryCatch } from './trycatch';
export { Breadcrumbs } from './breadcrumbs';
diff --git a/packages/browser/src/integrations/linkederrors.ts b/packages/browser/src/integrations/linkederrors.ts
index 8a166e7667d9..b03855303c58 100644
--- a/packages/browser/src/integrations/linkederrors.ts
+++ b/packages/browser/src/integrations/linkederrors.ts
@@ -1,4 +1,4 @@
-import { convertIntegrationFnToClass } from '@sentry/core';
+import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
import type { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
import { applyAggregateErrorsToEvent } from '@sentry/utils';
import { exceptionFromError } from '../eventbuilder';
@@ -13,7 +13,7 @@ const DEFAULT_LIMIT = 5;
const INTEGRATION_NAME = 'LinkedErrors';
-const linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => {
+const _linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => {
const limit = options.limit || DEFAULT_LIMIT;
const key = options.key || DEFAULT_KEY;
@@ -38,7 +38,12 @@ const linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => {
};
}) satisfies IntegrationFn;
-/** Aggregrate linked errors in an event. */
+export const linkedErrorsIntegration = defineIntegration(_linkedErrorsIntegration);
+
+/**
+ * Aggregrate linked errors in an event.
+ * @deprecated Use `linkedErrorsIntegration()` instead.
+ */
// eslint-disable-next-line deprecation/deprecation
export const LinkedErrors = convertIntegrationFnToClass(INTEGRATION_NAME, linkedErrorsIntegration) as IntegrationClass<
Integration & { preprocessEvent: (event: Event, hint: EventHint, client: Client) => void }
diff --git a/packages/browser/src/integrations/trycatch.ts b/packages/browser/src/integrations/trycatch.ts
index 2f5f06592805..bd7c50331022 100644
--- a/packages/browser/src/integrations/trycatch.ts
+++ b/packages/browser/src/integrations/trycatch.ts
@@ -1,4 +1,4 @@
-import { convertIntegrationFnToClass } from '@sentry/core';
+import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
import type { Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types';
import { fill, getFunctionName, getOriginalFunction } from '@sentry/utils';
@@ -50,7 +50,7 @@ interface TryCatchOptions {
eventTarget: boolean | string[];
}
-const browserApiErrorsIntegration = ((options: Partial = {}) => {
+const _browserApiErrorsIntegration = ((options: Partial = {}) => {
const _options = {
XMLHttpRequest: true,
eventTarget: true,
@@ -90,7 +90,12 @@ const browserApiErrorsIntegration = ((options: Partial = {}) =>
};
}) satisfies IntegrationFn;
-/** Wrap timer functions and event targets to catch errors and provide better meta data */
+export const browserApiErrorsIntegration = defineIntegration(_browserApiErrorsIntegration);
+
+/**
+ * Wrap timer functions and event targets to catch errors and provide better meta data.
+ * @deprecated Use `browserApiErrorsIntegration()` instead.
+ */
// eslint-disable-next-line deprecation/deprecation
export const TryCatch = convertIntegrationFnToClass(
INTEGRATION_NAME,
diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts
index 6332cdac9d4b..1c57f534867c 100644
--- a/packages/browser/src/sdk.ts
+++ b/packages/browser/src/sdk.ts
@@ -1,7 +1,6 @@
import type { Hub } from '@sentry/core';
+import { functionToStringIntegration, inboundFiltersIntegration } from '@sentry/core';
import {
- FunctionToString,
- InboundFilters,
captureSession,
getClient,
getCurrentHub,
@@ -23,22 +22,25 @@ import { BrowserClient } from './client';
import { DEBUG_BUILD } from './debug-build';
import type { ReportDialogOptions } from './helpers';
import { WINDOW, wrap as internalWrap } from './helpers';
-import { Breadcrumbs, Dedupe, GlobalHandlers, HttpContext, LinkedErrors, TryCatch } from './integrations';
+import { breadcrumbsIntegration } from './integrations/breadcrumbs';
+import { dedupeIntegration } from './integrations/dedupe';
+import { globalHandlersIntegration } from './integrations/globalhandlers';
+import { httpContextIntegration } from './integrations/httpcontext';
+import { linkedErrorsIntegration } from './integrations/linkederrors';
+import { browserApiErrorsIntegration } from './integrations/trycatch';
import { defaultStackParser } from './stack-parsers';
import { makeFetchTransport, makeXHRTransport } from './transports';
/** @deprecated Use `getDefaultIntegrations(options)` instead. */
export const defaultIntegrations = [
- /* eslint-disable deprecation/deprecation */
- new InboundFilters(),
- new FunctionToString(),
- /* eslint-enable deprecation/deprecation */
- new TryCatch(),
- new Breadcrumbs(),
- new GlobalHandlers(),
- new LinkedErrors(),
- new Dedupe(),
- new HttpContext(),
+ inboundFiltersIntegration(),
+ functionToStringIntegration(),
+ browserApiErrorsIntegration(),
+ breadcrumbsIntegration(),
+ globalHandlersIntegration(),
+ linkedErrorsIntegration(),
+ dedupeIntegration(),
+ httpContextIntegration(),
];
/** Get the default integrations for the browser SDK. */
diff --git a/packages/browser/test/unit/integrations/breadcrumbs.test.ts b/packages/browser/test/unit/integrations/breadcrumbs.test.ts
index c2aea2ee170f..28764c2cddfa 100644
--- a/packages/browser/test/unit/integrations/breadcrumbs.test.ts
+++ b/packages/browser/test/unit/integrations/breadcrumbs.test.ts
@@ -8,6 +8,7 @@ describe('Breadcrumbs', () => {
const client = new BrowserClient({
...getDefaultBrowserClientOptions(),
dsn: 'https://username@domain/123',
+ // eslint-disable-next-line deprecation/deprecation
integrations: [new Breadcrumbs()],
});
diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts
index 7adfcae9a894..65d82a0e6779 100644
--- a/packages/deno/src/index.ts
+++ b/packages/deno/src/index.ts
@@ -94,6 +94,7 @@ export {
init,
} from './sdk';
+export { breadcrumbsIntegration, dedupeIntegration } from '@sentry/browser';
import { Integrations as CoreIntegrations } from '@sentry/core';
import * as DenoIntegrations from './integrations';
diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts
index 81834a8b0081..990eb8146039 100644
--- a/packages/deno/src/sdk.ts
+++ b/packages/deno/src/sdk.ts
@@ -1,6 +1,6 @@
-import { Breadcrumbs, Dedupe } from '@sentry/browser';
+import { breadcrumbsIntegration, dedupeIntegration } from '@sentry/browser';
import type { ServerRuntimeClientOptions } from '@sentry/core';
-import { FunctionToString, InboundFilters, LinkedErrors } from '@sentry/core';
+import { functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration } from '@sentry/core';
import { getIntegrationsToSetup, initAndBind } from '@sentry/core';
import type { Integration, Options, StackParser } from '@sentry/types';
import { createStackParser, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils';
@@ -12,15 +12,13 @@ import type { DenoOptions } from './types';
/** @deprecated Use `getDefaultIntegrations(options)` instead. */
export const defaultIntegrations = [
- /* eslint-disable deprecation/deprecation */
// Common
- new InboundFilters(),
- new FunctionToString(),
- new LinkedErrors(),
- /* eslint-enable deprecation/deprecation */
+ inboundFiltersIntegration(),
+ functionToStringIntegration(),
+ linkedErrorsIntegration(),
// From Browser
- new Dedupe(),
- new Breadcrumbs({
+ dedupeIntegration(),
+ breadcrumbsIntegration({
dom: false,
history: false,
xhr: false,
diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts
index 14c788a44307..d1d5e1db7ff5 100644
--- a/packages/nextjs/src/client/index.ts
+++ b/packages/nextjs/src/client/index.ts
@@ -18,7 +18,9 @@ export * from '@sentry/react';
export { nextRouterInstrumentation } from './routing/nextRoutingInstrumentation';
export { captureUnderscoreErrorException } from '../common/_error';
+/** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */
export const Integrations = {
+ // eslint-disable-next-line deprecation/deprecation
...OriginalIntegrations,
BrowserTracing,
};
diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts
index b15bb6c40bab..21d51a01ee56 100644
--- a/packages/nextjs/src/index.types.ts
+++ b/packages/nextjs/src/index.types.ts
@@ -20,10 +20,13 @@ export declare function init(
): void;
// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere.
+// eslint-disable-next-line deprecation/deprecation
export declare const Integrations: typeof clientSdk.Integrations &
typeof serverSdk.Integrations &
typeof edgeSdk.Integrations;
+export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
+
export declare const defaultIntegrations: Integration[];
export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts
index d0f0de959170..464b7db14dc7 100644
--- a/packages/nextjs/test/clientSdk.test.ts
+++ b/packages/nextjs/test/clientSdk.test.ts
@@ -6,7 +6,7 @@ import type { Integration } from '@sentry/types';
import { logger } from '@sentry/utils';
import { JSDOM } from 'jsdom';
-import { BrowserTracing, Integrations, init, nextRouterInstrumentation } from '../src/client';
+import { BrowserTracing, breadcrumbsIntegration, init, nextRouterInstrumentation } from '../src/client';
const reactInit = jest.spyOn(SentryReact, 'init');
const captureEvent = jest.spyOn(BaseClient.prototype, 'captureEvent');
@@ -108,12 +108,12 @@ describe('Client init()', () => {
type ModifiedInitOptionsIntegrationArray = { defaultIntegrations: Integration[]; integrations: Integration[] };
it('supports passing unrelated integrations through options', () => {
- init({ integrations: [new Integrations.Breadcrumbs({ console: false })] });
+ init({ integrations: [breadcrumbsIntegration({ console: false })] });
const reactInitOptions = reactInit.mock.calls[0][0] as ModifiedInitOptionsIntegrationArray;
- const breadcrumbsIntegration = findIntegrationByName(reactInitOptions.integrations, 'Breadcrumbs');
+ const installedBreadcrumbsIntegration = findIntegrationByName(reactInitOptions.integrations, 'Breadcrumbs');
- expect(breadcrumbsIntegration).toBeDefined();
+ expect(installedBreadcrumbsIntegration).toBeDefined();
});
describe('`BrowserTracing` integration', () => {
diff --git a/packages/node/package.json b/packages/node/package.json
index b096befa6b39..eb8b3056b149 100644
--- a/packages/node/package.json
+++ b/packages/node/package.json
@@ -20,8 +20,8 @@
"types": "build/types/index.d.ts",
"typesVersions": {
"<4.9": {
- "build/npm/types/index.d.ts": [
- "build/npm/types-ts3.8/index.d.ts"
+ "build/types/index.d.ts": [
+ "build/types-ts3.8/index.d.ts"
]
}
},
diff --git a/packages/node/src/cron/cron.ts b/packages/node/src/cron/cron.ts
index 88a3e9e58eb5..2540d82e736b 100644
--- a/packages/node/src/cron/cron.ts
+++ b/packages/node/src/cron/cron.ts
@@ -8,10 +8,17 @@ export type CronJobParams = {
start?: boolean | null;
context?: unknown;
runOnInit?: boolean | null;
- utcOffset?: number;
- timeZone?: string;
unrefTimeout?: boolean | null;
-};
+} & (
+ | {
+ timeZone?: string | null;
+ utcOffset?: never;
+ }
+ | {
+ timeZone?: never;
+ utcOffset?: number | null;
+ }
+);
export type CronJob = {
//
@@ -28,6 +35,17 @@ export type CronJobConstructor = {
timeZone?: CronJobParams['timeZone'],
context?: CronJobParams['context'],
runOnInit?: CronJobParams['runOnInit'],
+ utcOffset?: null,
+ unrefTimeout?: CronJobParams['unrefTimeout'],
+ ): CronJob;
+ new (
+ cronTime: CronJobParams['cronTime'],
+ onTick: CronJobParams['onTick'],
+ onComplete?: CronJobParams['onComplete'],
+ start?: CronJobParams['start'],
+ timeZone?: null,
+ context?: CronJobParams['context'],
+ runOnInit?: CronJobParams['runOnInit'],
utcOffset?: CronJobParams['utcOffset'],
unrefTimeout?: CronJobParams['unrefTimeout'],
): CronJob;
diff --git a/packages/node/src/integrations/local-variables/local-variables-sync.ts b/packages/node/src/integrations/local-variables/local-variables-sync.ts
index bfe255002975..93f3b61b10f2 100644
--- a/packages/node/src/integrations/local-variables/local-variables-sync.ts
+++ b/packages/node/src/integrations/local-variables/local-variables-sync.ts
@@ -1,5 +1,5 @@
/* eslint-disable max-lines */
-import { convertIntegrationFnToClass } from '@sentry/core';
+import { convertIntegrationFnToClass, getClient } from '@sentry/core';
import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types';
import { LRUMap, logger } from '@sentry/utils';
import type { Debugger, InspectorNotification, Runtime, Session } from 'inspector';
@@ -326,12 +326,11 @@ const localVariablesSyncIntegration = ((
return {
name: INTEGRATION_NAME,
- // TODO v8: Remove this
- setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
- setup(client: NodeClient) {
- const clientOptions = client.getOptions();
+ setupOnce() {
+ const client = getClient();
+ const clientOptions = client?.getOptions();
- if (session && clientOptions.includeLocalVariables) {
+ if (session && clientOptions?.includeLocalVariables) {
// Only setup this integration if the Node version is >= v18
// https://github.com/getsentry/sentry-javascript/issues/7697
const unsupportedNodeVersion = NODE_VERSION.major < 18;
diff --git a/packages/node/test/integrations/localvariables.test.ts b/packages/node/test/integrations/localvariables.test.ts
index b0fc6094e6a5..abc1d241f842 100644
--- a/packages/node/test/integrations/localvariables.test.ts
+++ b/packages/node/test/integrations/localvariables.test.ts
@@ -1,356 +1,12 @@
-import type { Debugger, InspectorNotification } from 'inspector';
-
-import { NodeClient, defaultStackParser } from '../../src';
import { createRateLimiter } from '../../src/integrations/local-variables/common';
-import type { FrameVariables } from '../../src/integrations/local-variables/common';
-import type { DebugSession } from '../../src/integrations/local-variables/local-variables-sync';
-import { LocalVariablesSync, createCallbackList } from '../../src/integrations/local-variables/local-variables-sync';
+import { createCallbackList } from '../../src/integrations/local-variables/local-variables-sync';
import { NODE_VERSION } from '../../src/nodeVersion';
-import { getDefaultNodeClientOptions } from '../../test/helper/node-client-options';
jest.setTimeout(20_000);
const describeIf = (condition: boolean) => (condition ? describe : describe.skip);
-interface ThrowOn {
- configureAndConnect?: boolean;
- getLocalVariables?: boolean;
-}
-
-class MockDebugSession implements DebugSession {
- private _onPause?: (message: InspectorNotification, callback: () => void) => void;
-
- constructor(private readonly _vars: Record>, private readonly _throwOn?: ThrowOn) {}
-
- public configureAndConnect(
- onPause: (message: InspectorNotification, callback: () => void) => void,
- _captureAll: boolean,
- ): void {
- if (this._throwOn?.configureAndConnect) {
- throw new Error('configureAndConnect should not be called');
- }
-
- this._onPause = onPause;
- }
-
- public setPauseOnExceptions(_: boolean): void {}
-
- public getLocalVariables(objectId: string, callback: (vars: Record) => void): void {
- if (this._throwOn?.getLocalVariables) {
- throw new Error('getLocalVariables should not be called');
- }
-
- callback(this._vars[objectId]);
- }
-
- public runPause(message: InspectorNotification): Promise {
- return new Promise(resolve => {
- this._onPause?.(message, resolve);
- });
- }
-}
-
-interface LocalVariablesPrivate {
- _getCachedFramesCount(): number;
- _getFirstCachedFrame(): FrameVariables[] | undefined;
-}
-
-const exceptionEvent = {
- method: 'Debugger.paused',
- params: {
- reason: 'exception',
- data: {
- description:
- 'Error: Some error\n' +
- ' at two (/dist/javascript/src/main.js:23:9)\n' +
- ' at one (/dist/javascript/src/main.js:19:3)\n' +
- ' at Timeout._onTimeout (/dist/javascript/src/main.js:40:5)\n' +
- ' at listOnTimeout (node:internal/timers:559:17)\n' +
- ' at process.processTimers (node:internal/timers:502:7)',
- },
- callFrames: [
- {
- callFrameId: '-6224981551105448869.1.0',
- functionName: 'two',
- location: { scriptId: '134', lineNumber: 22 },
- url: '',
- scopeChain: [
- {
- type: 'local',
- object: {
- type: 'object',
- className: 'Object',
- objectId: '-6224981551105448869.1.2',
- },
- name: 'two',
- },
- ],
- this: {
- type: 'object',
- className: 'global',
- },
- },
- {
- callFrameId: '-6224981551105448869.1.1',
- functionName: 'one',
- location: { scriptId: '134', lineNumber: 18 },
- url: '',
- scopeChain: [
- {
- type: 'local',
- object: {
- type: 'object',
- className: 'Object',
- objectId: '-6224981551105448869.1.6',
- },
- name: 'one',
- },
- ],
- this: {
- type: 'object',
- className: 'global',
- },
- },
- ],
- },
-};
-
-const exceptionEvent100Frames = {
- method: 'Debugger.paused',
- params: {
- reason: 'exception',
- data: {
- description:
- 'Error: Some error\n' +
- ' at two (/dist/javascript/src/main.js:23:9)\n' +
- ' at one (/dist/javascript/src/main.js:19:3)\n' +
- ' at Timeout._onTimeout (/dist/javascript/src/main.js:40:5)\n' +
- ' at listOnTimeout (node:internal/timers:559:17)\n' +
- ' at process.processTimers (node:internal/timers:502:7)',
- },
- callFrames: new Array(100).fill({
- callFrameId: '-6224981551105448869.1.0',
- functionName: 'two',
- location: { scriptId: '134', lineNumber: 22 },
- url: '',
- scopeChain: [
- {
- type: 'local',
- object: {
- type: 'object',
- className: 'Object',
- objectId: '-6224981551105448869.1.2',
- },
- name: 'two',
- },
- ],
- this: {
- type: 'object',
- className: 'global',
- },
- }),
- },
-};
-
describeIf(NODE_VERSION.major >= 18)('LocalVariables', () => {
- it('Adds local variables to stack frames', async () => {
- const session = new MockDebugSession({
- '-6224981551105448869.1.2': { name: 'tim' },
- '-6224981551105448869.1.6': { arr: [1, 2, 3] },
- });
- const localVariables = new LocalVariablesSync({}, session);
- const options = getDefaultNodeClientOptions({
- stackParser: defaultStackParser,
- includeLocalVariables: true,
- integrations: [],
- });
-
- const client = new NodeClient(options);
- client.addIntegration(localVariables);
-
- const eventProcessors = client['_eventProcessors'];
- const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables');
-
- expect(eventProcessor).toBeDefined();
-
- await session.runPause(exceptionEvent);
-
- expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(1);
-
- const frames = (localVariables as unknown as LocalVariablesPrivate)._getFirstCachedFrame();
-
- expect(frames).toBeDefined();
-
- const vars = frames as FrameVariables[];
-
- expect(vars).toEqual([
- { function: 'two', vars: { name: 'tim' } },
- { function: 'one', vars: { arr: [1, 2, 3] } },
- ]);
-
- const event = await eventProcessor!(
- {
- event_id: '9cbf882ade9a415986632ac4e16918eb',
- platform: 'node',
- timestamp: 1671113680.306,
- level: 'fatal',
- exception: {
- values: [
- {
- type: 'Error',
- value: 'Some error',
- stacktrace: {
- frames: [
- {
- function: 'process.processTimers',
- lineno: 502,
- colno: 7,
- in_app: false,
- },
- {
- function: 'listOnTimeout',
- lineno: 559,
- colno: 17,
- in_app: false,
- },
- {
- function: 'Timeout._onTimeout',
- lineno: 40,
- colno: 5,
- in_app: true,
- },
- {
- function: 'one',
- lineno: 19,
- colno: 3,
- in_app: true,
- },
- {
- function: 'two',
- lineno: 23,
- colno: 9,
- in_app: true,
- },
- ],
- },
- mechanism: { type: 'generic', handled: true },
- },
- ],
- },
- },
- {},
- );
-
- expect(event?.exception?.values?.[0].stacktrace?.frames?.[3]?.vars).toEqual({ arr: [1, 2, 3] });
- expect(event?.exception?.values?.[0].stacktrace?.frames?.[4]?.vars).toEqual({ name: 'tim' });
-
- expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(0);
- });
-
- it('Only considers the first 5 frames', async () => {
- const session = new MockDebugSession({});
- const localVariables = new LocalVariablesSync({}, session);
- const options = getDefaultNodeClientOptions({
- stackParser: defaultStackParser,
- includeLocalVariables: true,
- integrations: [],
- });
-
- const client = new NodeClient(options);
- client.addIntegration(localVariables);
-
- await session.runPause(exceptionEvent100Frames);
-
- expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(1);
-
- const frames = (localVariables as unknown as LocalVariablesPrivate)._getFirstCachedFrame();
-
- expect(frames).toBeDefined();
-
- const vars = frames as FrameVariables[];
-
- expect(vars.length).toEqual(5);
- });
-
- it('Should not lookup variables for non-exception reasons', async () => {
- const session = new MockDebugSession({}, { getLocalVariables: true });
- const localVariables = new LocalVariablesSync({}, session);
- const options = getDefaultNodeClientOptions({
- stackParser: defaultStackParser,
- includeLocalVariables: true,
- integrations: [],
- });
-
- const client = new NodeClient(options);
- client.addIntegration(localVariables);
-
- const nonExceptionEvent = {
- method: exceptionEvent.method,
- params: { ...exceptionEvent.params, reason: 'non-exception-reason' },
- };
-
- await session.runPause(nonExceptionEvent);
-
- expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(0);
- });
-
- it('Should not initialize when disabled', async () => {
- const session = new MockDebugSession({}, { configureAndConnect: true });
- const localVariables = new LocalVariablesSync({}, session);
- const options = getDefaultNodeClientOptions({
- stackParser: defaultStackParser,
- integrations: [],
- });
-
- const client = new NodeClient(options);
- client.addIntegration(localVariables);
-
- const eventProcessors = client['_eventProcessors'];
- const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables');
-
- expect(eventProcessor).toBeDefined();
- });
-
- it('Should not initialize when inspector not loaded', async () => {
- const localVariables = new LocalVariablesSync({}, undefined);
- const options = getDefaultNodeClientOptions({
- stackParser: defaultStackParser,
- integrations: [],
- });
-
- const client = new NodeClient(options);
- client.addIntegration(localVariables);
-
- const eventProcessors = client['_eventProcessors'];
- const eventProcessor = eventProcessors.find(processor => processor.id === 'LocalVariables');
-
- expect(eventProcessor).toBeDefined();
- });
-
- it('Should cache identical uncaught exception events', async () => {
- const session = new MockDebugSession({
- '-6224981551105448869.1.2': { name: 'tim' },
- '-6224981551105448869.1.6': { arr: [1, 2, 3] },
- });
- const localVariables = new LocalVariablesSync({}, session);
- const options = getDefaultNodeClientOptions({
- stackParser: defaultStackParser,
- includeLocalVariables: true,
- integrations: [],
- });
-
- const client = new NodeClient(options);
- client.addIntegration(localVariables);
-
- await session.runPause(exceptionEvent);
- await session.runPause(exceptionEvent);
- await session.runPause(exceptionEvent);
- await session.runPause(exceptionEvent);
- await session.runPause(exceptionEvent);
-
- expect((localVariables as unknown as LocalVariablesPrivate)._getCachedFramesCount()).toBe(1);
- });
-
describe('createCallbackList', () => {
it('Should call callbacks in reverse order', done => {
const log: number[] = [];
diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx
index de87e5bb6881..c2dc56687571 100644
--- a/packages/react/src/reactrouterv6.tsx
+++ b/packages/react/src/reactrouterv6.tsx
@@ -35,6 +35,7 @@ let _createRoutesFromChildren: CreateRoutesFromChildren;
let _matchRoutes: MatchRoutes;
let _customStartTransaction: (context: TransactionContext) => Transaction | undefined;
let _startTransactionOnLocationChange: boolean;
+let _stripBasename: boolean = false;
const SENTRY_TAGS = {
'routing.instrumentation': 'react-router-v6',
@@ -46,6 +47,7 @@ export function reactRouterV6Instrumentation(
useNavigationType: UseNavigationType,
createRoutesFromChildren: CreateRoutesFromChildren,
matchRoutes: MatchRoutes,
+ stripBasename?: boolean,
) {
return (
customStartTransaction: (context: TransactionContext) => Transaction | undefined,
@@ -70,12 +72,40 @@ export function reactRouterV6Instrumentation(
_useNavigationType = useNavigationType;
_matchRoutes = matchRoutes;
_createRoutesFromChildren = createRoutesFromChildren;
+ _stripBasename = stripBasename || false;
_customStartTransaction = customStartTransaction;
_startTransactionOnLocationChange = startTransactionOnLocationChange;
};
}
+/**
+ * Strip the basename from a pathname if exists.
+ *
+ * Vendored and modified from `react-router`
+ * https://github.com/remix-run/react-router/blob/462bb712156a3f739d6139a0f14810b76b002df6/packages/router/utils.ts#L1038
+ */
+function stripBasenameFromPathname(pathname: string, basename: string): string {
+ if (!basename || basename === '/') {
+ return pathname;
+ }
+
+ if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
+ return pathname;
+ }
+
+ // We want to leave trailing slash behavior in the user's control, so if they
+ // specify a basename with a trailing slash, we should support it
+ const startIndex = basename.endsWith('/') ? basename.length - 1 : basename.length;
+ const nextChar = pathname.charAt(startIndex);
+ if (nextChar && nextChar !== '/') {
+ // pathname does not start with basename/
+ return pathname;
+ }
+
+ return pathname.slice(startIndex) || '/';
+}
+
function getNormalizedName(
routes: RouteObject[],
location: Location,
@@ -83,7 +113,7 @@ function getNormalizedName(
basename: string = '',
): [string, TransactionSource] {
if (!routes || routes.length === 0) {
- return [location.pathname, 'url'];
+ return [_stripBasename ? stripBasenameFromPathname(location.pathname, basename) : location.pathname, 'url'];
}
let pathBuilder = '';
@@ -95,7 +125,7 @@ function getNormalizedName(
if (route) {
// Early return if index route
if (route.index) {
- return [branch.pathname, 'route'];
+ return [_stripBasename ? stripBasenameFromPathname(branch.pathname, basename) : branch.pathname, 'route'];
}
const path = route.path;
@@ -112,16 +142,16 @@ function getNormalizedName(
// We should not count wildcard operators in the url segments calculation
pathBuilder.slice(-2) !== '/*'
) {
- return [basename + newPath, 'route'];
+ return [(_stripBasename ? '' : basename) + newPath, 'route'];
}
- return [basename + pathBuilder, 'route'];
+ return [(_stripBasename ? '' : basename) + pathBuilder, 'route'];
}
}
}
}
}
- return [location.pathname, 'url'];
+ return [_stripBasename ? stripBasenameFromPathname(location.pathname, basename) : location.pathname, 'url'];
}
function updatePageloadTransaction(
diff --git a/packages/react/test/reactrouterv6.4.test.tsx b/packages/react/test/reactrouterv6.4.test.tsx
index d6b9c0c45b49..29fe612f7e97 100644
--- a/packages/react/test/reactrouterv6.4.test.tsx
+++ b/packages/react/test/reactrouterv6.4.test.tsx
@@ -26,6 +26,7 @@ describe('React Router v6.4', () => {
function createInstrumentation(_opts?: {
startTransactionOnPageLoad?: boolean;
startTransactionOnLocationChange?: boolean;
+ stripBasename?: boolean;
}): [jest.Mock, { mockUpdateName: jest.Mock; mockFinish: jest.Mock; mockSetAttribute: jest.Mock }] {
const options = {
matchPath: _opts ? matchPath : undefined,
@@ -46,6 +47,7 @@ describe('React Router v6.4', () => {
useNavigationType,
createRoutesFromChildren,
matchRoutes,
+ options.stripBasename,
)(mockStartTransaction, options.startTransactionOnPageLoad, options.startTransactionOnLocationChange);
return [mockStartTransaction, { mockUpdateName, mockFinish, mockSetAttribute }];
}
@@ -359,5 +361,93 @@ describe('React Router v6.4', () => {
metadata: { source: 'route' },
});
});
+
+ it('strips `basename` from transaction names of parameterized paths', () => {
+ const [mockStartTransaction] = createInstrumentation({
+ stripBasename: true,
+ });
+ const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction);
+
+ const router = sentryCreateBrowserRouter(
+ [
+ {
+ path: '/',
+ element: ,
+ },
+ {
+ path: ':orgId',
+ children: [
+ {
+ path: 'users',
+ children: [
+ {
+ path: ':userId',
+ element: User
,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ {
+ initialEntries: ['/admin'],
+ basename: '/admin',
+ },
+ );
+
+ // @ts-expect-error router is fine
+ render();
+
+ expect(mockStartTransaction).toHaveBeenCalledTimes(2);
+ expect(mockStartTransaction).toHaveBeenLastCalledWith({
+ name: '/:orgId/users/:userId',
+ op: 'navigation',
+ origin: 'auto.navigation.react.reactrouterv6',
+ tags: { 'routing.instrumentation': 'react-router-v6' },
+ metadata: { source: 'route' },
+ });
+ });
+
+ it('strips `basename` from transaction names of non-parameterized paths', () => {
+ const [mockStartTransaction] = createInstrumentation({
+ stripBasename: true,
+ });
+ const sentryCreateBrowserRouter = wrapCreateBrowserRouter(createMemoryRouter as CreateRouterFunction);
+
+ const router = sentryCreateBrowserRouter(
+ [
+ {
+ path: '/',
+ element: ,
+ },
+ {
+ path: 'about',
+ element: About
,
+ children: [
+ {
+ path: 'us',
+ element: Us
,
+ },
+ ],
+ },
+ ],
+ {
+ initialEntries: ['/app'],
+ basename: '/app',
+ },
+ );
+
+ // @ts-expect-error router is fine
+ render();
+
+ expect(mockStartTransaction).toHaveBeenCalledTimes(2);
+ expect(mockStartTransaction).toHaveBeenLastCalledWith({
+ name: '/about/us',
+ op: 'navigation',
+ origin: 'auto.navigation.react.reactrouterv6',
+ tags: { 'routing.instrumentation': 'react-router-v6' },
+ metadata: { source: 'route' },
+ });
+ });
});
});
diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts
index 1a7c7fb847e5..0abe77c7a20d 100644
--- a/packages/remix/src/index.types.ts
+++ b/packages/remix/src/index.types.ts
@@ -13,8 +13,11 @@ import type { RemixOptions } from './utils/remixOptions';
export declare function init(options: RemixOptions): void;
// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere.
+// eslint-disable-next-line deprecation/deprecation
export declare const Integrations: typeof clientSdk.Integrations & typeof serverSdk.Integrations;
+export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
+
export declare const defaultIntegrations: Integration[];
export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
diff --git a/packages/replay/src/integration.ts b/packages/replay/src/integration.ts
index 9f4d03e7f5fa..3434888f6574 100644
--- a/packages/replay/src/integration.ts
+++ b/packages/replay/src/integration.ts
@@ -30,7 +30,7 @@ let _initialized = false;
type InitialReplayPluginOptions = Omit &
Partial>;
-export const replayIntegration = ((options?: InitialReplayPluginOptions) => {
+export const replayIntegration = ((options?: ReplayConfiguration) => {
// eslint-disable-next-line deprecation/deprecation
return new Replay(options);
}) satisfies IntegrationFn;
diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts
index 3e57a08c2538..6a5d3e3883e9 100644
--- a/packages/sveltekit/src/index.types.ts
+++ b/packages/sveltekit/src/index.types.ts
@@ -37,8 +37,11 @@ export declare function handleErrorWithSentry any>(origLoad: T): T;
// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere.
+// eslint-disable-next-line deprecation/deprecation
export declare const Integrations: typeof clientSdk.Integrations & typeof serverSdk.Integrations;
+export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsIntegration;
+
export declare const defaultIntegrations: Integration[];
export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
diff --git a/packages/tracing-internal/src/browser/instrument.ts b/packages/tracing-internal/src/browser/instrument.ts
index ae4532259f94..2a4e7acaf3b1 100644
--- a/packages/tracing-internal/src/browser/instrument.ts
+++ b/packages/tracing-internal/src/browser/instrument.ts
@@ -73,6 +73,8 @@ interface Metric {
type InstrumentHandlerType = InstrumentHandlerTypeMetric | InstrumentHandlerTypePerformanceObserver;
+type StopListening = undefined | void | (() => void);
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type InstrumentHandlerCallback = (data: any) => void;
@@ -88,17 +90,29 @@ let _previousLcp: Metric | undefined;
/**
* Add a callback that will be triggered when a CLS metric is available.
* Returns a cleanup callback which can be called to remove the instrumentation handler.
+ *
+ * Pass `stopOnCallback = true` to stop listening for CLS when the cleanup callback is called.
+ * This will lead to the CLS being finalized and frozen.
*/
-export function addClsInstrumentationHandler(callback: (data: { metric: Metric }) => void): CleanupHandlerCallback {
- return addMetricObserver('cls', callback, instrumentCls, _previousCls);
+export function addClsInstrumentationHandler(
+ callback: (data: { metric: Metric }) => void,
+ stopOnCallback = false,
+): CleanupHandlerCallback {
+ return addMetricObserver('cls', callback, instrumentCls, _previousCls, stopOnCallback);
}
/**
* Add a callback that will be triggered when a LCP metric is available.
* Returns a cleanup callback which can be called to remove the instrumentation handler.
+ *
+ * Pass `stopOnCallback = true` to stop listening for LCP when the cleanup callback is called.
+ * This will lead to the LCP being finalized and frozen.
*/
-export function addLcpInstrumentationHandler(callback: (data: { metric: Metric }) => void): CleanupHandlerCallback {
- return addMetricObserver('lcp', callback, instrumentLcp, _previousLcp);
+export function addLcpInstrumentationHandler(
+ callback: (data: { metric: Metric }) => void,
+ stopOnCallback = false,
+): CleanupHandlerCallback {
+ return addMetricObserver('lcp', callback, instrumentLcp, _previousLcp, stopOnCallback);
}
/**
@@ -158,8 +172,8 @@ function triggerHandlers(type: InstrumentHandlerType, data: unknown): void {
}
}
-function instrumentCls(): void {
- onCLS(metric => {
+function instrumentCls(): StopListening {
+ return onCLS(metric => {
triggerHandlers('cls', {
metric,
});
@@ -168,7 +182,7 @@ function instrumentCls(): void {
}
function instrumentFid(): void {
- onFID(metric => {
+ return onFID(metric => {
triggerHandlers('fid', {
metric,
});
@@ -176,8 +190,8 @@ function instrumentFid(): void {
});
}
-function instrumentLcp(): void {
- onLCP(metric => {
+function instrumentLcp(): StopListening {
+ return onLCP(metric => {
triggerHandlers('lcp', {
metric,
});
@@ -188,13 +202,16 @@ function instrumentLcp(): void {
function addMetricObserver(
type: InstrumentHandlerTypeMetric,
callback: InstrumentHandlerCallback,
- instrumentFn: () => void,
+ instrumentFn: () => StopListening,
previousValue: Metric | undefined,
+ stopOnCallback = false,
): CleanupHandlerCallback {
addHandler(type, callback);
+ let stopListening: StopListening | undefined;
+
if (!instrumented[type]) {
- instrumentFn();
+ stopListening = instrumentFn();
instrumented[type] = true;
}
@@ -202,7 +219,7 @@ function addMetricObserver(
callback({ metric: previousValue });
}
- return getCleanupCallback(type, callback);
+ return getCleanupCallback(type, callback, stopOnCallback ? stopListening : undefined);
}
function instrumentPerformanceObserver(type: InstrumentHandlerTypePerformanceObserver): void {
@@ -228,8 +245,16 @@ function addHandler(type: InstrumentHandlerType, handler: InstrumentHandlerCallb
}
// Get a callback which can be called to remove the instrumentation handler
-function getCleanupCallback(type: InstrumentHandlerType, callback: InstrumentHandlerCallback): CleanupHandlerCallback {
+function getCleanupCallback(
+ type: InstrumentHandlerType,
+ callback: InstrumentHandlerCallback,
+ stopListening: StopListening,
+): CleanupHandlerCallback {
return () => {
+ if (stopListening) {
+ stopListening();
+ }
+
const typeHandlers = handlers[type];
if (!typeHandlers) {
diff --git a/packages/tracing-internal/src/browser/metrics/index.ts b/packages/tracing-internal/src/browser/metrics/index.ts
index d0eb73b945d2..4c9c25111e11 100644
--- a/packages/tracing-internal/src/browser/metrics/index.ts
+++ b/packages/tracing-internal/src/browser/metrics/index.ts
@@ -39,7 +39,8 @@ let _lcpEntry: LargestContentfulPaint | undefined;
let _clsEntry: LayoutShift | undefined;
/**
- * Start tracking web vitals
+ * Start tracking web vitals.
+ * The callback returned by this function can be used to stop tracking & ensure all measurements are final & captured.
*
* @returns A function that forces web vitals collection
*/
@@ -129,7 +130,7 @@ export function startTrackingInteractions(): void {
/** Starts tracking the Cumulative Layout Shift on the current page. */
function _trackCLS(): () => void {
return addClsInstrumentationHandler(({ metric }) => {
- const entry = metric.entries.pop();
+ const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
@@ -137,13 +138,13 @@ function _trackCLS(): () => void {
DEBUG_BUILD && logger.log('[Measurements] Adding CLS');
_measurements['cls'] = { value: metric.value, unit: '' };
_clsEntry = entry as LayoutShift;
- });
+ }, true);
}
/** Starts tracking the Largest Contentful Paint on the current page. */
function _trackLCP(): () => void {
return addLcpInstrumentationHandler(({ metric }) => {
- const entry = metric.entries.pop();
+ const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
@@ -151,13 +152,13 @@ function _trackLCP(): () => void {
DEBUG_BUILD && logger.log('[Measurements] Adding LCP');
_measurements['lcp'] = { value: metric.value, unit: 'millisecond' };
_lcpEntry = entry as LargestContentfulPaint;
- });
+ }, true);
}
/** Starts tracking the First Input Delay on the current page. */
function _trackFID(): () => void {
return addFidInstrumentationHandler(({ metric }) => {
- const entry = metric.entries.pop();
+ const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
diff --git a/packages/tracing-internal/src/browser/request.ts b/packages/tracing-internal/src/browser/request.ts
index 461171ec1e25..c84d0545054b 100644
--- a/packages/tracing-internal/src/browser/request.ts
+++ b/packages/tracing-internal/src/browser/request.ts
@@ -1,6 +1,5 @@
/* eslint-disable max-lines */
import {
- getActiveSpan,
getClient,
getCurrentScope,
getDynamicSamplingContextFromClient,
@@ -9,6 +8,7 @@ import {
hasTracingEnabled,
spanToJSON,
spanToTraceHeader,
+ startInactiveSpan,
} from '@sentry/core';
import type { HandlerDataXhr, SentryWrappedXMLHttpRequest, Span } from '@sentry/types';
import {
@@ -275,22 +275,19 @@ export function xhrCallback(
}
const scope = getCurrentScope();
- const parentSpan = getActiveSpan();
-
- const span =
- shouldCreateSpanResult && parentSpan
- ? // eslint-disable-next-line deprecation/deprecation
- parentSpan.startChild({
- data: {
- type: 'xhr',
- 'http.method': sentryXhrData.method,
- url: sentryXhrData.url,
- },
- description: `${sentryXhrData.method} ${sentryXhrData.url}`,
- op: 'http.client',
- origin: 'auto.http.browser',
- })
- : undefined;
+
+ const span = shouldCreateSpanResult
+ ? startInactiveSpan({
+ attributes: {
+ type: 'xhr',
+ 'http.method': sentryXhrData.method,
+ url: sentryXhrData.url,
+ },
+ name: `${sentryXhrData.method} ${sentryXhrData.url}`,
+ op: 'http.client',
+ origin: 'auto.http.browser',
+ })
+ : undefined;
if (span) {
xhr.__sentry_xhr_span_id__ = span.spanContext().spanId;
diff --git a/packages/tracing-internal/src/common/fetch.ts b/packages/tracing-internal/src/common/fetch.ts
index e0a8e2ed9fa3..c96778f8cd35 100644
--- a/packages/tracing-internal/src/common/fetch.ts
+++ b/packages/tracing-internal/src/common/fetch.ts
@@ -1,5 +1,4 @@
import {
- getActiveSpan,
getClient,
getCurrentScope,
getDynamicSamplingContextFromClient,
@@ -7,6 +6,7 @@ import {
getRootSpan,
hasTracingEnabled,
spanToTraceHeader,
+ startInactiveSpan,
} from '@sentry/core';
import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/types';
import {
@@ -76,24 +76,21 @@ export function instrumentFetchRequest(
const scope = getCurrentScope();
const client = getClient();
- const parentSpan = getActiveSpan();
const { method, url } = handlerData.fetchData;
- const span =
- shouldCreateSpanResult && parentSpan
- ? // eslint-disable-next-line deprecation/deprecation
- parentSpan.startChild({
- data: {
- url,
- type: 'fetch',
- 'http.method': method,
- },
- description: `${method} ${url}`,
- op: 'http.client',
- origin: spanOrigin,
- })
- : undefined;
+ const span = shouldCreateSpanResult
+ ? startInactiveSpan({
+ attributes: {
+ url,
+ type: 'fetch',
+ 'http.method': method,
+ },
+ name: `${method} ${url}`,
+ op: 'http.client',
+ origin: spanOrigin,
+ })
+ : undefined;
if (span) {
handlerData.fetchData.__span = span.spanContext().spanId;
diff --git a/packages/tracing-internal/test/browser/request.test.ts b/packages/tracing-internal/test/browser/request.test.ts
index 782c0890e156..426325072984 100644
--- a/packages/tracing-internal/test/browser/request.test.ts
+++ b/packages/tracing-internal/test/browser/request.test.ts
@@ -1,32 +1,14 @@
/* eslint-disable deprecation/deprecation */
-import * as sentryCore from '@sentry/core';
-import { Hub, makeMain, spanToJSON } from '@sentry/core';
-import type { HandlerDataFetch, HandlerDataXhr, SentryWrappedXMLHttpRequest } from '@sentry/types';
import * as utils from '@sentry/utils';
-import { SENTRY_XHR_DATA_KEY } from '@sentry/utils';
-import type { Transaction } from '../../../tracing/src';
-import { Span, addExtensionMethods, spanStatusfromHttpCode } from '../../../tracing/src';
-import { getDefaultBrowserClientOptions } from '../../../tracing/test/testutils';
-import {
- extractNetworkProtocol,
- instrumentOutgoingRequests,
- shouldAttachHeaders,
- xhrCallback,
-} from '../../src/browser/request';
-import { instrumentFetchRequest } from '../../src/common/fetch';
-import { TestClient } from '../utils/TestClient';
+import { extractNetworkProtocol, instrumentOutgoingRequests, shouldAttachHeaders } from '../../src/browser/request';
beforeAll(() => {
- addExtensionMethods();
// @ts-expect-error need to override global Request because it's not in the jest environment (even with an
// `@jest-environment jsdom` directive, for some reason)
global.Request = {};
});
-const hasTracingEnabled = jest.spyOn(sentryCore, 'hasTracingEnabled');
-const setRequestHeader = jest.fn();
-
describe('instrumentOutgoingRequests', () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -59,363 +41,6 @@ describe('instrumentOutgoingRequests', () => {
});
});
-describe('callbacks', () => {
- let hub: Hub;
- let transaction: Transaction;
- const alwaysCreateSpan = () => true;
- const alwaysAttachHeaders = () => true;
- const startTimestamp = 1356996072000;
- const endTimestamp = 1356996072000;
-
- beforeAll(() => {
- const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 });
- hub = new Hub(new TestClient(options));
- makeMain(hub);
- });
-
- beforeEach(() => {
- transaction = hub.startTransaction({ name: 'organizations/users/:userid', op: 'pageload' }) as Transaction;
- hub.getScope().setSpan(transaction);
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- describe('fetchCallback()', () => {
- let fetchHandlerData: HandlerDataFetch;
-
- const fetchSpan = {
- data: {
- 'http.method': 'GET',
- url: 'http://dogs.are.great/',
- type: 'fetch',
- },
- description: 'GET http://dogs.are.great/',
- op: 'http.client',
- parentSpanId: expect.any(String),
- spanId: expect.any(String),
- startTimestamp: expect.any(Number),
- traceId: expect.any(String),
- };
-
- beforeEach(() => {
- fetchHandlerData = {
- args: ['http://dogs.are.great/', {}],
- fetchData: { url: 'http://dogs.are.great/', method: 'GET' },
- startTimestamp,
- };
- });
-
- it.each([
- // each case is [shouldCreateSpanReturnValue, shouldAttachHeadersReturnValue, expectedSpan, expectedHeaderKeys]
- [true, true, expect.objectContaining(fetchSpan), ['sentry-trace', 'baggage']],
- [true, false, expect.objectContaining(fetchSpan), []],
- [false, true, undefined, ['sentry-trace', 'baggage']],
- [false, false, undefined, []],
- ])(
- 'span creation/header attachment interaction - shouldCreateSpan: %s, shouldAttachHeaders: %s',
- (shouldCreateSpanReturnValue, shouldAttachHeadersReturnValue, expectedSpan, expectedHeaderKeys) => {
- instrumentFetchRequest(
- fetchHandlerData,
- () => shouldCreateSpanReturnValue,
- () => shouldAttachHeadersReturnValue,
- {},
- );
-
- // spans[0] is the transaction itself
- const newSpan = transaction.spanRecorder?.spans[1] as Span;
- expect(newSpan).toEqual(expectedSpan);
-
- const headers = (fetchHandlerData.args[1].headers as Record) || {};
- expect(Object.keys(headers)).toEqual(expectedHeaderKeys);
- },
- );
-
- it('adds neither fetch request spans nor fetch request headers if tracing is disabled', () => {
- hasTracingEnabled.mockReturnValueOnce(false);
- const spans = {};
-
- instrumentFetchRequest(fetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- expect(spans).toEqual({});
-
- const headers = (fetchHandlerData.args[1].headers as Record) || {};
- expect(Object.keys(headers)).toEqual([]);
- });
-
- it('creates and finishes fetch span on active transaction', () => {
- const spans = {};
-
- // triggered by request being sent
- instrumentFetchRequest(fetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- const newSpan = transaction.spanRecorder?.spans[1] as Span;
-
- expect(newSpan).toBeDefined();
- expect(newSpan).toBeInstanceOf(Span);
- expect(newSpan.data).toEqual({
- 'http.method': 'GET',
- type: 'fetch',
- url: 'http://dogs.are.great/',
- });
- expect(newSpan.description).toBe('GET http://dogs.are.great/');
- expect(newSpan.op).toBe('http.client');
- const spanId = fetchHandlerData.fetchData?.__span;
- expect(spanId).toBeDefined();
-
- const postRequestFetchHandlerData = {
- ...fetchHandlerData,
- endTimestamp,
- };
-
- // triggered by response coming back
- instrumentFetchRequest(postRequestFetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- expect(spanToJSON(newSpan).timestamp).toBeDefined();
- });
-
- it('sets response status on finish', () => {
- const spans: Record = {};
-
- // triggered by request being sent
- instrumentFetchRequest(fetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- const newSpan = transaction.spanRecorder?.spans[1] as Span;
-
- expect(newSpan).toBeDefined();
-
- const postRequestFetchHandlerData = {
- ...fetchHandlerData,
- endTimestamp,
- response: { status: 404 } as Response,
- };
-
- // triggered by response coming back
- instrumentFetchRequest(postRequestFetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- expect(newSpan.status).toBe(spanStatusfromHttpCode(404));
- });
-
- it('ignores response with no associated span', () => {
- // the request might be missed somehow. E.g. if it was sent before tracing gets enabled.
-
- const postRequestFetchHandlerData = {
- ...fetchHandlerData,
- endTimestamp,
- response: { status: 404 } as Response,
- };
-
- // in that case, the response coming back will be ignored
- instrumentFetchRequest(postRequestFetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, {});
-
- const newSpan = transaction.spanRecorder?.spans[1];
-
- expect(newSpan).toBeUndefined();
- });
-
- it('uses active span to generate sentry-trace header', () => {
- const spans: Record = {};
- // triggered by request being sent
- instrumentFetchRequest(fetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- const activeSpan = transaction.spanRecorder?.spans[1] as Span;
-
- const postRequestFetchHandlerData = {
- ...fetchHandlerData,
- endTimestamp,
- response: { status: 200 } as Response,
- };
-
- // triggered by response coming back
- instrumentFetchRequest(postRequestFetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- const headers = (fetchHandlerData.args[1].headers as Record) || {};
- expect(headers['sentry-trace']).toEqual(`${activeSpan.traceId}-${activeSpan.spanId}-1`);
- });
-
- it('adds content-length to span data on finish', () => {
- const spans: Record = {};
-
- // triggered by request being sent
- instrumentFetchRequest(fetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- const newSpan = transaction.spanRecorder?.spans[1] as Span;
-
- expect(newSpan).toBeDefined();
-
- const postRequestFetchHandlerData = {
- ...fetchHandlerData,
- endTimestamp,
- response: { status: 404, headers: { get: () => 123 } },
- } as unknown as HandlerDataFetch;
-
- // triggered by response coming back
- instrumentFetchRequest(postRequestFetchHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- const finishedSpan = transaction.spanRecorder?.spans[1] as Span;
-
- expect(finishedSpan).toBeDefined();
- expect(finishedSpan).toBeInstanceOf(Span);
- expect(spanToJSON(finishedSpan).data).toEqual({
- 'http.response_content_length': 123,
- 'http.method': 'GET',
- 'http.response.status_code': 404,
- type: 'fetch',
- url: 'http://dogs.are.great/',
- 'sentry.op': 'http.client',
- 'sentry.origin': 'auto.http.browser',
- });
- expect(finishedSpan.op).toBe('http.client');
- });
- });
-
- describe('xhrCallback()', () => {
- let xhrHandlerData: HandlerDataXhr;
-
- const xhrSpan = {
- data: {
- 'http.method': 'GET',
- url: 'http://dogs.are.great/',
- type: 'xhr',
- },
- description: 'GET http://dogs.are.great/',
- op: 'http.client',
- parentSpanId: expect.any(String),
- spanId: expect.any(String),
- startTimestamp: expect.any(Number),
- traceId: expect.any(String),
- };
-
- beforeEach(() => {
- xhrHandlerData = {
- args: ['GET', 'http://dogs.are.great/'],
- xhr: {
- [SENTRY_XHR_DATA_KEY]: {
- method: 'GET',
- url: 'http://dogs.are.great/',
- status_code: 200,
- request_headers: {},
- },
- __sentry_xhr_span_id__: '1231201211212012',
- setRequestHeader,
- } as SentryWrappedXMLHttpRequest,
- startTimestamp,
- };
- });
-
- it.each([
- // each case is [shouldCreateSpanReturnValue, shouldAttachHeadersReturnValue, expectedSpan, expectedHeaderKeys]
- [true, true, expect.objectContaining(xhrSpan), ['sentry-trace', 'baggage']],
- [true, false, expect.objectContaining(xhrSpan), []],
- [false, true, undefined, ['sentry-trace', 'baggage']],
- [false, false, undefined, []],
- ])(
- 'span creation/header attachment interaction - shouldCreateSpan: %s, shouldAttachHeaders: %s',
- (shouldCreateSpanReturnValue, shouldAttachHeadersReturnValue, expectedSpan, expectedHeaderKeys) => {
- xhrCallback(
- xhrHandlerData,
- () => shouldCreateSpanReturnValue,
- () => shouldAttachHeadersReturnValue,
- {},
- );
-
- // spans[0] is the transaction itself
- const newSpan = transaction.spanRecorder?.spans[1] as Span;
- expect(newSpan).toEqual(expectedSpan);
-
- const headerKeys = setRequestHeader.mock.calls.map(header => header[0]);
- expect(headerKeys).toEqual(expectedHeaderKeys);
- },
- );
-
- it('adds neither xhr request spans nor xhr request headers if tracing is disabled', () => {
- hasTracingEnabled.mockReturnValueOnce(false);
- const spans = {};
-
- xhrCallback(xhrHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- expect(spans).toEqual({});
- expect(setRequestHeader).not.toHaveBeenCalled();
- });
-
- it('creates and finishes XHR span on active transaction', () => {
- const spans = {};
-
- // triggered by request being sent
- xhrCallback(xhrHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- const newSpan = transaction.spanRecorder?.spans[1] as Span;
-
- expect(newSpan).toBeInstanceOf(Span);
- expect(newSpan.data).toEqual({
- 'http.method': 'GET',
- type: 'xhr',
- url: 'http://dogs.are.great/',
- });
- expect(newSpan.description).toBe('GET http://dogs.are.great/');
- expect(newSpan.op).toBe('http.client');
- const spanId = xhrHandlerData.xhr?.__sentry_xhr_span_id__;
- expect(spanId).toBeDefined();
- expect(spanId).toEqual(newSpan?.spanId);
-
- const postRequestXHRHandlerData = {
- ...xhrHandlerData,
- endTimestamp,
- };
-
- // triggered by response coming back
- xhrCallback(postRequestXHRHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- expect(spanToJSON(newSpan).timestamp).toBeDefined();
- });
-
- it('sets response status on finish', () => {
- const spans = {};
-
- // triggered by request being sent
- xhrCallback(xhrHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- const newSpan = transaction.spanRecorder?.spans[1] as Span;
-
- expect(newSpan).toBeDefined();
-
- const postRequestXHRHandlerData = {
- ...xhrHandlerData,
- endTimestamp,
- };
- postRequestXHRHandlerData.xhr![SENTRY_XHR_DATA_KEY]!.status_code = 404;
-
- // triggered by response coming back
- xhrCallback(postRequestXHRHandlerData, alwaysCreateSpan, alwaysAttachHeaders, spans);
-
- expect(newSpan.status).toBe(spanStatusfromHttpCode(404));
- });
-
- it('ignores response with no associated span', () => {
- // the request might be missed somehow. E.g. if it was sent before tracing gets enabled.
-
- const postRequestXHRHandlerData: HandlerDataXhr = {
- ...{
- xhr: {
- [SENTRY_XHR_DATA_KEY]: xhrHandlerData.xhr?.[SENTRY_XHR_DATA_KEY],
- },
- },
- args: ['GET', 'http://dogs.are.great/'],
- startTimestamp,
- endTimestamp,
- };
-
- // in that case, the response coming back will be ignored
- xhrCallback(postRequestXHRHandlerData, alwaysCreateSpan, alwaysAttachHeaders, {});
-
- const newSpan = transaction.spanRecorder?.spans[1];
-
- expect(newSpan).toBeUndefined();
- });
- });
-});
-
interface ProtocolInfo {
name: string;
version: string;
diff --git a/yarn.lock b/yarn.lock
index c02f5da0ff3c..3825958296ed 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5292,40 +5292,40 @@
magic-string "0.27.0"
unplugin "1.0.1"
-"@sentry/cli-darwin@2.25.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.25.0.tgz#a46c84852fbecdbd16948548f4c58302cb5471b9"
- integrity sha512-OgBioypi9S+cooC4mPj/gYyvjw3oP9TH9ACgzobL0oP9gCpyF36iv044SWHLgeFUb45cPpVZ7f7WeSbufItzCQ==
-
-"@sentry/cli-linux-arm64@2.25.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.25.0.tgz#dcf61eac6adc6dcc5aee2eaebff2e901370abc75"
- integrity sha512-GqxP3s0qHBgch3WI1my5P/h4YeEtNEar+jOGTPg66Bt042rUEHIlYuhULriu3v5rLnmlTuQ5i+LGr4Kq5SFW0Q==
-
-"@sentry/cli-linux-arm@2.25.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.25.0.tgz#bbea30b44b6b37c7e58d5e47b393d1139cdfe5ef"
- integrity sha512-EZT//Dnajc03juqBTRUlU7x/1R1ODq5w6ZC9zO5tJfURxljUJ/kkAScHpfHiqzhPMNArK0gu7vYrOS4CTA7eBw==
-
-"@sentry/cli-linux-i686@2.25.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.25.0.tgz#b11c4bc6679c253dbd0e000a92198f3ccb41e53c"
- integrity sha512-w25QuABMK7FDjlOgpWgJOhQdVQguOhz81DPoeXNWiDLcTHFsYDXxT88exaUQxrLhMNcRrQnS0rDhwd5y0cYh0g==
-
-"@sentry/cli-linux-x64@2.25.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.25.0.tgz#c2c3be3db81ee08185557fa502ecfb5e516995ab"
- integrity sha512-7Pr3JZTPWqSeLiG67v/7uR9prpCfNAW2naf/SSZOMg2ZTXSgG+kgXf6/ADI3WP1LtF3GVhexGtJ5eyFVYxfLsQ==
-
-"@sentry/cli-win32-i686@2.25.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.25.0.tgz#968f5093e3a74401fe15233808aee68311d15496"
- integrity sha512-AuHBpFB2DZr19KE3g7qejaVmGb0d7E4ZN2cBKX1Vixb+KTi9/bEcRrWaQ2PpqLTVb2Wwglf/VlZgsxOjfhp7Ag==
-
-"@sentry/cli-win32-x64@2.25.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.25.0.tgz#9bb6b271642e1cc2b571acd395bece2683a3faed"
- integrity sha512-EFGg2L4Wm8YNRV/yAy0bmztc2jkkhy0SfaQtxrHW22IRqfl2jXyKcHHmcEjwoYfCvIW+c5I0ftHhjueMuuRcXw==
+"@sentry/cli-darwin@2.26.0":
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.26.0.tgz#6b44500dd549415c5f8b7228a3f4aef18a2e6766"
+ integrity sha512-SJ4ts9VELoLdOx1g034Tv2nGqhjutBYNAI3WMsjBaQG3tqNPJkQJKGrOqfpL6kTdO2tqQIAYeVw60yqWuHU3FA==
+
+"@sentry/cli-linux-arm64@2.26.0":
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.26.0.tgz#dfe28a7caeffd8bd68476b709ba5f5f50fa74244"
+ integrity sha512-tAsK5pWrLyU+zqoW0uwylfLB7udOV8FtU8xqcfMsYGxt44zviiuxzKeDnaUdHsZcvk03aTAyf1Dxqn0u32A0MA==
+
+"@sentry/cli-linux-arm@2.26.0":
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.26.0.tgz#cae4cf7db31a0cb4dba5243fce826abfa71dc404"
+ integrity sha512-qNqKLf3eGowhm+4gg47jGLfova5SLgC0wvWX181U+w94oVGp4onuSjbqpy7wbSA9nsfTXllMhEFI5jA4CMmZVw==
+
+"@sentry/cli-linux-i686@2.26.0":
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.26.0.tgz#29cd6617d2e764dec4cff377ede58762b87e9a66"
+ integrity sha512-+dSFR9rK6o6F0gBxoU0mrHw18qVgF1t27Y0jvdItMtDuCuduBuXTffmsbBwbPFWBgWuLPG+ojB1LuoBt5qVMng==
+
+"@sentry/cli-linux-x64@2.26.0":
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.26.0.tgz#a54dbcdd5c377ba97f7d3cc8115f25e68c3aded4"
+ integrity sha512-oY86ECWVQuk434K+enUVZnn28T8qxjJTpxN079xvz7SIWOxQ609tMva91Ywo0gExcu07AZ0pg71XFsEQ9WhZgA==
+
+"@sentry/cli-win32-i686@2.26.0":
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.26.0.tgz#42fdf7006e9e420cb0b3e6b70d9f44f6807906e9"
+ integrity sha512-vLju9NFl4venKEVpuFJpxaCwa2NdG6C9mhYNqxRvZAPrXWMdMd697qBDOMepAPT7CI8EWiyXUwMli0WjGXGMeQ==
+
+"@sentry/cli-win32-x64@2.26.0":
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.26.0.tgz#805cb184652d4a608128b70f9e19c1117f25c6f6"
+ integrity sha512-r3ZaxdHGC6OyJhOxO5ADzAitpGcgT/PkqQzOzKXBOebHj5jzwY27JWjdNhpT6sJZDII13HxqwISRedVWftZgRw==
"@sentry/cli@^1.74.4", "@sentry/cli@^1.77.1":
version "1.77.1"
@@ -5340,9 +5340,9 @@
which "^2.0.2"
"@sentry/cli@^2.17.0", "@sentry/cli@^2.21.2", "@sentry/cli@^2.23.0":
- version "2.25.0"
- resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.25.0.tgz#2b142b763d21def99a3473f0cd3169f6b04f6235"
- integrity sha512-N7k3NdiiEyQkQ43hRDAVqMf+Lg3GTWevO+ndg4yZ8Zv+J1jEVD6ZbqNnshSwWOx9qzcWQ+V/8ZgjmNuHbcNRxg==
+ version "2.26.0"
+ resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.26.0.tgz#94cee60c89f457318540f74f8f1158357c2dd706"
+ integrity sha512-WRrY9nkjLLUvyo+l8KE0x0Q+0NtCd2U8HYJzh3kyJHyyfKWiSH7ZhExcsb2MoSIjlzbKjjrIJzxhklZABkidDw==
dependencies:
https-proxy-agent "^5.0.0"
node-fetch "^2.6.7"
@@ -5350,13 +5350,13 @@
proxy-from-env "^1.1.0"
which "^2.0.2"
optionalDependencies:
- "@sentry/cli-darwin" "2.25.0"
- "@sentry/cli-linux-arm" "2.25.0"
- "@sentry/cli-linux-arm64" "2.25.0"
- "@sentry/cli-linux-i686" "2.25.0"
- "@sentry/cli-linux-x64" "2.25.0"
- "@sentry/cli-win32-i686" "2.25.0"
- "@sentry/cli-win32-x64" "2.25.0"
+ "@sentry/cli-darwin" "2.26.0"
+ "@sentry/cli-linux-arm" "2.26.0"
+ "@sentry/cli-linux-arm64" "2.26.0"
+ "@sentry/cli-linux-i686" "2.26.0"
+ "@sentry/cli-linux-x64" "2.26.0"
+ "@sentry/cli-win32-i686" "2.26.0"
+ "@sentry/cli-win32-x64" "2.26.0"
"@sentry/vite-plugin@^0.6.1":
version "0.6.1"
@@ -5739,6 +5739,13 @@
dependencies:
bson "*"
+"@types/bson@1.x || 4.0.x":
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.5.tgz#9e0e1d1a6f8866483f96868a9b33bc804926b1fc"
+ integrity sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==
+ dependencies:
+ "@types/node" "*"
+
"@types/chai-as-promised@^7.1.2":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz#779166b90fda611963a3adbfd00b339d03b747bd"
@@ -6268,6 +6275,11 @@
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03"
integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w==
+"@types/luxon@~3.3.0":
+ version "3.3.8"
+ resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.3.8.tgz#84dbf2d020a9209a272058725e168f21d331a67e"
+ integrity sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==
+
"@types/md5@2.1.33":
version "2.1.33"
resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.1.33.tgz#8c8dba30df4ad0e92296424f08c4898dd808e8df"
@@ -6309,7 +6321,7 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==
-"@types/mongodb@^3.6.20":
+"@types/mongodb@^3.5.27", "@types/mongodb@^3.6.20":
version "3.6.20"
resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.20.tgz#b7c5c580644f6364002b649af1c06c3c0454e1d2"
integrity sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==
@@ -9535,6 +9547,11 @@ blank-object@^1.0.1:
resolved "https://registry.yarnpkg.com/blank-object/-/blank-object-1.0.2.tgz#f990793fbe9a8c8dd013fb3219420bec81d5f4b9"
integrity sha1-+ZB5P76ajI3QE/syGUIL7IHV9Lk=
+bluebird@3.5.1:
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
+ integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==
+
bluebird@^3.4.6, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@@ -11947,6 +11964,14 @@ critters@0.0.12:
postcss "^8.3.7"
pretty-bytes "^5.3.0"
+cron@^3.1.6:
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/cron/-/cron-3.1.6.tgz#e7e1798a468e017c8d31459ecd7c2d088f97346c"
+ integrity sha512-cvFiQCeVzsA+QPM6fhjBtlKGij7tLLISnTSvFxVdnFGLdz+ZdXN37kNe0i2gefmdD17XuZA6n2uPVwzl4FxW/w==
+ dependencies:
+ "@types/luxon" "~3.3.0"
+ luxon "~3.4.0"
+
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -19616,6 +19641,11 @@ jws@^4.0.0:
jwa "^2.0.0"
safe-buffer "^5.0.1"
+kareem@2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.2.tgz#78c4508894985b8d38a0dc15e1a8e11078f2ca93"
+ integrity sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==
+
karma-browserstack-launcher@^1.5.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz#2f6000647073e77ae296653b8830b279669766ef"
@@ -20696,6 +20726,11 @@ lunr@^2.3.8:
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
+luxon@~3.4.0:
+ version "3.4.4"
+ resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af"
+ integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==
+
lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
@@ -22120,6 +22155,19 @@ mongodb-memory-server-global@^7.6.3:
mongodb-memory-server-core "7.6.3"
tslib "^2.3.0"
+mongodb@3.7.4:
+ version "3.7.4"
+ resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.7.4.tgz#119530d826361c3e12ac409b769796d6977037a4"
+ integrity sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==
+ dependencies:
+ bl "^2.2.1"
+ bson "^1.1.4"
+ denque "^1.4.1"
+ optional-require "^1.1.8"
+ safe-buffer "^5.1.2"
+ optionalDependencies:
+ saslprep "^1.0.0"
+
mongodb@^3.7.3:
version "3.7.3"
resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.7.3.tgz#b7949cfd0adc4cc7d32d3f2034214d4475f175a5"
@@ -22133,6 +22181,31 @@ mongodb@^3.7.3:
optionalDependencies:
saslprep "^1.0.0"
+mongoose-legacy-pluralize@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4"
+ integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==
+
+mongoose@^5.13.22:
+ version "5.13.22"
+ resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.13.22.tgz#f9a6493ba5f45b7a3d5f9fce58ca9c71aedb8157"
+ integrity sha512-p51k/c4X/MfqeQ3I1ranlDiggLzNumZrTDD9CeezHwZxt2/btf+YZD7MCe07RAY2NgFYVMayq6jMamw02Jmf9w==
+ dependencies:
+ "@types/bson" "1.x || 4.0.x"
+ "@types/mongodb" "^3.5.27"
+ bson "^1.1.4"
+ kareem "2.3.2"
+ mongodb "3.7.4"
+ mongoose-legacy-pluralize "1.0.2"
+ mpath "0.8.4"
+ mquery "3.2.5"
+ ms "2.1.2"
+ optional-require "1.0.x"
+ regexp-clone "1.0.0"
+ safe-buffer "5.2.1"
+ sift "13.5.2"
+ sliced "1.0.1"
+
morgan@^1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
@@ -22161,6 +22234,22 @@ move-concurrently@^1.0.1:
rimraf "^2.5.4"
run-queue "^1.0.3"
+mpath@0.8.4:
+ version "0.8.4"
+ resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.4.tgz#6b566d9581621d9e931dd3b142ed3618e7599313"
+ integrity sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==
+
+mquery@3.2.5:
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.5.tgz#8f2305632e4bb197f68f60c0cffa21aaf4060c51"
+ integrity sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==
+ dependencies:
+ bluebird "3.5.1"
+ debug "3.1.0"
+ regexp-clone "^1.0.0"
+ safe-buffer "5.1.2"
+ sliced "1.0.1"
+
mri@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a"
@@ -23488,6 +23577,11 @@ opn@^5.5.0:
dependencies:
is-wsl "^1.1.0"
+optional-require@1.0.x:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/optional-require/-/optional-require-1.0.3.tgz#275b8e9df1dc6a17ad155369c2422a440f89cb07"
+ integrity sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==
+
optional-require@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/optional-require/-/optional-require-1.1.8.tgz#16364d76261b75d964c482b2406cb824d8ec44b7"
@@ -26650,6 +26744,11 @@ regex-parser@^2.2.11:
resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58"
integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==
+regexp-clone@1.0.0, regexp-clone@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63"
+ integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==
+
regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
@@ -28042,6 +28141,11 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
+sift@13.5.2:
+ version "13.5.2"
+ resolved "https://registry.yarnpkg.com/sift/-/sift-13.5.2.tgz#24a715e13c617b086166cd04917d204a591c9da6"
+ integrity sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==
+
siginfo@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
@@ -28212,6 +28316,11 @@ slice-ansi@^5.0.0:
ansi-styles "^6.0.0"
is-fullwidth-code-point "^4.0.0"
+sliced@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
+ integrity sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==
+
smart-buffer@^4.1.0, smart-buffer@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"