@grafana/faro-react-native 1.2.1 → 1.3.1

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 (217) 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/package.json +4 -4
  171. package/src/config/getRNInstrumentations.ts +5 -1
  172. package/src/config/makeRNConfig.test.ts +59 -3
  173. package/src/config/makeRNConfig.ts +48 -9
  174. package/src/config/types.ts +2 -1
  175. package/src/generated/faroRNPackageMeta.ts +1 -1
  176. package/src/index.ts +2 -1
  177. package/src/initialize.test.ts +71 -15
  178. package/src/initialize.ts +11 -5
  179. package/src/instrumentations/anr/anrStack.test.ts +27 -0
  180. package/src/instrumentations/anr/anrStack.ts +19 -0
  181. package/src/instrumentations/anr/instrumentation.test.ts +45 -0
  182. package/src/instrumentations/anr/instrumentation.ts +301 -67
  183. package/src/instrumentations/anr/types.ts +7 -1
  184. package/src/instrumentations/console/index.ts +5 -1
  185. package/src/instrumentations/console/instrumentation.test.ts +5 -0
  186. package/src/instrumentations/crashReporting/{instrumentation.ts → BaseCrashReportingInstrumentation.ts} +119 -93
  187. package/src/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.ts +23 -0
  188. package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +62 -0
  189. package/src/instrumentations/crashReporting/android/crashErrorMessage.test.ts +182 -0
  190. package/src/instrumentations/crashReporting/android/crashErrorMessage.ts +135 -0
  191. package/src/instrumentations/crashReporting/android/index.ts +4 -0
  192. package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.test.ts +19 -0
  193. package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.ts +8 -0
  194. package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.test.ts +232 -0
  195. package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.ts +235 -0
  196. package/src/instrumentations/crashReporting/index.ts +43 -2
  197. package/src/instrumentations/crashReporting/instrumentation.test.ts +42 -0
  198. package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +62 -0
  199. package/src/instrumentations/crashReporting/ios/crashErrorMessage.test.ts +170 -0
  200. package/src/instrumentations/crashReporting/ios/crashErrorMessage.ts +111 -0
  201. package/src/instrumentations/crashReporting/ios/index.ts +4 -0
  202. package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.test.ts +86 -0
  203. package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.ts +83 -0
  204. package/src/instrumentations/crashReporting/types.ts +19 -58
  205. package/src/instrumentations/errors/index.ts +2 -1
  206. package/src/instrumentations/errors/instrumentation.test.ts +8 -2
  207. package/src/instrumentations/errors/stackTraceParser.test.ts +1 -1
  208. package/src/instrumentations/session/installationId.ts +43 -0
  209. package/src/instrumentations/session/sessionAttributes.test.ts +260 -30
  210. package/src/instrumentations/session/sessionAttributes.ts +112 -22
  211. package/src/metas/appBuildIdentity.test.ts +48 -0
  212. package/src/metas/appBuildIdentity.ts +50 -0
  213. package/src/testUtils/mockDeviceInfo.ts +5 -0
  214. package/src/testUtils/setupTests.ts +7 -0
  215. package/dist/cjs/instrumentations/crashReporting/instrumentation.js.map +0 -1
  216. package/dist/esm/instrumentations/crashReporting/instrumentation.js.map +0 -1
  217. package/dist/types/instrumentations/crashReporting/instrumentation.d.ts +0 -58
@@ -4,7 +4,7 @@ import type { ConsoleTransportOptions } from './types';
4
4
  export declare class ConsoleTransport extends BaseTransport {
5
5
  private options;
6
6
  readonly name = "@grafana/faro-react-native:transport-console";
7
- readonly version = "2.3.1";
7
+ readonly version = "2.8.2";
8
8
  constructor(options?: ConsoleTransportOptions);
9
9
  send(item: TransportItem): void;
10
10
  }
