@ansight/react-native 1.2.0-preview.2 → 1.3.0-preview.10
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 +15 -3
- package/android/build.gradle +2 -2
- package/android/src/main/kotlin/ai/ansight/reactnative/AnsightReactNativeModule.kt +76 -0
- package/index.d.ts +35 -1
- package/index.js +174 -28
- package/ios/AnsightReactNative.swift +67 -0
- package/ios/AnsightReactNativeModule.m +4 -0
- package/package.json +1 -1
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.
|
|
81
|
-
- Maven: `ai.ansight:ansight-android:1.
|
|
80
|
+
- CocoaPods: `Ansight`, `AnsightObjC` version `1.3.0-preview.10`
|
|
81
|
+
- Maven: `ai.ansight:ansight-android:1.3.0-preview.10`
|
|
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 only on touch down and touch up.
|
|
204
|
+
Move and cancel events do not trigger capture. Native touch capture and
|
|
205
|
+
visual-tree tools/providers must 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.
|
package/android/build.gradle
CHANGED
|
@@ -67,6 +67,6 @@ repositories {
|
|
|
67
67
|
dependencies {
|
|
68
68
|
implementation("com.facebook.react:react-android")
|
|
69
69
|
def ansightAndroidVersion =
|
|
70
|
-
findProperty("ansightAndroidVersion") ?: "1.
|
|
71
|
-
implementation("ai.ansight:ansight-android:1.
|
|
70
|
+
findProperty("ansightAndroidVersion") ?: "1.3.0-preview.10"
|
|
71
|
+
implementation("ai.ansight:ansight-android:1.3.0-preview.10")
|
|
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
|
|
@@ -16,6 +17,7 @@ import ai.ansight.runtime.AnsightOptionsBuilder
|
|
|
16
17
|
import ai.ansight.runtime.AnsightRuntime
|
|
17
18
|
import ai.ansight.runtime.AnsightSecureStorageOptions
|
|
18
19
|
import ai.ansight.runtime.AnsightSessionJpegCaptureOptions
|
|
20
|
+
import ai.ansight.runtime.AnsightSessionJpegCaptureMode
|
|
19
21
|
import ai.ansight.runtime.AnsightToolGuard
|
|
20
22
|
import ai.ansight.runtime.AnsightTouchCaptureOptions
|
|
21
23
|
import ai.ansight.runtime.AppLifecycleState
|
|
@@ -224,6 +226,21 @@ class AnsightReactNativeModule(
|
|
|
224
226
|
}.resolve(promise)
|
|
225
227
|
}
|
|
226
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
|
+
|
|
227
244
|
@ReactMethod
|
|
228
245
|
fun screenViewed(name: String, details: ReadableMap?, promise: Promise) {
|
|
229
246
|
runCatching {
|
|
@@ -692,6 +709,22 @@ class AnsightReactNativeModule(
|
|
|
692
709
|
if (map.hasBoolean("enableBatteryLevel")) {
|
|
693
710
|
options = options.copy(enableBatteryLevel = map.booleanValue("enableBatteryLevel", options.enableBatteryLevel))
|
|
694
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
|
+
}
|
|
695
728
|
if (map.hasMap("defaultMemoryChannels")) {
|
|
696
729
|
val memory = map.getMapOrNull("defaultMemoryChannels")
|
|
697
730
|
val managedHeap = if (memory.hasBoolean("managedHeap")) {
|
|
@@ -749,6 +782,11 @@ class AnsightReactNativeModule(
|
|
|
749
782
|
"captureGpuBackedSurfaces",
|
|
750
783
|
AnsightSessionJpegCaptureOptions.DefaultCaptureGpuBackedSurfaces,
|
|
751
784
|
),
|
|
785
|
+
mode = sessionJpegCaptureMode(jpeg.stringValue("mode")),
|
|
786
|
+
captureKeyboardPresence = jpeg.booleanValue(
|
|
787
|
+
"captureKeyboardPresence",
|
|
788
|
+
AnsightSessionJpegCaptureOptions.DefaultCaptureKeyboardPresence,
|
|
789
|
+
),
|
|
752
790
|
)
|
|
753
791
|
},
|
|
754
792
|
)
|
|
@@ -766,6 +804,24 @@ class AnsightReactNativeModule(
|
|
|
766
804
|
},
|
|
767
805
|
)
|
|
768
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
|
+
}
|
|
769
825
|
if (map.hasString("toolGuard")) {
|
|
770
826
|
options = options.copy(toolGuard = toolGuard(map.stringValue("toolGuard")))
|
|
771
827
|
}
|
|
@@ -928,9 +984,21 @@ class AnsightReactNativeModule(
|
|
|
928
984
|
"captureGpuBackedSurfaces",
|
|
929
985
|
AnsightSessionJpegCaptureOptions.DefaultCaptureGpuBackedSurfaces,
|
|
930
986
|
),
|
|
987
|
+
mode = sessionJpegCaptureMode(map.stringValue("mode")),
|
|
988
|
+
captureKeyboardPresence = map.booleanValue(
|
|
989
|
+
"captureKeyboardPresence",
|
|
990
|
+
AnsightSessionJpegCaptureOptions.DefaultCaptureKeyboardPresence,
|
|
991
|
+
),
|
|
931
992
|
)
|
|
932
993
|
}
|
|
933
994
|
|
|
995
|
+
private fun sessionJpegCaptureMode(value: String?): AnsightSessionJpegCaptureMode =
|
|
996
|
+
when (value) {
|
|
997
|
+
"screenshotAndVisualTree" -> AnsightSessionJpegCaptureMode.ScreenshotAndVisualTree
|
|
998
|
+
"screenshotWithVisualTreeOnTouch" -> AnsightSessionJpegCaptureMode.ScreenshotWithVisualTreeOnTouch
|
|
999
|
+
else -> AnsightSessionJpegCaptureMode.ScreenshotOnly
|
|
1000
|
+
}
|
|
1001
|
+
|
|
934
1002
|
private fun toolDefinition(map: ReadableMap): ToolDefinition =
|
|
935
1003
|
ToolDefinition(
|
|
936
1004
|
id = map.stringValue("id") ?: "",
|
|
@@ -1101,6 +1169,8 @@ class AnsightReactNativeModule(
|
|
|
1101
1169
|
putInt("retentionPeriodSeconds", options.retentionPeriodSeconds)
|
|
1102
1170
|
putBoolean("enableFramesPerSecond", options.enableFramesPerSecond)
|
|
1103
1171
|
putBoolean("enableBatteryLevel", options.enableBatteryLevel)
|
|
1172
|
+
putBoolean("enableOpenFileHandleTracking", options.enableOpenFileHandleTracking)
|
|
1173
|
+
putBoolean("enableJniReferenceCountTracking", options.enableJniReferenceCountTracking)
|
|
1104
1174
|
putMap("defaultMemoryChannels", mapOf(
|
|
1105
1175
|
"managedHeap" to options.defaultMemoryChannels.javaHeap,
|
|
1106
1176
|
"javaHeap" to options.defaultMemoryChannels.javaHeap,
|
|
@@ -1117,6 +1187,12 @@ class AnsightReactNativeModule(
|
|
|
1117
1187
|
"quality" to capture.quality,
|
|
1118
1188
|
"maxWidth" to capture.maxWidth,
|
|
1119
1189
|
"captureGpuBackedSurfaces" to capture.captureGpuBackedSurfaces,
|
|
1190
|
+
"captureKeyboardPresence" to capture.captureKeyboardPresence,
|
|
1191
|
+
"mode" to when (capture.mode) {
|
|
1192
|
+
AnsightSessionJpegCaptureMode.ScreenshotAndVisualTree -> "screenshotAndVisualTree"
|
|
1193
|
+
AnsightSessionJpegCaptureMode.ScreenshotWithVisualTreeOnTouch -> "screenshotWithVisualTreeOnTouch"
|
|
1194
|
+
else -> "screenshotOnly"
|
|
1195
|
+
},
|
|
1120
1196
|
).toWritableMap())
|
|
1121
1197
|
} ?: putNull("sessionJpegCapture")
|
|
1122
1198
|
options.touchCapture?.let { touch ->
|
package/index.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface AnsightSessionJpegCaptureOptions {
|
|
|
37
37
|
quality?: number;
|
|
38
38
|
maxWidth?: number;
|
|
39
39
|
captureGpuBackedSurfaces?: boolean;
|
|
40
|
+
captureKeyboardPresence?: boolean;
|
|
41
|
+
mode?: "screenshotOnly" | "screenshotAndVisualTree" | "screenshotWithVisualTreeOnTouch";
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export interface AnsightTouchCaptureOptions {
|
|
@@ -109,6 +111,8 @@ export interface AnsightOptions {
|
|
|
109
111
|
retentionPeriodSeconds?: number;
|
|
110
112
|
enableFramesPerSecond?: boolean;
|
|
111
113
|
enableBatteryLevel?: boolean;
|
|
114
|
+
enableOpenFileHandleTracking?: boolean;
|
|
115
|
+
enableJniReferenceCountTracking?: boolean;
|
|
112
116
|
defaultMemoryChannels?: {
|
|
113
117
|
managedHeap?: boolean;
|
|
114
118
|
physicalFootprint?: boolean;
|
|
@@ -120,6 +124,7 @@ export interface AnsightOptions {
|
|
|
120
124
|
reactNativeMemory?: false | AnsightReactNativeMemoryOptions;
|
|
121
125
|
sessionJpegCapture?: false | AnsightSessionJpegCaptureOptions;
|
|
122
126
|
touchCapture?: false | AnsightTouchCaptureOptions;
|
|
127
|
+
crashCapture?: false | AnsightCrashCaptureOptions;
|
|
123
128
|
lifecycleCapture?: {
|
|
124
129
|
enabled?: boolean;
|
|
125
130
|
captureAppLifecycle?: boolean;
|
|
@@ -152,6 +157,25 @@ export interface AnsightOptions {
|
|
|
152
157
|
lifecycle?: boolean;
|
|
153
158
|
}
|
|
154
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
|
+
|
|
155
179
|
export interface AnsightOperationResult {
|
|
156
180
|
success: boolean;
|
|
157
181
|
message: string;
|
|
@@ -250,6 +274,10 @@ export class AnsightOptionsBuilder {
|
|
|
250
274
|
withoutFramesPerSecond(): this;
|
|
251
275
|
withBatteryLevel(): this;
|
|
252
276
|
withoutBatteryLevel(): this;
|
|
277
|
+
withOpenFileHandleTracking(): this;
|
|
278
|
+
withoutOpenFileHandleTracking(): this;
|
|
279
|
+
withJniReferenceCountTracking(): this;
|
|
280
|
+
withoutJniReferenceCountTracking(): this;
|
|
253
281
|
withRetentionPeriodSeconds(retentionPeriodSeconds: number): this;
|
|
254
282
|
withAdditionalChannels(additionalChannels: AnsightChannel[]): this;
|
|
255
283
|
addAdditionalChannel(additionalChannel: AnsightChannel): this;
|
|
@@ -262,11 +290,15 @@ export class AnsightOptionsBuilder {
|
|
|
262
290
|
intervalMilliseconds: number,
|
|
263
291
|
quality?: number,
|
|
264
292
|
maxWidth?: number | null,
|
|
265
|
-
captureGpuBackedSurfaces?: boolean
|
|
293
|
+
captureGpuBackedSurfaces?: boolean,
|
|
294
|
+
mode?: "screenshotOnly" | "screenshotAndVisualTree" | "screenshotWithVisualTreeOnTouch",
|
|
295
|
+
captureKeyboardPresence?: boolean
|
|
266
296
|
): this;
|
|
267
297
|
withoutSessionJpegCapture(): this;
|
|
268
298
|
withTouchCapture(touchCapture?: AnsightTouchCaptureOptions): this;
|
|
269
299
|
withoutTouchCapture(): this;
|
|
300
|
+
withCrashCapture(crashCapture?: AnsightCrashCaptureOptions): this;
|
|
301
|
+
withoutCrashCapture(): this;
|
|
270
302
|
withLifecycleCapture(lifecycleCapture?: NonNullable<AnsightOptions["lifecycleCapture"]>): this;
|
|
271
303
|
withToolGuard(toolGuard: NonNullable<AnsightOptions["toolGuard"]>): this;
|
|
272
304
|
withToolsDisabled(): this;
|
|
@@ -510,6 +542,7 @@ export function metric(value: number, channel?: number): Promise<AnsightDebugSna
|
|
|
510
542
|
export function recordMetric(value: number, channel?: number): Promise<AnsightDebugSnapshot>;
|
|
511
543
|
export function event(input: string | { label: string; type?: string; details?: string; channel?: number }): Promise<AnsightDebugSnapshot>;
|
|
512
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 }>;
|
|
513
546
|
export function screenViewed(name: string, details?: Record<string, string>): Promise<AnsightDebugSnapshot>;
|
|
514
547
|
export function trackRoute(name: string, details?: Record<string, string>): Promise<AnsightDebugSnapshot>;
|
|
515
548
|
export function setAppLifecycleState(state: AnsightLifecycleState): Promise<AnsightDebugSnapshot>;
|
|
@@ -583,6 +616,7 @@ declare const Ansight: {
|
|
|
583
616
|
recordMetric: typeof recordMetric;
|
|
584
617
|
event: typeof event;
|
|
585
618
|
recordEvent: typeof recordEvent;
|
|
619
|
+
recordCrashCandidate: typeof recordCrashCandidate;
|
|
586
620
|
screenViewed: typeof screenViewed;
|
|
587
621
|
trackRoute: typeof trackRoute;
|
|
588
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;
|
|
@@ -268,6 +293,8 @@ class AnsightOptionsBuilder {
|
|
|
268
293
|
quality: arguments.length > 1 ? arguments[1] : 60,
|
|
269
294
|
maxWidth: arguments.length > 2 ? arguments[2] : 480,
|
|
270
295
|
captureGpuBackedSurfaces: arguments.length > 3 ? arguments[3] : true,
|
|
296
|
+
mode: arguments.length > 4 ? arguments[4] : "screenshotOnly",
|
|
297
|
+
captureKeyboardPresence: arguments.length > 5 ? arguments[5] : false,
|
|
271
298
|
};
|
|
272
299
|
return this;
|
|
273
300
|
}
|
|
@@ -275,6 +302,8 @@ class AnsightOptionsBuilder {
|
|
|
275
302
|
intervalMilliseconds: 2000,
|
|
276
303
|
quality: 60,
|
|
277
304
|
maxWidth: 480,
|
|
305
|
+
captureKeyboardPresence: false,
|
|
306
|
+
mode: "screenshotOnly",
|
|
278
307
|
...(optionsOrIntervalMilliseconds || {}),
|
|
279
308
|
};
|
|
280
309
|
return this;
|
|
@@ -295,6 +324,16 @@ class AnsightOptionsBuilder {
|
|
|
295
324
|
return this;
|
|
296
325
|
}
|
|
297
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
|
+
|
|
298
337
|
withLifecycleCapture(lifecycleCapture = {}) {
|
|
299
338
|
this._options.lifecycleCapture = { ...lifecycleCapture };
|
|
300
339
|
return this;
|
|
@@ -1389,7 +1428,7 @@ function summarizeProps(props) {
|
|
|
1389
1428
|
}
|
|
1390
1429
|
const keys = Object.keys(props).filter((key) => key !== "children");
|
|
1391
1430
|
const summary = { keys };
|
|
1392
|
-
["testID", "nativeID", "accessibilityLabel", "role"].forEach((key) => {
|
|
1431
|
+
["testID", "nativeID", "accessibilityLabel", "accessibilityRole", "role"].forEach((key) => {
|
|
1393
1432
|
if (props[key] != null && !isSensitiveKey(key)) {
|
|
1394
1433
|
summary[key] = String(props[key]);
|
|
1395
1434
|
}
|
|
@@ -1401,6 +1440,42 @@ function summarizeProps(props) {
|
|
|
1401
1440
|
return summary;
|
|
1402
1441
|
}
|
|
1403
1442
|
|
|
1443
|
+
function reactSemanticRole(type, props, fiberTag) {
|
|
1444
|
+
const declared = props && (props.accessibilityRole || props.role);
|
|
1445
|
+
if (declared) return String(declared).toLowerCase();
|
|
1446
|
+
if (fiberTag === 6 || /text/i.test(type)) return "text";
|
|
1447
|
+
if (/button|pressable|touchable/i.test(type)) return "button";
|
|
1448
|
+
if (/textinput/i.test(type)) return "textbox";
|
|
1449
|
+
if (/switch/i.test(type)) return "switch";
|
|
1450
|
+
if (/scrollview|flatlist|sectionlist/i.test(type)) return "scrollview";
|
|
1451
|
+
return "view";
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
function reactSupportedActions(type, props) {
|
|
1455
|
+
const actions = [];
|
|
1456
|
+
if (props && typeof props.onPress === "function") actions.push("tap");
|
|
1457
|
+
if (props && (typeof props.onChangeText === "function" || /textinput/i.test(type))) {
|
|
1458
|
+
actions.push("typeText", "focus");
|
|
1459
|
+
}
|
|
1460
|
+
if (/scrollview|flatlist|sectionlist/i.test(type) || (props && typeof props.onScroll === "function")) {
|
|
1461
|
+
actions.push("scroll", "swipe");
|
|
1462
|
+
}
|
|
1463
|
+
return actions;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function applyReactTargetability(node, fiber, props, type) {
|
|
1467
|
+
const style = StyleSheet && typeof StyleSheet.flatten === "function"
|
|
1468
|
+
? StyleSheet.flatten(props && props.style)
|
|
1469
|
+
: props && props.style;
|
|
1470
|
+
node.text = node.label || (node.visual && node.visual.text) || null;
|
|
1471
|
+
node.role = reactSemanticRole(type, props, fiber.tag);
|
|
1472
|
+
node.supportedActions = reactSupportedActions(type, props);
|
|
1473
|
+
node.visible = !(style && style.display === "none") && !(props && props.accessibilityElementsHidden);
|
|
1474
|
+
node.enabled = !(props && (props.disabled === true || props.accessibilityState && props.accessibilityState.disabled === true));
|
|
1475
|
+
node.focusable = !!(props && props.focusable) || node.supportedActions.includes("focus");
|
|
1476
|
+
node.interactable = node.visible && node.enabled && node.supportedActions.length > 0;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1404
1479
|
function reactColorToArgbHex(value) {
|
|
1405
1480
|
if (value == null) {
|
|
1406
1481
|
return undefined;
|
|
@@ -1451,6 +1526,28 @@ function createReactVisual(fiber, props, maxStringLength) {
|
|
|
1451
1526
|
return visual;
|
|
1452
1527
|
}
|
|
1453
1528
|
|
|
1529
|
+
function createReactZIndex(props) {
|
|
1530
|
+
const style = StyleSheet && typeof StyleSheet.flatten === "function"
|
|
1531
|
+
? StyleSheet.flatten(props && props.style)
|
|
1532
|
+
: props && props.style;
|
|
1533
|
+
const zIndex = style && style.zIndex != null ? Number(style.zIndex) : NaN;
|
|
1534
|
+
const elevation = style && style.elevation != null ? Number(style.elevation) : NaN;
|
|
1535
|
+
if (Number.isFinite(zIndex) && zIndex !== 0) return zIndex;
|
|
1536
|
+
if (Number.isFinite(elevation) && elevation !== 0) return elevation;
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
function registerReactType(context, typeName) {
|
|
1541
|
+
const normalizedTypeName = String(typeName || "UnknownComponent").trim() || "UnknownComponent";
|
|
1542
|
+
const existingTypeId = context.typeIdsByName.get(normalizedTypeName);
|
|
1543
|
+
if (existingTypeId != null) return existingTypeId;
|
|
1544
|
+
|
|
1545
|
+
const typeId = context.types.length;
|
|
1546
|
+
context.types.push(normalizedTypeName);
|
|
1547
|
+
context.typeIdsByName.set(normalizedTypeName, typeId);
|
|
1548
|
+
return typeId;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1454
1551
|
function nativeTagForFiber(fiber) {
|
|
1455
1552
|
const stateNode = fiber && fiber.stateNode;
|
|
1456
1553
|
if (!stateNode) {
|
|
@@ -1522,17 +1619,17 @@ function serializeFiber(fiber, context, depth) {
|
|
|
1522
1619
|
|
|
1523
1620
|
const props = fiber.memoizedProps || fiber.pendingProps || {};
|
|
1524
1621
|
const type = reactFiberTypeName(fiber);
|
|
1525
|
-
const kind = reactFiberKind(fiber.tag);
|
|
1526
1622
|
const node = {
|
|
1527
1623
|
id: reactFiberId(fiber),
|
|
1528
|
-
type,
|
|
1529
|
-
kind,
|
|
1624
|
+
typeId: registerReactType(context, type),
|
|
1530
1625
|
tag: fiber.tag,
|
|
1531
1626
|
key: fiber.key == null ? null : String(fiber.key),
|
|
1532
1627
|
depth,
|
|
1533
1628
|
visual: createReactVisual(fiber, props, context.maxStringLength),
|
|
1534
1629
|
children: [],
|
|
1535
1630
|
};
|
|
1631
|
+
const zIndex = createReactZIndex(props);
|
|
1632
|
+
if (zIndex != null) node.z = zIndex;
|
|
1536
1633
|
|
|
1537
1634
|
if (fiber._debugSource) {
|
|
1538
1635
|
node.source = {
|
|
@@ -1544,7 +1641,7 @@ function serializeFiber(fiber, context, depth) {
|
|
|
1544
1641
|
|
|
1545
1642
|
const owner = fiber._debugOwner && reactFiberTypeName(fiber._debugOwner);
|
|
1546
1643
|
if (owner) {
|
|
1547
|
-
node.
|
|
1644
|
+
node.ownerTypeId = registerReactType(context, owner);
|
|
1548
1645
|
}
|
|
1549
1646
|
|
|
1550
1647
|
const nativeTag = nativeTagForFiber(fiber);
|
|
@@ -1575,6 +1672,8 @@ function serializeFiber(fiber, context, depth) {
|
|
|
1575
1672
|
node.state = sanitizeValue(fiber.memoizedState, context);
|
|
1576
1673
|
}
|
|
1577
1674
|
|
|
1675
|
+
applyReactTargetability(node, fiber, props, type);
|
|
1676
|
+
|
|
1578
1677
|
if (depth < context.maxDepth) {
|
|
1579
1678
|
let child = fiber.child;
|
|
1580
1679
|
while (child) {
|
|
@@ -1598,16 +1697,18 @@ function isShadowTreeFiber(fiber) {
|
|
|
1598
1697
|
|
|
1599
1698
|
function createShadowTreeNode(fiber, context, depth) {
|
|
1600
1699
|
const props = fiber.memoizedProps || fiber.pendingProps || {};
|
|
1700
|
+
const type = reactFiberTypeName(fiber);
|
|
1601
1701
|
const node = {
|
|
1602
1702
|
id: reactFiberId(fiber),
|
|
1603
|
-
|
|
1604
|
-
kind: fiber.tag === 3 ? "root" : fiber.tag === 6 ? "text" : "host",
|
|
1703
|
+
typeId: registerReactType(context, type),
|
|
1605
1704
|
tag: fiber.tag,
|
|
1606
1705
|
key: fiber.key == null ? null : String(fiber.key),
|
|
1607
1706
|
depth,
|
|
1608
1707
|
visual: createReactVisual(fiber, props, context.maxStringLength),
|
|
1609
1708
|
children: [],
|
|
1610
1709
|
};
|
|
1710
|
+
const zIndex = createReactZIndex(props);
|
|
1711
|
+
if (zIndex != null) node.z = zIndex;
|
|
1611
1712
|
|
|
1612
1713
|
const nativeTag = nativeTagForFiber(fiber);
|
|
1613
1714
|
if (nativeTag != null) {
|
|
@@ -1633,6 +1734,8 @@ function createShadowTreeNode(fiber, context, depth) {
|
|
|
1633
1734
|
}
|
|
1634
1735
|
}
|
|
1635
1736
|
|
|
1737
|
+
applyReactTargetability(node, fiber, props, type);
|
|
1738
|
+
|
|
1636
1739
|
return node;
|
|
1637
1740
|
}
|
|
1638
1741
|
|
|
@@ -1689,19 +1792,21 @@ async function captureReactVisualTree(rawOptions = {}) {
|
|
|
1689
1792
|
maxStringLength: rawOptions.maxStringLength || 180,
|
|
1690
1793
|
maxValueDepth: rawOptions.maxValueDepth || 2,
|
|
1691
1794
|
nodesWithNativeTags: [],
|
|
1795
|
+
typeIdsByName: new Map(),
|
|
1796
|
+
types: [],
|
|
1692
1797
|
count: 0,
|
|
1693
1798
|
truncated: false,
|
|
1694
1799
|
visited: new Set(),
|
|
1695
1800
|
};
|
|
1696
1801
|
const roots = getReactRoots();
|
|
1697
|
-
const rootNodes = roots.roots.map((root
|
|
1802
|
+
const rootNodes = roots.roots.map((root) => {
|
|
1698
1803
|
const node = serializeFiber(root.fiber, context, 0);
|
|
1699
1804
|
if (node) {
|
|
1700
1805
|
node.rendererId = root.rendererId;
|
|
1701
|
-
node.rootIndex = index;
|
|
1702
1806
|
}
|
|
1703
1807
|
return node;
|
|
1704
1808
|
}).filter(Boolean);
|
|
1809
|
+
const rootTypeId = registerReactType(context, "ReactRoots");
|
|
1705
1810
|
|
|
1706
1811
|
if (includeBounds && context.nodesWithNativeTags.length > 0) {
|
|
1707
1812
|
await Promise.all(context.nodesWithNativeTags.slice(0, 300).map(async (node) => {
|
|
@@ -1713,6 +1818,7 @@ async function captureReactVisualTree(rawOptions = {}) {
|
|
|
1713
1818
|
}
|
|
1714
1819
|
|
|
1715
1820
|
return {
|
|
1821
|
+
format: "ansight.react.visual-tree.compact.v2",
|
|
1716
1822
|
platform: Platform.OS,
|
|
1717
1823
|
source: "react",
|
|
1718
1824
|
adapter: "react.fiber",
|
|
@@ -1722,11 +1828,11 @@ async function captureReactVisualTree(rawOptions = {}) {
|
|
|
1722
1828
|
renderers: roots.renderers,
|
|
1723
1829
|
root: {
|
|
1724
1830
|
id: "react:roots",
|
|
1725
|
-
|
|
1726
|
-
|
|
1831
|
+
typeId: rootTypeId,
|
|
1832
|
+
childCount: rootNodes.length,
|
|
1727
1833
|
children: rootNodes,
|
|
1728
1834
|
},
|
|
1729
|
-
|
|
1835
|
+
types: context.types,
|
|
1730
1836
|
nodeCount: context.count,
|
|
1731
1837
|
truncated: context.truncated,
|
|
1732
1838
|
unavailableReason: roots.hookAvailable ? undefined : "React DevTools global hook is not available in this runtime.",
|
|
@@ -1746,19 +1852,21 @@ async function captureReactShadowTree(rawOptions = {}) {
|
|
|
1746
1852
|
maxStringLength: rawOptions.maxStringLength || 180,
|
|
1747
1853
|
maxValueDepth: rawOptions.maxValueDepth || 2,
|
|
1748
1854
|
nodesWithNativeTags: [],
|
|
1855
|
+
typeIdsByName: new Map(),
|
|
1856
|
+
types: [],
|
|
1749
1857
|
count: 0,
|
|
1750
1858
|
truncated: false,
|
|
1751
1859
|
visited: new Set(),
|
|
1752
1860
|
};
|
|
1753
1861
|
const roots = getReactRoots();
|
|
1754
|
-
const rootNodes = roots.roots.flatMap((root
|
|
1862
|
+
const rootNodes = roots.roots.flatMap((root) => {
|
|
1755
1863
|
const nodes = serializeShadowFiber(root.fiber, context, 0);
|
|
1756
1864
|
nodes.forEach((node) => {
|
|
1757
1865
|
node.rendererId = root.rendererId;
|
|
1758
|
-
node.rootIndex = index;
|
|
1759
1866
|
});
|
|
1760
1867
|
return nodes;
|
|
1761
1868
|
});
|
|
1869
|
+
const rootTypeId = registerReactType(context, "ReactNativeShadowRoots");
|
|
1762
1870
|
|
|
1763
1871
|
if (includeBounds && context.nodesWithNativeTags.length > 0) {
|
|
1764
1872
|
await Promise.all(context.nodesWithNativeTags.slice(0, 300).map(async (node) => {
|
|
@@ -1770,6 +1878,7 @@ async function captureReactShadowTree(rawOptions = {}) {
|
|
|
1770
1878
|
}
|
|
1771
1879
|
|
|
1772
1880
|
return {
|
|
1881
|
+
format: "ansight.react.visual-tree.compact.v2",
|
|
1773
1882
|
platform: Platform.OS,
|
|
1774
1883
|
source: "react-native",
|
|
1775
1884
|
adapter: "react-native.host-fiber",
|
|
@@ -1779,11 +1888,11 @@ async function captureReactShadowTree(rawOptions = {}) {
|
|
|
1779
1888
|
renderers: roots.renderers,
|
|
1780
1889
|
root: {
|
|
1781
1890
|
id: "react:shadow-roots",
|
|
1782
|
-
|
|
1783
|
-
|
|
1891
|
+
typeId: rootTypeId,
|
|
1892
|
+
childCount: rootNodes.length,
|
|
1784
1893
|
children: rootNodes,
|
|
1785
1894
|
},
|
|
1786
|
-
|
|
1895
|
+
types: context.types,
|
|
1787
1896
|
nodeCount: context.count,
|
|
1788
1897
|
truncated: context.truncated,
|
|
1789
1898
|
unavailableReason: roots.hookAvailable ? undefined : "React DevTools global hook is not available in this runtime.",
|
|
@@ -1799,13 +1908,18 @@ function flattenReactTree(node, output = []) {
|
|
|
1799
1908
|
return output;
|
|
1800
1909
|
}
|
|
1801
1910
|
|
|
1802
|
-
function
|
|
1911
|
+
function createReactNodeSnapshot(node) {
|
|
1912
|
+
const snapshot = { ...node };
|
|
1913
|
+
delete snapshot.children;
|
|
1914
|
+
return snapshot;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
function nodeSearchText(node, types) {
|
|
1803
1918
|
return [
|
|
1804
1919
|
node.id,
|
|
1805
|
-
node.
|
|
1806
|
-
node.kind,
|
|
1920
|
+
types[node.typeId],
|
|
1807
1921
|
node.label,
|
|
1808
|
-
node.
|
|
1922
|
+
types[node.ownerTypeId],
|
|
1809
1923
|
node.propsSummary && node.propsSummary.testID,
|
|
1810
1924
|
node.propsSummary && node.propsSummary.nativeID,
|
|
1811
1925
|
node.propsSummary && node.propsSummary.accessibilityLabel,
|
|
@@ -1813,14 +1927,14 @@ function nodeSearchText(node) {
|
|
|
1813
1927
|
].filter(Boolean).join(" ").toLowerCase();
|
|
1814
1928
|
}
|
|
1815
1929
|
|
|
1816
|
-
function matchesReactNode(node, args) {
|
|
1930
|
+
function matchesReactNode(node, args, types) {
|
|
1817
1931
|
const query = args.query ? String(args.query).toLowerCase() : null;
|
|
1818
1932
|
const type = args.type ? String(args.type).toLowerCase() : null;
|
|
1819
1933
|
const testID = args.testID ? String(args.testID).toLowerCase() : null;
|
|
1820
1934
|
const text = args.text ? String(args.text).toLowerCase() : null;
|
|
1821
|
-
const searchText = nodeSearchText(node);
|
|
1935
|
+
const searchText = nodeSearchText(node, types);
|
|
1822
1936
|
return (!query || searchText.includes(query)) &&
|
|
1823
|
-
(!type || String(node.
|
|
1937
|
+
(!type || String(types[node.typeId] || "").toLowerCase().includes(type)) &&
|
|
1824
1938
|
(!testID || String((node.propsSummary && node.propsSummary.testID) || "").toLowerCase() === testID) &&
|
|
1825
1939
|
(!text || searchText.includes(text));
|
|
1826
1940
|
}
|
|
@@ -2041,12 +2155,14 @@ function installReactTools(options = {}) {
|
|
|
2041
2155
|
});
|
|
2042
2156
|
const maxResults = parseInteger(args.maxResults, 50, 1, 500);
|
|
2043
2157
|
const matches = flattenReactTree(tree.root)
|
|
2044
|
-
.filter((node) => node.id !== "react:roots" && matchesReactNode(node, args))
|
|
2045
|
-
.slice(0, maxResults)
|
|
2158
|
+
.filter((node) => node.id !== "react:roots" && matchesReactNode(node, args, tree.types))
|
|
2159
|
+
.slice(0, maxResults)
|
|
2160
|
+
.map(createReactNodeSnapshot);
|
|
2046
2161
|
return {
|
|
2047
2162
|
success: tree.hookAvailable,
|
|
2048
2163
|
message: `Found ${matches.length} React component(s).`,
|
|
2049
2164
|
result: {
|
|
2165
|
+
types: tree.types,
|
|
2050
2166
|
matches,
|
|
2051
2167
|
count: matches.length,
|
|
2052
2168
|
truncated: matches.length === maxResults,
|
|
@@ -2058,7 +2174,11 @@ function installReactTools(options = {}) {
|
|
|
2058
2174
|
const tree = await captureReactVisualTree(reactToolOptions(options, args));
|
|
2059
2175
|
const node = flattenReactTree(tree.root).find((candidate) => candidate.id === args.nodeId);
|
|
2060
2176
|
return node
|
|
2061
|
-
? {
|
|
2177
|
+
? {
|
|
2178
|
+
success: true,
|
|
2179
|
+
message: "React component captured.",
|
|
2180
|
+
result: { types: tree.types, node: createReactNodeSnapshot(node) },
|
|
2181
|
+
}
|
|
2062
2182
|
: { success: false, message: `React component '${args.nodeId}' was not found.`, errorCode: "react_component_not_found" };
|
|
2063
2183
|
}
|
|
2064
2184
|
|
|
@@ -2099,7 +2219,7 @@ function installReactTools(options = {}) {
|
|
|
2099
2219
|
result: {
|
|
2100
2220
|
nodeId: args.nodeId,
|
|
2101
2221
|
prop,
|
|
2102
|
-
type: node.
|
|
2222
|
+
type: tree.types[node.typeId],
|
|
2103
2223
|
},
|
|
2104
2224
|
};
|
|
2105
2225
|
}
|
|
@@ -2135,6 +2255,14 @@ function installErrorHandlers(options = {}) {
|
|
|
2135
2255
|
|
|
2136
2256
|
if (global.ErrorUtils && global.ErrorUtils.setGlobalHandler) {
|
|
2137
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(() => {});
|
|
2138
2266
|
nativeModule.recordEvent({
|
|
2139
2267
|
label: error && error.message ? error.message : "Unhandled JavaScript error",
|
|
2140
2268
|
type: "Exception",
|
|
@@ -2157,6 +2285,13 @@ function installErrorHandlers(options = {}) {
|
|
|
2157
2285
|
global.__ansightUnhandledRejectionTrackingInstalled = true;
|
|
2158
2286
|
global.addEventListener("unhandledrejection", (event) => {
|
|
2159
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(() => {});
|
|
2160
2295
|
nativeModule.recordEvent({
|
|
2161
2296
|
label: reason && reason.message ? reason.message : "Unhandled JavaScript promise rejection",
|
|
2162
2297
|
type: "Exception",
|
|
@@ -2251,6 +2386,16 @@ function recordEvent(input) {
|
|
|
2251
2386
|
return nativeModule.recordEvent(input || {});
|
|
2252
2387
|
}
|
|
2253
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
|
+
|
|
2254
2399
|
function screenViewed(name, details) {
|
|
2255
2400
|
return nativeModule.screenViewed(name, details || {});
|
|
2256
2401
|
}
|
|
@@ -2293,6 +2438,7 @@ const Ansight = {
|
|
|
2293
2438
|
recordMetric: (value, channel = 255) => nativeModule.recordMetric(value, channel),
|
|
2294
2439
|
event: recordEvent,
|
|
2295
2440
|
recordEvent,
|
|
2441
|
+
recordCrashCandidate,
|
|
2296
2442
|
screenViewed,
|
|
2297
2443
|
trackRoute,
|
|
2298
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)) {
|
|
@@ -941,6 +973,14 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
941
973
|
jpeg,
|
|
942
974
|
"captureGpuBackedSurfaces",
|
|
943
975
|
defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureGpuBackedSurfaces
|
|
976
|
+
),
|
|
977
|
+
mode: AnsightSessionJpegCaptureMode(
|
|
978
|
+
rawValue: stringValue(jpeg, "mode") ?? ""
|
|
979
|
+
) ?? .screenshotOnly,
|
|
980
|
+
captureKeyboardPresence: boolValue(
|
|
981
|
+
jpeg,
|
|
982
|
+
"captureKeyboardPresence",
|
|
983
|
+
defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureKeyboardPresence
|
|
944
984
|
)
|
|
945
985
|
)
|
|
946
986
|
}
|
|
@@ -957,6 +997,21 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
957
997
|
)
|
|
958
998
|
}
|
|
959
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
|
+
}
|
|
960
1015
|
if let lifecycle = dictionary?["lifecycleCapture"] as? NSDictionary {
|
|
961
1016
|
options.lifecycleCapture = AnsightLifecycleCaptureOptions(
|
|
962
1017
|
enabled: boolValue(lifecycle, "enabled", defaultValue: options.lifecycleCapture.enabled),
|
|
@@ -1267,6 +1322,14 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
1267
1322
|
dictionary,
|
|
1268
1323
|
"captureGpuBackedSurfaces",
|
|
1269
1324
|
defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureGpuBackedSurfaces
|
|
1325
|
+
),
|
|
1326
|
+
mode: AnsightSessionJpegCaptureMode(
|
|
1327
|
+
rawValue: stringValue(dictionary, "mode") ?? ""
|
|
1328
|
+
) ?? .screenshotOnly,
|
|
1329
|
+
captureKeyboardPresence: boolValue(
|
|
1330
|
+
dictionary,
|
|
1331
|
+
"captureKeyboardPresence",
|
|
1332
|
+
defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureKeyboardPresence
|
|
1270
1333
|
)
|
|
1271
1334
|
)
|
|
1272
1335
|
}
|
|
@@ -1277,6 +1340,8 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
1277
1340
|
"retentionPeriodSeconds": options.retentionPeriodSeconds,
|
|
1278
1341
|
"enableFramesPerSecond": options.enableFramesPerSecond,
|
|
1279
1342
|
"enableBatteryLevel": options.enableBatteryLevel,
|
|
1343
|
+
"enableOpenFileHandleTracking": options.enableOpenFileHandleTracking,
|
|
1344
|
+
"enableJniReferenceCountTracking": false,
|
|
1280
1345
|
"defaultMemoryChannels": [
|
|
1281
1346
|
"managedHeap": options.defaultMemoryChannels.contains(.managedHeap),
|
|
1282
1347
|
"javaHeap": options.defaultMemoryChannels.contains(.managedHeap),
|
|
@@ -1316,6 +1381,8 @@ final class AnsightReactNative: RCTEventEmitter {
|
|
|
1316
1381
|
"quality": capture.quality,
|
|
1317
1382
|
"maxWidth": capture.maxWidth as Any,
|
|
1318
1383
|
"captureGpuBackedSurfaces": capture.captureGpuBackedSurfaces,
|
|
1384
|
+
"captureKeyboardPresence": capture.captureKeyboardPresence,
|
|
1385
|
+
"mode": capture.mode.rawValue,
|
|
1319
1386
|
]
|
|
1320
1387
|
} else {
|
|
1321
1388
|
dictionary["sessionJpegCapture"] = NSNull()
|
|
@@ -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
|