@ansight/react-native 1.0.2-preview.8 → 1.2.0-preview.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,8 @@ JavaScript-backed tools for React component-tree inspection.
10
10
 
11
11
  ## Install
12
12
 
13
+ ### React Native CLI
14
+
13
15
  ```sh
14
16
  npm install @ansight/react-native
15
17
  ```
@@ -23,41 +25,78 @@ npx react-native run-ios
23
25
  npx react-native run-android
24
26
  ```
25
27
 
28
+ ### Expo development builds
29
+
30
+ Ansight contains native iOS and Android code, so it requires an Expo
31
+ development build or production/EAS build. It does not run in Expo Go.
32
+
33
+ ```sh
34
+ npx expo install @ansight/react-native
35
+ ```
36
+
37
+ Add the bundled config plugin to the app config. It supplies the iOS camera
38
+ usage description required by QR enrollment and the local-network description
39
+ used when connecting to Ansight Studio:
40
+
41
+ ```json
42
+ {
43
+ "expo": {
44
+ "plugins": [
45
+ [
46
+ "@ansight/react-native",
47
+ {
48
+ "cameraPermission": "Allow $(PRODUCT_NAME) to scan an Ansight Studio enrollment QR code.",
49
+ "localNetworkPermission": "Allow $(PRODUCT_NAME) to connect to Ansight Studio on your local network."
50
+ }
51
+ ]
52
+ ]
53
+ }
54
+ }
55
+ ```
56
+
57
+ Omit either message to use the default. Set `cameraPermission` to `false` only
58
+ when the app will never call `enrollFromQrCode` or `scanPairingQrCode`. Setting
59
+ a permission option to `false` prevents Ansight from adding that key; it does
60
+ not remove a value supplied by another plugin or by the app.
61
+
62
+ Generate and rebuild the native projects after installing or updating the SDK
63
+ or changing its plugin options:
64
+
65
+ ```sh
66
+ npx expo prebuild --clean
67
+ npx expo run:ios
68
+ npx expo run:android
69
+ ```
70
+
71
+ EAS Build applies the same plugin during prebuild. Expo Updates can update the
72
+ JavaScript integration after the native binary has been built with a compatible
73
+ version of `@ansight/react-native`.
74
+
75
+ Expo Web is not currently supported. Do not import this package from a web
76
+ bundle; its API requires the native Ansight module.
77
+
26
78
  This package version expects matching native SDK packages:
27
79
 
28
- - CocoaPods: `Ansight`, `AnsightObjC` version `1.0.2-preview.8`
29
- - Maven: `ai.ansight:ansight-android:1.0.2-preview.8`
80
+ - CocoaPods: `Ansight`, `AnsightObjC` version `1.2.0-preview.1`
81
+ - Maven: `ai.ansight:ansight-android:1.2.0-preview.1`
30
82
 
31
83
  ## Quickstart
32
84
 
33
85
  ```ts
34
86
  import Ansight from "@ansight/react-native";
35
87
 
36
- const isDevelopmentOnly = __DEV__;
37
-
38
88
  await Ansight.initializeAndActivate({
39
- useNativeAllInOneDefaults: isDevelopmentOnly,
89
+ useNativeAllInOneDefaults: __DEV__,
40
90
  clientName: "My React Native App",
41
- hostAutoProbe: {
42
- enabled: isDevelopmentOnly,
43
- initialDelayMilliseconds: 1000,
44
- probeIntervalMilliseconds: 5000,
45
- reconnectDelayMilliseconds: 10000,
46
- clientName: "My React Native App",
47
- },
48
- hostConnection: isDevelopmentOnly ? {
49
- bundledDeveloperConfigJson: process.env.EXPO_PUBLIC_ANSIGHT_PAIRING_CONFIG_JSON,
50
- } : undefined,
51
- toolGuard: isDevelopmentOnly ? "readOnly" : "disabled",
91
+ toolGuard: __DEV__ ? "readOnly" : "disabled",
52
92
  lifecycle: true,
53
93
  });
