diff --git a/CHANGELOG.md b/CHANGELOG.md index a7fef15e6d..8f6e51a0ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Add App Context `in_foreground` ([#2826](https://github.com/getsentry/sentry-react-native/pull/2826)) + ## 5.0.0 The React Native SDK version 5 supports both Legacy (from RN 0.65 and above) and New Architecture (from RN 0.69 and above) as well as the new React Native Gradle Plugin (introduced in RN 0.71). For detailed [migration guide visit our docs](https://docs.sentry.io/platforms/react-native/migration/#from-4x-to-5x). diff --git a/src/js/integrations/devicecontext.ts b/src/js/integrations/devicecontext.ts index 1b0c193e1f..8e79970db5 100644 --- a/src/js/integrations/devicecontext.ts +++ b/src/js/integrations/devicecontext.ts @@ -1,6 +1,7 @@ /* eslint-disable complexity */ import type { Event, EventProcessor, Hub, Integration } from '@sentry/types'; import { logger, severityLevelFromString } from '@sentry/utils'; +import { AppState } from 'react-native'; import { breadcrumbFromObject } from '../breadcrumb'; import type { NativeDeviceContextsResponse } from '../NativeRNSentry'; @@ -47,7 +48,14 @@ export class DeviceContext implements Integration { event.user = nativeUser; } - const nativeContext = native.context; + let nativeContext = native.context; + if (AppState.currentState !== 'unknown') { + nativeContext = nativeContext || {}; + nativeContext.app = { + ...nativeContext.app, + in_foreground: AppState.currentState === 'active', + } + } if (nativeContext) { event.contexts = { ...nativeContext, ...event.contexts }; } diff --git a/test/integrations/devicecontext.test.ts b/test/integrations/devicecontext.test.ts index b3473f54b2..b4898f3aba 100644 --- a/test/integrations/devicecontext.test.ts +++ b/test/integrations/devicecontext.test.ts @@ -5,7 +5,14 @@ import { DeviceContext } from '../../src/js/integrations'; import type { NativeDeviceContextsResponse } from '../../src/js/NativeRNSentry'; import { NATIVE } from '../../src/js/wrapper'; +let mockCurrentAppState: string = 'unknown'; + jest.mock('../../src/js/wrapper'); +jest.mock('react-native', () => ({ + AppState: new Proxy({}, { get: () => mockCurrentAppState }), + NativeModules: {}, + Platform: {}, +})); describe('Device Context Integration', () => { let integration: DeviceContext; @@ -122,6 +129,50 @@ describe('Device Context Integration', () => { }); }); + it('adds in_foreground false to native app contexts', async () => { + mockCurrentAppState = 'background'; + const { processedEvent } = await executeIntegrationWith({ + nativeContexts: { context: { app: { native: 'value' } } }, + }); + expect(processedEvent).toStrictEqual({ + contexts: { + app: { + native: 'value', + in_foreground: false, + }, + }, + }); + }); + + it('adds in_foreground to native app contexts', async () => { + mockCurrentAppState = 'active'; + const { processedEvent } = await executeIntegrationWith({ + nativeContexts: { context: { app: { native: 'value' } } }, + }); + expect(processedEvent).toStrictEqual({ + contexts: { + app: { + native: 'value', + in_foreground: true, + }, + }, + }); + }); + + it('do not add in_foreground if unknown', async () => { + mockCurrentAppState = 'unknown'; + const { processedEvent } = await executeIntegrationWith({ + nativeContexts: { context: { app: { native: 'value' } } }, + }); + expect(processedEvent).toStrictEqual({ + contexts: { + app: { + native: 'value', + }, + }, + }); + }); + async function executeIntegrationWith({ nativeContexts, mockEvent }: { nativeContexts: Record; mockEvent?: Event;