diff --git a/packages/browser-integration-tests/suites/integrations/exportFromBrowser/init.js b/packages/browser-integration-tests/suites/integrations/exportFromBrowser/init.js new file mode 100644 index 000000000000..9d28a7b5a0f8 --- /dev/null +++ b/packages/browser-integration-tests/suites/integrations/exportFromBrowser/init.js @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [new Sentry.Integrations.ExtraErrorData()], +}); diff --git a/packages/browser-integration-tests/suites/integrations/exportFromBrowser/subject.js b/packages/browser-integration-tests/suites/integrations/exportFromBrowser/subject.js new file mode 100644 index 000000000000..10cf0cddd352 --- /dev/null +++ b/packages/browser-integration-tests/suites/integrations/exportFromBrowser/subject.js @@ -0,0 +1,5 @@ +const error = new TypeError('foo'); +error.baz = 42; +error.foo = 'bar'; + +Sentry.captureException(error); diff --git a/packages/browser-integration-tests/suites/integrations/exportFromBrowser/template.html b/packages/browser-integration-tests/suites/integrations/exportFromBrowser/template.html new file mode 100644 index 000000000000..57334d4ad2f1 --- /dev/null +++ b/packages/browser-integration-tests/suites/integrations/exportFromBrowser/template.html @@ -0,0 +1,7 @@ + + +
+ + + + diff --git a/packages/browser-integration-tests/suites/integrations/exportFromBrowser/test.ts b/packages/browser-integration-tests/suites/integrations/exportFromBrowser/test.ts new file mode 100644 index 000000000000..5630f1b6381f --- /dev/null +++ b/packages/browser-integration-tests/suites/integrations/exportFromBrowser/test.ts @@ -0,0 +1,45 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../utils/helpers'; + +sentryTest('allows to use pluggable integrations from @sentry/browser', async ({ getLocalTestUrl, page }) => { + const bundle = process.env.PW_BUNDLE; + + // Only run this for import-based tests, not CDN bundles/loader + if (bundle && bundle.startsWith('bundle_')) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const req = await waitForErrorRequestOnUrl(page, url); + + const eventData = envelopeRequestParser(req); + + expect(eventData).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + TypeError: { + baz: 42, + foo: 'bar', + }, + }), + exception: { + values: [ + { + type: 'TypeError', + value: 'foo', + mechanism: { + type: 'generic', + handled: true, + }, + stacktrace: { + frames: expect.any(Array), + }, + }, + ], + }, + }), + ); +}); diff --git a/packages/browser-integration-tests/utils/generatePlugin.ts b/packages/browser-integration-tests/utils/generatePlugin.ts index 45fe1cfb9a94..f74b6ec1956e 100644 --- a/packages/browser-integration-tests/utils/generatePlugin.ts +++ b/packages/browser-integration-tests/utils/generatePlugin.ts @@ -158,8 +158,14 @@ class SentryScenarioGenerationPlugin { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access parser.hooks.import.tap( this._name, - (statement: { specifiers: [{ imported: { name: string } }] }, source: string) => { - if (source === '@sentry/integrations') { + (statement: { specifiers: [{ imported?: { name: string }; name?: string }] }, source: string) => { + // We only want to handle the integrations import if it doesn't come from the @sentry/browser re-export + // In that case, we just want to leave it alone + if ( + source === '@sentry/integrations' && + statement.specifiers[0].name !== 'PluggableIntegrations' && + statement.specifiers[0].imported + ) { this.requiredIntegrations.push(statement.specifiers[0].imported.name.toLowerCase()); } else if (source === '@sentry/wasm') { this.requiresWASMIntegration = true; diff --git a/packages/browser/package.json b/packages/browser/package.json index 916057687691..fdbf7c3baf30 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -25,6 +25,7 @@ "dependencies": { "@sentry-internal/tracing": "7.64.0", "@sentry/core": "7.64.0", + "@sentry/integrations": "7.64.0", "@sentry/replay": "7.64.0", "@sentry/types": "7.64.0", "@sentry/utils": "7.64.0", diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 4ca6c7352261..2aeece5008fd 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -1,6 +1,7 @@ export * from './exports'; import { Integrations as CoreIntegrations } from '@sentry/core'; +import * as PluggableIntegrations from '@sentry/integrations'; import { WINDOW } from './helpers'; import * as BrowserIntegrations from './integrations'; @@ -16,6 +17,7 @@ const INTEGRATIONS = { ...windowIntegrations, ...CoreIntegrations, ...BrowserIntegrations, + ...PluggableIntegrations, }; export { INTEGRATIONS as Integrations }; diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 1872845f693f..4b6b6e6fd147 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -29,7 +29,6 @@ "tslib": "^2.4.1 || ^1.9.3" }, "devDependencies": { - "@sentry/browser": "7.64.0", "chai": "^4.1.2" }, "scripts": { diff --git a/packages/integrations/test/offline.test.ts b/packages/integrations/test/offline.test.ts index 1dac96bc1e53..6178f620db36 100644 --- a/packages/integrations/test/offline.test.ts +++ b/packages/integrations/test/offline.test.ts @@ -1,10 +1,14 @@ /* eslint-disable deprecation/deprecation */ -import { WINDOW } from '@sentry/browser'; import type { Event, EventProcessor, Hub, Integration, IntegrationClass } from '@sentry/types'; +import { GLOBAL_OBJ } from '@sentry/utils'; import type { Item } from '../src/offline'; import { Offline } from '../src/offline'; +// exporting a separate copy of `WINDOW` rather than exporting the one from `@sentry/browser` +// to avoid a circular dependency (as `@sentry/browser` imports `@sentry/integrations`) +const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; + // mock localforage methods jest.mock('localforage', () => ({ createInstance(_options: { name: string }): any { diff --git a/packages/node-integration-tests/suites/integrations/exportFromNode/scenario.ts b/packages/node-integration-tests/suites/integrations/exportFromNode/scenario.ts new file mode 100644 index 000000000000..d5ea2448f9b2 --- /dev/null +++ b/packages/node-integration-tests/suites/integrations/exportFromNode/scenario.ts @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [new Sentry.Integrations.ExtraErrorData({})], +}); + +const error = new TypeError('foo') as Error & { baz: number; foo: string }; +error.baz = 42; +error.foo = 'bar'; + +Sentry.captureException(error); diff --git a/packages/node-integration-tests/suites/integrations/exportFromNode/test.ts b/packages/node-integration-tests/suites/integrations/exportFromNode/test.ts new file mode 100644 index 000000000000..441be33c4bd3 --- /dev/null +++ b/packages/node-integration-tests/suites/integrations/exportFromNode/test.ts @@ -0,0 +1,30 @@ +import { assertSentryEvent, TestEnv } from '../../../utils'; + +test('allows to use pluggable integrations from @sentry/node export', async () => { + const env = await TestEnv.init(__dirname); + const event = await env.getEnvelopeRequest(); + + assertSentryEvent(event[2], { + contexts: expect.objectContaining({ + TypeError: { + baz: 42, + foo: 'bar', + }, + }), + exception: { + values: [ + { + type: 'TypeError', + value: 'foo', + mechanism: { + type: 'generic', + handled: true, + }, + stacktrace: { + frames: expect.any(Array), + }, + }, + ], + }, + }); +}); diff --git a/packages/node/package.json b/packages/node/package.json index 1e756dc5088b..8e9620bd43f4 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -25,6 +25,7 @@ "dependencies": { "@sentry-internal/tracing": "7.64.0", "@sentry/core": "7.64.0", + "@sentry/integrations": "7.64.0", "@sentry/types": "7.64.0", "@sentry/utils": "7.64.0", "cookie": "^0.4.1", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 1c172bc89618..d9ab8ba5f42b 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -70,6 +70,7 @@ export { deepReadDirSync } from './utils'; export { getModuleFromFilename } from './module'; import { Integrations as CoreIntegrations } from '@sentry/core'; +import * as PluggableIntegrations from '@sentry/integrations'; import * as Handlers from './handlers'; import * as NodeIntegrations from './integrations'; @@ -79,6 +80,7 @@ const INTEGRATIONS = { ...CoreIntegrations, ...NodeIntegrations, ...TracingIntegrations, + ...PluggableIntegrations, }; export { INTEGRATIONS as Integrations, Handlers };