54
-
55
- await Ansight.connect(null, {
56
- clientName: "My React Native App",
57
- expectedAppId: "com.example.app",
58
- });
59
94
  ```
60
95
 
96
+ The native iOS Simulator or Android emulator runtime registers automatically
97
+ with a running, signed-in Studio. No pairing file, environment variable, host
98
+ address, or build-time Studio probe is required.
99
+
61
100
  `useNativeAllInOneDefaults` defaults to `false`. It only applies the native
62
101
  iOS/Android all-in-one defaults: 400 ms sampling, 120 second retention, FPS,
63
102
  touch capture, 2000 ms JPEG capture at quality 60 and max width 480, host
@@ -92,7 +131,6 @@ The TypeScript `AnsightOptions` surface mirrors Android `AnsightOptions`, iOS
92
131
  | Option | Purpose |
93
132
  | --- | --- |
94
133
  | `useNativeAllInOneDefaults` | Applies native iOS/Android all-in-one defaults when true. Defaults to false. This enables the native visual-tree/screenshot tools required by Studio visual tree inspection unless `remoteTools.visualTree` is explicitly false. Configure `toolGuard`, capture options, and `hostConnection` separately. |
95
- | `pairingConfigJson` | Legacy top-level pairing JSON. Prefer `hostConnection.*`. |
96
134
  | `clientName` | Default client name for host auto-probe and connections. |
97
135
  | `sampleFrequencyMilliseconds` | Built-in telemetry sampling interval. |
98
136
  | `retentionPeriodSeconds` | Local metric/event retention window. |
@@ -107,14 +145,14 @@ The TypeScript `AnsightOptions` surface mirrors Android `AnsightOptions`, iOS
107
145
  | `toolGuard` | `"disabled"`, `"readOnly"`, `"readWrite"`, or `"fullAccess"`. |
108
146
  | `customProperties` | Grouped string properties sent with `session.open`. |
109
147
  | `hostAutoProbe` | Remembered-host retry settings for reconnecting after the host disappears and later reappears. |
110
- | `hostConnection` | Saved, bundled, and developer pairing settings. |
148
+ | `hostConnection` | Enrollment reconnect and network policy. |
111
149
  | `secureStorage` | Compatibility alias for native secure-storage allow-list settings. |
112
150
  | `remoteTools` | Native visual tree, file, database, preferences, reflection, and secure-storage tool options. |
113
151
  | `lifecycle` | JS AppState tracking toggle. Defaults to true. |
114
152
 
115
153
  Cellular host connections are disabled by default. The restriction applies to
116
- bundled configs, QR/payload flows, remembered/saved profiles, and manual
117
- connections. Opt in only for a trusted development host or personal hotspot:
154
+ enrollment and reconnect requests. Opt in only for a trusted development host
155
+ or personal hotspot:
118
156
 
119
157
  ```ts
120
158
  const options = Ansight.createOptionsBuilder()
@@ -124,7 +162,7 @@ const options = Ansight.createOptionsBuilder()
124
162
 
125
163
  You can also set `hostConnection.allowCellularConnections: true` directly.
126
164
  This may consume mobile data and permits connection attempts over a broader or
127
- carrier-managed network; signed pairing-config validation still applies.
165
+ carrier-managed network; use it only with a trusted development host.
128
166
 
129
167
  Example:
130
168
 
@@ -206,36 +244,36 @@ compatibility alias for `remoteTools.secureStorage`.
206
244
 
207
245
  ## Host Connection
208
246
 
209
- Use `connect(null, options)` for the default automatic flow:
247
+ No connection call is needed for a simulator or emulator. On a physical
248
+ device, scan the QR displayed by Studio once:
210
249
 
211
250
  ```ts
212
- await Ansight.connect(null, {
251
+ await Ansight.enrollFromQrCode({
213
252
  clientName: "My React Native App",
214
253
  expectedAppId: "com.example.app",
215
254
  });
216
255
  ```
217
256
 
218
- Automatic connection tries:
257
+ After physical-device enrollment, `connect(null, options)` and the runtime
258
+ connection loop use the remembered registration:
219
259
 
220
- 1. `hostConnection.bundledDeveloperConfigJson`
221
- 2. native remembered host profiles, newest first
222
- 3. saved pairing config
223
- 4. `hostConnection.bundledConfigJson`
260
+ ```ts
261
+ await Ansight.connect(null, { clientName: "My React Native App" });
262
+ ```
224
263
 
225
- Use explicit payloads for QR, paste, or app-owned import flows:
264
+ When Studio is closed or signed out, automatic attempts remain dormant and
265
+ retry later without failing the React Native app.
266
+
267
+ If the app already owns a scanner, pass its result through the explicit payload
268
+ API:
226
269
 
227
270
  ```ts
228
- await Ansight.connect(pairingJson, {
271
+ await Ansight.connect(enrollmentPayload, {
229
272
  clientName: "My React Native App",
230
273
  expectedAppId: "com.example.app",
231
274
  hostAddressOverride: "192.168.1.20",
232
275
  });
233
276
 
234
- await Ansight.savePairingConfig(pairingJson, {
235
- expectedAppId: "com.example.app",
236
- });
237
-
238
- await Ansight.clearSavedPairing();
239
277
  await Ansight.clearCachedSession();
240
278
  await Ansight.disconnect();
241
279
  ```
@@ -541,3 +579,14 @@ The first-party validation app lives in:
541
579
  It exercises the native runtime bridge, standard native remote tools,
542
580
  JavaScript custom tools, React visual-tree tools, SQLite/file fixtures,
543
581
  screenshot capture, and touch/session telemetry.
