@ansight/react-native 1.3.0-preview.3 → 1.3.0-preview.8

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/README.md CHANGED
@@ -77,8 +77,8 @@ bundle; its API requires the native Ansight module.
77
77
 
78
78
  This package version expects matching native SDK packages:
79
79
 
80
- - CocoaPods: `Ansight`, `AnsightObjC` version `1.3.0-preview.3`
81
- - Maven: `ai.ansight:ansight-android:1.3.0-preview.3`
80
+ - CocoaPods: `Ansight`, `AnsightObjC` version `1.3.0-preview.8`
81
+ - Maven: `ai.ansight:ansight-android:1.3.0-preview.8`
82
82
 
83
83
  ## Quickstart
84
84
 
@@ -136,10 +136,12 @@ The TypeScript `AnsightOptions` surface mirrors Android `AnsightOptions`, iOS
136
136
  | `retentionPeriodSeconds` | Local metric/event retention window. |
137
137
  | `enableFramesPerSecond` | Enables native FPS sampling. |
138
138
  | `enableBatteryLevel` | Enables battery sampling where available. |
139
+ | `enableOpenFileHandleTracking` | Enables native process open-file-handle sampling. Disabled by default. |
140
+ | `enableJniReferenceCountTracking` | Enables JNI reference-count tracking where the runtime can supply it. Disabled by default. |
139
141
  | `defaultMemoryChannels` | Selects built-in memory channels. Prefer `managedHeap`, `nativeHeap`, `residentSetSize`, and `physicalFootprint`; `javaHeap` and `rss` are accepted as Android/RN compatibility aliases. |
140
142
  | `reactNativeMemory` | Controls native React Native runtime memory channels. Enabled by default; set to `false` to disable, or use `{ jsHeapUsed, jsHeapTotal }`. |
141
143
  | `additionalChannels` | Registers custom metric channels. |
142
- | `sessionJpegCapture` | Object to enable/configure capture, or `false` to disable. Supports `captureGpuBackedSurfaces`; on iOS it defaults to `true` so Metal, SceneKit, and similar GPU-backed views are included. |
144
+ | `sessionJpegCapture` | Object to enable/configure capture, or `false` to disable. `mode` accepts `screenshotOnly`, `screenshotAndVisualTree`, or `screenshotWithVisualTreeOnTouch`. Supports `captureGpuBackedSurfaces`; on iOS it defaults to `true` so Metal, SceneKit, and similar GPU-backed views are included. |
143
145
  | `touchCapture` | Object to enable/configure capture, or `false` to disable. |
144
146
  | `lifecycleCapture` | Native lifecycle and screen-view capture options. |
145
147
  | `toolGuard` | `"disabled"`, `"readOnly"`, `"readWrite"`, or `"fullAccess"`. |
@@ -150,6 +152,11 @@ The TypeScript `AnsightOptions` surface mirrors Android `AnsightOptions`, iOS
150
152
  | `remoteTools` | Native visual tree, file, database, preferences, reflection, and secure-storage tool options. |
151
153
  | `lifecycle` | JS AppState tracking toggle. Defaults to true. |
152
154
 
155
+ Use `withOpenFileHandleTracking()` and `withJniReferenceCountTracking()` to
156
+ opt in through the builder. Matching `without...` methods disable the channels
157
+ again. Open handles are sampled by the native Android/iOS runtime. JNI counts
158
+ are available on Android only when the host integration can supply them.
159
+
153
160
  Cellular host connections are disabled by default. The restriction applies to
154
161
  enrollment and reconnect requests. Opt in only for a trusted development host
155
162
  or personal hotspot:
