Skip to content

Commit 85aa5da

Browse files
committed
feat(browser): Add mode option for the browser session integration
1 parent 76afe7d commit 85aa5da

File tree

10 files changed

+146
-9
lines changed

10 files changed

+146
-9
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
release: '0.1',
8+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
let clickCount = 0;
2+
3+
document.getElementById('navigate').addEventListener('click', () => {
4+
clickCount++;
5+
history.pushState({}, '', `/page-${clickCount}`);
6+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button id="navigate">Navigate via pushState</button>
8+
</body>
9+
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { expect } from '@playwright/test';
2+
import type { SessionContext } from '@sentry/core';
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { getMultipleSentryEnvelopeRequests } from '../../../utils/helpers';
5+
6+
sentryTest('should start new sessions on pushState navigation in default mode.', async ({ getLocalTestUrl, page }) => {
7+
const url = await getLocalTestUrl({ testDir: __dirname });
8+
9+
const sessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
10+
url,
11+
envelopeType: 'session',
12+
timeout: 4000,
13+
});
14+
15+
await page.waitForSelector('#navigate');
16+
17+
await page.locator('#navigate').click();
18+
await page.locator('#navigate').click();
19+
await page.locator('#navigate').click();
20+
21+
const sessions = (await sessionsPromise).filter(session => session.init);
22+
23+
expect(sessions.length).toBe(3);
24+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
release: '0.1',
8+
integrations: [Sentry.browserSessionIntegration({ mode: 'single' })],
9+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let clickCount = 0;
2+
3+
document.getElementById('navigate').addEventListener('click', () => {
4+
clickCount++;
5+
// Each click navigates to a different page
6+
history.pushState({}, '', `/page-${clickCount}`);
7+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button id="navigate">Navigate via pushState</button>
8+
</body>
9+
</html>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect } from '@playwright/test';
2+
import type { SessionContext } from '@sentry/core';
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import { getMultipleSentryEnvelopeRequests } from '../../../utils/helpers';
5+
6+
sentryTest('should start a session on pageload in single mode.', async ({ getLocalTestUrl, page }) => {
7+
const url = await getLocalTestUrl({ testDir: __dirname });
8+
9+
const sessions = await getMultipleSentryEnvelopeRequests<SessionContext>(page, 1, {
10+
url,
11+
envelopeType: 'session',
12+
timeout: 2000,
13+
});
14+
15+
expect(sessions.length).toBeGreaterThanOrEqual(1);
16+
const session = sessions[0];
17+
expect(session).toBeDefined();
18+
expect(session.init).toBe(true);
19+
expect(session.errors).toBe(0);
20+
expect(session.status).toBe('ok');
21+
});
22+
23+
sentryTest(
24+
'should NOT start a new session on pushState navigation in single mode.',
25+
async ({ getLocalTestUrl, page }) => {
26+
const url = await getLocalTestUrl({ testDir: __dirname });
27+
28+
const sessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
29+
url,
30+
envelopeType: 'session',
31+
timeout: 4000,
32+
});
33+
34+
await page.waitForSelector('#navigate');
35+
36+
await page.locator('#navigate').click();
37+
await page.locator('#navigate').click();
38+
await page.locator('#navigate').click();
39+
40+
const sessions = (await sessionsPromise).filter(session => session.init);
41+
42+
expect(sessions.length).toBe(1);
43+
expect(sessions[0].init).toBe(true);
44+
},
45+
);

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export { makeBrowserOfflineTransport } from './transports/offline';
7676
export { browserProfilingIntegration } from './profiling/integration';
7777
export { spotlightBrowserIntegration } from './integrations/spotlight';
7878
export { browserSessionIntegration } from './integrations/browsersession';
79+
export type { BrowserSessionOptions } from './integrations/browsersession';
7980
export { launchDarklyIntegration, buildLaunchDarklyFlagUsedHandler } from './integrations/featureFlags/launchdarkly';
8081
export { openFeatureIntegration, OpenFeatureIntegrationHook } from './integrations/featureFlags/openfeature';
8182
export { unleashIntegration } from './integrations/featureFlags/unleash';

packages/browser/src/integrations/browsersession.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,30 @@ import { addHistoryInstrumentationHandler } from '@sentry-internal/browser-utils
33
import { DEBUG_BUILD } from '../debug-build';
44
import { WINDOW } from '../helpers';
55

6+
export interface BrowserSessionOptions {
7+
/**
8+
* Controls when sessions are created.
9+
*
10+
* - `'single'`: A session is created once when the page is loaded. Session is not
11+
* updated on navigation. This is useful for webviews or single-page apps where
12+
* URL changes should not trigger new sessions.
13+
* - `'navigation'`: A session is created on page load and on every navigation.
14+
* This is the default behavior.
15+
*
16+
* @default 'navigation'
17+
*/
18+
mode?: 'single' | 'navigation';
19+
}
20+
621
/**
722
* When added, automatically creates sessions which allow you to track adoption and crashes (crash free rate) in your Releases in Sentry.
823
* More information: https://docs.sentry.io/product/releases/health/
924
*
1025
* Note: In order for session tracking to work, you need to set up Releases: https://docs.sentry.io/product/releases/
1126
*/
12-
export const browserSessionIntegration = defineIntegration(() => {
27+
export const browserSessionIntegration = defineIntegration((options: BrowserSessionOptions = {}) => {
28+
const mode = options.mode ?? 'navigation';
29+
1330
return {
1431
name: 'BrowserSession',
1532
setupOnce() {
@@ -26,14 +43,16 @@ export const browserSessionIntegration = defineIntegration(() => {
2643
startSession({ ignoreDuration: true });
2744
captureSession();
2845

29-
// We want to create a session for every navigation as well
30-
addHistoryInstrumentationHandler(({ from, to }) => {
31-
// Don't create an additional session for the initial route or if the location did not change
32-
if (from !== undefined && from !== to) {
33-
startSession({ ignoreDuration: true });
34-
captureSession();
35-
}
36-
});
46+
if (mode === 'navigation') {
47+
// We want to create a session for every navigation as well
48+
addHistoryInstrumentationHandler(({ from, to }) => {
49+
// Don't create an additional session for the initial route or if the location did not change
50+
if (from !== undefined && from !== to) {
51+
startSession({ ignoreDuration: true });
52+
captureSession();
53+
}
54+
});
55+
}
3756
},
3857
};
3958
});

0 commit comments

Comments
 (0)