diff --git a/dev-packages/browser-integration-tests/suites/sessions/update-session/test.ts b/dev-packages/browser-integration-tests/suites/sessions/update-session/test.ts index 3f1419d1615d..5a576bc1672d 100644 --- a/dev-packages/browser-integration-tests/suites/sessions/update-session/test.ts +++ b/dev-packages/browser-integration-tests/suites/sessions/update-session/test.ts @@ -2,14 +2,16 @@ import { expect } from '@playwright/test'; import type { SessionContext } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; +import { getFirstSentryEnvelopeRequest, waitForSession } from '../../../utils/helpers'; sentryTest('should update session when an error is thrown.', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadSession = await getFirstSentryEnvelopeRequest(page, url); - const updatedSession = ( - await Promise.all([page.locator('#throw-error').click(), getFirstSentryEnvelopeRequest(page)]) - )[1]; + + const updatedSessionPromise = waitForSession(page); + await page.locator('#throw-error').click(); + const updatedSession = await updatedSessionPromise; expect(pageloadSession).toBeDefined(); expect(pageloadSession.init).toBe(true); @@ -25,9 +27,10 @@ sentryTest('should update session when an exception is captured.', async ({ getL const url = await getLocalTestUrl({ testDir: __dirname }); const pageloadSession = await getFirstSentryEnvelopeRequest(page, url); - const updatedSession = ( - await Promise.all([page.locator('#capture-exception').click(), getFirstSentryEnvelopeRequest(page)]) - )[1]; + + const updatedSessionPromise = waitForSession(page); + await page.locator('#capture-exception').click(); + const updatedSession = await updatedSessionPromise; expect(pageloadSession).toBeDefined(); expect(pageloadSession.init).toBe(true); diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts index e02365302331..e89f5ae3c2f7 100644 --- a/dev-packages/browser-integration-tests/utils/helpers.ts +++ b/dev-packages/browser-integration-tests/utils/helpers.ts @@ -7,6 +7,7 @@ import type { Event, EventEnvelope, EventEnvelopeHeaders, + SessionContext, TransactionEvent, } from '@sentry/core'; @@ -157,7 +158,7 @@ export const countEnvelopes = async ( * @param {{ path?: string; content?: string }} impl * @return {*} {Promise} */ -async function runScriptInSandbox( +export async function runScriptInSandbox( page: Page, impl: { path?: string; @@ -178,7 +179,7 @@ async function runScriptInSandbox( * @param {string} [url] * @return {*} {Promise>} */ -async function getSentryEvents(page: Page, url?: string): Promise> { +export async function getSentryEvents(page: Page, url?: string): Promise> { if (url) { await page.goto(url); } @@ -250,6 +251,25 @@ export function waitForTransactionRequest( }); } +export async function waitForSession(page: Page): Promise { + const req = await page.waitForRequest(req => { + const postData = req.postData(); + if (!postData) { + return false; + } + + try { + const event = envelopeRequestParser(req); + + return typeof event.init === 'boolean' && event.started !== undefined; + } catch { + return false; + } + }); + + return envelopeRequestParser(req); +} + /** * We can only test tracing tests in certain bundles/packages: * - NPM (ESM, CJS) @@ -353,7 +373,7 @@ async function getMultipleRequests( /** * Wait and get multiple envelope requests at the given URL, or the current page */ -async function getMultipleSentryEnvelopeRequests( +export async function getMultipleSentryEnvelopeRequests( page: Page, count: number, options?: { @@ -374,7 +394,7 @@ async function getMultipleSentryEnvelopeRequests( * @param {string} [url] * @return {*} {Promise} */ -async function getFirstSentryEnvelopeRequest( +export async function getFirstSentryEnvelopeRequest( page: Page, url?: string, requestParser: (req: Request) => T = envelopeRequestParser as (req: Request) => T, @@ -388,5 +408,3 @@ async function getFirstSentryEnvelopeRequest( return req; } - -export { runScriptInSandbox, getMultipleSentryEnvelopeRequests, getFirstSentryEnvelopeRequest, getSentryEvents }; diff --git a/docs/migration/v8-to-v9.md b/docs/migration/v8-to-v9.md index ded2b0a89a8f..b8af294ec72f 100644 --- a/docs/migration/v8-to-v9.md +++ b/docs/migration/v8-to-v9.md @@ -143,10 +143,14 @@ Sentry.init({ - The `flatten` export has been removed. There is no replacement. - The `urlEncode` method has been removed. There is no replacement. - The `getDomElement` method has been removed. There is no replacement. -- The `Request` type has been removed. Use `RequestEventData` type instead. -- The `TransactionNamingScheme` type has been removed. There is no replacement. - The `memoBuilder` method has been removed. There is no replacement. +#### Other/Internal Changes + +The following changes are unlikely to affect users of the SDK. They are listed here only for completion sake, and to alert users that may be relying on internal behavior. + +- `client._prepareEvent()` now requires a currentScope & isolationScope to be passed as last arugments + ### `@sentry/browser` - The `captureUserFeedback` method has been removed. Use `captureFeedback` instead and update the `comments` field to `message`. @@ -210,6 +214,8 @@ This led to some duplication, where we had to keep an interface in `@sentry/type Since v9, the types have been merged into `@sentry/core`, which removed some of this duplication. This means that certain things that used to be a separate interface, will not expect an actual instance of the class/concrete implementation. This should not affect most users, unless you relied on passing things with a similar shape to internal methods. The following types are affected: - `Scope` now always expects the `Scope` class +- The `TransactionNamingScheme` type has been removed. There is no replacement. +- The `Request` type has been removed. Use `RequestEventData` type instead. - The `IntegrationClass` type is no longer exported - it was not used anymore. Instead, use `Integration` or `IntegrationFn`. # No Version Support Timeline diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index c6945289284a..c2822172faff 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -105,8 +105,13 @@ export class BrowserClient extends BaseClient { /** * @inheritDoc */ - protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike { + protected _prepareEvent( + event: Event, + hint: EventHint, + currentScope: Scope, + isolationScope: Scope, + ): PromiseLike { event.platform = event.platform || 'javascript'; - return super._prepareEvent(event, hint, scope); + return super._prepareEvent(event, hint, currentScope, isolationScope); } } diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 32cef2752509..0fa2c1f26b20 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -216,8 +216,11 @@ export abstract class BaseClient implements Client { const sdkProcessingMetadata = event.sdkProcessingMetadata || {}; const capturedSpanScope: Scope | undefined = sdkProcessingMetadata.capturedSpanScope; + const capturedSpanIsolationScope: Scope | undefined = sdkProcessingMetadata.capturedSpanIsolationScope; - this._process(this._captureEvent(event, hintWithEventId, capturedSpanScope || currentScope)); + this._process( + this._captureEvent(event, hintWithEventId, capturedSpanScope || currentScope, capturedSpanIsolationScope), + ); return hintWithEventId.event_id; } @@ -676,8 +679,8 @@ export abstract class BaseClient implements Client { protected _prepareEvent( event: Event, hint: EventHint, - currentScope = getCurrentScope(), - isolationScope = getIsolationScope(), + currentScope: Scope, + isolationScope: Scope, ): PromiseLike { const options = this.getOptions(); const integrations = Object.keys(this._integrations); @@ -718,12 +721,17 @@ export abstract class BaseClient implements Client { * @param hint * @param scope */ - protected _captureEvent(event: Event, hint: EventHint = {}, scope?: Scope): PromiseLike { + protected _captureEvent( + event: Event, + hint: EventHint = {}, + currentScope = getCurrentScope(), + isolationScope = getIsolationScope(), + ): PromiseLike { if (DEBUG_BUILD && isErrorEvent(event)) { logger.log(`Captured error event \`${getPossibleEventMessages(event)[0] || ''}\``); } - return this._processEvent(event, hint, scope).then( + return this._processEvent(event, hint, currentScope, isolationScope).then( finalEvent => { return finalEvent.event_id; }, @@ -756,7 +764,12 @@ export abstract class BaseClient implements Client { * @param currentScope A scope containing event metadata. * @returns A SyncPromise that resolves with the event or rejects in case event was/will not be send. */ - protected _processEvent(event: Event, hint: EventHint, currentScope?: Scope): PromiseLike { + protected _processEvent( + event: Event, + hint: EventHint, + currentScope: Scope, + isolationScope: Scope, + ): PromiseLike { const options = this.getOptions(); const { sampleRate } = options; @@ -779,12 +792,9 @@ export abstract class BaseClient implements Client { ); } - const dataCategory: DataCategory = eventType === 'replay_event' ? 'replay' : eventType; - - const sdkProcessingMetadata = event.sdkProcessingMetadata || {}; - const capturedSpanIsolationScope: Scope | undefined = sdkProcessingMetadata.capturedSpanIsolationScope; + const dataCategory = (eventType === 'replay_event' ? 'replay' : eventType) satisfies DataCategory; - return this._prepareEvent(event, hint, currentScope, capturedSpanIsolationScope) + return this._prepareEvent(event, hint, currentScope, isolationScope) .then(prepared => { if (prepared === null) { this.recordDroppedEvent('event_processor', dataCategory, event); @@ -811,8 +821,8 @@ export abstract class BaseClient implements Client { throw new SentryError(`${beforeSendLabel} returned \`null\`, will not send event.`, 'log'); } - const session = currentScope && currentScope.getSession(); - if (!isTransaction && session) { + const session = currentScope.getSession() || isolationScope.getSession(); + if (isError && session) { this._updateSessionFromEvent(session, processedEvent); } diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index d9ccca362e43..49133ce99cbe 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -287,10 +287,6 @@ export function startSession(context?: SessionContext): Session { // Afterwards we set the new session on the scope isolationScope.setSession(session); - // TODO (v8): Remove this and only use the isolation scope(?). - // For v7 though, we can't "soft-break" people using getCurrentHub().getScope().setSession() - currentScope.setSession(session); - return session; } @@ -309,10 +305,6 @@ export function endSession(): void { // the session is over; take it off of the scope isolationScope.setSession(); - - // TODO (v8): Remove this and only use the isolation scope(?). - // For v7 though, we can't "soft-break" people using getCurrentHub().getScope().setSession() - currentScope.setSession(); } /** @@ -320,11 +312,8 @@ export function endSession(): void { */ function _sendSessionUpdate(): void { const isolationScope = getIsolationScope(); - const currentScope = getCurrentScope(); const client = getClient(); - // TODO (v8): Remove currentScope and only use the isolation scope(?). - // For v7 though, we can't "soft-break" people using getCurrentHub().getScope().setSession() - const session = currentScope.getSession() || isolationScope.getSession(); + const session = isolationScope.getSession(); if (session && client) { client.captureSession(session); } diff --git a/packages/core/src/getCurrentHubShim.ts b/packages/core/src/getCurrentHubShim.ts index d0f9ab4133f4..e972f79a9c49 100644 --- a/packages/core/src/getCurrentHubShim.ts +++ b/packages/core/src/getCurrentHubShim.ts @@ -2,6 +2,7 @@ import { addBreadcrumb } from './breadcrumbs'; import { getClient, getCurrentScope, getIsolationScope, withScope } from './currentScopes'; import { captureEvent, + captureSession, endSession, setContext, setExtra, @@ -54,15 +55,7 @@ export function getCurrentHubShim(): Hub { startSession, endSession, - captureSession(end?: boolean): void { - // both send the update and pull the session from the scope - if (end) { - return endSession(); - } - - // only send the update - _sendSessionUpdate(); - }, + captureSession, }; } @@ -77,16 +70,3 @@ export function getCurrentHubShim(): Hub { */ // eslint-disable-next-line deprecation/deprecation export const getCurrentHub = getCurrentHubShim; - -/** - * Sends the current Session on the scope - */ -function _sendSessionUpdate(): void { - const scope = getCurrentScope(); - const client = getClient(); - - const session = scope.getSession(); - if (client && session) { - client.captureSession(session); - } -} diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index 479682d06998..4974f6ac72dd 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -166,8 +166,8 @@ export class ServerRuntimeClient< protected _prepareEvent( event: Event, hint: EventHint, - scope?: Scope, - isolationScope?: Scope, + currentScope: Scope, + isolationScope: Scope, ): PromiseLike { if (this._options.platform) { event.platform = event.platform || this._options.platform; @@ -184,7 +184,7 @@ export class ServerRuntimeClient< event.server_name = event.server_name || this._options.serverName; } - return super._prepareEvent(event, hint, scope, isolationScope); + return super._prepareEvent(event, hint, currentScope, isolationScope); } /** Extract trace information from scope */ diff --git a/packages/core/test/lib/serverruntimeclient.test.ts b/packages/core/test/lib/server-runtime-client.test.ts similarity index 93% rename from packages/core/test/lib/serverruntimeclient.test.ts rename to packages/core/test/lib/server-runtime-client.test.ts index bdf1c5242b80..2eeb90083f29 100644 --- a/packages/core/test/lib/serverruntimeclient.test.ts +++ b/packages/core/test/lib/server-runtime-client.test.ts @@ -1,6 +1,6 @@ import type { Event, EventHint } from '../../src/types-hoist'; -import { createTransport } from '../../src'; +import { Scope, createTransport } from '../../src'; import type { ServerRuntimeClientOptions } from '../../src/server-runtime-client'; import { ServerRuntimeClient } from '../../src/server-runtime-client'; @@ -18,6 +18,9 @@ function getDefaultClientOptions(options: Partial = describe('ServerRuntimeClient', () => { let client: ServerRuntimeClient; + const currentScope = new Scope(); + const isolationScope = new Scope(); + describe('_prepareEvent', () => { test('adds platform to event', () => { const options = getDefaultClientOptions({ dsn: PUBLIC_DSN }); @@ -25,7 +28,7 @@ describe('ServerRuntimeClient', () => { const event: Event = {}; const hint: EventHint = {}; - (client as any)._prepareEvent(event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.platform).toEqual('blargh'); }); @@ -36,7 +39,7 @@ describe('ServerRuntimeClient', () => { const event: Event = {}; const hint: EventHint = {}; - (client as any)._prepareEvent(event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.server_name).toEqual('server'); }); @@ -47,7 +50,7 @@ describe('ServerRuntimeClient', () => { const event: Event = {}; const hint: EventHint = {}; - (client as any)._prepareEvent(event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.contexts?.runtime).toEqual({ name: 'edge', @@ -60,7 +63,7 @@ describe('ServerRuntimeClient', () => { const event: Event = { contexts: { runtime: { name: 'foo', version: '1.2.3' } } }; const hint: EventHint = {}; - (client as any)._prepareEvent(event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.contexts?.runtime).toEqual({ name: 'foo', version: '1.2.3' }); expect(event.contexts?.runtime).not.toEqual({ name: 'edge' }); diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts index aa903789ad12..7b25d851137a 100644 --- a/packages/node/src/integrations/anr/index.ts +++ b/packages/node/src/integrations/anr/index.ts @@ -186,7 +186,7 @@ async function _startWorker( const timer = setInterval(() => { try { - const currentSession = getCurrentScope().getSession(); + const currentSession = getIsolationScope().getSession(); // We need to copy the session object and remove the toJSON method so it can be sent to the worker // serialized without making it a SerializedSession const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined; @@ -202,7 +202,7 @@ async function _startWorker( worker.on('message', (msg: string) => { if (msg === 'session-ended') { log('ANR event sent from ANR worker. Clearing session in this thread.'); - getCurrentScope().setSession(undefined); + getIsolationScope().setSession(undefined); } }); diff --git a/packages/node/test/sdk/client.test.ts b/packages/node/test/sdk/client.test.ts index 8b3842a95661..8eb66b78e82e 100644 --- a/packages/node/test/sdk/client.test.ts +++ b/packages/node/test/sdk/client.test.ts @@ -2,8 +2,7 @@ import * as os from 'os'; import { ProxyTracer } from '@opentelemetry/api'; import * as opentelemetryInstrumentationPackage from '@opentelemetry/instrumentation'; import type { Event, EventHint } from '@sentry/core'; -import { SDK_VERSION, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; - +import { SDK_VERSION, Scope, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import { NodeClient } from '../../src'; import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions'; @@ -65,13 +64,16 @@ describe('NodeClient', () => { }); describe('_prepareEvent', () => { + const currentScope = new Scope(); + const isolationScope = new Scope(); + test('adds platform to event', () => { const options = getDefaultNodeClientOptions({}); const client = new NodeClient(options); const event: Event = {}; const hint: EventHint = {}; - client['_prepareEvent'](event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.platform).toEqual('node'); }); @@ -82,7 +84,7 @@ describe('NodeClient', () => { const event: Event = {}; const hint: EventHint = {}; - client['_prepareEvent'](event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.contexts?.runtime).toEqual({ name: 'node', @@ -96,7 +98,7 @@ describe('NodeClient', () => { const event: Event = {}; const hint: EventHint = {}; - client['_prepareEvent'](event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.server_name).toEqual('foo'); }); @@ -108,7 +110,7 @@ describe('NodeClient', () => { const event: Event = {}; const hint: EventHint = {}; - client['_prepareEvent'](event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.server_name).toEqual('foo'); @@ -121,7 +123,7 @@ describe('NodeClient', () => { const event: Event = {}; const hint: EventHint = {}; - client['_prepareEvent'](event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.server_name).toEqual(os.hostname()); }); @@ -132,7 +134,7 @@ describe('NodeClient', () => { const event: Event = { contexts: { runtime: { name: 'foo', version: '1.2.3' } } }; const hint: EventHint = {}; - client['_prepareEvent'](event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.contexts?.runtime).toEqual({ name: 'foo', version: '1.2.3' }); expect(event.contexts?.runtime).not.toEqual({ name: 'node', version: process.version }); @@ -144,7 +146,7 @@ describe('NodeClient', () => { const event: Event = { server_name: 'foo' }; const hint: EventHint = {}; - client['_prepareEvent'](event, hint); + client['_prepareEvent'](event, hint, currentScope, isolationScope); expect(event.server_name).toEqual('foo'); expect(event.server_name).not.toEqual('bar');