@ansight/react-native 1.0.2-preview.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.
- package/AnsightReactNative.podspec +20 -0
- package/LICENSE +200 -0
- package/README.md +436 -0
- package/android/build.gradle +70 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/CMakeLists.txt +16 -0
- package/android/src/main/cpp/ansight_react_native_memory.cpp +57 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/AnsightReactNativeModule.kt +1353 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/AnsightReactNativePackage.kt +14 -0
- package/android/src/main/kotlin/ai/ansight/reactnative/ReactNativeMemoryProfiler.kt +167 -0
- package/index.d.ts +633 -0
- package/index.js +2303 -0
- package/ios/AnsightReactNative.swift +1635 -0
- package/ios/AnsightReactNativeMemorySampler.mm +130 -0
- package/ios/AnsightReactNativeModule.m +167 -0
- package/package.json +50 -0
- package/react-native.config.js +12 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,1353 @@
|
|
|
1
|
+
package ai.ansight.reactnative
|
|
2
|
+
|
|
3
|
+
import ai.ansight.Ansight
|
|
4
|
+
import ai.ansight.runtime.AnsightDeveloperMode
|
|
5
|
+
import ai.ansight.runtime.AndroidToolExecutionContext
|
|
6
|
+
import ai.ansight.runtime.AndroidToolResult
|
|
7
|
+
import ai.ansight.runtime.AnsightChannel
|
|
8
|
+
import ai.ansight.runtime.AnsightChannels
|
|
9
|
+
import ai.ansight.runtime.AnsightHostAutoProbeOptions
|
|
10
|
+
import ai.ansight.runtime.AnsightHostConnectionOptions
|
|
11
|
+
import ai.ansight.runtime.AnsightLogCallback
|
|
12
|
+
import ai.ansight.runtime.AnsightLogLevel
|
|
13
|
+
import ai.ansight.runtime.AnsightLogger
|
|
14
|
+
import ai.ansight.runtime.AnsightOptions
|
|
15
|
+
import ai.ansight.runtime.AnsightOptionsBuilder
|
|
16
|
+
import ai.ansight.runtime.AnsightRuntime
|
|
17
|
+
import ai.ansight.runtime.AnsightSecureStorageOptions
|
|
18
|
+
import ai.ansight.runtime.AnsightSessionJpegCaptureOptions
|
|
19
|
+
import ai.ansight.runtime.AnsightToolGuard
|
|
20
|
+
import ai.ansight.runtime.AnsightTouchCaptureOptions
|
|
21
|
+
import ai.ansight.runtime.AppLifecycleState
|
|
22
|
+
import ai.ansight.runtime.DefaultMemoryChannels
|
|
23
|
+
import ai.ansight.runtime.FunctionAndroidTool
|
|
24
|
+
import ai.ansight.runtime.HostConnectionRequest
|
|
25
|
+
import ai.ansight.runtime.HostConnectionRequestKind
|
|
26
|
+
import ai.ansight.runtime.HostConnectionCapabilities
|
|
27
|
+
import ai.ansight.runtime.HostConnectionResult
|
|
28
|
+
import ai.ansight.runtime.HostConnectionStatus
|
|
29
|
+
import ai.ansight.runtime.OperationResult
|
|
30
|
+
import ai.ansight.runtime.OpenSessionResult
|
|
31
|
+
import ai.ansight.runtime.PairingOpenOptions
|
|
32
|
+
import ai.ansight.runtime.PairingFileTransferWireProtocol
|
|
33
|
+
import ai.ansight.runtime.RecordedEvent
|
|
34
|
+
import ai.ansight.runtime.RecordedMetric
|
|
35
|
+
import ai.ansight.runtime.ToolDefinition
|
|
36
|
+
import ai.ansight.runtime.ToolSchema
|
|
37
|
+
import ai.ansight.runtime.ToolScope
|
|
38
|
+
import ai.ansight.runtime.ToolSecurity
|
|
39
|
+
import ai.ansight.runtime.ToolSecurityLevel
|
|
40
|
+
import ai.ansight.runtime.sendBinaryTransfer
|
|
41
|
+
import ai.ansight.tools.database.AndroidDatabaseRoot
|
|
42
|
+
import ai.ansight.tools.database.AndroidDatabaseToolsOptions
|
|
43
|
+
import ai.ansight.tools.database.withDatabaseTools
|
|
44
|
+
import ai.ansight.tools.filesystem.AndroidFileSystemRoot
|
|
45
|
+
import ai.ansight.tools.filesystem.AndroidFileSystemToolsOptions
|
|
46
|
+
import ai.ansight.tools.filesystem.withFileSystemTools
|
|
47
|
+
import ai.ansight.tools.preferences.AndroidPreferencesToolsOptions
|
|
48
|
+
import ai.ansight.tools.preferences.withPreferencesTools
|
|
49
|
+
import ai.ansight.tools.reflection.AndroidReflectionToolsOptions
|
|
50
|
+
import ai.ansight.tools.reflection.withReflectionTools
|
|
51
|
+
import ai.ansight.tools.securestorage.withSecureStorageTools
|
|
52
|
+
import ai.ansight.tools.visualtree.withVisualTreeTools
|
|
53
|
+
import android.app.Activity
|
|
54
|
+
import android.app.Application
|
|
55
|
+
import android.util.Base64
|
|
56
|
+
import com.facebook.react.bridge.Arguments
|
|
57
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
58
|
+
import com.facebook.react.bridge.Promise
|
|
59
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
60
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
61
|
+
import com.facebook.react.bridge.ReactMethod
|
|
62
|
+
import com.facebook.react.bridge.ReadableArray
|
|
63
|
+
import com.facebook.react.bridge.ReadableMap
|
|
64
|
+
import com.facebook.react.bridge.ReadableType
|
|
65
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
66
|
+
import com.facebook.react.bridge.WritableArray
|
|
67
|
+
import com.facebook.react.bridge.WritableMap
|
|
68
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
69
|
+
import org.json.JSONArray
|
|
70
|
+
import org.json.JSONObject
|
|
71
|
+
import java.util.UUID
|
|
72
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
73
|
+
import java.util.concurrent.CountDownLatch
|
|
74
|
+
import java.util.concurrent.Executors
|
|
75
|
+
import java.util.concurrent.TimeUnit
|
|
76
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
77
|
+
import java.util.Locale
|
|
78
|
+
|
|
79
|
+
class AnsightReactNativeModule(
|
|
80
|
+
private val reactContext: ReactApplicationContext,
|
|
81
|
+
) : ReactContextBaseJavaModule(reactContext), LifecycleEventListener {
|
|
82
|
+
private data class PendingToolCall(
|
|
83
|
+
val context: AndroidToolExecutionContext,
|
|
84
|
+
val latch: CountDownLatch = CountDownLatch(1),
|
|
85
|
+
@Volatile var result: AndroidToolResult? = null,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
private data class CustomToolRegistration(
|
|
89
|
+
val definition: ToolDefinition,
|
|
90
|
+
val timeoutMilliseconds: Long,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
private val backgroundExecutor = Executors.newCachedThreadPool()
|
|
94
|
+
private val pendingToolCalls = ConcurrentHashMap<String, PendingToolCall>()
|
|
95
|
+
private val customToolRegistrations = ConcurrentHashMap<String, CustomToolRegistration>()
|
|
96
|
+
private val activeCustomToolIds = ConcurrentHashMap.newKeySet<String>()
|
|
97
|
+
private val listenerCount = AtomicInteger(0)
|
|
98
|
+
private val reactNativeMemoryProfiler = ReactNativeMemoryProfiler(reactContext)
|
|
99
|
+
private var currentReactNativeMemoryOptions = ReactNativeMemoryProfilingOptions.Defaults
|
|
100
|
+
private val logCallback = AnsightLogCallback { level, message, throwable ->
|
|
101
|
+
emitLogEvent(level, message, throwable)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
init {
|
|
105
|
+
reactContext.addLifecycleEventListener(this)
|
|
106
|
+
AnsightLogger.registerCallback(logCallback)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override fun getName(): String = "AnsightReactNative"
|
|
110
|
+
|
|
111
|
+
override fun invalidate() {
|
|
112
|
+
AnsightLogger.removeCallback(logCallback)
|
|
113
|
+
reactContext.removeLifecycleEventListener(this)
|
|
114
|
+
super.invalidate()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override fun onHostResume() {
|
|
118
|
+
bindCurrentActivity()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun onHostPause() = Unit
|
|
122
|
+
|
|
123
|
+
override fun onHostDestroy() = Unit
|
|
124
|
+
|
|
125
|
+
@ReactMethod
|
|
126
|
+
fun addListener(eventName: String) {
|
|
127
|
+
listenerCount.incrementAndGet()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@ReactMethod
|
|
131
|
+
fun removeListeners(count: Int) {
|
|
132
|
+
listenerCount.updateAndGet { current -> (current - count).coerceAtLeast(0) }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@ReactMethod
|
|
136
|
+
fun initialize(options: ReadableMap?, promise: Promise) {
|
|
137
|
+
runCatching {
|
|
138
|
+
val runtimeOptions = buildOptions(options)
|
|
139
|
+
Ansight.initialize(application(), runtimeOptions)
|
|
140
|
+
configureReactNativeMemoryProfiling(options)
|
|
141
|
+
installRegisteredCustomTools()
|
|
142
|
+
snapshotMap()
|
|
143
|
+
}.resolve(promise)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@ReactMethod
|
|
147
|
+
fun initializeAndActivate(options: ReadableMap?, promise: Promise) {
|
|
148
|
+
runCatching {
|
|
149
|
+
val runtimeOptions = buildOptions(options)
|
|
150
|
+
Ansight.initializeAndActivate(application(), runtimeOptions)
|
|
151
|
+
configureReactNativeMemoryProfiling(options)
|
|
152
|
+
bindCurrentActivity()
|
|
153
|
+
installRegisteredCustomTools()
|
|
154
|
+
snapshotMap()
|
|
155
|
+
}.resolve(promise)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@ReactMethod
|
|
159
|
+
fun activate(promise: Promise) {
|
|
160
|
+
runCatching {
|
|
161
|
+
AnsightRuntime.activate()
|
|
162
|
+
bindCurrentActivity()
|
|
163
|
+
snapshotMap()
|
|
164
|
+
}.resolve(promise)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@ReactMethod
|
|
168
|
+
fun deactivate(promise: Promise) {
|
|
169
|
+
runCatching {
|
|
170
|
+
AnsightRuntime.deactivate()
|
|
171
|
+
snapshotMap()
|
|
172
|
+
}.resolve(promise)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@ReactMethod
|
|
176
|
+
fun clear(promise: Promise) {
|
|
177
|
+
runCatching {
|
|
178
|
+
AnsightRuntime.clear()
|
|
179
|
+
snapshotMap()
|
|
180
|
+
}.resolve(promise)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@ReactMethod
|
|
184
|
+
fun registerMetricChannel(channel: ReadableMap, promise: Promise) {
|
|
185
|
+
runCatching {
|
|
186
|
+
AnsightRuntime.registerMetricChannel(
|
|
187
|
+
AnsightChannel(
|
|
188
|
+
id = channel.intValue("id", -1),
|
|
189
|
+
name = channel.stringValue("name") ?: "",
|
|
190
|
+
unit = channel.stringValue("unit"),
|
|
191
|
+
type = channel.stringValue("type") ?: "custom",
|
|
192
|
+
colorHex = channel.stringValue("colorHex"),
|
|
193
|
+
source = channel.stringValue("source"),
|
|
194
|
+
group = channel.stringValue("group"),
|
|
195
|
+
kind = channel.stringValue("kind"),
|
|
196
|
+
),
|
|
197
|
+
)
|
|
198
|
+
snapshotMap()
|
|
199
|
+
}.resolve(promise)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@ReactMethod
|
|
203
|
+
fun recordMetric(value: Double, channel: Double, promise: Promise) {
|
|
204
|
+
runCatching {
|
|
205
|
+
val channelId = if (channel.isNaN()) AnsightChannels.Unspecified else channel.toInt()
|
|
206
|
+
AnsightRuntime.metric(value.toLong(), channelId)
|
|
207
|
+
snapshotMap()
|
|
208
|
+
}.resolve(promise)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@ReactMethod
|
|
212
|
+
fun recordEvent(input: ReadableMap, promise: Promise) {
|
|
213
|
+
runCatching {
|
|
214
|
+
val label = input.stringValue("label") ?: ""
|
|
215
|
+
AnsightRuntime.event(
|
|
216
|
+
label = label,
|
|
217
|
+
type = eventType(input.stringValue("type")),
|
|
218
|
+
details = input.stringValue("details"),
|
|
219
|
+
channel = input.intValue("channel", AnsightChannels.Unspecified),
|
|
220
|
+
)
|
|
221
|
+
snapshotMap()
|
|
222
|
+
}.resolve(promise)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@ReactMethod
|
|
226
|
+
fun screenViewed(name: String, details: ReadableMap?, promise: Promise) {
|
|
227
|
+
runCatching {
|
|
228
|
+
AnsightRuntime.screenViewed(name, details.toStringMap())
|
|
229
|
+
snapshotMap()
|
|
230
|
+
}.resolve(promise)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@ReactMethod
|
|
234
|
+
fun setAppLifecycleState(state: String, promise: Promise) {
|
|
235
|
+
runCatching {
|
|
236
|
+
AnsightRuntime.setAppLifecycleState(lifecycleState(state))
|
|
237
|
+
snapshotMap()
|
|
238
|
+
}.resolve(promise)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@ReactMethod
|
|
242
|
+
fun connect(pairingPayload: String?, options: ReadableMap?, promise: Promise) {
|
|
243
|
+
backgroundExecutor.execute {
|
|
244
|
+
runCatching {
|
|
245
|
+
val request = if (pairingPayload.isNullOrBlank()) {
|
|
246
|
+
HostConnectionRequest(
|
|
247
|
+
kind = HostConnectionRequestKind.Auto,
|
|
248
|
+
clientName = options.stringValue("clientName"),
|
|
249
|
+
expectedAppId = options.stringValue("expectedAppId"),
|
|
250
|
+
hostAddressOverride = options.stringValue("hostAddressOverride"),
|
|
251
|
+
)
|
|
252
|
+
} else {
|
|
253
|
+
HostConnectionRequest(
|
|
254
|
+
kind = HostConnectionRequestKind.Payload,
|
|
255
|
+
payload = pairingPayload,
|
|
256
|
+
clientName = options.stringValue("clientName"),
|
|
257
|
+
expectedAppId = options.stringValue("expectedAppId"),
|
|
258
|
+
hostAddressOverride = options.stringValue("hostAddressOverride"),
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
hostConnectionResultMap(AnsightRuntime.connect(request))
|
|
262
|
+
}.resolve(promise)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@ReactMethod
|
|
267
|
+
fun openSession(pairingPayload: String?, options: ReadableMap?, promise: Promise) {
|
|
268
|
+
backgroundExecutor.execute {
|
|
269
|
+
runCatching {
|
|
270
|
+
openSessionResultMap(
|
|
271
|
+
AnsightRuntime.openSession(
|
|
272
|
+
pairingPayload.orEmpty(),
|
|
273
|
+
pairingOpenOptions(options),
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
}.resolve(promise)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
@ReactMethod
|
|
281
|
+
fun disconnect(promise: Promise) {
|
|
282
|
+
backgroundExecutor.execute {
|
|
283
|
+
runCatching { hostConnectionResultMap(AnsightRuntime.disconnect()) }.resolve(promise)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@ReactMethod
|
|
288
|
+
fun completeSession(promise: Promise) {
|
|
289
|
+
backgroundExecutor.execute {
|
|
290
|
+
runCatching {
|
|
291
|
+
AnsightRuntime.completeSession()
|
|
292
|
+
operationResultMap(OperationResult.success("Session completed."))
|
|
293
|
+
}.resolve(promise)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
@ReactMethod
|
|
298
|
+
fun closeSession(promise: Promise) {
|
|
299
|
+
backgroundExecutor.execute {
|
|
300
|
+
runCatching {
|
|
301
|
+
AnsightRuntime.closeSession()
|
|
302
|
+
operationResultMap(OperationResult.success("Session closed."))
|
|
303
|
+
}.resolve(promise)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
@ReactMethod
|
|
308
|
+
fun savePairingConfig(pairingPayload: String?, options: ReadableMap?, promise: Promise) {
|
|
309
|
+
backgroundExecutor.execute {
|
|
310
|
+
runCatching {
|
|
311
|
+
hostConnectionResultMap(
|
|
312
|
+
AnsightRuntime.savePairingConfig(
|
|
313
|
+
pairingPayload.orEmpty(),
|
|
314
|
+
options.stringValue("expectedAppId"),
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
}.resolve(promise)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
@ReactMethod
|
|
322
|
+
fun clearSavedPairing(promise: Promise) {
|
|
323
|
+
runCatching { hostConnectionResultMap(AnsightRuntime.clearSavedPairingConfig()) }.resolve(promise)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
@ReactMethod
|
|
327
|
+
fun clearCachedSession(promise: Promise) {
|
|
328
|
+
runCatching { operationResultMap(AnsightRuntime.clearCachedSession()) }.resolve(promise)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
@ReactMethod
|
|
332
|
+
fun notifyHostConnectionConfigChanged(promise: Promise) {
|
|
333
|
+
runCatching { hostConnectionResultMap(AnsightRuntime.notifyHostConnectionConfigChanged()) }.resolve(promise)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@ReactMethod
|
|
337
|
+
fun status(promise: Promise) {
|
|
338
|
+
runCatching { snapshotMap() }.resolve(promise)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@ReactMethod
|
|
342
|
+
fun snapshot(promise: Promise) {
|
|
343
|
+
runCatching { snapshotMap() }.resolve(promise)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@ReactMethod
|
|
347
|
+
fun hostConnectionStatus(promise: Promise) {
|
|
348
|
+
runCatching { hostConnectionStatusMap(AnsightRuntime.hostConnectionStatus()) }.resolve(promise)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@ReactMethod
|
|
352
|
+
fun hostConnectionCapabilities(promise: Promise) {
|
|
353
|
+
runCatching { hostConnectionCapabilitiesMap(AnsightRuntime.hostConnectionCapabilities()) }.resolve(promise)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@ReactMethod
|
|
357
|
+
fun currentOptions(promise: Promise) {
|
|
358
|
+
runCatching { optionsMap(AnsightRuntime.options()) }.resolve(promise)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@ReactMethod
|
|
362
|
+
fun recordedMetrics(limit: Double, promise: Promise) {
|
|
363
|
+
runCatching {
|
|
364
|
+
val metrics = AnsightRuntime.recordedMetrics()
|
|
365
|
+
metricsArray(metrics.takeLast(limit.toInt().takeIf { it > 0 } ?: metrics.size))
|
|
366
|
+
}.resolve(promise)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
@ReactMethod
|
|
370
|
+
fun recordedEvents(limit: Double, promise: Promise) {
|
|
371
|
+
runCatching {
|
|
372
|
+
val events = AnsightRuntime.recordedEvents()
|
|
373
|
+
eventsArray(events.takeLast(limit.toInt().takeIf { it > 0 } ?: events.size))
|
|
374
|
+
}.resolve(promise)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@ReactMethod
|
|
378
|
+
fun sendClientLog(line: String, promise: Promise) {
|
|
379
|
+
runCatching { operationResultMap(AnsightRuntime.sendClientLog(line)) }.resolve(promise)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@ReactMethod
|
|
383
|
+
fun captureBuiltInTelemetrySample(promise: Promise) {
|
|
384
|
+
runCatching {
|
|
385
|
+
AnsightRuntime.captureBuiltInTelemetrySample()
|
|
386
|
+
snapshotMap()
|
|
387
|
+
}.resolve(promise)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@ReactMethod
|
|
391
|
+
fun isFramesPerSecondEnabled(promise: Promise) {
|
|
392
|
+
runCatching { AnsightRuntime.isFramesPerSecondEnabled() }.resolve(promise)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
@ReactMethod
|
|
396
|
+
fun enableFramesPerSecond(promise: Promise) {
|
|
397
|
+
runCatching {
|
|
398
|
+
AnsightRuntime.enableFramesPerSecond()
|
|
399
|
+
snapshotMap()
|
|
400
|
+
}.resolve(promise)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
@ReactMethod
|
|
404
|
+
fun disableFramesPerSecond(promise: Promise) {
|
|
405
|
+
runCatching {
|
|
406
|
+
AnsightRuntime.disableFramesPerSecond()
|
|
407
|
+
snapshotMap()
|
|
408
|
+
}.resolve(promise)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
@ReactMethod
|
|
412
|
+
fun captureScreenFrame(options: ReadableMap?, promise: Promise) {
|
|
413
|
+
backgroundExecutor.execute {
|
|
414
|
+
runCatching {
|
|
415
|
+
bindCurrentActivity()
|
|
416
|
+
operationResultMap(AnsightRuntime.captureScreenFrame(sessionJpegCaptureOptions(options)))
|
|
417
|
+
}.resolve(promise)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
@ReactMethod
|
|
422
|
+
fun enableTouchCapture(promise: Promise) {
|
|
423
|
+
runCatching { operationResultMap(AnsightRuntime.enableTouchCapture()) }.resolve(promise)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@ReactMethod
|
|
427
|
+
fun disableTouchCapture(promise: Promise) {
|
|
428
|
+
runCatching { operationResultMap(AnsightRuntime.disableTouchCapture()) }.resolve(promise)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@ReactMethod
|
|
432
|
+
fun updateSessionProperties(properties: ReadableMap?, promise: Promise) {
|
|
433
|
+
runCatching {
|
|
434
|
+
operationResultMap(AnsightRuntime.updateCustomProperties(properties.toGroupedStringMap()))
|
|
435
|
+
}.resolve(promise)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
@ReactMethod
|
|
439
|
+
fun clearSessionProperties(promise: Promise) {
|
|
440
|
+
runCatching { operationResultMap(AnsightRuntime.clearCustomProperties()) }.resolve(promise)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
@ReactMethod
|
|
444
|
+
fun registerCustomProperty(group: String, key: String, value: String, promise: Promise) {
|
|
445
|
+
runCatching { operationResultMap(AnsightRuntime.registerCustomProperty(group, key, value)) }.resolve(promise)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
@ReactMethod
|
|
449
|
+
fun removeCustomProperty(group: String, key: String, promise: Promise) {
|
|
450
|
+
runCatching { operationResultMap(AnsightRuntime.removeCustomProperty(group, key)) }.resolve(promise)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
@ReactMethod
|
|
454
|
+
fun registerCustomTool(definitionMap: ReadableMap, promise: Promise) {
|
|
455
|
+
runCatching {
|
|
456
|
+
val definition = toolDefinition(definitionMap)
|
|
457
|
+
val timeoutMilliseconds = definitionMap.intValue("timeoutMilliseconds", 30_000).toLong().coerceAtLeast(250L)
|
|
458
|
+
val registration = CustomToolRegistration(definition, timeoutMilliseconds)
|
|
459
|
+
customToolRegistrations[definition.id] = registration
|
|
460
|
+
activeCustomToolIds.add(definition.id)
|
|
461
|
+
if (AnsightRuntime.snapshot().initialized) {
|
|
462
|
+
installCustomTool(registration)
|
|
463
|
+
}
|
|
464
|
+
mapOf("id" to definition.id, "registered" to true).toWritableMap()
|
|
465
|
+
}.resolve(promise)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
@ReactMethod
|
|
469
|
+
fun unregisterCustomTool(id: String, promise: Promise) {
|
|
470
|
+
activeCustomToolIds.remove(id.trim())
|
|
471
|
+
customToolRegistrations.remove(id.trim())
|
|
472
|
+
promise.resolve(mapOf("id" to id.trim(), "registered" to false).toWritableMap())
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
@ReactMethod
|
|
476
|
+
fun clearRegisteredCustomTools(promise: Promise) {
|
|
477
|
+
activeCustomToolIds.clear()
|
|
478
|
+
customToolRegistrations.clear()
|
|
479
|
+
promise.resolve(mapOf("cleared" to true).toWritableMap())
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
@ReactMethod
|
|
483
|
+
fun resolveToolCall(requestId: String, resultMap: ReadableMap, promise: Promise) {
|
|
484
|
+
val pending = pendingToolCalls[requestId]
|
|
485
|
+
if (pending == null) {
|
|
486
|
+
promise.resolve(mapOf("requestId" to requestId, "accepted" to false).toWritableMap())
|
|
487
|
+
return
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
val success = resultMap.booleanValue("success", true)
|
|
491
|
+
val message = resultMap.stringValue("message")
|
|
492
|
+
val errorCode = resultMap.stringValue("errorCode")
|
|
493
|
+
val payload = resultPayload(resultMap)
|
|
494
|
+
pending.result = if (success) {
|
|
495
|
+
AndroidToolResult.success(payload, message)
|
|
496
|
+
} else {
|
|
497
|
+
AndroidToolResult.failure(message ?: "JavaScript tool failed.", errorCode, payload)
|
|
498
|
+
}
|
|
499
|
+
pending.latch.countDown()
|
|
500
|
+
promise.resolve(mapOf("requestId" to requestId, "accepted" to true).toWritableMap())
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@ReactMethod
|
|
504
|
+
fun queueBinaryTransfer(requestId: String, base64Data: String, chunkBytes: Int, promise: Promise) {
|
|
505
|
+
runCatching {
|
|
506
|
+
val pending = pendingToolCalls[requestId.trim()]
|
|
507
|
+
?: return@runCatching operationResultMap(OperationResult.failure("Binary transfer requires an active JavaScript tool request.")).apply {
|
|
508
|
+
putString("errorCode", "artifact_request_unavailable")
|
|
509
|
+
}
|
|
510
|
+
val transport = pending.context.transport
|
|
511
|
+
?: return@runCatching operationResultMap(OperationResult.failure("Binary transfers require an active pairing session.")).apply {
|
|
512
|
+
putString("errorCode", "artifact_transfer_unavailable")
|
|
513
|
+
}
|
|
514
|
+
val bytes = Base64.decode(base64Data, Base64.DEFAULT)
|
|
515
|
+
val normalizedChunkBytes = chunkBytes.coerceIn(1024, 512 * 1024)
|
|
516
|
+
val transferId = PairingFileTransferWireProtocol.newTransferId()
|
|
517
|
+
backgroundExecutor.execute {
|
|
518
|
+
transport.sendBinaryTransfer(transferId, bytes, normalizedChunkBytes)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
operationResultMap(OperationResult.success("Binary transfer queued.")).apply {
|
|
522
|
+
putString("transferId", transferId)
|
|
523
|
+
putString("deliveryMode", "websocket_binary")
|
|
524
|
+
putString("wireProtocol", PairingFileTransferWireProtocol.ProtocolName)
|
|
525
|
+
putString("status", "queued")
|
|
526
|
+
putInt("chunkBytes", normalizedChunkBytes)
|
|
527
|
+
putInt("sizeBytes", bytes.size)
|
|
528
|
+
}
|
|
529
|
+
}.resolve(promise)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private fun executeJavaScriptTool(
|
|
533
|
+
toolId: String,
|
|
534
|
+
arguments: Map<String, String>,
|
|
535
|
+
context: AndroidToolExecutionContext,
|
|
536
|
+
timeoutMilliseconds: Long,
|
|
537
|
+
): AndroidToolResult {
|
|
538
|
+
if (toolId !in activeCustomToolIds) {
|
|
539
|
+
return AndroidToolResult.failure("Tool '$toolId' is no longer registered in JavaScript.", "javascript_tool_unregistered")
|
|
540
|
+
}
|
|
541
|
+
if (listenerCount.get() <= 0 || !reactContext.hasActiveCatalystInstance()) {
|
|
542
|
+
return AndroidToolResult.failure("React Native JavaScript bridge is not listening for Ansight tool calls.", "javascript_bridge_unavailable")
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
val requestId = "android.${UUID.randomUUID().toString().replace("-", "")}"
|
|
546
|
+
val pending = PendingToolCall(context = context)
|
|
547
|
+
pendingToolCalls[requestId] = pending
|
|
548
|
+
emitToolCall(requestId, toolId, arguments, context)
|
|
549
|
+
|
|
550
|
+
val completed = pending.latch.await(timeoutMilliseconds, TimeUnit.MILLISECONDS)
|
|
551
|
+
pendingToolCalls.remove(requestId)
|
|
552
|
+
if (!completed) {
|
|
553
|
+
return AndroidToolResult.failure("JavaScript handler for tool '$toolId' timed out.", "javascript_tool_timeout")
|
|
554
|
+
}
|
|
555
|
+
return pending.result ?: AndroidToolResult.failure("JavaScript handler for tool '$toolId' returned no result.", "javascript_tool_empty_result")
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private fun installRegisteredCustomTools() {
|
|
559
|
+
customToolRegistrations.values.forEach { registration ->
|
|
560
|
+
installCustomTool(registration)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
private fun installCustomTool(registration: CustomToolRegistration) {
|
|
565
|
+
val definition = registration.definition
|
|
566
|
+
activeCustomToolIds.add(definition.id)
|
|
567
|
+
AnsightRuntime.registerTool(
|
|
568
|
+
FunctionAndroidTool(definition) { arguments, context ->
|
|
569
|
+
executeJavaScriptTool(definition.id, arguments, context, registration.timeoutMilliseconds)
|
|
570
|
+
},
|
|
571
|
+
replaceExisting = true,
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private fun emitToolCall(
|
|
576
|
+
requestId: String,
|
|
577
|
+
toolId: String,
|
|
578
|
+
arguments: Map<String, String>,
|
|
579
|
+
context: AndroidToolExecutionContext,
|
|
580
|
+
) {
|
|
581
|
+
val event = Arguments.createMap()
|
|
582
|
+
event.putString("requestId", requestId)
|
|
583
|
+
event.putString("toolId", toolId)
|
|
584
|
+
event.putString("platform", "android")
|
|
585
|
+
event.putString("sessionId", context.sessionId)
|
|
586
|
+
event.putString("nativeRequestId", context.requestId)
|
|
587
|
+
event.putMap("arguments", arguments.toWritableMap())
|
|
588
|
+
UiThreadUtil.runOnUiThread {
|
|
589
|
+
reactContext
|
|
590
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
591
|
+
.emit("AnsightToolCall", event)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
private fun emitLogEvent(level: AnsightLogLevel, message: String, throwable: Throwable?) {
|
|
596
|
+
if (listenerCount.get() <= 0) {
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
val event = Arguments.createMap()
|
|
600
|
+
event.putString("level", level.name.lowercase(Locale.US))
|
|
601
|
+
event.putString("message", message)
|
|
602
|
+
event.putString("platform", "android")
|
|
603
|
+
throwable?.message?.let { event.putString("error", it) }
|
|
604
|
+
UiThreadUtil.runOnUiThread {
|
|
605
|
+
reactContext
|
|
606
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
607
|
+
.emit("AnsightLog", event)
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private fun configureReactNativeMemoryProfiling(map: ReadableMap?) {
|
|
612
|
+
val options = reactNativeMemoryProfilingOptions(map)
|
|
613
|
+
currentReactNativeMemoryOptions = options
|
|
614
|
+
reactNativeMemoryProfiler.register(options)
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
private fun buildOptions(map: ReadableMap?): AnsightOptions {
|
|
618
|
+
val useNativeAllInOneDefaults = map.booleanValue("useNativeAllInOneDefaults", false)
|
|
619
|
+
val pairingConfigJson = map.stringValue("pairingConfigJson")
|
|
620
|
+
val clientName = map.stringValue("clientName")
|
|
621
|
+
var options = if (useNativeAllInOneDefaults) {
|
|
622
|
+
AnsightDeveloperMode.options(
|
|
623
|
+
bundledDeveloperConfigJson = pairingConfigJson,
|
|
624
|
+
clientName = clientName,
|
|
625
|
+
)
|
|
626
|
+
} else {
|
|
627
|
+
AnsightOptions()
|
|
628
|
+
}
|
|
629
|
+
if (!useNativeAllInOneDefaults && !pairingConfigJson.isNullOrBlank()) {
|
|
630
|
+
options = options.copy(
|
|
631
|
+
hostConnection = options.hostConnection.copy(
|
|
632
|
+
bundledConfigJson = pairingConfigJson,
|
|
633
|
+
),
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
if (useNativeAllInOneDefaults && !map.hasString("toolGuard")) {
|
|
637
|
+
options = options.copy(toolGuard = AnsightToolGuard.ReadOnly)
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (map.hasNumber("sampleFrequencyMilliseconds")) {
|
|
641
|
+
options = options.copy(sampleFrequencyMilliseconds = map.intValue("sampleFrequencyMilliseconds", options.sampleFrequencyMilliseconds))
|
|
642
|
+
}
|
|
643
|
+
if (map.hasNumber("retentionPeriodSeconds")) {
|
|
644
|
+
options = options.copy(retentionPeriodSeconds = map.intValue("retentionPeriodSeconds", options.retentionPeriodSeconds))
|
|
645
|
+
}
|
|
646
|
+
if (map.hasBoolean("enableFramesPerSecond")) {
|
|
647
|
+
options = options.copy(enableFramesPerSecond = map.booleanValue("enableFramesPerSecond", options.enableFramesPerSecond))
|
|
648
|
+
}
|
|
649
|
+
if (map.hasBoolean("enableBatteryLevel")) {
|
|
650
|
+
options = options.copy(enableBatteryLevel = map.booleanValue("enableBatteryLevel", options.enableBatteryLevel))
|
|
651
|
+
}
|
|
652
|
+
if (map.hasMap("defaultMemoryChannels")) {
|
|
653
|
+
val memory = map.getMapOrNull("defaultMemoryChannels")
|
|
654
|
+
val managedHeap = if (memory.hasBoolean("managedHeap")) {
|
|
655
|
+
memory.booleanValue("managedHeap", options.defaultMemoryChannels.javaHeap)
|
|
656
|
+
} else {
|
|
657
|
+
memory.booleanValue("javaHeap", options.defaultMemoryChannels.javaHeap)
|
|
658
|
+
}
|
|
659
|
+
val residentSetSize = if (memory.hasBoolean("residentSetSize")) {
|
|
660
|
+
memory.booleanValue("residentSetSize", options.defaultMemoryChannels.rss)
|
|
661
|
+
} else {
|
|
662
|
+
memory.booleanValue("rss", options.defaultMemoryChannels.rss)
|
|
663
|
+
}
|
|
664
|
+
options = options.copy(
|
|
665
|
+
defaultMemoryChannels = DefaultMemoryChannels(
|
|
666
|
+
javaHeap = managedHeap,
|
|
667
|
+
nativeHeap = memory.booleanValue("nativeHeap", options.defaultMemoryChannels.nativeHeap),
|
|
668
|
+
rss = residentSetSize,
|
|
669
|
+
),
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
if (map.hasArray("additionalChannels")) {
|
|
673
|
+
val channels = mutableListOf<AnsightChannel>()
|
|
674
|
+
val array = map.getArrayOrNull("additionalChannels")
|
|
675
|
+
if (array != null) {
|
|
676
|
+
for (index in 0 until array.size()) {
|
|
677
|
+
val channel = array.getMap(index)
|
|
678
|
+
channels += AnsightChannel(
|
|
679
|
+
id = channel.intValue("id", -1),
|
|
680
|
+
name = channel.stringValue("name") ?: "",
|
|
681
|
+
unit = channel.stringValue("unit"),
|
|
682
|
+
type = channel.stringValue("type") ?: "custom",
|
|
683
|
+
colorHex = channel.stringValue("colorHex"),
|
|
684
|
+
source = channel.stringValue("source"),
|
|
685
|
+
group = channel.stringValue("group"),
|
|
686
|
+
kind = channel.stringValue("kind"),
|
|
687
|
+
)
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
options = options.copy(additionalChannels = channels)
|
|
691
|
+
}
|
|
692
|
+
if (map.hasKey("sessionJpegCapture")) {
|
|
693
|
+
options = options.copy(
|
|
694
|
+
sessionJpegCapture = if (map.isFalse("sessionJpegCapture")) {
|
|
695
|
+
null
|
|
696
|
+
} else {
|
|
697
|
+
val jpeg = map.getMapOrNull("sessionJpegCapture")
|
|
698
|
+
AnsightSessionJpegCaptureOptions(
|
|
699
|
+
intervalMilliseconds = jpeg.intValue(
|
|
700
|
+
"intervalMilliseconds",
|
|
701
|
+
AnsightSessionJpegCaptureOptions.DefaultIntervalMilliseconds,
|
|
702
|
+
),
|
|
703
|
+
quality = jpeg.intValue("quality", AnsightSessionJpegCaptureOptions.DefaultQuality),
|
|
704
|
+
maxWidth = jpeg.optionalInt("maxWidth") ?: AnsightSessionJpegCaptureOptions.DefaultMaxWidth,
|
|
705
|
+
)
|
|
706
|
+
},
|
|
707
|
+
)
|
|
708
|
+
}
|
|
709
|
+
if (map.hasKey("touchCapture")) {
|
|
710
|
+
options = options.copy(
|
|
711
|
+
touchCapture = if (map.isFalse("touchCapture")) {
|
|
712
|
+
null
|
|
713
|
+
} else {
|
|
714
|
+
val touch = map.getMapOrNull("touchCapture")
|
|
715
|
+
AnsightTouchCaptureOptions(
|
|
716
|
+
moveCaptureDistanceThreshold = touch.doubleValue("moveCaptureDistanceThreshold", 8.0),
|
|
717
|
+
moveCaptureFramesPerSecond = touch.intValue("moveCaptureFramesPerSecond", 20),
|
|
718
|
+
)
|
|
719
|
+
},
|
|
720
|
+
)
|
|
721
|
+
}
|
|
722
|
+
if (map.hasString("toolGuard")) {
|
|
723
|
+
options = options.copy(toolGuard = toolGuard(map.stringValue("toolGuard")))
|
|
724
|
+
}
|
|
725
|
+
if (map.hasMap("customProperties")) {
|
|
726
|
+
options = options.copy(customProperties = map.getMapOrNull("customProperties").toGroupedStringMap())
|
|
727
|
+
}
|
|
728
|
+
if (map.hasMap("hostAutoProbe")) {
|
|
729
|
+
val autoProbe = map.getMapOrNull("hostAutoProbe")
|
|
730
|
+
options = options.copy(
|
|
731
|
+
hostAutoProbe = AnsightHostAutoProbeOptions(
|
|
732
|
+
enabled = autoProbe.booleanValue("enabled", options.hostAutoProbe.enabled),
|
|
733
|
+
initialDelayMilliseconds = autoProbe.longValue("initialDelayMilliseconds", options.hostAutoProbe.initialDelayMilliseconds),
|
|
734
|
+
probeIntervalMilliseconds = autoProbe.longValue("probeIntervalMilliseconds", options.hostAutoProbe.probeIntervalMilliseconds),
|
|
735
|
+
reconnectDelayMilliseconds = autoProbe.longValue("reconnectDelayMilliseconds", options.hostAutoProbe.reconnectDelayMilliseconds),
|
|
736
|
+
clientName = autoProbe.stringValue("clientName") ?: options.hostAutoProbe.clientName,
|
|
737
|
+
),
|
|
738
|
+
)
|
|
739
|
+
}
|
|
740
|
+
if (map.hasMap("hostConnection")) {
|
|
741
|
+
val host = map.getMapOrNull("hostConnection")
|
|
742
|
+
options = options.copy(
|
|
743
|
+
hostConnection = AnsightHostConnectionOptions(
|
|
744
|
+
savedConfigKey = host.stringValue("savedConfigKey") ?: options.hostConnection.savedConfigKey,
|
|
745
|
+
bundledConfigJson = host.stringValue("bundledConfigJson") ?: options.hostConnection.bundledConfigJson,
|
|
746
|
+
bundledDeveloperConfigJson = host.stringValue("bundledDeveloperConfigJson") ?: options.hostConnection.bundledDeveloperConfigJson,
|
|
747
|
+
discoveryPort = host.optionalInt("discoveryPort") ?: options.hostConnection.discoveryPort,
|
|
748
|
+
connectionProfileRetentionSeconds = host.longValue(
|
|
749
|
+
"connectionProfileRetentionSeconds",
|
|
750
|
+
options.hostConnection.connectionProfileRetentionSeconds,
|
|
751
|
+
),
|
|
752
|
+
),
|
|
753
|
+
)
|
|
754
|
+
}
|
|
755
|
+
if (map.hasMap("secureStorage")) {
|
|
756
|
+
val secure = map.getMapOrNull("secureStorage")
|
|
757
|
+
options = options.copy(
|
|
758
|
+
secureStorage = AnsightSecureStorageOptions(
|
|
759
|
+
preferencesName = secure.stringValue("preferencesName") ?: options.secureStorage.preferencesName,
|
|
760
|
+
allowedKeys = secure.getStringSet("allowedKeys"),
|
|
761
|
+
allowedPrefixes = secure.getStringSet("allowedPrefixes"),
|
|
762
|
+
),
|
|
763
|
+
)
|
|
764
|
+
}
|
|
765
|
+
return options.withNativeToolOptions(map)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
private fun AnsightOptions.withNativeToolOptions(map: ReadableMap?): AnsightOptions {
|
|
769
|
+
val remoteTools = map.getMapOrNull("remoteTools")
|
|
770
|
+
val builder = AnsightOptions.createBuilder(this)
|
|
771
|
+
if (remoteTools.toolSuiteEnabled("visualTree")) {
|
|
772
|
+
builder.withVisualTreeTools()
|
|
773
|
+
}
|
|
774
|
+
builder.withDatabaseTools(databaseToolsOptions(remoteTools.getMapOrNull("database")))
|
|
775
|
+
builder.withFileSystemTools(fileSystemToolsOptions(remoteTools.getMapOrNull("fileSystem")))
|
|
776
|
+
builder.withPreferencesTools(preferencesToolsOptions(remoteTools.getMapOrNull("preferences")))
|
|
777
|
+
builder.withReflectionTools(reflectionToolsOptions(remoteTools.getMapOrNull("reflection")))
|
|
778
|
+
builder.withSecureStorageTools(
|
|
779
|
+
secureStorageToolsOptions(
|
|
780
|
+
remoteTools.getMapOrNull("secureStorage") ?: map.getMapOrNull("secureStorage"),
|
|
781
|
+
secureStorage,
|
|
782
|
+
),
|
|
783
|
+
)
|
|
784
|
+
return builder.build()
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private fun fileSystemToolsOptions(map: ReadableMap?): AndroidFileSystemToolsOptions {
|
|
788
|
+
return AndroidFileSystemToolsOptions(
|
|
789
|
+
additionalRoots = rootOptions(map.getArrayOrNull("additionalRoots")).map { root ->
|
|
790
|
+
AndroidFileSystemRoot(root.alias, root.path)
|
|
791
|
+
},
|
|
792
|
+
).validated()
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
private fun databaseToolsOptions(map: ReadableMap?): AndroidDatabaseToolsOptions {
|
|
796
|
+
return AndroidDatabaseToolsOptions(
|
|
797
|
+
additionalRoots = rootOptions(map.getArrayOrNull("additionalRoots")).map { root ->
|
|
798
|
+
AndroidDatabaseRoot(root.alias, root.path)
|
|
799
|
+
},
|
|
800
|
+
includePlatformRoots = map.booleanValue("includePlatformRoots", true),
|
|
801
|
+
).validated()
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
private fun preferencesToolsOptions(map: ReadableMap?): AndroidPreferencesToolsOptions {
|
|
805
|
+
return AndroidPreferencesToolsOptions(
|
|
806
|
+
defaultStore = map.stringValue("defaultStore"),
|
|
807
|
+
allowedStores = map.getStringSet("allowedStores"),
|
|
808
|
+
allowedKeys = map.getStringSet("allowedKeys"),
|
|
809
|
+
allowedKeyPrefixes = map.getStringSet("allowedKeyPrefixes"),
|
|
810
|
+
).validated()
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private fun reflectionToolsOptions(map: ReadableMap?): AndroidReflectionToolsOptions {
|
|
814
|
+
return AndroidReflectionToolsOptions(
|
|
815
|
+
includeBuiltInRoots = map.booleanValue("includeBuiltInRoots", true),
|
|
816
|
+
allowedRootIds = map.getStringSet("allowedRootIds"),
|
|
817
|
+
allowedTypePrefixes = map.getStringSet("allowedTypePrefixes"),
|
|
818
|
+
).validated()
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
private fun secureStorageToolsOptions(
|
|
822
|
+
map: ReadableMap?,
|
|
823
|
+
fallback: AnsightSecureStorageOptions,
|
|
824
|
+
): AnsightSecureStorageOptions {
|
|
825
|
+
val allowedPrefixes = map.getStringSet("allowedKeyPrefixes") + map.getStringSet("allowedPrefixes")
|
|
826
|
+
return AnsightSecureStorageOptions(
|
|
827
|
+
preferencesName = map.stringValue("preferencesName") ?: fallback.preferencesName,
|
|
828
|
+
allowedKeys = map.getStringSet("allowedKeys"),
|
|
829
|
+
allowedPrefixes = allowedPrefixes,
|
|
830
|
+
).validated()
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
private data class NativeToolRoot(val alias: String, val path: String)
|
|
834
|
+
|
|
835
|
+
private fun rootOptions(array: ReadableArray?): List<NativeToolRoot> {
|
|
836
|
+
if (array == null) {
|
|
837
|
+
return emptyList()
|
|
838
|
+
}
|
|
839
|
+
val roots = mutableListOf<NativeToolRoot>()
|
|
840
|
+
for (index in 0 until array.size()) {
|
|
841
|
+
if (!array.isNull(index) && array.getType(index) == ReadableType.Map) {
|
|
842
|
+
val root = array.getMap(index)
|
|
843
|
+
val alias = root.stringValue("alias")
|
|
844
|
+
val path = root.stringValue("path")
|
|
845
|
+
if (alias != null && path != null) {
|
|
846
|
+
roots += NativeToolRoot(alias, path)
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return roots
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
private fun pairingOpenOptions(map: ReadableMap?): PairingOpenOptions =
|
|
854
|
+
PairingOpenOptions(
|
|
855
|
+
clientName = map.stringValue("clientName") ?: "React Native",
|
|
856
|
+
expectedAppId = map.stringValue("expectedAppId"),
|
|
857
|
+
hostAddressOverride = map.stringValue("hostAddressOverride"),
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
private fun sessionJpegCaptureOptions(map: ReadableMap?): AnsightSessionJpegCaptureOptions? {
|
|
861
|
+
if (map == null) {
|
|
862
|
+
return null
|
|
863
|
+
}
|
|
864
|
+
return AnsightSessionJpegCaptureOptions(
|
|
865
|
+
intervalMilliseconds = map.intValue(
|
|
866
|
+
"intervalMilliseconds",
|
|
867
|
+
AnsightSessionJpegCaptureOptions.DefaultIntervalMilliseconds,
|
|
868
|
+
),
|
|
869
|
+
quality = map.intValue("quality", AnsightSessionJpegCaptureOptions.DefaultQuality),
|
|
870
|
+
maxWidth = map.optionalInt("maxWidth") ?: AnsightSessionJpegCaptureOptions.DefaultMaxWidth,
|
|
871
|
+
)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
private fun toolDefinition(map: ReadableMap): ToolDefinition =
|
|
875
|
+
ToolDefinition(
|
|
876
|
+
id = map.stringValue("id") ?: "",
|
|
877
|
+
name = map.stringValue("name") ?: map.stringValue("id") ?: "",
|
|
878
|
+
description = map.stringValue("description") ?: "",
|
|
879
|
+
category = map.stringValue("category") ?: "custom",
|
|
880
|
+
scope = toolScope(map.stringValue("scope")),
|
|
881
|
+
keywords = when {
|
|
882
|
+
map.hasArray("keywords") -> map.getArray("keywords").toStringList().joinToString(" ")
|
|
883
|
+
else -> map.stringValue("keywords") ?: "react native custom tool"
|
|
884
|
+
},
|
|
885
|
+
argumentsSchema = schemaFrom(map.getMapOrNull("argumentsSchema")),
|
|
886
|
+
resultSchema = schemaFrom(map.getMapOrNull("resultSchema")),
|
|
887
|
+
security = toolSecurity(map.getMapOrNull("security")),
|
|
888
|
+
).validated()
|
|
889
|
+
|
|
890
|
+
private fun schemaFrom(map: ReadableMap?): ToolSchema {
|
|
891
|
+
if (map == null) {
|
|
892
|
+
return ToolSchema.obj(additionalProperties = true)
|
|
893
|
+
}
|
|
894
|
+
val type = when {
|
|
895
|
+
map.hasArray("type") -> map.getArray("type").toStringList().firstOrNull { it != "null" } ?: "object"
|
|
896
|
+
else -> map.stringValue("type") ?: "object"
|
|
897
|
+
}
|
|
898
|
+
val properties = mutableMapOf<String, ToolSchema>()
|
|
899
|
+
map.getMapOrNull("properties")?.let { props ->
|
|
900
|
+
val iterator = props.keySetIterator()
|
|
901
|
+
while (iterator.hasNextKey()) {
|
|
902
|
+
val key = iterator.nextKey()
|
|
903
|
+
props.getMapOrNull(key)?.let { properties[key] = schemaFrom(it) }
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return ToolSchema(
|
|
907
|
+
type = type,
|
|
908
|
+
description = map.stringValue("description"),
|
|
909
|
+
properties = properties,
|
|
910
|
+
required = map.getArrayOrNull("required").toStringList(),
|
|
911
|
+
items = schemaFrom(map.getMapOrNull("items")).takeIf { map.hasMap("items") },
|
|
912
|
+
enumValues = map.getArrayOrNull("enum").toStringList(),
|
|
913
|
+
additionalProperties = map.booleanValue("additionalProperties", false),
|
|
914
|
+
nullable = map.hasArray("type") && map.getArray("type").toStringList().contains("null"),
|
|
915
|
+
format = map.stringValue("format"),
|
|
916
|
+
)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
private fun toolSecurity(map: ReadableMap?): ToolSecurity {
|
|
920
|
+
if (map == null) {
|
|
921
|
+
return ToolSecurity.Unspecified
|
|
922
|
+
}
|
|
923
|
+
return ToolSecurity(
|
|
924
|
+
level = when (map.stringValue("level")?.trim()?.lowercase()) {
|
|
925
|
+
"medium", "moderate" -> ToolSecurityLevel.Medium
|
|
926
|
+
"high" -> ToolSecurityLevel.High
|
|
927
|
+
"critical" -> ToolSecurityLevel.Critical
|
|
928
|
+
else -> ToolSecurityLevel.Low
|
|
929
|
+
},
|
|
930
|
+
implications = map.getArrayOrNull("implications").toStringList(),
|
|
931
|
+
)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private fun resultPayload(map: ReadableMap): JSONObject? {
|
|
935
|
+
if (!map.hasKey("result") || map.isNull("result")) {
|
|
936
|
+
return null
|
|
937
|
+
}
|
|
938
|
+
return when (map.getType("result")) {
|
|
939
|
+
ReadableType.Map -> map.getMap("result")?.toJSONObject()
|
|
940
|
+
ReadableType.Array -> JSONObject().put("value", map.getArray("result")?.toJSONArray() ?: JSONArray())
|
|
941
|
+
ReadableType.String -> JSONObject().put("value", map.getString("result"))
|
|
942
|
+
ReadableType.Number -> JSONObject().put("value", map.getDouble("result"))
|
|
943
|
+
ReadableType.Boolean -> JSONObject().put("value", map.getBoolean("result"))
|
|
944
|
+
else -> null
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
private fun snapshotMap(): WritableMap {
|
|
949
|
+
val snapshot = AnsightRuntime.snapshot()
|
|
950
|
+
return Arguments.createMap().apply {
|
|
951
|
+
putBoolean("initialized", snapshot.initialized)
|
|
952
|
+
putBoolean("active", snapshot.active)
|
|
953
|
+
putBoolean("sessionOpen", snapshot.sessionOpen)
|
|
954
|
+
putString("lifecycleState", snapshot.lifecycleState.wireName)
|
|
955
|
+
putString("lifecycleChangedAtUtc", snapshot.lifecycleChangedAtUtc)
|
|
956
|
+
putInt("metricsRecorded", snapshot.metricsRecorded)
|
|
957
|
+
putInt("eventsRecorded", snapshot.eventsRecorded)
|
|
958
|
+
putInt("touchesRecorded", snapshot.touchesRecorded)
|
|
959
|
+
putInt("registeredTools", snapshot.registeredTools)
|
|
960
|
+
putString("sessionMessage", snapshot.sessionMessage)
|
|
961
|
+
putMap("connectionStatus", hostConnectionStatusMap(snapshot.connectionStatus))
|
|
962
|
+
putArray("channels", channelsArray(snapshot.channels))
|
|
963
|
+
snapshot.lastMetric?.let { putMap("lastMetric", metricMap(it)) }
|
|
964
|
+
snapshot.lastEvent?.let { putMap("lastEvent", eventMap(it)) }
|
|
965
|
+
snapshot.currentScreen?.let { screen ->
|
|
966
|
+
putMap("currentScreen", Arguments.createMap().apply {
|
|
967
|
+
putString("name", screen.name)
|
|
968
|
+
putString("capturedAtUtc", screen.capturedAtUtc)
|
|
969
|
+
putMap("details", screen.details.toWritableMap())
|
|
970
|
+
})
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
private fun hostConnectionStatusMap(status: HostConnectionStatus): WritableMap =
|
|
976
|
+
Arguments.createMap().apply {
|
|
977
|
+
putBoolean("isRuntimeActive", status.isRuntimeActive)
|
|
978
|
+
putBoolean("isConnected", status.isConnected)
|
|
979
|
+
putString("connectionState", status.connectionState.name)
|
|
980
|
+
putBoolean("hasCachedSession", status.hasCachedSession)
|
|
981
|
+
putBoolean("hasSavedConfig", status.hasSavedConfig)
|
|
982
|
+
putBoolean("hasBundledConfig", status.hasBundledConfig)
|
|
983
|
+
putString("summaryKind", status.summaryKind.name)
|
|
984
|
+
putString("summaryMessage", status.summaryMessage)
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
private fun hostConnectionCapabilitiesMap(capabilities: HostConnectionCapabilities): WritableMap =
|
|
988
|
+
Arguments.createMap().apply {
|
|
989
|
+
putBoolean("canConnectUsingSavedConfig", capabilities.canConnectUsingSavedConfig)
|
|
990
|
+
putBoolean("canConnectUsingBundledConfig", capabilities.canConnectUsingBundledConfig)
|
|
991
|
+
putBoolean("canChooseConfigFile", capabilities.canChooseConfigFile)
|
|
992
|
+
putBoolean("canScanConfigQrCode", capabilities.canScanConfigQrCode)
|
|
993
|
+
putBoolean("canClearSavedConfigs", capabilities.canClearSavedConfigs)
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
private fun openSessionResultMap(result: OpenSessionResult): WritableMap =
|
|
997
|
+
operationResultMap(OperationResult(result.success, result.message)).apply {
|
|
998
|
+
putBoolean("accepted", result.accepted)
|
|
999
|
+
putString("sessionId", result.sessionId)
|
|
1000
|
+
putString("configId", result.configId)
|
|
1001
|
+
putString("appId", result.appId)
|
|
1002
|
+
putString("resolvedHostAddress", result.resolvedHostAddress)
|
|
1003
|
+
putBoolean("usedEmbeddedDeveloperPairing", result.usedEmbeddedDeveloperPairing)
|
|
1004
|
+
putString("discoverySource", result.discoverySource)
|
|
1005
|
+
putString("reasonCode", result.reasonCode)
|
|
1006
|
+
putString("hostId", result.hostId)
|
|
1007
|
+
putString("hostName", result.hostName)
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
private fun hostConnectionResultMap(result: HostConnectionResult): WritableMap =
|
|
1011
|
+
operationResultMap(result).apply {
|
|
1012
|
+
putString("kind", result.kind.name)
|
|
1013
|
+
putString("source", result.source.name)
|
|
1014
|
+
putString("reasonCode", result.reasonCode ?: result.openSession?.reasonCode)
|
|
1015
|
+
result.openSession?.let { session ->
|
|
1016
|
+
putString("sessionId", session.sessionId)
|
|
1017
|
+
putString("configId", session.configId)
|
|
1018
|
+
putString("appId", session.appId)
|
|
1019
|
+
putString("resolvedHostAddress", session.resolvedHostAddress)
|
|
1020
|
+
putString("hostId", session.hostId)
|
|
1021
|
+
putString("hostName", session.hostName)
|
|
1022
|
+
putBoolean("accepted", session.accepted)
|
|
1023
|
+
putBoolean("usedEmbeddedDeveloperPairing", session.usedEmbeddedDeveloperPairing)
|
|
1024
|
+
putString("discoverySource", session.discoverySource)
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
private fun operationResultMap(result: OperationResult): WritableMap =
|
|
1029
|
+
Arguments.createMap().apply {
|
|
1030
|
+
putBoolean("success", result.success)
|
|
1031
|
+
putString("message", result.message)
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
private fun operationResultMap(result: HostConnectionResult): WritableMap =
|
|
1035
|
+
Arguments.createMap().apply {
|
|
1036
|
+
putBoolean("success", result.success)
|
|
1037
|
+
putString("message", result.message)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
private fun optionsMap(options: AnsightOptions): WritableMap =
|
|
1041
|
+
Arguments.createMap().apply {
|
|
1042
|
+
putInt("sampleFrequencyMilliseconds", options.sampleFrequencyMilliseconds)
|
|
1043
|
+
putInt("retentionPeriodSeconds", options.retentionPeriodSeconds)
|
|
1044
|
+
putBoolean("enableFramesPerSecond", options.enableFramesPerSecond)
|
|
1045
|
+
putBoolean("enableBatteryLevel", options.enableBatteryLevel)
|
|
1046
|
+
putMap("defaultMemoryChannels", mapOf(
|
|
1047
|
+
"managedHeap" to options.defaultMemoryChannels.javaHeap,
|
|
1048
|
+
"javaHeap" to options.defaultMemoryChannels.javaHeap,
|
|
1049
|
+
"nativeHeap" to options.defaultMemoryChannels.nativeHeap,
|
|
1050
|
+
"residentSetSize" to options.defaultMemoryChannels.rss,
|
|
1051
|
+
"rss" to options.defaultMemoryChannels.rss,
|
|
1052
|
+
"physicalFootprint" to false,
|
|
1053
|
+
).toWritableMap())
|
|
1054
|
+
putMap("reactNativeMemory", currentReactNativeMemoryOptions.toMap().toWritableMap())
|
|
1055
|
+
putArray("additionalChannels", channelsArray(options.additionalChannels))
|
|
1056
|
+
options.sessionJpegCapture?.let { capture ->
|
|
1057
|
+
putMap("sessionJpegCapture", mapOf(
|
|
1058
|
+
"intervalMilliseconds" to capture.intervalMilliseconds,
|
|
1059
|
+
"quality" to capture.quality,
|
|
1060
|
+
"maxWidth" to capture.maxWidth,
|
|
1061
|
+
).toWritableMap())
|
|
1062
|
+
} ?: putNull("sessionJpegCapture")
|
|
1063
|
+
options.touchCapture?.let { touch ->
|
|
1064
|
+
putMap("touchCapture", mapOf(
|
|
1065
|
+
"moveCaptureDistanceThreshold" to touch.moveCaptureDistanceThreshold,
|
|
1066
|
+
"moveCaptureFramesPerSecond" to touch.moveCaptureFramesPerSecond,
|
|
1067
|
+
).toWritableMap())
|
|
1068
|
+
} ?: putNull("touchCapture")
|
|
1069
|
+
putString("toolGuard", toolGuardName(options.toolGuard))
|
|
1070
|
+
putMap("customProperties", options.customProperties.toGroupedWritableMap())
|
|
1071
|
+
putMap("hostAutoProbe", mapOf(
|
|
1072
|
+
"enabled" to options.hostAutoProbe.enabled,
|
|
1073
|
+
"initialDelayMilliseconds" to options.hostAutoProbe.initialDelayMilliseconds,
|
|
1074
|
+
"probeIntervalMilliseconds" to options.hostAutoProbe.probeIntervalMilliseconds,
|
|
1075
|
+
"reconnectDelayMilliseconds" to options.hostAutoProbe.reconnectDelayMilliseconds,
|
|
1076
|
+
"clientName" to options.hostAutoProbe.clientName,
|
|
1077
|
+
).toWritableMap())
|
|
1078
|
+
putMap("hostConnection", mapOf(
|
|
1079
|
+
"savedConfigKey" to options.hostConnection.savedConfigKey,
|
|
1080
|
+
"hasBundledConfigJson" to (options.hostConnection.bundledConfigJson != null),
|
|
1081
|
+
"hasBundledDeveloperConfigJson" to (options.hostConnection.bundledDeveloperConfigJson != null),
|
|
1082
|
+
"discoveryPort" to options.hostConnection.discoveryPort,
|
|
1083
|
+
"connectionProfileRetentionSeconds" to options.hostConnection.connectionProfileRetentionSeconds,
|
|
1084
|
+
).toWritableMap())
|
|
1085
|
+
putMap("secureStorage", mapOf(
|
|
1086
|
+
"preferencesName" to options.secureStorage.preferencesName,
|
|
1087
|
+
"allowedKeys" to options.secureStorage.allowedKeys.sorted().joinToString(","),
|
|
1088
|
+
"allowedPrefixes" to options.secureStorage.allowedPrefixes.sorted().joinToString(","),
|
|
1089
|
+
).toWritableMap())
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
private fun channelMap(channel: AnsightChannel): WritableMap =
|
|
1093
|
+
Arguments.createMap().apply {
|
|
1094
|
+
putInt("id", channel.id)
|
|
1095
|
+
putString("name", channel.name)
|
|
1096
|
+
putString("unit", channel.unit)
|
|
1097
|
+
putString("type", channel.type)
|
|
1098
|
+
putString("colorHex", channel.colorHex)
|
|
1099
|
+
putString("source", channel.source)
|
|
1100
|
+
putString("group", channel.group)
|
|
1101
|
+
putString("kind", channel.kind)
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private fun channelsArray(channels: List<AnsightChannel>): WritableArray =
|
|
1105
|
+
Arguments.createArray().apply {
|
|
1106
|
+
channels.forEach { pushMap(channelMap(it)) }
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
private fun metricsArray(metrics: List<RecordedMetric>): WritableArray =
|
|
1110
|
+
Arguments.createArray().apply {
|
|
1111
|
+
metrics.forEach { pushMap(metricMap(it)) }
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
private fun eventsArray(events: List<RecordedEvent>): WritableArray =
|
|
1115
|
+
Arguments.createArray().apply {
|
|
1116
|
+
events.forEach { pushMap(eventMap(it)) }
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
private fun metricMap(metric: RecordedMetric): WritableMap =
|
|
1120
|
+
Arguments.createMap().apply {
|
|
1121
|
+
putDouble("value", metric.value.toDouble())
|
|
1122
|
+
putString("capturedAtUtc", metric.capturedAtUtc)
|
|
1123
|
+
putDouble("capturedAtEpochMs", metric.capturedAtEpochMs.toDouble())
|
|
1124
|
+
putInt("channel", metric.channel)
|
|
1125
|
+
putDouble("sequence", metric.sequence.toDouble())
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
private fun eventMap(event: RecordedEvent): WritableMap =
|
|
1129
|
+
Arguments.createMap().apply {
|
|
1130
|
+
putString("id", event.id)
|
|
1131
|
+
putString("label", event.label)
|
|
1132
|
+
putString("type", event.type.wireName)
|
|
1133
|
+
putString("details", event.details)
|
|
1134
|
+
putString("capturedAtUtc", event.capturedAtUtc)
|
|
1135
|
+
putDouble("capturedAtEpochMs", event.capturedAtEpochMs.toDouble())
|
|
1136
|
+
putString("externalId", event.externalId)
|
|
1137
|
+
putInt("channel", event.channel)
|
|
1138
|
+
putDouble("sequence", event.sequence.toDouble())
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
private fun application(): Application =
|
|
1142
|
+
reactContext.applicationContext as? Application
|
|
1143
|
+
?: error("React Native application context is not an Android Application.")
|
|
1144
|
+
|
|
1145
|
+
private fun bindCurrentActivity() {
|
|
1146
|
+
val activity: Activity = currentActivity ?: return
|
|
1147
|
+
AnsightRuntime.bindActivity(activity)
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
private fun <T> Result<T>.resolve(promise: Promise) {
|
|
1152
|
+
fold(
|
|
1153
|
+
onSuccess = { promise.resolve(it) },
|
|
1154
|
+
onFailure = { promise.reject("ansight_error", it.message, it) },
|
|
1155
|
+
)
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
private fun eventType(raw: String?): ai.ansight.runtime.AnsightEventType =
|
|
1159
|
+
when (raw?.trim()?.lowercase()) {
|
|
1160
|
+
"event" -> ai.ansight.runtime.AnsightEventType.Event
|
|
1161
|
+
"debug" -> ai.ansight.runtime.AnsightEventType.Debug
|
|
1162
|
+
"warning", "warn" -> ai.ansight.runtime.AnsightEventType.Warning
|
|
1163
|
+
"error" -> ai.ansight.runtime.AnsightEventType.Error
|
|
1164
|
+
"exception" -> ai.ansight.runtime.AnsightEventType.Exception
|
|
1165
|
+
"gc" -> ai.ansight.runtime.AnsightEventType.Gc
|
|
1166
|
+
"navigation" -> ai.ansight.runtime.AnsightEventType.Navigation
|
|
1167
|
+
"screenviewed", "screen_viewed" -> ai.ansight.runtime.AnsightEventType.ScreenViewed
|
|
1168
|
+
"lifecycle" -> ai.ansight.runtime.AnsightEventType.Lifecycle
|
|
1169
|
+
else -> ai.ansight.runtime.AnsightEventType.Info
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
private fun lifecycleState(raw: String): AppLifecycleState =
|
|
1173
|
+
when (raw.trim().lowercase()) {
|
|
1174
|
+
"foreground", "active" -> AppLifecycleState.Foreground
|
|
1175
|
+
"background", "inactive" -> AppLifecycleState.Background
|
|
1176
|
+
else -> AppLifecycleState.Unknown
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
private fun toolGuard(raw: String?): AnsightToolGuard =
|
|
1180
|
+
when (raw?.trim()?.lowercase()) {
|
|
1181
|
+
"readonly", "read_only", "read" -> AnsightToolGuard.ReadOnly
|
|
1182
|
+
"readwrite", "read_write", "write" -> AnsightToolGuard.ReadWrite
|
|
1183
|
+
"full", "fullaccess", "full_access" -> AnsightToolGuard.FullAccess
|
|
1184
|
+
else -> AnsightToolGuard.Disabled
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
private fun toolGuardName(guard: AnsightToolGuard): String =
|
|
1188
|
+
when (guard) {
|
|
1189
|
+
AnsightToolGuard.Disabled -> "disabled"
|
|
1190
|
+
AnsightToolGuard.ReadOnly -> "readOnly"
|
|
1191
|
+
AnsightToolGuard.ReadWrite -> "readWrite"
|
|
1192
|
+
AnsightToolGuard.FullAccess -> "fullAccess"
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
private fun toolScope(raw: String?): ToolScope =
|
|
1196
|
+
when (raw?.trim()?.lowercase()) {
|
|
1197
|
+
"write" -> ToolScope.Write
|
|
1198
|
+
"delete" -> ToolScope.Delete
|
|
1199
|
+
else -> ToolScope.Read
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
private fun ReadableMap?.hasKey(name: String): Boolean = this?.hasKey(name) == true
|
|
1203
|
+
private fun ReadableMap?.hasString(name: String): Boolean = this?.hasKey(name) == true && this.getType(name) == ReadableType.String
|
|
1204
|
+
private fun ReadableMap?.hasNumber(name: String): Boolean = this?.hasKey(name) == true && this.getType(name) == ReadableType.Number
|
|
1205
|
+
private fun ReadableMap?.hasBoolean(name: String): Boolean = this?.hasKey(name) == true && this.getType(name) == ReadableType.Boolean
|
|
1206
|
+
private fun ReadableMap?.hasMap(name: String): Boolean = this?.hasKey(name) == true && this.getType(name) == ReadableType.Map
|
|
1207
|
+
private fun ReadableMap?.hasArray(name: String): Boolean = this?.hasKey(name) == true && this.getType(name) == ReadableType.Array
|
|
1208
|
+
private fun ReadableMap?.isFalse(name: String): Boolean = this?.hasBoolean(name) == true && !this.getBoolean(name)
|
|
1209
|
+
private fun ReadableMap?.toolSuiteEnabled(name: String): Boolean {
|
|
1210
|
+
val map = this ?: return false
|
|
1211
|
+
if (!map.hasKey(name) || map.isNull(name)) {
|
|
1212
|
+
return false
|
|
1213
|
+
}
|
|
1214
|
+
return when (map.getType(name)) {
|
|
1215
|
+
ReadableType.Boolean -> map.getBoolean(name)
|
|
1216
|
+
ReadableType.Map -> map.getMap(name).booleanValue("enabled", true)
|
|
1217
|
+
else -> false
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
private fun ReadableMap?.stringValue(name: String): String? =
|
|
1221
|
+
if (this?.hasKey(name) == true && !isNull(name) && getType(name) == ReadableType.String) getString(name)?.trim()?.ifBlank { null } else null
|
|
1222
|
+
private fun ReadableMap?.booleanValue(name: String, default: Boolean): Boolean =
|
|
1223
|
+
if (this?.hasKey(name) == true && !isNull(name) && getType(name) == ReadableType.Boolean) getBoolean(name) else default
|
|
1224
|
+
private fun ReadableMap?.doubleValue(name: String, default: Double): Double =
|
|
1225
|
+
if (this?.hasKey(name) == true && !isNull(name) && getType(name) == ReadableType.Number) getDouble(name) else default
|
|
1226
|
+
private fun ReadableMap?.intValue(name: String, default: Int): Int =
|
|
1227
|
+
if (this?.hasKey(name) == true && !isNull(name) && getType(name) == ReadableType.Number) getDouble(name).toInt() else default
|
|
1228
|
+
private fun ReadableMap?.longValue(name: String, default: Long): Long =
|
|
1229
|
+
if (this?.hasKey(name) == true && !isNull(name) && getType(name) == ReadableType.Number) getDouble(name).toLong() else default
|
|
1230
|
+
private fun ReadableMap?.optionalInt(name: String): Int? =
|
|
1231
|
+
if (this?.hasKey(name) == true && !isNull(name) && getType(name) == ReadableType.Number) getDouble(name).toInt() else null
|
|
1232
|
+
private fun ReadableMap?.getMapOrNull(name: String): ReadableMap? =
|
|
1233
|
+
if (this?.hasKey(name) == true && !isNull(name) && getType(name) == ReadableType.Map) getMap(name) else null
|
|
1234
|
+
private fun ReadableMap?.getArrayOrNull(name: String): ReadableArray? =
|
|
1235
|
+
if (this?.hasKey(name) == true && !isNull(name) && getType(name) == ReadableType.Array) getArray(name) else null
|
|
1236
|
+
|
|
1237
|
+
private fun ReadableMap?.toStringMap(): Map<String, String> {
|
|
1238
|
+
if (this == null) {
|
|
1239
|
+
return emptyMap()
|
|
1240
|
+
}
|
|
1241
|
+
val result = linkedMapOf<String, String>()
|
|
1242
|
+
val iterator = keySetIterator()
|
|
1243
|
+
while (iterator.hasNextKey()) {
|
|
1244
|
+
val key = iterator.nextKey()
|
|
1245
|
+
if (!isNull(key)) {
|
|
1246
|
+
result[key] = when (getType(key)) {
|
|
1247
|
+
ReadableType.String -> getString(key).orEmpty()
|
|
1248
|
+
ReadableType.Number -> getDouble(key).toString()
|
|
1249
|
+
ReadableType.Boolean -> getBoolean(key).toString()
|
|
1250
|
+
else -> ""
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
return result
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
private fun ReadableMap?.toGroupedStringMap(): Map<String, Map<String, String>> {
|
|
1258
|
+
if (this == null) {
|
|
1259
|
+
return emptyMap()
|
|
1260
|
+
}
|
|
1261
|
+
val result = linkedMapOf<String, Map<String, String>>()
|
|
1262
|
+
val iterator = keySetIterator()
|
|
1263
|
+
while (iterator.hasNextKey()) {
|
|
1264
|
+
val key = iterator.nextKey()
|
|
1265
|
+
if (!isNull(key) && getType(key) == ReadableType.Map) {
|
|
1266
|
+
result[key] = getMap(key).toStringMap()
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return result
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
private fun ReadableMap?.getStringSet(name: String): Set<String> =
|
|
1273
|
+
getArrayOrNull(name).toStringList().toSet()
|
|
1274
|
+
|
|
1275
|
+
private fun ReadableArray?.toStringList(): List<String> {
|
|
1276
|
+
if (this == null) {
|
|
1277
|
+
return emptyList()
|
|
1278
|
+
}
|
|
1279
|
+
val result = mutableListOf<String>()
|
|
1280
|
+
for (index in 0 until size()) {
|
|
1281
|
+
if (!isNull(index)) {
|
|
1282
|
+
result += when (getType(index)) {
|
|
1283
|
+
ReadableType.String -> getString(index).orEmpty()
|
|
1284
|
+
ReadableType.Number -> getDouble(index).toString()
|
|
1285
|
+
ReadableType.Boolean -> getBoolean(index).toString()
|
|
1286
|
+
else -> ""
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return result.filter { it.isNotBlank() }
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
private fun ReadableMap.toJSONObject(): JSONObject {
|
|
1294
|
+
val result = JSONObject()
|
|
1295
|
+
val iterator = keySetIterator()
|
|
1296
|
+
while (iterator.hasNextKey()) {
|
|
1297
|
+
val key = iterator.nextKey()
|
|
1298
|
+
if (isNull(key)) {
|
|
1299
|
+
result.put(key, JSONObject.NULL)
|
|
1300
|
+
} else {
|
|
1301
|
+
when (getType(key)) {
|
|
1302
|
+
ReadableType.Map -> result.put(key, getMap(key)?.toJSONObject() ?: JSONObject.NULL)
|
|
1303
|
+
ReadableType.Array -> result.put(key, getArray(key)?.toJSONArray() ?: JSONObject.NULL)
|
|
1304
|
+
ReadableType.String -> result.put(key, getString(key))
|
|
1305
|
+
ReadableType.Number -> result.put(key, getDouble(key))
|
|
1306
|
+
ReadableType.Boolean -> result.put(key, getBoolean(key))
|
|
1307
|
+
else -> result.put(key, JSONObject.NULL)
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return result
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
private fun ReadableArray.toJSONArray(): JSONArray {
|
|
1315
|
+
val result = JSONArray()
|
|
1316
|
+
for (index in 0 until size()) {
|
|
1317
|
+
if (isNull(index)) {
|
|
1318
|
+
result.put(JSONObject.NULL)
|
|
1319
|
+
} else {
|
|
1320
|
+
when (getType(index)) {
|
|
1321
|
+
ReadableType.Map -> result.put(getMap(index)?.toJSONObject() ?: JSONObject.NULL)
|
|
1322
|
+
ReadableType.Array -> result.put(getArray(index)?.toJSONArray() ?: JSONObject.NULL)
|
|
1323
|
+
ReadableType.String -> result.put(getString(index))
|
|
1324
|
+
ReadableType.Number -> result.put(getDouble(index))
|
|
1325
|
+
ReadableType.Boolean -> result.put(getBoolean(index))
|
|
1326
|
+
else -> result.put(JSONObject.NULL)
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
return result
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
private fun Map<String, *>.toWritableMap(): WritableMap =
|
|
1334
|
+
Arguments.createMap().also { map ->
|
|
1335
|
+
entries.sortedBy { it.key }.forEach { (key, value) ->
|
|
1336
|
+
when (value) {
|
|
1337
|
+
is Boolean -> map.putBoolean(key, value)
|
|
1338
|
+
is Int -> map.putInt(key, value)
|
|
1339
|
+
is Double -> map.putDouble(key, value)
|
|
1340
|
+
is Number -> map.putDouble(key, value.toDouble())
|
|
1341
|
+
is String -> map.putString(key, value)
|
|
1342
|
+
null -> map.putNull(key)
|
|
1343
|
+
else -> map.putString(key, value.toString())
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
private fun Map<String, Map<String, String>>.toGroupedWritableMap(): WritableMap =
|
|
1349
|
+
Arguments.createMap().also { map ->
|
|
1350
|
+
entries.sortedBy { it.key }.forEach { (key, value) ->
|
|
1351
|
+
map.putMap(key, value.toWritableMap())
|
|
1352
|
+
}
|
|
1353
|
+
}
|