react-native-acoustic-connect-beta 19.0.20 → 19.0.22

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/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## 19.0.22 (2026-09-10)
2
+
3
+ ### Reverts
4
+
5
+ * Revert "Beta ReactNativeConnect build: 18.0.36" ([609ad7d](https://github.com/aipoweredmarketer/react-native-acoustic-connect-beta/commit/609ad7d3244ec06f27dced5864d40585c5b3c9cf))
6
+ ## 19.0.21 (2026-09-09)
7
+
8
+ ### Reverts
9
+
10
+ * Revert "Beta ReactNativeConnect build: 18.0.36" ([609ad7d](https://github.com/aipoweredmarketer/react-native-acoustic-connect-beta/commit/609ad7d3244ec06f27dced5864d40585c5b3c9cf))
1
11
  ## 19.0.20 (2026-09-09)
2
12
 
3
13
  ### Reverts
package/README.md CHANGED
@@ -569,13 +569,22 @@ one. To turn screenshot capture off on both platforms:
569
569
  "Connect": {
570
570
  "layoutConfig": {
571
571
  "AutoLayout": {
572
- "GlobalScreenSettings": { "ScreenShot": false, "CaptureScreenshotOn": 0 }
572
+ "GlobalScreenSettings": { "ScreenShot": false }
573
573
  }
574
574
  }
575
575
  }
576
576
  }
577
577
  ```
578
578
 
579
+ `ScreenShot` is the cross-platform switch — set it in `layoutConfig` (or per
580
+ platform) and both native SDKs honour it. `CaptureScreenshotOn` is a related
581
+ but **iOS-only** key: iOS's native auto-instrumentation reads it, but on
582
+ Android the equivalent reader has no callers, so `CaptureScreenshotOn` is
583
+ accepted wherever you put it and does nothing there. Set it only under
584
+ `layoutConfigIos` if you want it; leaving it out of `layoutConfigAndroid` (or
585
+ the shared `layoutConfig`) costs nothing on Android and avoids implying it
586
+ does something it doesn't.
587
+
579
588
  Put anything common in `layoutConfig` and use `layoutConfigIos` /
580
589
  `layoutConfigAndroid` only for what differs. The platform block is **deep-merged**
581
590
  over the shared one, so it refines the baseline rather than replacing it: an
@@ -584,6 +593,32 @@ rules, and every other shared value. Arrays are replaced outright rather than
584
593
  concatenated, so a platform block can shorten or clear a shared list such as
585
594
  `MaskIdList`.
586
595
 
596
+ #### Masking is selection, then redaction — an unmatched pattern leaves data unmasked
597
+
598
+ A screen rule's `Masking` block works in two independent steps: a control is
599
+ first **selected** by a matching `MaskIdList` (control id), `MaskValueList`
600
+ (control value), `MaskAccessibilityIdList` (accessibility id), or
601
+ `MaskAccessibilityLabelList` (accessibility label) pattern, and only a
602
+ *selected* control is redacted — its value, and (Connect iOS 2.1.22+ / Android
603
+ 11.0.23-beta+ only — older SDKs serialised the accessibility object verbatim
604
+ regardless of masking) its accessibility label and hint. A control whose value
605
+ never matches any pattern in any of the four lists is not masked in any field.
606
+ That's by design, not a bug — but it means a client whose patterns don't
607
+ happen to match a given value can believe that value is masked when it never
608
+ was.
609
+
610
+ Email addresses are an easy miss: they rarely match a card-number or
611
+ `SECRET-`-style pattern, and free-text fields (bios, support messages,
612
+ usernames) routinely contain one. For any screen that collects email
613
+ addresses, add a pattern to `MaskValueList`:
614
+
615
+ ```json
616
+ { "MaskValueList": ["[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"] }
617
+ ```
618
+
619
+ (The `bare-workflow` and `expo` sample `ConnectConfig.example.json` already
620
+ ship this pattern on `GlobalScreenSettings`.)
621
+
587
622
  When no block applies, each native SDK keeps the defaults from its own bundled
588
623
  layout config:
589
624
 
@@ -636,6 +671,54 @@ every shipped template uses. To stand layout capture down deliberately, set
636
671
  `CaptureLayoutOn: 0` on the rule instead — it is the key that means that, and it
637
672
  leaves `NumberOfWebViews` free to describe the screen.
638
673
 
674
+ #### WebView capture on Android: prerequisites and two open limitations
675
+
676
+ A screen that hosts a `react-native-webview` `WebView` has capture behaviour
677
+ beyond the config above:
678
+
679
+ - **The WebView must be scrolled on screen.** A capture only records what is
680
+ currently visible — the native tree-walk skips subtrees outside the
681
+ viewport by design. A `WebView` sitting below the fold at the default
682
+ scroll position yields a layout with no WebView nodes at all, which looks
683
+ exactly like WebView capture never engaged. Scroll it fully into view
684
+ before triggering (or waiting on) a capture.
685
+ - **`GoogleWebViewEnabled`** is a top-level `Connect` key (Android only,
686
+ default `true`) that gates whether the SDK instruments WebViews at all:
687
+
688
+ ```json
689
+ { "Connect": { "GoogleWebViewEnabled": false } }
690
+ ```
691
+
692
+ Set to `false`, the SDK does not discover or walk into any `WebView` on the
693
+ screen — the rest of that screen still captures normally. It does not gate
694
+ anything else on this list: screen views, clicks, and non-WebView layout
695
+ content are unaffected either way.
696
+ - **A discovered WebView currently drops that screen's whole layout
697
+ message.** Once the SDK finds a `WebView`, it waits for a DOM-capture
698
+ correlation id from the page before it can finish and post the layout — and
699
+ a React Native-hosted `WebView` never supplies one, so the wait never
700
+ resolves and the *entire* screen's layout message is dropped, not just the
701
+ WebView's portion. Confirmed present in Android Connect **11.0.23-beta**, the
702
+ newest published artifact at the time of writing, and in every earlier version
703
+ in the supported range — measured on an emulator, where a capture on a screen
704
+ with a visible `WebView` posted **no messages at all** for that session, since
705
+ the layout's queue placeholder blocks the batch it sits in. There is no
706
+ client-side fix; `GoogleWebViewEnabled: false` (below) avoids it by not
707
+ instrumenting the `WebView` in the first place. A fix exists in the native SDK
708
+ but is not in any published artifact yet — check the Android Connect release
709
+ notes for a version above 11.0.23-beta before assuming this still applies.
710
+ - **Enabling WebView capture replaces the app's `WebViewClient`.** The native
711
+ SDK installs its own `WebViewClient` on that `WebView` to instrument it, in
712
+ place of the one `react-native-webview` had set — so callbacks such as
713
+ `onHttpError` and `onNavigationStateChange` on that `WebView` stop firing
714
+ while capture is active. Same status as the item above: present through Android
715
+ Connect **11.0.23-beta**, fixed in the native SDK but not yet published, and
716
+ the rest of the screen is unaffected either way.
717
+
718
+ Both of the limitations above only occur once the SDK is instrumenting a
719
+ `WebView`, so `GoogleWebViewEnabled: false` avoids both — at the cost of
720
+ getting no WebView capture on that screen at all.
721
+
639
722
  #### Capture timing (`CaptureLayoutDelay`)
640
723
 
641
724
  `AutoLayout.GlobalScreenSettings.CaptureLayoutDelay` is how long, in
@@ -1,4 +1,4 @@
1
- #Wed Sep 09 11:26:30 GMT 2026
1
+ #Thu Sep 10 10:55:20 GMT 2026
2
2
  UseWhiteList=true
3
3
  PrintScreen=3
4
4
  UseRandomSample=false
package/package.json CHANGED
@@ -92,12 +92,15 @@
92
92
  "plugin/swift",
93
93
  "plugin/src",
94
94
  "cli",
95
+ "!cli/__tests__/**",
95
96
  "ConnectConfig.example.json",
96
97
  "*.podspec",
97
98
  "README.md",
98
99
  "CHANGELOG.md",
99
100
  "scripts",
100
101
  "Examples/SampleUI/**/*",
102
+ "!Examples/SampleUI/**/__tests__/**",
103
+ "!Examples/SampleUI/**/*.test.*",
101
104
  "!Examples/SampleUI/node_modules/**",
102
105
  "!Examples/SampleUI/temp/**",
103
106
  "!Examples/SampleUI/logs/**",
@@ -230,7 +233,7 @@
230
233
  "source": "src/index",
231
234
  "summary": "react-native ios android tealeaf connect cxa wxca er enhanced-replay",
232
235
  "types": "./lib/typescript/src/index.d.ts",
233
- "version": "19.0.20",
236
+ "version": "19.0.22",
234
237
  "workspaces": [
235
238
  "example",
236
239
  "Examples/bare-workflow"
@@ -1,17 +0,0 @@
1
- /**
2
- * @format
3
- */
4
-
5
- import 'react-native';
6
- import React from 'react';
7
- import App from '../App';
8
-
9
- // Note: import explicitly to use the types shipped with jest.
10
- import {it} from '@jest/globals';
11
-
12
- // Note: test renderer must be required after react-native.
13
- import renderer from 'react-test-renderer';
14
-
15
- it('renders correctly', () => {
16
- renderer.create(<App />);
17
- });
@@ -1,290 +0,0 @@
1
- import configureFonts, { fontConfig } from '../fonts';
2
- import { typescale } from '../themes/v3/tokens';
3
-
4
- const mockPlatform = (OS) => {
5
- jest.resetModules();
6
- jest.doMock('react-native/Libraries/Utilities/Platform', () => ({
7
- OS,
8
- select: (objs) => objs[OS],
9
- }));
10
- };
11
-
12
- const customFont = {
13
- custom: {
14
- fontFamily: 'sans-serif',
15
- fontWeight: 'bold',
16
- },
17
- };
18
-
19
- const customFontV3 = {
20
- displayLarge: {
21
- fontFamily: 'NotoSans',
22
- letterSpacing: 0,
23
- fontWeight: '400',
24
- lineHeight: 64,
25
- fontSize: 57,
26
- },
27
- displayMedium: {
28
- fontFamily: 'NotoSans',
29
- letterSpacing: 0,
30
- fontWeight: '400',
31
- lineHeight: 52,
32
- fontSize: 45,
33
- },
34
- displaySmall: {
35
- fontFamily: 'NotoSans',
36
- letterSpacing: 0,
37
- fontWeight: '400',
38
- lineHeight: 44,
39
- fontSize: 36,
40
- },
41
-
42
- headlineLarge: {
43
- fontFamily: 'NotoSans',
44
- letterSpacing: 0,
45
- fontWeight: '400',
46
- lineHeight: 40,
47
- fontSize: 32,
48
- },
49
- headlineMedium: {
50
- fontFamily: 'NotoSans',
51
- letterSpacing: 0,
52
- fontWeight: '400',
53
- lineHeight: 36,
54
- fontSize: 28,
55
- },
56
- headlineSmall: {
57
- fontFamily: 'NotoSans',
58
- letterSpacing: 0,
59
- fontWeight: '400',
60
- lineHeight: 32,
61
- fontSize: 24,
62
- },
63
-
64
- titleLarge: {
65
- fontFamily: 'NotoSans',
66
- letterSpacing: 0,
67
- fontWeight: '400',
68
- lineHeight: 28,
69
- fontSize: 22,
70
- },
71
- titleMedium: {
72
- fontFamily: 'NotoSans',
73
- letterSpacing: 0,
74
- fontWeight: '500',
75
- lineHeight: 24,
76
- fontSize: 16,
77
- },
78
- titleSmall: {
79
- fontFamily: 'NotoSans',
80
- fontWeight: '500',
81
- letterSpacing: 0,
82
- lineHeight: 20,
83
- fontSize: 14,
84
- },
85
-
86
- labelLarge: {
87
- fontFamily: 'NotoSans',
88
- fontWeight: '500',
89
- letterSpacing: 0,
90
- lineHeight: 20,
91
- fontSize: 14,
92
- },
93
- labelMedium: {
94
- fontFamily: 'NotoSans',
95
- fontWeight: '500',
96
- letterSpacing: 0,
97
- lineHeight: 16,
98
- fontSize: 12,
99
- },
100
- labelSmall: {
101
- fontFamily: 'NotoSans',
102
- fontWeight: '500',
103
- letterSpacing: 0,
104
- lineHeight: 16,
105
- fontSize: 11,
106
- },
107
-
108
- bodyLarge: {
109
- fontFamily: 'NotoSans',
110
- letterSpacing: 0,
111
- fontWeight: '400',
112
- lineHeight: 24,
113
- fontSize: 16,
114
- },
115
- bodyMedium: {
116
- fontFamily: 'NotoSans',
117
- fontWeight: '400',
118
- letterSpacing: 0,
119
- lineHeight: 20,
120
- fontSize: 14,
121
- },
122
- bodySmall: {
123
- fontFamily: 'NotoSans',
124
- fontWeight: '400',
125
- letterSpacing: 0,
126
- lineHeight: 16,
127
- fontSize: 12,
128
- },
129
-
130
- default: {
131
- fontFamily: 'NotoSans',
132
- letterSpacing: 0,
133
- fontWeight: '400',
134
- },
135
- };
136
-
137
- describe('configureFonts', () => {
138
- it('adds custom fonts to the iOS config', () => {
139
- mockPlatform('ios');
140
- expect(
141
- configureFonts({
142
- config: {
143
- ios: {
144
- ...fontConfig.ios,
145
- customFont,
146
- },
147
- },
148
- isV3: false,
149
- })
150
- ).toEqual({
151
- ...fontConfig.ios,
152
- customFont,
153
- });
154
- });
155
-
156
- it('adds custom fonts to the Android config', () => {
157
- mockPlatform('android');
158
- expect(
159
- configureFonts({
160
- config: {
161
- android: {
162
- ...fontConfig.android,
163
- customFont,
164
- },
165
- },
166
- isV3: false,
167
- })
168
- ).toEqual({
169
- ...fontConfig.android,
170
- customFont,
171
- });
172
- });
173
-
174
- it('adds custom fonts to the Web config', () => {
175
- mockPlatform('web');
176
- expect(
177
- configureFonts({
178
- config: {
179
- web: {
180
- ...fontConfig.web,
181
- customFont,
182
- },
183
- },
184
- isV3: false,
185
- })
186
- ).toEqual({
187
- ...fontConfig.web,
188
- customFont,
189
- });
190
- });
191
-
192
- it('overrides properties passed in config for all variants', () => {
193
- expect(
194
- configureFonts({
195
- config: {
196
- fontFamily: 'NotoSans',
197
- letterSpacing: 0,
198
- },
199
- })
200
- ).toEqual(customFontV3);
201
- });
202
-
203
- it('overrides properties passed in config for several variants', () => {
204
- expect(
205
- configureFonts({
206
- config: {
207
- bodyLarge: {
208
- fontFamily: 'NotoSans',
209
- fontSize: 18,
210
- fontStyle: 'italic',
211
- },
212
- headlineMedium: {
213
- fontSize: 30,
214
- },
215
- bodySmall: {
216
- fontStyle: 'italic',
217
- },
218
- },
219
- })
220
- ).toEqual({
221
- ...typescale,
222
- bodyLarge: {
223
- fontFamily: 'NotoSans',
224
- letterSpacing: 0.15,
225
- fontWeight: '400',
226
- fontStyle: 'italic',
227
- lineHeight: 24,
228
- fontSize: 18,
229
- },
230
- headlineMedium: {
231
- fontFamily: 'System',
232
- letterSpacing: 0,
233
- fontWeight: '400',
234
- lineHeight: 36,
235
- fontSize: 30,
236
- },
237
- bodySmall: {
238
- fontFamily: 'System',
239
- fontWeight: '400',
240
- fontStyle: 'italic',
241
- letterSpacing: 0.4,
242
- lineHeight: 16,
243
- fontSize: 12,
244
- },
245
- });
246
- });
247
-
248
- it('adds custom variant to theme fonts', () => {
249
- expect(
250
- configureFonts({
251
- config: {
252
- customVariant: {
253
- fontFamily: 'NotoSans',
254
- letterSpacing: 0,
255
- fontWeight: '400',
256
- fontStyle: 'italic',
257
- lineHeight: 64,
258
- fontSize: 57,
259
- },
260
- },
261
- })
262
- ).toEqual({
263
- ...typescale,
264
- customVariant: {
265
- fontFamily: 'NotoSans',
266
- letterSpacing: 0,
267
- fontWeight: '400',
268
- fontStyle: 'italic',
269
- lineHeight: 64,
270
- fontSize: 57,
271
- },
272
- });
273
- });
274
-
275
- it('should be deterministic', () => {
276
- // first call that should not mutate the original config
277
- configureFonts({
278
- config: {
279
- labelMedium: {
280
- color: 'coral',
281
- },
282
- },
283
- });
284
-
285
- // second call that should return the original config without modification
286
- const fontsB = configureFonts({ config: {} });
287
-
288
- expect(fontsB.labelMedium.color).toBeUndefined();
289
- });
290
- });
@@ -1,88 +0,0 @@
1
- // Copyright (C) 2026 Acoustic, L.P. All rights reserved.
2
- //
3
- // Unit tests for ensureConnectConfig's local→bundled→missing fallback in
4
- // cli/doctor.mjs. Uses Node's built-in test runner (node:test) — no build
5
- // step needed, cli/ is plain ESM.
6
- //
7
- // npm run test:cli
8
-
9
- import { test } from 'node:test'
10
- import assert from 'node:assert/strict'
11
- import * as fs from 'node:fs'
12
- import * as os from 'node:os'
13
- import * as path from 'node:path'
14
-
15
- import { ensureConnectConfig } from '../doctor.mjs'
16
- import { Reporter } from '../lib.mjs'
17
-
18
- function tmpDir() {
19
- return fs.mkdtempSync(path.join(os.tmpdir(), 'connect-doctor-test-'))
20
- }
21
-
22
- // ensureConnectConfig's 'exists' branch reports as `pass('ConnectConfig.json present')`
23
- // (label includes the detail) while the other branches use a plain
24
- // 'ConnectConfig.json' label with a separate detail string — match by prefix
25
- // so the assertion doesn't depend on that formatting quirk.
26
- function statusFor(reporter, labelPrefix) {
27
- return reporter.results.find((r) => r.label.startsWith(labelPrefix))?.status
28
- }
29
-
30
- test('ensureConnectConfig: prefers a project-local ConnectConfig.example.json over the bundled one', () => {
31
- const dir = tmpDir()
32
- fs.writeFileSync(
33
- path.join(dir, 'ConnectConfig.example.json'),
34
- JSON.stringify({ Connect: { AppKey: 'from-local-example' } }),
35
- )
36
- const reporter = new Reporter()
37
-
38
- ensureConnectConfig(reporter, dir, {
39
- bundledExample: path.join(dir, 'never-read.json'),
40
- })
41
-
42
- const written = JSON.parse(fs.readFileSync(path.join(dir, 'ConnectConfig.json'), 'utf8'))
43
- assert.equal(written.Connect.AppKey, 'from-local-example')
44
- assert.equal(statusFor(reporter, 'ConnectConfig.json'), 'warn')
45
- })
46
-
47
- test('ensureConnectConfig: falls back to the bundled example when no project-local one exists', () => {
48
- const dir = tmpDir()
49
- const bundledDir = tmpDir()
50
- const bundledExample = path.join(bundledDir, 'ConnectConfig.example.json')
51
- fs.writeFileSync(bundledExample, JSON.stringify({ Connect: { AppKey: 'from-bundled-example' } }))
52
- const reporter = new Reporter()
53
-
54
- ensureConnectConfig(reporter, dir, { bundledExample })
55
-
56
- const written = JSON.parse(fs.readFileSync(path.join(dir, 'ConnectConfig.json'), 'utf8'))
57
- assert.equal(written.Connect.AppKey, 'from-bundled-example')
58
- assert.equal(statusFor(reporter, 'ConnectConfig.json'), 'warn')
59
- })
60
-
61
- test('ensureConnectConfig: fails cleanly (no crash, no file written) when both are missing', () => {
62
- const dir = tmpDir()
63
- const reporter = new Reporter()
64
-
65
- ensureConnectConfig(reporter, dir, {
66
- bundledExample: path.join(dir, 'does-not-exist.json'),
67
- })
68
-
69
- assert.equal(fs.existsSync(path.join(dir, 'ConnectConfig.json')), false)
70
- assert.equal(statusFor(reporter, 'ConnectConfig.json'), 'fail')
71
- })
72
-
73
- test('ensureConnectConfig: never overwrites an existing ConnectConfig.json', () => {
74
- const dir = tmpDir()
75
- fs.writeFileSync(
76
- path.join(dir, 'ConnectConfig.json'),
77
- JSON.stringify({ Connect: { AppKey: 'already-configured' } }),
78
- )
79
- const reporter = new Reporter()
80
-
81
- ensureConnectConfig(reporter, dir, {
82
- bundledExample: path.join(dir, 'never-read.json'),
83
- })
84
-
85
- const written = JSON.parse(fs.readFileSync(path.join(dir, 'ConnectConfig.json'), 'utf8'))
86
- assert.equal(written.Connect.AppKey, 'already-configured')
87
- assert.equal(statusFor(reporter, 'ConnectConfig.json'), 'pass')
88
- })