Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = [
path: 'packages/browser/build/npm/esm/prod/index.js',
import: createImport('init'),
gzip: true,
limit: '24.1 KB',
limit: '24.5 KB',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundle size increases in browser packages

Low Severity

Multiple browser bundle size limits were increased (e.g., treeshaken build from 24.1 KB to 24.5 KB, CDN bundles up by ~1 KB each). The increases come from adding getIsolationScope, scope listener logic, and getCombinedScopeData import into the session integration. Flagging per project rules — these may be unavoidable given the fix.

Additional Locations (2)

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

modifyWebpackConfig: function (config) {
const webpack = require('webpack');

Expand Down Expand Up @@ -190,7 +190,7 @@ module.exports = [
name: 'CDN Bundle (incl. Logs, Metrics)',
path: createCDNPath('bundle.logs.metrics.min.js'),
gzip: true,
limit: '29 KB',
limit: '30 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Logs, Metrics)',
Expand All @@ -214,7 +214,7 @@ module.exports = [
name: 'CDN Bundle (incl. Tracing, Replay, Logs, Metrics)',
path: createCDNPath('bundle.tracing.replay.logs.metrics.min.js'),
gzip: true,
limit: '81 KB',
limit: '82 KB',
},
{
name: 'CDN Bundle (incl. Tracing, Replay, Feedback)',
Expand All @@ -241,7 +241,7 @@ module.exports = [
path: createCDNPath('bundle.tracing.min.js'),
gzip: false,
brotli: false,
limit: '128 KB',
limit: '129 KB',
},
{
name: 'CDN Bundle (incl. Logs, Metrics) - uncompressed',
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

### Important Changes

- **fix(browser): Ensure user id is consistently added to sessions ([#19341](https://github.com/getsentry/sentry-javascript/pull/19341))**

Previously, the SDK inconsistently set the user id on sessions, meaning sessions were often lacking proper coupling to the user set for example via `Sentry.setUser()`.
Additionally, the SDK incorrectly skipped starting a new session for the first soft navigation after the pageload.
This patch fixes these issues. As a result, metrics around sessions, like "Crash Free Sessions" or "Crash Free Users" might change.
This could also trigger alerts, depending on your set thresholds and conditions.
We apologize for any inconvenience caused!

While we're at it, if you're using Sentry in a Single Page App or meta framework, you might want to give the new `'page'` session lifecycle a try!
This new mode no longer creates a session per soft navigation but continues the initial session until the next hard page refresh.
Check out the [docs](TODO LINK) to learn more!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: Just a reminder to not forget to set the link in the docs

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placeholder TODO link in changelog entry

Medium Severity

The changelog contains a [docs](TODO LINK) placeholder that hasn't been replaced with an actual URL. This would be published as-is in the release notes, leaving users with a broken/meaningless link.

Fix in Cursor Fix in Web


## 10.39.0

### Important Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<meta charset="utf-8" />
</head>
<body>
<a id='navigate' href="foo">Navigate</button>
<a id='navigate' href="#foo">Navigate</button>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,31 +1,42 @@
import type { Route } from '@playwright/test';
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
import { waitForSession } from '../../../utils/helpers';

sentryTest('should start a new session on pageload.', async ({ getLocalTestUrl, page }) => {
sentryTest('starts a new session on pageload.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const session = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const sessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');

await page.goto(url);
const session = await sessionPromise;

expect(session).toBeDefined();
expect(session.init).toBe(true);
expect(session.errors).toBe(0);
expect(session.status).toBe('ok');
expect(session.did).toBe('1337');
expect(session).toEqual({
attrs: {
environment: 'production',
release: '0.1',
user_agent: expect.any(String),
},
did: '1337',
errors: 0,
init: true,
sid: expect.any(String),
started: expect.any(String),
status: 'ok',
timestamp: expect.any(String),
});
});

sentryTest('should start a new session with navigation.', async ({ getLocalTestUrl, page }) => {
sentryTest('starts a new session with navigation.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const initSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');

// Route must be set up before any navigation to avoid race conditions
await page.route('**/foo', (route: Route) => route.continue({ url }));

const initSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
await page.goto(url);
const initSession = await initSessionPromise;

const newSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.locator('#navigate').click();

const newSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const newSession = await newSessionPromise;

expect(newSession).toBeDefined();
expect(newSession.init).toBe(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,53 @@
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import type { SerializedSession } from '@sentry/core/src';
import { sentryTest } from '../../../utils/fixtures';
import { getMultipleSentryEnvelopeRequests } from '../../../utils/helpers';
import {
envelopeRequestParser,
getMultipleSentryEnvelopeRequests,
waitForErrorRequest,
waitForSession,
} from '../../../utils/helpers';

sentryTest('should start a session on pageload with page lifecycle.', async ({ getLocalTestUrl, page }) => {
sentryTest('starts a session on pageload with page lifecycle.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const sessions = await getMultipleSentryEnvelopeRequests<SessionContext>(page, 1, {
url,
envelopeType: 'session',
timeout: 2000,
});
const sessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const session = await sessionPromise;

expect(sessions.length).toBeGreaterThanOrEqual(1);
const session = sessions[0];
expect(session).toBeDefined();
expect(session.init).toBe(true);
expect(session.errors).toBe(0);
expect(session.status).toBe('ok');
expect(session).toEqual({
attrs: {
environment: 'production',
release: '0.1',
user_agent: expect.any(String),
},
errors: 0,
init: true,
sid: expect.any(String),
started: expect.any(String),
status: 'ok',
timestamp: expect.any(String),
});
});

sentryTest(
'should NOT start a new session on pushState navigation with page lifecycle.',
"doesn't start a new session on pushState navigation with page lifecycle.",
async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const sessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
const sessionsPromise = getMultipleSentryEnvelopeRequests<SerializedSession>(page, 10, {
url,
envelopeType: 'session',
timeout: 4000,
});

const manualSessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
const manualSessionsPromise = getMultipleSentryEnvelopeRequests<SerializedSession>(page, 10, {
envelopeType: 'session',
timeout: 4000,
});

const eventsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
envelopeType: 'event',
timeout: 4000,
});
const eventsPromise = waitForErrorRequest(page, e => e.message === 'Test error from manual session');

await page.waitForSelector('#navigate');

Expand All @@ -56,17 +64,17 @@ sentryTest(
await page.locator('#manual-session').click();

const newSessions = (await manualSessionsPromise).filter(session => session.init);
const events = await eventsPromise;
const event = envelopeRequestParser(await eventsPromise);

expect(newSessions.length).toBe(2);
expect(newSessions[0].init).toBe(true);
expect(newSessions[1].init).toBe(true);
expect(newSessions[1].sid).not.toBe(newSessions[0].sid);
expect(events).toEqual([
expect(event).toEqual(
expect.objectContaining({
level: 'error',
message: 'Test error from manual session',
}),
]);
);
},
);
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import type { SerializedSession } from '@sentry/core/src';
import { sentryTest } from '../../../utils/fixtures';
import { getMultipleSentryEnvelopeRequests } from '../../../utils/helpers';

Expand All @@ -8,7 +8,7 @@ sentryTest(
async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const sessionsPromise = getMultipleSentryEnvelopeRequests<SessionContext>(page, 10, {
const sessionsPromise = getMultipleSentryEnvelopeRequests<SerializedSession>(page, 10, {
url,
envelopeType: 'session',
timeout: 4000,
Expand All @@ -20,8 +20,8 @@ sentryTest(
await page.locator('#navigate').click();
await page.locator('#navigate').click();

const sessions = (await sessionsPromise).filter(session => session.init);
const startedSessions = (await sessionsPromise).filter(session => session.init);

expect(sessions.length).toBe(3);
expect(startedSessions.length).toBe(4);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<meta charset="utf-8" />
</head>
<body>
<a id='navigate' href="foo">Navigate</button>
<a id='navigate' href="#foo">Navigate</button>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Route } from '@playwright/test';
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers';
import { waitForSession } from '../../../utils/helpers';

sentryTest('should start a new session on pageload.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const session = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const sessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const session = await sessionPromise;

expect(session).toBeDefined();
expect(session.init).toBe(true);
Expand All @@ -16,18 +16,20 @@ sentryTest('should start a new session on pageload.', async ({ getLocalTestUrl,

sentryTest('should start a new session with navigation.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
await page.route('**/foo', (route: Route) => route.continue({ url }));

const initSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const initSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const initSession = await initSessionPromise;

const newSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.locator('#navigate').click();

const newSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const newSession = await newSessionPromise;

expect(newSession).toBeDefined();
expect(newSession.init).toBe(true);
expect(newSession.errors).toBe(0);
expect(newSession.status).toBe('ok');
expect(newSession.sid).toBeDefined();

expect(initSession.sid).not.toBe(newSession.sid);
});
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { expect } from '@playwright/test';
import type { SessionContext } from '@sentry/core';
import { sentryTest } from '../../../utils/fixtures';
import { getFirstSentryEnvelopeRequest, waitForSession } from '../../../utils/helpers';
import { waitForSession } from '../../../utils/helpers';

sentryTest('should update session when an error is thrown.', async ({ getLocalTestUrl, page }) => {
const pageloadSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
const url = await getLocalTestUrl({ testDir: __dirname });

const pageloadSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
await page.goto(url);
const pageloadSession = await pageloadSessionPromise;

const updatedSessionPromise = waitForSession(page);
const updatedSessionPromise = waitForSession(page, s => !s.init);
await page.locator('#throw-error').click();
const updatedSession = await updatedSessionPromise;

expect(pageloadSession).toBeDefined();
expect(pageloadSession.init).toBe(true);
expect(pageloadSession.errors).toBe(0);
expect(updatedSession).toBeDefined();

expect(updatedSession.init).toBe(false);
expect(updatedSession.errors).toBe(1);
expect(updatedSession.status).toBe('crashed');
Expand All @@ -25,7 +26,9 @@ sentryTest('should update session when an error is thrown.', async ({ getLocalTe
sentryTest('should update session when an exception is captured.', async ({ getLocalTestUrl, page }) => {
const url = await getLocalTestUrl({ testDir: __dirname });

const pageloadSession = await getFirstSentryEnvelopeRequest<SessionContext>(page, url);
const pageloadSessionPromise = waitForSession(page, s => !!s.init && s.status === 'ok');
await page.goto(url);
const pageloadSession = await pageloadSessionPromise;

const updatedSessionPromise = waitForSession(page);
await page.locator('#capture-exception').click();
Expand All @@ -34,6 +37,7 @@ sentryTest('should update session when an exception is captured.', async ({ getL
expect(pageloadSession).toBeDefined();
expect(pageloadSession.init).toBe(true);
expect(pageloadSession.errors).toBe(0);

expect(updatedSession).toBeDefined();
expect(updatedSession.init).toBe(false);
expect(updatedSession.errors).toBe(1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Sentry.setUser({
id: '1337',
email: 'user@name.com',
username: 'user1337',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<a id='navigate' href="#foo">Navigate</button>
</body>
</html>
Loading
Loading