@ansight/react-native 1.3.0-preview.9 → 1.4.0-preview.3

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.
@@ -137,8 +137,56 @@ final class AnsightReactNative: RCTEventEmitter {
137
137
  }
138
138
  }
139
139
 
140
+ private final class ReactNativeVisualTreeProvider: AnsightVisualTreeProvider, AnsightVisualTreeInteractionProvider, @unchecked Sendable {
141
+ let source = "react"
142
+ let displayName = "React Native"
143
+ private weak var module: AnsightReactNative?
144
+ private let timeoutMilliseconds: Int
145
+
146
+ init(module: AnsightReactNative, timeoutMilliseconds: Int) {
147
+ self.module = module
148
+ self.timeoutMilliseconds = timeoutMilliseconds
149
+ }
150
+
151
+ func getVisualTree(arguments: [String: String]) -> AnsightToolExecutionResult {
152
+ module?.executeJavaScriptTool(
153
+ toolId: "react.get_component_tree",
154
+ arguments: arguments,
155
+ timeoutMilliseconds: timeoutMilliseconds
156
+ ) ?? .failure("React Native bridge is unavailable.", errorCode: "javascript_bridge_unavailable")
157
+ }
158
+
159
+ func inspectNode(arguments: [String: String]) -> AnsightToolExecutionResult {
160
+ module?.executeJavaScriptTool(
161
+ toolId: "react.get_component",
162
+ arguments: arguments,
163
+ timeoutMilliseconds: timeoutMilliseconds
164
+ ) ?? .failure("React Native bridge is unavailable.", errorCode: "javascript_bridge_unavailable")
165
+ }
166
+
167
+ func performAction(_ request: AnsightVisualTreeActionRequest) -> AnsightToolExecutionResult {
168
+ let prop: String
169
+ switch request.action.lowercased() {
170
+ case "tap": prop = "onPress"
171
+ case "focus": prop = "onFocus"
172
+ case "setvalue", "typetext": prop = "onChangeText"
173
+ case "toggle": prop = "onValueChange"
174
+ default:
175
+ if case .string(let configured)? = request.options["prop"] { prop = configured } else { prop = request.action }
176
+ }
177
+ var arguments = ["nodeId": request.nodeId, "prop": prop]
178
+ if let value = request.value?.stringValue { arguments["value"] = value }
179
+ return module?.executeJavaScriptTool(
180
+ toolId: "react.invoke_component_action",
181
+ arguments: arguments,
182
+ timeoutMilliseconds: timeoutMilliseconds
183
+ ) ?? .failure("React Native bridge is unavailable.", errorCode: "javascript_bridge_unavailable")
184
+ }
185
+ }
186
+
140
187
  private let lock = NSLock()
141
188
  private var hasListeners = false
189
+ private var hostConnectionStatusSubscription: HostConnectionStatusSubscription?
142
190
  private var activeCustomToolIds: Set<String> = []
143
191
  private var pendingToolCalls: [String: PendingToolCall] = [:]
144
192
  private var reactNativeMemorySampler: ReactNativeMemorySamplerBox?
