@scalebun/react-native 1.10.5 → 1.10.7
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/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +75 -0
- package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +12 -0
- package/dist/scalebun.full.js +485 -51
- package/dist/scalebun.slim.js +485 -51
- package/ios/Core/DeviceIdentity.swift +50 -0
- package/ios/Ota/ScaleBunOtaBridge.mm +6 -0
- package/ios/Ota/ScaleBunOtaModule.swift +67 -0
- package/ios/Profiler/ScaleBunProfilerModule.swift +2 -8
- package/ios/ReplaySdk.swift +3 -1
- package/lib/commonjs/analytics/EventTracker.js +22 -0
- package/lib/commonjs/core/config/schema.js +34 -1
- package/lib/commonjs/core/constants/version.js +1 -1
- package/lib/commonjs/core/context/device.js +5 -0
- package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +24 -0
- package/lib/commonjs/features/journey/uiState.js +87 -0
- package/lib/commonjs/features/ota/crypto/builtinVerifier.js +248 -0
- package/lib/commonjs/features/ota/crypto/loadEd25519.js +40 -0
- package/lib/commonjs/features/ota/crypto/loadSha512.js +40 -0
- package/lib/commonjs/features/ota/crypto/nativeVerifier.js +121 -0
- package/lib/commonjs/features/ota/signature.js +87 -27
- package/lib/commonjs/metro/serializerCompose.js +32 -0
- package/lib/commonjs/public/ScaleBunFacade.js +108 -11
- package/lib/module/analytics/EventTracker.js +22 -0
- package/lib/module/core/config/schema.js +34 -1
- package/lib/module/core/constants/version.js +1 -1
- package/lib/module/core/context/device.js +5 -0
- package/lib/module/features/journey/ScaleBunDebugRoot.js +24 -0
- package/lib/module/features/journey/uiState.js +79 -0
- package/lib/module/features/ota/crypto/builtinVerifier.js +240 -0
- package/lib/module/features/ota/crypto/loadEd25519.js +34 -0
- package/lib/module/features/ota/crypto/loadSha512.js +34 -0
- package/lib/module/features/ota/crypto/nativeVerifier.js +113 -0
- package/lib/module/features/ota/signature.js +87 -27
- package/lib/module/metro/serializerCompose.js +32 -0
- package/lib/module/public/ScaleBunFacade.js +109 -12
- package/lib/typescript/analytics/EventTracker.d.ts +15 -0
- package/lib/typescript/core/config/schema.d.ts +2 -0
- package/lib/typescript/core/constants/version.d.ts +1 -1
- package/lib/typescript/core/context/device.d.ts +1 -0
- package/lib/typescript/features/journey/uiState.d.ts +53 -0
- package/lib/typescript/features/ota/OtaTypes.d.ts +50 -0
- package/lib/typescript/features/ota/crypto/builtinVerifier.d.ts +53 -0
- package/lib/typescript/features/ota/crypto/loadEd25519.d.ts +30 -0
- package/lib/typescript/features/ota/crypto/loadSha512.d.ts +15 -0
- package/lib/typescript/features/ota/crypto/nativeVerifier.d.ts +35 -0
- package/lib/typescript/features/ota/signature.d.ts +22 -7
- package/lib/typescript/public/ScaleBunFacade.d.ts +43 -6
- package/lib/typescript/specs/NativeScaleBunOta.d.ts +23 -0
- package/package.json +18 -3
- package/src/analytics/EventTracker.ts +19 -0
- package/src/core/config/schema.ts +30 -3
- package/src/core/constants/version.ts +1 -1
- package/src/core/context/device.ts +6 -0
- package/src/features/journey/ScaleBunDebugRoot.tsx +22 -0
- package/src/features/journey/uiState.ts +84 -0
- package/src/features/ota/OtaTypes.ts +51 -0
- package/src/features/ota/crypto/builtinVerifier.ts +257 -0
- package/src/features/ota/crypto/loadEd25519.ts +41 -0
- package/src/features/ota/crypto/loadSha512.ts +35 -0
- package/src/features/ota/crypto/nativeVerifier.ts +117 -0
- package/src/features/ota/signature.ts +108 -25
- package/src/metro/serializerCompose.ts +38 -2
- package/src/public/ScaleBunFacade.ts +128 -14
- package/src/specs/NativeScaleBunOta.ts +24 -0
package/dist/scalebun.full.js
CHANGED
|
@@ -89,9 +89,9 @@ var init_internalLogger = __esm({
|
|
|
89
89
|
}
|
|
90
90
|
/** Set level from the public string name used in config (e.g. 'warn'). */
|
|
91
91
|
setLevelByName(name) {
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
this.level =
|
|
92
|
+
const resolved3 = LEVEL_BY_NAME[name];
|
|
93
|
+
if (resolved3 !== void 0) {
|
|
94
|
+
this.level = resolved3;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
/** Current numeric level — exposed for callers that branch on dev verbosity. */
|
|
@@ -605,7 +605,7 @@ var SDK_VERSION;
|
|
|
605
605
|
var init_version = __esm({
|
|
606
606
|
"lib/module/core/constants/version.js"() {
|
|
607
607
|
"use strict";
|
|
608
|
-
SDK_VERSION = "1.10.
|
|
608
|
+
SDK_VERSION = "1.10.7";
|
|
609
609
|
}
|
|
610
610
|
});
|
|
611
611
|
|
|
@@ -647,6 +647,15 @@ function staticDeviceContext() {
|
|
|
647
647
|
if (c.Model) out.device_model = String(c.Model);
|
|
648
648
|
if (c.Manufacturer) out.manufacturer = String(c.Manufacturer);
|
|
649
649
|
else if (c.Brand) out.manufacturer = String(c.Brand);
|
|
650
|
+
else if (out.platform_os === "ios") out.manufacturer = "Apple";
|
|
651
|
+
return out;
|
|
652
|
+
}
|
|
653
|
+
function nativeDeviceContext(info) {
|
|
654
|
+
const out = {};
|
|
655
|
+
if (!info) return out;
|
|
656
|
+
if (info.osVersion) out.os_version = info.osVersion;
|
|
657
|
+
if (info.deviceModel) out.device_model = info.deviceModel;
|
|
658
|
+
if (info.manufacturer) out.manufacturer = info.manufacturer;
|
|
650
659
|
return out;
|
|
651
660
|
}
|
|
652
661
|
function mergeDeviceContext(configured, detected = staticDeviceContext()) {
|
|
@@ -11232,6 +11241,27 @@ var init_EventTracker = __esm({
|
|
|
11232
11241
|
}
|
|
11233
11242
|
this.persistQueue();
|
|
11234
11243
|
}
|
|
11244
|
+
/**
|
|
11245
|
+
* Fold in device facts that were not knowable at init.
|
|
11246
|
+
*
|
|
11247
|
+
* `Platform.constants` answers Android synchronously, so its model and manufacturer ride the
|
|
11248
|
+
* very first event. iOS exposes neither there — only the native bridge knows, and the bridge is
|
|
11249
|
+
* async and may not be installed at all. Without this the iOS half of every install reported no
|
|
11250
|
+
* model whatsoever.
|
|
11251
|
+
*
|
|
11252
|
+
* `buildEnvelope` reads `cfg.context` per event, so applying it here reaches every event from
|
|
11253
|
+
* the next one onward. Detected keys WIN over whatever the integrator configured, for the same
|
|
11254
|
+
* reason `mergeDeviceContext` inverts the usual precedence: a measurement must not be
|
|
11255
|
+
* overridable by a guess. Empty patches are ignored so a bridge that answered with nothing
|
|
11256
|
+
* cannot blank a value the static path already found.
|
|
11257
|
+
*/
|
|
11258
|
+
applyDetectedContext(patch) {
|
|
11259
|
+
if (!patch || Object.keys(patch).length === 0) return;
|
|
11260
|
+
this.cfg.context = {
|
|
11261
|
+
...this.cfg.context ?? {},
|
|
11262
|
+
...patch
|
|
11263
|
+
};
|
|
11264
|
+
}
|
|
11235
11265
|
// ─── internals ─────────────────────────────────────────────────────────────
|
|
11236
11266
|
buildEnvelope(eventName, properties) {
|
|
11237
11267
|
const ctx = this.cfg.context ?? {};
|
|
@@ -11581,10 +11611,249 @@ var init_deviceAttributes = __esm({
|
|
|
11581
11611
|
}
|
|
11582
11612
|
});
|
|
11583
11613
|
|
|
11614
|
+
// lib/module/features/ota/crypto/loadEd25519.js
|
|
11615
|
+
function loadEd25519() {
|
|
11616
|
+
try {
|
|
11617
|
+
const mod = require("@noble/ed25519");
|
|
11618
|
+
const ed = mod?.default ?? mod;
|
|
11619
|
+
if (typeof ed?.verifyAsync === "function" || typeof ed?.verify === "function") {
|
|
11620
|
+
return ed;
|
|
11621
|
+
}
|
|
11622
|
+
return null;
|
|
11623
|
+
} catch {
|
|
11624
|
+
return null;
|
|
11625
|
+
}
|
|
11626
|
+
}
|
|
11627
|
+
var init_loadEd25519 = __esm({
|
|
11628
|
+
"lib/module/features/ota/crypto/loadEd25519.js"() {
|
|
11629
|
+
"use strict";
|
|
11630
|
+
}
|
|
11631
|
+
});
|
|
11632
|
+
|
|
11633
|
+
// lib/module/features/ota/crypto/loadSha512.js
|
|
11634
|
+
function loadSha512() {
|
|
11635
|
+
try {
|
|
11636
|
+
const mod = require("@noble/hashes/sha2.js");
|
|
11637
|
+
const sha512 = mod?.sha512 ?? mod?.default?.sha512;
|
|
11638
|
+
return typeof sha512 === "function" ? sha512 : null;
|
|
11639
|
+
} catch {
|
|
11640
|
+
return null;
|
|
11641
|
+
}
|
|
11642
|
+
}
|
|
11643
|
+
var init_loadSha512 = __esm({
|
|
11644
|
+
"lib/module/features/ota/crypto/loadSha512.js"() {
|
|
11645
|
+
"use strict";
|
|
11646
|
+
}
|
|
11647
|
+
});
|
|
11648
|
+
|
|
11649
|
+
// lib/module/features/ota/crypto/builtinVerifier.js
|
|
11650
|
+
var builtinVerifier_exports = {};
|
|
11651
|
+
__export(builtinVerifier_exports, {
|
|
11652
|
+
_resetBuiltinVerifier: () => _resetBuiltinVerifier,
|
|
11653
|
+
decodeSignature: () => decodeSignature,
|
|
11654
|
+
getBuiltinVerifier: () => getBuiltinVerifier
|
|
11655
|
+
});
|
|
11656
|
+
function hexToBytes(hex) {
|
|
11657
|
+
const clean2 = hex.trim().toLowerCase().replace(/^0x/, "");
|
|
11658
|
+
if (clean2.length === 0 || clean2.length % 2 !== 0 || /[^0-9a-f]/.test(clean2)) return null;
|
|
11659
|
+
const out = new Uint8Array(clean2.length / 2);
|
|
11660
|
+
for (let i = 0; i < out.length; i++) {
|
|
11661
|
+
out[i] = parseInt(clean2.slice(i * 2, i * 2 + 2), 16);
|
|
11662
|
+
}
|
|
11663
|
+
return out;
|
|
11664
|
+
}
|
|
11665
|
+
function base64ToBytes2(b64) {
|
|
11666
|
+
const s = b64.trim().replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
|
|
11667
|
+
if (s.length === 0) return null;
|
|
11668
|
+
const out = [];
|
|
11669
|
+
let buffer = 0;
|
|
11670
|
+
let bits = 0;
|
|
11671
|
+
for (const ch of s) {
|
|
11672
|
+
const idx = B64_ALPHABET2.indexOf(ch);
|
|
11673
|
+
if (idx === -1) return null;
|
|
11674
|
+
buffer = buffer << 6 | idx;
|
|
11675
|
+
bits += 6;
|
|
11676
|
+
if (bits >= 8) {
|
|
11677
|
+
bits -= 8;
|
|
11678
|
+
out.push(buffer >> bits & 255);
|
|
11679
|
+
}
|
|
11680
|
+
}
|
|
11681
|
+
return Uint8Array.from(out);
|
|
11682
|
+
}
|
|
11683
|
+
function decodeSignature(sig) {
|
|
11684
|
+
const trimmed = sig.trim();
|
|
11685
|
+
if (/^(0x)?[0-9a-fA-F]{128}$/.test(trimmed)) {
|
|
11686
|
+
const hex2 = hexToBytes(trimmed);
|
|
11687
|
+
if (hex2 && hex2.length === ED25519_SIGNATURE_BYTES) return hex2;
|
|
11688
|
+
}
|
|
11689
|
+
const b64 = base64ToBytes2(trimmed);
|
|
11690
|
+
if (b64 && b64.length === ED25519_SIGNATURE_BYTES) return b64;
|
|
11691
|
+
const hex = hexToBytes(trimmed);
|
|
11692
|
+
if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
|
|
11693
|
+
return null;
|
|
11694
|
+
}
|
|
11695
|
+
function wireSha512(ed, sha512) {
|
|
11696
|
+
const concat2 = (parts) => {
|
|
11697
|
+
if (parts.length === 1) return parts[0];
|
|
11698
|
+
let total = 0;
|
|
11699
|
+
for (const p of parts) total += p.length;
|
|
11700
|
+
const out = new Uint8Array(total);
|
|
11701
|
+
let off = 0;
|
|
11702
|
+
for (const p of parts) {
|
|
11703
|
+
out.set(p, off);
|
|
11704
|
+
off += p.length;
|
|
11705
|
+
}
|
|
11706
|
+
return out;
|
|
11707
|
+
};
|
|
11708
|
+
const sync = (...msgs) => sha512(concat2(msgs));
|
|
11709
|
+
const asyncFn = async (...msgs) => sha512(concat2(msgs));
|
|
11710
|
+
let wired = false;
|
|
11711
|
+
try {
|
|
11712
|
+
if (ed.hashes && typeof ed.hashes === "object") {
|
|
11713
|
+
if (typeof ed.hashes.sha512 !== "function") ed.hashes.sha512 = sync;
|
|
11714
|
+
if (typeof ed.hashes.sha512Async !== "function") ed.hashes.sha512Async = asyncFn;
|
|
11715
|
+
wired = typeof ed.hashes.sha512 === "function";
|
|
11716
|
+
}
|
|
11717
|
+
} catch {
|
|
11718
|
+
}
|
|
11719
|
+
try {
|
|
11720
|
+
if (ed.etc && typeof ed.etc === "object") {
|
|
11721
|
+
if (typeof ed.etc.sha512Sync !== "function") ed.etc.sha512Sync = sync;
|
|
11722
|
+
if (typeof ed.etc.sha512Async !== "function") ed.etc.sha512Async = asyncFn;
|
|
11723
|
+
wired = wired || typeof ed.etc.sha512Sync === "function";
|
|
11724
|
+
}
|
|
11725
|
+
} catch {
|
|
11726
|
+
}
|
|
11727
|
+
return wired;
|
|
11728
|
+
}
|
|
11729
|
+
function getBuiltinVerifier() {
|
|
11730
|
+
if (resolved !== void 0) return resolved;
|
|
11731
|
+
const ed = loadEd25519();
|
|
11732
|
+
const sha512 = loadSha512();
|
|
11733
|
+
if (!ed || !sha512) {
|
|
11734
|
+
resolved = null;
|
|
11735
|
+
return null;
|
|
11736
|
+
}
|
|
11737
|
+
if (!wireSha512(ed, sha512)) {
|
|
11738
|
+
logger.error("[OTA] @noble/ed25519 is installed but its SHA-512 provider could not be wired (unrecognised version shape). Supply `ota.verifySignature` yourself, or pin a @noble/ed25519 version this SDK supports.");
|
|
11739
|
+
resolved = null;
|
|
11740
|
+
return null;
|
|
11741
|
+
}
|
|
11742
|
+
resolved = async ({
|
|
11743
|
+
messageHex,
|
|
11744
|
+
signature,
|
|
11745
|
+
publicKey
|
|
11746
|
+
}) => {
|
|
11747
|
+
const message = hexToBytes(messageHex);
|
|
11748
|
+
const pub = hexToBytes(publicKey);
|
|
11749
|
+
const sig = decodeSignature(signature);
|
|
11750
|
+
if (!message) {
|
|
11751
|
+
logger.error("[OTA] Bundle SHA-256 is not valid hex \u2014 refusing to stage.");
|
|
11752
|
+
return false;
|
|
11753
|
+
}
|
|
11754
|
+
if (!pub || pub.length !== ED25519_PUBLIC_KEY_BYTES) {
|
|
11755
|
+
logger.error(`[OTA] ota.publicSigningKey must be ${ED25519_PUBLIC_KEY_BYTES} bytes of hex (${ED25519_PUBLIC_KEY_BYTES * 2} characters) \u2014 got ${pub ? `${pub.length} bytes` : "a value that is not hex"}.`);
|
|
11756
|
+
return false;
|
|
11757
|
+
}
|
|
11758
|
+
if (!sig) {
|
|
11759
|
+
logger.error("[OTA] Bundle signature is not a 64-byte hex/base64 value.");
|
|
11760
|
+
return false;
|
|
11761
|
+
}
|
|
11762
|
+
try {
|
|
11763
|
+
if (typeof ed.verify === "function") return ed.verify(sig, message, pub);
|
|
11764
|
+
if (typeof ed.verifyAsync === "function") return await ed.verifyAsync(sig, message, pub);
|
|
11765
|
+
return false;
|
|
11766
|
+
} catch {
|
|
11767
|
+
return false;
|
|
11768
|
+
}
|
|
11769
|
+
};
|
|
11770
|
+
__DEV__ && logger.debug("[OTA] Using built-in ed25519 verifier (@noble/ed25519 detected).");
|
|
11771
|
+
return resolved;
|
|
11772
|
+
}
|
|
11773
|
+
function _resetBuiltinVerifier() {
|
|
11774
|
+
resolved = void 0;
|
|
11775
|
+
}
|
|
11776
|
+
var ED25519_SIGNATURE_BYTES, ED25519_PUBLIC_KEY_BYTES, B64_ALPHABET2, resolved;
|
|
11777
|
+
var init_builtinVerifier = __esm({
|
|
11778
|
+
"lib/module/features/ota/crypto/builtinVerifier.js"() {
|
|
11779
|
+
"use strict";
|
|
11780
|
+
init_loadEd25519();
|
|
11781
|
+
init_loadSha512();
|
|
11782
|
+
init_internalLogger();
|
|
11783
|
+
ED25519_SIGNATURE_BYTES = 64;
|
|
11784
|
+
ED25519_PUBLIC_KEY_BYTES = 32;
|
|
11785
|
+
B64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
11786
|
+
}
|
|
11787
|
+
});
|
|
11788
|
+
|
|
11789
|
+
// lib/module/features/ota/crypto/nativeVerifier.js
|
|
11790
|
+
function bytesToHex(bytes) {
|
|
11791
|
+
let out = "";
|
|
11792
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
11793
|
+
return out;
|
|
11794
|
+
}
|
|
11795
|
+
function normalizeHex(value, expectedChars) {
|
|
11796
|
+
const clean2 = value.trim().toLowerCase().replace(/^0x/, "");
|
|
11797
|
+
if (clean2.length !== expectedChars || /[^0-9a-f]/.test(clean2)) return null;
|
|
11798
|
+
return clean2;
|
|
11799
|
+
}
|
|
11800
|
+
function getNativeVerifier() {
|
|
11801
|
+
if (resolved2 !== void 0) return resolved2;
|
|
11802
|
+
const native = NativeScaleBunOta_default;
|
|
11803
|
+
if (!native || typeof native.verifyEd25519 !== "function") {
|
|
11804
|
+
resolved2 = null;
|
|
11805
|
+
return null;
|
|
11806
|
+
}
|
|
11807
|
+
resolved2 = async ({
|
|
11808
|
+
messageHex,
|
|
11809
|
+
signature,
|
|
11810
|
+
publicKey
|
|
11811
|
+
}) => {
|
|
11812
|
+
const message = normalizeHex(messageHex, 64);
|
|
11813
|
+
const key = normalizeHex(publicKey, ED25519_PUBLIC_KEY_HEX_CHARS);
|
|
11814
|
+
const sigBytes = decodeSignature(signature);
|
|
11815
|
+
if (!message || !key || !sigBytes) {
|
|
11816
|
+
logger.error("[OTA] Signature inputs malformed (bundle hash, signature, or public key) \u2014 refusing to stage.");
|
|
11817
|
+
return false;
|
|
11818
|
+
}
|
|
11819
|
+
let verdict;
|
|
11820
|
+
try {
|
|
11821
|
+
verdict = await native.verifyEd25519(message, bytesToHex(sigBytes), key);
|
|
11822
|
+
} catch (err) {
|
|
11823
|
+
logger.warn(`[OTA] Native ed25519 verify failed to run: ${err?.message ?? err}`);
|
|
11824
|
+
verdict = "unavailable";
|
|
11825
|
+
}
|
|
11826
|
+
if (verdict === "valid") return true;
|
|
11827
|
+
if (verdict === "invalid") return false;
|
|
11828
|
+
const builtin = getBuiltinVerifier();
|
|
11829
|
+
if (builtin) {
|
|
11830
|
+
return builtin({
|
|
11831
|
+
messageHex,
|
|
11832
|
+
signature,
|
|
11833
|
+
publicKey
|
|
11834
|
+
});
|
|
11835
|
+
}
|
|
11836
|
+
throw new Error("ed25519 unavailable: this OS has no native implementation (Android < 13) and the optional peers @noble/ed25519 + @noble/hashes are not installed.");
|
|
11837
|
+
};
|
|
11838
|
+
__DEV__ && logger.debug("[OTA] Using native ed25519 verifier (platform crypto).");
|
|
11839
|
+
return resolved2;
|
|
11840
|
+
}
|
|
11841
|
+
var ED25519_PUBLIC_KEY_HEX_CHARS, resolved2;
|
|
11842
|
+
var init_nativeVerifier = __esm({
|
|
11843
|
+
"lib/module/features/ota/crypto/nativeVerifier.js"() {
|
|
11844
|
+
"use strict";
|
|
11845
|
+
init_NativeScaleBunOta();
|
|
11846
|
+
init_internalLogger();
|
|
11847
|
+
init_builtinVerifier();
|
|
11848
|
+
ED25519_PUBLIC_KEY_HEX_CHARS = 64;
|
|
11849
|
+
}
|
|
11850
|
+
});
|
|
11851
|
+
|
|
11584
11852
|
// lib/module/features/ota/signature.js
|
|
11585
11853
|
async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
11586
|
-
const
|
|
11587
|
-
|
|
11854
|
+
const rawKey = config?.publicKey;
|
|
11855
|
+
const keys = (Array.isArray(rawKey) ? rawKey : rawKey ? [rawKey] : []).filter((k) => typeof k === "string" && k.trim().length > 0);
|
|
11856
|
+
if (rawKey === void 0 || rawKey === "" || rawKey === null) {
|
|
11588
11857
|
if (!warnedNotConfigured) {
|
|
11589
11858
|
warnedNotConfigured = true;
|
|
11590
11859
|
logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
|
|
@@ -11594,6 +11863,13 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
|
11594
11863
|
reason: "not_configured"
|
|
11595
11864
|
};
|
|
11596
11865
|
}
|
|
11866
|
+
if (keys.length === 0) {
|
|
11867
|
+
logger.error("[OTA] Signing key config resolves to zero usable keys (empty array or blank strings) \u2014 refusing to stage. Remove the config to disable signing, or pin at least one real key.");
|
|
11868
|
+
return {
|
|
11869
|
+
ok: false,
|
|
11870
|
+
reason: "no_keys"
|
|
11871
|
+
};
|
|
11872
|
+
}
|
|
11597
11873
|
if (!signature) {
|
|
11598
11874
|
logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
|
|
11599
11875
|
return {
|
|
@@ -11601,43 +11877,52 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
|
11601
11877
|
reason: "missing_signature"
|
|
11602
11878
|
};
|
|
11603
11879
|
}
|
|
11604
|
-
|
|
11605
|
-
|
|
11880
|
+
const verifier = typeof config?.verifier === "function" ? config.verifier : getNativeVerifier() ?? getBuiltinVerifier();
|
|
11881
|
+
if (!verifier) {
|
|
11882
|
+
logger.error("[OTA] A signing key is configured but no signature verifier is available \u2014 refusing to stage. Either install the optional peers `@noble/ed25519` and `@noble/hashes` (the SDK then verifies with no further code), or provide your own `ota.verifySignature`.");
|
|
11606
11883
|
return {
|
|
11607
11884
|
ok: false,
|
|
11608
11885
|
reason: "no_verifier"
|
|
11609
11886
|
};
|
|
11610
11887
|
}
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11888
|
+
let sawThrow = false;
|
|
11889
|
+
let sawCompletion = false;
|
|
11890
|
+
for (const key of keys) {
|
|
11891
|
+
try {
|
|
11892
|
+
const valid = await verifier({
|
|
11893
|
+
messageHex: bundleSha256,
|
|
11894
|
+
signature,
|
|
11895
|
+
publicKey: key
|
|
11896
|
+
});
|
|
11897
|
+
sawCompletion = true;
|
|
11898
|
+
if (valid) return {
|
|
11899
|
+
ok: true,
|
|
11900
|
+
reason: "verified"
|
|
11622
11901
|
};
|
|
11902
|
+
} catch (err) {
|
|
11903
|
+
sawThrow = true;
|
|
11904
|
+
logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
|
|
11623
11905
|
}
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
reason: "verified"
|
|
11627
|
-
};
|
|
11628
|
-
} catch (err) {
|
|
11629
|
-
logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
|
|
11906
|
+
}
|
|
11907
|
+
if (sawThrow && !sawCompletion) {
|
|
11630
11908
|
return {
|
|
11631
11909
|
ok: false,
|
|
11632
11910
|
reason: "verifier_threw"
|
|
11633
11911
|
};
|
|
11634
11912
|
}
|
|
11913
|
+
logger.error(keys.length > 1 ? `[OTA] Bundle signature is INVALID under all ${keys.length} pinned keys \u2014 refusing to stage.` : "[OTA] Bundle signature is INVALID \u2014 refusing to stage.");
|
|
11914
|
+
return {
|
|
11915
|
+
ok: false,
|
|
11916
|
+
reason: "invalid_signature"
|
|
11917
|
+
};
|
|
11635
11918
|
}
|
|
11636
11919
|
var warnedNotConfigured;
|
|
11637
11920
|
var init_signature = __esm({
|
|
11638
11921
|
"lib/module/features/ota/signature.js"() {
|
|
11639
11922
|
"use strict";
|
|
11640
11923
|
init_internalLogger();
|
|
11924
|
+
init_builtinVerifier();
|
|
11925
|
+
init_nativeVerifier();
|
|
11641
11926
|
warnedNotConfigured = false;
|
|
11642
11927
|
}
|
|
11643
11928
|
});
|
|
@@ -12457,6 +12742,35 @@ var init_OtaOrchestrator = __esm({
|
|
|
12457
12742
|
}
|
|
12458
12743
|
});
|
|
12459
12744
|
|
|
12745
|
+
// lib/module/features/journey/uiState.js
|
|
12746
|
+
var uiState_exports = {};
|
|
12747
|
+
__export(uiState_exports, {
|
|
12748
|
+
clearUiState: () => clearUiState,
|
|
12749
|
+
setUiState: () => setUiState,
|
|
12750
|
+
uiStateSignature: () => uiStateSignature
|
|
12751
|
+
});
|
|
12752
|
+
function setUiState(name, value) {
|
|
12753
|
+
const n = clean(name);
|
|
12754
|
+
const v = clean(value);
|
|
12755
|
+
if (n && v) declared.set(n, v);
|
|
12756
|
+
}
|
|
12757
|
+
function clearUiState(name) {
|
|
12758
|
+
if (name === void 0) declared.clear();
|
|
12759
|
+
else declared.delete(clean(name));
|
|
12760
|
+
}
|
|
12761
|
+
function uiStateSignature() {
|
|
12762
|
+
if (!declared.size) return void 0;
|
|
12763
|
+
return [...declared.keys()].sort().map((k) => `${k}:${declared.get(k)}`).join(";").slice(0, 96);
|
|
12764
|
+
}
|
|
12765
|
+
var declared, clean;
|
|
12766
|
+
var init_uiState = __esm({
|
|
12767
|
+
"lib/module/features/journey/uiState.js"() {
|
|
12768
|
+
"use strict";
|
|
12769
|
+
declared = /* @__PURE__ */ new Map();
|
|
12770
|
+
clean = (s) => typeof s === "string" ? s.replace(/[;:|]/g, "").trim().slice(0, 32) : "";
|
|
12771
|
+
}
|
|
12772
|
+
});
|
|
12773
|
+
|
|
12460
12774
|
// lib/module/features/engage/gestureTriggerDetector.js
|
|
12461
12775
|
var gestureTriggerDetector_exports = {};
|
|
12462
12776
|
__export(gestureTriggerDetector_exports, {
|
|
@@ -14374,6 +14688,14 @@ var SHAPE = {
|
|
|
14374
14688
|
kind: "str",
|
|
14375
14689
|
opt: true
|
|
14376
14690
|
},
|
|
14691
|
+
// Rotation support: several pinned keys, any of which may verify a
|
|
14692
|
+
// bundle. Ship a build pinning [old, new], re-sign server-side with
|
|
14693
|
+
// new, drop old next release — no emergency store submission when a
|
|
14694
|
+
// key must be replaced. Merged with publicSigningKey by the facade.
|
|
14695
|
+
publicSigningKeys: {
|
|
14696
|
+
kind: "strArr",
|
|
14697
|
+
opt: true
|
|
14698
|
+
},
|
|
14377
14699
|
mandatoryBlocksUi: bool(false)
|
|
14378
14700
|
}
|
|
14379
14701
|
}
|
|
@@ -14409,7 +14731,7 @@ function parseField(field, input, path, issues) {
|
|
|
14409
14731
|
value: field.def,
|
|
14410
14732
|
present: true
|
|
14411
14733
|
};
|
|
14412
|
-
return fail(field.kind === "bool" ? "Expected boolean, received undefined" : field.kind === "num" ? "Expected number, received undefined" : field.kind === "str" ? "Expected string, received undefined" : field.kind === "enum" ? "Invalid enum value. Expected " + field.values.map((v) => `'${v}'`).join(" | ") : "Expected object, received undefined");
|
|
14734
|
+
return fail(field.kind === "bool" ? "Expected boolean, received undefined" : field.kind === "num" ? "Expected number, received undefined" : field.kind === "str" ? "Expected string, received undefined" : field.kind === "strArr" ? "Expected array, received undefined" : field.kind === "enum" ? "Invalid enum value. Expected " + field.values.map((v) => `'${v}'`).join(" | ") : "Expected object, received undefined");
|
|
14413
14735
|
}
|
|
14414
14736
|
switch (field.kind) {
|
|
14415
14737
|
case "any":
|
|
@@ -14435,6 +14757,27 @@ function parseField(field, input, path, issues) {
|
|
|
14435
14757
|
value: input,
|
|
14436
14758
|
present: true
|
|
14437
14759
|
};
|
|
14760
|
+
case "strArr": {
|
|
14761
|
+
if (!Array.isArray(input)) return fail(`Expected array, received ${typeofName2(input)}`);
|
|
14762
|
+
let allOk = true;
|
|
14763
|
+
for (let i = 0; i < input.length; i++) {
|
|
14764
|
+
if (typeof input[i] !== "string") {
|
|
14765
|
+
issues.push({
|
|
14766
|
+
path: [...path, i],
|
|
14767
|
+
message: `Expected string, received ${typeofName2(input[i])}`
|
|
14768
|
+
});
|
|
14769
|
+
allOk = false;
|
|
14770
|
+
}
|
|
14771
|
+
}
|
|
14772
|
+
return allOk ? {
|
|
14773
|
+
ok: true,
|
|
14774
|
+
value: [...input],
|
|
14775
|
+
present: true
|
|
14776
|
+
} : {
|
|
14777
|
+
ok: false,
|
|
14778
|
+
present: true
|
|
14779
|
+
};
|
|
14780
|
+
}
|
|
14438
14781
|
case "num":
|
|
14439
14782
|
if (typeof input !== "number" || Number.isNaN(input)) {
|
|
14440
14783
|
return fail(`Expected number, received ${typeofName2(input)}`);
|
|
@@ -15638,16 +15981,16 @@ function base64ToBytes(base64) {
|
|
|
15638
15981
|
return decodeManual(base64);
|
|
15639
15982
|
}
|
|
15640
15983
|
function decodeManual(base64) {
|
|
15641
|
-
const
|
|
15642
|
-
const pad =
|
|
15643
|
-
const outLen = Math.floor(
|
|
15984
|
+
const clean2 = base64.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
15985
|
+
const pad = clean2.endsWith("==") ? 2 : clean2.endsWith("=") ? 1 : 0;
|
|
15986
|
+
const outLen = Math.floor(clean2.length * 3 / 4) - pad;
|
|
15644
15987
|
const out = new Uint8Array(outLen);
|
|
15645
15988
|
let p = 0;
|
|
15646
|
-
for (let i = 0; i <
|
|
15647
|
-
const c0 = B64_ALPHABET.indexOf(
|
|
15648
|
-
const c1 = B64_ALPHABET.indexOf(
|
|
15649
|
-
const c2 = B64_ALPHABET.indexOf(
|
|
15650
|
-
const c3 = B64_ALPHABET.indexOf(
|
|
15989
|
+
for (let i = 0; i < clean2.length; i += 4) {
|
|
15990
|
+
const c0 = B64_ALPHABET.indexOf(clean2[i]);
|
|
15991
|
+
const c1 = B64_ALPHABET.indexOf(clean2[i + 1]);
|
|
15992
|
+
const c2 = B64_ALPHABET.indexOf(clean2[i + 2]);
|
|
15993
|
+
const c3 = B64_ALPHABET.indexOf(clean2[i + 3]);
|
|
15651
15994
|
const n = c0 << 18 | c1 << 12 | (c2 & 63) << 6 | c3 & 63;
|
|
15652
15995
|
if (p < outLen) out[p++] = n >> 16 & 255;
|
|
15653
15996
|
if (p < outLen) out[p++] = n >> 8 & 255;
|
|
@@ -17408,6 +17751,7 @@ var SDKBootstrapper = class {
|
|
|
17408
17751
|
|
|
17409
17752
|
// lib/module/public/ScaleBunFacade.js
|
|
17410
17753
|
init_EventTracker();
|
|
17754
|
+
init_bridgeAdapter();
|
|
17411
17755
|
init_automaticEvents();
|
|
17412
17756
|
|
|
17413
17757
|
// lib/module/analytics/eventLane.js
|
|
@@ -19834,12 +20178,17 @@ var ScaleBunFacade = class {
|
|
|
19834
20178
|
/**
|
|
19835
20179
|
* Boot the OTA orchestrator when the init config asks for it.
|
|
19836
20180
|
*
|
|
19837
|
-
* `publicSigningKey`
|
|
19838
|
-
* from configuration alone. The
|
|
19839
|
-
*
|
|
19840
|
-
*
|
|
19841
|
-
*
|
|
19842
|
-
*
|
|
20181
|
+
* `publicSigningKey` / `publicSigningKeys` are honoured here so signature
|
|
20182
|
+
* enforcement is reachable from configuration alone. The list form exists
|
|
20183
|
+
* for key ROTATION: a build pinning [old, new] keeps verifying while the
|
|
20184
|
+
* server moves to the new key, so replacing a key never needs an emergency
|
|
20185
|
+
* store release. Both fields merge (deduplicated) into one pinned set.
|
|
20186
|
+
*
|
|
20187
|
+
* Verification comes from `ota.verifySignature` when supplied, else the
|
|
20188
|
+
* built-in @noble-based verifier (optional peers). Pinning keys with
|
|
20189
|
+
* NEITHER available is fail-CLOSED by design (signature.ts) — an update
|
|
20190
|
+
* that cannot be verified is not installed. We say that out loud rather
|
|
20191
|
+
* than letting the app discover it as a silent no-update condition.
|
|
19843
20192
|
*/
|
|
19844
20193
|
_maybeStartOta(rawConfig) {
|
|
19845
20194
|
const ota = rawConfig?.ota;
|
|
@@ -19848,15 +20197,25 @@ var ScaleBunFacade = class {
|
|
|
19848
20197
|
const {
|
|
19849
20198
|
otaOrchestrator: otaOrchestrator2
|
|
19850
20199
|
} = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
|
|
19851
|
-
const
|
|
20200
|
+
const rawSingle = ota.publicSigningKey;
|
|
20201
|
+
const rawList = ota.publicSigningKeys;
|
|
20202
|
+
const publicKeys = Array.from(new Set([...typeof rawSingle === "string" && rawSingle ? [rawSingle] : [], ...Array.isArray(rawList) ? rawList : []].filter((k) => typeof k === "string" && k.trim().length > 0)));
|
|
20203
|
+
const signingRequested = publicKeys.length > 0 || Array.isArray(rawList);
|
|
19852
20204
|
const verifier = ota.verifySignature;
|
|
19853
|
-
if (
|
|
19854
|
-
|
|
20205
|
+
if (signingRequested && typeof verifier !== "function") {
|
|
20206
|
+
const {
|
|
20207
|
+
getBuiltinVerifier: getBuiltinVerifier2
|
|
20208
|
+
} = (init_builtinVerifier(), __toCommonJS(builtinVerifier_exports));
|
|
20209
|
+
if (!getBuiltinVerifier2()) {
|
|
20210
|
+
logger.warn("[ScaleBun] A signing key is pinned but no signature verifier is available. Signature checking is fail-closed: updates will be REJECTED until one exists. Install the optional peers `@noble/ed25519` + `@noble/hashes` (no further code needed), or supply `ota.verifySignature`.");
|
|
20211
|
+
}
|
|
19855
20212
|
}
|
|
19856
20213
|
otaOrchestrator2.init({
|
|
19857
|
-
...
|
|
20214
|
+
...signingRequested ? {
|
|
19858
20215
|
signature: {
|
|
19859
|
-
|
|
20216
|
+
// Single key stays a plain string — the shape every
|
|
20217
|
+
// existing consumer and test already handles.
|
|
20218
|
+
publicKey: publicKeys.length === 1 ? publicKeys[0] : publicKeys,
|
|
19860
20219
|
verifier
|
|
19861
20220
|
}
|
|
19862
20221
|
} : {},
|
|
@@ -19925,6 +20284,8 @@ var ScaleBunFacade = class {
|
|
|
19925
20284
|
* the whole time. The SDK knows the device; the integrator is guessing at it.
|
|
19926
20285
|
* Everything outside the detected keys is still theirs.
|
|
19927
20286
|
*/
|
|
20287
|
+
// Everything Platform.constants knows. On iOS that is neither the model nor the
|
|
20288
|
+
// manufacturer, so the bridge completes it below once it answers.
|
|
19928
20289
|
context: mergeDeviceContext(rawConfig?.context),
|
|
19929
20290
|
// Use the same foreground id as replay/journey capture. The
|
|
19930
20291
|
// callback is resolved per event because the session subsystem
|
|
@@ -19938,6 +20299,7 @@ var ScaleBunFacade = class {
|
|
|
19938
20299
|
});
|
|
19939
20300
|
this._eventTracker.start();
|
|
19940
20301
|
__DEV__ && logger.info("[ScaleBun] Envelope event tracking started \u2192 /v1/batch.");
|
|
20302
|
+
this._applyNativeDeviceContext(this._eventTracker);
|
|
19941
20303
|
this._captureInstallReferrer(this._eventTracker);
|
|
19942
20304
|
const tracker = this._eventTracker;
|
|
19943
20305
|
this._configManager = new ConfigManager({
|
|
@@ -19956,6 +20318,25 @@ var ScaleBunFacade = class {
|
|
|
19956
20318
|
logger.warn("[ScaleBun] Failed to start envelope event tracking:", err?.message);
|
|
19957
20319
|
}
|
|
19958
20320
|
}
|
|
20321
|
+
/**
|
|
20322
|
+
* Fold the native bridge's device facts into the analytics context once it answers.
|
|
20323
|
+
*
|
|
20324
|
+
* Detected values never overwrite a populated one with an empty one — `nativeDeviceContext`
|
|
20325
|
+
* drops absent keys and `applyDetectedContext` ignores an empty patch — so a bridge that
|
|
20326
|
+
* cannot answer leaves whatever `Platform.constants` already found.
|
|
20327
|
+
*/
|
|
20328
|
+
_applyNativeDeviceContext(tracker) {
|
|
20329
|
+
Promise.resolve().then(() => bridgeAdapter.getDeviceInfo()).then((info) => {
|
|
20330
|
+
if (!info) return;
|
|
20331
|
+
tracker.applyDetectedContext(nativeDeviceContext({
|
|
20332
|
+
osVersion: info.osVersion,
|
|
20333
|
+
deviceModel: info.deviceModel
|
|
20334
|
+
// iOS ships no manufacturer over the bridge; it is a constant there and the
|
|
20335
|
+
// static path already set it. Android's arrives via Platform.constants.
|
|
20336
|
+
}));
|
|
20337
|
+
}).catch(() => {
|
|
20338
|
+
});
|
|
20339
|
+
}
|
|
19959
20340
|
/**
|
|
19960
20341
|
* Android-only: read the Play Install Referrer once per install and route it into
|
|
19961
20342
|
* attribution. `scalebun_click_id` → the deterministic click path; the full
|
|
@@ -20046,6 +20427,46 @@ var ScaleBunFacade = class {
|
|
|
20046
20427
|
};
|
|
20047
20428
|
}
|
|
20048
20429
|
/** Convenience: emit a Phase 1 purchase event (revenue + currency + transaction_id). */
|
|
20430
|
+
/**
|
|
20431
|
+
* Declare which UI state the user is looking at, so taps are attributed to the surface they
|
|
20432
|
+
* happened on rather than averaged across every variant of the screen.
|
|
20433
|
+
*
|
|
20434
|
+
* ScaleBun.setUiState('filter-sheet', 'open');
|
|
20435
|
+
* ScaleBun.setUiState('checkout-step', 'payment');
|
|
20436
|
+
*
|
|
20437
|
+
* Call it when the state CHANGES — the value is read at tap time, so it only has to be right by
|
|
20438
|
+
* the time the next tap lands. On React Native this is the ONLY source of UI state: unlike the web,
|
|
20439
|
+
* there is no queryable accessibility tree that says a sheet is up, and guessing would produce a
|
|
20440
|
+
* state key that is right on some apps and silently wrong on others.
|
|
20441
|
+
*
|
|
20442
|
+
* Names and values are identifiers, not content: they become query keys in the dashboard, so a
|
|
20443
|
+
* person's name or a cart total does not belong in one. `;`, `:` and `|` are stripped and both
|
|
20444
|
+
* halves are capped at 32 characters.
|
|
20445
|
+
*/
|
|
20446
|
+
setUiState(name, value) {
|
|
20447
|
+
try {
|
|
20448
|
+
const {
|
|
20449
|
+
setUiState: setUiState2
|
|
20450
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
20451
|
+
setUiState2(name, value);
|
|
20452
|
+
} catch {
|
|
20453
|
+
}
|
|
20454
|
+
}
|
|
20455
|
+
/**
|
|
20456
|
+
* Stop reporting a UI state dimension — or, with no argument, all of them.
|
|
20457
|
+
*
|
|
20458
|
+
* Clear on screen unmount. A declaration left behind follows the user onto a surface where it means
|
|
20459
|
+
* nothing, and every tap there is filed under a state that was not on screen.
|
|
20460
|
+
*/
|
|
20461
|
+
clearUiState(name) {
|
|
20462
|
+
try {
|
|
20463
|
+
const {
|
|
20464
|
+
clearUiState: clearUiState2
|
|
20465
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
20466
|
+
clearUiState2(name);
|
|
20467
|
+
} catch {
|
|
20468
|
+
}
|
|
20469
|
+
}
|
|
20049
20470
|
trackPurchase(input) {
|
|
20050
20471
|
noThrow(() => this._eventTracker?.trackPurchase(input));
|
|
20051
20472
|
}
|
|
@@ -23669,11 +24090,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
|
|
|
23669
24090
|
function resolveWheelOutcome(segments, source) {
|
|
23670
24091
|
const identity = wheelOutcomeIdentity(source);
|
|
23671
24092
|
const index = findWheelOutcomeIndex(segments, identity);
|
|
23672
|
-
const
|
|
24093
|
+
const resolved3 = index >= 0;
|
|
23673
24094
|
return {
|
|
23674
|
-
index:
|
|
23675
|
-
label: identity.resultLabel || (
|
|
23676
|
-
resolved
|
|
24095
|
+
index: resolved3 ? index : 0,
|
|
24096
|
+
label: identity.resultLabel || (resolved3 ? segments[index]?.fullLabel ?? "" : ""),
|
|
24097
|
+
resolved: resolved3
|
|
23677
24098
|
};
|
|
23678
24099
|
}
|
|
23679
24100
|
function SpinWheelInApp(props) {
|
|
@@ -32532,8 +32953,8 @@ function EngagePromptProvider({
|
|
|
32532
32953
|
__DEV__ && logger.debug("[Engage] no eligible in-app message to render");
|
|
32533
32954
|
return false;
|
|
32534
32955
|
}
|
|
32535
|
-
const
|
|
32536
|
-
activeInAppVariantRef.current = message.experiment?.enabled ?
|
|
32956
|
+
const resolved3 = selectInAppVariant(message, getDeviceId());
|
|
32957
|
+
activeInAppVariantRef.current = message.experiment?.enabled ? resolved3 : null;
|
|
32537
32958
|
store.recordImpression(message.id, Date.now(), message.revision);
|
|
32538
32959
|
setActiveInApp(message);
|
|
32539
32960
|
return true;
|
|
@@ -33241,9 +33662,22 @@ function ScaleBunDebugRoot({
|
|
|
33241
33662
|
screen = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
|
|
33242
33663
|
} catch {
|
|
33243
33664
|
}
|
|
33665
|
+
let ui;
|
|
33666
|
+
try {
|
|
33667
|
+
const {
|
|
33668
|
+
uiStateSignature: uiStateSignature2
|
|
33669
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
33670
|
+
ui = uiStateSignature2();
|
|
33671
|
+
} catch {
|
|
33672
|
+
}
|
|
33244
33673
|
emitAutomaticEvent("element_interacted", {
|
|
33245
33674
|
gesture_type: gestureType,
|
|
33246
33675
|
screen_name: screen,
|
|
33676
|
+
/* THIS MAP ENUMERATES. A field added to the payload and forgotten here reaches the
|
|
33677
|
+
backend as undefined with no error anywhere — the recurring defect class in this
|
|
33678
|
+
codebase. `ui` is the key the grid aggregate reads for state, byte-identical to
|
|
33679
|
+
the web SDK's, so one dashboard control queries both platforms. */
|
|
33680
|
+
ui,
|
|
33247
33681
|
// testID/nativeID is an author-controlled stable identifier.
|
|
33248
33682
|
// Accessibility labels and rendered text are deliberately omitted.
|
|
33249
33683
|
target_id: targetInfo?.testId,
|