@ansight/react-native 1.1.0-preview.1 → 1.2.0-preview.2

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,10 +25,60 @@ 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.1.0-preview.1`
29
- - Maven: `ai.ansight:ansight-android:1.1.0-preview.1`
80
+ - CocoaPods: `Ansight`, `AnsightObjC` version `1.2.0-preview.2`
81
+ - Maven: `ai.ansight:ansight-android:1.2.0-preview.2`
30
82
 
31
83
  ## Quickstart
32
84
 
@@ -527,3 +579,14 @@ The first-party validation app lives in:
527
579
  It exercises the native runtime bridge, standard native remote tools,
528
580
  JavaScript custom tools, React visual-tree tools, SQLite/file fixtures,
529
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.
@@ -67,6 +67,6 @@ repositories {
67
67
  dependencies {
68
68
  implementation("com.facebook.react:react-android")
69
69
  def ansightAndroidVersion =
70
- findProperty("ansightAndroidVersion") ?: "1.1.0-preview.1"
71
- implementation("ai.ansight:ansight-android:1.1.0-preview.1")
70
+ findProperty("ansightAndroidVersion") ?: "1.2.0-preview.2"
71
+ implementation("ai.ansight:ansight-android:1.2.0-preview.2")
72
72
  }
@@ -267,7 +267,7 @@ class AnsightReactNativeModule(
267
267
 
268
268
  @ReactMethod
269
269
  fun scanPairingQrCode(options: ReadableMap?, promise: Promise) {
270
- val activity = currentActivity
270
+ val activity = reactContext.currentActivity
271
271
  if (activity == null) {
272
272
  promise.reject("ansight_qr_unavailable", "QR pairing is unavailable because no Android activity is available.")
273
273
  return
@@ -1202,7 +1202,7 @@ class AnsightReactNativeModule(
1202
1202
  ?: error("React Native application context is not an Android Application.")
1203
1203
 
1204
1204
  private fun bindCurrentActivity() {
1205
- val activity: Activity = currentActivity ?: return
1205
+ val activity: Activity = reactContext.currentActivity ?: return
1206
1206
  AnsightRuntime.bindActivity(activity)
1207
1207
  }
1208
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.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
 
@@ -1392,6 +1401,56 @@ function summarizeProps(props) {
1392
1401
  return summary;
1393
1402
  }
1394
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
+
1395
1454
  function nativeTagForFiber(fiber) {
1396
1455
  const stateNode = fiber && fiber.stateNode;
1397
1456
  if (!stateNode) {
@@ -1471,6 +1530,7 @@ function serializeFiber(fiber, context, depth) {
1471
1530
  tag: fiber.tag,
1472
1531
  key: fiber.key == null ? null : String(fiber.key),
1473
1532
  depth,
1533
+ visual: createReactVisual(fiber, props, context.maxStringLength),
1474
1534
  children: [],
1475
1535
  };
1476
1536
 
@@ -1545,6 +1605,7 @@ function createShadowTreeNode(fiber, context, depth) {
1545
1605
  tag: fiber.tag,
1546
1606
  key: fiber.key == null ? null : String(fiber.key),
1547
1607
  depth,
1608
+ visual: createReactVisual(fiber, props, context.maxStringLength),
1548
1609
  children: [],
1549
1610
  };
1550
1611
 
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansight/react-native",
3
- "version": "1.1.0-preview.1",
3
+ "version": "1.2.0-preview.2",
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": {