@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,1144 @@
|
|
|
1
|
+
import Ansight
|
|
2
|
+
import Capacitor
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
@objc(AnsightCapacitorPlugin)
|
|
6
|
+
public final class AnsightCapacitorPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
7
|
+
public let identifier = "AnsightCapacitorPlugin"
|
|
8
|
+
public let jsName = "Ansight"
|
|
9
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
10
|
+
CAPPluginMethod(name: "initialize", returnType: CAPPluginReturnPromise),
|
|
11
|
+
CAPPluginMethod(name: "initializeAndActivate", returnType: CAPPluginReturnPromise),
|
|
12
|
+
CAPPluginMethod(name: "activate", returnType: CAPPluginReturnPromise),
|
|
13
|
+
CAPPluginMethod(name: "deactivate", returnType: CAPPluginReturnPromise),
|
|
14
|
+
CAPPluginMethod(name: "clear", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "registerMetricChannel", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "recordMetric", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "recordEvent", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "screenViewed", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "setAppLifecycleState", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "connect", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "scanPairingQrCode", returnType: CAPPluginReturnPromise),
|
|
22
|
+
CAPPluginMethod(name: "openSession", returnType: CAPPluginReturnPromise),
|
|
23
|
+
CAPPluginMethod(name: "disconnect", returnType: CAPPluginReturnPromise),
|
|
24
|
+
CAPPluginMethod(name: "completeSession", returnType: CAPPluginReturnPromise),
|
|
25
|
+
CAPPluginMethod(name: "closeSession", returnType: CAPPluginReturnPromise),
|
|
26
|
+
CAPPluginMethod(name: "savePairingConfig", returnType: CAPPluginReturnPromise),
|
|
27
|
+
CAPPluginMethod(name: "clearSavedPairing", returnType: CAPPluginReturnPromise),
|
|
28
|
+
CAPPluginMethod(name: "clearCachedSession", returnType: CAPPluginReturnPromise),
|
|
29
|
+
CAPPluginMethod(name: "notifyHostConnectionConfigChanged", returnType: CAPPluginReturnPromise),
|
|
30
|
+
CAPPluginMethod(name: "status", returnType: CAPPluginReturnPromise),
|
|
31
|
+
CAPPluginMethod(name: "snapshot", returnType: CAPPluginReturnPromise),
|
|
32
|
+
CAPPluginMethod(name: "hostConnectionStatus", returnType: CAPPluginReturnPromise),
|
|
33
|
+
CAPPluginMethod(name: "hostConnectionCapabilities", returnType: CAPPluginReturnPromise),
|
|
34
|
+
CAPPluginMethod(name: "currentOptions", returnType: CAPPluginReturnPromise),
|
|
35
|
+
CAPPluginMethod(name: "recordedMetrics", returnType: CAPPluginReturnPromise),
|
|
36
|
+
CAPPluginMethod(name: "recordedEvents", returnType: CAPPluginReturnPromise),
|
|
37
|
+
CAPPluginMethod(name: "sendClientLog", returnType: CAPPluginReturnPromise),
|
|
38
|
+
CAPPluginMethod(name: "captureBuiltInTelemetrySample", returnType: CAPPluginReturnPromise),
|
|
39
|
+
CAPPluginMethod(name: "isFramesPerSecondEnabled", returnType: CAPPluginReturnPromise),
|
|
40
|
+
CAPPluginMethod(name: "enableFramesPerSecond", returnType: CAPPluginReturnPromise),
|
|
41
|
+
CAPPluginMethod(name: "disableFramesPerSecond", returnType: CAPPluginReturnPromise),
|
|
42
|
+
CAPPluginMethod(name: "captureScreenFrame", returnType: CAPPluginReturnPromise),
|
|
43
|
+
CAPPluginMethod(name: "enableTouchCapture", returnType: CAPPluginReturnPromise),
|
|
44
|
+
CAPPluginMethod(name: "disableTouchCapture", returnType: CAPPluginReturnPromise),
|
|
45
|
+
CAPPluginMethod(name: "updateSessionProperties", returnType: CAPPluginReturnPromise),
|
|
46
|
+
CAPPluginMethod(name: "clearSessionProperties", returnType: CAPPluginReturnPromise),
|
|
47
|
+
CAPPluginMethod(name: "registerCustomProperty", returnType: CAPPluginReturnPromise),
|
|
48
|
+
CAPPluginMethod(name: "removeCustomProperty", returnType: CAPPluginReturnPromise),
|
|
49
|
+
CAPPluginMethod(name: "registerCustomTool", returnType: CAPPluginReturnPromise),
|
|
50
|
+
CAPPluginMethod(name: "unregisterCustomTool", returnType: CAPPluginReturnPromise),
|
|
51
|
+
CAPPluginMethod(name: "clearRegisteredCustomTools", returnType: CAPPluginReturnPromise),
|
|
52
|
+
CAPPluginMethod(name: "resolveToolCall", returnType: CAPPluginReturnPromise),
|
|
53
|
+
CAPPluginMethod(name: "queueBinaryTransfer", returnType: CAPPluginReturnPromise),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
private final class PendingToolCall {
|
|
57
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
58
|
+
var result: AnsightToolExecutionResult?
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private final class CapacitorTool: AnsightTool, @unchecked Sendable {
|
|
62
|
+
let descriptor: AnsightToolDescriptor
|
|
63
|
+
private weak var plugin: AnsightCapacitorPlugin?
|
|
64
|
+
private let timeoutMilliseconds: Int
|
|
65
|
+
|
|
66
|
+
init(descriptor: AnsightToolDescriptor, plugin: AnsightCapacitorPlugin, timeoutMilliseconds: Int) {
|
|
67
|
+
self.descriptor = descriptor
|
|
68
|
+
self.plugin = plugin
|
|
69
|
+
self.timeoutMilliseconds = timeoutMilliseconds
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func execute(arguments: [String: String]) throws -> AnsightToolExecutionResult {
|
|
73
|
+
guard let plugin else {
|
|
74
|
+
return .failure("Capacitor bridge is unavailable.", errorCode: "javascript_bridge_unavailable")
|
|
75
|
+
}
|
|
76
|
+
return plugin.executeJavaScriptTool(
|
|
77
|
+
toolId: descriptor.id,
|
|
78
|
+
arguments: arguments,
|
|
79
|
+
timeoutMilliseconds: timeoutMilliseconds
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private let lock = NSLock()
|
|
85
|
+
private var activeCustomToolIds: Set<String> = []
|
|
86
|
+
private var pendingToolCalls: [String: PendingToolCall] = [:]
|
|
87
|
+
private lazy var logCallback = AnsightClosureLogCallback { [weak self] level, message, error in
|
|
88
|
+
var data: [String: Any] = [
|
|
89
|
+
"level": level.rawValue,
|
|
90
|
+
"message": message,
|
|
91
|
+
"platform": "ios",
|
|
92
|
+
]
|
|
93
|
+
if let error {
|
|
94
|
+
data["error"] = error.localizedDescription
|
|
95
|
+
}
|
|
96
|
+
self?.notifyListeners("ansightLog", data: data)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public override func load() {
|
|
100
|
+
AnsightLogger.registerCallback(logCallback)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
deinit {
|
|
104
|
+
AnsightLogger.removeCallback(logCallback)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@objc func initialize(_ call: CAPPluginCall) {
|
|
108
|
+
do {
|
|
109
|
+
let options = dictionary(call)
|
|
110
|
+
try AnsightRuntime.shared.initialize(options: buildOptions(options))
|
|
111
|
+
try AnsightRuntime.shared.registerAnsightRemoteTools(options: remoteToolOptions(options))
|
|
112
|
+
call.resolve(snapshotDictionary())
|
|
113
|
+
} catch {
|
|
114
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@objc func initializeAndActivate(_ call: CAPPluginCall) {
|
|
119
|
+
do {
|
|
120
|
+
let options = dictionary(call)
|
|
121
|
+
try AnsightRuntime.shared.initializeAndActivateAnsightSdk(
|
|
122
|
+
options: buildOptions(options),
|
|
123
|
+
remoteToolOptions: remoteToolOptions(options)
|
|
124
|
+
)
|
|
125
|
+
call.resolve(snapshotDictionary())
|
|
126
|
+
} catch {
|
|
127
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@objc func activate(_ call: CAPPluginCall) {
|
|
132
|
+
do {
|
|
133
|
+
try AnsightRuntime.shared.activate()
|
|
134
|
+
call.resolve(snapshotDictionary())
|
|
135
|
+
} catch {
|
|
136
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc func deactivate(_ call: CAPPluginCall) {
|
|
141
|
+
AnsightRuntime.shared.deactivate()
|
|
142
|
+
call.resolve(snapshotDictionary())
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@objc func clear(_ call: CAPPluginCall) {
|
|
146
|
+
AnsightRuntime.shared.clear()
|
|
147
|
+
call.resolve(snapshotDictionary())
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@objc func registerMetricChannel(_ call: CAPPluginCall) {
|
|
151
|
+
let channel = nestedDictionary(call, "channel")
|
|
152
|
+
do {
|
|
153
|
+
try AnsightRuntime.shared.registerMetricChannel(
|
|
154
|
+
AnsightChannel(
|
|
155
|
+
id: intValue(channel, "id", defaultValue: -1),
|
|
156
|
+
name: stringValue(channel, "name") ?? "",
|
|
157
|
+
colorHex: stringValue(channel, "colorHex"),
|
|
158
|
+
unit: stringValue(channel, "unit"),
|
|
159
|
+
type: stringValue(channel, "type") ?? "custom",
|
|
160
|
+
source: stringValue(channel, "source"),
|
|
161
|
+
group: stringValue(channel, "group"),
|
|
162
|
+
kind: stringValue(channel, "kind")
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
call.resolve(snapshotDictionary())
|
|
166
|
+
} catch {
|
|
167
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@objc func recordMetric(_ call: CAPPluginCall) {
|
|
172
|
+
do {
|
|
173
|
+
try AnsightRuntime.shared.metric(
|
|
174
|
+
Int64(call.getDouble("value") ?? 0),
|
|
175
|
+
channel: call.getInt("channel") ?? AnsightChannels.unspecified
|
|
176
|
+
)
|
|
177
|
+
call.resolve(snapshotDictionary())
|
|
178
|
+
} catch {
|
|
179
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@objc func recordEvent(_ call: CAPPluginCall) {
|
|
184
|
+
do {
|
|
185
|
+
try AnsightRuntime.shared.event(
|
|
186
|
+
call.getString("label") ?? "",
|
|
187
|
+
type: eventType(call.getString("type")),
|
|
188
|
+
details: call.getString("details"),
|
|
189
|
+
channel: call.getInt("channel") ?? AnsightChannels.unspecified
|
|
190
|
+
)
|
|
191
|
+
call.resolve(snapshotDictionary())
|
|
192
|
+
} catch {
|
|
193
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@objc func screenViewed(_ call: CAPPluginCall) {
|
|
198
|
+
do {
|
|
199
|
+
try AnsightRuntime.shared.screenViewed(
|
|
200
|
+
call.getString("name") ?? "",
|
|
201
|
+
details: stringDictionary(nestedDictionary(call, "details"))
|
|
202
|
+
)
|
|
203
|
+
call.resolve(snapshotDictionary())
|
|
204
|
+
} catch {
|
|
205
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@objc func setAppLifecycleState(_ call: CAPPluginCall) {
|
|
210
|
+
AnsightRuntime.shared.setAppLifecycleState(lifecycleState(call.getString("state") ?? "unknown"))
|
|
211
|
+
call.resolve(snapshotDictionary())
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@objc func connect(_ call: CAPPluginCall) {
|
|
215
|
+
Task {
|
|
216
|
+
let payload = call.getString("pairingPayload")
|
|
217
|
+
let request: HostConnectionRequest
|
|
218
|
+
if let payload, !payload.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
219
|
+
request = .payloadText(
|
|
220
|
+
payload,
|
|
221
|
+
clientName: call.getString("clientName"),
|
|
222
|
+
expectedAppId: call.getString("expectedAppId"),
|
|
223
|
+
hostAddressOverride: call.getString("hostAddressOverride"),
|
|
224
|
+
sourceDescription: "Capacitor"
|
|
225
|
+
)
|
|
226
|
+
} else {
|
|
227
|
+
request = .auto(
|
|
228
|
+
clientName: call.getString("clientName"),
|
|
229
|
+
expectedAppId: call.getString("expectedAppId"),
|
|
230
|
+
hostAddressOverride: call.getString("hostAddressOverride"),
|
|
231
|
+
sourceDescription: "Capacitor"
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
call.resolve(hostConnectionResultDictionary(await AnsightRuntime.shared.connect(request)))
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
@objc func scanPairingQrCode(_ call: CAPPluginCall) {
|
|
239
|
+
Task {
|
|
240
|
+
let request = HostConnectionRequest.qrCode(
|
|
241
|
+
title: call.getString("title") ?? "Scan Ansight Pairing QR",
|
|
242
|
+
clientName: call.getString("clientName"),
|
|
243
|
+
expectedAppId: call.getString("expectedAppId"),
|
|
244
|
+
hostAddressOverride: call.getString("hostAddressOverride"),
|
|
245
|
+
sourceDescription: "Capacitor native QR scanner"
|
|
246
|
+
)
|
|
247
|
+
call.resolve(hostConnectionResultDictionary(await AnsightRuntime.shared.connect(request)))
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@objc func openSession(_ call: CAPPluginCall) {
|
|
252
|
+
Task {
|
|
253
|
+
do {
|
|
254
|
+
let result = try await AnsightRuntime.shared.openLiveSession(
|
|
255
|
+
pairingJson: call.getString("pairingPayload") ?? "",
|
|
256
|
+
options: pairingOpenOptions(dictionary(call))
|
|
257
|
+
)
|
|
258
|
+
call.resolve(openSessionResultDictionary(result))
|
|
259
|
+
} catch {
|
|
260
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@objc func disconnect(_ call: CAPPluginCall) {
|
|
266
|
+
Task {
|
|
267
|
+
call.resolve(hostConnectionResultDictionary(await AnsightRuntime.shared.disconnect()))
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@objc func completeSession(_ call: CAPPluginCall) {
|
|
272
|
+
Task {
|
|
273
|
+
call.resolve(operationResultDictionary(await AnsightRuntime.shared.completeLiveSession()))
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
@objc func closeSession(_ call: CAPPluginCall) {
|
|
278
|
+
AnsightRuntime.shared.closeSession()
|
|
279
|
+
call.resolve(["success": true, "message": "Session closed."])
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@objc func savePairingConfig(_ call: CAPPluginCall) {
|
|
283
|
+
call.resolve(hostConnectionResultDictionary(
|
|
284
|
+
AnsightRuntime.shared.savePairingConfig(
|
|
285
|
+
call.getString("pairingPayload") ?? "",
|
|
286
|
+
expectedAppId: call.getString("expectedAppId")
|
|
287
|
+
)
|
|
288
|
+
))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@objc func clearSavedPairing(_ call: CAPPluginCall) {
|
|
292
|
+
AnsightRuntime.shared.clearSavedPairing()
|
|
293
|
+
call.resolve([
|
|
294
|
+
"success": true,
|
|
295
|
+
"message": "Saved pairing config cleared.",
|
|
296
|
+
"kind": "savedConfig",
|
|
297
|
+
"source": "savedConfig",
|
|
298
|
+
])
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@objc func clearCachedSession(_ call: CAPPluginCall) {
|
|
302
|
+
AnsightRuntime.shared.clearCachedSession()
|
|
303
|
+
call.resolve(["success": true, "message": "Cached live session cleared."])
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@objc func notifyHostConnectionConfigChanged(_ call: CAPPluginCall) {
|
|
307
|
+
call.resolve(hostConnectionResultDictionary(AnsightRuntime.shared.notifyHostConnectionConfigChanged()))
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@objc func status(_ call: CAPPluginCall) {
|
|
311
|
+
call.resolve(snapshotDictionary())
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@objc func snapshot(_ call: CAPPluginCall) {
|
|
315
|
+
call.resolve(snapshotDictionary())
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@objc func hostConnectionStatus(_ call: CAPPluginCall) {
|
|
319
|
+
call.resolve(hostConnectionStatusDictionary(AnsightRuntime.shared.hostConnectionStatus()))
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@objc func hostConnectionCapabilities(_ call: CAPPluginCall) {
|
|
323
|
+
call.resolve(hostConnectionCapabilitiesDictionary(AnsightRuntime.shared.hostConnectionCapabilities()))
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
@objc func currentOptions(_ call: CAPPluginCall) {
|
|
327
|
+
call.resolve(optionsDictionary(AnsightRuntime.shared.currentOptions()))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
@objc func recordedMetrics(_ call: CAPPluginCall) {
|
|
331
|
+
let metrics = AnsightRuntime.shared.recordedMetrics()
|
|
332
|
+
let limit = max(0, call.getInt("limit") ?? 0)
|
|
333
|
+
call.resolve(["items": (limit > 0 ? Array(metrics.suffix(limit)) : metrics).map(metricDictionary)])
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@objc func recordedEvents(_ call: CAPPluginCall) {
|
|
337
|
+
let events = AnsightRuntime.shared.recordedEvents()
|
|
338
|
+
let limit = max(0, call.getInt("limit") ?? 0)
|
|
339
|
+
call.resolve(["items": (limit > 0 ? Array(events.suffix(limit)) : events).map(eventDictionary)])
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@objc func sendClientLog(_ call: CAPPluginCall) {
|
|
343
|
+
Task {
|
|
344
|
+
call.resolve(operationResultDictionary(
|
|
345
|
+
await AnsightRuntime.shared.sendClientLog(call.getString("line") ?? "")
|
|
346
|
+
))
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@objc func captureBuiltInTelemetrySample(_ call: CAPPluginCall) {
|
|
351
|
+
AnsightRuntime.shared.captureBuiltInTelemetrySample()
|
|
352
|
+
call.resolve(snapshotDictionary())
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
@objc func isFramesPerSecondEnabled(_ call: CAPPluginCall) {
|
|
356
|
+
call.resolve(["value": AnsightRuntime.shared.isFramesPerSecondEnabled])
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@objc func enableFramesPerSecond(_ call: CAPPluginCall) {
|
|
360
|
+
AnsightRuntime.shared.enableFramesPerSecond()
|
|
361
|
+
call.resolve(snapshotDictionary())
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@objc func disableFramesPerSecond(_ call: CAPPluginCall) {
|
|
365
|
+
AnsightRuntime.shared.disableFramesPerSecond()
|
|
366
|
+
call.resolve(snapshotDictionary())
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
@objc func captureScreenFrame(_ call: CAPPluginCall) {
|
|
370
|
+
Task {
|
|
371
|
+
call.resolve(operationResultDictionary(
|
|
372
|
+
await AnsightRuntime.shared.captureScreenFrame(options: screenCaptureOptions(dictionary(call)))
|
|
373
|
+
))
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@objc func enableTouchCapture(_ call: CAPPluginCall) {
|
|
378
|
+
AnsightRuntime.shared.enableTouchCapture()
|
|
379
|
+
call.resolve(snapshotDictionary())
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@objc func disableTouchCapture(_ call: CAPPluginCall) {
|
|
383
|
+
AnsightRuntime.shared.disableTouchCapture()
|
|
384
|
+
call.resolve(snapshotDictionary())
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
@objc func updateSessionProperties(_ call: CAPPluginCall) {
|
|
388
|
+
Task {
|
|
389
|
+
call.resolve(operationResultDictionary(
|
|
390
|
+
await AnsightRuntime.shared.updateSessionProperties(
|
|
391
|
+
groupedStringDictionary(nestedDictionary(call, "properties"))
|
|
392
|
+
)
|
|
393
|
+
))
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@objc func clearSessionProperties(_ call: CAPPluginCall) {
|
|
398
|
+
Task {
|
|
399
|
+
call.resolve(operationResultDictionary(await AnsightRuntime.shared.clearSessionProperties()))
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
@objc func registerCustomProperty(_ call: CAPPluginCall) {
|
|
404
|
+
Task {
|
|
405
|
+
let group = (call.getString("group") ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
406
|
+
let key = (call.getString("key") ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
407
|
+
guard !group.isEmpty, !key.isEmpty else {
|
|
408
|
+
call.resolve(["success": false, "message": "Custom property group and key must not be blank."])
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
var properties = AnsightRuntime.shared.currentOptions().customProperties
|
|
412
|
+
var groupProperties = properties[group] ?? [:]
|
|
413
|
+
groupProperties[key] = call.getString("value") ?? ""
|
|
414
|
+
properties[group] = groupProperties
|
|
415
|
+
call.resolve(operationResultDictionary(await AnsightRuntime.shared.updateSessionProperties(properties)))
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
@objc func removeCustomProperty(_ call: CAPPluginCall) {
|
|
420
|
+
Task {
|
|
421
|
+
let group = (call.getString("group") ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
422
|
+
let key = (call.getString("key") ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
423
|
+
var properties = AnsightRuntime.shared.currentOptions().customProperties
|
|
424
|
+
properties[group]?.removeValue(forKey: key)
|
|
425
|
+
if properties[group]?.isEmpty == true {
|
|
426
|
+
properties.removeValue(forKey: group)
|
|
427
|
+
}
|
|
428
|
+
call.resolve(operationResultDictionary(await AnsightRuntime.shared.updateSessionProperties(properties)))
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
@objc func registerCustomTool(_ call: CAPPluginCall) {
|
|
433
|
+
do {
|
|
434
|
+
let definition = nestedDictionary(call, "definition")
|
|
435
|
+
let descriptor = try toolDescriptor(definition)
|
|
436
|
+
let timeout = max(250, intValue(definition, "timeoutMilliseconds", defaultValue: 30_000))
|
|
437
|
+
_ = lock.withLock { activeCustomToolIds.insert(descriptor.id) }
|
|
438
|
+
try AnsightRuntime.shared.registerTool(
|
|
439
|
+
CapacitorTool(descriptor: descriptor, plugin: self, timeoutMilliseconds: timeout),
|
|
440
|
+
replaceExisting: true
|
|
441
|
+
)
|
|
442
|
+
call.resolve(["success": true, "message": "Tool registered.", "id": descriptor.id])
|
|
443
|
+
} catch {
|
|
444
|
+
call.reject(error.localizedDescription, "ansight_error", error)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
@objc func unregisterCustomTool(_ call: CAPPluginCall) {
|
|
449
|
+
let id = call.getString("id") ?? ""
|
|
450
|
+
_ = lock.withLock { activeCustomToolIds.remove(id) }
|
|
451
|
+
call.resolve(["success": true, "message": "Tool unregistered.", "id": id])
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
@objc func clearRegisteredCustomTools(_ call: CAPPluginCall) {
|
|
455
|
+
lock.withLock { activeCustomToolIds.removeAll() }
|
|
456
|
+
call.resolve(["success": true, "message": "JavaScript tools cleared."])
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
@objc func resolveToolCall(_ call: CAPPluginCall) {
|
|
460
|
+
let requestId = call.getString("requestId") ?? ""
|
|
461
|
+
guard let pending = lock.withLock({ pendingToolCalls[requestId] }) else {
|
|
462
|
+
call.resolve(["success": false, "message": "Tool request is no longer pending.", "accepted": false])
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
let result = nestedDictionary(call, "result")
|
|
466
|
+
let success = boolValue(result, "success", defaultValue: true)
|
|
467
|
+
let payload = jsonValue(result?["result"])
|
|
468
|
+
pending.result = success
|
|
469
|
+
? .success(payload, message: stringValue(result, "message"))
|
|
470
|
+
: .failure(
|
|
471
|
+
stringValue(result, "message") ?? "JavaScript tool failed.",
|
|
472
|
+
errorCode: stringValue(result, "errorCode"),
|
|
473
|
+
result: payload
|
|
474
|
+
)
|
|
475
|
+
pending.semaphore.signal()
|
|
476
|
+
call.resolve(["success": true, "message": "Tool result accepted.", "accepted": true])
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
@objc func queueBinaryTransfer(_ call: CAPPluginCall) {
|
|
480
|
+
let requestId = (call.getString("requestId") ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
481
|
+
guard !requestId.isEmpty,
|
|
482
|
+
let data = Data(base64Encoded: call.getString("base64Data") ?? "") else {
|
|
483
|
+
call.resolve([
|
|
484
|
+
"success": false,
|
|
485
|
+
"message": "Binary transfer requires a request id and base64 payload.",
|
|
486
|
+
"errorCode": "artifact_payload_invalid",
|
|
487
|
+
])
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
let transferId = UUID()
|
|
491
|
+
let chunkBytes = min(max(call.getInt("chunkBytes") ?? 65_536, 1_024), 512 * 1_024)
|
|
492
|
+
let result = AnsightRuntime.shared.queueBinaryTransfer(
|
|
493
|
+
requestId: requestId,
|
|
494
|
+
transferId: transferId,
|
|
495
|
+
data: data,
|
|
496
|
+
chunkBytes: chunkBytes,
|
|
497
|
+
description: "capacitor-artifact:\(transferId.uuidString.lowercased())"
|
|
498
|
+
)
|
|
499
|
+
call.resolve([
|
|
500
|
+
"success": result.success,
|
|
501
|
+
"message": result.message,
|
|
502
|
+
"transferId": transferId.uuidString.replacingOccurrences(of: "-", with: "").lowercased(),
|
|
503
|
+
"deliveryMode": "websocket_binary",
|
|
504
|
+
"wireProtocol": PairingFileTransferWireProtocol.protocolName,
|
|
505
|
+
"status": result.success ? "queued" : "failed",
|
|
506
|
+
"chunkBytes": chunkBytes,
|
|
507
|
+
"sizeBytes": data.count,
|
|
508
|
+
])
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
fileprivate func executeJavaScriptTool(
|
|
512
|
+
toolId: String,
|
|
513
|
+
arguments: [String: String],
|
|
514
|
+
timeoutMilliseconds: Int
|
|
515
|
+
) -> AnsightToolExecutionResult {
|
|
516
|
+
let requestId = "ios.capacitor.\(UUID().uuidString.replacingOccurrences(of: "-", with: ""))"
|
|
517
|
+
let pending = PendingToolCall()
|
|
518
|
+
let active = lock.withLock { () -> Bool in
|
|
519
|
+
guard activeCustomToolIds.contains(toolId) else { return false }
|
|
520
|
+
pendingToolCalls[requestId] = pending
|
|
521
|
+
return true
|
|
522
|
+
}
|
|
523
|
+
guard active else {
|
|
524
|
+
return .failure("JavaScript tool is not registered.", errorCode: "javascript_tool_not_registered")
|
|
525
|
+
}
|
|
526
|
+
notifyListeners("ansightToolCall", data: [
|
|
527
|
+
"requestId": requestId,
|
|
528
|
+
"nativeRequestId": requestId,
|
|
529
|
+
"toolId": toolId,
|
|
530
|
+
"arguments": arguments,
|
|
531
|
+
"platform": "ios",
|
|
532
|
+
])
|
|
533
|
+
let wait = pending.semaphore.wait(timeout: .now() + .milliseconds(timeoutMilliseconds))
|
|
534
|
+
_ = lock.withLock { pendingToolCalls.removeValue(forKey: requestId) }
|
|
535
|
+
if wait == .timedOut {
|
|
536
|
+
return .failure("JavaScript tool timed out.", errorCode: "javascript_tool_timeout")
|
|
537
|
+
}
|
|
538
|
+
return pending.result ?? .failure(
|
|
539
|
+
"JavaScript tool completed without a result.",
|
|
540
|
+
errorCode: "javascript_tool_result_missing"
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private func buildOptions(_ dictionary: NSDictionary?) throws -> AnsightOptions {
|
|
545
|
+
let useDefaults = boolValue(dictionary, "useNativeAllInOneDefaults", defaultValue: false)
|
|
546
|
+
var options = useDefaults ? AnsightOptions.ansightDeveloperDefaults : AnsightOptions()
|
|
547
|
+
if let value = stringValue(dictionary, "pairingConfigJson") {
|
|
548
|
+
if useDefaults {
|
|
549
|
+
options.hostConnection.bundledDeveloperConfigJson = value
|
|
550
|
+
} else {
|
|
551
|
+
options.hostConnection.bundledConfigJson = value
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if let value = stringValue(dictionary, "clientName") {
|
|
555
|
+
options.hostAutoProbe.clientName = value
|
|
556
|
+
}
|
|
557
|
+
if hasNumber(dictionary, "sampleFrequencyMilliseconds") {
|
|
558
|
+
options.sampleFrequencyMilliseconds = intValue(
|
|
559
|
+
dictionary, "sampleFrequencyMilliseconds", defaultValue: options.sampleFrequencyMilliseconds
|
|
560
|
+
)
|
|
561
|
+
}
|
|
562
|
+
if hasNumber(dictionary, "retentionPeriodSeconds") {
|
|
563
|
+
options.retentionPeriodSeconds = intValue(
|
|
564
|
+
dictionary, "retentionPeriodSeconds", defaultValue: options.retentionPeriodSeconds
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
if hasBool(dictionary, "enableFramesPerSecond") {
|
|
568
|
+
options.enableFramesPerSecond = boolValue(
|
|
569
|
+
dictionary, "enableFramesPerSecond", defaultValue: options.enableFramesPerSecond
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
if hasBool(dictionary, "enableBatteryLevel") {
|
|
573
|
+
options.enableBatteryLevel = boolValue(
|
|
574
|
+
dictionary, "enableBatteryLevel", defaultValue: options.enableBatteryLevel
|
|
575
|
+
)
|
|
576
|
+
}
|
|
577
|
+
if let guardName = stringValue(dictionary, "toolGuard") {
|
|
578
|
+
options.toolGuard = toolGuard(guardName)
|
|
579
|
+
} else if useDefaults {
|
|
580
|
+
options.toolGuard = .readOnly
|
|
581
|
+
}
|
|
582
|
+
if let properties = dictionary?["customProperties"] as? NSDictionary {
|
|
583
|
+
options.customProperties = groupedStringDictionary(properties)
|
|
584
|
+
}
|
|
585
|
+
if let memory = dictionary?["defaultMemoryChannels"] as? NSDictionary {
|
|
586
|
+
var channels: DefaultMemoryChannels = []
|
|
587
|
+
if boolValue(memory, "managedHeap", defaultValue: boolValue(memory, "javaHeap", defaultValue: false)) {
|
|
588
|
+
channels.insert(.managedHeap)
|
|
589
|
+
}
|
|
590
|
+
if boolValue(memory, "nativeHeap", defaultValue: false) {
|
|
591
|
+
channels.insert(.nativeHeap)
|
|
592
|
+
}
|
|
593
|
+
if boolValue(memory, "residentSetSize", defaultValue: boolValue(memory, "rss", defaultValue: false)) {
|
|
594
|
+
channels.insert(.residentSetSize)
|
|
595
|
+
}
|
|
596
|
+
if boolValue(memory, "physicalFootprint", defaultValue: false) {
|
|
597
|
+
channels.insert(.physicalFootprint)
|
|
598
|
+
}
|
|
599
|
+
options.defaultMemoryChannels = channels
|
|
600
|
+
}
|
|
601
|
+
if let raw = dictionary?["sessionJpegCapture"] {
|
|
602
|
+
if (raw as? Bool) == false {
|
|
603
|
+
options.sessionJpegCapture = nil
|
|
604
|
+
} else if let capture = raw as? NSDictionary {
|
|
605
|
+
options.sessionJpegCapture = screenCaptureOptions(capture)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if let raw = dictionary?["touchCapture"] {
|
|
609
|
+
if (raw as? Bool) == false {
|
|
610
|
+
options.touchCapture = nil
|
|
611
|
+
} else if let touch = raw as? NSDictionary {
|
|
612
|
+
options.touchCapture = AnsightTouchCaptureOptions(
|
|
613
|
+
captureMoveEvents: boolValue(touch, "captureMoveEvents", defaultValue: true),
|
|
614
|
+
captureCancelEvents: boolValue(touch, "captureCancelEvents", defaultValue: true),
|
|
615
|
+
moveCaptureDistanceThreshold: doubleValue(
|
|
616
|
+
touch,
|
|
617
|
+
"moveCaptureDistanceThreshold",
|
|
618
|
+
defaultValue: AnsightTouchCaptureOptions.defaultMoveCaptureDistanceThreshold
|
|
619
|
+
),
|
|
620
|
+
moveCaptureFramesPerSecond: intValue(
|
|
621
|
+
touch,
|
|
622
|
+
"moveCaptureFramesPerSecond",
|
|
623
|
+
defaultValue: AnsightTouchCaptureOptions.defaultMoveCaptureFramesPerSecond
|
|
624
|
+
)
|
|
625
|
+
)
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if let lifecycle = dictionary?["lifecycleCapture"] as? NSDictionary {
|
|
629
|
+
options.lifecycleCapture = AnsightLifecycleCaptureOptions(
|
|
630
|
+
enabled: boolValue(lifecycle, "enabled", defaultValue: options.lifecycleCapture.enabled),
|
|
631
|
+
captureAppLifecycle: boolValue(
|
|
632
|
+
lifecycle, "captureAppLifecycle", defaultValue: options.lifecycleCapture.captureAppLifecycle
|
|
633
|
+
),
|
|
634
|
+
captureScreenViews: boolValue(
|
|
635
|
+
lifecycle, "captureScreenViews", defaultValue: options.lifecycleCapture.captureScreenViews
|
|
636
|
+
),
|
|
637
|
+
minimumScreenViewIntervalMilliseconds: intValue(
|
|
638
|
+
lifecycle,
|
|
639
|
+
"minimumScreenViewIntervalMilliseconds",
|
|
640
|
+
defaultValue: options.lifecycleCapture.minimumScreenViewIntervalMilliseconds
|
|
641
|
+
)
|
|
642
|
+
)
|
|
643
|
+
}
|
|
644
|
+
if let autoProbe = dictionary?["hostAutoProbe"] as? NSDictionary {
|
|
645
|
+
options.hostAutoProbe = AnsightHostAutoProbeOptions(
|
|
646
|
+
enabled: boolValue(autoProbe, "enabled", defaultValue: options.hostAutoProbe.enabled),
|
|
647
|
+
initialDelayMilliseconds: intValue(
|
|
648
|
+
autoProbe, "initialDelayMilliseconds", defaultValue: options.hostAutoProbe.initialDelayMilliseconds
|
|
649
|
+
),
|
|
650
|
+
probeIntervalMilliseconds: intValue(
|
|
651
|
+
autoProbe, "probeIntervalMilliseconds", defaultValue: options.hostAutoProbe.probeIntervalMilliseconds
|
|
652
|
+
),
|
|
653
|
+
reconnectDelayMilliseconds: intValue(
|
|
654
|
+
autoProbe, "reconnectDelayMilliseconds", defaultValue: options.hostAutoProbe.reconnectDelayMilliseconds
|
|
655
|
+
),
|
|
656
|
+
clientName: stringValue(autoProbe, "clientName") ?? options.hostAutoProbe.clientName
|
|
657
|
+
)
|
|
658
|
+
}
|
|
659
|
+
if let host = dictionary?["hostConnection"] as? NSDictionary {
|
|
660
|
+
options.hostConnection = AnsightHostConnectionOptions(
|
|
661
|
+
savedConfigKey: stringValue(host, "savedConfigKey") ?? options.hostConnection.savedConfigKey,
|
|
662
|
+
connectionProfileRetentionSeconds: intValue(
|
|
663
|
+
host,
|
|
664
|
+
"connectionProfileRetentionSeconds",
|
|
665
|
+
defaultValue: options.hostConnection.connectionProfileRetentionSeconds
|
|
666
|
+
),
|
|
667
|
+
discoveryPort: optionalInt(host, "discoveryPort") ?? options.hostConnection.discoveryPort,
|
|
668
|
+
bundledDeveloperConfigJson: stringValue(host, "bundledDeveloperConfigJson")
|
|
669
|
+
?? options.hostConnection.bundledDeveloperConfigJson,
|
|
670
|
+
bundledConfigJson: stringValue(host, "bundledConfigJson")
|
|
671
|
+
?? options.hostConnection.bundledConfigJson
|
|
672
|
+
)
|
|
673
|
+
}
|
|
674
|
+
if let channels = dictionary?["additionalChannels"] as? [NSDictionary] {
|
|
675
|
+
options.additionalChannels = channels.map(channelFromDictionary)
|
|
676
|
+
}
|
|
677
|
+
return try options.validated()
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
private func remoteToolOptions(_ dictionary: NSDictionary?) -> AnsightRemoteToolOptions {
|
|
681
|
+
let remoteTools = dictionary?["remoteTools"] as? NSDictionary
|
|
682
|
+
let defaultEnabled = boolValue(dictionary, "useNativeAllInOneDefaults", defaultValue: false)
|
|
683
|
+
return AnsightRemoteToolOptions(
|
|
684
|
+
visualTree: toolSuiteEnabled(remoteTools?["visualTree"], defaultValue: defaultEnabled),
|
|
685
|
+
database: AnsightDatabaseToolsOptions(
|
|
686
|
+
additionalRoots: rootDictionaries((remoteTools?["database"] as? NSDictionary)?["additionalRoots"]).map {
|
|
687
|
+
AnsightDatabaseRoot(alias: stringValue($0, "alias") ?? "", path: stringValue($0, "path") ?? "")
|
|
688
|
+
},
|
|
689
|
+
includePlatformRoots: boolValue(
|
|
690
|
+
remoteTools?["database"] as? NSDictionary, "includePlatformRoots", defaultValue: true
|
|
691
|
+
)
|
|
692
|
+
),
|
|
693
|
+
fileSystem: AnsightFileSystemToolsOptions(
|
|
694
|
+
additionalRoots: rootDictionaries((remoteTools?["fileSystem"] as? NSDictionary)?["additionalRoots"]).map {
|
|
695
|
+
AnsightFileSystemRoot(alias: stringValue($0, "alias") ?? "", path: stringValue($0, "path") ?? "")
|
|
696
|
+
}
|
|
697
|
+
),
|
|
698
|
+
preferences: AnsightPreferencesToolOptions(
|
|
699
|
+
defaultStore: stringValue(remoteTools?["preferences"] as? NSDictionary, "defaultStore"),
|
|
700
|
+
allowedStores: stringArray(remoteTools?["preferences"] as? NSDictionary, "allowedStores"),
|
|
701
|
+
allowedKeys: stringArray(remoteTools?["preferences"] as? NSDictionary, "allowedKeys"),
|
|
702
|
+
allowedKeyPrefixes: stringArray(
|
|
703
|
+
remoteTools?["preferences"] as? NSDictionary, "allowedKeyPrefixes"
|
|
704
|
+
)
|
|
705
|
+
),
|
|
706
|
+
reflection: AnsightReflectionToolsOptions(
|
|
707
|
+
includeBuiltInRoots: boolValue(
|
|
708
|
+
remoteTools?["reflection"] as? NSDictionary, "includeBuiltInRoots", defaultValue: true
|
|
709
|
+
),
|
|
710
|
+
allowedRootIds: stringArray(remoteTools?["reflection"] as? NSDictionary, "allowedRootIds"),
|
|
711
|
+
allowedTypePrefixes: stringArray(
|
|
712
|
+
remoteTools?["reflection"] as? NSDictionary, "allowedTypePrefixes"
|
|
713
|
+
)
|
|
714
|
+
),
|
|
715
|
+
secureStorage: secureStorageToolsOptions(
|
|
716
|
+
remoteTools?["secureStorage"] as? NSDictionary ?? dictionary?["secureStorage"] as? NSDictionary
|
|
717
|
+
)
|
|
718
|
+
)
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private func secureStorageToolsOptions(_ dictionary: NSDictionary?) -> AnsightSecureStorageToolsOptions {
|
|
722
|
+
AnsightSecureStorageToolsOptions(
|
|
723
|
+
appleService: stringValue(dictionary, "appleService"),
|
|
724
|
+
allowedKeys: stringArray(dictionary, "allowedKeys"),
|
|
725
|
+
allowedKeyPrefixes: stringArray(dictionary, "allowedKeyPrefixes")
|
|
726
|
+
+ stringArray(dictionary, "allowedPrefixes")
|
|
727
|
+
)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
private func toolDescriptor(_ dictionary: NSDictionary?) throws -> AnsightToolDescriptor {
|
|
731
|
+
AnsightToolDescriptor(
|
|
732
|
+
id: stringValue(dictionary, "id") ?? "",
|
|
733
|
+
name: stringValue(dictionary, "name") ?? stringValue(dictionary, "id") ?? "",
|
|
734
|
+
description: stringValue(dictionary, "description") ?? "",
|
|
735
|
+
category: stringValue(dictionary, "category") ?? "custom",
|
|
736
|
+
scope: toolScope(stringValue(dictionary, "scope")).rawValue,
|
|
737
|
+
keywords: keywords(dictionary?["keywords"]),
|
|
738
|
+
security: toolSecurity(dictionary?["security"] as? NSDictionary),
|
|
739
|
+
argumentsSchema: AnsightToolSchema(json: jsonValue(dictionary?["argumentsSchema"]) ?? .object([:])),
|
|
740
|
+
resultSchema: AnsightToolSchema(json: jsonValue(dictionary?["resultSchema"]) ?? .object([:]))
|
|
741
|
+
)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private func snapshotDictionary() -> [String: Any] {
|
|
745
|
+
let snapshot = AnsightRuntime.shared.snapshot()
|
|
746
|
+
var result: [String: Any] = [
|
|
747
|
+
"initialized": snapshot.initialized,
|
|
748
|
+
"active": snapshot.active,
|
|
749
|
+
"sessionOpen": snapshot.sessionOpen,
|
|
750
|
+
"lifecycleState": snapshot.lifecycleState.rawValue,
|
|
751
|
+
"metricsRecorded": snapshot.metricsRecorded,
|
|
752
|
+
"eventsRecorded": snapshot.eventsRecorded,
|
|
753
|
+
"registeredTools": snapshot.registeredTools,
|
|
754
|
+
"executableTools": snapshot.executableTools,
|
|
755
|
+
"touchesRecorded": snapshot.touchesCaptured,
|
|
756
|
+
"touchesCaptured": snapshot.touchesCaptured,
|
|
757
|
+
"touchesSent": snapshot.touchesSent,
|
|
758
|
+
"touchCaptureEnabled": snapshot.touchCaptureEnabled,
|
|
759
|
+
"screenFramesCaptured": snapshot.screenFramesCaptured,
|
|
760
|
+
"screenFramesSent": snapshot.screenFramesSent,
|
|
761
|
+
"connectionStatus": hostConnectionStatusDictionary(snapshot.hostConnectionStatus),
|
|
762
|
+
"channels": snapshot.channels.map(channelDictionary),
|
|
763
|
+
]
|
|
764
|
+
if let value = snapshot.lastMetric { result["lastMetric"] = metricDictionary(value) }
|
|
765
|
+
if let value = snapshot.lastEvent { result["lastEvent"] = eventDictionary(value) }
|
|
766
|
+
if let value = snapshot.sessionMessage { result["sessionMessage"] = value }
|
|
767
|
+
if let value = snapshot.currentScreen {
|
|
768
|
+
result["currentScreen"] = [
|
|
769
|
+
"name": value.name,
|
|
770
|
+
"capturedAtUtc": value.capturedAtUtc,
|
|
771
|
+
"details": value.details,
|
|
772
|
+
]
|
|
773
|
+
}
|
|
774
|
+
return result
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
private func hostConnectionStatusDictionary(_ status: HostConnectionStatus) -> [String: Any] {
|
|
778
|
+
[
|
|
779
|
+
"isRuntimeActive": status.isRuntimeActive,
|
|
780
|
+
"isConnected": status.isConnected,
|
|
781
|
+
"connectionState": status.connectionState.rawValue,
|
|
782
|
+
"hasCachedSession": status.hasCachedSession,
|
|
783
|
+
"hasSavedConfig": status.hasSavedConfig,
|
|
784
|
+
"hasBundledConfig": status.hasBundledConfig,
|
|
785
|
+
"summaryKind": status.summaryKind.rawValue,
|
|
786
|
+
"summaryMessage": status.summaryMessage,
|
|
787
|
+
]
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private func hostConnectionCapabilitiesDictionary(_ capabilities: HostConnectionCapabilities) -> [String: Any] {
|
|
791
|
+
[
|
|
792
|
+
"canConnectUsingSavedConfig": capabilities.canConnectUsingSavedConfig,
|
|
793
|
+
"canConnectUsingBundledConfig": capabilities.canConnectUsingBundledConfig,
|
|
794
|
+
"canChooseConfigFile": capabilities.canChooseConfigFile,
|
|
795
|
+
"canScanConfigQrCode": capabilities.canScanConfigQrCode,
|
|
796
|
+
"canClearSavedConfigs": capabilities.canClearSavedConfigs,
|
|
797
|
+
]
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
private func hostConnectionResultDictionary(_ result: HostConnectionResult) -> [String: Any] {
|
|
801
|
+
var dictionary: [String: Any] = [
|
|
802
|
+
"success": result.success,
|
|
803
|
+
"message": result.message,
|
|
804
|
+
"kind": result.kind.rawValue,
|
|
805
|
+
"source": result.source.rawValue,
|
|
806
|
+
]
|
|
807
|
+
if let value = result.reasonCode ?? result.openSession?.reasonCode { dictionary["reasonCode"] = value }
|
|
808
|
+
if let session = result.openSession {
|
|
809
|
+
dictionary["accepted"] = session.accepted
|
|
810
|
+
dictionary["usedEmbeddedDeveloperPairing"] = session.usedEmbeddedDeveloperPairing
|
|
811
|
+
if let value = session.sessionId { dictionary["sessionId"] = value }
|
|
812
|
+
if let value = session.configId { dictionary["configId"] = value }
|
|
813
|
+
if let value = session.appId { dictionary["appId"] = value }
|
|
814
|
+
if let value = session.resolvedHostAddress { dictionary["resolvedHostAddress"] = value }
|
|
815
|
+
if let value = session.discoverySource { dictionary["discoverySource"] = value }
|
|
816
|
+
if let value = session.hostId { dictionary["hostId"] = value }
|
|
817
|
+
if let value = session.hostName { dictionary["hostName"] = value }
|
|
818
|
+
}
|
|
819
|
+
return dictionary
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
private func openSessionResultDictionary(_ result: OpenSessionResult) -> [String: Any] {
|
|
823
|
+
var dictionary: [String: Any] = [
|
|
824
|
+
"success": result.success,
|
|
825
|
+
"message": result.message,
|
|
826
|
+
"accepted": result.accepted,
|
|
827
|
+
"usedEmbeddedDeveloperPairing": result.usedEmbeddedDeveloperPairing,
|
|
828
|
+
]
|
|
829
|
+
if let value = result.sessionId { dictionary["sessionId"] = value }
|
|
830
|
+
if let value = result.configId { dictionary["configId"] = value }
|
|
831
|
+
if let value = result.appId { dictionary["appId"] = value }
|
|
832
|
+
if let value = result.resolvedHostAddress { dictionary["resolvedHostAddress"] = value }
|
|
833
|
+
if let value = result.discoverySource { dictionary["discoverySource"] = value }
|
|
834
|
+
if let value = result.reasonCode { dictionary["reasonCode"] = value }
|
|
835
|
+
if let value = result.hostId { dictionary["hostId"] = value }
|
|
836
|
+
if let value = result.hostName { dictionary["hostName"] = value }
|
|
837
|
+
return dictionary
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
private func operationResultDictionary(_ result: OperationResult) -> [String: Any] {
|
|
841
|
+
["success": result.success, "message": result.message]
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
private func optionsDictionary(_ options: AnsightOptions) -> [String: Any] {
|
|
845
|
+
[
|
|
846
|
+
"sampleFrequencyMilliseconds": options.sampleFrequencyMilliseconds,
|
|
847
|
+
"retentionPeriodSeconds": options.retentionPeriodSeconds,
|
|
848
|
+
"enableFramesPerSecond": options.enableFramesPerSecond,
|
|
849
|
+
"enableBatteryLevel": options.enableBatteryLevel,
|
|
850
|
+
"toolGuard": toolGuardName(options.toolGuard),
|
|
851
|
+
"additionalChannels": options.additionalChannels.map(channelDictionary),
|
|
852
|
+
"customProperties": options.customProperties,
|
|
853
|
+
"lifecycleCapture": [
|
|
854
|
+
"enabled": options.lifecycleCapture.enabled,
|
|
855
|
+
"captureAppLifecycle": options.lifecycleCapture.captureAppLifecycle,
|
|
856
|
+
"captureScreenViews": options.lifecycleCapture.captureScreenViews,
|
|
857
|
+
"minimumScreenViewIntervalMilliseconds": options.lifecycleCapture.minimumScreenViewIntervalMilliseconds,
|
|
858
|
+
],
|
|
859
|
+
"hostAutoProbe": [
|
|
860
|
+
"enabled": options.hostAutoProbe.enabled,
|
|
861
|
+
"initialDelayMilliseconds": options.hostAutoProbe.initialDelayMilliseconds,
|
|
862
|
+
"probeIntervalMilliseconds": options.hostAutoProbe.probeIntervalMilliseconds,
|
|
863
|
+
"reconnectDelayMilliseconds": options.hostAutoProbe.reconnectDelayMilliseconds,
|
|
864
|
+
],
|
|
865
|
+
"hostConnection": [
|
|
866
|
+
"savedConfigKey": options.hostConnection.savedConfigKey,
|
|
867
|
+
"connectionProfileRetentionSeconds": options.hostConnection.connectionProfileRetentionSeconds,
|
|
868
|
+
"hasBundledDeveloperConfigJson": options.hostConnection.bundledDeveloperConfigJson != nil,
|
|
869
|
+
"hasBundledConfigJson": options.hostConnection.bundledConfigJson != nil,
|
|
870
|
+
],
|
|
871
|
+
]
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
private func screenCaptureOptions(_ dictionary: NSDictionary?) -> AnsightSessionJpegCaptureOptions {
|
|
875
|
+
AnsightSessionJpegCaptureOptions(
|
|
876
|
+
intervalMilliseconds: intValue(
|
|
877
|
+
dictionary,
|
|
878
|
+
"intervalMilliseconds",
|
|
879
|
+
defaultValue: AnsightSessionJpegCaptureOptions.defaultIntervalMilliseconds
|
|
880
|
+
),
|
|
881
|
+
quality: intValue(
|
|
882
|
+
dictionary, "quality", defaultValue: AnsightSessionJpegCaptureOptions.defaultQuality
|
|
883
|
+
),
|
|
884
|
+
maxWidth: optionalInt(dictionary, "maxWidth") ?? AnsightSessionJpegCaptureOptions.defaultMaxWidth,
|
|
885
|
+
captureGpuBackedSurfaces: boolValue(
|
|
886
|
+
dictionary,
|
|
887
|
+
"captureGpuBackedSurfaces",
|
|
888
|
+
defaultValue: AnsightSessionJpegCaptureOptions.defaultCaptureGpuBackedSurfaces
|
|
889
|
+
)
|
|
890
|
+
)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
private func pairingOpenOptions(_ dictionary: NSDictionary?) -> PairingOpenOptions {
|
|
894
|
+
PairingOpenOptions(
|
|
895
|
+
clientName: stringValue(dictionary, "clientName") ?? "Capacitor",
|
|
896
|
+
expectedAppId: stringValue(dictionary, "expectedAppId"),
|
|
897
|
+
hostAddressOverride: stringValue(dictionary, "hostAddressOverride"),
|
|
898
|
+
discoveryPort: optionalInt(dictionary, "discoveryPort")
|
|
899
|
+
)
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private func channelFromDictionary(_ dictionary: NSDictionary) -> AnsightChannel {
|
|
903
|
+
AnsightChannel(
|
|
904
|
+
id: intValue(dictionary, "id", defaultValue: -1),
|
|
905
|
+
name: stringValue(dictionary, "name") ?? "",
|
|
906
|
+
colorHex: stringValue(dictionary, "colorHex"),
|
|
907
|
+
unit: stringValue(dictionary, "unit"),
|
|
908
|
+
type: stringValue(dictionary, "type") ?? "custom",
|
|
909
|
+
source: stringValue(dictionary, "source"),
|
|
910
|
+
group: stringValue(dictionary, "group"),
|
|
911
|
+
kind: stringValue(dictionary, "kind")
|
|
912
|
+
)
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
private func channelDictionary(_ channel: AnsightChannel) -> [String: Any] {
|
|
916
|
+
var result: [String: Any] = ["id": channel.id, "name": channel.name, "type": channel.type]
|
|
917
|
+
if let value = channel.unit { result["unit"] = value }
|
|
918
|
+
if let value = channel.colorHex { result["colorHex"] = value }
|
|
919
|
+
if let value = channel.source { result["source"] = value }
|
|
920
|
+
if let value = channel.group { result["group"] = value }
|
|
921
|
+
if let value = channel.kind { result["kind"] = value }
|
|
922
|
+
return result
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
private func metricDictionary(_ metric: RecordedMetric) -> [String: Any] {
|
|
926
|
+
[
|
|
927
|
+
"value": metric.value,
|
|
928
|
+
"capturedAtUtc": metric.capturedAtUtc,
|
|
929
|
+
"capturedAtEpochMs": metric.capturedAtEpochMs,
|
|
930
|
+
"channel": metric.channel,
|
|
931
|
+
"sequence": metric.sequence,
|
|
932
|
+
]
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
private func eventDictionary(_ event: RecordedEvent) -> [String: Any] {
|
|
936
|
+
var result: [String: Any] = [
|
|
937
|
+
"id": event.id,
|
|
938
|
+
"label": event.label,
|
|
939
|
+
"type": eventTypeName(event.type),
|
|
940
|
+
"capturedAtUtc": event.capturedAtUtc,
|
|
941
|
+
"capturedAtEpochMs": event.capturedAtEpochMs,
|
|
942
|
+
"channel": event.channel,
|
|
943
|
+
"sequence": event.sequence,
|
|
944
|
+
]
|
|
945
|
+
if let value = event.details { result["details"] = value }
|
|
946
|
+
if let value = event.externalId { result["externalId"] = value }
|
|
947
|
+
return result
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
private extension NSLock {
|
|
952
|
+
func withLock<T>(_ body: () -> T) -> T {
|
|
953
|
+
lock()
|
|
954
|
+
defer { unlock() }
|
|
955
|
+
return body()
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
private func dictionary(_ call: CAPPluginCall) -> NSDictionary {
|
|
960
|
+
call.dictionaryRepresentation
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
private func nestedDictionary(_ call: CAPPluginCall, _ key: String) -> NSDictionary? {
|
|
964
|
+
call.dictionaryRepresentation[key] as? NSDictionary
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
private func stringValue(_ dictionary: NSDictionary?, _ key: String) -> String? {
|
|
968
|
+
guard let value = dictionary?[key], !(value is NSNull) else { return nil }
|
|
969
|
+
let string = value as? String ?? "\(value)"
|
|
970
|
+
let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
971
|
+
return trimmed.isEmpty ? nil : trimmed
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
private func hasNumber(_ dictionary: NSDictionary?, _ key: String) -> Bool {
|
|
975
|
+
dictionary?[key] is NSNumber
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
private func hasBool(_ dictionary: NSDictionary?, _ key: String) -> Bool {
|
|
979
|
+
guard let number = dictionary?[key] as? NSNumber else { return false }
|
|
980
|
+
return CFGetTypeID(number) == CFBooleanGetTypeID()
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
private func boolValue(_ dictionary: NSDictionary?, _ key: String, defaultValue: Bool) -> Bool {
|
|
984
|
+
dictionary?[key] as? Bool ?? defaultValue
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
private func intValue(_ dictionary: NSDictionary?, _ key: String, defaultValue: Int) -> Int {
|
|
988
|
+
(dictionary?[key] as? NSNumber)?.intValue ?? defaultValue
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
private func optionalInt(_ dictionary: NSDictionary?, _ key: String) -> Int? {
|
|
992
|
+
(dictionary?[key] as? NSNumber)?.intValue
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
private func doubleValue(_ dictionary: NSDictionary?, _ key: String, defaultValue: Double) -> Double {
|
|
996
|
+
(dictionary?[key] as? NSNumber)?.doubleValue ?? defaultValue
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
private func stringDictionary(_ dictionary: NSDictionary?) -> [String: String] {
|
|
1000
|
+
guard let dictionary else { return [:] }
|
|
1001
|
+
var result: [String: String] = [:]
|
|
1002
|
+
for (key, value) in dictionary {
|
|
1003
|
+
guard let key = key as? String, !(value is NSNull) else { continue }
|
|
1004
|
+
result[key] = value as? String ?? "\(value)"
|
|
1005
|
+
}
|
|
1006
|
+
return result
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
private func groupedStringDictionary(_ dictionary: NSDictionary?) -> [String: [String: String]] {
|
|
1010
|
+
guard let dictionary else { return [:] }
|
|
1011
|
+
var result: [String: [String: String]] = [:]
|
|
1012
|
+
for (key, value) in dictionary {
|
|
1013
|
+
guard let key = key as? String, let group = value as? NSDictionary else { continue }
|
|
1014
|
+
result[key] = stringDictionary(group)
|
|
1015
|
+
}
|
|
1016
|
+
return result
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
private func stringArray(_ dictionary: NSDictionary?, _ key: String) -> [String] {
|
|
1020
|
+
(dictionary?[key] as? [Any])?.compactMap {
|
|
1021
|
+
guard !($0 is NSNull) else { return nil }
|
|
1022
|
+
let value = ($0 as? String ?? "\($0)").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1023
|
+
return value.isEmpty ? nil : value
|
|
1024
|
+
} ?? []
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
private func rootDictionaries(_ value: Any?) -> [NSDictionary] {
|
|
1028
|
+
(value as? [Any])?.compactMap { $0 as? NSDictionary } ?? []
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
private func toolSuiteEnabled(_ value: Any?, defaultValue: Bool) -> Bool {
|
|
1032
|
+
if let value = value as? Bool { return value }
|
|
1033
|
+
if let value = value as? NSDictionary {
|
|
1034
|
+
return boolValue(value, "enabled", defaultValue: true)
|
|
1035
|
+
}
|
|
1036
|
+
return defaultValue
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
private func eventType(_ value: String?) -> AnsightEventType {
|
|
1040
|
+
switch value?.lowercased() {
|
|
1041
|
+
case "event": return .event
|
|
1042
|
+
case "debug": return .debug
|
|
1043
|
+
case "warning", "warn": return .warning
|
|
1044
|
+
case "error": return .error
|
|
1045
|
+
case "exception": return .exception
|
|
1046
|
+
case "gc": return .gc
|
|
1047
|
+
case "navigation": return .navigation
|
|
1048
|
+
case "screenviewed", "screen_viewed": return .screenViewed
|
|
1049
|
+
case "lifecycle": return .lifecycle
|
|
1050
|
+
default: return .info
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private func eventTypeName(_ value: AnsightEventType) -> String {
|
|
1055
|
+
switch value {
|
|
1056
|
+
case .event: return "Event"
|
|
1057
|
+
case .debug: return "Debug"
|
|
1058
|
+
case .info: return "Info"
|
|
1059
|
+
case .warning: return "Warning"
|
|
1060
|
+
case .error: return "Error"
|
|
1061
|
+
case .exception: return "Exception"
|
|
1062
|
+
case .gc: return "Gc"
|
|
1063
|
+
case .navigation: return "Navigation"
|
|
1064
|
+
case .screenViewed: return "ScreenViewed"
|
|
1065
|
+
case .lifecycle: return "Lifecycle"
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
private func lifecycleState(_ value: String) -> AppLifecycleState {
|
|
1070
|
+
switch value.lowercased() {
|
|
1071
|
+
case "foreground", "active": return .foreground
|
|
1072
|
+
case "background", "inactive": return .background
|
|
1073
|
+
default: return .unknown
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
private func toolGuard(_ value: String) -> AnsightToolGuard {
|
|
1078
|
+
switch value.lowercased() {
|
|
1079
|
+
case "readonly", "read_only", "read": return .readOnly
|
|
1080
|
+
case "readwrite", "read_write", "write": return .readWrite
|
|
1081
|
+
case "full", "fullaccess", "full_access": return .fullAccess
|
|
1082
|
+
default: return .disabled
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
private func toolGuardName(_ value: AnsightToolGuard) -> String {
|
|
1087
|
+
if value == .disabled { return "disabled" }
|
|
1088
|
+
if value == .readOnly { return "readOnly" }
|
|
1089
|
+
if value == .readWrite { return "readWrite" }
|
|
1090
|
+
if value == .fullAccess { return "fullAccess" }
|
|
1091
|
+
return "custom"
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
private func toolScope(_ value: String?) -> AnsightToolScope {
|
|
1095
|
+
switch value?.lowercased() {
|
|
1096
|
+
case "write": return .write
|
|
1097
|
+
case "delete": return .delete
|
|
1098
|
+
default: return .read
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
private func toolSecurity(_ dictionary: NSDictionary?) -> AnsightToolSecurity {
|
|
1103
|
+
guard let dictionary else { return .unspecified }
|
|
1104
|
+
let level: AnsightToolSecurityLevel
|
|
1105
|
+
switch stringValue(dictionary, "level")?.lowercased() {
|
|
1106
|
+
case "low": level = .low
|
|
1107
|
+
case "medium", "moderate": level = .moderate
|
|
1108
|
+
case "high": level = .high
|
|
1109
|
+
case "critical": level = .critical
|
|
1110
|
+
default: level = .unspecified
|
|
1111
|
+
}
|
|
1112
|
+
return AnsightToolSecurity(
|
|
1113
|
+
level: level,
|
|
1114
|
+
summary: stringValue(dictionary, "summary") ?? "",
|
|
1115
|
+
implications: (dictionary["implications"] as? [Any])?.compactMap { $0 as? String } ?? []
|
|
1116
|
+
)
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
private func keywords(_ value: Any?) -> String {
|
|
1120
|
+
if let value = value as? String { return value }
|
|
1121
|
+
if let value = value as? [Any] { return value.compactMap { $0 as? String }.joined(separator: " ") }
|
|
1122
|
+
return "capacitor javascript custom tool"
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
private func jsonValue(_ value: Any?) -> JSONValue? {
|
|
1126
|
+
guard let value, !(value is NSNull) else { return nil }
|
|
1127
|
+
if let dictionary = value as? NSDictionary {
|
|
1128
|
+
var object: [String: JSONValue] = [:]
|
|
1129
|
+
for (key, value) in dictionary {
|
|
1130
|
+
guard let key = key as? String, let converted = jsonValue(value) else { continue }
|
|
1131
|
+
object[key] = converted
|
|
1132
|
+
}
|
|
1133
|
+
return .object(object)
|
|
1134
|
+
}
|
|
1135
|
+
if let array = value as? [Any] { return .array(array.map { jsonValue($0) ?? .null }) }
|
|
1136
|
+
if let string = value as? String { return .string(string) }
|
|
1137
|
+
if let number = value as? NSNumber {
|
|
1138
|
+
if CFGetTypeID(number) == CFBooleanGetTypeID() { return .bool(number.boolValue) }
|
|
1139
|
+
let double = number.doubleValue
|
|
1140
|
+
return double.rounded() == double ? .integer(number.int64Value) : .number(double)
|
|
1141
|
+
}
|
|
1142
|
+
if let bool = value as? Bool { return .bool(bool) }
|
|
1143
|
+
return .string("\(value)")
|
|
1144
|
+
}
|