@grafana/faro-react-native 1.2.0 → 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.
Files changed (218) hide show
  1. package/android/build.gradle +118 -36
  2. package/android/consumer-proguard-rules.pro +2 -0
  3. package/android/src/main/java/com/grafana/faro/reactnative/ANRTracker.kt +61 -9
  4. package/android/src/main/java/com/grafana/faro/reactnative/AnrThreadDumpParser.kt +70 -0
  5. package/android/src/main/java/com/grafana/faro/reactnative/ApplicationExitTraceReader.kt +142 -0
  6. package/android/src/main/java/com/grafana/faro/reactnative/FaroAnrCache.kt +163 -0
  7. package/android/src/main/java/com/grafana/faro/reactnative/FaroAnrReporter.kt +163 -0
  8. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +243 -50
  9. package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +75 -0
  10. package/android/src/main/java/com/grafana/faro/reactnative/FaroNativeCrashTrace.kt +39 -0
  11. package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +99 -8
  12. package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativePackage.kt +1 -0
  13. package/android/src/main/java/com/grafana/faro/reactnative/FaroUncaughtExceptionHandler.kt +62 -0
  14. package/android/src/main/java/com/grafana/faro/reactnative/TombstoneBacktraceFormatter.kt +117 -0
  15. package/android/src/main/java/com/grafana/faro/reactnative/TombstoneModels.kt +35 -0
  16. package/android/src/main/java/com/grafana/faro/reactnative/TombstoneWireParser.kt +235 -0
  17. package/android/src/main/proto/tombstone.proto +62 -0
  18. package/android/src/test/java/com/grafana/faro/reactnative/AnrStackFiltersTest.kt +28 -0
  19. package/android/src/test/java/com/grafana/faro/reactnative/AnrThreadDumpParserTest.kt +38 -0
  20. package/android/src/test/java/com/grafana/faro/reactnative/FaroAnrCacheTest.kt +81 -0
  21. package/android/src/test/java/com/grafana/faro/reactnative/TombstoneWireParserTest.kt +51 -0
  22. package/dist/cjs/config/getRNInstrumentations.js +3 -1
  23. package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
  24. package/dist/cjs/config/makeRNConfig.js +48 -9
  25. package/dist/cjs/config/makeRNConfig.js.map +1 -1
  26. package/dist/cjs/config/types.js.map +1 -1
  27. package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
  28. package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
  29. package/dist/cjs/index.js +3 -2
  30. package/dist/cjs/index.js.map +1 -1
  31. package/dist/cjs/initialize.js +14 -8
  32. package/dist/cjs/initialize.js.map +1 -1
  33. package/dist/cjs/instrumentations/anr/anrStack.js +19 -0
  34. package/dist/cjs/instrumentations/anr/anrStack.js.map +1 -0
  35. package/dist/cjs/instrumentations/anr/instrumentation.js +374 -72
  36. package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
  37. package/dist/cjs/instrumentations/anr/types.js.map +1 -1
  38. package/dist/cjs/instrumentations/console/index.js +5 -1
  39. package/dist/cjs/instrumentations/console/index.js.map +1 -1
  40. package/dist/cjs/instrumentations/crashReporting/{instrumentation.js → BaseCrashReportingInstrumentation.js} +110 -103
  41. package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -0
  42. package/dist/cjs/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js +41 -0
  43. package/dist/cjs/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js.map +1 -0
  44. package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +87 -0
  45. package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -0
  46. package/dist/cjs/instrumentations/crashReporting/android/crashErrorMessage.js +123 -0
  47. package/dist/cjs/instrumentations/crashReporting/android/crashErrorMessage.js.map +1 -0
  48. package/dist/cjs/instrumentations/crashReporting/android/index.js +12 -0
  49. package/dist/cjs/instrumentations/crashReporting/android/index.js.map +1 -0
  50. package/dist/cjs/instrumentations/crashReporting/android/nativeTombstoneTrace.js +11 -0
  51. package/dist/cjs/instrumentations/crashReporting/android/nativeTombstoneTrace.js.map +1 -0
  52. package/dist/cjs/instrumentations/crashReporting/android/parseAndroidCrashTrace.js +181 -0
  53. package/dist/cjs/instrumentations/crashReporting/android/parseAndroidCrashTrace.js.map +1 -0
  54. package/dist/cjs/instrumentations/crashReporting/index.js +35 -2
  55. package/dist/cjs/instrumentations/crashReporting/index.js.map +1 -1
  56. package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +78 -0
  57. package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -0
  58. package/dist/cjs/instrumentations/crashReporting/ios/crashErrorMessage.js +103 -0
  59. package/dist/cjs/instrumentations/crashReporting/ios/crashErrorMessage.js.map +1 -0
  60. package/dist/cjs/instrumentations/crashReporting/ios/index.js +11 -0
  61. package/dist/cjs/instrumentations/crashReporting/ios/index.js.map +1 -0
  62. package/dist/cjs/instrumentations/crashReporting/ios/parseIosCrashTrace.js +48 -0
  63. package/dist/cjs/instrumentations/crashReporting/ios/parseIosCrashTrace.js.map +1 -0
  64. package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
  65. package/dist/cjs/instrumentations/errors/index.js +2 -1
  66. package/dist/cjs/instrumentations/errors/index.js.map +1 -1
  67. package/dist/cjs/instrumentations/session/installationId.js +91 -0
  68. package/dist/cjs/instrumentations/session/installationId.js.map +1 -0
  69. package/dist/cjs/instrumentations/session/sessionAttributes.js +152 -89
  70. package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
  71. package/dist/cjs/metas/appBuildIdentity.js +95 -0
  72. package/dist/cjs/metas/appBuildIdentity.js.map +1 -0
  73. package/dist/cjs/testUtils/mockDeviceInfo.js +5 -0
  74. package/dist/cjs/testUtils/mockDeviceInfo.js.map +1 -1
  75. package/dist/cjs/testUtils/setupTests.js +7 -0
  76. package/dist/cjs/testUtils/setupTests.js.map +1 -1
  77. package/dist/esm/config/getRNInstrumentations.js +3 -1
  78. package/dist/esm/config/getRNInstrumentations.js.map +1 -1
  79. package/dist/esm/config/makeRNConfig.js +49 -10
  80. package/dist/esm/config/makeRNConfig.js.map +1 -1
  81. package/dist/esm/config/types.js.map +1 -1
  82. package/dist/esm/generated/faroRNPackageMeta.js +1 -1
  83. package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
  84. package/dist/esm/index.js +1 -1
  85. package/dist/esm/index.js.map +1 -1
  86. package/dist/esm/initialize.js +11 -5
  87. package/dist/esm/initialize.js.map +1 -1
  88. package/dist/esm/instrumentations/anr/anrStack.js +16 -0
  89. package/dist/esm/instrumentations/anr/anrStack.js.map +1 -0
  90. package/dist/esm/instrumentations/anr/instrumentation.js +260 -67
  91. package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
  92. package/dist/esm/instrumentations/anr/types.js.map +1 -1
  93. package/dist/esm/instrumentations/console/index.js +5 -1
  94. package/dist/esm/instrumentations/console/index.js.map +1 -1
  95. package/dist/esm/instrumentations/crashReporting/{instrumentation.js → BaseCrashReportingInstrumentation.js} +76 -94
  96. package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -0
  97. package/dist/esm/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js +18 -0
  98. package/dist/esm/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js.map +1 -0
  99. package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +56 -0
  100. package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -0
  101. package/dist/esm/instrumentations/crashReporting/android/crashErrorMessage.js +116 -0
  102. package/dist/esm/instrumentations/crashReporting/android/crashErrorMessage.js.map +1 -0
  103. package/dist/esm/instrumentations/crashReporting/android/index.js +4 -0
  104. package/dist/esm/instrumentations/crashReporting/android/index.js.map +1 -0
  105. package/dist/esm/instrumentations/crashReporting/android/nativeTombstoneTrace.js +8 -0
  106. package/dist/esm/instrumentations/crashReporting/android/nativeTombstoneTrace.js.map +1 -0
  107. package/dist/esm/instrumentations/crashReporting/android/parseAndroidCrashTrace.js +174 -0
  108. package/dist/esm/instrumentations/crashReporting/android/parseAndroidCrashTrace.js.map +1 -0
  109. package/dist/esm/instrumentations/crashReporting/index.js +35 -1
  110. package/dist/esm/instrumentations/crashReporting/index.js.map +1 -1
  111. package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +56 -0
  112. package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -0
  113. package/dist/esm/instrumentations/crashReporting/ios/crashErrorMessage.js +98 -0
  114. package/dist/esm/instrumentations/crashReporting/ios/crashErrorMessage.js.map +1 -0
  115. package/dist/esm/instrumentations/crashReporting/ios/index.js +4 -0
  116. package/dist/esm/instrumentations/crashReporting/ios/index.js.map +1 -0
  117. package/dist/esm/instrumentations/crashReporting/ios/parseIosCrashTrace.js +44 -0
  118. package/dist/esm/instrumentations/crashReporting/ios/parseIosCrashTrace.js.map +1 -0
  119. package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
  120. package/dist/esm/instrumentations/errors/index.js +2 -1
  121. package/dist/esm/instrumentations/errors/index.js.map +1 -1
  122. package/dist/esm/instrumentations/session/installationId.js +48 -0
  123. package/dist/esm/instrumentations/session/installationId.js.map +1 -0
  124. package/dist/esm/instrumentations/session/sessionAttributes.js +83 -38
  125. package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
  126. package/dist/esm/metas/appBuildIdentity.js +56 -0
  127. package/dist/esm/metas/appBuildIdentity.js.map +1 -0
  128. package/dist/esm/testUtils/mockDeviceInfo.js +5 -0
  129. package/dist/esm/testUtils/mockDeviceInfo.js.map +1 -1
  130. package/dist/esm/testUtils/setupTests.js +7 -0
  131. package/dist/esm/testUtils/setupTests.js.map +1 -1
  132. package/dist/types/config/makeRNConfig.d.ts +4 -3
  133. package/dist/types/config/types.d.ts +2 -1
  134. package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
  135. package/dist/types/index.d.ts +2 -2
  136. package/dist/types/initialize.d.ts +4 -2
  137. package/dist/types/instrumentations/anr/anrStack.d.ts +7 -0
  138. package/dist/types/instrumentations/anr/instrumentation.d.ts +23 -28
  139. package/dist/types/instrumentations/anr/types.d.ts +5 -1
  140. package/dist/types/instrumentations/appState/index.d.ts +1 -1
  141. package/dist/types/instrumentations/console/index.d.ts +1 -1
  142. package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +56 -0
  143. package/dist/types/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.d.ts +12 -0
  144. package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +26 -0
  145. package/dist/types/instrumentations/crashReporting/android/crashErrorMessage.d.ts +23 -0
  146. package/dist/types/instrumentations/crashReporting/android/index.d.ts +4 -0
  147. package/dist/types/instrumentations/crashReporting/android/nativeTombstoneTrace.d.ts +2 -0
  148. package/dist/types/instrumentations/crashReporting/android/parseAndroidCrashTrace.d.ts +45 -0
  149. package/dist/types/instrumentations/crashReporting/index.d.ts +25 -2
  150. package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +28 -0
  151. package/dist/types/instrumentations/crashReporting/ios/crashErrorMessage.d.ts +14 -0
  152. package/dist/types/instrumentations/crashReporting/ios/index.d.ts +4 -0
  153. package/dist/types/instrumentations/crashReporting/ios/parseIosCrashTrace.d.ts +38 -0
  154. package/dist/types/instrumentations/crashReporting/types.d.ts +18 -51
  155. package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +1 -1
  156. package/dist/types/instrumentations/http/index.d.ts +1 -1
  157. package/dist/types/instrumentations/performance/index.d.ts +1 -1
  158. package/dist/types/instrumentations/session/index.d.ts +1 -1
  159. package/dist/types/instrumentations/session/installationId.d.ts +1 -0
  160. package/dist/types/instrumentations/session/sessionAttributes.d.ts +13 -4
  161. package/dist/types/instrumentations/startup/index.d.ts +1 -1
  162. package/dist/types/instrumentations/userActions/index.d.ts +1 -1
  163. package/dist/types/instrumentations/view/index.d.ts +1 -1
  164. package/dist/types/instrumentations/xhr/index.d.ts +1 -1
  165. package/dist/types/metas/appBuildIdentity.d.ts +16 -0
  166. package/dist/types/testUtils/mockFaroReactNativeTracing.d.ts +1 -1
  167. package/dist/types/transports/console/transport.d.ts +1 -1
  168. package/dist/types/transports/fetch/transport.d.ts +1 -1
  169. package/dist/types/transports/offline/transport.d.ts +1 -1
  170. package/ios/faro-upload-composed-source-map.sh +8 -2
  171. package/package.json +5 -5
  172. package/src/config/getRNInstrumentations.ts +5 -1
  173. package/src/config/makeRNConfig.test.ts +59 -3
  174. package/src/config/makeRNConfig.ts +48 -9
  175. package/src/config/types.ts +2 -1
  176. package/src/generated/faroRNPackageMeta.ts +1 -1
  177. package/src/index.ts +2 -1
  178. package/src/initialize.test.ts +71 -15
  179. package/src/initialize.ts +11 -5
  180. package/src/instrumentations/anr/anrStack.test.ts +27 -0
  181. package/src/instrumentations/anr/anrStack.ts +19 -0
  182. package/src/instrumentations/anr/instrumentation.test.ts +45 -0
  183. package/src/instrumentations/anr/instrumentation.ts +301 -67
  184. package/src/instrumentations/anr/types.ts +7 -1
  185. package/src/instrumentations/console/index.ts +5 -1
  186. package/src/instrumentations/console/instrumentation.test.ts +5 -0
  187. package/src/instrumentations/crashReporting/{instrumentation.ts → BaseCrashReportingInstrumentation.ts} +119 -93
  188. package/src/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.ts +23 -0
  189. package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +62 -0
  190. package/src/instrumentations/crashReporting/android/crashErrorMessage.test.ts +182 -0
  191. package/src/instrumentations/crashReporting/android/crashErrorMessage.ts +135 -0
  192. package/src/instrumentations/crashReporting/android/index.ts +4 -0
  193. package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.test.ts +19 -0
  194. package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.ts +8 -0
  195. package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.test.ts +232 -0
  196. package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.ts +235 -0
  197. package/src/instrumentations/crashReporting/index.ts +43 -2
  198. package/src/instrumentations/crashReporting/instrumentation.test.ts +42 -0
  199. package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +62 -0
  200. package/src/instrumentations/crashReporting/ios/crashErrorMessage.test.ts +170 -0
  201. package/src/instrumentations/crashReporting/ios/crashErrorMessage.ts +111 -0
  202. package/src/instrumentations/crashReporting/ios/index.ts +4 -0
  203. package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.test.ts +86 -0
  204. package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.ts +83 -0
  205. package/src/instrumentations/crashReporting/types.ts +19 -58
  206. package/src/instrumentations/errors/index.ts +2 -1
  207. package/src/instrumentations/errors/instrumentation.test.ts +8 -2
  208. package/src/instrumentations/errors/stackTraceParser.test.ts +1 -1
  209. package/src/instrumentations/session/installationId.ts +43 -0
  210. package/src/instrumentations/session/sessionAttributes.test.ts +260 -30
  211. package/src/instrumentations/session/sessionAttributes.ts +112 -22
  212. package/src/metas/appBuildIdentity.test.ts +48 -0
  213. package/src/metas/appBuildIdentity.ts +50 -0
  214. package/src/testUtils/mockDeviceInfo.ts +5 -0
  215. package/src/testUtils/setupTests.ts +7 -0
  216. package/dist/cjs/instrumentations/crashReporting/instrumentation.js.map +0 -1
  217. package/dist/esm/instrumentations/crashReporting/instrumentation.js.map +0 -1
  218. 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
- /** Raw model identifier (e.g., "iPhone16,1", "SM-A155F") */
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., "iPhone", "samsung") */
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
- /** Unique device ID (UUID) */
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
- * Get device ID using react-native-device-info
95
- * Returns unique device identifier or 'unknown' on error
96
- */
97
- async function getDeviceId(): Promise<string> {
98
- try {
99
- // getUniqueId returns a UUID that persists across app installations
100
- return await DeviceInfo.getUniqueId();
101
- } catch (_error) {
102
- return 'unknown';
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
- // Get device ID asynchronously
156
- const deviceId = await getDeviceId();
157
- const deviceOsDetail = await getDeviceOsDetail();
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: deviceId,
225
- device_type: isTablet ? 'tablet' : 'mobile',
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 attributes;
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
- return minimalSessionDeviceAttributes();
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 getSessionAttributes();
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
- }