Skip to content

Commit e62d70b

Browse files
authored
feat(browser): Add mode option for the browser session integration (#18997)
closes #18921 closes [JS-1526](https://linear.app/getsentry/issue/JS-1526/sessions-provide-more-options-to-control-session-creation) This adds a new `lifecycle` option to the `BrowserSessionIntegration`. New explained: - pagae: A session is created once when the page is loaded. Session is not updated on navigation. This is useful for webviews or single-page apps where URL changes should not trigger new sessions. - route (default): A session is created on page load and on every navigation change The default is to `route` to not introduce any breaking change ## Merge checklist - [ ] Add Sentry Docs issue to add this to [the docs](https://docs.sentry.io/platforms/javascript/configuration/integrations/browsersession/)
1 parent 389ab1f commit e62d70b

File tree

12 files changed

+184
-12
lines changed

12 files changed

+184
-12
lines changed

.size-limit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ module.exports = [
156156
path: 'packages/vue/build/esm/index.js',
157157
import: createImport('init'),
158158
gzip: true,
159-
limit: '30 KB',
159+
limit: '31 KB',
160160
},
161161
{
162162
name: '@sentry/vue (incl. Tracing)',
@@ -196,7 +196,7 @@ module.exports = [
196196
name: 'CDN Bundle (incl. Tracing, Logs, Metrics)',
197197
path: createCDNPath('bundle.tracing.logs.metrics.min.js'),
198198
gzip: true,
199-
limit: '44 KB',
199+
limit: '45 KB',
200200
},
201201
{
202202
name: 'CDN Bundle (incl. Replay, Logs, Metrics)',
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({ lifecycle: 'page' })],
9+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
});
8+
9+
document.getElementById('manual-session').addEventListener('click', () => {
10+
Sentry.startSession();
11+
Sentry.captureException('Test error from manual session');
12+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
<button id="manual-session">Manual session</button>
9+
</body>
10+
</html>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 with page lifecycle.', 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 with page lifecycle.',
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+
const manualSessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
35+
envelopeType: 'session',
36+
timeout: 4000,
37+
});
38+
39+
const eventsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
40+
envelopeType: 'event',
41+
timeout: 4000,
42+
});
43+
44+
await page.waitForSelector('#navigate');
45+
46+
await page.locator('#navigate').click();
47+
await page.locator('#navigate').click();
48+
await page.locator('#navigate').click();
49+
50+
const sessions = (await sessionsPromise).filter(session => session.init);
51+
52+
expect(sessions.length).toBe(1);
53+
expect(sessions[0].init).toBe(true);
54+
55+
// teardown and verify if nothing else got sent
56+
await page.locator('#manual-session').click();
57+
58+
const newSessions = (await manualSessionsPromise).filter(session => session.init);
59+
const events = await eventsPromise;
60+
61+
expect(newSessions.length).toBe(2);
62+
expect(newSessions[0].init).toBe(true);
63+
expect(newSessions[1].init).toBe(true);
64+
expect(newSessions[1].sid).not.toBe(newSessions[0].sid);
65+
expect(events).toEqual([
66+
expect.objectContaining({
67+
level: 'error',
68+
message: 'Test error from manual session',
69+
}),
70+
]);
71+
},
72+
);
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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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(
7+
'should start new sessions on pushState navigation with route lifecycle (default).',
8+
async ({ getLocalTestUrl, page }) => {
9+
const url = await getLocalTestUrl({ testDir: __dirname });
10+
11+
const sessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
12+
url,
13+
envelopeType: 'session',
14+
timeout: 4000,
15+
});
16+
17+
await page.waitForSelector('#navigate');
18+
19+
await page.locator('#navigate').click();
20+
await page.locator('#navigate').click();
21+
await page.locator('#navigate').click();
22+
23+
const sessions = (await sessionsPromise).filter(session => session.init);
24+
25+
expect(sessions.length).toBe(3);
26+
},
27+
);

packages/browser/src/exports.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,6 @@ export { globalHandlersIntegration } from './integrations/globalhandlers';
102102
export { httpContextIntegration } from './integrations/httpcontext';
103103
export { linkedErrorsIntegration } from './integrations/linkederrors';
104104
export { browserApiErrorsIntegration } from './integrations/browserapierrors';
105+
export { browserSessionIntegration } from './integrations/browsersession';
105106

106107
export { lazyLoadIntegration } from './utils/lazyLoadIntegration';

0 commit comments

Comments
 (0)