@@ -4,7 +4,7 @@ import type { FetchTransportOptions } from './types';
4
4
  export declare class FetchTransport extends BaseTransport {
5
5
  private options;
6
6
  readonly name = "@grafana/faro-react-native:transport-fetch";
7
- readonly version = "2.3.1";
7
+ readonly version = "2.8.2";
8
8
  promiseBuffer: PromiseBuffer<Response | void>;
9
9
  private readonly rateLimitBackoffMs;
10
10
  private readonly getNow;
@@ -31,7 +31,7 @@ import type { OfflineTransportOptions } from './types';
31
31
  */
32
32
  export declare class OfflineTransport extends BaseTransport {
33
33
  readonly name = "@grafana/faro-react-native:transport-offline";
34
- readonly version = "2.3.1";
34
+ readonly version = "2.8.2";
35
35
  private readonly cache;
36
36
  private readonly connectivityService;
37
37
  private readonly maxCacheDurationMs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/faro-react-native",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "Faro instrumentations, metas, transports for React Native.",
5
5
  "keywords": [
6
6
  "observability",
@@ -56,7 +56,7 @@
56
56
  "version": "node ./scripts/sync-faro-rn-package-meta.mjs && git add src/generated/faroRNPackageMeta.ts"
57
57
  },
58
58
  "dependencies": {
59
- "@grafana/faro-core": "^2.2.3",
59
+ "@grafana/faro-core": "^2.8.2",
60
60
  "@react-native-async-storage/async-storage": "^1.21.0",
61
61
  "react-native-device-info": "^11.1.0"
62
62
  },
@@ -79,10 +79,10 @@
79
79
  "@types/react": "19.2.15",
80
80
  "@types/react-native": "0.73.0",
81
81
  "react-native-mmkv": "4.3.1",
82
- "react-native-nitro-modules": "0.35.6"
82
+ "react-native-nitro-modules": "0.36.1"
83
83
  },
84
84
  "publishConfig": {
85
85
  "access": "public"
86
86
  },
87
- "gitHead": "ae48b3fc6f2fc4201d634edd38e2713f459dd008"
87
+ "gitHead": "fed3084557bb9c34129fff2b190ad4758fad7daf"
88
88
  }
@@ -144,7 +144,11 @@ export function getRNInstrumentations(config: Partial<ReactNativeConfig> = {}):
144
144
 
145
145
  // Crash reporting
146
146
  if (enableCrashReporting) {
147
- instrumentations.push(new CrashReportingInstrumentation());
147
+ instrumentations.push(
148
+ new CrashReportingInstrumentation({
149
+ releaseBundleFilename: config.releaseBundleFilename,
150
+ })
151
+ );
148
152
  }
149
153
 