@@ -175,6 +182,7 @@ await Ansight.initializeAndActivate({
175
182
  quality: 60,
176
183
  maxWidth: 480,
177
184
  captureGpuBackedSurfaces: true,
185
+ mode: "screenshotWithVisualTreeOnTouch",
178
186
  },
179
187
  touchCapture: {
180
188
  captureMoveEvents: true,
@@ -192,6 +200,10 @@ await Ansight.initializeAndActivate({
192
200
  });
193
201
  ```
194
202
 
203
+ The touch mode captures native visual trees at gesture start, every 250 ms
204
+ throughout the gesture, and at the final up or cancel. It requires native touch
205
+ capture and visual-tree tools/providers to remain enabled.
206
+
195
207
  On iOS, `captureGpuBackedSurfaces` defaults to `true` so Metal, SceneKit, and
196
208
  similar GPU-backed views are included. Set it to `false` to use a lower-overhead
197
209
  capture path when those surfaces are not needed.
@@ -67,6 +67,6 @@ repositories {
67
67
  dependencies {
68
68
  implementation("com.facebook.react:react-android")
69
69
  def ansightAndroidVersion =
70
- findProperty("ansightAndroidVersion") ?: "1.3.0-preview.3"
71
- implementation("ai.ansight:ansight-android:1.3.0-preview.3")
70
+ findProperty("ansightAndroidVersion") ?: "1.3.0-preview.8"
71
+ implementation("ai.ansight:ansight-android:1.3.0-preview.8")
72
72
  }
@@ -6,6 +6,7 @@ import ai.ansight.runtime.AndroidToolExecutionContext
6
6
  import ai.ansight.runtime.AndroidToolResult
7
7
  import ai.ansight.runtime.AnsightChannel
8
8
  import ai.ansight.runtime.AnsightChannels
9
+ import ai.ansight.runtime.AnsightCrashCaptureOptions
9
10
  import ai.ansight.runtime.AnsightHostAutoProbeOptions
10
11
  import ai.ansight.runtime.AnsightHostConnectionOptions
11
12
  import ai.ansight.runtime.AnsightLogCallback
@@ -225,6 +226,21 @@ class AnsightReactNativeModule(
225
226
  }.resolve(promise)
226
227
  }
227
228
 
229
+ @ReactMethod
230
+ fun recordCrashCandidate(input: ReadableMap, promise: Promise) {
231
+ runCatching {
232
+ val candidateId = AnsightRuntime.recordCrashCandidate(
233
+ runtime = input.stringValue("runtime") ?: "react-native-javascript",
234
+ kind = input.stringValue("kind") ?: "unhandled_javascript_error",
235
+ message = input.stringValue("message"),
236
+ stack = input.stringValue("stack"),
237
+ fatal = input.booleanValue("fatal", false),
238
+ metadataJson = input.stringValue("metadata"),
239
+ )
240
+ Arguments.createMap().apply { putString("candidateId", candidateId) }
241
+ }.resolve(promise)
242
+ }
243
+
228
244
  @ReactMethod
229
245
  fun screenViewed(name: String, details: ReadableMap?, promise: Promise) {
230
246
  runCatching {
@@ -693,6 +709,22 @@ class AnsightReactNativeModule(
693
709
  if (map.hasBoolean("enableBatteryLevel")) {
694
710
  options = options.copy(enableBatteryLevel = map.booleanValue("enableBatteryLevel", options.enableBatteryLevel))
695
711
  }
712
+ if (map.hasBoolean("enableOpenFileHandleTracking")) {
713
+ options = options.copy(
714
+ enableOpenFileHandleTracking = map.booleanValue(
715
+ "enableOpenFileHandleTracking",
716
+ options.enableOpenFileHandleTracking,
717
+ ),
718
+ )
719
+ }
720
+ if (map.hasBoolean("enableJniReferenceCountTracking")) {
721
+ options = options.copy(
722
+ enableJniReferenceCountTracking = map.booleanValue(
723
+ "enableJniReferenceCountTracking",
724
+ options.enableJniReferenceCountTracking,
725
+ ),
726
+ )
727
+ }
696
728
  if (map.hasMap("defaultMemoryChannels")) {
697
729
  val memory = map.getMapOrNull("defaultMemoryChannels")
698
730
  val managedHeap = if (memory.hasBoolean("managedHeap")) {
@@ -751,6 +783,10 @@ class AnsightReactNativeModule(
751
783
  AnsightSessionJpegCaptureOptions.DefaultCaptureGpuBackedSurfaces,
752
784
  ),
753
785
  mode = sessionJpegCaptureMode(jpeg.stringValue("mode")),
786
+ captureKeyboardPresence = jpeg.booleanValue(
787
+ "captureKeyboardPresence",
788
+ AnsightSessionJpegCaptureOptions.DefaultCaptureKeyboardPresence,
789
+ ),
754
790
  )
755
791
  },
756
792
  )
@@ -768,6 +804,24 @@ class AnsightReactNativeModule(
768
804
  },
769
805
  )
770
806
  }
807
+ if (map.hasKey("crashCapture")) {
808
+ options = options.copy(
809
+ crashCapture = if (map.isFalse("crashCapture")) {
810
+ options.crashCapture.copy(enabled = false)
811
+ } else {
812
+ val crash = map.getMapOrNull("crashCapture")
813
+ AnsightCrashCaptureOptions(
814
+ enabled = crash.booleanValue("enabled", true),
815
+ studioHandoffEnabled = crash.booleanValue("studioHandoffEnabled", true),
816
+ offlineCaptureAttachmentEnabled = crash.booleanValue("offlineCaptureAttachmentEnabled", true),
817
+ maximumPendingReports = crash.intValue("maximumPendingReports", 8),
818
+ retentionDays = crash.intValue("retentionDays", 7),
819
+ maximumBreadcrumbs = crash.intValue("maximumBreadcrumbs", 64),
820
+ maximumTraceBytes = crash.intValue("maximumTraceBytes", 1_048_576),
821
+ )
822
+ },
823
+ )
824
+ }
771
825
  if (map.hasString("toolGuard")) {
772
826
  options = options.copy(toolGuard = toolGuard(map.stringValue("toolGuard")))
773
827
  }
@@ -931,14 +985,18 @@ class AnsightReactNativeModule(
931
985
  AnsightSessionJpegCaptureOptions.DefaultCaptureGpuBackedSurfaces,
932
986
  ),
933
987
  mode = sessionJpegCaptureMode(map.stringValue("mode")),
988
+ captureKeyboardPresence = map.booleanValue(
989
+ "captureKeyboardPresence",
990
+ AnsightSessionJpegCaptureOptions.DefaultCaptureKeyboardPresence,
991
+ ),
934
992
  )
935
993
  }
936
994
 
937
995
  private fun sessionJpegCaptureMode(value: String?): AnsightSessionJpegCaptureMode =
938
- if (value == "screenshotAndVisualTree") {
939
- AnsightSessionJpegCaptureMode.ScreenshotAndVisualTree
940
- } else {
941
- AnsightSessionJpegCaptureMode.ScreenshotOnly
996
+ when (value) {
997
+ "screenshotAndVisualTree" -> AnsightSessionJpegCaptureMode.ScreenshotAndVisualTree
998
+ "screenshotWithVisualTreeOnTouch" -> AnsightSessionJpegCaptureMode.ScreenshotWithVisualTreeOnTouch
999
+ else -> AnsightSessionJpegCaptureMode.ScreenshotOnly
942
1000
  }
943
1001
 
944
1002
  private fun toolDefinition(map: ReadableMap): ToolDefinition =
@@ -1111,6 +1169,8 @@ class AnsightReactNativeModule(
1111
1169
  putInt("retentionPeriodSeconds", options.retentionPeriodSeconds)
1112
1170
  putBoolean("enableFramesPerSecond", options.enableFramesPerSecond)
1113
1171
  putBoolean("enableBatteryLevel", options.enableBatteryLevel)
1172
+ putBoolean("enableOpenFileHandleTracking", options.enableOpenFileHandleTracking)
1173
+ putBoolean("enableJniReferenceCountTracking", options.enableJniReferenceCountTracking)
1114
1174
  putMap("defaultMemoryChannels", mapOf(
1115
1175
  "managedHeap" to options.defaultMemoryChannels.javaHeap,
1116
1176
  "javaHeap" to options.defaultMemoryChannels.javaHeap,
@@ -1127,10 +1187,11 @@ class AnsightReactNativeModule(
1127
1187
  "quality" to capture.quality,
1128
1188
  "maxWidth" to capture.maxWidth,
1129
1189
  "captureGpuBackedSurfaces" to capture.captureGpuBackedSurfaces,
1130
- "mode" to if (capture.mode == AnsightSessionJpegCaptureMode.ScreenshotAndVisualTree) {
1131
- "screenshotAndVisualTree"
1132
- } else {
1133
- "screenshotOnly"
1190
+ "captureKeyboardPresence" to capture.captureKeyboardPresence,
1191
+ "mode" to when (capture.mode) {
1192
+ AnsightSessionJpegCaptureMode.ScreenshotAndVisualTree -> "screenshotAndVisualTree"
1193
+ AnsightSessionJpegCaptureMode.ScreenshotWithVisualTreeOnTouch -> "screenshotWithVisualTreeOnTouch"
1194
+ else -> "screenshotOnly"
1134
1195
  },
1135
1196
  ).toWritableMap())
1136
1197
  } ?: putNull("sessionJpegCapture")
package/index.d.ts CHANGED
@@ -37,7 +37,8 @@ export interface AnsightSessionJpegCaptureOptions {
37
37
  quality?: number;
38
38
  maxWidth?: number;
39
39
  captureGpuBackedSurfaces?: boolean;
40
- mode?: "screenshotOnly" | "screenshotAndVisualTree";
40
+ captureKeyboardPresence?: boolean;
41
+ mode?: "screenshotOnly" | "screenshotAndVisualTree" | "screenshotWithVisualTreeOnTouch";
41
42
  }
42
43
 
43
44
  export interface AnsightTouchCaptureOptions {
@@ -110,6 +111,8 @@ export interface AnsightOptions {
110
111
  retentionPeriodSeconds?: number;
111
112
  enableFramesPerSecond?: boolean;
112
113
  enableBatteryLevel?: boolean;
114
+ enableOpenFileHandleTracking?: boolean;
115
+ enableJniReferenceCountTracking?: boolean;
113
116
  defaultMemoryChannels?: {
114
117
  managedHeap?: boolean;
115
118
  physicalFootprint?: boolean;
@@ -121,6 +124,7 @@ export interface AnsightOptions {
121
124
  reactNativeMemory?: false | AnsightReactNativeMemoryOptions;
122
125
  sessionJpegCapture?: false | AnsightSessionJpegCaptureOptions;
123
126
  touchCapture?: false | AnsightTouchCaptureOptions;
127
+ crashCapture?: false | AnsightCrashCaptureOptions;
124
128
  lifecycleCapture?: {
125
129
  enabled?: boolean;
126
130
  captureAppLifecycle?: boolean;
@@ -153,6 +157,25 @@ export interface AnsightOptions {
153
157
  lifecycle?: boolean;
154
158
  }
155
159
 
160
+ export interface AnsightCrashCaptureOptions {
161
+ enabled?: boolean;
162
+ studioHandoffEnabled?: boolean;
163
+ offlineCaptureAttachmentEnabled?: boolean;
164
+ maximumPendingReports?: number;
165
+ retentionDays?: number;
166
+ maximumBreadcrumbs?: number;
167
+ maximumTraceBytes?: number;
168
+ }
169
+
170
+ export interface AnsightCrashCandidate {
171
+ runtime?: string;
172
+ kind?: string;
173
+ message?: string;
174
+ stack?: string;
175
+ fatal?: boolean;
176
+ metadata?: string | Record<string, string>;
177
+ }
178
+
156
179
  export interface AnsightOperationResult {
157
180
  success: boolean;
158
181
  message: string;
@@ -251,6 +274,10 @@ export class AnsightOptionsBuilder {
251
274
  withoutFramesPerSecond(): this;
252
275
  withBatteryLevel(): this;
253
276
  withoutBatteryLevel(): this;
277
+ withOpenFileHandleTracking(): this;
278
+ withoutOpenFileHandleTracking(): this;
279
+ withJniReferenceCountTracking(): this;
280
+ withoutJniReferenceCountTracking(): this;
254
281
  withRetentionPeriodSeconds(retentionPeriodSeconds: number): this;
255
282
  withAdditionalChannels(additionalChannels: AnsightChannel[]): this;
256
283
  addAdditionalChannel(additionalChannel: AnsightChannel): this;
@@ -264,11 +291,14 @@ export class AnsightOptionsBuilder {
264
291
  quality?: number,
265
292
  maxWidth?: number | null,
266
293
  captureGpuBackedSurfaces?: boolean,
267
- mode?: "screenshotOnly" | "screenshotAndVisualTree"
294
+ mode?: "screenshotOnly" | "screenshotAndVisualTree" | "screenshotWithVisualTreeOnTouch",
295
+ captureKeyboardPresence?: boolean
268
296
  ): this;
269
297
  withoutSessionJpegCapture(): this;
270
298
  withTouchCapture(touchCapture?: AnsightTouchCaptureOptions): this;
271
299
  withoutTouchCapture(): this;
300
+ withCrashCapture(crashCapture?: AnsightCrashCaptureOptions): this;
301
+ withoutCrashCapture(): this;
272
302
  withLifecycleCapture(lifecycleCapture?: NonNullable<AnsightOptions["lifecycleCapture"]>): this;
273
303
  withToolGuard(toolGuard: NonNullable<AnsightOptions["toolGuard"]>): this;
274
304
  withToolsDisabled(): this;
@@ -512,6 +542,7 @@ export function metric(value: number, channel?: number): Promise<AnsightDebugSna
512
542
  export function recordMetric(value: number, channel?: number): Promise<AnsightDebugSnapshot>;
513
543
  export function event(input: string | { label: string; type?: string; details?: string; channel?: number }): Promise<AnsightDebugSnapshot>;
514
544
  export function recordEvent(input: string | { label: string; type?: string; details?: string; channel?: number }): Promise<AnsightDebugSnapshot>;
545
+ export function recordCrashCandidate(input?: AnsightCrashCandidate): Promise<{ candidateId?: string }>;
515
546
  export function screenViewed(name: string, details?: Record<string, string>): Promise<AnsightDebugSnapshot>;
516
547
  export function trackRoute(name: string, details?: Record<string, string>): Promise<AnsightDebugSnapshot>;
517
548
  export function setAppLifecycleState(state: AnsightLifecycleState): Promise<AnsightDebugSnapshot>;
@@ -585,6 +616,7 @@ declare const Ansight: {
585
616
  recordMetric: typeof recordMetric;
586
617
  event: typeof event;
587
618
  recordEvent: typeof recordEvent;
619
+ recordCrashCandidate: typeof recordCrashCandidate;
588
620
  screenViewed: typeof screenViewed;
589
621
  trackRoute: typeof trackRoute;
590
622
  setAppLifecycleState: typeof setAppLifecycleState;
package/index.js CHANGED
@@ -61,6 +61,9 @@ function cloneOptions(options = {}) {
61
61
  if (options.touchCapture && typeof options.touchCapture === "object") {
62
62
  clone.touchCapture = { ...options.touchCapture };
63
63
  }
64
+ if (options.crashCapture && typeof options.crashCapture === "object") {
65
+ clone.crashCapture = { ...options.crashCapture };
66
+ }
64
67
  if (options.lifecycleCapture) {
65
68
  clone.lifecycleCapture = { ...options.lifecycleCapture };
66
69
  }
@@ -157,6 +160,8 @@ class AnsightOptionsBuilder {
157
160
  retentionPeriodSeconds: 120,
158
161
  enableFramesPerSecond: true,
159
162
  enableBatteryLevel: false,
163
+ enableOpenFileHandleTracking: false,
164
+ enableJniReferenceCountTracking: false,
160
165
  sessionJpegCapture: {
161
166
  intervalMilliseconds: 2000,
162
167
  quality: 60,
@@ -209,6 +214,26 @@ class AnsightOptionsBuilder {
209
214
  return this;
210
215
  }
211
216
 
217
+ withOpenFileHandleTracking() {
218
+ this._options.enableOpenFileHandleTracking = true;
219
+ return this;
220
+ }
221
+
222
+ withoutOpenFileHandleTracking() {
223
+ this._options.enableOpenFileHandleTracking = false;
224
+ return this;
225
+ }
226
+
227
+ withJniReferenceCountTracking() {
228
+ this._options.enableJniReferenceCountTracking = true;
229
+ return this;
230
+ }
231
+
232
+ withoutJniReferenceCountTracking() {
233
+ this._options.enableJniReferenceCountTracking = false;
234
+ return this;
235
+ }
236
+
212
237
  withRetentionPeriodSeconds(retentionPeriodSeconds) {
213
238
  this._options.retentionPeriodSeconds = retentionPeriodSeconds;
214
239
  return this;
@@ -269,6 +294,7 @@ class AnsightOptionsBuilder {
269
294
  maxWidth: arguments.length > 2 ? arguments[2] : 480,
270
295
  captureGpuBackedSurfaces: arguments.length > 3 ? arguments[3] : true,
271
296
  mode: arguments.length > 4 ? arguments[4] : "screenshotOnly",
297
+ captureKeyboardPresence: arguments.length > 5 ? arguments[5] : false,
272
298
  };
273
299
  return this;
274
300
  }
@@ -276,6 +302,7 @@ class AnsightOptionsBuilder {
276
302
  intervalMilliseconds: 2000,
277
303
  quality: 60,
278
304
  maxWidth: 480,
305
+ captureKeyboardPresence: false,
279
306
  mode: "screenshotOnly",
280
307
  ...(optionsOrIntervalMilliseconds || {}),
281
308
  };
@@ -297,6 +324,16 @@ class AnsightOptionsBuilder {
297
324
  return this;
298
325
  }
299
326
 
327
+ withCrashCapture(crashCapture = {}) {
328
+ this._options.crashCapture = { ...crashCapture, enabled: true };
329
+ return this;
330
+ }
331
+
332
+ withoutCrashCapture() {
333
+ this._options.crashCapture = false;
334
+ return this;
335
+ }
336
+
300
337
  withLifecycleCapture(lifecycleCapture = {}) {
301
338
  this._options.lifecycleCapture = { ...lifecycleCapture };
302
339
  return this;
@@ -2218,6 +2255,14 @@ function installErrorHandlers(options = {}) {
2218
2255
 
2219
2256
  if (global.ErrorUtils && global.ErrorUtils.setGlobalHandler) {
2220
2257
  global.ErrorUtils.setGlobalHandler((error, isFatal) => {
2258
+ nativeModule.recordCrashCandidate({
2259
+ runtime: "react-native-javascript",
2260
+ kind: "unhandled_javascript_error",
2261
+ message: error && error.message,
2262
+ stack: error && error.stack,
2263
+ fatal: !!isFatal,
2264
+ metadata: JSON.stringify({ name: error && error.name }),
2265
+ }).catch(() => {});
2221
2266
  nativeModule.recordEvent({
2222
2267
  label: error && error.message ? error.message : "Unhandled JavaScript error",
2223
2268
  type: "Exception",
@@ -2240,6 +2285,13 @@ function installErrorHandlers(options = {}) {
2240
2285
  global.__ansightUnhandledRejectionTrackingInstalled = true;
2241
2286
  global.addEventListener("unhandledrejection", (event) => {
2242
2287
  const reason = event && event.reason;
2288
+ nativeModule.recordCrashCandidate({
2289
+ runtime: "react-native-javascript",
2290
+ kind: "unhandled_promise_rejection",
2291
+ message: reason && reason.message ? reason.message : String(reason),
2292
+ stack: reason && reason.stack,
2293
+ fatal: false,
2294
+ }).catch(() => {});
2243
2295
  nativeModule.recordEvent({
2244
2296
  label: reason && reason.message ? reason.message : "Unhandled JavaScript promise rejection",
2245
2297
  type: "Exception",
@@ -2334,6 +2386,16 @@ function recordEvent(input) {
2334
2386
  return nativeModule.recordEvent(input || {});
2335
2387
  }
2336
2388
 
2389
+ function recordCrashCandidate(input = {}) {
2390
+ const metadata = input.metadata && typeof input.metadata === "object"
2391
+ ? JSON.stringify(input.metadata)
2392
+ : input.metadata;
2393
+ return nativeModule.recordCrashCandidate({
2394
+ ...input,
2395
+ metadata,
2396
+ });
2397
+ }
2398
+
2337
2399
  function screenViewed(name, details) {
2338
2400
  return nativeModule.screenViewed(name, details || {});
2339
2401
  }
@@ -2376,6 +2438,7 @@ const Ansight = {
2376
2438
  recordMetric: (value, channel = 255) => nativeModule.recordMetric(value, channel),
2377
2439
  event: recordEvent,
2378
2440
  recordEvent,
2441
+ recordCrashCandidate,
2379
2442
  screenViewed,
2380
2443
  trackRoute,
2381
2444
  setAppLifecycleState: (state) => nativeModule.setAppLifecycleState(state),
@@ -292,6 +292,31 @@ final class AnsightReactNative: RCTEventEmitter {
292
292
  }
293
293
  }
294
294
 
295
+ @objc(recordCrashCandidate:resolver:rejecter:)
296
+ func recordCrashCandidate(
297
+ _ input: NSDictionary,
298
+ resolver resolve: RCTPromiseResolveBlock,
299
+ rejecter reject: RCTPromiseRejectBlock
300
+ ) {
301
+ let metadata: [String: String]
302
+ if let metadataJson = stringValue(input, "metadata"),
303
+ let data = metadataJson.data(using: .utf8),
304
+ let decoded = try? JSONDecoder().decode([String: String].self, from: data) {
305
+ metadata = decoded
306
+ } else {
307
+ metadata = [:]
308
+ }
309
+ let candidateId = AnsightRuntime.shared.recordCrashCandidate(
310
+ runtime: stringValue(input, "runtime") ?? "react-native-javascript",
311
+ kind: stringValue(input, "kind") ?? "unhandled_javascript_error",
312
+ message: stringValue(input, "message"),
313
+ stack: stringValue(input, "stack"),
314
+ fatal: boolValue(input, "fatal", defaultValue: false),
315
+ metadata: metadata
316
+ )
317
+ resolve(candidateId.map { ["candidateId": $0] } ?? [:])
318
+ }
319
+
295
320
  @objc(screenViewed:details:resolver:rejecter:)
296
321
  func screenViewed(
297
322
  _ name: NSString,
@@ -895,6 +920,13 @@ final class AnsightReactNative: RCTEventEmitter {
895
920
  if hasBool(dictionary, "enableBatteryLevel") {
896
921
  options.enableBatteryLevel = boolValue(dictionary, "enableBatteryLevel", defaultValue: options.enableBatteryLevel)
897
922
  }
923
+ if hasBool(dictionary, "enableOpenFileHandleTracking") {
924
+ options.enableOpenFileHandleTracking = boolValue(
925
+ dictionary,
926
+ "enableOpenFileHandleTracking",
927
+ defaultValue: options.enableOpenFileHandleTracking
928
+ )
929
+ }
898
930
  if let memory = dictionary?["defaultMemoryChannels"] as? NSDictionary {
899
931
  var channels: DefaultMemoryChannels = []
900
932
  if boolValue(memory, "managedHeap", defaultValue: boolValue(memory, "javaHeap", defaultValue: false)) {
@@ -942,9 +974,14 @@ final class AnsightReactNative: RCTEventEmitter {
942
974
  "captureGpuBackedSurfaces",
943
975
  defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureGpuBackedSurfaces
944
976
  ),
945
- mode: stringValue(jpeg, "mode") == "screenshotAndVisualTree"
946
- ? .screenshotAndVisualTree
947
- : .screenshotOnly
977
+ mode: AnsightSessionJpegCaptureMode(
978
+ rawValue: stringValue(jpeg, "mode") ?? ""
979
+ ) ?? .screenshotOnly,
980
+ captureKeyboardPresence: boolValue(
981
+ jpeg,
982
+ "captureKeyboardPresence",
983
+ defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureKeyboardPresence
984
+ )
948
985
  )
949
986
  }
950
987
  }
@@ -960,6 +997,21 @@ final class AnsightReactNative: RCTEventEmitter {
960
997
  )
961
998
  }
962
999
  }
1000
+ if let raw = dictionary?["crashCapture"] {
1001
+ if (raw as? Bool) == false {
1002
+ options.crashCapture.enabled = false
1003
+ } else if let crash = raw as? NSDictionary {
1004
+ options.crashCapture = AnsightCrashCaptureOptions(
1005
+ enabled: boolValue(crash, "enabled", defaultValue: true),
1006
+ studioHandoffEnabled: boolValue(crash, "studioHandoffEnabled", defaultValue: true),
1007
+ offlineCaptureAttachmentEnabled: boolValue(crash, "offlineCaptureAttachmentEnabled", defaultValue: true),
1008
+ maximumPendingReports: intValue(crash, "maximumPendingReports", defaultValue: 8),
1009
+ retentionDays: intValue(crash, "retentionDays", defaultValue: 7),
1010
+ maximumBreadcrumbs: intValue(crash, "maximumBreadcrumbs", defaultValue: 64),
1011
+ maximumTraceBytes: intValue(crash, "maximumTraceBytes", defaultValue: 1_048_576)
1012
+ )
1013
+ }
1014
+ }
963
1015
  if let lifecycle = dictionary?["lifecycleCapture"] as? NSDictionary {
964
1016
  options.lifecycleCapture = AnsightLifecycleCaptureOptions(
965
1017
  enabled: boolValue(lifecycle, "enabled", defaultValue: options.lifecycleCapture.enabled),
@@ -1271,9 +1323,14 @@ final class AnsightReactNative: RCTEventEmitter {
1271
1323
  "captureGpuBackedSurfaces",
1272
1324
  defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureGpuBackedSurfaces
1273
1325
  ),
1274
- mode: stringValue(dictionary, "mode") == "screenshotAndVisualTree"
1275
- ? .screenshotAndVisualTree
1276
- : .screenshotOnly
1326
+ mode: AnsightSessionJpegCaptureMode(
1327
+ rawValue: stringValue(dictionary, "mode") ?? ""
1328
+ ) ?? .screenshotOnly,
1329
+ captureKeyboardPresence: boolValue(
1330
+ dictionary,
1331
+ "captureKeyboardPresence",
1332
+ defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureKeyboardPresence
1333
+ )
1277
1334
  )
1278
1335
  }
1279
1336
 
@@ -1283,6 +1340,8 @@ final class AnsightReactNative: RCTEventEmitter {
1283
1340
  "retentionPeriodSeconds": options.retentionPeriodSeconds,
1284
1341
  "enableFramesPerSecond": options.enableFramesPerSecond,
1285
1342
  "enableBatteryLevel": options.enableBatteryLevel,
1343
+ "enableOpenFileHandleTracking": options.enableOpenFileHandleTracking,
1344
+ "enableJniReferenceCountTracking": false,
1286
1345
  "defaultMemoryChannels": [
1287
1346
  "managedHeap": options.defaultMemoryChannels.contains(.managedHeap),
1288
1347
  "javaHeap": options.defaultMemoryChannels.contains(.managedHeap),
@@ -1322,6 +1381,7 @@ final class AnsightReactNative: RCTEventEmitter {
1322
1381
  "quality": capture.quality,
1323
1382
  "maxWidth": capture.maxWidth as Any,
1324
1383
  "captureGpuBackedSurfaces": capture.captureGpuBackedSurfaces,
1384
+ "captureKeyboardPresence": capture.captureKeyboardPresence,
1325
1385
  "mode": capture.mode.rawValue,
1326
1386
  ]
1327
1387
  } else {
@@ -33,6 +33,10 @@ RCT_EXTERN_METHOD(recordEvent:(NSDictionary *)input
33
33
  resolver:(RCTPromiseResolveBlock)resolve
34
34
  rejecter:(RCTPromiseRejectBlock)reject)
35
35
 
36
+ RCT_EXTERN_METHOD(recordCrashCandidate:(NSDictionary *)input
37
+ resolver:(RCTPromiseResolveBlock)resolve
38
+ rejecter:(RCTPromiseRejectBlock)reject)
39
+
36
40
  RCT_EXTERN_METHOD(screenViewed:(NSString *)name
37
41
  details:(NSDictionary *)details
38
42
  resolver:(RCTPromiseResolveBlock)resolve
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansight/react-native",
3
- "version": "1.3.0-preview.3",
3
+ "version": "1.3.0-preview.8",
4
4
  "description": "React Native bridge for the Ansight mobile SDK.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",