@@ -150,10 +198,14 @@ final class AnsightReactNative: RCTEventEmitter {
150
198
  override init() {
151
199
  super.init()
152
200
  AnsightLogger.registerCallback(logCallback)
201
+ hostConnectionStatusSubscription = AnsightRuntime.shared.addHostConnectionStatusListener { [weak self] status, _ in
202
+ self?.emitHostConnectionStatusEvent(status)
203
+ }
153
204
  }
154
205
 
155
206
  deinit {
156
207
  AnsightLogger.removeCallback(logCallback)
208
+ hostConnectionStatusSubscription?.remove()
157
209
  }
158
210
 
159
211
  override static func requiresMainQueueSetup() -> Bool {
@@ -161,7 +213,7 @@ final class AnsightReactNative: RCTEventEmitter {
161
213
  }
162
214
 
163
215
  override func supportedEvents() -> [String]! {
164
- ["AnsightToolCall", "AnsightLog"]
216
+ ["AnsightToolCall", "AnsightLog", "AnsightHostConnectionStatus"]
165
217
  }
166
218
 
167
219
  override func startObserving() {
@@ -292,6 +344,27 @@ final class AnsightReactNative: RCTEventEmitter {
292
344
  }
293
345
  }
294
346
 
347
+ @objc(recordNetworkRequest:resolver:rejecter:)
348
+ func recordNetworkRequest(
349
+ _ input: NSDictionary,
350
+ resolver resolve: @escaping RCTPromiseResolveBlock,
351
+ rejecter reject: RCTPromiseRejectBlock
352
+ ) {
353
+ guard JSONSerialization.isValidJSONObject(input),
354
+ let data = try? JSONSerialization.data(withJSONObject: input),
355
+ let request = try? JSONDecoder().decode(AnsightNetworkRequest.self, from: data),
356
+ request.schema == AnsightNetworkRequest.schemaName
357
+ else {
358
+ resolve(operationResultDictionary(
359
+ .failure("Network request must use the ansight.network-request.v1 schema.")
360
+ ))
361
+ return
362
+ }
363
+ Task {
364
+ resolve(operationResultDictionary(await AnsightRuntime.shared.recordNetworkRequest(request)))
365
+ }
366
+ }
367
+
295
368
  @objc(recordCrashCandidate:resolver:rejecter:)
296
369
  func recordCrashCandidate(
297
370
  _ input: NSDictionary,
@@ -676,6 +749,12 @@ final class AnsightReactNative: RCTEventEmitter {
676
749
  ReactNativeTool(descriptor: descriptor, module: self, timeoutMilliseconds: timeout),
677
750
  replaceExisting: true
678
751
  )
752
+ if descriptor.id == "react.get_component_tree" {
753
+ try AnsightVisualTreeProviderRegistry.register(
754
+ ReactNativeVisualTreeProvider(module: self, timeoutMilliseconds: timeout),
755
+ replaceExisting: true
756
+ )
757
+ }
679
758
  resolve(["id": descriptor.id, "registered": true])
680
759
  } catch {
681
760
  reject("ansight_error", error.localizedDescription, error)
@@ -859,6 +938,17 @@ final class AnsightReactNative: RCTEventEmitter {
859
938
  }
860
939
  }
861
940
 
941
+ private func emitHostConnectionStatusEvent(_ status: HostConnectionStatus) {
942
+ let shouldEmit = lock.withLock { hasListeners }
943
+ guard shouldEmit else { return }
944
+ DispatchQueue.main.async {
945
+ self.sendEvent(
946
+ withName: "AnsightHostConnectionStatus",
947
+ body: self.hostConnectionStatusDictionary(status)
948
+ )
949
+ }
950
+ }
951
+
862
952
  private func configureReactNativeMemoryProfiling(_ dictionary: NSDictionary?) throws {
863
953
  let options = ReactNativeMemoryProfilingOptions(dictionary: dictionary)
864
954
  currentReactNativeMemoryOptions = options
@@ -1137,11 +1227,11 @@ final class AnsightReactNative: RCTEventEmitter {
1137
1227
  name: stringValue(dictionary, "name") ?? stringValue(dictionary, "id") ?? "",
1138
1228
  description: stringValue(dictionary, "description") ?? "",
1139
1229
  category: stringValue(dictionary, "category") ?? "custom",
1140
- scope: toolScope(stringValue(dictionary, "scope")).rawValue,
1230
+ policy: toolPolicy(stringValue(dictionary, "policy")),
1141
1231
  keywords: keywords(dictionary["keywords"]),
1142
- security: toolSecurity(dictionary["security"] as? NSDictionary),
1143
1232
  argumentsSchema: AnsightToolSchema(json: jsonValue(dictionary["argumentsSchema"]) ?? .object([:])),
1144
- resultSchema: AnsightToolSchema(json: jsonValue(dictionary["resultSchema"]) ?? .object([:]))
1233
+ resultSchema: AnsightToolSchema(json: jsonValue(dictionary["resultSchema"]) ?? .object([:])),
1234
+ prerequisiteToolIds: stringArray(dictionary, "prerequisiteToolIds")
1145
1235
  )
1146
1236
  }
1147
1237
 
@@ -1647,41 +1737,17 @@ private func toolGuard(_ rawValue: String) -> AnsightToolGuard {
1647
1737
  }
1648
1738
  }
1649
1739
 
1650
- private func toolScope(_ rawValue: String?) -> AnsightToolScope {
1740
+ private func toolPolicy(_ rawValue: String?) -> AnsightToolPolicy {
1651
1741
  switch rawValue?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
1652
1742
  case "write":
1653
1743
  return .write
1654
- case "delete":
1655
- return .delete
1744
+ case "critical", "delete":
1745
+ return .critical
1656
1746
  default:
1657
1747
  return .read
1658
1748
  }
1659
1749
  }
1660
1750
 
1661
- private func toolSecurity(_ dictionary: NSDictionary?) -> AnsightToolSecurity {
1662
- guard let dictionary else {
1663
- return .unspecified
1664
- }
1665
- let level: AnsightToolSecurityLevel
1666
- switch stringValue(dictionary, "level")?.lowercased() {
1667
- case "low":
1668
- level = .low
1669
- case "medium", "moderate":
1670
- level = .moderate
1671
- case "high":
1672
- level = .high
1673
- case "critical":
1674
- level = .critical
1675
- default:
1676
- level = .unspecified
1677
- }
1678
- return AnsightToolSecurity(
1679
- level: level,
1680
- summary: stringValue(dictionary, "summary") ?? "",
1681
- implications: (dictionary["implications"] as? [Any])?.compactMap { $0 as? String } ?? []
1682
- )
1683
- }
1684
-
1685
1751
  private func keywords(_ value: Any?) -> String {
1686
1752
  if let string = value as? String {
1687
1753
  return string
@@ -33,6 +33,10 @@ RCT_EXTERN_METHOD(recordEvent:(NSDictionary *)input
33
33
  resolver:(RCTPromiseResolveBlock)resolve
34
34
  rejecter:(RCTPromiseRejectBlock)reject)
35
35
 
36
+ RCT_EXTERN_METHOD(recordNetworkRequest:(NSDictionary *)input
37
+ resolver:(RCTPromiseResolveBlock)resolve
38
+ rejecter:(RCTPromiseRejectBlock)reject)
39
+
36
40
  RCT_EXTERN_METHOD(recordCrashCandidate:(NSDictionary *)input
37
41
  resolver:(RCTPromiseResolveBlock)resolve
38
42
  rejecter:(RCTPromiseRejectBlock)reject)