Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Features

- Add expo constants on event context ([#5748](https://github.com/getsentry/sentry-react-native/pull/5748))
- Capture dynamic route params as span attributes for Expo Router navigations ([#5750](https://github.com/getsentry/sentry-react-native/pull/5750))

### Fixes
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/js/integrations/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
dedupeIntegration,
deviceContextIntegration,
eventOriginIntegration,
expoConstantsIntegration,
expoContextIntegration,
functionToStringIntegration,
hermesProfilingIntegration,
Expand Down Expand Up @@ -131,6 +132,7 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ
}

integrations.push(expoContextIntegration());
integrations.push(expoConstantsIntegration());

if (options.spotlight && __DEV__) {
const sidecarUrl = typeof options.spotlight === 'string' ? options.spotlight : undefined;
Expand Down
128 changes: 128 additions & 0 deletions packages/core/src/js/integrations/expoconstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import type { Event, Integration } from '@sentry/core';
import { isExpo } from '../utils/environment';
import type { ExpoConstants } from '../utils/expoglobalobject';
import { getExpoConstants } from '../utils/expomodules';

const INTEGRATION_NAME = 'ExpoConstants';

export const EXPO_CONSTANTS_CONTEXT_KEY = 'expo_constants';

/** Load Expo Constants as event context. */
export const expoConstantsIntegration = (): Integration => {
let _expoConstantsContextCached: ExpoConstantsContext | undefined;

function processEvent(event: Event): Event {
if (!isExpo()) {
return event;
}

event.contexts = event.contexts || {};
event.contexts[EXPO_CONSTANTS_CONTEXT_KEY] = {
...getExpoConstantsContextCached(),
};

return event;
}

function getExpoConstantsContextCached(): ExpoConstantsContext {
if (_expoConstantsContextCached) {
return _expoConstantsContextCached;
}

return (_expoConstantsContextCached = getExpoConstantsContext());
}

return {
name: INTEGRATION_NAME,
processEvent,
};
};

/**
* @internal Exposed for testing purposes
*/
export function getExpoConstantsContext(): ExpoConstantsContext {
const expoConstants = getExpoConstants();
if (!expoConstants) {
return {};
}

const context: ExpoConstantsContext = {};

addStringField(context, 'execution_environment', expoConstants.executionEnvironment);
addStringField(context, 'app_ownership', expoConstants.appOwnership);
addBooleanField(context, 'debug_mode', expoConstants.debugMode);
addStringField(context, 'expo_version', expoConstants.expoVersion);
addStringField(context, 'expo_runtime_version', expoConstants.expoRuntimeVersion);
addStringField(context, 'session_id', expoConstants.sessionId);
addNumberField(context, 'status_bar_height', expoConstants.statusBarHeight);

addExpoConfigFields(context, expoConstants);
addEasConfigFields(context, expoConstants);

return context;
}

function addStringField(
context: ExpoConstantsContext,
key: keyof ExpoConstantsContext,
value: string | null | undefined,
): void {
if (typeof value === 'string' && value) {
(context as Record<string, unknown>)[key] = value;
}
}

function addBooleanField(
context: ExpoConstantsContext,
key: keyof ExpoConstantsContext,
value: boolean | undefined,
): void {
if (typeof value === 'boolean') {
(context as Record<string, unknown>)[key] = value;
}
}

function addNumberField(
context: ExpoConstantsContext,
key: keyof ExpoConstantsContext,
value: number | undefined,
): void {
if (typeof value === 'number') {
(context as Record<string, unknown>)[key] = value;
}
}

function addExpoConfigFields(context: ExpoConstantsContext, expoConstants: ExpoConstants): void {
if (!expoConstants.expoConfig) {
return;
}

addStringField(context, 'app_name', expoConstants.expoConfig.name);
addStringField(context, 'app_slug', expoConstants.expoConfig.slug);
addStringField(context, 'app_version', expoConstants.expoConfig.version);
addStringField(context, 'expo_sdk_version', expoConstants.expoConfig.sdkVersion);
}

function addEasConfigFields(context: ExpoConstantsContext, expoConstants: ExpoConstants): void {
if (!expoConstants.easConfig) {
return;
}

addStringField(context, 'eas_project_id', expoConstants.easConfig.projectId);
}

type ExpoConstantsContext = Partial<{
execution_environment: string;
app_ownership: string;
debug_mode: boolean;
expo_version: string;
expo_runtime_version: string;
session_id: string;
status_bar_height: number;
app_name: string;
app_slug: string;
app_version: string;
expo_sdk_version?: string;
eas_project_id: string;
}>;
1 change: 1 addition & 0 deletions packages/core/src/js/integrations/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { hermesProfilingIntegration } from '../profiling/integration';
export { screenshotIntegration } from './screenshot';
export { viewHierarchyIntegration } from './viewhierarchy';
export { expoContextIntegration } from './expocontext';
export { expoConstantsIntegration } from './expoconstants';
export { spotlightIntegration } from './spotlight';
export { mobileReplayIntegration } from '../replay/mobilereplay';
export { feedbackIntegration } from '../feedback/integration';
Expand Down
54 changes: 47 additions & 7 deletions packages/core/src/js/utils/expoglobalobject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@
*
* https://github.com/expo/expo/blob/b51b5139f2caa2a9495e4132437d7ca612276158/packages/expo-constants/src/Constants.ts
* https://github.com/expo/expo/blob/b51b5139f2caa2a9495e4132437d7ca612276158/packages/expo-manifests/src/Manifests.ts
* https://github.com/expo/expo/blob/fce7f6eb2ea2611cb30e9cb20baaeee2ac0a18b6/packages/expo-constants/src/Constants.types.ts
*/
export interface ExpoConstants {
/**
* Deprecated. But until removed we can use it as user ID to match the native SDKs.
*/
installationId?: string;
/**
* Version of the Expo Go app
*/
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This field no longer exists on expo and is not used by out SDK.

expoVersion?: string | null;
manifest?: null | {
[key: string]: unknown;
Expand All @@ -23,6 +17,52 @@ export interface ExpoConstants {
*/
runtimeVersion?: string;
};
/**
* Returns the current execution environment.
* Values: 'bare', 'standalone', 'storeClient'
*/
executionEnvironment?: string;
/**
* Deprecated. Returns 'expo' when running in Expo Go, otherwise null.
*/
appOwnership?: string | null;
/**
* Identifies debug vs. production builds.
*/
debugMode?: boolean;
/**
* Unique identifier per app session.
*/
sessionId?: string;
/**
* Runtime version info.
*/
expoRuntimeVersion?: string | null;
/**
* Device status bar height.
*/
statusBarHeight?: number;
/**
* Available system fonts.
*/
systemFonts?: string[];
/**
* The standard Expo config object defined in app.json and app.config.js files.
*/
expoConfig?: null | {
[key: string]: unknown;
name?: string;
slug?: string;
version?: string;
sdkVersion?: string;
};
/**
* EAS configuration when applicable.
*/
easConfig?: null | {
[key: string]: unknown;
projectId?: string;
};
}

/**
Expand Down
Loading
Loading