582
+
583
+ The current-Expo validation app lives in:
584
+
585
+ ```text
586
+ /Users/matthewrobbins/Development/git/ansight-sdk-test-apps/react-native/ansight-expo-harness
587
+ ```
588
+
589
+ It is pinned to Expo SDK 57 and React Native 0.86 with the New Architecture and
590
+ Hermes enabled. It validates Expo CNG/autolinking, the bundled config plugin,
591
+ Android and iOS native builds, and the same live Studio feature surface as the
592
+ baseline harness.
@@ -66,5 +66,7 @@ repositories {
66
66
 
67
67
  dependencies {
68
68
  implementation("com.facebook.react:react-android")
69
- implementation("ai.ansight:ansight-android:1.0.2-preview.8")
69
+ def ansightAndroidVersion =
70
+ findProperty("ansightAndroidVersion") ?: "1.2.0-preview.1"
71
+ implementation("ai.ansight:ansight-android:1.2.0-preview.1")
70
72
  }
@@ -38,6 +38,7 @@ import ai.ansight.runtime.ToolScope
38
38
  import ai.ansight.runtime.ToolSecurity
39
39
  import ai.ansight.runtime.ToolSecurityLevel
40
40
  import ai.ansight.runtime.sendBinaryTransfer
41
+ import ai.ansight.pairing.AnsightPairing
41
42
  import ai.ansight.tools.database.AndroidDatabaseRoot
42
43
  import ai.ansight.tools.database.AndroidDatabaseToolsOptions
43
44
  import ai.ansight.tools.database.withDatabaseTools
@@ -74,6 +75,7 @@ import java.util.concurrent.CountDownLatch
74
75
  import java.util.concurrent.Executors
75
76
  import java.util.concurrent.TimeUnit
76
77
  import java.util.concurrent.atomic.AtomicInteger
78
+ import java.util.concurrent.atomic.AtomicBoolean
77
79
  import java.util.Locale
78
80
 
79
81
  class AnsightReactNativeModule(
@@ -263,6 +265,56 @@ class AnsightReactNativeModule(
263
265
  }
264
266
  }
265
267
 
268
+ @ReactMethod
269
+ fun scanPairingQrCode(options: ReadableMap?, promise: Promise) {
270
+ val activity = reactContext.currentActivity
271
+ if (activity == null) {
272
+ promise.reject("ansight_qr_unavailable", "QR pairing is unavailable because no Android activity is available.")
273
+ return
274
+ }
275
+
276
+ val completed = AtomicBoolean(false)
277
+ AnsightPairing.scanQrCode(
278
+ activity = activity,
279
+ onPayload = { payload ->
280
+ if (!completed.compareAndSet(false, true)) {
281
+ return@scanQrCode
282
+ }
283
+ if (payload.isNullOrBlank()) {
284
+ promise.resolve(
285
+ hostConnectionResultMap(
286
+ HostConnectionResult.failure(
287
+ message = "QR pairing canceled.",
288
+ reasonCode = "pairing_canceled",
289
+ ),
290
+ ),
291
+ )
292
+ return@scanQrCode
293
+ }
294
+ backgroundExecutor.execute {
295
+ runCatching {
296
+ hostConnectionResultMap(
297
+ AnsightRuntime.connect(
298
+ HostConnectionRequest(
299
+ kind = HostConnectionRequestKind.QrCode,
300
+ payload = payload,
301
+ clientName = options.stringValue("clientName"),
302
+ expectedAppId = options.stringValue("expectedAppId"),
303
+ hostAddressOverride = options.stringValue("hostAddressOverride"),
304
+ ),
305
+ ),
306
+ )
307
+ }.resolve(promise)
308
+ }
309
+ },
310
+ onError = { error ->
311
+ if (completed.compareAndSet(false, true)) {
312
+ promise.reject("ansight_qr_error", error.message, error)
313
+ }
314
+ },
315
+ )
316
+ }
317
+
266
318
  @ReactMethod
267
319
  fun openSession(pairingPayload: String?, options: ReadableMap?, promise: Promise) {
268
320
  backgroundExecutor.execute {
@@ -616,23 +668,14 @@ class AnsightReactNativeModule(
616
668
 
617
669
  private fun buildOptions(map: ReadableMap?): AnsightOptions {
618
670
  val useNativeAllInOneDefaults = map.booleanValue("useNativeAllInOneDefaults", false)
619
- val pairingConfigJson = map.stringValue("pairingConfigJson")
620
671
  val clientName = map.stringValue("clientName")
621
672
  var options = if (useNativeAllInOneDefaults) {
622
673
  AnsightDeveloperMode.options(
623
- bundledDeveloperConfigJson = pairingConfigJson,
624
674
  clientName = clientName,
625
675
  )
626
676
  } else {
627
677
  AnsightOptions()
628
678
  }
629
- if (!useNativeAllInOneDefaults && !pairingConfigJson.isNullOrBlank()) {
630
- options = options.copy(
631
- hostConnection = options.hostConnection.copy(
632
- bundledConfigJson = pairingConfigJson,
633
- ),
634
- )
635
- }
636
679
  if (useNativeAllInOneDefaults && !map.hasString("toolGuard")) {
637
680
  options = options.copy(toolGuard = AnsightToolGuard.ReadOnly)
638
681
  }
@@ -747,7 +790,6 @@ class AnsightReactNativeModule(
747
790
  hostConnection = AnsightHostConnectionOptions(
748
791
  savedConfigKey = host.stringValue("savedConfigKey") ?: options.hostConnection.savedConfigKey,
749
792
  bundledConfigJson = host.stringValue("bundledConfigJson") ?: options.hostConnection.bundledConfigJson,
750
- bundledDeveloperConfigJson = host.stringValue("bundledDeveloperConfigJson") ?: options.hostConnection.bundledDeveloperConfigJson,
751
793
  discoveryPort = host.optionalInt("discoveryPort") ?: options.hostConnection.discoveryPort,
752
794
  allowCellularConnections = host.booleanValue(
753
795
  "allowCellularConnections",
@@ -1018,7 +1060,6 @@ class AnsightReactNativeModule(
1018
1060
  putString("configId", result.configId)
1019
1061
  putString("appId", result.appId)
1020
1062
  putString("resolvedHostAddress", result.resolvedHostAddress)
1021
- putBoolean("usedEmbeddedDeveloperPairing", result.usedEmbeddedDeveloperPairing)
1022
1063
  putString("discoverySource", result.discoverySource)
1023
1064
  putString("reasonCode", result.reasonCode)
1024
1065
  putString("hostId", result.hostId)
@@ -1038,7 +1079,6 @@ class AnsightReactNativeModule(
1038
1079
  putString("hostId", session.hostId)
1039
1080
  putString("hostName", session.hostName)
1040
1081
  putBoolean("accepted", session.accepted)
1041
- putBoolean("usedEmbeddedDeveloperPairing", session.usedEmbeddedDeveloperPairing)
1042
1082
  putString("discoverySource", session.discoverySource)
1043
1083
  }
1044
1084
  }
@@ -1097,7 +1137,6 @@ class AnsightReactNativeModule(
1097
1137
  putMap("hostConnection", mapOf(
1098
1138
  "savedConfigKey" to options.hostConnection.savedConfigKey,
1099
1139
  "hasBundledConfigJson" to (options.hostConnection.bundledConfigJson != null),
1100
- "hasBundledDeveloperConfigJson" to (options.hostConnection.bundledDeveloperConfigJson != null),
1101
1140
  "discoveryPort" to options.hostConnection.discoveryPort,
1102
1141
  "allowCellularConnections" to options.hostConnection.allowCellularConnections,
1103
1142
  "connectionProfileRetentionSeconds" to options.hostConnection.connectionProfileRetentionSeconds,
@@ -1163,7 +1202,7 @@ class AnsightReactNativeModule(
1163
1202
  ?: error("React Native application context is not an Android Application.")
1164
1203
 
1165
1204
  private fun bindCurrentActivity() {
1166
- val activity: Activity = currentActivity ?: return
1205
+ val activity: Activity = reactContext.currentActivity ?: return
1167
1206
  AnsightRuntime.bindActivity(activity)
1168
1207
  }
1169
1208
  }
package/app.plugin.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ module.exports = require("./expo-plugin");
package/expo-plugin.js ADDED
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ const DEFAULT_CAMERA_PERMISSION =
4
+ "Allow $(PRODUCT_NAME) to use the camera to scan an Ansight Studio enrollment QR code.";
5
+ const DEFAULT_LOCAL_NETWORK_PERMISSION =
6
+ "Allow $(PRODUCT_NAME) to connect to Ansight Studio on your local network during development.";
7
+
8
+ function withAnsightReactNative(config, options = {}) {
9
+ const { withInfoPlist } = loadExpoConfigPlugins();
10
+
11
+ return withInfoPlist(config, (modConfig) => {
12
+ const infoPlist = modConfig.modResults;
13
+
14
+ applyPermission(
15
+ infoPlist,
16
+ "NSCameraUsageDescription",
17
+ options.cameraPermission,
18
+ DEFAULT_CAMERA_PERMISSION,
19
+ );
20
+ applyPermission(
21
+ infoPlist,
22
+ "NSLocalNetworkUsageDescription",
23
+ options.localNetworkPermission,
24
+ DEFAULT_LOCAL_NETWORK_PERMISSION,
25
+ );
26
+
27
+ return modConfig;
28
+ });
29
+ }
30
+
31
+ function loadExpoConfigPlugins() {
32
+ const modulePath = require.resolve("expo/config-plugins", {
33
+ paths: [process.cwd(), __dirname],
34
+ });
35
+ return require(modulePath);
36
+ }
37
+
38
+ function applyPermission(infoPlist, key, configuredValue, defaultValue) {
39
+ if (configuredValue === false) {
40
+ return;
41
+ }
42
+
43
+ if (typeof configuredValue === "string" && configuredValue.trim()) {
44
+ infoPlist[key] = configuredValue;
45
+ return;
46
+ }
47
+
48
+ if (typeof infoPlist[key] !== "string" || !infoPlist[key].trim()) {
49
+ infoPlist[key] = defaultValue;
50
+ }
51
+ }
52
+
53
+ module.exports = withAnsightReactNative;
54
+ module.exports.default = withAnsightReactNative;
55
+ module.exports.DEFAULT_CAMERA_PERMISSION = DEFAULT_CAMERA_PERMISSION;
56
+ module.exports.DEFAULT_LOCAL_NETWORK_PERMISSION = DEFAULT_LOCAL_NETWORK_PERMISSION;
package/index.d.ts CHANGED
@@ -104,9 +104,6 @@ export interface AnsightOptions {
104
104
  * `hostAutoProbe`, and `hostConnection` explicitly for the workflow.
105
105
  */
106
106
  useNativeAllInOneDefaults?: boolean;
107
- pairingConfig?: string | object;
108
- bundledPairingConfig?: string | object;
109
- pairingConfigJson?: string;
110
107
  clientName?: string;
111
108
  sampleFrequencyMilliseconds?: number;
112
109
  retentionPeriodSeconds?: number;
@@ -141,7 +138,6 @@ export interface AnsightOptions {
141
138
  hostConnection?: {
142
139
  savedConfigKey?: string;
143
140
  bundledConfigJson?: string;
144
- bundledDeveloperConfigJson?: string;
145
141
  discoveryPort?: number;
146
142
  allowCellularConnections?: boolean;
147
143
  connectionProfileRetentionSeconds?: number;
@@ -209,7 +205,6 @@ export interface AnsightHostConnectionResult extends AnsightOperationResult {
209
205
  configId?: string;
210
206
  appId?: string;
211
207
  resolvedHostAddress?: string;
212
- usedEmbeddedDeveloperPairing?: boolean;
213
208
  discoverySource?: string;
214
209
  hostId?: string;
215
210
  hostName?: string;
@@ -290,7 +285,6 @@ export class AnsightOptionsBuilder {
290
285
  ) => NonNullable<AnsightOptions["hostConnection"]> | void
291
286
  ): this;
292
287
  withBundledHostConnection(options?: {
293
- bundledDeveloperConfigJson?: string;
294
288
  bundledConfigJson?: string;
295
289
  }): this;
296
290
  withHostConnectionDiscoveryPort(discoveryPort: number): this;
@@ -475,6 +469,10 @@ export interface AnsightConnectOptions {
475
469
  hostAddressOverride?: string;
476
470
  }
477
471
 
472
+ export interface AnsightScanPairingOptions extends AnsightConnectOptions {
473
+ title?: string;
474
+ }
475
+
478
476
  export interface AnsightOpenSessionOptions extends AnsightConnectOptions {}
479
477
 
480
478
  export interface AnsightSavePairingOptions {
@@ -516,6 +514,9 @@ export function screenViewed(name: string, details?: Record<string, string>): Pr
516
514
  export function trackRoute(name: string, details?: Record<string, string>): Promise<AnsightDebugSnapshot>;
517
515
  export function setAppLifecycleState(state: AnsightLifecycleState): Promise<AnsightDebugSnapshot>;
518
516
  export function connect(pairingPayload?: string | object | null, options?: AnsightConnectOptions): Promise<AnsightHostConnectionResult>;
517
+ /** Scans a one-use Studio invite, registers this installation, and saves automatic reconnect state. */
518
+ export function enrollFromQrCode(options?: AnsightScanPairingOptions): Promise<AnsightHostConnectionResult>;
519
+ export function scanPairingQrCode(options?: AnsightScanPairingOptions): Promise<AnsightHostConnectionResult>;
519
520
  export function openSession(pairingPayload: string | object, options?: AnsightOpenSessionOptions): Promise<AnsightHostConnectionResult>;
520
521
  export function disconnect(): Promise<AnsightHostConnectionResult>;
521
522
  export function completeSession(): Promise<AnsightOperationResult>;
@@ -586,6 +587,8 @@ declare const Ansight: {
586
587
  trackRoute: typeof trackRoute;
587
588
  setAppLifecycleState: typeof setAppLifecycleState;
588
589
  connect: typeof connect;
590
+ enrollFromQrCode: typeof enrollFromQrCode;
591
+ scanPairingQrCode: typeof scanPairingQrCode;
589
592
  openSession: typeof openSession;
590
593
  disconnect: typeof disconnect;
591
594
  completeSession: typeof completeSession;
package/index.js CHANGED
@@ -1,6 +1,15 @@
1
1
  "use strict";
2
2
 
3
- const { AppState, NativeEventEmitter, NativeModules, Platform, UIManager, findNodeHandle } = require("react-native");
3
+ const {
4
+ AppState,
5
+ NativeEventEmitter,
6
+ NativeModules,
7
+ Platform,
8
+ StyleSheet,
9
+ UIManager,
10
+ findNodeHandle,
11
+ processColor,
12
+ } = require("react-native");
4
13
 
5
14
  const nativeModule = NativeModules.AnsightReactNative;
6
15
 
@@ -35,16 +44,7 @@ function normalizePairingPayload(payload) {
35
44
  }
36
45
 
37
46
  function normalizeOptions(options = {}) {
38
- const normalized = { ...options };
39
- if (normalized.pairingConfig != null) {
40
- normalized.pairingConfigJson = normalizePairingPayload(normalized.pairingConfig);
41
- delete normalized.pairingConfig;
42
- }
43
- if (normalized.bundledPairingConfig != null) {
44
- normalized.pairingConfigJson = normalizePairingPayload(normalized.bundledPairingConfig);
45
- delete normalized.bundledPairingConfig;
46
- }
47
- return normalized;
47
+ return { ...options };
48
48
  }
49
49
 
50
50
  function cloneOptions(options = {}) {
@@ -383,10 +383,9 @@ class AnsightOptionsBuilder {
383
383
  return this;
384
384
  }
385
385
 
386
- withBundledHostConnection({ bundledDeveloperConfigJson = undefined, bundledConfigJson = undefined } = {}) {
386
+ withBundledHostConnection({ bundledConfigJson = undefined } = {}) {
387
387
  return this.configureHostConnection((hostConnection) => ({
388
388
  ...hostConnection,
389
- bundledDeveloperConfigJson,
390
389
  bundledConfigJson,
391
390
  }));
392
391
  }
@@ -1402,6 +1401,56 @@ function summarizeProps(props) {
1402
1401
  return summary;
1403
1402
  }
1404
1403
 
1404
+ function reactColorToArgbHex(value) {
1405
+ if (value == null) {
1406
+ return undefined;
1407
+ }
1408
+ try {
1409
+ const processed = typeof processColor === "function" ? processColor(value) : null;
1410
+ if (typeof processed === "number") {
1411
+ return `#${(processed >>> 0).toString(16).padStart(8, "0")}`.toUpperCase();
1412
+ }
1413
+ } catch (_) {
1414
+ // Some dynamic platform colors cannot be represented as one resolved color.
1415
+ }
1416
+ return undefined;
1417
+ }
1418
+
1419
+ function reactVisualText(fiber, props, maxStringLength) {
1420
+ if (fiber.tag === 6) {
1421
+ return sanitizeString(String(fiber.memoizedProps || fiber.pendingProps || ""), maxStringLength);
1422
+ }
1423
+ const children = summarizeReactChildren(props.children);
1424
+ const candidate = typeof children === "string" || typeof children === "number"
1425
+ ? children
1426
+ : props.placeholder ?? props.title ?? props.accessibilityLabel;
1427
+ return candidate == null ? undefined : sanitizeString(String(candidate), maxStringLength);
1428
+ }
1429
+
1430
+ function createReactVisual(fiber, props, maxStringLength) {
1431
+ const style = StyleSheet && typeof StyleSheet.flatten === "function"
1432
+ ? StyleSheet.flatten(props && props.style)
1433
+ : props && props.style;
1434
+ const opacity = style && Number.isFinite(Number(style.opacity))
1435
+ ? Math.max(0, Math.min(1, Number(style.opacity)))
1436
+ : 1;
1437
+ const visual = { opacity };
1438
+ const foreground = reactColorToArgbHex(style && style.color);
1439
+ const background = reactColorToArgbHex(style && style.backgroundColor);
1440
+ const text = reactVisualText(fiber, props || {}, maxStringLength);
1441
+ if (foreground) visual.foreground = foreground;
1442
+ if (background) visual.background = background;
1443
+ if (text) visual.text = text;
1444
+
1445
+ if (props && !props.secureTextEntry) {
1446
+ const value = props.value ?? props.defaultValue;
1447
+ if (value != null && ["string", "number", "boolean"].includes(typeof value)) {
1448
+ visual.value = sanitizeString(String(value), maxStringLength);
1449
+ }
1450
+ }
1451
+ return visual;
1452
+ }
1453
+
1405
1454
  function nativeTagForFiber(fiber) {
1406
1455
  const stateNode = fiber && fiber.stateNode;
1407
1456
  if (!stateNode) {
@@ -1481,6 +1530,7 @@ function serializeFiber(fiber, context, depth) {
1481
1530
  tag: fiber.tag,
1482
1531
  key: fiber.key == null ? null : String(fiber.key),
1483
1532
  depth,
1533
+ visual: createReactVisual(fiber, props, context.maxStringLength),
1484
1534
  children: [],
1485
1535
  };
1486
1536
 
@@ -1555,6 +1605,7 @@ function createShadowTreeNode(fiber, context, depth) {
1555
1605
  tag: fiber.tag,
1556
1606
  key: fiber.key == null ? null : String(fiber.key),
1557
1607
  depth,
1608
+ visual: createReactVisual(fiber, props, context.maxStringLength),
1558
1609
  children: [],
1559
1610
  };
1560
1611
 
@@ -2181,6 +2232,14 @@ function connect(pairingPayload, options = {}) {
2181
2232
  return notifyAfterHostConnectionChange(() => nativeModule.connect(normalizePairingPayload(pairingPayload), options));
2182
2233
  }
2183
2234
 
2235
+ function scanPairingQrCode(options = {}) {
2236
+ return notifyAfterHostConnectionChange(() => nativeModule.scanPairingQrCode(options));
2237
+ }
2238
+
2239
+ function enrollFromQrCode(options = {}) {
2240
+ return scanPairingQrCode(options);
2241
+ }
2242
+
2184
2243
  function openSession(pairingPayload, options = {}) {
2185
2244
  return notifyAfterHostConnectionChange(() => nativeModule.openSession(normalizePairingPayload(pairingPayload), options));
2186
2245
  }
@@ -2238,6 +2297,8 @@ const Ansight = {
2238
2297
  trackRoute,
2239
2298
  setAppLifecycleState: (state) => nativeModule.setAppLifecycleState(state),
2240
2299
  connect,
2300
+ enrollFromQrCode,
2301
+ scanPairingQrCode,
2241
2302
  openSession,
2242
2303
  disconnect: () => notifyAfterHostConnectionChange(() => nativeModule.disconnect()),
2243
2304
  completeSession: () => notifyAfterHostConnectionChange(() => nativeModule.completeSession()),
@@ -2302,6 +2363,8 @@ module.exports.default = Ansight;
2302
2363
  module.exports.AnsightOptionsBuilder = AnsightOptionsBuilder;
2303
2364
  module.exports.createOptionsBuilder = createOptionsBuilder;
2304
2365
  module.exports.notifyHostConnectionConfigChanged = Ansight.notifyHostConnectionConfigChanged;
2366
+ module.exports.enrollFromQrCode = Ansight.enrollFromQrCode;
2367
+ module.exports.scanPairingQrCode = Ansight.scanPairingQrCode;
2305
2368
  module.exports.addHostConnectionStatusListener = addHostConnectionStatusListener;
2306
2369
  module.exports.addLogListener = addLogListener;
2307
2370
  module.exports.registerArtifactProvider = registerArtifactProvider;
@@ -350,6 +350,24 @@ final class AnsightReactNative: RCTEventEmitter {
350
350
  }
351
351
  }
352
352
 
353
+ @objc(scanPairingQrCode:resolver:rejecter:)
354
+ func scanPairingQrCode(
355
+ _ options: NSDictionary?,
356
+ resolver resolve: @escaping RCTPromiseResolveBlock,
357
+ rejecter reject: @escaping RCTPromiseRejectBlock
358
+ ) {
359
+ Task {
360
+ let request = HostConnectionRequest.qrCode(
361
+ title: stringValue(options, "title") ?? "Scan Ansight Enrollment QR",
362
+ clientName: stringValue(options, "clientName"),
363
+ expectedAppId: stringValue(options, "expectedAppId"),
364
+ hostAddressOverride: stringValue(options, "hostAddressOverride"),
365
+ sourceDescription: "React Native native QR scanner"
366
+ )
367
+ resolve(hostConnectionResultDictionary(await AnsightRuntime.shared.connect(request)))
368
+ }
369
+ }
370
+
353
371
  @objc(openSession:options:resolver:rejecter:)
354
372
  func openSession(
355
373
  _ pairingPayload: NSString?,
@@ -411,7 +429,7 @@ final class AnsightReactNative: RCTEventEmitter {
411
429
  AnsightRuntime.shared.clearSavedPairing()
412
430
  resolve(hostConnectionResultDictionary(HostConnectionResult(
413
431
  success: true,
414
- message: "Saved pairing config cleared.",
432
+ message: "Saved registration cleared.",
415
433
  kind: .savedConfig,
416
434
  source: .savedConfig
417
435
  )))
@@ -859,13 +877,6 @@ final class AnsightReactNative: RCTEventEmitter {
859
877
  let useNativeAllInOneDefaults = boolValue(dictionary, "useNativeAllInOneDefaults", defaultValue: false)
860
878
  var options = useNativeAllInOneDefaults ? AnsightOptions.ansightDeveloperDefaults : AnsightOptions()
861
879
 
862
- if let value = stringValue(dictionary, "pairingConfigJson") {
863
- if useNativeAllInOneDefaults {
864
- options.hostConnection.bundledDeveloperConfigJson = value
865
- } else {
866
- options.hostConnection.bundledConfigJson = value
867
- }
868
- }
869
880
  if useNativeAllInOneDefaults && stringValue(dictionary, "toolGuard") == nil {
870
881
  options.toolGuard = .readOnly
871
882
  }
@@ -983,7 +994,6 @@ final class AnsightReactNative: RCTEventEmitter {
983
994
  "allowCellularConnections",
984
995
  defaultValue: options.hostConnection.allowCellularConnections
985
996
  ),
986
- bundledDeveloperConfigJson: stringValue(host, "bundledDeveloperConfigJson") ?? options.hostConnection.bundledDeveloperConfigJson,
987
997
  bundledConfigJson: stringValue(host, "bundledConfigJson") ?? options.hostConnection.bundledConfigJson
988
998
  )
989
999
  }
@@ -1092,7 +1102,6 @@ final class AnsightReactNative: RCTEventEmitter {
1092
1102
  "executableTools": snapshot.executableTools,
1093
1103
  "toolDiscoveryEnabled": snapshot.toolDiscoveryEnabled,
1094
1104
  "toolExecutionEnabled": snapshot.toolExecutionEnabled,
1095
- "embeddedDeveloperPairingAvailable": snapshot.embeddedDeveloperPairingAvailable,
1096
1105
  "detectedBundledTools": snapshot.detectedBundledTools,
1097
1106
  "touchesRecorded": snapshot.touchesCaptured,
1098
1107
  "touchesCaptured": snapshot.touchesCaptured,
@@ -1177,7 +1186,6 @@ final class AnsightReactNative: RCTEventEmitter {
1177
1186
  }
1178
1187
  if let session = result.openSession {
1179
1188
  dictionary["accepted"] = session.accepted
1180
- dictionary["usedEmbeddedDeveloperPairing"] = session.usedEmbeddedDeveloperPairing
1181
1189
  if let value = session.sessionId {
1182
1190
  dictionary["sessionId"] = value
1183
1191
  }
@@ -1208,7 +1216,6 @@ final class AnsightReactNative: RCTEventEmitter {
1208
1216
  "success": result.success,
1209
1217
  "message": result.message,
1210
1218
  "accepted": result.accepted,
1211
- "usedEmbeddedDeveloperPairing": result.usedEmbeddedDeveloperPairing,
1212
1219
  ]
1213
1220
  if let value = result.sessionId {
1214
1221
  dictionary["sessionId"] = value
@@ -1300,7 +1307,6 @@ final class AnsightReactNative: RCTEventEmitter {
1300
1307
  "connectionProfileRetentionSeconds": options.hostConnection.connectionProfileRetentionSeconds,
1301
1308
  "discoveryPort": options.hostConnection.discoveryPort as Any,
1302
1309
  "allowCellularConnections": options.hostConnection.allowCellularConnections,
1303
- "hasBundledDeveloperConfigJson": options.hostConnection.bundledDeveloperConfigJson != nil,
1304
1310
  "hasBundledConfigJson": options.hostConnection.bundledConfigJson != nil,
1305
1311
  ],
1306
1312
  ]
@@ -12,10 +12,6 @@
12
12
  #include <string>
13
13
  #include <unordered_map>
14
14
 
15
- @interface RCTBridge ()
16
- - (void)invokeAsync:(std::function<void()> &&)func;
17
- @end
18
-
19
15
  namespace {
20
16
  int64_t heapValue(
21
17
  const std::unordered_map<std::string, int64_t>& heapInfo,
@@ -89,7 +85,7 @@ int64_t heapValue(
89
85
 
90
86
  __weak AnsightReactNativeMemorySampler *weakSelf = self;
91
87
  __weak RCTBridge *weakBridge = bridge;
92
- [bridge invokeAsync:[weakSelf, weakBridge]() {
88
+ [bridge dispatchBlock:^{
93
89
  AnsightReactNativeMemorySampler *strongSelf = weakSelf;
94
90
  RCTBridge *strongBridge = weakBridge;
95
91
  if (!strongSelf || !strongBridge) {
@@ -101,6 +97,9 @@ int64_t heapValue(
101
97
 
102
98
  @try {
103
99
  try {
100
+ // RCTBridgeProxy implements both dispatchBlock:queue: and runtime, so
101
+ // this works with the New Architecture without relying on the legacy
102
+ // RCTCxxBridge-only invokeAsync: selector.
104
103
  RCTCxxBridge *cxxBridge = (RCTCxxBridge *)strongBridge;
105
104
  auto *runtime = reinterpret_cast<facebook::jsi::Runtime *>(cxxBridge.runtime);
106
105
  if (runtime) {
@@ -124,7 +123,7 @@ int64_t heapValue(
124
123
  }
125
124
 
126
125
  strongSelf->_refreshScheduled.store(false);
127
- }];
126
+ } queue:RCTJSThread];
128
127
  }
129
128
 
130
129
  @end
@@ -47,6 +47,10 @@ RCT_EXTERN_METHOD(connect:(NSString *)pairingPayload
47
47
  resolver:(RCTPromiseResolveBlock)resolve
48
48
  rejecter:(RCTPromiseRejectBlock)reject)
49
49
 
50
+ RCT_EXTERN_METHOD(scanPairingQrCode:(NSDictionary *)options
51
+ resolver:(RCTPromiseResolveBlock)resolve
52
+ rejecter:(RCTPromiseRejectBlock)reject)
53
+
50
54
  RCT_EXTERN_METHOD(openSession:(NSString *)pairingPayload
51
55
  options:(NSDictionary *)options
52
56
  resolver:(RCTPromiseResolveBlock)resolve
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansight/react-native",
3
- "version": "1.0.2-preview.8",
3
+ "version": "1.2.0-preview.1",
4
4
  "description": "React Native bridge for the Ansight mobile SDK.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -25,6 +25,8 @@
25
25
  "README.md",
26
26
  "index.js",
27
27
  "index.d.ts",
28
+ "app.plugin.js",
29
+ "expo-plugin.js",
28
30
  "tsconfig.json",
29
31
  "AnsightReactNative.podspec",
30
32
  "react-native.config.js"
@@ -32,6 +34,7 @@
32
34
  "keywords": [
33
35
  "ansight",
34
36
  "react-native",
37
+ "expo",
35
38
  "sdk",
36
39
  "debugging",
37
40
  "inspection"
@@ -41,7 +44,7 @@
41
44
  "react-native": ">=0.72"
42
45
  },
43
46
  "scripts": {
44
- "check": "node --check index.js && tsc --noEmit",
47
+ "check": "node --check index.js && node --check app.plugin.js && node --check expo-plugin.js && tsc --noEmit",
45
48
  "typecheck": "tsc --noEmit"
46
49
  },
47
50
  "devDependencies": {