diff --git a/.github/workflows/buildandtest.yml b/.github/workflows/buildandtest.yml index c9bbd44850..797c2f3385 100644 --- a/.github/workflows/buildandtest.yml +++ b/.github/workflows/buildandtest.yml @@ -272,7 +272,7 @@ jobs: echo "OK: scripts/sentry-xcode.sh executable" job_type_check: - name: Type Check Typescript 3.8 + name: Type Check Typescript 5.0 runs-on: ubuntu-latest needs: [job_build, diff_check] if: ${{ needs.diff_check.outputs.skip_ci != 'true' }} diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index 5f1e5df8ec..1d0717cd6a 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@babel/preset-env": "^7.25.3", "@babel/preset-typescript": "^7.18.6", - "@sentry/core": "10.73.0", + "@sentry/core": "11.0.0-beta.0", "@sentry/react-native": "8.25.0", "@types/node": "^20.9.3", "@types/react": "^19.1.4", diff --git a/dev-packages/type-check/run-type-check.sh b/dev-packages/type-check/run-type-check.sh index 82814732b9..135c69e857 100755 --- a/dev-packages/type-check/run-type-check.sh +++ b/dev-packages/type-check/run-type-check.sh @@ -12,7 +12,7 @@ cd "${__dirpath}/../../packages/core" yalc publish -cd "${__dirpath}/ts3.8-test" +cd "${__dirpath}/ts5.0-test" # Add yalc packages (creates .yalc/ directory and updates package.json) yalc add @sentry/expo-upload-sourcemaps diff --git a/dev-packages/type-check/ts3.8-test/.gitignore b/dev-packages/type-check/ts5.0-test/.gitignore similarity index 100% rename from dev-packages/type-check/ts3.8-test/.gitignore rename to dev-packages/type-check/ts5.0-test/.gitignore diff --git a/dev-packages/type-check/ts3.8-test/index.ts b/dev-packages/type-check/ts5.0-test/index.ts similarity index 100% rename from dev-packages/type-check/ts3.8-test/index.ts rename to dev-packages/type-check/ts5.0-test/index.ts diff --git a/dev-packages/type-check/ts3.8-test/package.json b/dev-packages/type-check/ts5.0-test/package.json similarity index 81% rename from dev-packages/type-check/ts3.8-test/package.json rename to dev-packages/type-check/ts5.0-test/package.json index f718ddf707..119ef0bcfd 100644 --- a/dev-packages/type-check/ts3.8-test/package.json +++ b/dev-packages/type-check/ts5.0-test/package.json @@ -1,8 +1,8 @@ { - "name": "sentry-react-native-ts-3-8-test", + "name": "sentry-react-native-ts-5-0-test", "version": "0.0.0", "private": true, - "description": "Sentry React Native TypeScript 3.8 Test", + "description": "Sentry React Native TypeScript 5.0 Test", "main": "index.ts", "scripts": { "type-check": "tsc --project tsconfig.build.json" @@ -11,7 +11,7 @@ "devDependencies": { "@types/react": "17.0.83", "@types/react-native": "0.65.30", - "typescript": "3.8.3" + "typescript": "5.0.4" }, "dependencies": { "@sentry/expo-upload-sourcemaps": "file:.yalc/@sentry/expo-upload-sourcemaps", diff --git a/dev-packages/type-check/ts3.8-test/tsconfig.build.json b/dev-packages/type-check/ts5.0-test/tsconfig.build.json similarity index 100% rename from dev-packages/type-check/ts3.8-test/tsconfig.build.json rename to dev-packages/type-check/ts5.0-test/tsconfig.build.json diff --git a/dev-packages/type-check/ts3.8-test/yarn.lock b/dev-packages/type-check/ts5.0-test/yarn.lock similarity index 100% rename from dev-packages/type-check/ts3.8-test/yarn.lock rename to dev-packages/type-check/ts5.0-test/yarn.lock diff --git a/packages/core/etc/sentry-react-native.api.md b/packages/core/etc/sentry-react-native.api.md index 55de3c98ae..e514cd512b 100644 --- a/packages/core/etc/sentry-react-native.api.md +++ b/packages/core/etc/sentry-react-native.api.md @@ -8,10 +8,6 @@ import { addBreadcrumb } from '@sentry/core'; import { addConsoleInstrumentationFilter } from '@sentry/core'; import { addEventProcessor } from '@sentry/core'; import { addIntegration } from '@sentry/core'; -import { AnthropicAiClient } from '@sentry/core'; -import { AnthropicAiInstrumentedMethod } from '@sentry/core'; -import { AnthropicAiOptions } from '@sentry/core'; -import { AnthropicAiResponse } from '@sentry/core'; import { Breadcrumb } from '@sentry/core'; import { browserApiErrorsIntegration } from '@sentry/react'; import { globalHandlersIntegration as browserGlobalHandlersIntegration } from '@sentry/react'; @@ -24,10 +20,8 @@ import { captureFeedback } from '@sentry/core'; import { captureMessage } from '@sentry/core'; import { Client } from '@sentry/core'; import type { ClientOptions } from '@sentry/core'; -import { CompiledGraph } from '@sentry/core'; import { consoleLoggingIntegration } from '@sentry/browser'; import { consoleSandbox } from '@sentry/core'; -import { createLangChainCallbackHandler } from '@sentry/core'; import { createReduxEnhancer } from '@sentry/react'; import { dedupeIntegration } from '@sentry/react'; import type { Envelope } from '@sentry/core'; @@ -47,33 +41,17 @@ import { getCurrentScope } from '@sentry/core'; import { getGlobalScope } from '@sentry/core'; import { getIsolationScope } from '@sentry/core'; import { getRootSpan } from '@sentry/core'; -import { GoogleGenAIChat } from '@sentry/core'; -import { GoogleGenAIClient } from '@sentry/core'; -import { GoogleGenAIOptions } from '@sentry/core'; import type { HostComponent } from 'react-native'; import { httpClientIntegration } from '@sentry/react'; import { httpContextIntegration } from '@sentry/react'; import type { ImageStyle } from 'react-native'; -import { inboundFiltersIntegration } from '@sentry/react'; -import { instrumentAnthropicAiClient } from '@sentry/core'; -import { InstrumentedMethod } from '@sentry/core'; -import { instrumentGoogleGenAIClient } from '@sentry/core'; -import { instrumentLangGraph } from '@sentry/core'; -import { instrumentOpenAiClient } from '@sentry/core'; -import { instrumentStateGraph } from '@sentry/core'; -import { instrumentStateGraphCompile } from '@sentry/core'; +import { eventFiltersIntegration as inboundFiltersIntegration } from '@sentry/react'; import { Integration } from '@sentry/core'; -import { LangChainIntegration } from '@sentry/core'; -import { LangChainOptions } from '@sentry/core'; -import { LangGraphIntegration } from '@sentry/core'; -import { LangGraphOptions } from '@sentry/core'; import { lastEventId } from '@sentry/core'; import { logger } from '@sentry/browser'; import type { makeFetchTransport } from '@sentry/browser'; import { Metric } from '@sentry/core'; import { metrics } from '@sentry/browser'; -import { OpenAiClient } from '@sentry/core'; -import { OpenAiOptions } from '@sentry/core'; import type { Options } from '@sentry/core'; import { Profiler } from '@sentry/react'; import * as React_2 from 'react'; @@ -126,14 +104,6 @@ export { addEventProcessor } export { addIntegration } -export { AnthropicAiClient } - -export { AnthropicAiInstrumentedMethod } - -export { AnthropicAiOptions } - -export { AnthropicAiResponse } - // @public export function appLoaded(): void; @@ -179,8 +149,6 @@ export { captureMessage } // @public export function close(): Promise; -export { CompiledGraph } - export { consoleLoggingIntegration } export { consoleSandbox } @@ -188,8 +156,6 @@ export { consoleSandbox } // @public export function crashedLastRun(): Promise; -export { createLangChainCallbackHandler } - // @public (undocumented) export const createNativeFramesIntegrations: (enable: boolean | undefined) => Integration | undefined; @@ -430,12 +396,6 @@ export type GlobalErrorBoundaryProps = ErrorBoundaryProps & { includeUnhandledRejections?: boolean; }; -export { GoogleGenAIChat } - -export { GoogleGenAIClient } - -export { GoogleGenAIOptions } - // Warning: (ae-forgotten-export) The symbol "GraphQLReactNativeIntegrationOptions" needs to be exported by the entry point index.d.ts // // @public @@ -458,28 +418,6 @@ export { inboundFiltersIntegration } // @public export function init(passedOptions: ReactNativeOptions): void; -export { instrumentAnthropicAiClient } - -export { InstrumentedMethod } - -export { instrumentGoogleGenAIClient } - -export { instrumentLangGraph } - -export { instrumentOpenAiClient } - -export { instrumentStateGraph } - -export { instrumentStateGraphCompile } - -export { LangChainIntegration } - -export { LangChainOptions } - -export { LangGraphIntegration } - -export { LangGraphOptions } - export { lastEventId } // @public (undocumented) @@ -552,10 +490,6 @@ export interface NavigationTheme { }; } -export { OpenAiClient } - -export { OpenAiOptions } - // @public export function pauseAppHangTracking(): void; @@ -949,7 +883,7 @@ export function wrapTurboModule(name: string, module: T | null // src/js/feedback/integration.ts:21:5 - (ae-forgotten-export) The symbol "ScreenshotButtonProps" needs to be exported by the entry point index.d.ts // src/js/feedback/integration.ts:23:5 - (ae-forgotten-export) The symbol "FeedbackFormTheme" needs to be exported by the entry point index.d.ts // src/js/tracing/reactnativetracing.ts:90:3 - (ae-forgotten-export) The symbol "ReactNativeTracingState" needs to be exported by the entry point index.d.ts -// src/js/tracing/reactnavigation.ts:228:3 - (ae-forgotten-export) The symbol "RouteOverrideProvider" needs to be exported by the entry point index.d.ts +// src/js/tracing/reactnavigation.ts:229:3 - (ae-forgotten-export) The symbol "RouteOverrideProvider" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/core/package.json b/packages/core/package.json index 3dd3bfbee0..5d90917977 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -73,12 +73,12 @@ "react-native": ">=0.65.0" }, "dependencies": { - "@sentry/browser": "10.73.0", - "@sentry/bundler-plugins": "10.73.0", + "@sentry/browser": "11.0.0-beta.0", + "@sentry/bundler-plugins": "11.0.0-beta.0", "@sentry/cli": "3.7.0", - "@sentry/core": "10.73.0", + "@sentry/core": "11.0.0-beta.0", "@sentry/expo-upload-sourcemaps": "workspace:*", - "@sentry/react": "10.73.0" + "@sentry/react": "11.0.0-beta.0" }, "devDependencies": { "@babel/core": "^7.26.7", @@ -87,8 +87,8 @@ "@mswjs/interceptors": "^0.25.15", "@react-native/babel-preset": "0.87.1", "@react-native/jest-preset": "0.87.1", - "@sentry/eslint-plugin-sdk": "10.73.0", - "@sentry/typescript": "10.73.0", + "@sentry/eslint-plugin-sdk": "11.0.0-beta.0", + "@sentry/typescript": "11.0.0-beta.0", "@sentry/wizard": "7.0.3", "@testing-library/react-native": "^13.2.2", "@types/jest": "^29.5.13", diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx index 1b221c7f81..d6e7ff86ac 100644 --- a/packages/core/src/js/feedback/FeedbackForm.tsx +++ b/packages/core/src/js/feedback/FeedbackForm.tsx @@ -62,7 +62,10 @@ export class FeedbackForm extends React.Component[0]); + } else if (isPlainObject(linkedError) && 'stackReturnAddresses' in linkedError) { // isObjCException - const { appleException, appleDebugImages } = exceptionFromAppleStackReturnAddresses(linkedError); + const { appleException, appleDebugImages } = exceptionFromAppleStackReturnAddresses( + linkedError as Parameters[0], + ); exception = appleException; exceptionDebugImages = appleDebugImages; } else if (isInstanceOf(linkedError, Error)) { - exception = exceptionFromError(parser, error[key]); + exception = exceptionFromError(parser, linkedError); } else if (isPlainObject(linkedError)) { - // oxlint-disable-next-line typescript-eslint(no-unnecessary-type-assertion) - const plainError = linkedError as Record; + const plainError = linkedError; exception = { type: typeof plainError.name === 'string' ? plainError.name : undefined, value: typeof plainError.message === 'string' ? plainError.message : undefined, @@ -122,7 +127,7 @@ function walkErrorTree( return walkErrorTree( parser, limit, - linkedError, + linkedError as ExtendedError, key, [...exceptions, exception], [...debugImages, ...(exceptionDebugImages || [])], diff --git a/packages/core/src/js/integrations/turboModuleContext.ts b/packages/core/src/js/integrations/turboModuleContext.ts index ad37e56a91..d1cad2a36c 100644 --- a/packages/core/src/js/integrations/turboModuleContext.ts +++ b/packages/core/src/js/integrations/turboModuleContext.ts @@ -1,7 +1,7 @@ /* oxlint-disable eslint(max-lines) */ import type { Client, Event, Integration, Span, TransactionEvent } from '@sentry/core'; -import { addBreadcrumb, debug, spanToJSON } from '@sentry/core'; +import { addBreadcrumb, debug, spanToStaticSpanJSON } from '@sentry/core'; import { addTurboModuleCallStartObserver, @@ -439,7 +439,7 @@ function attachWindowToSpan( window.writtenPerMethodKeys = nextKeys; if (rows.length > topN) { - const spanId = spanToJSON(span).span_id; + const spanId = spanToStaticSpanJSON(span).span_id; debug.log( `[TurboModuleContext] Span ${spanId ?? '(unknown)'} touched ${rows.length} unique TurboModule methods, ` + `truncated to top ${topN} by duration. Summary attributes still reflect the full totals.`, @@ -448,7 +448,7 @@ function attachWindowToSpan( span.setAttributes(attributes); - const spanId = spanToJSON(span).span_id; + const spanId = spanToStaticSpanJSON(span).span_id; if (spanId) { if (!pendingSpanAttributes.has(spanId) && pendingSpanAttributes.size >= MAX_PENDING_SPAN_ATTRIBUTES) { const oldest = pendingSpanAttributes.keys().next().value; diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 3780539caf..dd847311b7 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -510,6 +510,17 @@ export interface BaseReactNativeOptions { */ propagateTraceparent?: boolean; + /** + * Enables sending log events to Sentry. + * + * On the JavaScript layer this used to be a `@sentry/core` option. As of JS SDK v11 the option + * was removed from core (logs are captured on-usage there), so the React Native SDK owns it to + * keep gating native log capture and its `logsOrigin` orchestration. + * + * @default false + */ + enableLogs?: boolean; + /** * Controls which log origin is captured when `enableLogs` is set to true. * 'all' will log all origins. diff --git a/packages/core/src/js/replay/xhrUtils.ts b/packages/core/src/js/replay/xhrUtils.ts index 81a805024f..2c0d470b95 100644 --- a/packages/core/src/js/replay/xhrUtils.ts +++ b/packages/core/src/js/replay/xhrUtils.ts @@ -1,9 +1,22 @@ -import type { Breadcrumb, BreadcrumbHint, SentryWrappedXMLHttpRequest, XhrBreadcrumbHint } from '@sentry/core'; +import type { Breadcrumb, BreadcrumbHint } from '@sentry/core'; +// `XhrBreadcrumbHint` moved from the `@sentry/core` root entry to its `browser` +// subpath in JS v11 (the entry point was split into shared/browser/server). +import type { XhrBreadcrumbHint } from '@sentry/core/browser'; import { dropUndefinedKeys } from '@sentry/core'; import type { NetworkBody, RequestBody, ResolvedNetworkOptions } from './networkUtils'; +// `SentryWrappedXMLHttpRequest` / `SentryXhrData` were removed from `@sentry/core` in +// JS v11. Declare the subset this module relies on locally. +interface SentryXhrData { + request_headers: Record; +} + +interface SentryWrappedXMLHttpRequest { + __sentry_xhr_v3__?: SentryXhrData; +} + import { decodeUtf8, filterHeaders, diff --git a/packages/core/src/js/sdk.tsx b/packages/core/src/js/sdk.tsx index a5f00c4f05..b030856ee4 100644 --- a/packages/core/src/js/sdk.tsx +++ b/packages/core/src/js/sdk.tsx @@ -63,6 +63,11 @@ const DEFAULT_OPTIONS: ReactNativeOptions = { enableStallTracking: true, enableUserInteractionTracing: false, propagateTraceparent: false, + // Mobile keeps the classic (v8) trace lifecycle: spans are sent on their + // transaction rather than streamed. JS v11 defaults to `'stream'`; opting + // mobile into `'static'` preserves the transaction-envelope shape, the + // classic `spanToJSON`/`beforeSendSpan` behavior, and native-SDK ingestion. + traceLifecycle: 'static', }; /** diff --git a/packages/core/src/js/tracing/expoRouter.ts b/packages/core/src/js/tracing/expoRouter.ts index 2394919287..122ad64cf7 100644 --- a/packages/core/src/js/tracing/expoRouter.ts +++ b/packages/core/src/js/tracing/expoRouter.ts @@ -1,5 +1,7 @@ import { addBreadcrumb, getClient, SPAN_STATUS_ERROR, SPAN_STATUS_OK, startInactiveSpan } from '@sentry/core'; +import type { ReactNativeClientOptions } from '../options'; + import { SPAN_ORIGIN_AUTO_EXPO_ROUTER_NAVIGATION, SPAN_ORIGIN_AUTO_EXPO_ROUTER_PREFETCH } from './origin'; import { clearPendingExpoRouterNavigation, setPendingExpoRouterNavigation } from './pendingExpoRouterNavigation'; @@ -239,5 +241,5 @@ function serializeHref(href: unknown): string { } function isSendDefaultPiiEnabled(): boolean { - return getClient()?.getOptions()?.sendDefaultPii ?? false; + return (getClient()?.getOptions() as ReactNativeClientOptions | undefined)?.sendDefaultPii ?? false; } diff --git a/packages/core/src/js/tracing/expoRouterErrorBoundary.tsx b/packages/core/src/js/tracing/expoRouterErrorBoundary.tsx index 089f9ffab6..a679da4120 100644 --- a/packages/core/src/js/tracing/expoRouterErrorBoundary.tsx +++ b/packages/core/src/js/tracing/expoRouterErrorBoundary.tsx @@ -9,10 +9,12 @@ import { getClient, getRootSpan, SPAN_STATUS_ERROR, - spanToJSON, + spanToStaticSpanJSON, } from '@sentry/core'; import * as React from 'react'; +import type { ReactNativeClientOptions } from '../options'; + import { registerFeatureMarker } from '../utils/featureMarkers'; import { getCurrentExpoRouterRouteInfo } from './expoRouterStore'; @@ -110,7 +112,7 @@ export function wrapExpoRouterErrorBoundary

