@grafana/faro-react-native 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +118 -36
- package/android/consumer-proguard-rules.pro +2 -0
- package/android/src/main/java/com/grafana/faro/reactnative/ANRTracker.kt +61 -9
- package/android/src/main/java/com/grafana/faro/reactnative/AnrThreadDumpParser.kt +70 -0
- package/android/src/main/java/com/grafana/faro/reactnative/ApplicationExitTraceReader.kt +142 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroAnrCache.kt +163 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroAnrReporter.kt +163 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +243 -50
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +75 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroNativeCrashTrace.kt +39 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +99 -8
- package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativePackage.kt +1 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroUncaughtExceptionHandler.kt +62 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneBacktraceFormatter.kt +117 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneModels.kt +35 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneWireParser.kt +235 -0
- package/android/src/main/proto/tombstone.proto +62 -0
- package/android/src/test/java/com/grafana/faro/reactnative/AnrStackFiltersTest.kt +28 -0
- package/android/src/test/java/com/grafana/faro/reactnative/AnrThreadDumpParserTest.kt +38 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroAnrCacheTest.kt +81 -0
- package/android/src/test/java/com/grafana/faro/reactnative/TombstoneWireParserTest.kt +51 -0
- package/dist/cjs/config/getRNInstrumentations.js +3 -1
- package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
- package/dist/cjs/config/makeRNConfig.js +48 -9
- package/dist/cjs/config/makeRNConfig.js.map +1 -1
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/initialize.js +14 -8
- package/dist/cjs/initialize.js.map +1 -1
- package/dist/cjs/instrumentations/anr/anrStack.js +19 -0
- package/dist/cjs/instrumentations/anr/anrStack.js.map +1 -0
- package/dist/cjs/instrumentations/anr/instrumentation.js +374 -72
- package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/anr/types.js.map +1 -1
- package/dist/cjs/instrumentations/console/index.js +5 -1
- package/dist/cjs/instrumentations/console/index.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/{instrumentation.js → BaseCrashReportingInstrumentation.js} +110 -103
- package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js +41 -0
- package/dist/cjs/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +87 -0
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/crashErrorMessage.js +123 -0
- package/dist/cjs/instrumentations/crashReporting/android/crashErrorMessage.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/index.js +12 -0
- package/dist/cjs/instrumentations/crashReporting/android/index.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/nativeTombstoneTrace.js +11 -0
- package/dist/cjs/instrumentations/crashReporting/android/nativeTombstoneTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/parseAndroidCrashTrace.js +181 -0
- package/dist/cjs/instrumentations/crashReporting/android/parseAndroidCrashTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/index.js +35 -2
- package/dist/cjs/instrumentations/crashReporting/index.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +78 -0
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/crashErrorMessage.js +103 -0
- package/dist/cjs/instrumentations/crashReporting/ios/crashErrorMessage.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/index.js +11 -0
- package/dist/cjs/instrumentations/crashReporting/ios/index.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/parseIosCrashTrace.js +48 -0
- package/dist/cjs/instrumentations/crashReporting/ios/parseIosCrashTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/cjs/instrumentations/errors/index.js +2 -1
- package/dist/cjs/instrumentations/errors/index.js.map +1 -1
- package/dist/cjs/instrumentations/session/installationId.js +91 -0
- package/dist/cjs/instrumentations/session/installationId.js.map +1 -0
- package/dist/cjs/instrumentations/session/sessionAttributes.js +152 -89
- package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/cjs/metas/appBuildIdentity.js +95 -0
- package/dist/cjs/metas/appBuildIdentity.js.map +1 -0
- package/dist/cjs/testUtils/mockDeviceInfo.js +5 -0
- package/dist/cjs/testUtils/mockDeviceInfo.js.map +1 -1
- package/dist/cjs/testUtils/setupTests.js +7 -0
- package/dist/cjs/testUtils/setupTests.js.map +1 -1
- package/dist/esm/config/getRNInstrumentations.js +3 -1
- package/dist/esm/config/getRNInstrumentations.js.map +1 -1
- package/dist/esm/config/makeRNConfig.js +49 -10
- package/dist/esm/config/makeRNConfig.js.map +1 -1
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/initialize.js +11 -5
- package/dist/esm/initialize.js.map +1 -1
- package/dist/esm/instrumentations/anr/anrStack.js +16 -0
- package/dist/esm/instrumentations/anr/anrStack.js.map +1 -0
- package/dist/esm/instrumentations/anr/instrumentation.js +260 -67
- package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/esm/instrumentations/anr/types.js.map +1 -1
- package/dist/esm/instrumentations/console/index.js +5 -1
- package/dist/esm/instrumentations/console/index.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/{instrumentation.js → BaseCrashReportingInstrumentation.js} +76 -94
- package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js +18 -0
- package/dist/esm/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +56 -0
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/crashErrorMessage.js +116 -0
- package/dist/esm/instrumentations/crashReporting/android/crashErrorMessage.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/index.js +4 -0
- package/dist/esm/instrumentations/crashReporting/android/index.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/nativeTombstoneTrace.js +8 -0
- package/dist/esm/instrumentations/crashReporting/android/nativeTombstoneTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/parseAndroidCrashTrace.js +174 -0
- package/dist/esm/instrumentations/crashReporting/android/parseAndroidCrashTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/index.js +35 -1
- package/dist/esm/instrumentations/crashReporting/index.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +56 -0
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/crashErrorMessage.js +98 -0
- package/dist/esm/instrumentations/crashReporting/ios/crashErrorMessage.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/index.js +4 -0
- package/dist/esm/instrumentations/crashReporting/ios/index.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/parseIosCrashTrace.js +44 -0
- package/dist/esm/instrumentations/crashReporting/ios/parseIosCrashTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/esm/instrumentations/errors/index.js +2 -1
- package/dist/esm/instrumentations/errors/index.js.map +1 -1
- package/dist/esm/instrumentations/session/installationId.js +48 -0
- package/dist/esm/instrumentations/session/installationId.js.map +1 -0
- package/dist/esm/instrumentations/session/sessionAttributes.js +83 -38
- package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/esm/metas/appBuildIdentity.js +56 -0
- package/dist/esm/metas/appBuildIdentity.js.map +1 -0
- package/dist/esm/testUtils/mockDeviceInfo.js +5 -0
- package/dist/esm/testUtils/mockDeviceInfo.js.map +1 -1
- package/dist/esm/testUtils/setupTests.js +7 -0
- package/dist/esm/testUtils/setupTests.js.map +1 -1
- package/dist/types/config/makeRNConfig.d.ts +4 -3
- package/dist/types/config/types.d.ts +2 -1
- package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/initialize.d.ts +4 -2
- package/dist/types/instrumentations/anr/anrStack.d.ts +7 -0
- package/dist/types/instrumentations/anr/instrumentation.d.ts +23 -28
- package/dist/types/instrumentations/anr/types.d.ts +5 -1
- package/dist/types/instrumentations/appState/index.d.ts +1 -1
- package/dist/types/instrumentations/console/index.d.ts +1 -1
- package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +56 -0
- package/dist/types/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.d.ts +12 -0
- package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +26 -0
- package/dist/types/instrumentations/crashReporting/android/crashErrorMessage.d.ts +23 -0
- package/dist/types/instrumentations/crashReporting/android/index.d.ts +4 -0
- package/dist/types/instrumentations/crashReporting/android/nativeTombstoneTrace.d.ts +2 -0
- package/dist/types/instrumentations/crashReporting/android/parseAndroidCrashTrace.d.ts +45 -0
- package/dist/types/instrumentations/crashReporting/index.d.ts +25 -2
- package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +28 -0
- package/dist/types/instrumentations/crashReporting/ios/crashErrorMessage.d.ts +14 -0
- package/dist/types/instrumentations/crashReporting/ios/index.d.ts +4 -0
- package/dist/types/instrumentations/crashReporting/ios/parseIosCrashTrace.d.ts +38 -0
- package/dist/types/instrumentations/crashReporting/types.d.ts +18 -51
- package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +1 -1
- package/dist/types/instrumentations/http/index.d.ts +1 -1
- package/dist/types/instrumentations/performance/index.d.ts +1 -1
- package/dist/types/instrumentations/session/index.d.ts +1 -1
- package/dist/types/instrumentations/session/installationId.d.ts +1 -0
- package/dist/types/instrumentations/session/sessionAttributes.d.ts +13 -4
- package/dist/types/instrumentations/startup/index.d.ts +1 -1
- package/dist/types/instrumentations/userActions/index.d.ts +1 -1
- package/dist/types/instrumentations/view/index.d.ts +1 -1
- package/dist/types/instrumentations/xhr/index.d.ts +1 -1
- package/dist/types/metas/appBuildIdentity.d.ts +16 -0
- package/dist/types/testUtils/mockFaroReactNativeTracing.d.ts +1 -1
- package/dist/types/transports/console/transport.d.ts +1 -1
- package/dist/types/transports/fetch/transport.d.ts +1 -1
- package/dist/types/transports/offline/transport.d.ts +1 -1
- package/package.json +3 -3
- package/src/config/getRNInstrumentations.ts +5 -1
- package/src/config/makeRNConfig.test.ts +59 -3
- package/src/config/makeRNConfig.ts +48 -9
- package/src/config/types.ts +2 -1
- package/src/generated/faroRNPackageMeta.ts +1 -1
- package/src/index.ts +2 -1
- package/src/initialize.test.ts +71 -15
- package/src/initialize.ts +11 -5
- package/src/instrumentations/anr/anrStack.test.ts +27 -0
- package/src/instrumentations/anr/anrStack.ts +19 -0
- package/src/instrumentations/anr/instrumentation.test.ts +45 -0
- package/src/instrumentations/anr/instrumentation.ts +301 -67
- package/src/instrumentations/anr/types.ts +7 -1
- package/src/instrumentations/console/index.ts +5 -1
- package/src/instrumentations/console/instrumentation.test.ts +5 -0
- package/src/instrumentations/crashReporting/{instrumentation.ts → BaseCrashReportingInstrumentation.ts} +119 -93
- package/src/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.ts +23 -0
- package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +62 -0
- package/src/instrumentations/crashReporting/android/crashErrorMessage.test.ts +182 -0
- package/src/instrumentations/crashReporting/android/crashErrorMessage.ts +135 -0
- package/src/instrumentations/crashReporting/android/index.ts +4 -0
- package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.test.ts +19 -0
- package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.ts +8 -0
- package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.test.ts +232 -0
- package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.ts +235 -0
- package/src/instrumentations/crashReporting/index.ts +43 -2
- package/src/instrumentations/crashReporting/instrumentation.test.ts +42 -0
- package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +62 -0
- package/src/instrumentations/crashReporting/ios/crashErrorMessage.test.ts +170 -0
- package/src/instrumentations/crashReporting/ios/crashErrorMessage.ts +111 -0
- package/src/instrumentations/crashReporting/ios/index.ts +4 -0
- package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.test.ts +86 -0
- package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.ts +83 -0
- package/src/instrumentations/crashReporting/types.ts +19 -58
- package/src/instrumentations/errors/index.ts +2 -1
- package/src/instrumentations/errors/instrumentation.test.ts +8 -2
- package/src/instrumentations/errors/stackTraceParser.test.ts +1 -1
- package/src/instrumentations/session/installationId.ts +43 -0
- package/src/instrumentations/session/sessionAttributes.test.ts +260 -30
- package/src/instrumentations/session/sessionAttributes.ts +112 -22
- package/src/metas/appBuildIdentity.test.ts +48 -0
- package/src/metas/appBuildIdentity.ts +50 -0
- package/src/testUtils/mockDeviceInfo.ts +5 -0
- package/src/testUtils/setupTests.ts +7 -0
- package/dist/cjs/instrumentations/crashReporting/instrumentation.js.map +0 -1
- package/dist/esm/instrumentations/crashReporting/instrumentation.js.map +0 -1
- package/dist/types/instrumentations/crashReporting/instrumentation.d.ts +0 -58
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
import DeviceInfo from 'react-native-device-info';
|
|
3
3
|
|
|
4
|
+
import type { Meta } from '@grafana/faro-core';
|
|
5
|
+
|
|
6
|
+
import { getInstallationId } from './installationId';
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
9
|
* Session attributes for React Native
|
|
6
10
|
* These attributes are automatically included with every telemetry event
|
|
@@ -25,22 +29,22 @@ export interface SessionAttributes {
|
|
|
25
29
|
/** Detailed OS info (e.g., "iOS 17.0" or "Android 15 (SDK 35)") */
|
|
26
30
|
device_os_detail?: string;
|
|
27
31
|
|
|
28
|
-
/** Device manufacturer (e.g., "apple", "samsung") */
|
|
32
|
+
/** Device manufacturer (e.g., "apple", "samsung", "Google") */
|
|
29
33
|
device_manufacturer?: string;
|
|
30
34
|
|
|
31
|
-
/**
|
|
35
|
+
/** Device model in flat session attributes (e.g., "iPhone 15 Pro", "SM-A155F") */
|
|
32
36
|
device_model?: string;
|
|
33
37
|
|
|
34
38
|
/** Human-readable model name (e.g., "iPhone 15 Pro") */
|
|
35
39
|
device_model_name?: string;
|
|
36
40
|
|
|
37
|
-
/** Device brand (e.g., "
|
|
41
|
+
/** Device brand in flat session attributes (e.g., "Apple", "samsung") */
|
|
38
42
|
device_brand?: string;
|
|
39
43
|
|
|
40
44
|
/** Whether device is physical or emulator ("true" or "false") */
|
|
41
45
|
device_is_physical?: string;
|
|
42
46
|
|
|
43
|
-
/**
|
|
47
|
+
/** Temporary flat attribute mirroring `app.installationId` during migration. */
|
|
44
48
|
device_id?: string;
|
|
45
49
|
|
|
46
50
|
/** Device type ("mobile" or "tablet") */
|
|
@@ -65,6 +69,11 @@ export interface SessionAttributes {
|
|
|
65
69
|
device_carrier?: string;
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
export interface PreloadedMobileMeta {
|
|
73
|
+
sessionAttributes: SessionAttributes;
|
|
74
|
+
meta: Pick<Meta, 'app' | 'device' | 'os'>;
|
|
75
|
+
}
|
|
76
|
+
|
|
68
77
|
/**
|
|
69
78
|
* React Native framework version from `Platform.constants` (host app runtime), not `@grafana/faro-react-native` semver.
|
|
70
79
|
*/
|
|
@@ -90,17 +99,42 @@ function getReactNativeVersion(): string {
|
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
function getStructuredDeviceBrand(model: string, brand: string): string {
|
|
103
|
+
if (Platform.OS !== 'ios') {
|
|
104
|
+
return brand;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return model.toLowerCase().includes('ipad') ? 'iPad' : 'iPhone';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getStructuredDeviceManufacturer(manufacturer: string): string {
|
|
111
|
+
return Platform.OS === 'ios' ? manufacturer.toLowerCase() : manufacturer;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getStructuredDeviceModelIdentifier(model: string): string | undefined {
|
|
115
|
+
// Android getDeviceId() returns a board code, so use Build.MODEL for the Faro
|
|
116
|
+
// model identifier.
|
|
117
|
+
if (Platform.OS === 'android') {
|
|
118
|
+
return model;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// React Native reports iPhone and iPadOS apps as Platform.OS === 'ios'.
|
|
122
|
+
// react-native-device-info exposes the Apple hardware identifier here.
|
|
123
|
+
if (Platform.OS === 'ios') {
|
|
124
|
+
return DeviceInfo.getDeviceId();
|
|
103
125
|
}
|
|
126
|
+
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getMobileDeviceType(isTablet: boolean): 'mobile' | 'tablet' | undefined {
|
|
131
|
+
// iPadOS is still reported as Platform.OS === 'ios'; DeviceInfo.isTablet()
|
|
132
|
+
// distinguishes iPad/tablet form factor from phone form factor.
|
|
133
|
+
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
134
|
+
return isTablet ? 'tablet' : 'mobile';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return undefined;
|
|
104
138
|
}
|
|
105
139
|
|
|
106
140
|
/**
|
|
@@ -124,6 +158,17 @@ async function getDeviceOsDetail(): Promise<string> {
|
|
|
124
158
|
return `${systemName} ${systemVersion}`;
|
|
125
159
|
}
|
|
126
160
|
|
|
161
|
+
async function getDeviceOsBuildId(): Promise<string | undefined> {
|
|
162
|
+
try {
|
|
163
|
+
// RN device-info exposes a useful OS build id on both Android and iOS.
|
|
164
|
+
// Keep it omitted when unavailable instead of sending "unknown".
|
|
165
|
+
const buildId = await DeviceInfo.getBuildId();
|
|
166
|
+
return buildId && buildId !== 'unknown' ? buildId : undefined;
|
|
167
|
+
} catch (_error) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
127
172
|
/**
|
|
128
173
|
* Session attributes without device props when async collection or DeviceInfo is unavailable.
|
|
129
174
|
* No synchronous DeviceInfo reads — use {@link getSessionAttributes} or the package async `initializeFaro`.
|
|
@@ -151,20 +196,28 @@ export function minimalSessionDeviceAttributes(): SessionAttributes {
|
|
|
151
196
|
* - device_carrier
|
|
152
197
|
*/
|
|
153
198
|
export async function getSessionAttributes(): Promise<SessionAttributes> {
|
|
199
|
+
return (await collectMobileMeta()).sessionAttributes;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function collectMobileMeta(): Promise<PreloadedMobileMeta> {
|
|
154
203
|
try {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
204
|
+
const [deviceOsDetail, deviceOsBuildId, installationId] = await Promise.all([
|
|
205
|
+
getDeviceOsDetail(),
|
|
206
|
+
getDeviceOsBuildId(),
|
|
207
|
+
getInstallationId(),
|
|
208
|
+
]);
|
|
158
209
|
|
|
159
210
|
// Get synchronous device info
|
|
160
211
|
const systemName = DeviceInfo.getSystemName();
|
|
161
212
|
const systemVersion = DeviceInfo.getSystemVersion();
|
|
162
213
|
const manufacturer = DeviceInfo.getManufacturerSync();
|
|
163
214
|
const model = DeviceInfo.getModel();
|
|
215
|
+
const modelIdentifier = getStructuredDeviceModelIdentifier(model);
|
|
164
216
|
const deviceName = DeviceInfo.getDeviceNameSync();
|
|
165
217
|
const brand = DeviceInfo.getBrand();
|
|
166
218
|
const isEmulator = DeviceInfo.isEmulatorSync();
|
|
167
219
|
const isTablet = DeviceInfo.isTablet();
|
|
220
|
+
const deviceType = getMobileDeviceType(isTablet);
|
|
168
221
|
|
|
169
222
|
// Memory info
|
|
170
223
|
const totalMemory = DeviceInfo.getTotalMemorySync();
|
|
@@ -221,8 +274,8 @@ export async function getSessionAttributes(): Promise<SessionAttributes> {
|
|
|
221
274
|
device_model_name: deviceName,
|
|
222
275
|
device_brand: brand,
|
|
223
276
|
device_is_physical: String(!isEmulator),
|
|
224
|
-
device_id:
|
|
225
|
-
device_type:
|
|
277
|
+
...(installationId ? { device_id: installationId } : {}),
|
|
278
|
+
...(deviceType ? { device_type: deviceType } : {}),
|
|
226
279
|
device_memory_total: String(totalMemory),
|
|
227
280
|
device_memory_used: String(usedMemory),
|
|
228
281
|
device_battery_level: batteryLevel,
|
|
@@ -230,20 +283,57 @@ export async function getSessionAttributes(): Promise<SessionAttributes> {
|
|
|
230
283
|
device_low_power_mode: lowPowerMode,
|
|
231
284
|
device_carrier: carrier,
|
|
232
285
|
};
|
|
286
|
+
const appMeta = installationId ? { installationId } : {};
|
|
287
|
+
const structuredDeviceBrand = getStructuredDeviceBrand(model, brand);
|
|
288
|
+
const structuredDeviceManufacturer = getStructuredDeviceManufacturer(manufacturer);
|
|
289
|
+
const osMeta = {
|
|
290
|
+
...(deviceOsBuildId ? { build_id: deviceOsBuildId } : {}),
|
|
291
|
+
detail: deviceOsDetail,
|
|
292
|
+
name: systemName,
|
|
293
|
+
version: systemVersion,
|
|
294
|
+
};
|
|
233
295
|
|
|
234
|
-
return
|
|
296
|
+
return {
|
|
297
|
+
sessionAttributes: attributes,
|
|
298
|
+
meta: {
|
|
299
|
+
app: appMeta,
|
|
300
|
+
device: {
|
|
301
|
+
brand: structuredDeviceBrand,
|
|
302
|
+
is_physical: !isEmulator,
|
|
303
|
+
manufacturer: structuredDeviceManufacturer,
|
|
304
|
+
...(modelIdentifier ? { model_identifier: modelIdentifier } : {}),
|
|
305
|
+
model_name: model,
|
|
306
|
+
...(deviceType ? { type: deviceType } : {}),
|
|
307
|
+
},
|
|
308
|
+
os: osMeta,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
235
311
|
} catch (_error) {
|
|
236
|
-
|
|
312
|
+
const installationId = await getInstallationId();
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
sessionAttributes: minimalSessionDeviceAttributes(),
|
|
316
|
+
meta: {
|
|
317
|
+
app: installationId ? { installationId } : {},
|
|
318
|
+
},
|
|
319
|
+
};
|
|
237
320
|
}
|
|
238
321
|
}
|
|
239
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Get structured mobile meta plus flat session attributes for async `initializeFaro`.
|
|
325
|
+
*/
|
|
326
|
+
export async function loadMobileMetaForInit(): Promise<PreloadedMobileMeta> {
|
|
327
|
+
return collectMobileMeta();
|
|
328
|
+
}
|
|
329
|
+
|
|
240
330
|
/**
|
|
241
331
|
* Await full async session device attributes (battery, carrier, etc.), then fall back to
|
|
242
332
|
* {@link minimalSessionDeviceAttributes} if anything throws. Used by async `initializeFaro`.
|
|
243
333
|
*/
|
|
244
334
|
export async function loadSessionDeviceAttributesForInit(): Promise<SessionAttributes> {
|
|
245
335
|
try {
|
|
246
|
-
return await
|
|
336
|
+
return (await loadMobileMetaForInit()).sessionAttributes;
|
|
247
337
|
} catch {
|
|
248
338
|
return minimalSessionDeviceAttributes();
|
|
249
339
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import DeviceInfo from 'react-native-device-info';
|
|
2
|
+
|
|
3
|
+
import { formatSymbolsBundleId, loadAppSymbolsBundleIdForInit } from './appBuildIdentity';
|
|
4
|
+
|
|
5
|
+
describe('formatSymbolsBundleId', () => {
|
|
6
|
+
it('joins applicationId, versionCode and versionName with @', () => {
|
|
7
|
+
expect(formatSymbolsBundleId('com.example.myapp', '42', '1.0.0')).toBe('com.example.myapp@42@1.0.0');
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('loadAppSymbolsBundleIdForInit', () => {
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('maps DeviceInfo build info to an encoded bundle id', async () => {
|
|
17
|
+
const bundleId = await loadAppSymbolsBundleIdForInit();
|
|
18
|
+
|
|
19
|
+
expect(bundleId).toBe('com.example.myapp@42@1.0.0');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('coerces a numeric build number to a string in the bundle id', async () => {
|
|
23
|
+
(DeviceInfo.getBuildNumber as jest.Mock).mockReturnValueOnce(7);
|
|
24
|
+
|
|
25
|
+
const bundleId = await loadAppSymbolsBundleIdForInit();
|
|
26
|
+
|
|
27
|
+
expect(bundleId).toBe('com.example.myapp@7@1.0.0');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns undefined when required fields are missing', async () => {
|
|
31
|
+
(DeviceInfo.getBundleId as jest.Mock).mockReturnValueOnce('');
|
|
32
|
+
(DeviceInfo.getBuildNumber as jest.Mock).mockReturnValueOnce('');
|
|
33
|
+
|
|
34
|
+
const bundleId = await loadAppSymbolsBundleIdForInit();
|
|
35
|
+
|
|
36
|
+
expect(bundleId).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('returns undefined when DeviceInfo throws', async () => {
|
|
40
|
+
(DeviceInfo.getBundleId as jest.Mock).mockImplementationOnce(() => {
|
|
41
|
+
throw new Error('not available');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const bundleId = await loadAppSymbolsBundleIdForInit();
|
|
45
|
+
|
|
46
|
+
expect(bundleId).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import DeviceInfo from 'react-native-device-info';
|
|
2
|
+
|
|
3
|
+
import { globalObject } from '@grafana/faro-core';
|
|
4
|
+
|
|
5
|
+
const BUNDLE_ID_SEPARATOR = '@';
|
|
6
|
+
|
|
7
|
+
/** Metro / webpack preamble bundle id for JS source maps (`__faroBundleId_<appName>`). */
|
|
8
|
+
export function getMetroInjectedBundleId(appName: string | undefined): string | undefined {
|
|
9
|
+
if (!appName) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const key = `__faroBundleId_${appName}`;
|
|
13
|
+
const fromGlobal = (globalObject as Record<string, unknown>)[key];
|
|
14
|
+
if (typeof fromGlobal === 'string' && fromGlobal !== '') {
|
|
15
|
+
return fromGlobal;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Encodes the Android/iOS build identity used as `meta.app.bundleId` for
|
|
22
|
+
* server-side symbol retrace. Must match the symbols upload bundle id
|
|
23
|
+
* (`{applicationId}@{versionCode}@{versionName}`).
|
|
24
|
+
*/
|
|
25
|
+
export function formatSymbolsBundleId(applicationId: string, versionCode: string, versionName: string): string {
|
|
26
|
+
return `${applicationId}${BUNDLE_ID_SEPARATOR}${versionCode}${BUNDLE_ID_SEPARATOR}${versionName}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Reads the installed build's identity from `react-native-device-info` and
|
|
31
|
+
* returns the encoded bundle id when all parts are available.
|
|
32
|
+
*
|
|
33
|
+
* Values must match what the symbol uploader (Gradle plugin / faro-cli) sends
|
|
34
|
+
* for the same build so the collector can locate the mapping for retrace.
|
|
35
|
+
*/
|
|
36
|
+
export async function loadAppSymbolsBundleIdForInit(): Promise<string | undefined> {
|
|
37
|
+
try {
|
|
38
|
+
const applicationId = DeviceInfo.getBundleId();
|
|
39
|
+
const versionName = DeviceInfo.getVersion();
|
|
40
|
+
const versionCodeRaw = DeviceInfo.getBuildNumber();
|
|
41
|
+
|
|
42
|
+
if (!applicationId || versionCodeRaw === '' || versionCodeRaw == null || !versionName) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return formatSymbolsBundleId(applicationId, String(versionCodeRaw), versionName);
|
|
47
|
+
} catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -6,10 +6,15 @@ export function mockDeviceInfo(): void {
|
|
|
6
6
|
default: {
|
|
7
7
|
getBrand: jest.fn(() => 'Apple'),
|
|
8
8
|
getDeviceId: jest.fn(() => 'iPhone14,2'),
|
|
9
|
+
getDeviceNameSync: jest.fn(() => "Test's iPhone"),
|
|
10
|
+
getUniqueId: jest.fn(() => Promise.resolve('test-device-id')),
|
|
9
11
|
getModel: jest.fn(() => 'iPhone 13 Pro'),
|
|
12
|
+
getManufacturerSync: jest.fn(() => 'Apple'),
|
|
10
13
|
getSystemName: jest.fn(() => 'iOS'),
|
|
11
14
|
getSystemVersion: jest.fn(() => '16.0'),
|
|
12
15
|
getVersion: jest.fn(() => '1.0.0'),
|
|
16
|
+
getApiLevel: jest.fn(() => Promise.resolve(35)),
|
|
17
|
+
getBuildId: jest.fn(() => Promise.resolve('22B83')),
|
|
13
18
|
isTablet: jest.fn(() => false),
|
|
14
19
|
isEmulatorSync: jest.fn(() => false),
|
|
15
20
|
getTotalMemorySync: jest.fn(() => 6442450944), // 6GB in bytes
|
|
@@ -8,10 +8,17 @@ jest.mock('react-native-device-info', () => {
|
|
|
8
8
|
const mockDeviceInfo = {
|
|
9
9
|
getBrand: jest.fn(() => 'Apple'),
|
|
10
10
|
getDeviceId: jest.fn(() => 'iPhone14,2'),
|
|
11
|
+
getDeviceNameSync: jest.fn(() => "Test's iPhone"),
|
|
12
|
+
getUniqueId: jest.fn(() => Promise.resolve('test-device-id')),
|
|
11
13
|
getModel: jest.fn(() => 'iPhone 13 Pro'),
|
|
14
|
+
getManufacturerSync: jest.fn(() => 'Apple'),
|
|
12
15
|
getSystemName: jest.fn(() => 'iOS'),
|
|
13
16
|
getSystemVersion: jest.fn(() => '16.0'),
|
|
14
17
|
getVersion: jest.fn(() => '1.0.0'),
|
|
18
|
+
getBundleId: jest.fn(() => 'com.example.myapp'),
|
|
19
|
+
getBuildNumber: jest.fn(() => '42'),
|
|
20
|
+
getApiLevel: jest.fn(() => Promise.resolve(35)),
|
|
21
|
+
getBuildId: jest.fn(() => Promise.resolve('22B83')),
|
|
15
22
|
isTablet: jest.fn(() => false),
|
|
16
23
|
isEmulatorSync: jest.fn(() => false),
|
|
17
24
|
getTotalMemorySync: jest.fn(() => 6442450944), // 6GB in bytes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport { ErrorMechanism } from '../errors/const';\n\nimport type { CrashReport, CrashReportingOptions } from './types';\n\n/**\n * Crash Reporting Instrumentation (Experimental).\n *\n * Retrieves crash reports from previous app sessions and sends them via Faro API.\n *\n * **Platform Support**:\n * - **Android**: Uses ApplicationExitInfo API (Android 11+ / API 30+)\n * Captures: CRASH, CRASH_NATIVE, ANR, LOW_MEMORY, EXCESSIVE_RESOURCE_USAGE\n * - **iOS**: Uses PLCrashReporter (requires adding dependency to podspec)\n * Captures: Signal crashes (SIGSEGV, SIGABRT, etc.) and Mach exceptions\n *\n * **Note**: This instrumentation only retrieves crash data from previous sessions.\n * The crash itself terminates the app, so the report is sent on next app launch.\n *\n * @example\n * ```typescript\n * import { initializeFaro, CrashReportingInstrumentation } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://collector.example.com',\n * instrumentations: [\n * new CrashReportingInstrumentation(),\n * ],\n * });\n * ```\n */\nexport class CrashReportingInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-crash';\n readonly version = VERSION;\n\n private readonly options: Required<CrashReportingOptions>;\n\n constructor(options: CrashReportingOptions = {}) {\n super();\n this.options = {\n enabled: options.enabled ?? true,\n };\n }\n\n async initialize(): Promise<void> {\n if (!this.options.enabled) {\n this.logDebug('Crash reporting is disabled');\n return;\n }\n\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n this.logWarn('Native module not available for crash reporting');\n return;\n }\n\n this.logDebug('Initializing crash reporting instrumentation');\n\n // Wait for session attributes to be populated before sending crash reports\n // This ensures crash reports include full session metadata\n await this.waitForSessionAttributes();\n\n // Get crash reports from previous session\n await this.processCrashReports(nativeModule);\n }\n\n /**\n * Wait for session attributes to be populated.\n *\n * Crash reports are sent in a new session, so we need to wait for\n * SessionInstrumentation to finish collecting device info, OS version, etc.\n * before sending the crash report. Otherwise, the crash report will be missing\n * session metadata.\n *\n * This checks for multiple device-specific attributes that are collected\n * asynchronously (device_id, device_os_detail, device_model_name) to ensure\n * the async getSessionAttributes() call has completed.\n *\n * @param maxWaitMs Maximum time to wait in milliseconds (default 10000ms)\n */\n private async waitForSessionAttributes(maxWaitMs = 10000): Promise<void> {\n const startTime = Date.now();\n const pollInterval = 200; // Check every 200ms\n let checkCount = 0;\n\n while (Date.now() - startTime < maxWaitMs) {\n checkCount++;\n try {\n // Check if session attributes are populated\n const sessionAttrs = this.metas?.value?.session?.attributes;\n\n if (sessionAttrs) {\n const attrCount = Object.keys(sessionAttrs).length;\n const attrKeys = JSON.stringify(Object.keys(sessionAttrs));\n\n // Look for multiple device-specific attributes that indicate full async collection is done.\n // getSessionAttributes() collects these asynchronously:\n // - device_id (async getDeviceId)\n // - device_os_detail (async getDeviceOsDetail)\n // - device_model_name (DeviceInfo.getDeviceNameSync)\n // All three should be present when collection is complete.\n const hasDeviceId = 'device_id' in sessionAttrs && sessionAttrs['device_id'] !== 'unknown';\n const hasDeviceOsDetail =\n 'device_os_detail' in sessionAttrs && sessionAttrs['device_os_detail'] !== 'unknown';\n const hasDeviceModelName = 'device_model_name' in sessionAttrs;\n\n if (hasDeviceId && hasDeviceOsDetail && hasDeviceModelName) {\n const elapsed = Date.now() - startTime;\n this.logDebug(`Session attributes ready after ${elapsed}ms (${checkCount} checks, ${attrCount} attrs)`);\n return;\n } else {\n this.logDebug(\n `Check #${checkCount}: Found ${attrCount} session attributes: ${attrKeys} but still missing required attrs - device_id:${hasDeviceId}, device_os_detail:${hasDeviceOsDetail}, device_model_name:${hasDeviceModelName}`\n );\n }\n } else {\n this.logDebug(`Check #${checkCount}: No session attributes available yet`);\n }\n } catch (error) {\n // Continue waiting if we can't access metas yet\n this.logDebug(`Check #${checkCount}: Error accessing metas: ${error}`);\n }\n\n // Wait before next check\n await new Promise((resolve) => setTimeout(resolve, pollInterval));\n }\n\n // Timeout reached - log warning but continue\n const finalAttrs = this.metas?.value?.session?.attributes;\n const finalAttrCount = finalAttrs ? Object.keys(finalAttrs).length : 0;\n const finalAttrKeys = finalAttrs ? JSON.stringify(Object.keys(finalAttrs)) : 'none';\n this.logWarn(\n `Session attributes not ready after ${maxWaitMs}ms timeout (${checkCount} checks, ${finalAttrCount} attrs: ${finalAttrKeys}). Sending crash report anyway.`\n );\n }\n\n private getNativeModule(): typeof NativeModules.FaroReactNativeModule | null {\n const { FaroReactNativeModule } = NativeModules;\n\n if (!FaroReactNativeModule) {\n return null;\n }\n\n return FaroReactNativeModule;\n }\n\n private async processCrashReports(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<void> {\n try {\n if (typeof nativeModule.getCrashReport !== 'function') {\n this.logDebug('getCrashReport method not available');\n return;\n }\n\n this.logDebug('Attempting to retrieve crash reports from native module');\n const crashReports = (await nativeModule.getCrashReport()) as string[] | null;\n\n if (!crashReports || crashReports.length === 0) {\n this.logDebug('No crash reports from previous session');\n return;\n }\n\n this.logDebug(`Processing ${crashReports.length} crash report(s) from previous session`);\n\n for (const crashJson of crashReports) {\n try {\n this.logDebug(`Parsing crash report JSON: ${crashJson.substring(0, 200)}...`);\n const crash = JSON.parse(crashJson) as CrashReport;\n this.sendCrashReport(crash);\n } catch (parseError) {\n // If parsing fails, still try to report something\n this.logError('Failed to parse crash report JSON', parseError);\n this.api.pushError(new Error('Application crash (parse error)'), {\n context: {\n mechanism: ErrorMechanism.CRASH,\n parseError: String(parseError),\n raw: crashJson.substring(0, 500), // Limit size\n },\n type: 'crash',\n });\n }\n }\n } catch (error) {\n this.logError('Failed to process crash reports', error);\n }\n }\n\n private sendCrashReport(crash: CrashReport): void {\n const errorMessage = this.getErrorMessage(crash);\n const error = new Error(errorMessage);\n\n // Build context from crash data (matching Flutter pattern)\n const context: Record<string, string> = {\n mechanism: ErrorMechanism.CRASH,\n };\n\n if (crash.trace) {\n context['trace'] = crash.trace;\n }\n if (crash.signal) {\n context['signal'] = crash.signal;\n }\n if (crash.timestamp) {\n context['timestamp'] = String(crash.timestamp);\n }\n if (crash.description) {\n context['description'] = crash.description;\n }\n if (crash.processName) {\n context['processName'] = crash.processName;\n }\n if (crash.pid) {\n context['pid'] = String(crash.pid);\n }\n if (crash.importance !== undefined) {\n context['importance'] = String(crash.importance);\n }\n\n // Push as error via Faro API (matching Flutter pattern)\n this.api.pushError(error, {\n type: 'crash',\n context,\n });\n\n this.logDebug(`Reported crash: ${crash.reason} at ${crash.timestamp}`);\n }\n\n /**\n * Build error message matching Flutter SDK format:\n * \"{reason}: {description}, status: {status}\"\n */\n private getErrorMessage(crash: CrashReport): string {\n const reason = crash.reason || 'UNKNOWN';\n const status = crash.status ?? 0;\n\n let description: string;\n switch (crash.reason) {\n case 'ANR':\n description = 'Application Not Responding';\n break;\n case 'CRASH':\n description = 'Application crash (Java/Kotlin)';\n break;\n case 'CRASH_NATIVE':\n description = 'Application crash (Native)';\n break;\n case 'LOW_MEMORY':\n description = 'Application terminated due to low memory';\n break;\n case 'EXCESSIVE_RESOURCE_USAGE':\n description = 'Application terminated due to excessive resource usage';\n break;\n case 'INITIALIZATION_FAILURE':\n description = 'Application failed to initialize';\n break;\n default:\n description = crash.description || 'Application crash';\n break;\n }\n\n return `${reason}: ${description}, status: ${status}`;\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModules } from 'react-native';\n\nimport { BaseInstrumentation, VERSION } from '@grafana/faro-core';\n\nimport { ErrorMechanism } from '../errors/const';\n\nimport type { CrashReport, CrashReportingOptions } from './types';\n\n/**\n * Crash Reporting Instrumentation (Experimental).\n *\n * Retrieves crash reports from previous app sessions and sends them via Faro API.\n *\n * **Platform Support**:\n * - **Android**: Uses ApplicationExitInfo API (Android 11+ / API 30+)\n * Captures: CRASH, CRASH_NATIVE, ANR, LOW_MEMORY, EXCESSIVE_RESOURCE_USAGE\n * - **iOS**: Uses PLCrashReporter (requires adding dependency to podspec)\n * Captures: Signal crashes (SIGSEGV, SIGABRT, etc.) and Mach exceptions\n *\n * **Note**: This instrumentation only retrieves crash data from previous sessions.\n * The crash itself terminates the app, so the report is sent on next app launch.\n *\n * @example\n * ```typescript\n * import { initializeFaro, CrashReportingInstrumentation } from '@grafana/faro-react-native';\n *\n * initializeFaro({\n * url: 'https://collector.example.com',\n * instrumentations: [\n * new CrashReportingInstrumentation(),\n * ],\n * });\n * ```\n */\nexport class CrashReportingInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-crash';\n readonly version = VERSION;\n\n private readonly options: Required<CrashReportingOptions>;\n\n constructor(options: CrashReportingOptions = {}) {\n super();\n this.options = {\n enabled: options.enabled ?? true,\n };\n }\n\n async initialize(): Promise<void> {\n if (!this.options.enabled) {\n this.logDebug('Crash reporting is disabled');\n return;\n }\n\n const nativeModule = this.getNativeModule();\n if (!nativeModule) {\n this.logWarn('Native module not available for crash reporting');\n return;\n }\n\n this.logDebug('Initializing crash reporting instrumentation');\n\n // Wait for session attributes to be populated before sending crash reports\n // This ensures crash reports include full session metadata\n await this.waitForSessionAttributes();\n\n // Get crash reports from previous session\n await this.processCrashReports(nativeModule);\n }\n\n /**\n * Wait for session attributes to be populated.\n *\n * Crash reports are sent in a new session, so we need to wait for\n * SessionInstrumentation to finish collecting device info, OS version, etc.\n * before sending the crash report. Otherwise, the crash report will be missing\n * session metadata.\n *\n * This checks for multiple device-specific attributes that are collected\n * asynchronously (device_id, device_os_detail, device_model_name) to ensure\n * the async getSessionAttributes() call has completed.\n *\n * @param maxWaitMs Maximum time to wait in milliseconds (default 10000ms)\n */\n private async waitForSessionAttributes(maxWaitMs = 10000): Promise<void> {\n const startTime = Date.now();\n const pollInterval = 200; // Check every 200ms\n let checkCount = 0;\n\n while (Date.now() - startTime < maxWaitMs) {\n checkCount++;\n try {\n // Check if session attributes are populated\n const sessionAttrs = this.metas?.value?.session?.attributes;\n\n if (sessionAttrs) {\n const attrCount = Object.keys(sessionAttrs).length;\n const attrKeys = JSON.stringify(Object.keys(sessionAttrs));\n\n // Look for multiple device-specific attributes that indicate full async collection is done.\n // getSessionAttributes() collects these asynchronously:\n // - device_id (async getDeviceId)\n // - device_os_detail (async getDeviceOsDetail)\n // - device_model_name (DeviceInfo.getDeviceNameSync)\n // All three should be present when collection is complete.\n const hasDeviceId = 'device_id' in sessionAttrs && sessionAttrs['device_id'] !== 'unknown';\n const hasDeviceOsDetail =\n 'device_os_detail' in sessionAttrs && sessionAttrs['device_os_detail'] !== 'unknown';\n const hasDeviceModelName = 'device_model_name' in sessionAttrs;\n\n if (hasDeviceId && hasDeviceOsDetail && hasDeviceModelName) {\n const elapsed = Date.now() - startTime;\n this.logDebug(`Session attributes ready after ${elapsed}ms (${checkCount} checks, ${attrCount} attrs)`);\n return;\n } else {\n this.logDebug(\n `Check #${checkCount}: Found ${attrCount} session attributes: ${attrKeys} but still missing required attrs - device_id:${hasDeviceId}, device_os_detail:${hasDeviceOsDetail}, device_model_name:${hasDeviceModelName}`\n );\n }\n } else {\n this.logDebug(`Check #${checkCount}: No session attributes available yet`);\n }\n } catch (error) {\n // Continue waiting if we can't access metas yet\n this.logDebug(`Check #${checkCount}: Error accessing metas: ${error}`);\n }\n\n // Wait before next check\n await new Promise((resolve) => setTimeout(resolve, pollInterval));\n }\n\n // Timeout reached - log warning but continue\n const finalAttrs = this.metas?.value?.session?.attributes;\n const finalAttrCount = finalAttrs ? Object.keys(finalAttrs).length : 0;\n const finalAttrKeys = finalAttrs ? JSON.stringify(Object.keys(finalAttrs)) : 'none';\n this.logWarn(\n `Session attributes not ready after ${maxWaitMs}ms timeout (${checkCount} checks, ${finalAttrCount} attrs: ${finalAttrKeys}). Sending crash report anyway.`\n );\n }\n\n private getNativeModule(): typeof NativeModules.FaroReactNativeModule | null {\n const { FaroReactNativeModule } = NativeModules;\n\n if (!FaroReactNativeModule) {\n return null;\n }\n\n return FaroReactNativeModule;\n }\n\n private async processCrashReports(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<void> {\n try {\n if (typeof nativeModule.getCrashReport !== 'function') {\n this.logDebug('getCrashReport method not available');\n return;\n }\n\n this.logDebug('Attempting to retrieve crash reports from native module');\n const crashReports = (await nativeModule.getCrashReport()) as string[] | null;\n\n if (!crashReports || crashReports.length === 0) {\n this.logDebug('No crash reports from previous session');\n return;\n }\n\n this.logDebug(`Processing ${crashReports.length} crash report(s) from previous session`);\n\n for (const crashJson of crashReports) {\n try {\n this.logDebug(`Parsing crash report JSON: ${crashJson.substring(0, 200)}...`);\n const crash = JSON.parse(crashJson) as CrashReport;\n this.sendCrashReport(crash);\n } catch (parseError) {\n // If parsing fails, still try to report something\n this.logError('Failed to parse crash report JSON', parseError);\n this.api.pushError(new Error('Application crash (parse error)'), {\n context: {\n mechanism: ErrorMechanism.CRASH,\n parseError: String(parseError),\n raw: crashJson.substring(0, 500), // Limit size\n },\n type: 'crash',\n });\n }\n }\n } catch (error) {\n this.logError('Failed to process crash reports', error);\n }\n }\n\n private sendCrashReport(crash: CrashReport): void {\n const errorMessage = this.getErrorMessage(crash);\n const error = new Error(errorMessage);\n\n // Build context from crash data (matching Flutter pattern)\n const context: Record<string, string> = {\n mechanism: ErrorMechanism.CRASH,\n };\n\n if (crash.trace) {\n context['trace'] = crash.trace;\n }\n if (crash.signal) {\n context['signal'] = crash.signal;\n }\n if (crash.timestamp) {\n context['timestamp'] = String(crash.timestamp);\n }\n if (crash.description) {\n context['description'] = crash.description;\n }\n if (crash.processName) {\n context['processName'] = crash.processName;\n }\n if (crash.pid) {\n context['pid'] = String(crash.pid);\n }\n if (crash.importance !== undefined) {\n context['importance'] = String(crash.importance);\n }\n\n // Push as error via Faro API (matching Flutter pattern)\n this.api.pushError(error, {\n type: 'crash',\n context,\n });\n\n this.logDebug(`Reported crash: ${crash.reason} at ${crash.timestamp}`);\n }\n\n /**\n * Build error message matching Flutter SDK format:\n * \"{reason}: {description}, status: {status}\"\n */\n private getErrorMessage(crash: CrashReport): string {\n const reason = crash.reason || 'UNKNOWN';\n const status = crash.status ?? 0;\n\n let description: string;\n switch (crash.reason) {\n case 'ANR':\n description = 'Application Not Responding';\n break;\n case 'CRASH':\n description = 'Application crash (Java/Kotlin)';\n break;\n case 'CRASH_NATIVE':\n description = 'Application crash (Native)';\n break;\n case 'LOW_MEMORY':\n description = 'Application terminated due to low memory';\n break;\n case 'EXCESSIVE_RESOURCE_USAGE':\n description = 'Application terminated due to excessive resource usage';\n break;\n case 'INITIALIZATION_FAILURE':\n description = 'Application failed to initialize';\n break;\n default:\n description = crash.description || 'Application crash';\n break;\n }\n\n return `${reason}: ${description}, status: ${status}`;\n }\n}\n"]}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { BaseInstrumentation } from '@grafana/faro-core';
|
|
2
|
-
import type { CrashReportingOptions } from './types';
|
|
3
|
-
/**
|
|
4
|
-
* Crash Reporting Instrumentation (Experimental).
|
|
5
|
-
*
|
|
6
|
-
* Retrieves crash reports from previous app sessions and sends them via Faro API.
|
|
7
|
-
*
|
|
8
|
-
* **Platform Support**:
|
|
9
|
-
* - **Android**: Uses ApplicationExitInfo API (Android 11+ / API 30+)
|
|
10
|
-
* Captures: CRASH, CRASH_NATIVE, ANR, LOW_MEMORY, EXCESSIVE_RESOURCE_USAGE
|
|
11
|
-
* - **iOS**: Uses PLCrashReporter (requires adding dependency to podspec)
|
|
12
|
-
* Captures: Signal crashes (SIGSEGV, SIGABRT, etc.) and Mach exceptions
|
|
13
|
-
*
|
|
14
|
-
* **Note**: This instrumentation only retrieves crash data from previous sessions.
|
|
15
|
-
* The crash itself terminates the app, so the report is sent on next app launch.
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```typescript
|
|
19
|
-
* import { initializeFaro, CrashReportingInstrumentation } from '@grafana/faro-react-native';
|
|
20
|
-
*
|
|
21
|
-
* initializeFaro({
|
|
22
|
-
* url: 'https://collector.example.com',
|
|
23
|
-
* instrumentations: [
|
|
24
|
-
* new CrashReportingInstrumentation(),
|
|
25
|
-
* ],
|
|
26
|
-
* });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export declare class CrashReportingInstrumentation extends BaseInstrumentation {
|
|
30
|
-
readonly name = "@grafana/faro-react-native:instrumentation-crash";
|
|
31
|
-
readonly version = "2.3.1";
|
|
32
|
-
private readonly options;
|
|
33
|
-
constructor(options?: CrashReportingOptions);
|
|
34
|
-
initialize(): Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
* Wait for session attributes to be populated.
|
|
37
|
-
*
|
|
38
|
-
* Crash reports are sent in a new session, so we need to wait for
|
|
39
|
-
* SessionInstrumentation to finish collecting device info, OS version, etc.
|
|
40
|
-
* before sending the crash report. Otherwise, the crash report will be missing
|
|
41
|
-
* session metadata.
|
|
42
|
-
*
|
|
43
|
-
* This checks for multiple device-specific attributes that are collected
|
|
44
|
-
* asynchronously (device_id, device_os_detail, device_model_name) to ensure
|
|
45
|
-
* the async getSessionAttributes() call has completed.
|
|
46
|
-
*
|
|
47
|
-
* @param maxWaitMs Maximum time to wait in milliseconds (default 10000ms)
|
|
48
|
-
*/
|
|
49
|
-
private waitForSessionAttributes;
|
|
50
|
-
private getNativeModule;
|
|
51
|
-
private processCrashReports;
|
|
52
|
-
private sendCrashReport;
|
|
53
|
-
/**
|
|
54
|
-
* Build error message matching Flutter SDK format:
|
|
55
|
-
* "{reason}: {description}, status: {status}"
|
|
56
|
-
*/
|
|
57
|
-
private getErrorMessage;
|
|
58
|
-
}
|