@ansight/capacitor 1.0.2-preview.6
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/AnsightCapacitor.podspec +14 -0
- package/LICENSE +200 -0
- package/Package.swift +38 -0
- package/README.md +139 -0
- package/android/build.gradle +52 -0
- package/android/settings.gradle +20 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/ai/ansight/capacitor/AnsightCapacitorPlugin.kt +1131 -0
- package/dist/esm/definitions.d.ts +521 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/dom.d.ts +9 -0
- package/dist/esm/dom.d.ts.map +1 -0
- package/dist/esm/dom.js +237 -0
- package/dist/esm/dom.js.map +1 -0
- package/dist/esm/index.d.ts +156 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +576 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/options.d.ts +57 -0
- package/dist/esm/options.d.ts.map +1 -0
- package/dist/esm/options.js +294 -0
- package/dist/esm/options.js.map +1 -0
- package/dist/esm/standalone.d.ts +5 -0
- package/dist/esm/standalone.d.ts.map +1 -0
- package/dist/esm/standalone.js +18 -0
- package/dist/esm/standalone.js.map +1 -0
- package/dist/plugin.cjs.js +1176 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +1179 -0
- package/dist/plugin.js.map +1 -0
- package/dist/standalone.js +1764 -0
- package/dist/standalone.js.map +1 -0
- package/ios/Sources/AnsightCapacitorPlugin/AnsightCapacitorPlugin.swift +1144 -0
- package/package.json +81 -0
|
@@ -0,0 +1,1131 @@
|
|
|
1
|
+
package ai.ansight.capacitor
|
|
2
|
+
|
|
3
|
+
import ai.ansight.Ansight
|
|
4
|
+
import ai.ansight.runtime.AndroidToolExecutionContext
|
|
5
|
+
import ai.ansight.runtime.AndroidToolResult
|
|
6
|
+
import ai.ansight.runtime.AnsightChannel
|
|
7
|
+
import ai.ansight.runtime.AnsightChannels
|
|
8
|
+
import ai.ansight.runtime.AnsightDeveloperMode
|
|
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.AnsightRuntime
|
|
16
|
+
import ai.ansight.runtime.AnsightSecureStorageOptions
|
|
17
|
+
import ai.ansight.runtime.AnsightSessionJpegCaptureOptions
|
|
18
|
+
import ai.ansight.runtime.AnsightToolGuard
|
|
19
|
+
import ai.ansight.runtime.AnsightTouchCaptureOptions
|
|
20
|
+
import ai.ansight.runtime.AppLifecycleState
|
|
21
|
+
import ai.ansight.runtime.DefaultMemoryChannels
|
|
22
|
+
import ai.ansight.runtime.FunctionAndroidTool
|
|
23
|
+
import ai.ansight.runtime.HostConnectionCapabilities
|
|
24
|
+
import ai.ansight.runtime.HostConnectionActionKind
|
|
25
|
+
import ai.ansight.runtime.HostConnectionRequest
|
|
26
|
+
import ai.ansight.runtime.HostConnectionRequestKind
|
|
27
|
+
import ai.ansight.runtime.HostConnectionResult
|
|
28
|
+
import ai.ansight.runtime.HostConnectionSource
|
|
29
|
+
import ai.ansight.runtime.HostConnectionStatus
|
|
30
|
+
import ai.ansight.runtime.OperationResult
|
|
31
|
+
import ai.ansight.runtime.OpenSessionResult
|
|
32
|
+
import ai.ansight.runtime.PairingFileTransferWireProtocol
|
|
33
|
+
import ai.ansight.runtime.PairingOpenOptions
|
|
34
|
+
import ai.ansight.runtime.RecordedEvent
|
|
35
|
+
import ai.ansight.runtime.RecordedMetric
|
|
36
|
+
import ai.ansight.runtime.ToolDefinition
|
|
37
|
+
import ai.ansight.runtime.ToolSchema
|
|
38
|
+
import ai.ansight.runtime.ToolScope
|
|
39
|
+
import ai.ansight.runtime.ToolSecurity
|
|
40
|
+
import ai.ansight.runtime.ToolSecurityLevel
|
|
41
|
+
import ai.ansight.runtime.sendBinaryTransfer
|
|
42
|
+
import ai.ansight.tools.database.AndroidDatabaseRoot
|
|
43
|
+
import ai.ansight.tools.database.AndroidDatabaseToolsOptions
|
|
44
|
+
import ai.ansight.tools.database.withDatabaseTools
|
|
45
|
+
import ai.ansight.tools.filesystem.AndroidFileSystemRoot
|
|
46
|
+
import ai.ansight.tools.filesystem.AndroidFileSystemToolsOptions
|
|
47
|
+
import ai.ansight.tools.filesystem.withFileSystemTools
|
|
48
|
+
import ai.ansight.tools.preferences.AndroidPreferencesToolsOptions
|
|
49
|
+
import ai.ansight.tools.preferences.withPreferencesTools
|
|
50
|
+
import ai.ansight.tools.reflection.AndroidReflectionToolsOptions
|
|
51
|
+
import ai.ansight.tools.reflection.withReflectionTools
|
|
52
|
+
import ai.ansight.tools.securestorage.withSecureStorageTools
|
|
53
|
+
import ai.ansight.tools.visualtree.withVisualTreeTools
|
|
54
|
+
import ai.ansight.pairing.AnsightPairing
|
|
55
|
+
import android.app.Application
|
|
56
|
+
import android.os.Handler
|
|
57
|
+
import android.os.Looper
|
|
58
|
+
import android.util.Base64
|
|
59
|
+
import com.getcapacitor.JSObject
|
|
60
|
+
import com.getcapacitor.Plugin
|
|
61
|
+
import com.getcapacitor.PluginCall
|
|
62
|
+
import com.getcapacitor.PluginMethod
|
|
63
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
64
|
+
import org.json.JSONArray
|
|
65
|
+
import org.json.JSONObject
|
|
66
|
+
import java.util.Locale
|
|
67
|
+
import java.util.UUID
|
|
68
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
69
|
+
import java.util.concurrent.CountDownLatch
|
|
70
|
+
import java.util.concurrent.Executors
|
|
71
|
+
import java.util.concurrent.TimeUnit
|
|
72
|
+
|
|
73
|
+
@CapacitorPlugin(name = "Ansight")
|
|
74
|
+
class AnsightCapacitorPlugin : Plugin() {
|
|
75
|
+
private data class PendingToolCall(
|
|
76
|
+
val context: AndroidToolExecutionContext,
|
|
77
|
+
val latch: CountDownLatch = CountDownLatch(1),
|
|
78
|
+
@Volatile var result: AndroidToolResult? = null,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
private data class CustomToolRegistration(
|
|
82
|
+
val definition: ToolDefinition,
|
|
83
|
+
val timeoutMilliseconds: Long,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
private val executor = Executors.newCachedThreadPool()
|
|
87
|
+
private val pendingToolCalls = ConcurrentHashMap<String, PendingToolCall>()
|
|
88
|
+
private val customToolRegistrations = ConcurrentHashMap<String, CustomToolRegistration>()
|
|
89
|
+
private val activeCustomToolIds = ConcurrentHashMap.newKeySet<String>()
|
|
90
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
91
|
+
private val logCallback = AnsightLogCallback { level, message, throwable ->
|
|
92
|
+
val event = JSObject()
|
|
93
|
+
.putValue("level", level.name.lowercase(Locale.US))
|
|
94
|
+
.putValue("message", message)
|
|
95
|
+
.putValue("platform", "android")
|
|
96
|
+
throwable?.message?.let { event.put("error", it) }
|
|
97
|
+
mainHandler.post { notifyListeners("ansightLog", event) }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
override fun load() {
|
|
101
|
+
AnsightLogger.registerCallback(logCallback)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
override fun handleOnDestroy() {
|
|
105
|
+
AnsightLogger.removeCallback(logCallback)
|
|
106
|
+
executor.shutdownNow()
|
|
107
|
+
super.handleOnDestroy()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@PluginMethod
|
|
111
|
+
fun initialize(call: PluginCall) = resolve(call) {
|
|
112
|
+
Ansight.initialize(application(), buildOptions(call.data))
|
|
113
|
+
installRegisteredCustomTools()
|
|
114
|
+
snapshot()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@PluginMethod
|
|
118
|
+
fun initializeAndActivate(call: PluginCall) = resolve(call) {
|
|
119
|
+
Ansight.initializeAndActivate(application(), buildOptions(call.data))
|
|
120
|
+
bindCurrentActivity()
|
|
121
|
+
installRegisteredCustomTools()
|
|
122
|
+
snapshot()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@PluginMethod
|
|
126
|
+
fun activate(call: PluginCall) = resolve(call) {
|
|
127
|
+
AnsightRuntime.activate()
|
|
128
|
+
bindCurrentActivity()
|
|
129
|
+
snapshot()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@PluginMethod
|
|
133
|
+
fun deactivate(call: PluginCall) = resolve(call) {
|
|
134
|
+
AnsightRuntime.deactivate()
|
|
135
|
+
snapshot()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@PluginMethod
|
|
139
|
+
fun clear(call: PluginCall) = resolve(call) {
|
|
140
|
+
AnsightRuntime.clear()
|
|
141
|
+
snapshot()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@PluginMethod
|
|
145
|
+
fun registerMetricChannel(call: PluginCall) = resolve(call) {
|
|
146
|
+
val channel = call.data.objectValue("channel")
|
|
147
|
+
AnsightRuntime.registerMetricChannel(
|
|
148
|
+
AnsightChannel(
|
|
149
|
+
id = channel.intValue("id", -1),
|
|
150
|
+
name = channel.stringValue("name") ?: "",
|
|
151
|
+
unit = channel.stringValue("unit"),
|
|
152
|
+
type = channel.stringValue("type") ?: "custom",
|
|
153
|
+
colorHex = channel.stringValue("colorHex"),
|
|
154
|
+
source = channel.stringValue("source"),
|
|
155
|
+
group = channel.stringValue("group"),
|
|
156
|
+
kind = channel.stringValue("kind"),
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
snapshot()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@PluginMethod
|
|
163
|
+
fun recordMetric(call: PluginCall) = resolve(call) {
|
|
164
|
+
AnsightRuntime.metric(
|
|
165
|
+
call.data.doubleValue("value", 0.0).toLong(),
|
|
166
|
+
call.data.intValue("channel", AnsightChannels.Unspecified),
|
|
167
|
+
)
|
|
168
|
+
snapshot()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@PluginMethod
|
|
172
|
+
fun recordEvent(call: PluginCall) = resolve(call) {
|
|
173
|
+
AnsightRuntime.event(
|
|
174
|
+
label = call.getString("label").orEmpty(),
|
|
175
|
+
type = eventType(call.getString("type")),
|
|
176
|
+
details = call.getString("details"),
|
|
177
|
+
channel = call.data.intValue("channel", AnsightChannels.Unspecified),
|
|
178
|
+
)
|
|
179
|
+
snapshot()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@PluginMethod
|
|
183
|
+
fun screenViewed(call: PluginCall) = resolve(call) {
|
|
184
|
+
AnsightRuntime.screenViewed(
|
|
185
|
+
call.getString("name").orEmpty(),
|
|
186
|
+
call.data.objectValue("details").toStringMap(),
|
|
187
|
+
)
|
|
188
|
+
snapshot()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@PluginMethod
|
|
192
|
+
fun setAppLifecycleState(call: PluginCall) = resolve(call) {
|
|
193
|
+
AnsightRuntime.setAppLifecycleState(lifecycleState(call.getString("state").orEmpty()))
|
|
194
|
+
snapshot()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@PluginMethod
|
|
198
|
+
fun connect(call: PluginCall) = background(call) {
|
|
199
|
+
val pairingPayload = call.getString("pairingPayload")
|
|
200
|
+
val request = HostConnectionRequest(
|
|
201
|
+
kind = if (pairingPayload.isNullOrBlank()) HostConnectionRequestKind.Auto else HostConnectionRequestKind.Payload,
|
|
202
|
+
payload = pairingPayload,
|
|
203
|
+
clientName = call.getString("clientName"),
|
|
204
|
+
expectedAppId = call.getString("expectedAppId"),
|
|
205
|
+
hostAddressOverride = call.getString("hostAddressOverride"),
|
|
206
|
+
)
|
|
207
|
+
hostConnectionResult(AnsightRuntime.connect(request))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@PluginMethod
|
|
211
|
+
fun scanPairingQrCode(call: PluginCall) {
|
|
212
|
+
val currentActivity = activity
|
|
213
|
+
if (currentActivity == null) {
|
|
214
|
+
call.reject(
|
|
215
|
+
"QR pairing is unavailable because no Android activity is available.",
|
|
216
|
+
"ansight_qr_unavailable",
|
|
217
|
+
)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
AnsightPairing.scanQrCode(
|
|
222
|
+
activity = currentActivity,
|
|
223
|
+
onPayload = { payload ->
|
|
224
|
+
if (payload.isNullOrBlank()) {
|
|
225
|
+
call.resolve(
|
|
226
|
+
hostConnectionResult(
|
|
227
|
+
HostConnectionResult.failure(
|
|
228
|
+
message = "QR pairing canceled.",
|
|
229
|
+
kind = HostConnectionActionKind.Connect,
|
|
230
|
+
source = HostConnectionSource.ConfigReader,
|
|
231
|
+
reasonCode = "pairing_canceled",
|
|
232
|
+
),
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
return@scanQrCode
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
executor.execute {
|
|
239
|
+
resolve(call) {
|
|
240
|
+
hostConnectionResult(
|
|
241
|
+
AnsightRuntime.connect(
|
|
242
|
+
HostConnectionRequest(
|
|
243
|
+
kind = HostConnectionRequestKind.QrCode,
|
|
244
|
+
payload = payload,
|
|
245
|
+
clientName = call.getString("clientName"),
|
|
246
|
+
expectedAppId = call.getString("expectedAppId"),
|
|
247
|
+
hostAddressOverride = call.getString("hostAddressOverride"),
|
|
248
|
+
),
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
onError = {
|
|
255
|
+
call.reject(
|
|
256
|
+
it.message ?: "QR pairing failed.",
|
|
257
|
+
"ansight_qr_error",
|
|
258
|
+
it as? Exception,
|
|
259
|
+
)
|
|
260
|
+
},
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@PluginMethod
|
|
265
|
+
fun openSession(call: PluginCall) = background(call) {
|
|
266
|
+
openSessionResult(
|
|
267
|
+
AnsightRuntime.openSession(
|
|
268
|
+
call.getString("pairingPayload").orEmpty(),
|
|
269
|
+
pairingOpenOptions(call.data),
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@PluginMethod
|
|
275
|
+
fun disconnect(call: PluginCall) = background(call) {
|
|
276
|
+
hostConnectionResult(AnsightRuntime.disconnect())
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@PluginMethod
|
|
280
|
+
fun completeSession(call: PluginCall) = background(call) {
|
|
281
|
+
AnsightRuntime.completeSession()
|
|
282
|
+
operationResult(OperationResult.success("Session completed."))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@PluginMethod
|
|
286
|
+
fun closeSession(call: PluginCall) = background(call) {
|
|
287
|
+
AnsightRuntime.closeSession()
|
|
288
|
+
operationResult(OperationResult.success("Session closed."))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@PluginMethod
|
|
292
|
+
fun savePairingConfig(call: PluginCall) = background(call) {
|
|
293
|
+
hostConnectionResult(
|
|
294
|
+
AnsightRuntime.savePairingConfig(
|
|
295
|
+
call.getString("pairingPayload").orEmpty(),
|
|
296
|
+
call.getString("expectedAppId"),
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@PluginMethod
|
|
302
|
+
fun clearSavedPairing(call: PluginCall) = resolve(call) {
|
|
303
|
+
hostConnectionResult(AnsightRuntime.clearSavedPairingConfig())
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@PluginMethod
|
|
307
|
+
fun clearCachedSession(call: PluginCall) = resolve(call) {
|
|
308
|
+
operationResult(AnsightRuntime.clearCachedSession())
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@PluginMethod
|
|
312
|
+
fun notifyHostConnectionConfigChanged(call: PluginCall) = resolve(call) {
|
|
313
|
+
hostConnectionResult(AnsightRuntime.notifyHostConnectionConfigChanged())
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@PluginMethod
|
|
317
|
+
fun status(call: PluginCall) = resolve(call) { snapshot() }
|
|
318
|
+
|
|
319
|
+
@PluginMethod
|
|
320
|
+
fun snapshot(call: PluginCall) = resolve(call) { snapshot() }
|
|
321
|
+
|
|
322
|
+
@PluginMethod
|
|
323
|
+
fun hostConnectionStatus(call: PluginCall) = resolve(call) {
|
|
324
|
+
hostConnectionStatus(AnsightRuntime.hostConnectionStatus())
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@PluginMethod
|
|
328
|
+
fun hostConnectionCapabilities(call: PluginCall) = resolve(call) {
|
|
329
|
+
hostConnectionCapabilities(AnsightRuntime.hostConnectionCapabilities())
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@PluginMethod
|
|
333
|
+
fun currentOptions(call: PluginCall) = resolve(call) {
|
|
334
|
+
options(AnsightRuntime.options())
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
@PluginMethod
|
|
338
|
+
fun recordedMetrics(call: PluginCall) = resolve(call) {
|
|
339
|
+
val metrics = AnsightRuntime.recordedMetrics()
|
|
340
|
+
val limit = call.data.intValue("limit", 0)
|
|
341
|
+
JSObject().putValue(
|
|
342
|
+
"items",
|
|
343
|
+
JSONArray(metrics.takeLast(if (limit > 0) limit else metrics.size).map(::metric)),
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
@PluginMethod
|
|
348
|
+
fun recordedEvents(call: PluginCall) = resolve(call) {
|
|
349
|
+
val events = AnsightRuntime.recordedEvents()
|
|
350
|
+
val limit = call.data.intValue("limit", 0)
|
|
351
|
+
JSObject().putValue(
|
|
352
|
+
"items",
|
|
353
|
+
JSONArray(events.takeLast(if (limit > 0) limit else events.size).map(::event)),
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
@PluginMethod
|
|
358
|
+
fun sendClientLog(call: PluginCall) = resolve(call) {
|
|
359
|
+
operationResult(AnsightRuntime.sendClientLog(call.getString("line").orEmpty()))
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
@PluginMethod
|
|
363
|
+
fun captureBuiltInTelemetrySample(call: PluginCall) = resolve(call) {
|
|
364
|
+
AnsightRuntime.captureBuiltInTelemetrySample()
|
|
365
|
+
snapshot()
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@PluginMethod
|
|
369
|
+
fun isFramesPerSecondEnabled(call: PluginCall) = resolve(call) {
|
|
370
|
+
JSObject().putValue("value", AnsightRuntime.isFramesPerSecondEnabled())
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@PluginMethod
|
|
374
|
+
fun enableFramesPerSecond(call: PluginCall) = resolve(call) {
|
|
375
|
+
AnsightRuntime.enableFramesPerSecond()
|
|
376
|
+
snapshot()
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
@PluginMethod
|
|
380
|
+
fun disableFramesPerSecond(call: PluginCall) = resolve(call) {
|
|
381
|
+
AnsightRuntime.disableFramesPerSecond()
|
|
382
|
+
snapshot()
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@PluginMethod
|
|
386
|
+
fun captureScreenFrame(call: PluginCall) = background(call) {
|
|
387
|
+
bindCurrentActivity()
|
|
388
|
+
operationResult(AnsightRuntime.captureScreenFrame(sessionJpegCaptureOptions(call.data)))
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
@PluginMethod
|
|
392
|
+
fun enableTouchCapture(call: PluginCall) = resolve(call) {
|
|
393
|
+
operationResult(AnsightRuntime.enableTouchCapture())
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
@PluginMethod
|
|
397
|
+
fun disableTouchCapture(call: PluginCall) = resolve(call) {
|
|
398
|
+
operationResult(AnsightRuntime.disableTouchCapture())
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@PluginMethod
|
|
402
|
+
fun updateSessionProperties(call: PluginCall) = resolve(call) {
|
|
403
|
+
operationResult(
|
|
404
|
+
AnsightRuntime.updateCustomProperties(
|
|
405
|
+
call.data.objectValue("properties").toGroupedStringMap(),
|
|
406
|
+
),
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@PluginMethod
|
|
411
|
+
fun clearSessionProperties(call: PluginCall) = resolve(call) {
|
|
412
|
+
operationResult(AnsightRuntime.clearCustomProperties())
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
@PluginMethod
|
|
416
|
+
fun registerCustomProperty(call: PluginCall) = resolve(call) {
|
|
417
|
+
operationResult(
|
|
418
|
+
AnsightRuntime.registerCustomProperty(
|
|
419
|
+
call.getString("group").orEmpty(),
|
|
420
|
+
call.getString("key").orEmpty(),
|
|
421
|
+
call.getString("value").orEmpty(),
|
|
422
|
+
),
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@PluginMethod
|
|
427
|
+
fun removeCustomProperty(call: PluginCall) = resolve(call) {
|
|
428
|
+
operationResult(
|
|
429
|
+
AnsightRuntime.removeCustomProperty(
|
|
430
|
+
call.getString("group").orEmpty(),
|
|
431
|
+
call.getString("key").orEmpty(),
|
|
432
|
+
),
|
|
433
|
+
)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
@PluginMethod
|
|
437
|
+
fun registerCustomTool(call: PluginCall) = resolve(call) {
|
|
438
|
+
val map = call.data.objectValue("definition")
|
|
439
|
+
val definition = toolDefinition(map)
|
|
440
|
+
val registration = CustomToolRegistration(
|
|
441
|
+
definition,
|
|
442
|
+
map.intValue("timeoutMilliseconds", 30_000).toLong().coerceAtLeast(250L),
|
|
443
|
+
)
|
|
444
|
+
customToolRegistrations[definition.id] = registration
|
|
445
|
+
activeCustomToolIds.add(definition.id)
|
|
446
|
+
if (AnsightRuntime.snapshot().initialized) installCustomTool(registration)
|
|
447
|
+
operationResult(OperationResult.success("Tool registered."))
|
|
448
|
+
.putValue("id", definition.id)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
@PluginMethod
|
|
452
|
+
fun unregisterCustomTool(call: PluginCall) = resolve(call) {
|
|
453
|
+
val id = call.getString("id").orEmpty().trim()
|
|
454
|
+
activeCustomToolIds.remove(id)
|
|
455
|
+
customToolRegistrations.remove(id)
|
|
456
|
+
operationResult(OperationResult.success("Tool unregistered.")).putValue("id", id)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
@PluginMethod
|
|
460
|
+
fun clearRegisteredCustomTools(call: PluginCall) = resolve(call) {
|
|
461
|
+
activeCustomToolIds.clear()
|
|
462
|
+
customToolRegistrations.clear()
|
|
463
|
+
operationResult(OperationResult.success("JavaScript tools cleared."))
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
@PluginMethod
|
|
467
|
+
fun resolveToolCall(call: PluginCall) {
|
|
468
|
+
val requestId = call.getString("requestId").orEmpty()
|
|
469
|
+
val pending = pendingToolCalls[requestId]
|
|
470
|
+
if (pending == null) {
|
|
471
|
+
call.resolve(
|
|
472
|
+
operationResult(OperationResult.failure("Tool request is no longer pending."))
|
|
473
|
+
.putValue("accepted", false),
|
|
474
|
+
)
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
val result = call.data.objectValue("result")
|
|
478
|
+
val payload = resultPayload(result)
|
|
479
|
+
pending.result = if (result.booleanValue("success", true)) {
|
|
480
|
+
AndroidToolResult.success(payload, result.stringValue("message"))
|
|
481
|
+
} else {
|
|
482
|
+
AndroidToolResult.failure(
|
|
483
|
+
result.stringValue("message") ?: "JavaScript tool failed.",
|
|
484
|
+
result.stringValue("errorCode"),
|
|
485
|
+
payload,
|
|
486
|
+
)
|
|
487
|
+
}
|
|
488
|
+
pending.latch.countDown()
|
|
489
|
+
call.resolve(
|
|
490
|
+
operationResult(OperationResult.success("Tool result accepted."))
|
|
491
|
+
.putValue("accepted", true),
|
|
492
|
+
)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
@PluginMethod
|
|
496
|
+
fun queueBinaryTransfer(call: PluginCall) = resolve(call) {
|
|
497
|
+
val requestId = call.getString("requestId").orEmpty().trim()
|
|
498
|
+
val pending = pendingToolCalls[requestId]
|
|
499
|
+
?: return@resolve operationResult(
|
|
500
|
+
OperationResult.failure("Binary transfer requires an active JavaScript tool request."),
|
|
501
|
+
).putValue("errorCode", "artifact_request_unavailable")
|
|
502
|
+
val transport = pending.context.transport
|
|
503
|
+
?: return@resolve operationResult(
|
|
504
|
+
OperationResult.failure("Binary transfers require an active pairing session."),
|
|
505
|
+
).putValue("errorCode", "artifact_transfer_unavailable")
|
|
506
|
+
val bytes = Base64.decode(call.getString("base64Data").orEmpty(), Base64.DEFAULT)
|
|
507
|
+
val chunkBytes = call.data.intValue("chunkBytes", 65_536).coerceIn(1_024, 512 * 1_024)
|
|
508
|
+
val transferId = PairingFileTransferWireProtocol.newTransferId()
|
|
509
|
+
executor.execute { transport.sendBinaryTransfer(transferId, bytes, chunkBytes) }
|
|
510
|
+
operationResult(OperationResult.success("Binary transfer queued."))
|
|
511
|
+
.putValue("transferId", transferId)
|
|
512
|
+
.putValue("deliveryMode", "websocket_binary")
|
|
513
|
+
.putValue("wireProtocol", PairingFileTransferWireProtocol.ProtocolName)
|
|
514
|
+
.putValue("status", "queued")
|
|
515
|
+
.putValue("chunkBytes", chunkBytes)
|
|
516
|
+
.putValue("sizeBytes", bytes.size)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private fun executeJavaScriptTool(
|
|
520
|
+
toolId: String,
|
|
521
|
+
arguments: Map<String, String>,
|
|
522
|
+
context: AndroidToolExecutionContext,
|
|
523
|
+
timeoutMilliseconds: Long,
|
|
524
|
+
): AndroidToolResult {
|
|
525
|
+
if (toolId !in activeCustomToolIds) {
|
|
526
|
+
return AndroidToolResult.failure(
|
|
527
|
+
"Tool '$toolId' is no longer registered in JavaScript.",
|
|
528
|
+
"javascript_tool_unregistered",
|
|
529
|
+
)
|
|
530
|
+
}
|
|
531
|
+
val requestId = "android.capacitor.${UUID.randomUUID().toString().replace("-", "")}"
|
|
532
|
+
val pending = PendingToolCall(context)
|
|
533
|
+
pendingToolCalls[requestId] = pending
|
|
534
|
+
val event = JSObject()
|
|
535
|
+
.putValue("requestId", requestId)
|
|
536
|
+
.putValue("toolId", toolId)
|
|
537
|
+
.putValue("platform", "android")
|
|
538
|
+
.putValue("sessionId", context.sessionId)
|
|
539
|
+
.putValue("nativeRequestId", context.requestId)
|
|
540
|
+
.putValue("arguments", arguments.toJSObject())
|
|
541
|
+
mainHandler.post { notifyListeners("ansightToolCall", event) }
|
|
542
|
+
val completed = pending.latch.await(timeoutMilliseconds, TimeUnit.MILLISECONDS)
|
|
543
|
+
pendingToolCalls.remove(requestId)
|
|
544
|
+
if (!completed) {
|
|
545
|
+
return AndroidToolResult.failure(
|
|
546
|
+
"JavaScript handler for '$toolId' timed out.",
|
|
547
|
+
"javascript_tool_timeout",
|
|
548
|
+
)
|
|
549
|
+
}
|
|
550
|
+
return pending.result ?: AndroidToolResult.failure(
|
|
551
|
+
"JavaScript handler returned no result.",
|
|
552
|
+
"javascript_tool_result_missing",
|
|
553
|
+
)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
private fun installRegisteredCustomTools() {
|
|
557
|
+
customToolRegistrations.values.forEach(::installCustomTool)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
private fun installCustomTool(registration: CustomToolRegistration) {
|
|
561
|
+
activeCustomToolIds.add(registration.definition.id)
|
|
562
|
+
AnsightRuntime.registerTool(
|
|
563
|
+
FunctionAndroidTool(registration.definition) { arguments, context ->
|
|
564
|
+
executeJavaScriptTool(
|
|
565
|
+
registration.definition.id,
|
|
566
|
+
arguments,
|
|
567
|
+
context,
|
|
568
|
+
registration.timeoutMilliseconds,
|
|
569
|
+
)
|
|
570
|
+
},
|
|
571
|
+
replaceExisting = true,
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private fun buildOptions(map: JSObject): AnsightOptions {
|
|
576
|
+
val useDefaults = map.booleanValue("useNativeAllInOneDefaults", false)
|
|
577
|
+
val pairingConfigJson = map.stringValue("pairingConfigJson")
|
|
578
|
+
var options = if (useDefaults) {
|
|
579
|
+
AnsightDeveloperMode.options(
|
|
580
|
+
bundledDeveloperConfigJson = pairingConfigJson,
|
|
581
|
+
clientName = map.stringValue("clientName"),
|
|
582
|
+
)
|
|
583
|
+
} else {
|
|
584
|
+
AnsightOptions()
|
|
585
|
+
}
|
|
586
|
+
if (!useDefaults && !pairingConfigJson.isNullOrBlank()) {
|
|
587
|
+
options = options.copy(
|
|
588
|
+
hostConnection = options.hostConnection.copy(bundledConfigJson = pairingConfigJson),
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
if (map.has("sampleFrequencyMilliseconds")) {
|
|
592
|
+
options = options.copy(
|
|
593
|
+
sampleFrequencyMilliseconds = map.intValue(
|
|
594
|
+
"sampleFrequencyMilliseconds",
|
|
595
|
+
options.sampleFrequencyMilliseconds,
|
|
596
|
+
),
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
if (map.has("retentionPeriodSeconds")) {
|
|
600
|
+
options = options.copy(
|
|
601
|
+
retentionPeriodSeconds = map.intValue(
|
|
602
|
+
"retentionPeriodSeconds",
|
|
603
|
+
options.retentionPeriodSeconds,
|
|
604
|
+
),
|
|
605
|
+
)
|
|
606
|
+
}
|
|
607
|
+
if (map.has("enableFramesPerSecond")) {
|
|
608
|
+
options = options.copy(
|
|
609
|
+
enableFramesPerSecond = map.booleanValue(
|
|
610
|
+
"enableFramesPerSecond",
|
|
611
|
+
options.enableFramesPerSecond,
|
|
612
|
+
),
|
|
613
|
+
)
|
|
614
|
+
}
|
|
615
|
+
if (map.has("enableBatteryLevel")) {
|
|
616
|
+
options = options.copy(
|
|
617
|
+
enableBatteryLevel = map.booleanValue("enableBatteryLevel", options.enableBatteryLevel),
|
|
618
|
+
)
|
|
619
|
+
}
|
|
620
|
+
map.objectValueOrNull("defaultMemoryChannels")?.let { memory ->
|
|
621
|
+
options = options.copy(
|
|
622
|
+
defaultMemoryChannels = DefaultMemoryChannels(
|
|
623
|
+
javaHeap = memory.booleanValue(
|
|
624
|
+
"managedHeap",
|
|
625
|
+
memory.booleanValue("javaHeap", options.defaultMemoryChannels.javaHeap),
|
|
626
|
+
),
|
|
627
|
+
nativeHeap = memory.booleanValue(
|
|
628
|
+
"nativeHeap",
|
|
629
|
+
options.defaultMemoryChannels.nativeHeap,
|
|
630
|
+
),
|
|
631
|
+
rss = memory.booleanValue(
|
|
632
|
+
"residentSetSize",
|
|
633
|
+
memory.booleanValue("rss", options.defaultMemoryChannels.rss),
|
|
634
|
+
),
|
|
635
|
+
),
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
map.arrayValue("additionalChannels")?.let { channels ->
|
|
639
|
+
options = options.copy(
|
|
640
|
+
additionalChannels = (0 until channels.length()).mapNotNull { index ->
|
|
641
|
+
channels.optJSONObject(index)?.let(::channel)
|
|
642
|
+
},
|
|
643
|
+
)
|
|
644
|
+
}
|
|
645
|
+
if (map.has("sessionJpegCapture")) {
|
|
646
|
+
options = options.copy(
|
|
647
|
+
sessionJpegCapture = if (map.opt("sessionJpegCapture") == false) {
|
|
648
|
+
null
|
|
649
|
+
} else {
|
|
650
|
+
sessionJpegCaptureOptions(map.objectValue("sessionJpegCapture"))
|
|
651
|
+
},
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
if (map.has("touchCapture")) {
|
|
655
|
+
options = options.copy(
|
|
656
|
+
touchCapture = if (map.opt("touchCapture") == false) {
|
|
657
|
+
null
|
|
658
|
+
} else {
|
|
659
|
+
val touch = map.objectValue("touchCapture")
|
|
660
|
+
AnsightTouchCaptureOptions(
|
|
661
|
+
moveCaptureDistanceThreshold = touch.doubleValue(
|
|
662
|
+
"moveCaptureDistanceThreshold",
|
|
663
|
+
8.0,
|
|
664
|
+
),
|
|
665
|
+
moveCaptureFramesPerSecond = touch.intValue(
|
|
666
|
+
"moveCaptureFramesPerSecond",
|
|
667
|
+
20,
|
|
668
|
+
),
|
|
669
|
+
)
|
|
670
|
+
},
|
|
671
|
+
)
|
|
672
|
+
}
|
|
673
|
+
map.stringValue("toolGuard")?.let { options = options.copy(toolGuard = toolGuard(it)) }
|
|
674
|
+
map.objectValueOrNull("customProperties")?.let {
|
|
675
|
+
options = options.copy(customProperties = it.toGroupedStringMap())
|
|
676
|
+
}
|
|
677
|
+
map.objectValueOrNull("hostAutoProbe")?.let { autoProbe ->
|
|
678
|
+
options = options.copy(
|
|
679
|
+
hostAutoProbe = AnsightHostAutoProbeOptions(
|
|
680
|
+
enabled = autoProbe.booleanValue("enabled", options.hostAutoProbe.enabled),
|
|
681
|
+
initialDelayMilliseconds = autoProbe.longValue(
|
|
682
|
+
"initialDelayMilliseconds",
|
|
683
|
+
options.hostAutoProbe.initialDelayMilliseconds,
|
|
684
|
+
),
|
|
685
|
+
probeIntervalMilliseconds = autoProbe.longValue(
|
|
686
|
+
"probeIntervalMilliseconds",
|
|
687
|
+
options.hostAutoProbe.probeIntervalMilliseconds,
|
|
688
|
+
),
|
|
689
|
+
reconnectDelayMilliseconds = autoProbe.longValue(
|
|
690
|
+
"reconnectDelayMilliseconds",
|
|
691
|
+
options.hostAutoProbe.reconnectDelayMilliseconds,
|
|
692
|
+
),
|
|
693
|
+
clientName = autoProbe.stringValue("clientName") ?: options.hostAutoProbe.clientName,
|
|
694
|
+
),
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
map.objectValueOrNull("hostConnection")?.let { host ->
|
|
698
|
+
options = options.copy(
|
|
699
|
+
hostConnection = AnsightHostConnectionOptions(
|
|
700
|
+
savedConfigKey = host.stringValue("savedConfigKey")
|
|
701
|
+
?: options.hostConnection.savedConfigKey,
|
|
702
|
+
bundledConfigJson = host.stringValue("bundledConfigJson")
|
|
703
|
+
?: options.hostConnection.bundledConfigJson,
|
|
704
|
+
bundledDeveloperConfigJson = host.stringValue("bundledDeveloperConfigJson")
|
|
705
|
+
?: options.hostConnection.bundledDeveloperConfigJson,
|
|
706
|
+
discoveryPort = host.optionalInt("discoveryPort")
|
|
707
|
+
?: options.hostConnection.discoveryPort,
|
|
708
|
+
connectionProfileRetentionSeconds = host.longValue(
|
|
709
|
+
"connectionProfileRetentionSeconds",
|
|
710
|
+
options.hostConnection.connectionProfileRetentionSeconds,
|
|
711
|
+
),
|
|
712
|
+
),
|
|
713
|
+
)
|
|
714
|
+
}
|
|
715
|
+
map.objectValueOrNull("secureStorage")?.let { secure ->
|
|
716
|
+
options = options.copy(
|
|
717
|
+
secureStorage = AnsightSecureStorageOptions(
|
|
718
|
+
preferencesName = secure.stringValue("preferencesName")
|
|
719
|
+
?: options.secureStorage.preferencesName,
|
|
720
|
+
allowedKeys = secure.stringSet("allowedKeys"),
|
|
721
|
+
allowedPrefixes = secure.stringSet("allowedPrefixes"),
|
|
722
|
+
),
|
|
723
|
+
)
|
|
724
|
+
}
|
|
725
|
+
return options.withNativeTools(map, useDefaults)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private fun AnsightOptions.withNativeTools(map: JSObject, enableVisualTreeByDefault: Boolean): AnsightOptions {
|
|
729
|
+
val remote = map.objectValue("remoteTools")
|
|
730
|
+
val builder = AnsightOptions.createBuilder(this)
|
|
731
|
+
if (remote.toolSuiteEnabled("visualTree", enableVisualTreeByDefault)) {
|
|
732
|
+
builder.withVisualTreeTools()
|
|
733
|
+
}
|
|
734
|
+
val database = remote.objectValue("database")
|
|
735
|
+
builder.withDatabaseTools(
|
|
736
|
+
AndroidDatabaseToolsOptions(
|
|
737
|
+
additionalRoots = roots(database.arrayValue("additionalRoots")).map {
|
|
738
|
+
AndroidDatabaseRoot(it.first, it.second)
|
|
739
|
+
},
|
|
740
|
+
includePlatformRoots = database.booleanValue("includePlatformRoots", true),
|
|
741
|
+
).validated(),
|
|
742
|
+
)
|
|
743
|
+
val files = remote.objectValue("fileSystem")
|
|
744
|
+
builder.withFileSystemTools(
|
|
745
|
+
AndroidFileSystemToolsOptions(
|
|
746
|
+
additionalRoots = roots(files.arrayValue("additionalRoots")).map {
|
|
747
|
+
AndroidFileSystemRoot(it.first, it.second)
|
|
748
|
+
},
|
|
749
|
+
).validated(),
|
|
750
|
+
)
|
|
751
|
+
val preferences = remote.objectValue("preferences")
|
|
752
|
+
builder.withPreferencesTools(
|
|
753
|
+
AndroidPreferencesToolsOptions(
|
|
754
|
+
defaultStore = preferences.stringValue("defaultStore"),
|
|
755
|
+
allowedStores = preferences.stringSet("allowedStores"),
|
|
756
|
+
allowedKeys = preferences.stringSet("allowedKeys"),
|
|
757
|
+
allowedKeyPrefixes = preferences.stringSet("allowedKeyPrefixes"),
|
|
758
|
+
).validated(),
|
|
759
|
+
)
|
|
760
|
+
val reflection = remote.objectValue("reflection")
|
|
761
|
+
builder.withReflectionTools(
|
|
762
|
+
AndroidReflectionToolsOptions(
|
|
763
|
+
includeBuiltInRoots = reflection.booleanValue("includeBuiltInRoots", true),
|
|
764
|
+
allowedRootIds = reflection.stringSet("allowedRootIds"),
|
|
765
|
+
allowedTypePrefixes = reflection.stringSet("allowedTypePrefixes"),
|
|
766
|
+
).validated(),
|
|
767
|
+
)
|
|
768
|
+
val secure = remote.objectValueOrNull("secureStorage") ?: map.objectValue("secureStorage")
|
|
769
|
+
builder.withSecureStorageTools(
|
|
770
|
+
AnsightSecureStorageOptions(
|
|
771
|
+
preferencesName = secure.stringValue("preferencesName") ?: secureStorage.preferencesName,
|
|
772
|
+
allowedKeys = secure.stringSet("allowedKeys"),
|
|
773
|
+
allowedPrefixes = secure.stringSet("allowedKeyPrefixes") + secure.stringSet("allowedPrefixes"),
|
|
774
|
+
).validated(),
|
|
775
|
+
)
|
|
776
|
+
return builder.build()
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
private fun toolDefinition(map: JSObject): ToolDefinition =
|
|
780
|
+
ToolDefinition(
|
|
781
|
+
id = map.stringValue("id") ?: "",
|
|
782
|
+
name = map.stringValue("name") ?: map.stringValue("id") ?: "",
|
|
783
|
+
description = map.stringValue("description") ?: "",
|
|
784
|
+
category = map.stringValue("category") ?: "custom",
|
|
785
|
+
scope = toolScope(map.stringValue("scope")),
|
|
786
|
+
keywords = map.stringValue("keywords")
|
|
787
|
+
?: map.stringList("keywords").joinToString(" ").ifBlank { "capacitor javascript custom tool" },
|
|
788
|
+
argumentsSchema = schema(map.objectValueOrNull("argumentsSchema")),
|
|
789
|
+
resultSchema = schema(map.objectValueOrNull("resultSchema")),
|
|
790
|
+
security = toolSecurity(map.objectValueOrNull("security")),
|
|
791
|
+
).validated()
|
|
792
|
+
|
|
793
|
+
private fun schema(map: JSObject?): ToolSchema {
|
|
794
|
+
if (map == null) return ToolSchema.obj(additionalProperties = true)
|
|
795
|
+
val properties = mutableMapOf<String, ToolSchema>()
|
|
796
|
+
map.objectValueOrNull("properties")?.let { values ->
|
|
797
|
+
values.keys().forEach { key ->
|
|
798
|
+
values.objectValueOrNull(key)?.let { properties[key] = schema(it) }
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return ToolSchema(
|
|
802
|
+
type = map.stringValue("type") ?: map.stringList("type").firstOrNull { it != "null" } ?: "object",
|
|
803
|
+
description = map.stringValue("description"),
|
|
804
|
+
properties = properties,
|
|
805
|
+
required = map.stringList("required"),
|
|
806
|
+
items = map.objectValueOrNull("items")?.let(::schema),
|
|
807
|
+
enumValues = map.stringList("enum"),
|
|
808
|
+
additionalProperties = map.booleanValue("additionalProperties", false),
|
|
809
|
+
nullable = "null" in map.stringList("type"),
|
|
810
|
+
format = map.stringValue("format"),
|
|
811
|
+
)
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
private fun toolSecurity(map: JSObject?): ToolSecurity {
|
|
815
|
+
if (map == null) return ToolSecurity.Unspecified
|
|
816
|
+
return ToolSecurity(
|
|
817
|
+
level = when (map.stringValue("level")?.lowercase()) {
|
|
818
|
+
"medium", "moderate" -> ToolSecurityLevel.Medium
|
|
819
|
+
"high" -> ToolSecurityLevel.High
|
|
820
|
+
"critical" -> ToolSecurityLevel.Critical
|
|
821
|
+
else -> ToolSecurityLevel.Low
|
|
822
|
+
},
|
|
823
|
+
implications = map.stringList("implications"),
|
|
824
|
+
)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
private fun resultPayload(map: JSObject): JSONObject? {
|
|
828
|
+
if (!map.has("result") || map.isNull("result")) return null
|
|
829
|
+
return when (val value = map.opt("result")) {
|
|
830
|
+
is JSONObject -> value
|
|
831
|
+
is JSONArray -> JSONObject().put("value", value)
|
|
832
|
+
else -> JSONObject().put("value", value)
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
private fun snapshot(): JSObject {
|
|
837
|
+
val value = AnsightRuntime.snapshot()
|
|
838
|
+
return JSObject()
|
|
839
|
+
.putValue("initialized", value.initialized)
|
|
840
|
+
.putValue("active", value.active)
|
|
841
|
+
.putValue("sessionOpen", value.sessionOpen)
|
|
842
|
+
.putValue("lifecycleState", value.lifecycleState.wireName)
|
|
843
|
+
.putValue("lifecycleChangedAtUtc", value.lifecycleChangedAtUtc)
|
|
844
|
+
.putValue("metricsRecorded", value.metricsRecorded)
|
|
845
|
+
.putValue("eventsRecorded", value.eventsRecorded)
|
|
846
|
+
.putValue("touchesRecorded", value.touchesRecorded)
|
|
847
|
+
.putValue("registeredTools", value.registeredTools)
|
|
848
|
+
.putValue("sessionMessage", value.sessionMessage)
|
|
849
|
+
.putValue("connectionStatus", hostConnectionStatus(value.connectionStatus))
|
|
850
|
+
.putValue("channels", JSONArray(value.channels.map(::channel)))
|
|
851
|
+
.apply {
|
|
852
|
+
value.lastMetric?.let { put("lastMetric", metric(it)) }
|
|
853
|
+
value.lastEvent?.let { put("lastEvent", event(it)) }
|
|
854
|
+
value.currentScreen?.let {
|
|
855
|
+
put(
|
|
856
|
+
"currentScreen",
|
|
857
|
+
JSObject()
|
|
858
|
+
.putValue("name", it.name)
|
|
859
|
+
.putValue("capturedAtUtc", it.capturedAtUtc)
|
|
860
|
+
.putValue("details", it.details.toJSObject()),
|
|
861
|
+
)
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
private fun hostConnectionStatus(value: HostConnectionStatus): JSObject =
|
|
867
|
+
JSObject()
|
|
868
|
+
.putValue("isRuntimeActive", value.isRuntimeActive)
|
|
869
|
+
.putValue("isConnected", value.isConnected)
|
|
870
|
+
.putValue("connectionState", value.connectionState.name)
|
|
871
|
+
.putValue("hasCachedSession", value.hasCachedSession)
|
|
872
|
+
.putValue("hasSavedConfig", value.hasSavedConfig)
|
|
873
|
+
.putValue("hasBundledConfig", value.hasBundledConfig)
|
|
874
|
+
.putValue("summaryKind", value.summaryKind.name)
|
|
875
|
+
.putValue("summaryMessage", value.summaryMessage)
|
|
876
|
+
|
|
877
|
+
private fun hostConnectionCapabilities(value: HostConnectionCapabilities): JSObject =
|
|
878
|
+
JSObject()
|
|
879
|
+
.putValue("canConnectUsingSavedConfig", value.canConnectUsingSavedConfig)
|
|
880
|
+
.putValue("canConnectUsingBundledConfig", value.canConnectUsingBundledConfig)
|
|
881
|
+
.putValue("canChooseConfigFile", value.canChooseConfigFile)
|
|
882
|
+
.putValue("canScanConfigQrCode", value.canScanConfigQrCode)
|
|
883
|
+
.putValue("canClearSavedConfigs", value.canClearSavedConfigs)
|
|
884
|
+
|
|
885
|
+
private fun openSessionResult(value: OpenSessionResult): JSObject =
|
|
886
|
+
operationResult(OperationResult(value.success, value.message))
|
|
887
|
+
.putValue("accepted", value.accepted)
|
|
888
|
+
.putValue("sessionId", value.sessionId)
|
|
889
|
+
.putValue("configId", value.configId)
|
|
890
|
+
.putValue("appId", value.appId)
|
|
891
|
+
.putValue("resolvedHostAddress", value.resolvedHostAddress)
|
|
892
|
+
.putValue("usedEmbeddedDeveloperPairing", value.usedEmbeddedDeveloperPairing)
|
|
893
|
+
.putValue("discoverySource", value.discoverySource)
|
|
894
|
+
.putValue("reasonCode", value.reasonCode)
|
|
895
|
+
.putValue("hostId", value.hostId)
|
|
896
|
+
.putValue("hostName", value.hostName)
|
|
897
|
+
|
|
898
|
+
private fun hostConnectionResult(value: HostConnectionResult): JSObject =
|
|
899
|
+
JSObject()
|
|
900
|
+
.putValue("success", value.success)
|
|
901
|
+
.putValue("message", value.message)
|
|
902
|
+
.putValue("kind", value.kind.name)
|
|
903
|
+
.putValue("source", value.source.name)
|
|
904
|
+
.putValue("reasonCode", value.reasonCode ?: value.openSession?.reasonCode)
|
|
905
|
+
.apply {
|
|
906
|
+
value.openSession?.let {
|
|
907
|
+
put("sessionId", it.sessionId)
|
|
908
|
+
put("configId", it.configId)
|
|
909
|
+
put("appId", it.appId)
|
|
910
|
+
put("resolvedHostAddress", it.resolvedHostAddress)
|
|
911
|
+
put("hostId", it.hostId)
|
|
912
|
+
put("hostName", it.hostName)
|
|
913
|
+
put("accepted", it.accepted)
|
|
914
|
+
put("usedEmbeddedDeveloperPairing", it.usedEmbeddedDeveloperPairing)
|
|
915
|
+
put("discoverySource", it.discoverySource)
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
private fun operationResult(value: OperationResult): JSObject =
|
|
920
|
+
JSObject().putValue("success", value.success).putValue("message", value.message)
|
|
921
|
+
|
|
922
|
+
private fun options(value: AnsightOptions): JSObject =
|
|
923
|
+
JSObject()
|
|
924
|
+
.putValue("sampleFrequencyMilliseconds", value.sampleFrequencyMilliseconds)
|
|
925
|
+
.putValue("retentionPeriodSeconds", value.retentionPeriodSeconds)
|
|
926
|
+
.putValue("enableFramesPerSecond", value.enableFramesPerSecond)
|
|
927
|
+
.putValue("enableBatteryLevel", value.enableBatteryLevel)
|
|
928
|
+
.putValue("toolGuard", toolGuardName(value.toolGuard))
|
|
929
|
+
.putValue("customProperties", value.customProperties.toGroupedJSObject())
|
|
930
|
+
.putValue("additionalChannels", JSONArray(value.additionalChannels.map(::channel)))
|
|
931
|
+
|
|
932
|
+
private fun channel(value: JSONObject): AnsightChannel =
|
|
933
|
+
AnsightChannel(
|
|
934
|
+
id = value.optInt("id", -1),
|
|
935
|
+
name = value.optString("name"),
|
|
936
|
+
unit = value.stringValue("unit"),
|
|
937
|
+
type = value.stringValue("type") ?: "custom",
|
|
938
|
+
colorHex = value.stringValue("colorHex"),
|
|
939
|
+
source = value.stringValue("source"),
|
|
940
|
+
group = value.stringValue("group"),
|
|
941
|
+
kind = value.stringValue("kind"),
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
private fun channel(value: AnsightChannel): JSObject =
|
|
945
|
+
JSObject()
|
|
946
|
+
.putValue("id", value.id)
|
|
947
|
+
.putValue("name", value.name)
|
|
948
|
+
.putValue("unit", value.unit)
|
|
949
|
+
.putValue("type", value.type)
|
|
950
|
+
.putValue("colorHex", value.colorHex)
|
|
951
|
+
.putValue("source", value.source)
|
|
952
|
+
.putValue("group", value.group)
|
|
953
|
+
.putValue("kind", value.kind)
|
|
954
|
+
|
|
955
|
+
private fun metric(value: RecordedMetric): JSObject =
|
|
956
|
+
JSObject()
|
|
957
|
+
.putValue("value", value.value)
|
|
958
|
+
.putValue("capturedAtUtc", value.capturedAtUtc)
|
|
959
|
+
.putValue("capturedAtEpochMs", value.capturedAtEpochMs)
|
|
960
|
+
.putValue("channel", value.channel)
|
|
961
|
+
.putValue("sequence", value.sequence)
|
|
962
|
+
|
|
963
|
+
private fun event(value: RecordedEvent): JSObject =
|
|
964
|
+
JSObject()
|
|
965
|
+
.putValue("id", value.id)
|
|
966
|
+
.putValue("label", value.label)
|
|
967
|
+
.putValue("type", value.type.wireName)
|
|
968
|
+
.putValue("details", value.details)
|
|
969
|
+
.putValue("capturedAtUtc", value.capturedAtUtc)
|
|
970
|
+
.putValue("capturedAtEpochMs", value.capturedAtEpochMs)
|
|
971
|
+
.putValue("externalId", value.externalId)
|
|
972
|
+
.putValue("channel", value.channel)
|
|
973
|
+
.putValue("sequence", value.sequence)
|
|
974
|
+
|
|
975
|
+
private fun pairingOpenOptions(map: JSObject): PairingOpenOptions =
|
|
976
|
+
PairingOpenOptions(
|
|
977
|
+
clientName = map.stringValue("clientName") ?: "Capacitor",
|
|
978
|
+
expectedAppId = map.stringValue("expectedAppId"),
|
|
979
|
+
hostAddressOverride = map.stringValue("hostAddressOverride"),
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
private fun sessionJpegCaptureOptions(map: JSObject): AnsightSessionJpegCaptureOptions =
|
|
983
|
+
AnsightSessionJpegCaptureOptions(
|
|
984
|
+
intervalMilliseconds = map.intValue(
|
|
985
|
+
"intervalMilliseconds",
|
|
986
|
+
AnsightSessionJpegCaptureOptions.DefaultIntervalMilliseconds,
|
|
987
|
+
),
|
|
988
|
+
quality = map.intValue("quality", AnsightSessionJpegCaptureOptions.DefaultQuality),
|
|
989
|
+
maxWidth = map.optionalInt("maxWidth") ?: AnsightSessionJpegCaptureOptions.DefaultMaxWidth,
|
|
990
|
+
captureGpuBackedSurfaces = map.booleanValue(
|
|
991
|
+
"captureGpuBackedSurfaces",
|
|
992
|
+
AnsightSessionJpegCaptureOptions.DefaultCaptureGpuBackedSurfaces,
|
|
993
|
+
),
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
private fun application(): Application =
|
|
997
|
+
context.applicationContext as? Application
|
|
998
|
+
?: error("Capacitor application context is not an Android Application.")
|
|
999
|
+
|
|
1000
|
+
private fun bindCurrentActivity() {
|
|
1001
|
+
activity?.let { AnsightRuntime.bindActivity(it) }
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
private fun resolve(call: PluginCall, block: () -> JSObject) {
|
|
1005
|
+
runCatching(block).fold(
|
|
1006
|
+
onSuccess = call::resolve,
|
|
1007
|
+
onFailure = { call.reject(it.message ?: "Ansight operation failed.", "ansight_error", it as? Exception) },
|
|
1008
|
+
)
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
private fun background(call: PluginCall, block: () -> JSObject) {
|
|
1012
|
+
executor.execute { resolve(call, block) }
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
private fun JSObject.putValue(key: String, value: Any?): JSObject = apply {
|
|
1017
|
+
if (value == null) put(key, JSONObject.NULL) else put(key, value)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
private fun JSObject.stringValue(key: String): String? =
|
|
1021
|
+
optString(key).takeIf { has(key) && !isNull(key) && it.isNotBlank() }
|
|
1022
|
+
|
|
1023
|
+
private fun JSONObject.stringValue(key: String): String? =
|
|
1024
|
+
optString(key).takeIf { has(key) && !isNull(key) && it.isNotBlank() }
|
|
1025
|
+
|
|
1026
|
+
private fun JSObject.booleanValue(key: String, default: Boolean): Boolean =
|
|
1027
|
+
if (has(key) && !isNull(key)) optBoolean(key, default) else default
|
|
1028
|
+
|
|
1029
|
+
private fun JSObject.intValue(key: String, default: Int): Int =
|
|
1030
|
+
if (has(key) && !isNull(key)) optInt(key, default) else default
|
|
1031
|
+
|
|
1032
|
+
private fun JSObject.longValue(key: String, default: Long): Long =
|
|
1033
|
+
if (has(key) && !isNull(key)) optLong(key, default) else default
|
|
1034
|
+
|
|
1035
|
+
private fun JSObject.doubleValue(key: String, default: Double): Double =
|
|
1036
|
+
if (has(key) && !isNull(key)) optDouble(key, default) else default
|
|
1037
|
+
|
|
1038
|
+
private fun JSObject.optionalInt(key: String): Int? =
|
|
1039
|
+
if (has(key) && !isNull(key)) optInt(key) else null
|
|
1040
|
+
|
|
1041
|
+
private fun JSObject.objectValue(key: String): JSObject =
|
|
1042
|
+
objectValueOrNull(key) ?: JSObject()
|
|
1043
|
+
|
|
1044
|
+
private fun JSObject.objectValueOrNull(key: String): JSObject? {
|
|
1045
|
+
val value = optJSONObject(key) ?: return null
|
|
1046
|
+
return if (value is JSObject) value else JSObject(value.toString())
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
private fun JSObject.arrayValue(key: String): JSONArray? = optJSONArray(key)
|
|
1050
|
+
|
|
1051
|
+
private fun JSObject.stringList(key: String): List<String> {
|
|
1052
|
+
val array = optJSONArray(key) ?: return emptyList()
|
|
1053
|
+
return (0 until array.length()).mapNotNull { array.optString(it).takeIf(String::isNotBlank) }
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
private fun JSObject.stringSet(key: String): Set<String> = stringList(key).toSet()
|
|
1057
|
+
|
|
1058
|
+
private fun JSObject.toolSuiteEnabled(key: String, default: Boolean): Boolean =
|
|
1059
|
+
when (val value = opt(key)) {
|
|
1060
|
+
is Boolean -> value
|
|
1061
|
+
is JSONObject -> value.optBoolean("enabled", true)
|
|
1062
|
+
else -> default
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
private fun JSObject.toStringMap(): Map<String, String> =
|
|
1066
|
+
keys().asSequence().associateWith { key -> opt(key)?.takeUnless { it == JSONObject.NULL }?.toString().orEmpty() }
|
|
1067
|
+
|
|
1068
|
+
private fun JSObject.toGroupedStringMap(): Map<String, Map<String, String>> =
|
|
1069
|
+
keys().asSequence().mapNotNull { key ->
|
|
1070
|
+
objectValueOrNull(key)?.let { key to it.toStringMap() }
|
|
1071
|
+
}.toMap()
|
|
1072
|
+
|
|
1073
|
+
private fun Map<String, *>.toJSObject(): JSObject =
|
|
1074
|
+
JSObject().also { result -> forEach { (key, value) -> result.putValue(key, value) } }
|
|
1075
|
+
|
|
1076
|
+
private fun Map<String, Map<String, String>>.toGroupedJSObject(): JSObject =
|
|
1077
|
+
JSObject().also { result -> forEach { (key, value) -> result.put(key, value.toJSObject()) } }
|
|
1078
|
+
|
|
1079
|
+
private fun roots(array: JSONArray?): List<Pair<String, String>> {
|
|
1080
|
+
if (array == null) return emptyList()
|
|
1081
|
+
return (0 until array.length()).mapNotNull { index ->
|
|
1082
|
+
val value = array.optJSONObject(index) ?: return@mapNotNull null
|
|
1083
|
+
val alias = value.stringValue("alias") ?: return@mapNotNull null
|
|
1084
|
+
val path = value.stringValue("path") ?: return@mapNotNull null
|
|
1085
|
+
alias to path
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
private fun eventType(raw: String?): ai.ansight.runtime.AnsightEventType =
|
|
1090
|
+
when (raw?.trim()?.lowercase()) {
|
|
1091
|
+
"event" -> ai.ansight.runtime.AnsightEventType.Event
|
|
1092
|
+
"debug" -> ai.ansight.runtime.AnsightEventType.Debug
|
|
1093
|
+
"warning", "warn" -> ai.ansight.runtime.AnsightEventType.Warning
|
|
1094
|
+
"error" -> ai.ansight.runtime.AnsightEventType.Error
|
|
1095
|
+
"exception" -> ai.ansight.runtime.AnsightEventType.Exception
|
|
1096
|
+
"gc" -> ai.ansight.runtime.AnsightEventType.Gc
|
|
1097
|
+
"navigation" -> ai.ansight.runtime.AnsightEventType.Navigation
|
|
1098
|
+
"screenviewed", "screen_viewed" -> ai.ansight.runtime.AnsightEventType.ScreenViewed
|
|
1099
|
+
"lifecycle" -> ai.ansight.runtime.AnsightEventType.Lifecycle
|
|
1100
|
+
else -> ai.ansight.runtime.AnsightEventType.Info
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
private fun lifecycleState(raw: String): AppLifecycleState =
|
|
1104
|
+
when (raw.trim().lowercase()) {
|
|
1105
|
+
"foreground", "active" -> AppLifecycleState.Foreground
|
|
1106
|
+
"background", "inactive" -> AppLifecycleState.Background
|
|
1107
|
+
else -> AppLifecycleState.Unknown
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
private fun toolGuard(raw: String): AnsightToolGuard =
|
|
1111
|
+
when (raw.trim().lowercase()) {
|
|
1112
|
+
"readonly", "read_only", "read" -> AnsightToolGuard.ReadOnly
|
|
1113
|
+
"readwrite", "read_write", "write" -> AnsightToolGuard.ReadWrite
|
|
1114
|
+
"full", "fullaccess", "full_access" -> AnsightToolGuard.FullAccess
|
|
1115
|
+
else -> AnsightToolGuard.Disabled
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
private fun toolGuardName(value: AnsightToolGuard): String =
|
|
1119
|
+
when (value) {
|
|
1120
|
+
AnsightToolGuard.Disabled -> "disabled"
|
|
1121
|
+
AnsightToolGuard.ReadOnly -> "readOnly"
|
|
1122
|
+
AnsightToolGuard.ReadWrite -> "readWrite"
|
|
1123
|
+
AnsightToolGuard.FullAccess -> "fullAccess"
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
private fun toolScope(raw: String?): ToolScope =
|
|
1127
|
+
when (raw?.trim()?.lowercase()) {
|
|
1128
|
+
"write" -> ToolScope.Write
|
|
1129
|
+
"delete" -> ToolScope.Delete
|
|
1130
|
+
else -> ToolScope.Read
|
|
1131
|
+
}
|