{ let childEndFramesPromise: Promise | undefined; if (!isRootSpan(span)) { const rootSpanId = getRootSpan(span).spanContext().spanId; - const spanTimestamp = spanToJSON(span).timestamp; + const spanTimestamp = spanToStaticSpanJSON(span).timestamp; if (spanTimestamp) { childEndFramesPromise = fetchNativeFrames(); _lastChildSpanEndFramesByRootSpan.set( @@ -195,7 +195,7 @@ export const nativeFramesIntegration = (): Integration => { `[${INTEGRATION_NAME}] Attached frame data to span ${spanId}: total=${totalFrames}, slow=${slowFrames}, frozen=${frozenFrames}`, ); - const spanJson = spanToJSON(span); + const spanJson = spanToStaticSpanJSON(span); if (spanJson.start_timestamp && spanJson.timestamp) { try { const delay = await fetchNativeFramesDelay(spanJson.start_timestamp, spanJson.timestamp); diff --git a/packages/core/src/js/tracing/integrations/stalltracking.ts b/packages/core/src/js/tracing/integrations/stalltracking.ts index 2453afd011..d5fd24b6b3 100644 --- a/packages/core/src/js/tracing/integrations/stalltracking.ts +++ b/packages/core/src/js/tracing/integrations/stalltracking.ts @@ -1,7 +1,7 @@ import type { Client, Integration, Measurements, MeasurementUnit, Span } from '@sentry/core'; import type { AppStateStatus } from 'react-native'; -import { debug, getRootSpan, spanIsSampled, spanToJSON, timestampInSeconds } from '@sentry/core'; +import { debug, getRootSpan, spanIsSampled, spanToStaticSpanJSON, timestampInSeconds } from '@sentry/core'; import { AppState } from 'react-native'; import { STALL_COUNT, STALL_LONGEST_TIME, STALL_TOTAL_TIME } from '../../measurements'; @@ -163,7 +163,7 @@ export const stallTrackingIntegration = ({ // The endTimestamp is always set, but type-wise it's optional // https://github.com/getsentry/sentry-javascript/blob/38bd57b0785c97c413f36f89ff931d927e469078/packages/core/src/tracing/sentrySpan.ts#L170 - const endTimestamp = spanToJSON(rootSpan).timestamp; + const endTimestamp = spanToStaticSpanJSON(rootSpan).timestamp; let statsOnFinish: StallMeasurements | undefined; if (isNearToNow(endTimestamp)) { @@ -232,7 +232,7 @@ export const stallTrackingIntegration = ({ const _onChildSpanEnd = (childSpan: Span): void => { const rootSpan = getRootSpan(childSpan); - const finalEndTimestamp = spanToJSON(childSpan).timestamp; + const finalEndTimestamp = spanToStaticSpanJSON(childSpan).timestamp; if (finalEndTimestamp) { _markSpanFinish(rootSpan, finalEndTimestamp); } diff --git a/packages/core/src/js/tracing/integrations/userInteraction.ts b/packages/core/src/js/tracing/integrations/userInteraction.ts index c1fbf3a48c..551c05af78 100644 --- a/packages/core/src/js/tracing/integrations/userInteraction.ts +++ b/packages/core/src/js/tracing/integrations/userInteraction.ts @@ -6,7 +6,7 @@ import { getClient, getCurrentScope, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - spanToJSON, + spanToStaticSpanJSON, } from '@sentry/core'; import type { ReactNativeClientOptions } from '../../options'; @@ -63,7 +63,7 @@ export const startUserInteractionSpan = (userInteractionId: { if (activeTransaction && activeTransactionIsNotInteraction) { debug.warn( `[${INTEGRATION_NAME}] Did not create ${op} transaction because active transaction ${ - spanToJSON(activeTransaction).description + spanToStaticSpanJSON(activeTransaction).description } exists on the scope.`, ); return undefined; @@ -72,12 +72,12 @@ export const startUserInteractionSpan = (userInteractionId: { const name = `${tracing.state.currentRoute}.${elementId}`; if ( activeTransaction && - spanToJSON(activeTransaction).description === name && - spanToJSON(activeTransaction).op === op + spanToStaticSpanJSON(activeTransaction).description === name && + spanToStaticSpanJSON(activeTransaction).op === op ) { debug.warn( `[${INTEGRATION_NAME}] Did not create ${op} transaction because it the same transaction ${ - spanToJSON(activeTransaction).description + spanToStaticSpanJSON(activeTransaction).description } already exists on the scope.`, ); return undefined; diff --git a/packages/core/src/js/tracing/onSpanEndUtils.ts b/packages/core/src/js/tracing/onSpanEndUtils.ts index aa61b08735..f8f7ef7dc0 100644 --- a/packages/core/src/js/tracing/onSpanEndUtils.ts +++ b/packages/core/src/js/tracing/onSpanEndUtils.ts @@ -1,7 +1,7 @@ import type { Client, Event, Span } from '@sentry/core'; import type { AppStateStatus } from 'react-native'; -import { debug, getSpanDescendants, SPAN_STATUS_ERROR, spanToJSON, timestampInSeconds } from '@sentry/core'; +import { debug, getSpanDescendants, SPAN_STATUS_ERROR, spanToStaticSpanJSON, timestampInSeconds } from '@sentry/core'; import { AppState, Platform } from 'react-native'; import { isRootSpan, isSentrySpan } from '../utils/span'; @@ -38,7 +38,7 @@ export function markRootSpanForDiscard(span: Span, reason: SentryDiscardReason): * Returns the SDK-side discard reason recorded on a root span, if any. */ export function getRootSpanDiscardReason(span: Span): SentryDiscardReason | undefined { - const value = spanToJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]; + const value = spanToStaticSpanJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]; return typeof value === 'string' ? (value as SentryDiscardReason) : undefined; } @@ -81,8 +81,8 @@ export const adjustTransactionDuration = (client: Client, span: Span, maxDuratio } unsubscribe(); - const endTimestamp = spanToJSON(span).timestamp; - const startTimestamp = spanToJSON(span).start_timestamp; + const endTimestamp = spanToStaticSpanJSON(span).timestamp; + const startTimestamp = spanToStaticSpanJSON(span).start_timestamp; if (!endTimestamp || !startTimestamp) { return; } @@ -106,8 +106,8 @@ function getMeaningfulChildSpans(span: Span): Span[] { return children.filter( child => child.spanContext().spanId !== span.spanContext().spanId && - spanToJSON(child).op !== 'ui.load.initial_display' && - spanToJSON(child).op !== 'navigation.processing', + spanToStaticSpanJSON(child).op !== 'ui.load.initial_display' && + spanToStaticSpanJSON(child).op !== 'navigation.processing', ); } @@ -157,7 +157,7 @@ export const ignoreEmptyBackNavigation = (client: Client | undefined, span: Span client, span, // Only discard if route has been seen before - span => spanToJSON(span).data?.['route.has_been_seen'] === true, + span => spanToStaticSpanJSON(span).data?.['route.has_been_seen'] === true, // Log message and mark the span for discard via the event processor. span => { debug.log( @@ -190,7 +190,7 @@ export const ignoreEmptyRouteChangeTransactions = ( // 2. No route information was set // 3. Still being tracked (state listener never called) span => { - const spanJSON = spanToJSON(span); + const spanJSON = spanToStaticSpanJSON(span); return ( spanJSON.description === defaultNavigationSpanName && !spanJSON.data?.['route.name'] && isSpanStillTracked() ); @@ -226,7 +226,7 @@ export const onlySampleIfChildSpans = (client: Client, span: Span): void => { if (children.length <= 1) { // Span always has at lest one child, itself - debug.log(`Not sampling as ${spanToJSON(span).op} transaction has no child spans.`); + debug.log(`Not sampling as ${spanToStaticSpanJSON(span).op} transaction has no child spans.`); markRootSpanForDiscard(span, 'no_child_spans'); } }); @@ -256,7 +256,9 @@ export const cancelInBackground = (client: Client, span: Span): void => { clearTimeout(inactiveTimeout); inactiveTimeout = undefined; } - debug.log(`Setting ${spanToJSON(span).op} transaction to cancelled because the app is in the background.`); + debug.log( + `Setting ${spanToStaticSpanJSON(span).op} transaction to cancelled because the app is in the background.`, + ); // End still-recording http.client children at the time the app left // the foreground, not when the deferred timer fires. On iOS, the JS @@ -266,7 +268,7 @@ export const cancelInBackground = (client: Client, span: Span): void => { const childEndTimestamp = leftForegroundTimestamp ?? timestampInSeconds(); const children = getSpanDescendants(span); for (const child of children) { - if (child !== span && child.isRecording() && spanToJSON(child).op === 'http.client') { + if (child !== span && child.isRecording() && spanToStaticSpanJSON(child).op === 'http.client') { child.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' }); child.end(childEndTimestamp); } @@ -305,7 +307,7 @@ export const cancelInBackground = (client: Client, span: Span): void => { return; } unsubscribe(); - debug.log(`Removing AppState listener for ${spanToJSON(span).op} transaction.`); + debug.log(`Removing AppState listener for ${spanToStaticSpanJSON(span).op} transaction.`); if (inactiveTimeout !== undefined) { clearTimeout(inactiveTimeout); inactiveTimeout = undefined; diff --git a/packages/core/src/js/tracing/reactnativenavigation.ts b/packages/core/src/js/tracing/reactnativenavigation.ts index e93909ce8d..eafe79fe95 100644 --- a/packages/core/src/js/tracing/reactnativenavigation.ts +++ b/packages/core/src/js/tracing/reactnativenavigation.ts @@ -5,8 +5,8 @@ import { getClient, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - spanToJSON, + SENTRY_SEGMENT_NAME_SOURCE as SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + spanToStaticSpanJSON, } from '@sentry/core'; import type { EmitterSubscription } from '../utils/rnlibrariesinterface'; @@ -175,7 +175,7 @@ export const reactNativeNavigationIntegration = ({ const routeHasBeenSeen = recentComponentIds.includes(event.componentId); - if (spanToJSON(latestNavigationSpan).description === DEFAULT_NAVIGATION_SPAN_NAME) { + if (spanToStaticSpanJSON(latestNavigationSpan).description === DEFAULT_NAVIGATION_SPAN_NAME) { latestNavigationSpan.updateName(event.componentName); } latestNavigationSpan.setAttributes({ diff --git a/packages/core/src/js/tracing/reactnavigation.ts b/packages/core/src/js/tracing/reactnavigation.ts index 507773a914..8b4fef0728 100644 --- a/packages/core/src/js/tracing/reactnavigation.ts +++ b/packages/core/src/js/tracing/reactnavigation.ts @@ -9,11 +9,12 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_OK, - spanToJSON, + spanToStaticSpanJSON, startInactiveSpan, timestampInSeconds, } from '@sentry/core'; +import type { ReactNativeClientOptions } from '../options'; import type { UnsafeAction } from '../vendor/react-navigation/types'; import type { PendingDeepLink } from './pendingDeepLink'; import type { ReactNativeTracingIntegration } from './reactnativetracing'; @@ -655,7 +656,7 @@ export const reactNavigationIntegration = ({ navigationProcessingSpan = startInactiveSpan({ op: 'navigation.processing', name: 'Navigation dispatch to navigation cancelled or screen mounted', - startTime: spanToJSON(latestNavigationSpan).start_timestamp, + startTime: spanToStaticSpanJSON(latestNavigationSpan).start_timestamp, }); navigationProcessingSpan.setAttribute( SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, @@ -813,7 +814,7 @@ export const reactNavigationIntegration = ({ if (!latestNavigationSpanNameCustomized) { latestNavigationSpan.updateName(routeName); } - const sendDefaultPii = getClient()?.getOptions()?.sendDefaultPii ?? false; + const sendDefaultPii = (getClient()?.getOptions() as ReactNativeClientOptions | undefined)?.sendDefaultPii ?? false; latestNavigationSpan.setAttributes({ 'route.name': routeName, 'route.key': route.key, @@ -945,7 +946,7 @@ function tagSpanWithDeepLink(span: Span, link: PendingDeepLink): boolean { } taggedDeepLinkSpans.add(span); - const sendDefaultPii = getClient()?.getOptions()?.sendDefaultPii ?? false; + const sendDefaultPii = (getClient()?.getOptions() as ReactNativeClientOptions | undefined)?.sendDefaultPii ?? false; const url = sendDefaultPii ? link.url : sanitizeDeepLinkUrl(link.url); span.setAttributes({ @@ -961,7 +962,7 @@ function tagSpanWithDeepLink(span: Span, link: PendingDeepLink): boolean { /** Returns true if the span is still recording (has not been ended). */ function isSpanRecording(span: Span): boolean { - return spanToJSON(span).timestamp === undefined; + return spanToStaticSpanJSON(span).timestamp === undefined; } /** diff --git a/packages/core/src/js/tracing/semanticAttributes.ts b/packages/core/src/js/tracing/semanticAttributes.ts index 1e7d3b4474..11592dc1fe 100644 --- a/packages/core/src/js/tracing/semanticAttributes.ts +++ b/packages/core/src/js/tracing/semanticAttributes.ts @@ -1,5 +1,7 @@ export { - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + // `SEMANTIC_ATTRIBUTE_SENTRY_SOURCE` (`sentry.source`) was removed in JS v11. + // Its replacement is `SENTRY_SEGMENT_NAME_SOURCE` (`sentry.segment.name.source`). + SENTRY_SEGMENT_NAME_SOURCE as SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, diff --git a/packages/core/src/js/tracing/span.ts b/packages/core/src/js/tracing/span.ts index 7b9188224a..2ed39e77e6 100644 --- a/packages/core/src/js/tracing/span.ts +++ b/packages/core/src/js/tracing/span.ts @@ -1,6 +1,7 @@ import type { Client, Scope, Span, SpanJSON, StartSpanOptions } from '@sentry/core'; import { + _INTERNAL_setSpanForScope, debug, generateTraceId, getActiveSpan, @@ -10,9 +11,11 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, SPAN_STATUS_ERROR, - spanToJSON, - startIdleSpan as coreStartIdleSpan, + spanToStaticSpanJSON, } from '@sentry/core'; +// `startIdleSpan` moved from the `@sentry/core` root entry to its `browser` +// subpath in JS v11 (the entry point was split into shared/browser/server). +import { startIdleSpan as coreStartIdleSpan } from '@sentry/core/browser'; import { AppState, Platform } from 'react-native'; import { isRootSpan } from '../utils/span'; @@ -70,14 +73,14 @@ export const startIdleNavigationSpan = ( if (isActiveSpanInteraction && isAppRestart) { debug.log( `[startIdleNavigationSpan] Not canceling ${ - spanToJSON(activeSpan).op + spanToStaticSpanJSON(activeSpan).op } transaction because navigation is from app restart - preserving error context.`, ); // Don't end the span - it will timeout naturally and remains available for error/replay processing } else if (isActiveSpanInteraction) { debug.log( `[startIdleNavigationSpan] Canceling ${ - spanToJSON(activeSpan).op + spanToStaticSpanJSON(activeSpan).op } transaction because of a new navigation root span.`, ); activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' }); @@ -150,21 +153,21 @@ export function getDefaultIdleNavigationSpanOptions(): StartSpanOptions { * Checks if the span is a Sentry User Interaction span. */ export function isSentryInteractionSpan(span: Span): boolean { - return [SPAN_ORIGIN_AUTO_INTERACTION, SPAN_ORIGIN_MANUAL_INTERACTION].includes(spanToJSON(span).origin || ''); + return [SPAN_ORIGIN_AUTO_INTERACTION, SPAN_ORIGIN_MANUAL_INTERACTION].includes( + spanToStaticSpanJSON(span).origin || '', + ); } -export const SCOPE_SPAN_FIELD = '_sentrySpan'; - -export type ScopeWithMaybeSpan = Scope & { - [SCOPE_SPAN_FIELD]?: Span; -}; - /** * Removes the active span from the scope. + * + * JS v11 no longer stores the active span on a `_sentrySpan` scope property; it + * keeps a `WeakRef` under `scope.refs.span`. Deleting the old field is a no-op, + * so we must clear via the official helper — otherwise the previous span stays + * active and becomes the parent of the next root (navigation/interaction) span. */ -export function clearActiveSpanFromScope(scope: ScopeWithMaybeSpan): void { - // oxlint-disable-next-line typescript-eslint(no-dynamic-delete) - delete scope[SCOPE_SPAN_FIELD]; +export function clearActiveSpanFromScope(scope: Scope): void { + _INTERNAL_setSpanForScope(scope, undefined); } /** @@ -172,7 +175,7 @@ export function clearActiveSpanFromScope(scope: ScopeWithMaybeSpan): void { */ export function addDefaultOpForSpanFrom(client: Client): void { client.on('spanStart', (span: Span) => { - if (!spanToJSON(span).op) { + if (!spanToStaticSpanJSON(span).op) { span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'default'); } }); @@ -188,7 +191,7 @@ export const SPAN_THREAD_NAME_JAVASCRIPT = 'javascript'; */ export function addThreadInfoToSpan(client: Client): void { client.on('spanStart', (span: Span) => { - if (!spanToJSON(span).data?.[SPAN_THREAD_NAME]) { + if (!spanToStaticSpanJSON(span).data?.[SPAN_THREAD_NAME]) { span.setAttribute(SPAN_THREAD_NAME, SPAN_THREAD_NAME_JAVASCRIPT); } }); diff --git a/packages/core/src/js/tracing/timetodisplay.tsx b/packages/core/src/js/tracing/timetodisplay.tsx index 112b90afa1..90544b4a17 100644 --- a/packages/core/src/js/tracing/timetodisplay.tsx +++ b/packages/core/src/js/tracing/timetodisplay.tsx @@ -10,7 +10,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR, SPAN_STATUS_OK, - spanToJSON, + spanToStaticSpanJSON, startInactiveSpan, timestampInSeconds, } from '@sentry/core'; @@ -82,7 +82,7 @@ export function TimeToInitialDisplay(props: TimeToDisplayProps): React.ReactElem manualInitialDisplaySpans.set(activeSpan, true); } - const parentSpanId = activeSpan && spanToJSON(activeSpan).span_id; + const parentSpanId = activeSpan && spanToStaticSpanJSON(activeSpan).span_id; const initialDisplay = useCoordinatedDisplay('ttid', parentSpanId, props); return ( @@ -101,7 +101,7 @@ export function TimeToInitialDisplay(props: TimeToDisplayProps): React.ReactElem */ export function TimeToFullDisplay(props: TimeToDisplayProps): React.ReactElement { const activeSpan = getActiveSpan(); - const parentSpanId = activeSpan && spanToJSON(activeSpan).span_id; + const parentSpanId = activeSpan && spanToStaticSpanJSON(activeSpan).span_id; const fullDisplay = useCoordinatedDisplay('ttfd', parentSpanId, props); return ( @@ -226,7 +226,9 @@ export function startTimeToInitialDisplaySpan( return undefined; } - const existingSpan = getSpanDescendants(activeSpan).find(span => spanToJSON(span).op === 'ui.load.initial_display'); + const existingSpan = getSpanDescendants(activeSpan).find( + span => spanToStaticSpanJSON(span).op === 'ui.load.initial_display', + ); if (existingSpan) { debug.log('[TimeToDisplay] Found existing ui.load.initial_display span.'); return existingSpan; @@ -235,7 +237,7 @@ export function startTimeToInitialDisplaySpan( const initialDisplaySpan = startInactiveSpan({ op: 'ui.load.initial_display', name: 'Time To Initial Display', - startTime: spanToJSON(activeSpan).start_timestamp, + startTime: spanToStaticSpanJSON(activeSpan).start_timestamp, ...options, }); @@ -284,13 +286,13 @@ export function startTimeToFullDisplaySpan( const descendantSpans = getSpanDescendants(activeSpan); - const initialDisplaySpan = descendantSpans.find(span => spanToJSON(span).op === 'ui.load.initial_display'); + const initialDisplaySpan = descendantSpans.find(span => spanToStaticSpanJSON(span).op === 'ui.load.initial_display'); if (!initialDisplaySpan) { debug.warn('[TimeToDisplay] No initial display span found to attach ui.load.full_display to.'); return undefined; } - const existingSpan = descendantSpans.find(span => spanToJSON(span).op === 'ui.load.full_display'); + const existingSpan = descendantSpans.find(span => spanToStaticSpanJSON(span).op === 'ui.load.full_display'); if (existingSpan) { debug.log('[TimeToDisplay] Found existing ui.load.full_display span.'); return existingSpan; @@ -299,7 +301,7 @@ export function startTimeToFullDisplaySpan( const fullDisplaySpan = startInactiveSpan({ op: 'ui.load.full_display', name: 'Time To Full Display', - startTime: spanToJSON(initialDisplaySpan).start_timestamp, + startTime: spanToStaticSpanJSON(initialDisplaySpan).start_timestamp, ...options, }); if (!fullDisplaySpan) { @@ -314,12 +316,12 @@ export function startTimeToFullDisplaySpan( }); const timeout = setTimeout(() => { - if (spanToJSON(fullDisplaySpan).timestamp) { + if (spanToStaticSpanJSON(fullDisplaySpan).timestamp) { return; } fullDisplaySpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }); - const fullDisplayEndTimestamp = spanToJSON(initialDisplaySpan).timestamp; + const fullDisplayEndTimestamp = spanToStaticSpanJSON(initialDisplaySpan).timestamp; captureEndFramesAndAttachToSpan(fullDisplaySpan, fullDisplayEndTimestamp) .then(() => { debug.log(`[TimeToDisplay] span ${fullDisplaySpan.spanContext().spanId} updated with frame data.`); @@ -330,7 +332,7 @@ export function startTimeToFullDisplaySpan( debug.warn( `[TimeToDisplay] Failed to capture end frames for full display span (${fullDisplaySpan.spanContext().spanId}).`, ); - fullDisplaySpan.end(spanToJSON(initialDisplaySpan).timestamp); + fullDisplaySpan.end(spanToStaticSpanJSON(initialDisplaySpan).timestamp); setSpanDurationAsMeasurement('time_to_full_display', fullDisplaySpan); }); @@ -377,13 +379,13 @@ export function updateInitialDisplaySpan( return; } - if (spanToJSON(span).parent_span_id !== spanToJSON(activeSpan).span_id) { + if (spanToStaticSpanJSON(span).parent_span_id !== spanToStaticSpanJSON(activeSpan).span_id) { debug.warn('[TimeToDisplay] Initial display span is not a child of current active span.'); return; } - if (spanToJSON(span).timestamp) { - debug.warn(`[TimeToDisplay] ${spanToJSON(span).description} span already ended.`); + if (spanToStaticSpanJSON(span).timestamp) { + debug.warn(`[TimeToDisplay] ${spanToStaticSpanJSON(span).description} span already ended.`); return; } @@ -391,7 +393,9 @@ export function updateInitialDisplaySpan( .then(() => { span.end(frameTimestampSeconds); span.setStatus({ code: SPAN_STATUS_OK }); - debug.log(`[TimeToDisplay] ${spanToJSON(span).description} span updated with end timestamp and frame data.`); + debug.log( + `[TimeToDisplay] ${spanToStaticSpanJSON(span).description} span updated with end timestamp and frame data.`, + ); if (fullDisplayBeforeInitialDisplay.has(activeSpan)) { fullDisplayBeforeInitialDisplay.delete(activeSpan); @@ -443,7 +447,7 @@ export function reportFullyDisplayed(): void { return; } const rootSpan = getRootSpan(activeSpan); - const rootSpanId = spanToJSON(rootSpan).span_id; + const rootSpanId = spanToStaticSpanJSON(rootSpan).span_id; if (rootSpanId && !_imperativeTtfdTimestamps.has(rootSpanId)) { if (_imperativeTtfdTimestamps.size >= MAX_IMPERATIVE_TTFD_ENTRIES) { const oldestKey = _imperativeTtfdTimestamps.keys().next().value; @@ -476,8 +480,9 @@ function updateFullDisplaySpan(frameTimestampSeconds: number, passedInitialDispl const existingInitialDisplaySpan = passedInitialDisplaySpan || - getSpanDescendants(activeSpan).find(span => spanToJSON(span).op === 'ui.load.initial_display'); - const initialDisplayEndTimestamp = existingInitialDisplaySpan && spanToJSON(existingInitialDisplaySpan).timestamp; + getSpanDescendants(activeSpan).find(span => spanToStaticSpanJSON(span).op === 'ui.load.initial_display'); + const initialDisplayEndTimestamp = + existingInitialDisplaySpan && spanToStaticSpanJSON(existingInitialDisplaySpan).timestamp; if (!initialDisplayEndTimestamp) { fullDisplayBeforeInitialDisplay.set(activeSpan, true); debug.warn( @@ -494,7 +499,7 @@ function updateFullDisplaySpan(frameTimestampSeconds: number, passedInitialDispl return; } - const spanJSON = spanToJSON(span); + const spanJSON = spanToStaticSpanJSON(span); if (spanJSON.timestamp) { debug.warn(`[TimeToDisplay] ${spanJSON.description} (${spanJSON.span_id}) span already ended.`); return; @@ -678,10 +683,10 @@ async function captureEndFramesAndAttachToSpan(span: Span, spanEndTimestampSecon attachFrameDataToSpan(span, frameData.startFrames, endFrames); - const spanStartTimestamp = spanToJSON(span).start_timestamp; + const spanStartTimestamp = spanToStaticSpanJSON(span).start_timestamp; if (spanStartTimestamp) { try { - const endTimestamp = spanEndTimestampSeconds || spanToJSON(span).timestamp || Date.now() / 1000; + const endTimestamp = spanEndTimestampSeconds || spanToStaticSpanJSON(span).timestamp || Date.now() / 1000; const framesDelay = await Promise.race([ NATIVE.fetchNativeFramesDelay(spanStartTimestamp, endTimestamp), new Promise(resolve => setTimeout(() => resolve(null), FETCH_FRAMES_TIMEOUT_MS)), diff --git a/packages/core/src/js/tracing/utils.ts b/packages/core/src/js/tracing/utils.ts index 57f63cf336..19a6d43eeb 100644 --- a/packages/core/src/js/tracing/utils.ts +++ b/packages/core/src/js/tracing/utils.ts @@ -19,7 +19,7 @@ import { setMeasurement, SPAN_STATUS_ERROR, SPAN_STATUS_OK, - spanToJSON, + spanToStaticSpanJSON, startInactiveSpan, timestampInSeconds, uuid4, @@ -60,7 +60,7 @@ export function isNearToNow(timestamp: number | undefined): boolean { * Uses `setMeasurement` function from @sentry/core. */ export function setSpanDurationAsMeasurement(name: string, span: Span): void { - const { timestamp: spanEnd, start_timestamp: spanStart } = spanToJSON(span); + const { timestamp: spanEnd, start_timestamp: spanStart } = spanToStaticSpanJSON(span); if (!spanEnd || !spanStart) { return; } @@ -73,7 +73,7 @@ export function setSpanDurationAsMeasurement(name: string, span: Span): void { * Uses `setMeasurement` function from @sentry/core. */ export function setSpanDurationAsMeasurementOnSpan(name: string, span: Span, on: Span): void { - const { timestamp: spanEnd, start_timestamp: spanStart } = spanToJSON(span); + const { timestamp: spanEnd, start_timestamp: spanStart } = spanToStaticSpanJSON(span); if (!spanEnd || !spanStart) { return; } @@ -104,7 +104,7 @@ export function setSpanMeasurement(span: Span, key: string, value: number, unit: */ export function getLatestChildSpanEndTimestamp(span: Span): number | undefined { const childEndTimestamps = getSpanDescendants(span) - .map(span => spanToJSON(span).timestamp) + .map(span => spanToStaticSpanJSON(span).timestamp) .filter(timestamp => !!timestamp) as number[]; return childEndTimestamps.length ? Math.max(...childEndTimestamps) : undefined; diff --git a/packages/core/test/aiExports.test.ts b/packages/core/test/aiExports.test.ts deleted file mode 100644 index 73d9f36d77..0000000000 --- a/packages/core/test/aiExports.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as Sentry from '../src/js'; - -describe('AI SDK manual instrumentation re-exports', () => { - test.each([ - 'instrumentOpenAiClient', - 'instrumentAnthropicAiClient', - 'instrumentGoogleGenAIClient', - 'createLangChainCallbackHandler', - 'instrumentStateGraph', - 'instrumentLangGraph', - 'instrumentStateGraphCompile', - ])('re-exports %s from @sentry/core', name => { - expect(typeof (Sentry as Record)[name]).toBe('function'); - }); - - test('deprecated instrumentLangGraph is an alias of instrumentStateGraph', () => { - expect(Sentry.instrumentLangGraph).toBe(Sentry.instrumentStateGraph); - }); -}); diff --git a/packages/core/test/feedback.test.ts b/packages/core/test/feedback.test.ts index 37a2c37f33..a76dff6c51 100644 --- a/packages/core/test/feedback.test.ts +++ b/packages/core/test/feedback.test.ts @@ -12,11 +12,12 @@ import { } from '@sentry/core'; import { getDefaultTestClientOptions, TestClient } from './mocks/client'; +import { clearAllScopes } from './testutils'; describe('captureFeedback', () => { beforeEach(() => { getCurrentScope().setClient(undefined); - getCurrentScope().clear(); + clearAllScopes(); }); test('it works without a client', () => { diff --git a/packages/core/test/integrations/expocontext.test.ts b/packages/core/test/integrations/expocontext.test.ts index e12fa55aee..7610d6dc98 100644 --- a/packages/core/test/integrations/expocontext.test.ts +++ b/packages/core/test/integrations/expocontext.test.ts @@ -1,4 +1,4 @@ -import { type Client, type Event, getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; +import { type Client, type Event } from '@sentry/core'; import type { ExpoUpdates } from '../../src/js/utils/expoglobalobject'; @@ -12,6 +12,7 @@ import { getExpoDevice } from '../../src/js/utils/expomodules'; import * as expoModules from '../../src/js/utils/expomodules'; import { setupTestClient, TestClient } from '../mocks/client'; import { NATIVE } from '../mockWrapper'; +import { clearAllScopes } from '../testutils'; jest.mock('../../src/js/wrapper', () => jest.requireActual('../mockWrapper')); jest.mock('../../src/js/utils/expomodules'); @@ -20,9 +21,7 @@ describe('Expo Context Integration', () => { afterEach(() => { jest.clearAllMocks(); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); }); describe('Set Native Context after init()', () => { diff --git a/packages/core/test/integrations/expoupdateslistener.integration.test.ts b/packages/core/test/integrations/expoupdateslistener.integration.test.ts index b29ff63f4b..054b2e41bb 100644 --- a/packages/core/test/integrations/expoupdateslistener.integration.test.ts +++ b/packages/core/test/integrations/expoupdateslistener.integration.test.ts @@ -1,10 +1,9 @@ import type { TransactionEvent } from '@sentry/core'; -import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; - import { handleSpanLifecycle } from '../../src/js/integrations/expoupdateslistener'; import { SPAN_ORIGIN_AUTO_EXPO_UPDATES } from '../../src/js/tracing/origin'; import { setupTestClient } from '../mocks/client'; +import { clearAllScopes } from '../testutils'; jest.mock('../../src/js/wrapper', () => jest.requireActual('../mockWrapper')); @@ -21,9 +20,7 @@ describe('ExpoUpdatesListener Integration (real spans)', () => { }; afterEach(() => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); }); it('check span produces a transaction with correct op, origin, and ok status', () => { @@ -71,7 +68,10 @@ describe('ExpoUpdatesListener Integration (real spans)', () => { const event = client.event as TransactionEvent | undefined; expect(event).toBeDefined(); expect(event?.contexts?.trace?.op).toBe('app.update.check'); - expect(event?.contexts?.trace?.status).toBe('Network failed'); + // JS v11 surfaces only canonical span status messages; a non-canonical + // message like the raw error text ('Network failed') reports as + // 'internal_error'. The failure is still recorded as an error status. + expect(event?.contexts?.trace?.status).toBe('internal_error'); }); it('download span produces a transaction with correct op and update id', () => { diff --git a/packages/core/test/integrations/expoupdateslistener.test.ts b/packages/core/test/integrations/expoupdateslistener.test.ts index 243cf619f9..b85109162a 100644 --- a/packages/core/test/integrations/expoupdateslistener.test.ts +++ b/packages/core/test/integrations/expoupdateslistener.test.ts @@ -1,13 +1,4 @@ -import { - addBreadcrumb, - getCurrentScope, - getGlobalScope, - getIsolationScope, - type Span, - SPAN_STATUS_ERROR, - SPAN_STATUS_OK, - startInactiveSpan, -} from '@sentry/core'; +import { addBreadcrumb, type Span, SPAN_STATUS_ERROR, SPAN_STATUS_OK, startInactiveSpan } from '@sentry/core'; import { expoUpdatesListenerIntegration, @@ -17,6 +8,7 @@ import { import { SPAN_ORIGIN_AUTO_EXPO_UPDATES } from '../../src/js/tracing/origin'; import * as environment from '../../src/js/utils/environment'; import { setupTestClient } from '../mocks/client'; +import { clearAllScopes } from '../testutils'; jest.mock('../../src/js/wrapper', () => jest.requireActual('../mockWrapper')); jest.mock('@sentry/core', () => { @@ -64,9 +56,7 @@ describe('ExpoUpdatesListener Integration', () => { isRestarting: false, }; - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); }); describe('setup', () => { diff --git a/packages/core/test/integrations/featureFlags.test.ts b/packages/core/test/integrations/featureFlags.test.ts index a30fc6b261..2587ef62b0 100644 --- a/packages/core/test/integrations/featureFlags.test.ts +++ b/packages/core/test/integrations/featureFlags.test.ts @@ -4,6 +4,7 @@ import { getCurrentScope } from '@sentry/core'; import { featureFlagsIntegration } from '../../src/js/integrations/featureFlags'; import { NATIVE } from '../../src/js/wrapper'; +import { clearAllScopes } from '../testutils'; jest.mock('../../src/js/wrapper'); @@ -12,12 +13,12 @@ describe('Feature Flags Integration', () => { beforeEach(() => { integration = featureFlagsIntegration(); - getCurrentScope().clear(); + clearAllScopes(); }); afterEach(() => { jest.clearAllMocks(); - getCurrentScope().clear(); + clearAllScopes(); }); it('registers under the shared FeatureFlags name', () => { diff --git a/packages/core/test/integrations/turboModuleContext.spans.test.ts b/packages/core/test/integrations/turboModuleContext.spans.test.ts index 3212bd39ea..02d54f0bd7 100644 --- a/packages/core/test/integrations/turboModuleContext.spans.test.ts +++ b/packages/core/test/integrations/turboModuleContext.spans.test.ts @@ -14,21 +14,14 @@ */ import type { Event, Span, TransactionEvent } from '@sentry/core'; -import { - getCurrentScope, - getGlobalScope, - getIsolationScope, - setCurrentClient, - spanToJSON, - startNewTrace, - startSpanManual, -} from '@sentry/core'; +import { setCurrentClient, spanToStaticSpanJSON, startNewTrace, startSpanManual } from '@sentry/core'; import { turboModuleContextIntegration } from '../../src/js/integrations/turboModuleContext'; import { _resetTurboModuleAggregator } from '../../src/js/turbomodule/turboModuleAggregator'; import { _resetTurboModuleTracker } from '../../src/js/turbomodule/turboModuleTracker'; import { _resetWrappedModules } from '../../src/js/turbomodule/wrapTurboModule'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; +import { clearAllScopes } from '../testutils'; const SYNC_CALL_COUNT = 5; @@ -37,9 +30,7 @@ describe('turboModuleContextIntegration with real spans', () => { _resetTurboModuleTracker(); _resetTurboModuleAggregator(); _resetWrappedModules(); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); }); afterEach(() => { @@ -90,7 +81,7 @@ describe('turboModuleContextIntegration with real spans', () => { // `SentrySpan.end()` emits `spanEnd` — where the integration writes the // attributes — before it seals the span, and spans created through the // core span API are never sealed at all, so this read sees them. - onSpanData = (spanToJSON(span).data ?? {}) as Record; + onSpanData = (spanToStaticSpanJSON(span).data ?? {}) as Record; }); }); const transaction = (await sentEvent) as TransactionEvent; diff --git a/packages/core/test/metrics.test.ts b/packages/core/test/metrics.test.ts index e14ef503e8..3f3f4db0c3 100644 --- a/packages/core/test/metrics.test.ts +++ b/packages/core/test/metrics.test.ts @@ -24,13 +24,15 @@ describe('Metrics', () => { }); describe('beforeSendMetric', () => { - it('is called when enableMetrics is true and a metric is sent', async () => { + // JS v11 removed the `enableMetrics` option: metrics are captured whenever a + // metric API is used, so there is no longer a flag that suppresses them. The + // previous `enableMetrics: false` suppression test was dropped accordingly. + it('is called when a metric is sent', async () => { const beforeSendMetric = jest.fn(metric => metric); const client = new ReactNativeClient({ ...getDefaultTestClientOptions({ dsn: EXAMPLE_DSN, - enableMetrics: true, beforeSendMetric, }), }); @@ -45,27 +47,6 @@ describe('Metrics', () => { expect(beforeSendMetric).toHaveBeenCalled(); }); - it('is not called when enableMetrics is false', async () => { - const beforeSendMetric = jest.fn(metric => metric); - - const client = new ReactNativeClient({ - ...getDefaultTestClientOptions({ - dsn: EXAMPLE_DSN, - enableMetrics: false, - beforeSendMetric, - }), - }); - - setCurrentClient(client); - client.init(); - - // Send a metric - metrics.count('test_metric', 1); - - jest.advanceTimersByTime(10000); - expect(beforeSendMetric).not.toHaveBeenCalled(); - }); - it('is called when enableMetrics is undefined (metrics are enabled by default)', async () => { const beforeSendMetric = jest.fn(metric => metric); diff --git a/packages/core/test/mocks/client.ts b/packages/core/test/mocks/client.ts index 8595bea471..c8ddd261db 100644 --- a/packages/core/test/mocks/client.ts +++ b/packages/core/test/mocks/client.ts @@ -1,24 +1,18 @@ import type { Event, EventHint, Integration, Outcome, ParameterizedString, Session, SeverityLevel } from '@sentry/core'; -import { - Client, - createTransport, - getCurrentScope, - getGlobalScope, - getIsolationScope, - initAndBind, - resolvedSyncPromise, - setCurrentClient, -} from '@sentry/core'; +import { Client, createTransport, initAndBind, resolvedSyncPromise, setCurrentClient } from '@sentry/core'; import type { ReactNativeClientOptions } from '../../src/js/options'; +import { clearAllScopes } from '../testutils'; + export function getDefaultTestClientOptions(options: Partial = {}): TestClientOptions { return { dsn: 'https://1234@some-domain.com/4505526893805568', enabled: true, integrations: [], sendClientReports: true, + traceLifecycle: 'static', transport: () => createTransport( { @@ -106,9 +100,7 @@ export function init(options: TestClientOptions): void { } export function setupTestClient(options: Partial = {}): TestClient { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const finalOptions = getDefaultTestClientOptions({ tracesSampleRate: 1.0, ...options }); const client = new TestClient(finalOptions); diff --git a/packages/core/test/sdk.test.ts b/packages/core/test/sdk.test.ts index f22052ace2..b604b0d38c 100644 --- a/packages/core/test/sdk.test.ts +++ b/packages/core/test/sdk.test.ts @@ -1096,7 +1096,9 @@ describe('Tests the SDK functionality', () => { it('adds react default integrations', () => { init({}); - expectIntegration('InboundFilters'); + // `InboundFilters` was renamed to `EventFilters` in JS v11 (the integration's + // internal `name`). RN still re-exports the factory as `inboundFiltersIntegration`. + expectIntegration('EventFilters'); expectIntegration('FunctionToString'); expectIntegration('Breadcrumbs'); expectIntegration('Dedupe'); diff --git a/packages/core/test/testutils.ts b/packages/core/test/testutils.ts index dc7eda9864..97afd94e68 100644 --- a/packages/core/test/testutils.ts +++ b/packages/core/test/testutils.ts @@ -1,6 +1,13 @@ -import type { Session, Transport, UserFeedback } from '@sentry/core'; +import type { Scope, Session, Transport, UserFeedback } from '@sentry/core'; -import { rejectedSyncPromise } from '@sentry/core'; +import { + _INTERNAL_setSpanForScope, + generateTraceId, + getCurrentScope, + getGlobalScope, + getIsolationScope, + rejectedSyncPromise, +} from '@sentry/core'; export type MockInterface = { [K in keyof T]: T[K] extends (...args: infer A) => infer B ? jest.Mock : T[K]; @@ -73,3 +80,60 @@ export const secondAgoTimestampMs = (): number => { export const secondInFutureTimestampMs = (): number => { return new Date(Date.now() + 1000).getTime(); }; + +/** + * Resets a single {@link Scope} to its pristine state. + * + * JS v11 removed `Scope.clear()` — MIGRATION.md advises re-initializing the SDK + * or running code in a fresh scope via `withScope`/`withIsolationScope`, neither + * of which fits the suite's shared `beforeEach` reset of the current, isolation + * and global scopes. This mirrors exactly what the removed `clear()` reset: + * breadcrumbs, tags, attributes, extra, user, contexts, level, transaction name, + * fingerprint, session, conversation id, active span, attachments and the + * propagation context. As in the original, the client and event processors are + * intentionally left intact. Reaching into the `_`-prefixed fields is deliberate + * and confined to test setup. + */ +export function resetScope(scope: Scope): void { + const internal = scope as unknown as { + _breadcrumbs: unknown[]; + _tags: Record; + _attributes: Record; + _extra: Record; + _user: Record; + _contexts: Record; + _level: unknown; + _transactionName: unknown; + _fingerprint: unknown; + _session: unknown; + _conversationId: unknown; + _attachments: unknown[]; + }; + + internal._breadcrumbs = []; + internal._tags = {}; + internal._attributes = {}; + internal._extra = {}; + internal._user = {}; + internal._contexts = {}; + internal._level = undefined; + internal._transactionName = undefined; + internal._fingerprint = undefined; + internal._session = undefined; + internal._conversationId = undefined; + internal._attachments = []; + _INTERNAL_setSpanForScope(scope, undefined); + scope.setPropagationContext({ traceId: generateTraceId(), sampleRand: Math.random() }); +} + +/** + * Resets the current, isolation and global Sentry scopes to a pristine state + * between tests. Drop-in replacement for the pre-v11 + * `getCurrentScope().clear(); getIsolationScope().clear(); getGlobalScope().clear();` + * block that the suite relied on before `Scope.clear()` was removed. + */ +export function clearAllScopes(): void { + resetScope(getCurrentScope()); + resetScope(getIsolationScope()); + resetScope(getGlobalScope()); +} diff --git a/packages/core/test/tracing/addTracingExtensions.test.ts b/packages/core/test/tracing/addTracingExtensions.test.ts index 48a2a9a432..7389fbae1c 100644 --- a/packages/core/test/tracing/addTracingExtensions.test.ts +++ b/packages/core/test/tracing/addTracingExtensions.test.ts @@ -1,6 +1,6 @@ import type { Span } from '@sentry/core'; -import { getCurrentScope, spanToJSON, startSpanManual } from '@sentry/core'; +import { getCurrentScope, spanToStaticSpanJSON, startSpanManual } from '@sentry/core'; import { reactNativeTracingIntegration } from '../../src/js'; import { setupTestClient, type TestClient } from '../mocks/client'; @@ -17,7 +17,7 @@ describe('Tracing extensions', () => { test('transaction has default op', async () => { const transaction = startSpanManual({ name: 'parent' }, span => span); - expect(spanToJSON(transaction)).toEqual( + expect(spanToStaticSpanJSON(transaction)).toEqual( expect.objectContaining({ op: 'default', }), @@ -27,7 +27,7 @@ describe('Tracing extensions', () => { test('transaction does not overwrite custom op', async () => { const transaction = startSpanManual({ name: 'parent', op: 'custom' }, span => span); - expect(spanToJSON(transaction)).toEqual( + expect(spanToStaticSpanJSON(transaction)).toEqual( expect.objectContaining({ op: 'custom', }), @@ -38,7 +38,7 @@ describe('Tracing extensions', () => { startSpanManual({ name: 'parent', scope: getCurrentScope() }, () => {}); const span = startSpanManual({ name: 'child', scope: getCurrentScope() }, span => span); - expect(spanToJSON(span)).toEqual( + expect(spanToStaticSpanJSON(span)).toEqual( expect.objectContaining({ op: 'default', }), @@ -49,7 +49,7 @@ describe('Tracing extensions', () => { startSpanManual({ name: 'parent', op: 'custom', scope: getCurrentScope() }, () => {}); const span = startSpanManual({ name: 'child', op: 'custom', scope: getCurrentScope() }, span => span); - expect(spanToJSON(span)).toEqual( + expect(spanToStaticSpanJSON(span)).toEqual( expect.objectContaining({ op: 'custom', }), @@ -75,9 +75,9 @@ describe('Tracing extensions', () => { }), }), ); - expect(spanToJSON(childSpan)).toEqual( + expect(spanToStaticSpanJSON(childSpan)).toEqual( expect.objectContaining({ - parent_span_id: spanToJSON(transaction).span_id, + parent_span_id: spanToStaticSpanJSON(transaction).span_id, }), ); }); diff --git a/packages/core/test/tracing/adjustTransactionDuration.test.ts b/packages/core/test/tracing/adjustTransactionDuration.test.ts index 88ecf9836d..3a79ddbe52 100644 --- a/packages/core/test/tracing/adjustTransactionDuration.test.ts +++ b/packages/core/test/tracing/adjustTransactionDuration.test.ts @@ -1,4 +1,4 @@ -import { getClient, spanToJSON, startSpanManual } from '@sentry/core'; +import { getClient, spanToStaticSpanJSON, startSpanManual } from '@sentry/core'; import { adjustTransactionDuration } from '../../src/js/tracing/onSpanEndUtils'; import { setupTestClient } from '../mocks/client'; @@ -34,11 +34,11 @@ describe('adjustTransactionDuration', () => { adjustTransactionDuration(client, span, maxDurationMs); // End the span 120 seconds after it started (exceeds 60s max) - const startTimestamp = spanToJSON(span).start_timestamp; + const startTimestamp = spanToStaticSpanJSON(span).start_timestamp; span.end(startTimestamp + 120); - expect(spanToJSON(span).status).toBe('deadline_exceeded'); - expect(spanToJSON(span).data).toMatchObject({ maxTransactionDurationExceeded: 'true' }); + expect(spanToStaticSpanJSON(span).status).toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(span).data).toMatchObject({ maxTransactionDurationExceeded: 'true' }); }); it('does not mark span as deadline_exceeded when duration is within maxDurationMs', () => { @@ -49,11 +49,11 @@ describe('adjustTransactionDuration', () => { adjustTransactionDuration(client, span, maxDurationMs); // End the span 30 seconds after it started (within 60s max) - const startTimestamp = spanToJSON(span).start_timestamp; + const startTimestamp = spanToStaticSpanJSON(span).start_timestamp; span.end(startTimestamp + 30); - expect(spanToJSON(span).status).not.toBe('deadline_exceeded'); - expect(spanToJSON(span).data).not.toMatchObject({ maxTransactionDurationExceeded: 'true' }); + expect(spanToStaticSpanJSON(span).status).not.toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(span).data).not.toMatchObject({ maxTransactionDurationExceeded: 'true' }); }); it('does not mark span as deadline_exceeded when duration equals maxDurationMs exactly', () => { @@ -64,11 +64,11 @@ describe('adjustTransactionDuration', () => { adjustTransactionDuration(client, span, maxDurationMs); // End the span exactly 60 seconds after it started - const startTimestamp = spanToJSON(span).start_timestamp; + const startTimestamp = spanToStaticSpanJSON(span).start_timestamp; span.end(startTimestamp + 60); - expect(spanToJSON(span).status).not.toBe('deadline_exceeded'); - expect(spanToJSON(span).data).not.toMatchObject({ maxTransactionDurationExceeded: 'true' }); + expect(spanToStaticSpanJSON(span).status).not.toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(span).data).not.toMatchObject({ maxTransactionDurationExceeded: 'true' }); }); it('marks span as deadline_exceeded when duration is negative', () => { @@ -79,11 +79,11 @@ describe('adjustTransactionDuration', () => { adjustTransactionDuration(client, span, maxDurationMs); // End the span before it started (negative duration) - const startTimestamp = spanToJSON(span).start_timestamp; + const startTimestamp = spanToStaticSpanJSON(span).start_timestamp; span.end(startTimestamp - 10); - expect(spanToJSON(span).status).toBe('deadline_exceeded'); - expect(spanToJSON(span).data).toMatchObject({ maxTransactionDurationExceeded: 'true' }); + expect(spanToStaticSpanJSON(span).status).toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(span).data).toMatchObject({ maxTransactionDurationExceeded: 'true' }); }); it('correctly handles maxDurationMs in milliseconds not seconds', () => { @@ -95,11 +95,11 @@ describe('adjustTransactionDuration', () => { const maxDurationMs = 600_000; adjustTransactionDuration(client, span, maxDurationMs); - const startTimestamp = spanToJSON(span).start_timestamp; + const startTimestamp = spanToStaticSpanJSON(span).start_timestamp; span.end(startTimestamp + 601); - expect(spanToJSON(span).status).toBe('deadline_exceeded'); - expect(spanToJSON(span).data).toMatchObject({ maxTransactionDurationExceeded: 'true' }); + expect(spanToStaticSpanJSON(span).status).toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(span).data).toMatchObject({ maxTransactionDurationExceeded: 'true' }); }); it('does not mark span when duration is 599 seconds with 600_000ms max', () => { @@ -111,11 +111,11 @@ describe('adjustTransactionDuration', () => { const maxDurationMs = 600_000; adjustTransactionDuration(client, span, maxDurationMs); - const startTimestamp = spanToJSON(span).start_timestamp; + const startTimestamp = spanToStaticSpanJSON(span).start_timestamp; span.end(startTimestamp + 599); - expect(spanToJSON(span).status).not.toBe('deadline_exceeded'); - expect(spanToJSON(span).data).not.toMatchObject({ maxTransactionDurationExceeded: 'true' }); + expect(spanToStaticSpanJSON(span).status).not.toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(span).data).not.toMatchObject({ maxTransactionDurationExceeded: 'true' }); }); it('does not affect spans from other transactions', () => { @@ -127,17 +127,17 @@ describe('adjustTransactionDuration', () => { adjustTransactionDuration(client, trackedSpan, maxDurationMs); // End the other span with a duration that exceeds the limit - const otherStartTimestamp = spanToJSON(otherSpan).start_timestamp; + const otherStartTimestamp = spanToStaticSpanJSON(otherSpan).start_timestamp; otherSpan.end(otherStartTimestamp + 120); // Other span should not be affected by adjustTransactionDuration - expect(spanToJSON(otherSpan).status).not.toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(otherSpan).status).not.toBe('deadline_exceeded'); // End tracked span within limits - const trackedStartTimestamp = spanToJSON(trackedSpan).start_timestamp; + const trackedStartTimestamp = spanToStaticSpanJSON(trackedSpan).start_timestamp; trackedSpan.end(trackedStartTimestamp + 30); - expect(spanToJSON(trackedSpan).status).not.toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(trackedSpan).status).not.toBe('deadline_exceeded'); }); it('handles very short maxDurationMs values', () => { @@ -149,10 +149,10 @@ describe('adjustTransactionDuration', () => { adjustTransactionDuration(client, span, maxDurationMs); // End after 1 second (1000ms > 100ms) - const startTimestamp = spanToJSON(span).start_timestamp; + const startTimestamp = spanToStaticSpanJSON(span).start_timestamp; span.end(startTimestamp + 1); - expect(spanToJSON(span).status).toBe('deadline_exceeded'); - expect(spanToJSON(span).data).toMatchObject({ maxTransactionDurationExceeded: 'true' }); + expect(spanToStaticSpanJSON(span).status).toBe('deadline_exceeded'); + expect(spanToStaticSpanJSON(span).data).toMatchObject({ maxTransactionDurationExceeded: 'true' }); }); }); diff --git a/packages/core/test/tracing/expoRouterErrorBoundary.test.tsx b/packages/core/test/tracing/expoRouterErrorBoundary.test.tsx index ebe82b35c5..ca3790f4ae 100644 --- a/packages/core/test/tracing/expoRouterErrorBoundary.test.tsx +++ b/packages/core/test/tracing/expoRouterErrorBoundary.test.tsx @@ -30,7 +30,7 @@ jest.mock('@sentry/core', () => { }), getActiveSpan: () => mockActiveSpan, getRootSpan: (span: unknown) => span, - spanToJSON: (span: { __origin?: string } | undefined) => ({ origin: span?.__origin }), + spanToStaticSpanJSON: (span: { __origin?: string } | undefined) => ({ origin: span?.__origin }), }; }); diff --git a/packages/core/test/tracing/gesturetracing.test.ts b/packages/core/test/tracing/gesturetracing.test.ts index 7cc38aa469..0cedaa2113 100644 --- a/packages/core/test/tracing/gesturetracing.test.ts +++ b/packages/core/test/tracing/gesturetracing.test.ts @@ -1,6 +1,6 @@ import type { Breadcrumb } from '@sentry/core'; -import { getActiveSpan, spanToJSON, startSpan } from '@sentry/core'; +import { getActiveSpan, spanToStaticSpanJSON, startSpan } from '@sentry/core'; import type { ReactNativeTracingIntegration } from '../../src/js/tracing/reactnativetracing'; @@ -88,7 +88,7 @@ describe('GestureTracing', () => { jest.runAllTimers(); expect(transaction).toBeDefined(); - expect(spanToJSON(transaction!)).toEqual( + expect(spanToStaticSpanJSON(transaction!)).toEqual( expect.objectContaining({ timestamp: expect.any(Number), op: `${UI_ACTION}.mock`, @@ -107,7 +107,7 @@ describe('GestureTracing', () => { jest.runAllTimers(); expect(transaction).toBeDefined(); - expect(spanToJSON(transaction!)).toEqual( + expect(spanToStaticSpanJSON(transaction!)).toEqual( expect.objectContaining({ timestamp: expect.any(Number), op: `${UI_ACTION}.gesture`, diff --git a/packages/core/test/tracing/idleNavigationSpan.test.ts b/packages/core/test/tracing/idleNavigationSpan.test.ts index 8245ed88b3..c4c85a8569 100644 --- a/packages/core/test/tracing/idleNavigationSpan.test.ts +++ b/packages/core/test/tracing/idleNavigationSpan.test.ts @@ -1,19 +1,19 @@ import type { Span } from '@sentry/core'; +import type { Scope } from '@sentry/core'; import type { AppStateStatus } from 'react-native'; import { + _INTERNAL_setSpanForScope, getActiveSpan, getCurrentScope, - spanToJSON, + spanToStaticSpanJSON, startInactiveSpan, startSpanManual, timestampInSeconds, } from '@sentry/core'; import { AppState } from 'react-native'; -import type { ScopeWithMaybeSpan } from '../../src/js/tracing/span'; - -import { SCOPE_SPAN_FIELD, startIdleNavigationSpan } from '../../src/js/tracing/span'; +import { startIdleNavigationSpan } from '../../src/js/tracing/span'; import { NATIVE } from '../../src/js/wrapper'; import { setupTestClient } from '../mocks/client'; @@ -74,7 +74,7 @@ describe('startIdleNavigationSpan', () => { jest.runAllTimers(); expect(routeTransaction).toBeDefined(); - expect(spanToJSON(routeTransaction!).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(routeTransaction!).status).toBe('cancelled'); expect(mockedAppState.removeSubscription).toHaveBeenCalledTimes(1); }); @@ -93,7 +93,7 @@ describe('startIdleNavigationSpan', () => { jest.runAllTimers(); - expect(spanToJSON(transaction!).timestamp).toBeDefined(); + expect(spanToStaticSpanJSON(transaction!).timestamp).toBeDefined(); }); it('Returns non-recording span when app is already in background', () => { @@ -153,15 +153,15 @@ describe('startIdleNavigationSpan', () => { mockedAppState.setState('inactive'); // Span should still be open — not cancelled immediately - expect(spanToJSON(routeTransaction!).status).not.toBe('cancelled'); - expect(spanToJSON(routeTransaction!).timestamp).toBeUndefined(); + expect(spanToStaticSpanJSON(routeTransaction!).status).not.toBe('cancelled'); + expect(spanToStaticSpanJSON(routeTransaction!).timestamp).toBeUndefined(); // Advance past the deferred cancellation timeout (5 seconds) jest.advanceTimersByTime(5_000); // Now the deferred cancellation should have fired - expect(spanToJSON(routeTransaction!).status).toBe('cancelled'); - expect(spanToJSON(routeTransaction!).timestamp).toBeDefined(); + expect(spanToStaticSpanJSON(routeTransaction!).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(routeTransaction!).timestamp).toBeDefined(); expect(mockedAppState.removeSubscription).toHaveBeenCalledTimes(1); }); @@ -183,7 +183,7 @@ describe('startIdleNavigationSpan', () => { jest.advanceTimersByTime(10_000); // Span should NOT be cancelled — it's still recording - expect(spanToJSON(routeTransaction!).status).not.toBe('cancelled'); + expect(spanToStaticSpanJSON(routeTransaction!).status).not.toBe('cancelled'); }); it('Cancels immediately on background even if inactive timeout is pending', () => { @@ -199,8 +199,8 @@ describe('startIdleNavigationSpan', () => { mockedAppState.setState('background'); // Span should be cancelled immediately - expect(spanToJSON(routeTransaction!).status).toBe('cancelled'); - expect(spanToJSON(routeTransaction!).timestamp).toBeDefined(); + expect(spanToStaticSpanJSON(routeTransaction!).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(routeTransaction!).timestamp).toBeDefined(); expect(mockedAppState.removeSubscription).toHaveBeenCalledTimes(1); }); @@ -220,7 +220,7 @@ describe('startIdleNavigationSpan', () => { // AppState listener should be cleaned up expect(mockedAppState.removeSubscription).toHaveBeenCalledTimes(1); // Span should not have the cancelled status since it ended normally - expect(spanToJSON(routeTransaction!).status).not.toBe('cancelled'); + expect(spanToStaticSpanJSON(routeTransaction!).status).not.toBe('cancelled'); }); }); @@ -228,7 +228,7 @@ describe('startIdleNavigationSpan', () => { it('ends http.client child at the time the app went inactive, not when the deferred timer fires', () => { const navSpan = startIdleNavigationSpan({ name: 'test' }); const httpSpan = startInactiveSpan({ name: 'GET /api/data', op: 'http.client' }); - const httpStartTime = spanToJSON(httpSpan).start_timestamp; + const httpStartTime = spanToStaticSpanJSON(httpSpan).start_timestamp; // App goes inactive at a known time (e.g. user presses home on iOS) mockedAppState.setState('inactive'); @@ -238,14 +238,14 @@ describe('startIdleNavigationSpan', () => { // while the app is in the background, then resumed much later. jest.advanceTimersByTime(30_000); - expect(spanToJSON(navSpan!).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(navSpan!).status).toBe('cancelled'); // The http.client span should be ended at approximately when the app // went inactive, NOT 30 seconds later when the timer fired. - const httpEndTime = spanToJSON(httpSpan).timestamp!; + const httpEndTime = spanToStaticSpanJSON(httpSpan).timestamp!; const httpDuration = httpEndTime - httpStartTime; expect(httpDuration).toBeLessThan(1); - expect(spanToJSON(httpSpan).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(httpSpan).status).toBe('cancelled'); }); it('uses fresh timestamp after inactive → active → background cycle', () => { @@ -267,7 +267,7 @@ describe('startIdleNavigationSpan', () => { const backgroundTime = timestampInSeconds(); mockedAppState.setState('background'); - const httpEndTime = spanToJSON(httpSpan).timestamp!; + const httpEndTime = spanToStaticSpanJSON(httpSpan).timestamp!; // The end time should match the background event, not the earlier inactive event. // Use toBeCloseTo because timestampInSeconds() may advance slightly between calls. @@ -287,9 +287,9 @@ describe('startIdleNavigationSpan', () => { // App goes directly to background (Android, or iOS without inactive) mockedAppState.setState('background'); - expect(spanToJSON(navSpan!).status).toBe('cancelled'); - expect(spanToJSON(httpSpan).timestamp).toBeDefined(); - expect(spanToJSON(httpSpan).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(navSpan!).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(httpSpan).timestamp).toBeDefined(); + expect(spanToStaticSpanJSON(httpSpan).status).toBe('cancelled'); }); it('preserves already-ended http.client spans when app backgrounds', () => { @@ -300,16 +300,16 @@ describe('startIdleNavigationSpan', () => { httpSpan.setStatus({ code: 1 }); // OK httpSpan.end(); - const httpEndTimeBefore = spanToJSON(httpSpan).timestamp; + const httpEndTimeBefore = spanToStaticSpanJSON(httpSpan).timestamp; // App goes to background mockedAppState.setState('background'); - expect(spanToJSON(navSpan!).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(navSpan!).status).toBe('cancelled'); // The already-ended http.client span should be untouched - expect(spanToJSON(httpSpan).status).toBe('ok'); - expect(spanToJSON(httpSpan).timestamp).toBe(httpEndTimeBefore); + expect(spanToStaticSpanJSON(httpSpan).status).toBe('ok'); + expect(spanToStaticSpanJSON(httpSpan).timestamp).toBe(httpEndTimeBefore); }); it('still cancels non-http.client children when app backgrounds', () => { @@ -320,11 +320,11 @@ describe('startIdleNavigationSpan', () => { mockedAppState.setState('background'); - expect(spanToJSON(navSpan!).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(navSpan!).status).toBe('cancelled'); // Non-http.client children should still be cancelled by idle span logic - expect(spanToJSON(uiSpan).timestamp).toBeDefined(); - expect(spanToJSON(uiSpan).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(uiSpan).timestamp).toBeDefined(); + expect(spanToStaticSpanJSON(uiSpan).status).toBe('cancelled'); }); }); @@ -347,7 +347,7 @@ describe('startIdleNavigationSpan', () => { }); expect(secondSpan).toBe(getActiveSpan()); - expect(spanToJSON(secondSpan!).parent_span_id).toBeUndefined(); + expect(spanToStaticSpanJSON(secondSpan!).parent_span_id).toBeUndefined(); }); it('Starts a new span when current active navigation span is ended', () => { @@ -362,7 +362,7 @@ describe('startIdleNavigationSpan', () => { }); expect(secondSpan).toBe(getActiveSpan()); - expect(spanToJSON(secondSpan!).parent_span_id).toBeUndefined(); + expect(spanToStaticSpanJSON(secondSpan!).parent_span_id).toBeUndefined(); }); it('Starts a new span when current active span is not a navigation span', () => { @@ -378,7 +378,7 @@ describe('startIdleNavigationSpan', () => { name: 'test', }); expect(newSpan).toBe(getActiveSpan()); - expect(spanToJSON(newSpan!).parent_span_id).toBeUndefined(); + expect(spanToStaticSpanJSON(newSpan!).parent_span_id).toBeUndefined(); }); it('Cancels user interaction span during normal navigation', () => { @@ -398,8 +398,8 @@ describe('startIdleNavigationSpan', () => { name: 'test', }); - expect(spanToJSON(userInteractionSpan).timestamp).toBeDefined(); - expect(spanToJSON(userInteractionSpan).status).toBe('cancelled'); + expect(spanToStaticSpanJSON(userInteractionSpan).timestamp).toBeDefined(); + expect(spanToStaticSpanJSON(userInteractionSpan).status).toBe('cancelled'); expect(navigationSpan).toBe(getActiveSpan()); }); @@ -426,8 +426,8 @@ describe('startIdleNavigationSpan', () => { ); // User interaction span should NOT be cancelled/ended - preserving it for replay capture - expect(spanToJSON(userInteractionSpan).timestamp).toBeUndefined(); - expect(spanToJSON(userInteractionSpan).status).not.toBe('cancelled'); + expect(spanToStaticSpanJSON(userInteractionSpan).timestamp).toBeUndefined(); + expect(spanToStaticSpanJSON(userInteractionSpan).status).not.toBe('cancelled'); expect(navigationSpan).toBeDefined(); expect(getActiveSpan()).toBe(navigationSpan); @@ -435,6 +435,8 @@ describe('startIdleNavigationSpan', () => { }); }); -export function setActiveSpanOnScope(scope: ScopeWithMaybeSpan, span: Span): void { - scope[SCOPE_SPAN_FIELD] = span; +export function setActiveSpanOnScope(scope: Scope, span: Span): void { + // JS v11 stores the active span in a `WeakRef` under `scope.refs.span`; use the + // official helper instead of assigning the removed `_sentrySpan` property. + _INTERNAL_setSpanForScope(scope, span); } diff --git a/packages/core/test/tracing/integrations/__snapshots__/appStart.test.ts.snap b/packages/core/test/tracing/integrations/__snapshots__/appStart.test.ts.snap index 7531213c21..7048d67de8 100644 --- a/packages/core/test/tracing/integrations/__snapshots__/appStart.test.ts.snap +++ b/packages/core/test/tracing/integrations/__snapshots__/appStart.test.ts.snap @@ -77,14 +77,14 @@ exports[`App Start Integration Standalone App Start matches the locked standalon "sentry.op": "app.start", "sentry.origin": "auto.app.start", "sentry.sample_rate": 1, - "sentry.source": "custom", + "sentry.segment.name.source": "custom", }, "links": undefined, "op": "app.start", "origin": "auto.app.start", "parent_span_id": undefined, "span_id": Any, - "status": undefined, + "status": "ok", "trace_id": Any, }, }, diff --git a/packages/core/test/tracing/integrations/appStart.test.ts b/packages/core/test/tracing/integrations/appStart.test.ts index 86b5122344..2ea0fc2c42 100644 --- a/packages/core/test/tracing/integrations/appStart.test.ts +++ b/packages/core/test/tracing/integrations/appStart.test.ts @@ -9,7 +9,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SentryNonRecordingSpan, setCurrentClient, - spanToJSON, + spanToStaticSpanJSON, startInactiveSpan, timestampInSeconds, } from '@sentry/core'; @@ -53,7 +53,7 @@ import { RN_GLOBAL_OBJ } from '../../../src/js/utils/worldwide'; import { NATIVE } from '../../../src/js/wrapper'; import { mockAppRegistryIntegration } from '../../mocks/appRegistryIntegrationMock'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; -import { mockFunction } from '../../testutils'; +import { clearAllScopes, mockFunction } from '../../testutils'; type AppStartIntegrationTest = ReturnType & { setFirstStartedActiveRootSpanId: (spanId: string | undefined) => void; @@ -413,9 +413,7 @@ describe('App Start Integration', () => { }); it('Does not add app start span twice', async () => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const [timeOriginMilliseconds, appStartTimeMilliseconds] = mockAppStart({ cold: true }); @@ -467,9 +465,7 @@ describe('App Start Integration', () => { // starts a navigation transaction before the standalone app start transaction is created. // The fix ensures that when standalone: true, the span ID check is skipped so app start // can be attached to the standalone transaction even if a navigation transaction started first. - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); mockAppStart({ cold: true }); @@ -519,9 +515,7 @@ describe('App Start Integration', () => { }); it('Connects the standalone app start transaction to the active trace', async () => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); mockAppStart({ cold: true }); @@ -1302,9 +1296,7 @@ describe('Extended App Start', () => { beforeEach(() => { jest.clearAllMocks(); mockReactNativeBundleExecutionStartTimestamp(); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); _clearAppStartEndData(); _clearRootComponentCreationTimestampMs(); }); @@ -1348,7 +1340,7 @@ describe('Extended App Start', () => { integration.extendAppStart(); const extendedSpan = integration.getExtendedAppStartSpan(); - expect(spanToJSON(extendedSpan).op).toBe(APP_START_EXTENDED_OP); + expect(spanToStaticSpanJSON(extendedSpan).op).toBe(APP_START_EXTENDED_OP); const child = startInactiveSpan({ parentSpan: extendedSpan, op: 'app.init', name: 'load config' }); child.end(); @@ -1432,7 +1424,7 @@ describe('Extended App Start', () => { name: 'work', }); child.end(); - const childEndSeconds = spanToJSON(child).timestamp as number; + const childEndSeconds = spanToStaticSpanJSON(child).timestamp as number; // A gap between the last child finishing and the app declaring itself ready. await new Promise(resolve => setTimeout(resolve, 100)); @@ -1895,9 +1887,7 @@ describe('appLoaded() standalone mode', () => { }); it('triggers standalone app start capture via appLoaded()', async () => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const [, appStartTimeMilliseconds] = mockAppStart({ cold: true }); @@ -1930,9 +1920,7 @@ describe('appLoaded() standalone mode', () => { }); it('overrides already-flushed standalone transaction when appLoaded() is called after auto-capture', async () => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const [, appStartTimeMilliseconds] = mockAppStart({ cold: true }); @@ -1974,9 +1962,7 @@ describe('appLoaded() standalone mode', () => { it('sends deferred standalone transaction when appLoaded() is not called', async () => { jest.useFakeTimers(); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const [, appStartTimeMilliseconds] = mockAppStart({ cold: true }); @@ -2014,9 +2000,7 @@ describe('appLoaded() standalone mode', () => { it('does not send a second standalone transaction when appLoaded() arrives after the deferred auto-capture fired', async () => { jest.useFakeTimers(); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); mockAppStart({ cold: true }); @@ -2050,9 +2034,7 @@ describe('appLoaded() standalone mode', () => { }); it('does not send a second standalone transaction when appLoaded() races an in-flight capture', async () => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); // Reset module-level state so the test is hermetic (clears isAppLoadedManuallyInvoked so // appLoaded() actually runs its capture, and any leaked app start end data). _clearAppStartEndData(); @@ -2104,9 +2086,7 @@ describe('appLoaded() standalone mode', () => { }); it('captures once per run and re-captures for a new run after runApplication', async () => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); _clearAppStartEndData(); _clearRootComponentCreationTimestampMs(); @@ -2144,9 +2124,7 @@ describe('appLoaded() standalone mode', () => { }); it('allows auto-capture again after isAppLoadedManuallyInvoked is reset', async () => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); mockAppStart({ cold: true }); @@ -2448,9 +2426,7 @@ function expectStandaloneChildrenHaveAppStartVitals( } async function captureStandAloneAppStart(): Promise | Event | null> { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const integration = appStartIntegration({ standalone: true, diff --git a/packages/core/test/tracing/integrations/nativeframes.test.ts b/packages/core/test/tracing/integrations/nativeframes.test.ts index 695bca3d2f..bc35883a58 100644 --- a/packages/core/test/tracing/integrations/nativeframes.test.ts +++ b/packages/core/test/tracing/integrations/nativeframes.test.ts @@ -1,11 +1,11 @@ import type { Event, Measurements } from '@sentry/core'; -import { getCurrentScope, getGlobalScope, getIsolationScope, setCurrentClient, startSpan } from '@sentry/core'; +import { setCurrentClient, startSpan } from '@sentry/core'; import { nativeFramesIntegration } from '../../../src/js'; import { NATIVE } from '../../../src/js/wrapper'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; -import { mockFunction } from '../../testutils'; +import { clearAllScopes, mockFunction } from '../../testutils'; jest.mock('../../../src/js/wrapper', () => { return { @@ -34,9 +34,7 @@ describe('NativeFramesInstrumentation', () => { beforeEach(() => { global.Date.now = jest.fn(() => mockDate.getTime()); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0, @@ -469,7 +467,7 @@ describe('NativeFramesInstrumentation', () => { // Start and complete txn-2 during txn-1's event processing. // With a global variable (old code), txn-2's child end would overwrite txn-1's data. // Clear scope so txn-2 is a new root span, not a child of txn-1. - getCurrentScope().clear(); + clearAllScopes(); await startSpan({ name: 'txn-2' }, async () => { startSpan({ name: 'txn-2-child' }, () => {}); await Promise.resolve(); @@ -528,9 +526,7 @@ describe('NativeFramesInstrumentation', () => { beforeEach(() => { global.Date.now = jest.fn(() => mockDate.getTime()); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const options = getDefaultTestClientOptions({ tracesSampleRate: 0, diff --git a/packages/core/test/tracing/integrations/stallTracking/stalltracking.test.ts b/packages/core/test/tracing/integrations/stallTracking/stalltracking.test.ts index b4d5dbb43e..2be0a7f35c 100644 --- a/packages/core/test/tracing/integrations/stallTracking/stalltracking.test.ts +++ b/packages/core/test/tracing/integrations/stallTracking/stalltracking.test.ts @@ -1,18 +1,13 @@ import type { Span } from '@sentry/core'; -import { - getCurrentScope, - getGlobalScope, - getIsolationScope, - setCurrentClient, - startIdleSpan, - startSpan, - startSpanManual, - timestampInSeconds, -} from '@sentry/core'; +import { setCurrentClient, startSpan, startSpanManual, timestampInSeconds } from '@sentry/core'; +// `startIdleSpan` moved from the `@sentry/core` root entry to its `browser` +// subpath in JS v11 (the entry point was split into shared/browser/server). +import { startIdleSpan } from '@sentry/core/browser'; import { stallTrackingIntegration } from '../../../../src/js/tracing/integrations/stalltracking'; import { getDefaultTestClientOptions, TestClient } from '../../../mocks/client'; +import { clearAllScopes } from '../../../testutils'; import { expectNonZeroStallMeasurements, expectStallMeasurements } from './stalltrackingutils'; jest.useFakeTimers({ @@ -35,9 +30,7 @@ describe('StallTracking', () => { let client: TestClient; beforeEach(() => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0, @@ -229,9 +222,7 @@ describe('StallTracking', () => { }); it('does not track stalls for unsampled spans', async () => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const unsampledOptions = getDefaultTestClientOptions({ tracesSampleRate: 0, diff --git a/packages/core/test/tracing/integrations/userInteraction.test.ts b/packages/core/test/tracing/integrations/userInteraction.test.ts index c39097745d..a6923715b9 100644 --- a/packages/core/test/tracing/integrations/userInteraction.test.ts +++ b/packages/core/test/tracing/integrations/userInteraction.test.ts @@ -5,7 +5,7 @@ import { getActiveSpan, getCurrentScope, SPAN_STATUS_ERROR, - spanToJSON, + spanToStaticSpanJSON, startInactiveSpan, startSpanManual, } from '@sentry/core'; @@ -117,7 +117,7 @@ describe('User Interaction Tracing', () => { startUserInteractionSpan(mockedUserInteractionId); const actualTransaction = getActiveSpan(); - const actualTransactionContext = spanToJSON(actualTransaction!); + const actualTransactionContext = spanToStaticSpanJSON(actualTransaction!); expect(client.getOptions().enableUserInteractionTracing).toBeTruthy(); expect(actualTransactionContext).toEqual( expect.objectContaining({ @@ -147,7 +147,7 @@ describe('User Interaction Tracing', () => { mockedAppState.setState('background'); jest.runAllTimers(); - const actualTransactionContext = spanToJSON(actualTransaction!); + const actualTransactionContext = spanToStaticSpanJSON(actualTransaction!); expect(actualTransactionContext).toEqual( expect.objectContaining({ timestamp: expect.any(Number), @@ -165,11 +165,14 @@ describe('User Interaction Tracing', () => { jest.runAllTimers(); - const actualTransactionContext = spanToJSON(actualTransaction!); + const actualTransactionContext = spanToStaticSpanJSON(actualTransaction!); + // The pre-set error status survives the idle-span finish (it is not + // overwritten to 'ok'). JS v11 surfaces only canonical status messages, so + // a non-canonical message like 'mocked_status' reports as 'internal_error'. expect(actualTransactionContext).toEqual( expect.objectContaining({ timestamp: expect.any(Number), - status: 'mocked_status', + status: 'internal_error', }), ); }); @@ -183,7 +186,7 @@ describe('User Interaction Tracing', () => { startUserInteractionSpan(mockedUserInteractionId); jest.advanceTimersByTime(timeoutCloseToActualIdleTimeoutMs); - expect(spanToJSON(actualTransaction!).timestamp).toEqual(expect.any(Number)); + expect(spanToStaticSpanJSON(actualTransaction!).timestamp).toEqual(expect.any(Number)); }); test('different UI event and same element finish first and start new transaction', () => { @@ -213,13 +216,15 @@ describe('User Interaction Tracing', () => { ); expect(secondTransaction).toBeDefined(); - expect(spanToJSON(secondTransaction!)).toEqual( + expect(spanToStaticSpanJSON(secondTransaction!)).toEqual( expect.objectContaining({ timestamp: expect.any(Number), op: 'different.op', }), ); - expect(firstTransactionEvent.timestamp).toBeGreaterThanOrEqual(spanToJSON(secondTransaction).start_timestamp); + expect(firstTransactionEvent.timestamp).toBeGreaterThanOrEqual( + spanToStaticSpanJSON(secondTransaction).start_timestamp, + ); }); test('different UI event and same element finish first transaction with last span', () => { @@ -257,8 +262,8 @@ describe('User Interaction Tracing', () => { const secondTransaction = getActiveSpan(); jest.runAllTimers(); - const firstTransactionContext = spanToJSON(firstTransaction!); - const secondTransactionContext = spanToJSON(secondTransaction!); + const firstTransactionContext = spanToStaticSpanJSON(firstTransaction!); + const secondTransactionContext = spanToStaticSpanJSON(secondTransaction!); expect(firstTransactionContext.timestamp).toEqual(expect.any(Number)); expect(secondTransactionContext.timestamp).toEqual(expect.any(Number)); expect(firstTransactionContext.span_id).not.toEqual(secondTransactionContext.span_id); @@ -291,8 +296,8 @@ describe('User Interaction Tracing', () => { }); jest.runAllTimers(); - const interactionTransactionContext = spanToJSON(interactionTransaction!); - const routingTransactionContext = spanToJSON(routingTransaction!); + const interactionTransactionContext = spanToStaticSpanJSON(interactionTransaction!); + const routingTransactionContext = spanToStaticSpanJSON(routingTransaction!); expect(interactionTransactionContext).toEqual( expect.objectContaining({ timestamp: expect.any(Number), diff --git a/packages/core/test/tracing/onSpanEndUtils.test.ts b/packages/core/test/tracing/onSpanEndUtils.test.ts index f35d667ae4..78bc75374e 100644 --- a/packages/core/test/tracing/onSpanEndUtils.test.ts +++ b/packages/core/test/tracing/onSpanEndUtils.test.ts @@ -1,6 +1,6 @@ import type { Client, Span } from '@sentry/core'; -import { getClient, spanToJSON, startSpan, startSpanManual } from '@sentry/core'; +import { getClient, spanToStaticSpanJSON, startSpan, startSpanManual } from '@sentry/core'; import { adjustTransactionDuration, @@ -141,7 +141,7 @@ describe('onSpanEndUtils', () => { ignoreEmptyBackNavigation(client, span); span.end(); - expect(spanToJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBe('empty_back_navigation'); + expect(spanToStaticSpanJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBe('empty_back_navigation'); expect(span._sampled).not.toBe(false); }); @@ -152,7 +152,7 @@ describe('onSpanEndUtils', () => { ignoreEmptyBackNavigation(client, span); span.end(); - expect(spanToJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBeUndefined(); + expect(spanToStaticSpanJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBeUndefined(); }); it('does not mark the span when meaningful child spans exist', () => { @@ -165,7 +165,7 @@ describe('onSpanEndUtils', () => { startSpan({ name: 'meaningful child' }, () => undefined); }); - expect(spanToJSON(parent!).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBeUndefined(); + expect(spanToStaticSpanJSON(parent!).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBeUndefined(); }); }); @@ -189,7 +189,7 @@ describe('onSpanEndUtils', () => { ignoreEmptyRouteChangeTransactions(client, span, 'Route Change', () => true); span.end(); - expect(spanToJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBe('no_route_info'); + expect(spanToStaticSpanJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBe('no_route_info'); expect(span._sampled).not.toBe(false); }); @@ -201,7 +201,7 @@ describe('onSpanEndUtils', () => { ignoreEmptyRouteChangeTransactions(client, span, 'Route Change', () => true); span.end(); - expect(spanToJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBeUndefined(); + expect(spanToStaticSpanJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBeUndefined(); }); }); @@ -225,7 +225,7 @@ describe('onSpanEndUtils', () => { onlySampleIfChildSpans(client, span); span.end(); - expect(spanToJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBe('no_child_spans'); + expect(spanToStaticSpanJSON(span).data?.[SENTRY_DISCARD_REASON_ATTRIBUTE]).toBe('no_child_spans'); expect(span._sampled).not.toBe(false); }); }); diff --git a/packages/core/test/tracing/reactnativenavigation.test.ts b/packages/core/test/tracing/reactnativenavigation.test.ts index 1ff880b599..b2315f28fd 100644 --- a/packages/core/test/tracing/reactnativenavigation.test.ts +++ b/packages/core/test/tracing/reactnativenavigation.test.ts @@ -1,14 +1,7 @@ import type { Event, StartSpanOptions } from '@sentry/core'; import type { EmitterSubscription } from 'react-native'; -import { - getActiveSpan, - getCurrentScope, - getGlobalScope, - getIsolationScope, - setCurrentClient, - spanToJSON, -} from '@sentry/core'; +import { getActiveSpan, setCurrentClient, spanToStaticSpanJSON } from '@sentry/core'; import type { BottomTabPressedEvent, @@ -35,6 +28,7 @@ import { } from '../../src/js/tracing/semanticAttributes'; import { SPAN_THREAD_NAME, SPAN_THREAD_NAME_JAVASCRIPT } from '../../src/js/tracing/span'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; +import { clearAllScopes } from '../testutils'; interface MockEventsRegistry extends EventsRegistry { componentWillAppearListener?: (event: ComponentWillAppearEvent) => void; @@ -55,9 +49,7 @@ describe('React Native Navigation Instrumentation', () => { let client: TestClient; beforeEach(() => { - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); }); test('Correctly instruments a route change', async () => { @@ -161,7 +153,7 @@ describe('React Native Navigation Instrumentation', () => { mockEventsRegistry.onCommand('root', {}); - expect(spanToJSON(getActiveSpan()!).description).toEqual('Route Change'); + expect(spanToStaticSpanJSON(getActiveSpan()!).description).toEqual('Route Change'); expect(getActiveSpan()!.isRecording()).toBe(true); await jest.runAllTimersAsync(); diff --git a/packages/core/test/tracing/reactnavigation.stalltracking.test.ts b/packages/core/test/tracing/reactnavigation.stalltracking.test.ts index e33da6c377..b2c37104aa 100644 --- a/packages/core/test/tracing/reactnavigation.stalltracking.test.ts +++ b/packages/core/test/tracing/reactnavigation.stalltracking.test.ts @@ -3,13 +3,14 @@ jest.mock('../../src/js/tracing/utils', () => ({ isNearToNow: jest.fn(), })); -import { getCurrentScope, getGlobalScope, getIsolationScope, setCurrentClient, startSpanManual } from '@sentry/core'; +import { setCurrentClient, startSpanManual } from '@sentry/core'; import { reactNativeTracingIntegration, reactNavigationIntegration } from '../../src/js'; import { stallTrackingIntegration } from '../../src/js/tracing/integrations/stalltracking'; import { isNearToNow } from '../../src/js/tracing/utils'; import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; +import { clearAllScopes } from '../testutils'; import { expectStallMeasurements } from './integrations/stallTracking/stalltrackingutils'; import { createMockNavigationAndAttachTo } from './reactnavigationutils'; @@ -25,9 +26,7 @@ describe('StallTracking with ReactNavigation', () => { beforeEach(() => { RN_GLOBAL_OBJ.__sentry_rn_v5_registered = false; - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const rnavigation = reactNavigationIntegration(); mockNavigation = createMockNavigationAndAttachTo(rnavigation); diff --git a/packages/core/test/tracing/reactnavigation.test.ts b/packages/core/test/tracing/reactnavigation.test.ts index c04743968d..8aa92c3d65 100644 --- a/packages/core/test/tracing/reactnavigation.test.ts +++ b/packages/core/test/tracing/reactnavigation.test.ts @@ -1,14 +1,7 @@ import type { Event, Measurements, SentrySpan, StartSpanOptions } from '@sentry/core'; import * as core from '@sentry/core'; -import { - getActiveSpan, - getCurrentScope, - getGlobalScope, - getIsolationScope, - setCurrentClient, - spanToJSON, -} from '@sentry/core'; +import { getActiveSpan, setCurrentClient, spanToStaticSpanJSON } from '@sentry/core'; import type { NavigationRoute } from '../../src/js/tracing/reactnavigation'; import type { UnsafeAction } from '../../src/js/vendor/react-navigation/types'; @@ -38,6 +31,7 @@ import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide'; import { mockAppRegistryIntegration } from '../mocks/appRegistryIntegrationMock'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; import { NATIVE } from '../mockWrapper'; +import { clearAllScopes } from '../testutils'; import { getDevServer } from './../../src/js/integrations/debugsymbolicatorutils'; import { createMockNavigationAndAttachTo, createMockNavigationWithNestedState } from './reactnavigationutils'; @@ -122,9 +116,7 @@ describe('ReactNavigationInstrumentation', () => { jest.clearAllMocks(); RN_GLOBAL_OBJ.__sentry_rn_v5_registered = false; - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); }); test('transaction set on initialize', async () => { @@ -903,7 +895,7 @@ describe('ReactNavigationInstrumentation', () => { await jest.runOnlyPendingTimersAsync(); - expect(spanToJSON(mockTransaction).data?.['sentry.rn.discard_reason']).toBeUndefined(); + expect(spanToStaticSpanJSON(mockTransaction).data?.['sentry.rn.discard_reason']).toBeUndefined(); expect(mockTransaction['_sampled']).not.toBe(false); }); @@ -927,7 +919,7 @@ describe('ReactNavigationInstrumentation', () => { const span = getActiveSpan(); expect(span).toBeDefined(); - expect(spanToJSON(span)).toEqual( + expect(spanToStaticSpanJSON(span)).toEqual( expect.objectContaining({ description: DEFAULT_NAVIGATION_SPAN_NAME, op: 'navigation', @@ -994,7 +986,7 @@ describe('ReactNavigationInstrumentation', () => { const span = getActiveSpan(); expect(span).toBeDefined(); - expect(spanToJSON(span!).description).toBe('Route'); + expect(spanToStaticSpanJSON(span!).description).toBe('Route'); await jest.runOnlyPendingTimersAsync(); await client.flush(); @@ -1134,7 +1126,7 @@ describe('ReactNavigationInstrumentation', () => { const span = getActiveSpan(); expect(span).toBeDefined(); - expect(spanToJSON(span!).op).toBe('navigation'); + expect(spanToStaticSpanJSON(span!).op).toBe('navigation'); }); }); @@ -1158,7 +1150,7 @@ describe('ReactNavigationInstrumentation', () => { expect(mockTransaction['_sampled']).toBe(true); expect(mockTransaction['_name']).toBe(DEFAULT_NAVIGATION_SPAN_NAME); - expect(spanToJSON(mockTransaction).data?.['sentry.rn.discard_reason']).toBeUndefined(); + expect(spanToStaticSpanJSON(mockTransaction).data?.['sentry.rn.discard_reason']).toBeUndefined(); jest.advanceTimersByTime(20); @@ -1168,7 +1160,7 @@ describe('ReactNavigationInstrumentation', () => { // `ignoreEmptyRouteChangeTransactions` runs after `_discardLatestTransaction` // and overwrites the reason with the more specific `no_route_info`. expect(mockTransaction['_sampled']).toBe(true); - expect(spanToJSON(mockTransaction).data?.['sentry.rn.discard_reason']).toBe('no_route_info'); + expect(spanToStaticSpanJSON(mockTransaction).data?.['sentry.rn.discard_reason']).toBe('no_route_info'); }); }); diff --git a/packages/core/test/tracing/reactnavigation.ttid.test.tsx b/packages/core/test/tracing/reactnavigation.ttid.test.tsx index b63f2cdc4e..cd4a3fbd5b 100644 --- a/packages/core/test/tracing/reactnavigation.ttid.test.tsx +++ b/packages/core/test/tracing/reactnavigation.ttid.test.tsx @@ -1,6 +1,6 @@ -import type { Scope, Span, SpanJSON, TransactionEvent, Transport } from '@sentry/core'; +import type { SpanJSON, TransactionEvent, Transport } from '@sentry/core'; -import { getActiveSpan, spanToJSON, timestampInSeconds } from '@sentry/core'; +import { _INTERNAL_setSpanForScope, getActiveSpan, spanToStaticSpanJSON, timestampInSeconds } from '@sentry/core'; import * as TestRenderer from '@testing-library/react-native'; import * as React from 'react'; @@ -30,12 +30,6 @@ import { nowInSeconds, secondInFutureTimestampMs } from '../testutils'; import { mockRecordedTimeToDisplay } from './mockedtimetodisplaynative'; import { createMockNavigationAndAttachTo } from './reactnavigationutils'; -const SCOPE_SPAN_FIELD = '_sentrySpan'; - -type ScopeWithMaybeSpan = Scope & { - [SCOPE_SPAN_FIELD]?: Span; -}; - describe('React Navigation - TTID', () => { let transportSendMock: jest.Mock, Parameters>; let mockedNavigation: ReturnType; @@ -117,7 +111,7 @@ describe('React Navigation - TTID', () => { mockedNavigation.navigateToNewScreen(); mockAutomaticTimeToDisplay(); - (Sentry.getCurrentScope() as ScopeWithMaybeSpan)[SCOPE_SPAN_FIELD] = undefined; + _INTERNAL_setSpanForScope(Sentry.getCurrentScope(), undefined); jest.runOnlyPendingTimers(); // Flush transaction const transaction = getLastTransaction(transportSendMock); @@ -154,7 +148,10 @@ describe('React Navigation - TTID', () => { mockedNavigation.navigateToNewScreen(); mockAutomaticTimeToDisplay(); - (Sentry.getCurrentScope() as ScopeWithMaybeSpan)[SCOPE_SPAN_FIELD] = startSpanManual({ name: 'test' }, s => s); + _INTERNAL_setSpanForScope( + Sentry.getCurrentScope(), + startSpanManual({ name: 'test' }, s => s), + ); jest.runOnlyPendingTimers(); // Flush transaction const transaction = getLastTransaction(transportSendMock); @@ -223,7 +220,8 @@ describe('React Navigation - TTID', () => { data: { 'sentry.op': 'navigation.processing', 'sentry.origin': SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NAVIGATION, - 'sentry.source': 'custom', + // JS v11 no longer sets a default `sentry.source: 'custom'` on + // child spans; source now lives on the segment only. [SPAN_THREAD_NAME]: SPAN_THREAD_NAME_JAVASCRIPT, }, description: 'Navigation dispatch to screen New Screen mounted', @@ -252,7 +250,8 @@ describe('React Navigation - TTID', () => { data: { 'sentry.op': 'navigation.processing', 'sentry.origin': SPAN_ORIGIN_AUTO_NAVIGATION_REACT_NAVIGATION, - 'sentry.source': 'custom', + // JS v11 no longer sets a default `sentry.source: 'custom'` on + // child spans; source now lives on the segment only. [SPAN_THREAD_NAME]: SPAN_THREAD_NAME_JAVASCRIPT, }, description: 'Navigation dispatch to screen Initial Screen mounted', @@ -304,10 +303,10 @@ describe('React Navigation - TTID', () => { TestRenderer.render(); mockRecordedTimeToDisplay({ ttidNavigation: { - [spanToJSON(getActiveSpan()).span_id]: nowInSeconds(), + [spanToStaticSpanJSON(getActiveSpan()).span_id]: nowInSeconds(), }, ttfd: { - [spanToJSON(getActiveSpan()).span_id]: nowInSeconds(), + [spanToStaticSpanJSON(getActiveSpan()).span_id]: nowInSeconds(), }, }); @@ -383,10 +382,10 @@ describe('React Navigation - TTID', () => { TestRenderer.render(); mockRecordedTimeToDisplay({ ttidNavigation: { - [spanToJSON(getActiveSpan()).span_id]: timestampInSeconds(), + [spanToStaticSpanJSON(getActiveSpan()).span_id]: timestampInSeconds(), }, ttfd: { - [spanToJSON(getActiveSpan()).span_id]: timestampInSeconds() - 1, + [spanToStaticSpanJSON(getActiveSpan()).span_id]: timestampInSeconds() - 1, }, }); @@ -412,10 +411,10 @@ describe('React Navigation - TTID', () => { TestRenderer.render(); mockRecordedTimeToDisplay({ ttidNavigation: { - [spanToJSON(getActiveSpan()).span_id]: timestampInSeconds(), + [spanToStaticSpanJSON(getActiveSpan()).span_id]: timestampInSeconds(), }, ttfd: { - [spanToJSON(getActiveSpan()).span_id]: timestampInSeconds(), + [spanToStaticSpanJSON(getActiveSpan()).span_id]: timestampInSeconds(), }, }); @@ -451,8 +450,8 @@ describe('React Navigation - TTID', () => { jest.runOnlyPendingTimers(); // Flush app start transaction mockedNavigation.navigateToNewScreen(); - const activeSpanId = spanToJSON(getActiveSpan()).span_id; - const transactionStartTimestamp = spanToJSON(getActiveSpan()).start_timestamp; + const activeSpanId = spanToStaticSpanJSON(getActiveSpan()).span_id; + const transactionStartTimestamp = spanToStaticSpanJSON(getActiveSpan()).start_timestamp; // Simulate a stale TTID 60 seconds after the transaction start mockRecordedTimeToDisplay({ @@ -551,7 +550,7 @@ describe('React Navigation - TTID', () => { timeToDisplayComponent.update(); mockRecordedTimeToDisplay({ ttid: { - [spanToJSON(getActiveSpan()).span_id]: manualInitialDisplayEndTimestampMs / 1_000, + [spanToStaticSpanJSON(getActiveSpan()).span_id]: manualInitialDisplayEndTimestampMs / 1_000, }, }); @@ -742,7 +741,7 @@ describe('React Navigation - TTID', () => { function mockAutomaticTimeToDisplay(): void { mockRecordedTimeToDisplay({ ttidNavigation: { - [spanToJSON(getActiveSpan()).span_id]: nowInSeconds(), + [spanToStaticSpanJSON(getActiveSpan()).span_id]: nowInSeconds(), }, }); } diff --git a/packages/core/test/tracing/timetodisplay.test.tsx b/packages/core/test/tracing/timetodisplay.test.tsx index c12521157c..c51d427b09 100644 --- a/packages/core/test/tracing/timetodisplay.test.tsx +++ b/packages/core/test/tracing/timetodisplay.test.tsx @@ -1,15 +1,6 @@ import type { Event, Measurements, Span, SpanJSON } from '@sentry/core'; -import { - debug, - getCurrentScope, - getGlobalScope, - getIsolationScope, - setCurrentClient, - spanToJSON, - startSpan, - startSpanManual, -} from '@sentry/core'; +import { debug, setCurrentClient, spanToJSON, startSpan, startSpanManual } from '@sentry/core'; jest.spyOn(debug, 'warn'); @@ -42,7 +33,7 @@ import { } from '../../src/js/tracing/timetodisplay'; import { _resetTimeToDisplayCoordinator } from '../../src/js/tracing/timeToDisplayCoordinator'; import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; -import { nowInSeconds, secondAgoTimestampMs, secondInFutureTimestampMs } from '../testutils'; +import { clearAllScopes, nowInSeconds, secondAgoTimestampMs, secondInFutureTimestampMs } from '../testutils'; jest.mock('../../src/js/utils/environment', () => ({ isWeb: jest.fn().mockReturnValue(false), @@ -81,9 +72,7 @@ describe('TimeToDisplay', () => { beforeEach(() => { clearMockedOnDrawReportedProps(); _resetTimeToDisplayCoordinator(); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0, @@ -534,9 +523,7 @@ describe('Frame Data', () => { beforeEach(() => { clearMockedOnDrawReportedProps(); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0, @@ -1004,9 +991,7 @@ describe('reportFullyDisplayed', () => { clearMockedOnDrawReportedProps(); _resetTimeToDisplayCoordinator(); _resetImperativeTtfdTimestamps(); - getCurrentScope().clear(); - getIsolationScope().clear(); - getGlobalScope().clear(); + clearAllScopes(); const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0, diff --git a/packages/core/test/turbomodule/turboModuleTracker.test.ts b/packages/core/test/turbomodule/turboModuleTracker.test.ts index 67a800d10b..e4e775e640 100644 --- a/packages/core/test/turbomodule/turboModuleTracker.test.ts +++ b/packages/core/test/turbomodule/turboModuleTracker.test.ts @@ -8,6 +8,8 @@ import { pushTurboModuleCall, relabelTurboModuleCallKind, } from '../../src/js/turbomodule/turboModuleTracker'; +// JS v11 removed `Scope.clear()`; `resetScope` mirrors exactly what it reset. +import { resetScope } from '../testutils'; describe('turboModuleTracker', () => { let scope: Scope; @@ -192,7 +194,7 @@ describe('turboModuleTracker', () => { // forked current scope never reach the native SDKs — and native crashes // captured during a TurboModule call lose the `turbo_module` attribution. const isolation = getIsolationScope(); - isolation.clear(); + resetScope(isolation); const id = pushTurboModuleCall({ name: 'RNSentry', method: 'crash', kind: 'sync' }); @@ -205,7 +207,7 @@ describe('turboModuleTracker', () => { expect(isolation.getScopeData().tags['turbo_module.method']).toBe('crash'); popTurboModuleCall(id); - isolation.clear(); + resetScope(isolation); }); it('is a no-op when popping an unknown id', () => { diff --git a/samples/expo/package.json b/samples/expo/package.json index e0e9b84620..839cf2e6de 100644 --- a/samples/expo/package.json +++ b/samples/expo/package.json @@ -23,7 +23,7 @@ "eas-build-development-android": "eas build --profile development --platform android" }, "dependencies": { - "@sentry/core": "10.73.0", + "@sentry/core": "11.0.0-beta.0", "@sentry/react-native": "8.25.0", "@types/react": "~19.2.10", "expo": "^57.0.0", @@ -47,7 +47,7 @@ "devDependencies": { "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", - "@sentry/bundler-plugins": "10.73.0", + "@sentry/bundler-plugins": "11.0.0-beta.0", "@types/node": "20.10.4", "sentry-react-native-samples-utils": "workspace:^" }, diff --git a/samples/react-native-macos/package.json b/samples/react-native-macos/package.json index 851ca34e24..de5144ddd9 100644 --- a/samples/react-native-macos/package.json +++ b/samples/react-native-macos/package.json @@ -16,8 +16,8 @@ "@react-navigation/bottom-tabs": "^6.5.12", "@react-navigation/native": "^6.1.9", "@react-navigation/stack": "^6.3.20", - "@sentry/core": "10.73.0", - "@sentry/react": "10.73.0", + "@sentry/core": "11.0.0-beta.0", + "@sentry/react": "11.0.0-beta.0", "@sentry/react-native": "8.25.0", "delay": "^6.0.0", "react": "19.1.4", diff --git a/samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.ts b/samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.ts index 44f8b0dc84..d9d7b09050 100644 --- a/samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.ts +++ b/samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.ts @@ -74,7 +74,9 @@ describe('Capture Errors Screen Transaction', () => { 'sentry.op': 'ui.load', 'sentry.origin': 'auto.app.start', 'sentry.sample_rate': 1, - 'sentry.source': 'component', + // JS v11 removed `sentry.source`; the transaction name source now + // lives under `sentry.segment.name.source` on the segment span. + 'sentry.segment.name.source': 'component', 'thread.name': 'javascript', }), op: 'ui.load', diff --git a/samples/react-native/package.json b/samples/react-native/package.json index b694b16ee6..1af0837d83 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -48,8 +48,8 @@ "@react-navigation/native-stack": "^7.3.24", "@react-navigation/stack": "^7.4.5", "@reduxjs/toolkit": "^2.8.2", - "@sentry/core": "10.73.0", - "@sentry/react": "10.73.0", + "@sentry/core": "11.0.0-beta.0", + "@sentry/react": "11.0.0-beta.0", "@sentry/react-native": "8.25.0", "@shopify/flash-list": "^2.0.2", "delay": "^6.0.0", @@ -79,7 +79,7 @@ "@react-native/jest-preset": "0.87.1", "@react-native/metro-config": "0.87.1", "@react-native/typescript-config": "0.87.1", - "@sentry/bundler-plugins": "10.73.0", + "@sentry/bundler-plugins": "11.0.0-beta.0", "@testing-library/react-native": "^13.2.2", "@types/jest": "^29.5.14", "@types/node": "^22.13.1", diff --git a/samples/react-native/src/Screens/TurboModuleScreen.tsx b/samples/react-native/src/Screens/TurboModuleScreen.tsx index 23102398b7..bbba2de78f 100644 --- a/samples/react-native/src/Screens/TurboModuleScreen.tsx +++ b/samples/react-native/src/Screens/TurboModuleScreen.tsx @@ -10,7 +10,7 @@ import { } from 'react-native'; import { StackNavigationProp } from '@react-navigation/stack'; -import { spanToJSON, startNewTrace } from '@sentry/core'; +import { spanToStaticSpanJSON, startNewTrace } from '@sentry/core'; import * as Sentry from '@sentry/react-native'; import NativePlatformSampleModule from '../../tm/NativePlatformSampleModule'; @@ -34,7 +34,9 @@ interface Props { const isNewArchitecture = !!NativeSampleModule && !!NativePlatformSampleModule; function collectTurboModuleAttributes(span: Sentry.Span): string[] { - const data = spanToJSON(span).data ?? {}; + // JS v11's `spanToJSON` returns the streamed shape (no `.data`); the classic + // `.data` bag of span attributes now lives on `spanToStaticSpanJSON`. + const data = spanToStaticSpanJSON(span).data ?? {}; return Object.keys(data) .filter(key => key.startsWith(ATTRIBUTE_PREFIX)) .sort() diff --git a/yarn.lock b/yarn.lock index 4df898b278..89acb55fb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9960,41 +9960,43 @@ __metadata: languageName: node linkType: hard -"@sentry/browser-utils@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/browser-utils@npm:10.73.0" +"@sentry/browser-utils@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/browser-utils@npm:11.0.0-beta.0" dependencies: - "@sentry/conventions": "npm:^0.16.0" - "@sentry/core": "npm:10.73.0" - checksum: 10c0/01ce6ee0f8b009560a6a05d073a244152011aea0fbdc4198f16308bace6ef80a0dd9dcf5d0dacc3bf2198da1d346159e67c17060ab6720b28f73e8766c0e784c + "@sentry/conventions": "npm:^0.20.0" + "@sentry/core": "npm:11.0.0-beta.0" + web-vitals: "npm:^6.0.1" + checksum: 10c0/0cdb625fbc52ee758f0542859aefe516daa47e29245870ed1365e0a4451a37cb578c6367401ea920671ca011c504a445e8b8d4561171b7e2322c5ec30e28269d languageName: node linkType: hard -"@sentry/browser@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/browser@npm:10.73.0" +"@sentry/browser@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/browser@npm:11.0.0-beta.0" dependencies: - "@sentry/browser-utils": "npm:10.73.0" - "@sentry/conventions": "npm:^0.16.0" - "@sentry/core": "npm:10.73.0" - "@sentry/feedback": "npm:10.73.0" - "@sentry/replay": "npm:10.73.0" - "@sentry/replay-canvas": "npm:10.73.0" - checksum: 10c0/5d6f9581ca8c142d6a4c409c9f34c6f3f5585c3006da12e952836ff83b9bf4fd6c0e0f650b245be4d5497ac4bb8f34d7c658a79300ecd2b165db262fd36c3544 + "@sentry/browser-utils": "npm:11.0.0-beta.0" + "@sentry/conventions": "npm:^0.20.0" + "@sentry/core": "npm:11.0.0-beta.0" + "@sentry/feedback": "npm:11.0.0-beta.0" + "@sentry/replay": "npm:11.0.0-beta.0" + "@sentry/replay-canvas": "npm:11.0.0-beta.0" + checksum: 10c0/f95aee1b3f51e4b9f0fbe8b1727967d19fbde883f9a6aad0f43d6c6cc55c4376149fc6f8b15d8ced376b88cc3b38768d532fadae89e96c0055db22a9b9157087 languageName: node linkType: hard -"@sentry/bundler-plugins@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/bundler-plugins@npm:10.73.0" +"@sentry/bundler-plugins@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/bundler-plugins@npm:11.0.0-beta.0" dependencies: "@babel/core": "npm:^7.18.5" - "@sentry/cli": "npm:^2.58.6" - "@sentry/core": "npm:10.73.0" + "@sentry/core": "npm:11.0.0-beta.0" dotenv: "npm:^17.4.2" find-up: "npm:^5.0.0" glob: "npm:^13.0.6" magic-string: "npm:~0.30.8" + sentry: "npm:^0.44.0" + supports-color: "npm:^8.1.1" peerDependencies: rollup: ">=3.2.0" webpack: ">=5.0.0" @@ -10003,14 +10005,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10c0/4dc13135609328978863d885edd3452368bd7da2d35d9ed6c85e8304df34485758ad465b3d6aad528cdd56cafe261c47c315c628e0ba25dffc9082ac3c3a322e - languageName: node - linkType: hard - -"@sentry/cli-darwin@npm:2.58.6": - version: 2.58.6 - resolution: "@sentry/cli-darwin@npm:2.58.6" - conditions: os=darwin + checksum: 10c0/5d69156efc815134e114a03cde0d9db91e7177cdcac7346ea83cc62662744c04c663a8e878cc8401398f821b2166f8fef302d48e264f9d8b21dbe018cee9eff4 languageName: node linkType: hard @@ -10021,13 +10016,6 @@ __metadata: languageName: node linkType: hard -"@sentry/cli-linux-arm64@npm:2.58.6": - version: 2.58.6 - resolution: "@sentry/cli-linux-arm64@npm:2.58.6" - conditions: (os=linux | os=freebsd | os=android) & cpu=arm64 - languageName: node - linkType: hard - "@sentry/cli-linux-arm64@npm:3.7.0": version: 3.7.0 resolution: "@sentry/cli-linux-arm64@npm:3.7.0" @@ -10035,13 +10023,6 @@ __metadata: languageName: node linkType: hard -"@sentry/cli-linux-arm@npm:2.58.6": - version: 2.58.6 - resolution: "@sentry/cli-linux-arm@npm:2.58.6" - conditions: (os=linux | os=freebsd | os=android) & cpu=arm - languageName: node - linkType: hard - "@sentry/cli-linux-arm@npm:3.7.0": version: 3.7.0 resolution: "@sentry/cli-linux-arm@npm:3.7.0" @@ -10049,13 +10030,6 @@ __metadata: languageName: node linkType: hard -"@sentry/cli-linux-i686@npm:2.58.6": - version: 2.58.6 - resolution: "@sentry/cli-linux-i686@npm:2.58.6" - conditions: (os=linux | os=freebsd | os=android) & (cpu=x86 | cpu=ia32) - languageName: node - linkType: hard - "@sentry/cli-linux-i686@npm:3.7.0": version: 3.7.0 resolution: "@sentry/cli-linux-i686@npm:3.7.0" @@ -10063,13 +10037,6 @@ __metadata: languageName: node linkType: hard -"@sentry/cli-linux-x64@npm:2.58.6": - version: 2.58.6 - resolution: "@sentry/cli-linux-x64@npm:2.58.6" - conditions: (os=linux | os=freebsd | os=android) & cpu=x64 - languageName: node - linkType: hard - "@sentry/cli-linux-x64@npm:3.7.0": version: 3.7.0 resolution: "@sentry/cli-linux-x64@npm:3.7.0" @@ -10077,13 +10044,6 @@ __metadata: languageName: node linkType: hard -"@sentry/cli-win32-arm64@npm:2.58.6": - version: 2.58.6 - resolution: "@sentry/cli-win32-arm64@npm:2.58.6" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@sentry/cli-win32-arm64@npm:3.7.0": version: 3.7.0 resolution: "@sentry/cli-win32-arm64@npm:3.7.0" @@ -10091,13 +10051,6 @@ __metadata: languageName: node linkType: hard -"@sentry/cli-win32-i686@npm:2.58.6": - version: 2.58.6 - resolution: "@sentry/cli-win32-i686@npm:2.58.6" - conditions: os=win32 & (cpu=x86 | cpu=ia32) - languageName: node - linkType: hard - "@sentry/cli-win32-i686@npm:3.7.0": version: 3.7.0 resolution: "@sentry/cli-win32-i686@npm:3.7.0" @@ -10105,13 +10058,6 @@ __metadata: languageName: node linkType: hard -"@sentry/cli-win32-x64@npm:2.58.6": - version: 2.58.6 - resolution: "@sentry/cli-win32-x64@npm:2.58.6" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@sentry/cli-win32-x64@npm:3.7.0": version: 3.7.0 resolution: "@sentry/cli-win32-x64@npm:3.7.0" @@ -10158,46 +10104,6 @@ __metadata: languageName: node linkType: hard -"@sentry/cli@npm:^2.58.6": - version: 2.58.6 - resolution: "@sentry/cli@npm:2.58.6" - dependencies: - "@sentry/cli-darwin": "npm:2.58.6" - "@sentry/cli-linux-arm": "npm:2.58.6" - "@sentry/cli-linux-arm64": "npm:2.58.6" - "@sentry/cli-linux-i686": "npm:2.58.6" - "@sentry/cli-linux-x64": "npm:2.58.6" - "@sentry/cli-win32-arm64": "npm:2.58.6" - "@sentry/cli-win32-i686": "npm:2.58.6" - "@sentry/cli-win32-x64": "npm:2.58.6" - https-proxy-agent: "npm:^5.0.0" - node-fetch: "npm:^2.6.7" - progress: "npm:^2.0.3" - proxy-from-env: "npm:^1.1.0" - which: "npm:^2.0.2" - dependenciesMeta: - "@sentry/cli-darwin": - optional: true - "@sentry/cli-linux-arm": - optional: true - "@sentry/cli-linux-arm64": - optional: true - "@sentry/cli-linux-i686": - optional: true - "@sentry/cli-linux-x64": - optional: true - "@sentry/cli-win32-arm64": - optional: true - "@sentry/cli-win32-i686": - optional: true - "@sentry/cli-win32-x64": - optional: true - bin: - sentry-cli: bin/sentry-cli - checksum: 10c0/3037c8c03d0b56114d4bdccc409a88b2e48df3544b2ddd800556a70d45b72e889e1dc5fc1a2997d962f9d047e360d30e5090d8f454078c9d7be1545ad32e7c0c - languageName: node - linkType: hard - "@sentry/conventions@npm:^0.16.0": version: 0.16.0 resolution: "@sentry/conventions@npm:0.16.0" @@ -10205,6 +10111,13 @@ __metadata: languageName: node linkType: hard +"@sentry/conventions@npm:^0.20.0": + version: 0.20.0 + resolution: "@sentry/conventions@npm:0.20.0" + checksum: 10c0/8b294e5a7c64b0e46b3d5056043f31c86c1e295c46045734d9149ad57587824608cb9f01498e6abe4492becab842e73c4eb27285e7c981ac67cd3a354060efaf + languageName: node + linkType: hard + "@sentry/core@npm:10.70.0": version: 10.70.0 resolution: "@sentry/core@npm:10.70.0" @@ -10214,19 +10127,19 @@ __metadata: languageName: node linkType: hard -"@sentry/core@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/core@npm:10.73.0" +"@sentry/core@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/core@npm:11.0.0-beta.0" dependencies: - "@sentry/conventions": "npm:^0.16.0" - checksum: 10c0/553469fd82ec11f5697ab4b2881b6f811834a1036e88afe24920940f131d59ad1dfcd008b99656661c49c7c9de51678719b884b0a6cfcb696312fd58bb911495 + "@sentry/conventions": "npm:^0.20.0" + checksum: 10c0/78e549da893ca0f1bc076bccf648877db6c48d3e2f83c0b77468e76a79407951f12ea59eddb1791c43a35d71c5a9f323464a577a3df515d11ec06a8cd9518645 languageName: node linkType: hard -"@sentry/eslint-plugin-sdk@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/eslint-plugin-sdk@npm:10.73.0" - checksum: 10c0/2c6ece825d2782e8cfa76e373c9588411e16368116ac99fb70a938f39254e78da00e216a037ff80ddafaf752bc41fa1f95256fe5640d6a0f94c501004b6f7be4 +"@sentry/eslint-plugin-sdk@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/eslint-plugin-sdk@npm:11.0.0-beta.0" + checksum: 10c0/22906197cae5f64f0480d022b9bce475f332918be52b7de5c35124a948724680e5e4b67467f14720354f0beb7a85906c76052ee8648662994b92713dc05ccc13 languageName: node linkType: hard @@ -10248,12 +10161,12 @@ __metadata: languageName: unknown linkType: soft -"@sentry/feedback@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/feedback@npm:10.73.0" +"@sentry/feedback@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/feedback@npm:11.0.0-beta.0" dependencies: - "@sentry/core": "npm:10.73.0" - checksum: 10c0/7dcb318f742a9d4aa5379be889df6f55fab9e5dcbaed0a0a1c90565f8ea6047c562435616c5bbe5a78bdb3a31dc3d023b16ff082c6c8c908aaec8255da801112 + "@sentry/core": "npm:11.0.0-beta.0" + checksum: 10c0/978e4ebfcaa08b6c498e2db6e354d3183e2c4a7391769f009ad2523a29cc859939a382ab87f940f1cd71fdce28b08ee3e4c3f19f34e7d4eeeb23d2cdaec1b0dd languageName: node linkType: hard @@ -10327,14 +10240,14 @@ __metadata: "@mswjs/interceptors": "npm:^0.25.15" "@react-native/babel-preset": "npm:0.87.1" "@react-native/jest-preset": "npm:0.87.1" - "@sentry/browser": "npm:10.73.0" - "@sentry/bundler-plugins": "npm:10.73.0" + "@sentry/browser": "npm:11.0.0-beta.0" + "@sentry/bundler-plugins": "npm:11.0.0-beta.0" "@sentry/cli": "npm:3.7.0" - "@sentry/core": "npm:10.73.0" - "@sentry/eslint-plugin-sdk": "npm:10.73.0" + "@sentry/core": "npm:11.0.0-beta.0" + "@sentry/eslint-plugin-sdk": "npm:11.0.0-beta.0" "@sentry/expo-upload-sourcemaps": "workspace:*" - "@sentry/react": "npm:10.73.0" - "@sentry/typescript": "npm:10.73.0" + "@sentry/react": "npm:11.0.0-beta.0" + "@sentry/typescript": "npm:11.0.0-beta.0" "@sentry/wizard": "npm:7.0.3" "@testing-library/react-native": "npm:^13.2.2" "@types/jest": "npm:^29.5.13" @@ -10380,36 +10293,36 @@ __metadata: languageName: unknown linkType: soft -"@sentry/react@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/react@npm:10.73.0" +"@sentry/react@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/react@npm:11.0.0-beta.0" dependencies: - "@sentry/browser": "npm:10.73.0" - "@sentry/conventions": "npm:^0.16.0" - "@sentry/core": "npm:10.73.0" + "@sentry/browser": "npm:11.0.0-beta.0" + "@sentry/conventions": "npm:^0.20.0" + "@sentry/core": "npm:11.0.0-beta.0" peerDependencies: - react: ^16.14.0 || 17.x || 18.x || 19.x - checksum: 10c0/84afea9290b8a59486566abc011dfee353c0427cf9a22725444a762153a10c31d61eb7b2e5c8cc458bf245e9974b9a2993dadf066e976e5c429bf324559ecefc + react: 17.x || 18.x || 19.x + checksum: 10c0/89934c6b6bfacee15df37eaa165bf19a73116f8741e2bd6c4aa0a98791abe6cfbc1c7488d0438010992ce44483589947ea27583f884b951729e63be95f577eb3 languageName: node linkType: hard -"@sentry/replay-canvas@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/replay-canvas@npm:10.73.0" +"@sentry/replay-canvas@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/replay-canvas@npm:11.0.0-beta.0" dependencies: - "@sentry/core": "npm:10.73.0" - "@sentry/replay": "npm:10.73.0" - checksum: 10c0/1ba25555afaa65bfc731a2b997a73a24c0f502ef15514dca9e068bb44acb3538fafe317c6e7fb3fe58df9a6f1a7915245f61dd4ab1ee6e4ef2bb713280e2fe58 + "@sentry/core": "npm:11.0.0-beta.0" + "@sentry/replay": "npm:11.0.0-beta.0" + checksum: 10c0/92ebf536299c93099d3c94d35e80461deb896250616251da0ddebea5dabfd43543e3d975508d0843cfb5c94681a79b7b529ee8f47ed8f5c43dfedb9a108dac88 languageName: node linkType: hard -"@sentry/replay@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/replay@npm:10.73.0" +"@sentry/replay@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/replay@npm:11.0.0-beta.0" dependencies: - "@sentry/browser-utils": "npm:10.73.0" - "@sentry/core": "npm:10.73.0" - checksum: 10c0/f0bfaa9d824afd2abc91d594b24c75d4509908c16761bb6a62e5aeb70c0a3665b40b07e75d4bfdebaa735a4fc29057783fd7465b08b22744221ca7a3d5913ca1 + "@sentry/browser-utils": "npm:11.0.0-beta.0" + "@sentry/core": "npm:11.0.0-beta.0" + checksum: 10c0/58a5a6ea5d1662f7bc29690e79ec9bd2ad6467bb1fe3bf70051f249505eac53df1801719ebcd920808313520bad107efbc814268c5165e58a814bfc6e7284372 languageName: node linkType: hard @@ -10426,12 +10339,12 @@ __metadata: languageName: node linkType: hard -"@sentry/typescript@npm:10.73.0": - version: 10.73.0 - resolution: "@sentry/typescript@npm:10.73.0" +"@sentry/typescript@npm:11.0.0-beta.0": + version: 11.0.0-beta.0 + resolution: "@sentry/typescript@npm:11.0.0-beta.0" peerDependencies: typescript: ~5.8.0 - checksum: 10c0/6b15c4c7e04d7e474a065c57c3ff1c04884d7cd0a04f9f464f6b4fdcf2a58132cca19157c03f7940b76b0790497fa192bbdcaf862cda812f8c3ecab234877b9b + checksum: 10c0/966a430f155e2b5efe2333c66c93691516b8d12e3587d72f0afbec05d3b9cff4c44c6e1f06fe3622e2558d6d297aa1d4eece37e5b74d5b792f6740cfde608b17 languageName: node linkType: hard @@ -18831,7 +18744,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1": +"https-proxy-agent@npm:^5.0.1": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" dependencies: @@ -23850,7 +23763,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.6, node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.6": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -28273,7 +28186,7 @@ __metadata: dependencies: "@babel/preset-env": "npm:^7.25.3" "@babel/preset-typescript": "npm:^7.18.6" - "@sentry/core": "npm:10.73.0" + "@sentry/core": "npm:11.0.0-beta.0" "@sentry/react-native": "npm:8.25.0" "@types/node": "npm:^20.9.3" "@types/react": "npm:^19.1.4" @@ -28302,8 +28215,8 @@ __metadata: dependencies: "@babel/core": "npm:^7.26.0" "@babel/preset-env": "npm:^7.26.0" - "@sentry/bundler-plugins": "npm:10.73.0" - "@sentry/core": "npm:10.73.0" + "@sentry/bundler-plugins": "npm:11.0.0-beta.0" + "@sentry/core": "npm:11.0.0-beta.0" "@sentry/react-native": "npm:8.25.0" "@types/node": "npm:20.10.4" "@types/react": "npm:~19.2.10" @@ -28344,8 +28257,8 @@ __metadata: "@react-navigation/bottom-tabs": "npm:^6.5.12" "@react-navigation/native": "npm:^6.1.9" "@react-navigation/stack": "npm:^6.3.20" - "@sentry/core": "npm:10.73.0" - "@sentry/react": "npm:10.73.0" + "@sentry/core": "npm:11.0.0-beta.0" + "@sentry/react": "npm:11.0.0-beta.0" "@sentry/react-native": "npm:8.25.0" "@types/react": "npm:^19.1.4" "@types/react-native-vector-icons": "npm:^6.4.18" @@ -28390,9 +28303,9 @@ __metadata: "@react-navigation/native-stack": "npm:^7.3.24" "@react-navigation/stack": "npm:^7.4.5" "@reduxjs/toolkit": "npm:^2.8.2" - "@sentry/bundler-plugins": "npm:10.73.0" - "@sentry/core": "npm:10.73.0" - "@sentry/react": "npm:10.73.0" + "@sentry/bundler-plugins": "npm:11.0.0-beta.0" + "@sentry/core": "npm:11.0.0-beta.0" + "@sentry/react": "npm:11.0.0-beta.0" "@sentry/react-native": "npm:8.25.0" "@shopify/flash-list": "npm:^2.0.2" "@testing-library/react-native": "npm:^13.2.2" @@ -28459,6 +28372,15 @@ __metadata: languageName: unknown linkType: soft +"sentry@npm:^0.44.0": + version: 0.44.1 + resolution: "sentry@npm:0.44.1" + bin: + sentry: dist/bin.cjs + checksum: 10c0/9fcbaf3761c0229460a48747f2ca744e1451462e59773e9fef21aa7bc394a3b8336fb490b5ff0873084bbd0c93019eb4c86a8c00412781359c370d4cb341e9a6 + languageName: node + linkType: hard + "serialize-error@npm:^11.0.1": version: 11.0.3 resolution: "serialize-error@npm:11.0.3" @@ -29677,7 +29599,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0, supports-color@npm:~8.1.1": +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1, supports-color@npm:~8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -31107,6 +31029,13 @@ __metadata: languageName: node linkType: hard +"web-vitals@npm:^6.0.1": + version: 6.2.1 + resolution: "web-vitals@npm:6.2.1" + checksum: 10c0/6f5cfb5875f6164898ea1097067a8ab29ed6fbd9ad38ac7ea519c110bf1d38a441a5b1e38d18f3afd17c62f9aaa88b6bac3b719bd8e29660f6d939bc12640508 + languageName: node + linkType: hard + "webdriver@npm:8.40.3": version: 8.40.3 resolution: "webdriver@npm:8.40.3"