diff --git a/CHANGELOG.md b/CHANGELOG.md index a7fef15e6..8f6e51a0f 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 1b0c193e1..8e79970db 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 b3473f54b..b4898f3ab 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;