150
154
  if (enableTracing) {
@@ -1,6 +1,9 @@
1
1
  import { BaseInstrumentation, VERSION } from '@grafana/faro-core';
2
2
 
3
- import { minimalSessionDeviceAttributes } from '../instrumentations/session/sessionAttributes';
3
+ import {
4
+ minimalSessionDeviceAttributes,
5
+ type PreloadedMobileMeta,
6
+ } from '../instrumentations/session/sessionAttributes';
4
7
  import { defaultSessionTrackingConfig } from '../instrumentations/session/sessionManager/sessionConstants';
5
8
  import { ConsoleTransport } from '../transports/console';
6
9
  import { FetchTransport } from '../transports/fetch';
@@ -116,16 +119,69 @@ describe('makeRNConfig', () => {
116
119
  expect(minimal.instrumentations?.map((i) => i.name)).toEqual(direct.map((i) => i.name));
117
120
  });
118
121
 
119
- it('passes preloaded session device attributes to core config when provided as second argument', () => {
122
+ it('passes preloaded mobile meta and flat session device attributes to core config', () => {
120
123
  const preloaded = minimalSessionDeviceAttributes();
121
124
  preloaded.device_id = 'test-preloaded-id';
122
- const cfg = makeRNConfig({ ...base }, preloaded);
125
+ const preloadedMobileMeta: PreloadedMobileMeta = {
126
+ sessionAttributes: preloaded,
127
+ meta: {
128
+ app: {
129
+ installationId: 'test-installation-id',
130
+ },
131
+ device: {
132
+ is_physical: true,
133
+ model_identifier: 'iPhone16,1',
134
+ },
135
+ os: {
136
+ name: 'iOS',
137
+ version: '17.0',
138
+ },
139
+ },
140
+ };
141
+
142
+ const cfg = makeRNConfig({ ...base }, preloadedMobileMeta);
143
+
144
+ expect(cfg.app.name).toBe('test-app');
145
+ expect(cfg.app.installationId).toBe('test-installation-id');
146
+ expect(cfg.preloadedMobileMeta).toEqual(preloadedMobileMeta.meta);
123
147
  expect(
124
148
  (cfg as { preloadedSessionDeviceAttributes?: { device_id: string } }).preloadedSessionDeviceAttributes
125
149
  ).toBeDefined();
126
150
  expect(
127
151
  (cfg as { preloadedSessionDeviceAttributes?: { device_id: string } }).preloadedSessionDeviceAttributes?.device_id
128
152
  ).toBe('test-preloaded-id');
153
+ expect(cfg.metas).toContainEqual({
154
+ device: {
155
+ is_physical: true,
156
+ model_identifier: 'iPhone16,1',
157
+ },
158
+ os: {
159
+ name: 'iOS',
160
+ version: '17.0',
161
+ },
162
+ });
163
+ });
164
+
165
+ it('keeps explicit config app installationId over preloaded installationId', () => {
166
+ const cfg = makeRNConfig(
167
+ {
168
+ ...base,
169
+ app: {
170
+ ...base.app,
171
+ installationId: 'explicit-installation-id',
172
+ },
173
+ },
174
+ {
175
+ sessionAttributes: minimalSessionDeviceAttributes(),
176
+ meta: {
177
+ app: {
178
+ installationId: 'preloaded-installation-id',
179
+ },
180
+ },
181
+ }
182
+ );
183
+
184
+ expect(cfg.app.installationId).toBe('explicit-installation-id');
129
185
  });
130
186
 
131
187
  it('uses releaseBundleFilename in default parseStacktrace when set', () => {
@@ -1,10 +1,11 @@
1
1
  import { defaultGlobalObjectKey, defaultUnpatchedConsole } from '@grafana/faro-core';
2
- import type { Config } from '@grafana/faro-core';
2
+ import type { Config, MetaApp } from '@grafana/faro-core';
3
3
 
4
4
  import { getStackFramesFromError } from '../instrumentations/errors/stackTraceParser';
5
- import type { SessionAttributes } from '../instrumentations/session/sessionAttributes';
5
+ import type { PreloadedMobileMeta } from '../instrumentations/session/sessionAttributes';
6
6
  import { defaultSessionTrackingConfig } from '../instrumentations/session/sessionManager/sessionConstants';
7
7
  import { InternalLoggerLevel, LogLevel } from '../internalLogger';
8
+ import { getMetroInjectedBundleId } from '../metas/appBuildIdentity';
8
9
  import { getPageMeta } from '../metas/page';
9
10
  import { getScreenMeta } from '../metas/screen';
10
11
  import { getSdkMeta } from '../metas/sdk';
@@ -17,6 +18,28 @@ import type { ReactNativeConfig, ReactNativeFullConfig } from './types';
17
18
 
18
19
  const DEFAULT_OFFLINE_CACHE_MS = 3 * 24 * 60 * 60 * 1000; // 3 days
19
20
 
21
+ /**
22
+ * Resolves `meta.app.bundleId` when it is not already supplied by faro-core from
23
+ * the Metro preamble (`registerInitialMetas` / `__faroBundleId_<appName>`).
24
+ *
25
+ * Priority: explicit `config.app.bundleId` → Metro preamble (core) → DeviceInfo
26
+ * fallback (`applicationId@versionCode@versionName`, same shape Gradle/Metro use).
27
+ */
28
+ function resolveAppBundleId(
29
+ configApp: MetaApp | undefined,
30
+ appName: string | undefined,
31
+ appSymbolsBundleId?: string
32
+ ): string | undefined {
33
+ if (configApp?.bundleId) {
34
+ return configApp.bundleId;
35
+ }
36
+ const metroId = getMetroInjectedBundleId(appName);
37
+ if (metroId) {
38
+ return metroId;
39
+ }
40
+ return appSymbolsBundleId;
41
+ }
42
+
20
43
  /**
21
44
  * Builds transports. FetchTransport is always added when url is provided.
22
45
  * User can enable offline and console via enableTransports.
@@ -77,22 +100,38 @@ function createParseStacktrace(releaseBundleFilename: string | undefined): Confi
77
100
  * Based on flags, builds instrumentations and transports automatically.
78
101
  * Client just enables what they need; makeRNConfig does the rest.
79
102
  *
80
- * @param preloadedSessionDeviceAttributes Device/session fields for session meta (passed from async `initializeFaro`).
103
+ * @param preloadedMobileMeta Device/session fields and structured mobile meta (passed from async `initializeFaro`).
104
+ * @param appSymbolsBundleId Encoded `meta.app.bundleId` for server-side symbol retrace.
81
105
  */
82
106
  export function makeRNConfig(
83
107
  config: ReactNativeConfig,
84
- preloadedSessionDeviceAttributes?: SessionAttributes
108
+ preloadedMobileMeta?: PreloadedMobileMeta,
109
+ appSymbolsBundleId?: string
85
110
  ): ReactNativeFullConfig {
86
- const defaultMetas = [getSdkMeta(), getPageMeta(), getScreenMeta()];
111
+ const { app: preloadedAppMeta, ...structuredMobileMeta } = preloadedMobileMeta?.meta ?? {};
112
+ const mobileMetas = Object.keys(structuredMobileMeta).length > 0 ? [structuredMobileMeta] : [];
113
+ const defaultMetas = [getSdkMeta(), getPageMeta(), getScreenMeta(), ...mobileMetas];
87
114
  const customMetas = config.metas ?? [];
88
115
  const transports = buildTransports(config);
89
116
  const instrumentations = buildInstrumentations(config);
117
+ const installationId = config.app.installationId ?? preloadedAppMeta?.installationId;
90
118
 
91
119
  const releaseBundleFilename = config.releaseBundleFilename;
120
+ // Merge bundleId (from config, Metro, or DeviceInfo) with preloaded app meta
121
+ const bundleIdValue = resolveAppBundleId(config.app, config.app?.name, appSymbolsBundleId);
122
+ const appMetasIfPresent =
123
+ preloadedAppMeta || bundleIdValue
124
+ ? [{ app: { ...preloadedAppMeta, ...(bundleIdValue && { bundleId: bundleIdValue }) } }]
125
+ : [];
126
+
92
127
  return {
93
- app: config.app,
94
- ...(preloadedSessionDeviceAttributes != null && {
95
- preloadedSessionDeviceAttributes,
128
+ app: {
129
+ ...config.app,
130
+ ...(installationId && { installationId }),
131
+ },
132
+ ...(preloadedMobileMeta != null && {
133
+ preloadedMobileMeta: preloadedMobileMeta.meta,
134
+ preloadedSessionDeviceAttributes: preloadedMobileMeta.sessionAttributes,
96
135
  }),
97
136
  dedupe: config.dedupe ?? true,
98
137
  globalObjectKey: config.globalObjectKey ?? defaultGlobalObjectKey,
@@ -111,7 +150,7 @@ export function makeRNConfig(
111
150
  ...defaultSessionTrackingConfig,
112
151
  ...config.sessionTracking,
113
152
  },
114
- metas: [...defaultMetas, ...customMetas],
153
+ metas: [...defaultMetas, ...customMetas, ...appMetasIfPresent],
115
154
  instrumentations,
116
155
  transports,
117
156
  ignoreUrls: config.ignoreUrls ?? [],
@@ -2,7 +2,7 @@ import type { Config } from '@grafana/faro-core';
2
2
 
3
3
  import type { ANRInstrumentationOptions } from '../instrumentations/anr';
4
4
  import type { FrameMonitoringOptions } from '../instrumentations/frameMonitoring';
5
- import type { SessionAttributes } from '../instrumentations/session/sessionAttributes';
5
+ import type { PreloadedMobileMeta, SessionAttributes } from '../instrumentations/session/sessionAttributes';
6
6
 
7
7
  import type { Sampling } from './sampling';
8
8
 
@@ -92,5 +92,6 @@ export type ReactNativeConfig = Partial<
92
92
  * Extends faro-core `Config` with RN-specific fields that instrumentations need at init time.
93
93
  */
94
94
  export type ReactNativeFullConfig = Config & {
95
+ preloadedMobileMeta?: PreloadedMobileMeta['meta'];
95
96
  preloadedSessionDeviceAttributes?: SessionAttributes;
96
97
  };
@@ -2,4 +2,4 @@
2
2
  * NPM package identity for this build. Generated by scripts/sync-faro-rn-package-meta.mjs - do not edit.
3
3
  */
4
4
  export const FARO_REACT_NATIVE_NPM_NAME = '@grafana/faro-react-native' as const;
5
- export const FARO_REACT_NATIVE_NPM_VERSION = '1.2.1' as const;
5
+ export const FARO_REACT_NATIVE_NPM_VERSION = '1.3.1' as const;
package/src/index.ts CHANGED
@@ -21,10 +21,11 @@ export { ConsoleInstrumentation } from './instrumentations/console';
21
21
  export { SessionInstrumentation } from './instrumentations/session';
22
22
  export {
23
23
  getSessionAttributes,
24
+ loadMobileMetaForInit,
24
25
  loadSessionDeviceAttributesForInit,
25
26
  minimalSessionDeviceAttributes,
26
27
  } from './instrumentations/session/sessionAttributes';
27
- export type { SessionAttributes } from './instrumentations/session/sessionAttributes';
28
+ export type { PreloadedMobileMeta, SessionAttributes } from './instrumentations/session/sessionAttributes';
28
29
  export { ViewInstrumentation } from './instrumentations/view';
29
30
  export { AppStateInstrumentation } from './instrumentations/appState';
30
31
  export { UserActionInstrumentation } from './instrumentations/userActions';
@@ -92,21 +92,41 @@ describe('initializeFaro', () => {
92
92
  ).rejects.toThrow('url is required');
93
93
  });
94
94
 
95
- it('should await device attributes then merge preloaded session attributes', async () => {
96
- const spy = jest.spyOn(sessionAttributes, 'loadSessionDeviceAttributesForInit').mockResolvedValue({
97
- react_native_version: '0.0.1',
98
- device_os: 'iOS',
99
- device_os_version: '17.0',
100
- device_os_detail: 'iOS 17.0',
101
- device_manufacturer: 'apple',
102
- device_model: 'test-model',
103
- device_model_name: 'Test Phone',
104
- device_brand: 'Apple',
105
- device_is_physical: 'true',
106
- device_id: 'preloaded-device-id',
107
- device_type: 'mobile',
108
- device_memory_total: '100',
109
- device_memory_used: '50',
95
+ it('should await mobile meta then merge structured meta and preloaded session attributes', async () => {
96
+ const spy = jest.spyOn(sessionAttributes, 'loadMobileMetaForInit').mockResolvedValue({
97
+ sessionAttributes: {
98
+ react_native_version: '0.0.1',
99
+ device_os: 'iOS',
100
+ device_os_version: '17.0',
101
+ device_os_detail: 'iOS 17.0',
102
+ device_manufacturer: 'apple',
103
+ device_model: 'Test Phone',
104
+ device_model_name: "Test's iPhone",
105
+ device_brand: 'Apple',
106
+ device_is_physical: 'true',
107
+ device_id: 'preloaded-device-id',
108
+ device_type: 'mobile',
109
+ device_memory_total: '100',
110
+ device_memory_used: '50',
111
+ },
112
+ meta: {
113
+ app: {
114
+ installationId: 'preloaded-installation-id',
115
+ },
116
+ device: {
117
+ brand: 'iPhone',
118
+ is_physical: true,
119
+ manufacturer: 'apple',
120
+ model_identifier: 'test-model-identifier',
121
+ model_name: 'Test Phone',
122
+ type: 'mobile',
123
+ },
124
+ os: {
125
+ detail: 'iOS 17.0',
126
+ name: 'iOS',
127
+ version: '17.0',
128
+ },
129
+ },
110
130
  });
111
131
 
112
132
  const faro = await initializeFaro(
@@ -125,6 +145,9 @@ describe('initializeFaro', () => {
125
145
  expect(faro.metas.value.sdk?.name).toBe('faro-react-native');
126
146
  expect(faro.metas.value.sdk?.version).toBe(VERSION);
127
147
  expect(faro.metas.value.sdk?.integrations).toEqual([{ name: packageJson.name, version: packageJson.version }]);
148
+ expect(faro.metas.value.app?.installationId).toBe('preloaded-installation-id');
149
+ expect(faro.metas.value.device?.model_identifier).toBe('test-model-identifier');
150
+ expect(faro.metas.value.os?.name).toBe('iOS');
128
151
  expect(faro.metas.value.session?.attributes?.['react_native_version']).toBe('0.0.1');
129
152
  expect(faro.metas.value.session?.attributes?.['device_id']).toBe('preloaded-device-id');
130
153
 
@@ -148,4 +171,37 @@ describe('initializeFaro', () => {
148
171
 
149
172
  expect(faro.metas.value.app?.bundleId).toBe('release-bundle-from-metro');
150
173
  });
174
+
175
+ it('should keep config.app.bundleId when set explicitly (do not override with symbols id)', async () => {
176
+ const faro = await initializeFaro(
177
+ mockConfig({
178
+ url: 'http://localhost:12345/collect',
179
+ app: { bundleId: 'user-env-bundle-id' },
180
+ transports: [new MockTransport()],
181
+ instrumentations: [new SessionInstrumentation()],
182
+ sessionTracking: {
183
+ enabled: true,
184
+ persistent: false,
185
+ },
186
+ })
187
+ );
188
+
189
+ expect(faro.metas.value.app?.bundleId).toBe('user-env-bundle-id');
190
+ });
191
+
192
+ it('should set meta.app.bundleId (encoded build identity) from DeviceInfo when no Metro id', async () => {
193
+ const faro = await initializeFaro(
194
+ mockConfig({
195
+ url: 'http://localhost:12345/collect',
196
+ transports: [new MockTransport()],
197
+ instrumentations: [new SessionInstrumentation()],
198
+ sessionTracking: {
199
+ enabled: true,
200
+ persistent: false,
201
+ },
202
+ })
203
+ );
204
+
205
+ expect(faro.metas.value.app?.bundleId).toBe('com.example.myapp@42@1.0.0');
206
+ });
151
207
  });
package/src/initialize.ts CHANGED
@@ -2,14 +2,20 @@ import { type Faro, initializeFaro as initializeFaroCore } from '@grafana/faro-c
2
2
 
3
3
  import { makeRNConfig } from './config/makeRNConfig';
4
4
  import type { ReactNativeConfig } from './config/types';
5
- import { loadSessionDeviceAttributesForInit } from './instrumentations/session/sessionAttributes';
5
+ import { loadMobileMetaForInit } from './instrumentations/session/sessionAttributes';
6
+ import { loadAppSymbolsBundleIdForInit } from './metas/appBuildIdentity';
6
7
 
7
8
  /**
8
- * Awaits async device/session attribute collection, then initializes Faro with core `initializeFaro`.
9
- * On failure during collection, uses `minimalSessionDeviceAttributes` before init.
9
+ * Awaits async device/session attribute collection and the build identity used
10
+ * for server-side symbol retrace, then initializes Faro with core
11
+ * `initializeFaro`. On failure during collection, uses
12
+ * `minimalSessionDeviceAttributes` / an empty build identity before init.
10
13
  */
11
14
  export async function initializeFaro(config: ReactNativeConfig): Promise<Faro> {
12
- const preloadedSessionDeviceAttributes = await loadSessionDeviceAttributesForInit();
13
- const fullConfig = makeRNConfig(config, preloadedSessionDeviceAttributes);
15
+ const [preloadedMobileMeta, appSymbolsBundleId] = await Promise.all([
16
+ loadMobileMetaForInit(),
17
+ loadAppSymbolsBundleIdForInit(),
18
+ ]);
19
+ const fullConfig = makeRNConfig(config, preloadedMobileMeta, appSymbolsBundleId);
14
20
  return initializeFaroCore(fullConfig);
15
21
  }
@@ -0,0 +1,27 @@
1
+ import { isInvalidAnrCaptureStack } from './anrStack';
2
+
3
+ describe('isInvalidAnrCaptureStack', () => {
4
+ it('rejects empty stacks', () => {
5
+ expect(isInvalidAnrCaptureStack(undefined)).toBe(true);
6
+ expect(isInvalidAnrCaptureStack('')).toBe(true);
7
+ });
8
+
9
+ it('rejects crash-handler contamination', () => {
10
+ const stack = `
11
+ at com.android.internal.os.RuntimeInit$KillApplicationHandler.uncaughtException(RuntimeInit.java:174)
12
+ at com.grafana.faro.reactnative.FaroUncaughtExceptionHandler.uncaughtException(FaroUncaughtExceptionHandler.kt:42)
13
+ `.trim();
14
+
15
+ expect(isInvalidAnrCaptureStack(stack)).toBe(true);
16
+ });
17
+
18
+ it('accepts a typical blocked main-thread stack', () => {
19
+ const stack = `
20
+ at com.example.myapp.ui.HomeFragment.loadCatalog(HomeFragment.kt:84)
21
+ at com.example.myapp.ui.HomeFragment.onResume(HomeFragment.kt:52)
22
+ at android.app.Activity.performResume(Activity.java:8911)
23
+ `.trim();
24
+
25
+ expect(isInvalidAnrCaptureStack(stack)).toBe(false);
26
+ });
27
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Returns true when an ANR stack was sampled on the crash-killer path instead of
3
+ * the blocked main thread (common when ANRTracker fires during process teardown).
4
+ *
5
+ * Uses only Android framework and Faro SDK frame names — no app-specific identifiers.
6
+ */
7
+ export function isInvalidAnrCaptureStack(stack: string | undefined): boolean {
8
+ if (!stack?.trim()) {
9
+ return true;
10
+ }
11
+
12
+ const normalized = stack.toLowerCase();
13
+
14
+ return (
15
+ normalized.includes('handleapplicationcrash') ||
16
+ normalized.includes('killapplicationhandler') ||
17
+ normalized.includes('farouncaughtexceptionhandler')
18
+ );
19
+ }
@@ -0,0 +1,45 @@
1
+ import { type ExceptionEvent, initializeFaro, type TransportItem } from '@grafana/faro-core';
2
+ import { mockConfig, MockTransport } from '@grafana/faro-test-utils';
3
+
4
+ import { ANRInstrumentation } from './instrumentation';
5
+
6
+ describe('ANRInstrumentation', () => {
7
+ it('should report ANRs as fatal exceptions', async () => {
8
+ const transport = new MockTransport();
9
+ const instrumentation = new ANRInstrumentation();
10
+ const nativeModule = {
11
+ getANRStatus: jest.fn(() =>
12
+ Promise.resolve([
13
+ JSON.stringify({
14
+ duration: 5000,
15
+ stacktrace: 'main thread stack',
16
+ timestamp: 1710000000000,
17
+ type: 'ANR',
18
+ }),
19
+ ])
20
+ ),
21
+ };
22
+
23
+ initializeFaro(
24
+ mockConfig({
25
+ transports: [transport],
26
+ instrumentations: [instrumentation],
27
+ })
28
+ );
29
+
30
+ await (
31
+ instrumentation as unknown as {
32
+ checkANRStatus: (nativeModule: typeof nativeModule) => Promise<void>;
33
+ }
34
+ ).checkANRStatus(nativeModule);
35
+
36
+ const item = transport.items.find((transportItem) => {
37
+ return (transportItem.payload as { type?: string }).type === 'ANR';
38
+ }) as TransportItem<ExceptionEvent>;
39
+
40
+ expect(item.payload.type).toBe('ANR');
41
+ expect(item.payload.fatal).toBe(true);
42
+ expect(item.payload.context?.mechanism).toBe('anr');
43
+ expect(item.payload.context?.stacktrace).toBe('main thread stack');
44
+ });
45
+ });