Skip to content

Commit 4b49b57

Browse files
feat(expo): Auto collect Expo Updates Context (#4767)
1 parent ce76a0b commit 4b49b57

File tree

7 files changed

+368
-87
lines changed

7 files changed

+368
-87
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
99
## Unreleased
1010

11+
### Features
12+
13+
- Add Expo Updates Event Context ([#4767](https://github.com/getsentry/sentry-react-native/pull/4767))
14+
- Automatically collects `updateId`, `channel`, Emergency Launch Reason and other Expo Updates constants
15+
1116
### Fixes
1217

1318
- Export `extraErrorDataIntegration` from `@sentry/core` ([#4762](https://github.com/getsentry/sentry-react-native/pull/4762))

packages/core/src/js/integrations/default.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Integration } from '@sentry/core';
33

44
import type { ReactNativeClientOptions } from '../options';
55
import { reactNativeTracingIntegration } from '../tracing';
6-
import { isExpoGo, notWeb } from '../utils/environment';
6+
import { notWeb } from '../utils/environment';
77
import {
88
appRegistryIntegration,
99
appStartIntegration,
@@ -123,9 +123,7 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ
123123
integrations.push(httpClientIntegration());
124124
}
125125

126-
if (isExpoGo()) {
127-
integrations.push(expoContextIntegration());
128-
}
126+
integrations.push(expoContextIntegration());
129127

130128
if (options.spotlight) {
131129
const sidecarUrl = typeof options.spotlight === 'string' ? options.spotlight : undefined;

packages/core/src/js/integrations/expocontext.ts

+111-8
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,112 @@
1-
import type { DeviceContext, Event, Integration, OsContext } from '@sentry/core';
1+
import { type DeviceContext, type Event, type Integration, type OsContext, logger } from '@sentry/core';
22

3-
import { getExpoDevice } from '../utils/expomodules';
3+
import { isExpo, isExpoGo } from '../utils/environment';
4+
import { getExpoDevice, getExpoUpdates } from '../utils/expomodules';
5+
import { NATIVE } from '../wrapper';
46

57
const INTEGRATION_NAME = 'ExpoContext';
68

9+
export const EXPO_UPDATES_CONTEXT_KEY = 'expo_updates';
10+
711
/** Load device context from expo modules. */
812
export const expoContextIntegration = (): Integration => {
13+
let _expoUpdatesContextCached: ExpoUpdatesContext | undefined;
14+
15+
function setup(): void {
16+
setExpoUpdatesNativeContext();
17+
}
18+
19+
function setExpoUpdatesNativeContext(): void {
20+
if (!isExpo() || isExpoGo()) {
21+
return;
22+
}
23+
24+
const expoUpdates = getExpoUpdatesContextCached();
25+
26+
try {
27+
// Ensures native errors and crashes have the same context as JS errors
28+
NATIVE.setContext(EXPO_UPDATES_CONTEXT_KEY, expoUpdates);
29+
} catch (error) {
30+
logger.error('Error setting Expo updates context:', error);
31+
}
32+
}
33+
34+
function processEvent(event: Event): Event {
35+
if (!isExpo()) {
36+
return event;
37+
}
38+
39+
addExpoGoContext(event);
40+
addExpoUpdatesContext(event);
41+
return event;
42+
}
43+
44+
function addExpoUpdatesContext(event: Event): void {
45+
event.contexts = event.contexts || {};
46+
event.contexts[EXPO_UPDATES_CONTEXT_KEY] = {
47+
...getExpoUpdatesContextCached(),
48+
};
49+
}
50+
51+
function getExpoUpdatesContextCached(): ExpoUpdatesContext {
52+
if (_expoUpdatesContextCached) {
53+
return _expoUpdatesContextCached;
54+
}
55+
56+
return (_expoUpdatesContextCached = getExpoUpdatesContext());
57+
}
58+
959
return {
1060
name: INTEGRATION_NAME,
11-
setupOnce: () => {
12-
// noop
13-
},
61+
setup,
1462
processEvent,
1563
};
1664
};
1765

18-
function processEvent(event: Event): Event {
66+
function getExpoUpdatesContext(): ExpoUpdatesContext {
67+
const expoUpdates = getExpoUpdates();
68+
if (!expoUpdates) {
69+
return {
70+
is_enabled: false,
71+
};
72+
}
73+
74+
const updatesContext: ExpoUpdatesContext = {
75+
is_enabled: !!expoUpdates.isEnabled,
76+
is_embedded_launch: !!expoUpdates.isEmbeddedLaunch,
77+
is_emergency_launch: !!expoUpdates.isEmergencyLaunch,
78+
is_using_embedded_assets: !!expoUpdates.isUsingEmbeddedAssets,
79+
};
80+
81+
if (typeof expoUpdates.updateId === 'string') {
82+
updatesContext.update_id = expoUpdates.updateId;
83+
}
84+
if (typeof expoUpdates.channel === 'string') {
85+
updatesContext.channel = expoUpdates.channel;
86+
}
87+
if (typeof expoUpdates.runtimeVersion === 'string') {
88+
updatesContext.runtime_version = expoUpdates.runtimeVersion;
89+
}
90+
if (typeof expoUpdates.checkAutomatically === 'string') {
91+
updatesContext.check_automatically = expoUpdates.checkAutomatically;
92+
}
93+
if (typeof expoUpdates.emergencyLaunchReason === 'string') {
94+
updatesContext.emergency_launch_reason = expoUpdates.emergencyLaunchReason;
95+
}
96+
if (typeof expoUpdates.launchDuration === 'number') {
97+
updatesContext.launch_duration = expoUpdates.launchDuration;
98+
}
99+
if (expoUpdates.createdAt instanceof Date) {
100+
updatesContext.created_at = expoUpdates.createdAt.toISOString();
101+
}
102+
return updatesContext;
103+
}
104+
105+
function addExpoGoContext(event: Event): void {
106+
if (!isExpoGo()) {
107+
return;
108+
}
109+
19110
const expoDeviceContext = getExpoDeviceContext();
20111
if (expoDeviceContext) {
21112
event.contexts = event.contexts || {};
@@ -27,8 +118,6 @@ function processEvent(event: Event): Event {
27118
event.contexts = event.contexts || {};
28119
event.contexts.os = { ...expoOsContext, ...event.contexts.os };
29120
}
30-
31-
return event;
32121
}
33122

34123
/**
@@ -66,3 +155,17 @@ function getExpoOsContext(): OsContext | undefined {
66155
name: expoDevice.osName,
67156
};
68157
}
158+
159+
type ExpoUpdatesContext = Partial<{
160+
is_enabled: boolean;
161+
is_embedded_launch: boolean;
162+
is_emergency_launch: boolean;
163+
is_using_embedded_assets: boolean;
164+
update_id: string;
165+
channel: string;
166+
runtime_version: string;
167+
check_automatically: string;
168+
emergency_launch_reason: string;
169+
launch_duration: number;
170+
created_at: string;
171+
}>;

packages/core/src/js/utils/expoglobalobject.ts

+22
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,31 @@ export interface ExpoDevice {
4444
totalMemory?: number;
4545
}
4646

47+
/**
48+
* Interface from the Expo SDK defined here
49+
* (we are describing the native module
50+
* the TS typing is only guideline)
51+
*
52+
* https://github.com/expo/expo/blob/8b7165ad2c6751c741f588c72dac50fb3a814dcc/packages/expo-updates/src/Updates.ts
53+
*/
54+
export interface ExpoUpdates {
55+
isEnabled?: boolean;
56+
updateId?: string | null;
57+
channel?: string | null;
58+
runtimeVersion?: string | null;
59+
checkAutomatically?: string | null;
60+
isEmergencyLaunch?: boolean;
61+
emergencyLaunchReason?: string | null;
62+
launchDuration?: number | null;
63+
isEmbeddedLaunch?: boolean;
64+
isUsingEmbeddedAssets?: boolean;
65+
createdAt?: Date | null;
66+
}
67+
4768
export interface ExpoGlobalObject {
4869
modules?: {
4970
ExponentConstants?: ExpoConstants;
5071
ExpoDevice?: ExpoDevice;
72+
ExpoUpdates?: ExpoUpdates;
5173
};
5274
}
+10-5
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
import type { ExpoConstants, ExpoDevice } from './expoglobalobject';
1+
import type { ExpoConstants, ExpoDevice, ExpoUpdates } from './expoglobalobject';
22
import { RN_GLOBAL_OBJ } from './worldwide';
33

44
/**
55
* Returns the Expo Constants module if present
66
*/
77
export function getExpoConstants(): ExpoConstants | undefined {
8-
return (
9-
(RN_GLOBAL_OBJ.expo && RN_GLOBAL_OBJ.expo.modules && RN_GLOBAL_OBJ.expo.modules.ExponentConstants) || undefined
10-
);
8+
return RN_GLOBAL_OBJ.expo?.modules?.ExponentConstants ?? undefined;
119
}
1210

1311
/**
1412
* Returns the Expo Device module if present
1513
*/
1614
export function getExpoDevice(): ExpoDevice | undefined {
17-
return (RN_GLOBAL_OBJ.expo && RN_GLOBAL_OBJ.expo.modules && RN_GLOBAL_OBJ.expo.modules.ExpoDevice) || undefined;
15+
return RN_GLOBAL_OBJ.expo?.modules?.ExpoDevice ?? undefined;
16+
}
17+
18+
/**
19+
* Returns the Expo Updates module if present
20+
*/
21+
export function getExpoUpdates(): ExpoUpdates | undefined {
22+
return RN_GLOBAL_OBJ.expo?.modules?.ExpoUpdates ?? undefined;
1823
}

0 commit comments

Comments
 (0)