diff --git a/dev-packages/browser-integration-tests/suites/public-api/debug/test.ts b/dev-packages/browser-integration-tests/suites/public-api/debug/test.ts index 8e4e953d69d9..6738215c0d5a 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/debug/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/debug/test.ts @@ -34,6 +34,7 @@ sentryTest('logs debug messages correctly', async ({ getLocalTestUrl, page }) => 'Sentry Logger [log]: Integration installed: Dedupe', 'Sentry Logger [log]: Integration installed: HttpContext', 'Sentry Logger [warn]: Discarded session because of missing or non-string release', + 'Sentry Logger [log]: Integration installed: BrowserSession', 'test log', ] : ['[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.', 'test log'], diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts index 85353801980d..e581a8eacd57 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts @@ -34,7 +34,7 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT event_id: expect.stringMatching(/\w{32}/), environment: 'production', sdk: { - integrations: [ + integrations: expect.arrayContaining([ 'InboundFilters', 'FunctionToString', 'BrowserApiErrors', @@ -43,8 +43,9 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT 'LinkedErrors', 'Dedupe', 'HttpContext', + 'BrowserSession', 'Replay', - ], + ]), version: SDK_VERSION, name: 'sentry.javascript.browser', }, @@ -71,7 +72,7 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT event_id: expect.stringMatching(/\w{32}/), environment: 'production', sdk: { - integrations: [ + integrations: expect.arrayContaining([ 'InboundFilters', 'FunctionToString', 'BrowserApiErrors', @@ -80,8 +81,9 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT 'LinkedErrors', 'Dedupe', 'HttpContext', + 'BrowserSession', 'Replay', - ], + ]), version: SDK_VERSION, name: 'sentry.javascript.browser', }, diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts index 82bbe104ab98..3a10ea72e18c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts @@ -34,7 +34,7 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g event_id: expect.stringMatching(/\w{32}/), environment: 'production', sdk: { - integrations: [ + integrations: expect.arrayContaining([ 'InboundFilters', 'FunctionToString', 'BrowserApiErrors', @@ -43,8 +43,9 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g 'LinkedErrors', 'Dedupe', 'HttpContext', + 'BrowserSession', 'Replay', - ], + ]), version: SDK_VERSION, name: 'sentry.javascript.browser', }, @@ -71,7 +72,7 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g event_id: expect.stringMatching(/\w{32}/), environment: 'production', sdk: { - integrations: [ + integrations: expect.arrayContaining([ 'InboundFilters', 'FunctionToString', 'BrowserApiErrors', @@ -80,8 +81,9 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g 'LinkedErrors', 'Dedupe', 'HttpContext', + 'BrowserSession', 'Replay', - ], + ]), version: SDK_VERSION, name: 'sentry.javascript.browser', }, diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts index 63152d54870b..52dbbca1c086 100644 --- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts +++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts @@ -16,7 +16,7 @@ const DEFAULT_REPLAY_EVENT = { event_id: expect.stringMatching(/\w{32}/), environment: 'production', sdk: { - integrations: [ + integrations: expect.arrayContaining([ 'InboundFilters', 'FunctionToString', 'BrowserApiErrors', @@ -25,8 +25,9 @@ const DEFAULT_REPLAY_EVENT = { 'LinkedErrors', 'Dedupe', 'HttpContext', + 'BrowserSession', 'Replay', - ], + ]), version: SDK_VERSION, name: 'sentry.javascript.browser', }, diff --git a/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.client.test.ts b/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.client.test.ts index 4cbf4bf36604..e1e13d231fef 100644 --- a/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.client.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-4/tests/errors.client.test.ts @@ -59,6 +59,7 @@ test.describe('client-side errors', () => { 'LinkedErrors', 'Dedupe', 'HttpContext', + 'BrowserSession', 'BrowserTracing', ]), name: 'sentry.javascript.astro', diff --git a/packages/angular/src/sdk.ts b/packages/angular/src/sdk.ts index f5bf79be2f59..5a9de0bf9fb4 100755 --- a/packages/angular/src/sdk.ts +++ b/packages/angular/src/sdk.ts @@ -2,6 +2,7 @@ import { VERSION } from '@angular/core'; import type { BrowserOptions } from '@sentry/browser'; import { breadcrumbsIntegration, + browserSessionIntegration, globalHandlersIntegration, httpContextIntegration, init as browserInit, @@ -22,7 +23,7 @@ import { IS_DEBUG_BUILD } from './flags'; /** * Get the default integrations for the Angular SDK. */ -export function getDefaultIntegrations(): Integration[] { +export function getDefaultIntegrations(options: BrowserOptions = {}): Integration[] { // Don't include the BrowserApiErrors integration as it interferes with the Angular SDK's `ErrorHandler`: // BrowserApiErrors would catch certain errors before they reach the `ErrorHandler` and // thus provide a lower fidelity error than what `SentryErrorHandler` @@ -31,7 +32,7 @@ export function getDefaultIntegrations(): Integration[] { // see: // - https://github.com/getsentry/sentry-javascript/issues/5417#issuecomment-1453407097 // - https://github.com/getsentry/sentry-javascript/issues/2744 - return [ + const integrations = [ inboundFiltersIntegration(), functionToStringIntegration(), breadcrumbsIntegration(), @@ -40,6 +41,12 @@ export function getDefaultIntegrations(): Integration[] { dedupeIntegration(), httpContextIntegration(), ]; + + if (options.autoSessionTracking !== false) { + integrations.push(browserSessionIntegration()); + } + + return integrations; } /** diff --git a/packages/angular/test/sdk.test.ts b/packages/angular/test/sdk.test.ts index d3d41df6f5bf..25beb721a448 100644 --- a/packages/angular/test/sdk.test.ts +++ b/packages/angular/test/sdk.test.ts @@ -15,7 +15,7 @@ describe('init', () => { }); it('does not include the BrowserApiErrors integration', () => { - const browserDefaultIntegrationsWithoutBrowserApiErrors = SentryBrowser.getDefaultIntegrations() + const browserDefaultIntegrationsWithoutBrowserApiErrors = SentryBrowser.getDefaultIntegrations({}) .filter(i => i.name !== 'BrowserApiErrors') .map(i => i.name) .sort(); diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 827d2a90c993..9440333caec4 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -77,3 +77,4 @@ export type { Span } from '@sentry/core'; export { makeBrowserOfflineTransport } from './transports/offline'; export { browserProfilingIntegration } from './profiling/integration'; export { spotlightBrowserIntegration } from './integrations/spotlight'; +export { browserSessionIntegration } from './integrations/browsersession'; diff --git a/packages/browser/src/integrations/browsersession.ts b/packages/browser/src/integrations/browsersession.ts new file mode 100644 index 000000000000..7863351f182f --- /dev/null +++ b/packages/browser/src/integrations/browsersession.ts @@ -0,0 +1,39 @@ +import { addHistoryInstrumentationHandler } from '@sentry-internal/browser-utils'; +import { captureSession, defineIntegration, logger, startSession } from '@sentry/core'; +import { DEBUG_BUILD } from '../debug-build'; +import { WINDOW } from '../helpers'; + +/** + * When added, automatically creates sessions which allow you to track adoption and crashes (crash free rate) in your Releases in Sentry. + * More information: https://docs.sentry.io/product/releases/health/ + * + * Note: In order for session tracking to work, you need to set up Releases: https://docs.sentry.io/product/releases/ + */ +export const browserSessionIntegration = defineIntegration(() => { + return { + name: 'BrowserSession', + setupOnce() { + if (typeof WINDOW.document === 'undefined') { + DEBUG_BUILD && + logger.warn('Using the `browserSessionIntegration` in non-browser environments is not supported.'); + return; + } + + // The session duration for browser sessions does not track a meaningful + // concept that can be used as a metric. + // Automatically captured sessions are akin to page views, and thus we + // discard their duration. + startSession({ ignoreDuration: true }); + captureSession(); + + // We want to create a session for every navigation as well + addHistoryInstrumentationHandler(({ from, to }) => { + // Don't create an additional session for the initial route or if the location did not change + if (from !== undefined && from !== to) { + startSession({ ignoreDuration: true }); + captureSession(); + } + }); + }, + }; +}); diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 70d21bfd3501..a033a4c848d2 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -1,6 +1,4 @@ -import { addHistoryInstrumentationHandler } from '@sentry-internal/browser-utils'; import { - captureSession, consoleSandbox, dedupeIntegration, functionToStringIntegration, @@ -13,7 +11,6 @@ import { lastEventId, logger, stackParserFromStackParserOptions, - startSession, supportsFetch, } from '@sentry/core'; import type { Client, DsnLike, Integration, Options, UserFeedback } from '@sentry/core'; @@ -23,6 +20,7 @@ import { DEBUG_BUILD } from './debug-build'; import { WINDOW } from './helpers'; import { breadcrumbsIntegration } from './integrations/breadcrumbs'; import { browserApiErrorsIntegration } from './integrations/browserapierrors'; +import { browserSessionIntegration } from './integrations/browsersession'; import { globalHandlersIntegration } from './integrations/globalhandlers'; import { httpContextIntegration } from './integrations/httpcontext'; import { linkedErrorsIntegration } from './integrations/linkederrors'; @@ -30,12 +28,12 @@ import { defaultStackParser } from './stack-parsers'; import { makeFetchTransport } from './transports/fetch'; /** Get the default integrations for the browser SDK. */ -export function getDefaultIntegrations(_options: Options): Integration[] { +export function getDefaultIntegrations(options: Options): Integration[] { /** * Note: Please make sure this stays in sync with Angular SDK, which re-exports * `getDefaultIntegrations` but with an adjusted set of integrations. */ - return [ + const integrations = [ inboundFiltersIntegration(), functionToStringIntegration(), browserApiErrorsIntegration(), @@ -45,6 +43,12 @@ export function getDefaultIntegrations(_options: Options): Integration[] { dedupeIntegration(), httpContextIntegration(), ]; + + if (options.autoSessionTracking !== false) { + integrations.push(browserSessionIntegration()); + } + + return integrations; } function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOptions { @@ -187,13 +191,7 @@ export function init(browserOptions: BrowserOptions = {}): Client | undefined { transport: options.transport || makeFetchTransport, }; - const client = initAndBind(BrowserClient, clientOptions); - - if (options.autoSessionTracking) { - startSessionTracking(); - } - - return client; + return initAndBind(BrowserClient, clientOptions); } /** @@ -308,32 +306,6 @@ export function onLoad(callback: () => void): void { callback(); } -/** - * Enable automatic Session Tracking for the initial page load. - */ -function startSessionTracking(): void { - if (typeof WINDOW.document === 'undefined') { - DEBUG_BUILD && logger.warn('Session tracking in non-browser environment with @sentry/browser is not supported.'); - return; - } - - // The session duration for browser sessions does not track a meaningful - // concept that can be used as a metric. - // Automatically captured sessions are akin to page views, and thus we - // discard their duration. - startSession({ ignoreDuration: true }); - captureSession(); - - // We want to create a session for every navigation as well - addHistoryInstrumentationHandler(({ from, to }) => { - // Don't create an additional session for the initial route or if the location did not change - if (from !== undefined && from !== to) { - startSession({ ignoreDuration: true }); - captureSession(); - } - }); -} - /** * Captures user feedback and sends it to Sentry. *