@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.slim.js
CHANGED
|
@@ -92,9 +92,9 @@ var init_internalLogger = __esm({
|
|
|
92
92
|
}
|
|
93
93
|
/** Set level from the public string name used in config (e.g. 'warn'). */
|
|
94
94
|
setLevelByName(name) {
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
97
|
-
this.level =
|
|
95
|
+
const resolved3 = LEVEL_BY_NAME[name];
|
|
96
|
+
if (resolved3 !== void 0) {
|
|
97
|
+
this.level = resolved3;
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
/** Current numeric level — exposed for callers that branch on dev verbosity. */
|
|
@@ -600,7 +600,7 @@ var SDK_VERSION;
|
|
|
600
600
|
var init_version = __esm({
|
|
601
601
|
"lib/module/core/constants/version.js"() {
|
|
602
602
|
"use strict";
|
|
603
|
-
SDK_VERSION = "1.10.
|
|
603
|
+
SDK_VERSION = "1.10.7";
|
|
604
604
|
}
|
|
605
605
|
});
|
|
606
606
|
|
|
@@ -642,6 +642,15 @@ function staticDeviceContext() {
|
|
|
642
642
|
if (c.Model) out.device_model = String(c.Model);
|
|
643
643
|
if (c.Manufacturer) out.manufacturer = String(c.Manufacturer);
|
|
644
644
|
else if (c.Brand) out.manufacturer = String(c.Brand);
|
|
645
|
+
else if (out.platform_os === "ios") out.manufacturer = "Apple";
|
|
646
|
+
return out;
|
|
647
|
+
}
|
|
648
|
+
function nativeDeviceContext(info) {
|
|
649
|
+
const out = {};
|
|
650
|
+
if (!info) return out;
|
|
651
|
+
if (info.osVersion) out.os_version = info.osVersion;
|
|
652
|
+
if (info.deviceModel) out.device_model = info.deviceModel;
|
|
653
|
+
if (info.manufacturer) out.manufacturer = info.manufacturer;
|
|
645
654
|
return out;
|
|
646
655
|
}
|
|
647
656
|
function mergeDeviceContext(configured, detected = staticDeviceContext()) {
|
|
@@ -6528,6 +6537,27 @@ var init_EventTracker = __esm({
|
|
|
6528
6537
|
}
|
|
6529
6538
|
this.persistQueue();
|
|
6530
6539
|
}
|
|
6540
|
+
/**
|
|
6541
|
+
* Fold in device facts that were not knowable at init.
|
|
6542
|
+
*
|
|
6543
|
+
* `Platform.constants` answers Android synchronously, so its model and manufacturer ride the
|
|
6544
|
+
* very first event. iOS exposes neither there — only the native bridge knows, and the bridge is
|
|
6545
|
+
* async and may not be installed at all. Without this the iOS half of every install reported no
|
|
6546
|
+
* model whatsoever.
|
|
6547
|
+
*
|
|
6548
|
+
* `buildEnvelope` reads `cfg.context` per event, so applying it here reaches every event from
|
|
6549
|
+
* the next one onward. Detected keys WIN over whatever the integrator configured, for the same
|
|
6550
|
+
* reason `mergeDeviceContext` inverts the usual precedence: a measurement must not be
|
|
6551
|
+
* overridable by a guess. Empty patches are ignored so a bridge that answered with nothing
|
|
6552
|
+
* cannot blank a value the static path already found.
|
|
6553
|
+
*/
|
|
6554
|
+
applyDetectedContext(patch) {
|
|
6555
|
+
if (!patch || Object.keys(patch).length === 0) return;
|
|
6556
|
+
this.cfg.context = {
|
|
6557
|
+
...this.cfg.context ?? {},
|
|
6558
|
+
...patch
|
|
6559
|
+
};
|
|
6560
|
+
}
|
|
6531
6561
|
// ─── internals ─────────────────────────────────────────────────────────────
|
|
6532
6562
|
buildEnvelope(eventName, properties) {
|
|
6533
6563
|
const ctx = this.cfg.context ?? {};
|
|
@@ -6877,10 +6907,249 @@ var init_deviceAttributes = __esm({
|
|
|
6877
6907
|
}
|
|
6878
6908
|
});
|
|
6879
6909
|
|
|
6910
|
+
// lib/module/features/ota/crypto/loadEd25519.js
|
|
6911
|
+
function loadEd25519() {
|
|
6912
|
+
try {
|
|
6913
|
+
const mod = require("@noble/ed25519");
|
|
6914
|
+
const ed = mod?.default ?? mod;
|
|
6915
|
+
if (typeof ed?.verifyAsync === "function" || typeof ed?.verify === "function") {
|
|
6916
|
+
return ed;
|
|
6917
|
+
}
|
|
6918
|
+
return null;
|
|
6919
|
+
} catch {
|
|
6920
|
+
return null;
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
var init_loadEd25519 = __esm({
|
|
6924
|
+
"lib/module/features/ota/crypto/loadEd25519.js"() {
|
|
6925
|
+
"use strict";
|
|
6926
|
+
}
|
|
6927
|
+
});
|
|
6928
|
+
|
|
6929
|
+
// lib/module/features/ota/crypto/loadSha512.js
|
|
6930
|
+
function loadSha512() {
|
|
6931
|
+
try {
|
|
6932
|
+
const mod = require("@noble/hashes/sha2.js");
|
|
6933
|
+
const sha512 = mod?.sha512 ?? mod?.default?.sha512;
|
|
6934
|
+
return typeof sha512 === "function" ? sha512 : null;
|
|
6935
|
+
} catch {
|
|
6936
|
+
return null;
|
|
6937
|
+
}
|
|
6938
|
+
}
|
|
6939
|
+
var init_loadSha512 = __esm({
|
|
6940
|
+
"lib/module/features/ota/crypto/loadSha512.js"() {
|
|
6941
|
+
"use strict";
|
|
6942
|
+
}
|
|
6943
|
+
});
|
|
6944
|
+
|
|
6945
|
+
// lib/module/features/ota/crypto/builtinVerifier.js
|
|
6946
|
+
var builtinVerifier_exports = {};
|
|
6947
|
+
__export(builtinVerifier_exports, {
|
|
6948
|
+
_resetBuiltinVerifier: () => _resetBuiltinVerifier,
|
|
6949
|
+
decodeSignature: () => decodeSignature,
|
|
6950
|
+
getBuiltinVerifier: () => getBuiltinVerifier
|
|
6951
|
+
});
|
|
6952
|
+
function hexToBytes(hex) {
|
|
6953
|
+
const clean2 = hex.trim().toLowerCase().replace(/^0x/, "");
|
|
6954
|
+
if (clean2.length === 0 || clean2.length % 2 !== 0 || /[^0-9a-f]/.test(clean2)) return null;
|
|
6955
|
+
const out = new Uint8Array(clean2.length / 2);
|
|
6956
|
+
for (let i = 0; i < out.length; i++) {
|
|
6957
|
+
out[i] = parseInt(clean2.slice(i * 2, i * 2 + 2), 16);
|
|
6958
|
+
}
|
|
6959
|
+
return out;
|
|
6960
|
+
}
|
|
6961
|
+
function base64ToBytes2(b64) {
|
|
6962
|
+
const s = b64.trim().replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
|
|
6963
|
+
if (s.length === 0) return null;
|
|
6964
|
+
const out = [];
|
|
6965
|
+
let buffer = 0;
|
|
6966
|
+
let bits = 0;
|
|
6967
|
+
for (const ch of s) {
|
|
6968
|
+
const idx = B64_ALPHABET2.indexOf(ch);
|
|
6969
|
+
if (idx === -1) return null;
|
|
6970
|
+
buffer = buffer << 6 | idx;
|
|
6971
|
+
bits += 6;
|
|
6972
|
+
if (bits >= 8) {
|
|
6973
|
+
bits -= 8;
|
|
6974
|
+
out.push(buffer >> bits & 255);
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
return Uint8Array.from(out);
|
|
6978
|
+
}
|
|
6979
|
+
function decodeSignature(sig) {
|
|
6980
|
+
const trimmed = sig.trim();
|
|
6981
|
+
if (/^(0x)?[0-9a-fA-F]{128}$/.test(trimmed)) {
|
|
6982
|
+
const hex2 = hexToBytes(trimmed);
|
|
6983
|
+
if (hex2 && hex2.length === ED25519_SIGNATURE_BYTES) return hex2;
|
|
6984
|
+
}
|
|
6985
|
+
const b64 = base64ToBytes2(trimmed);
|
|
6986
|
+
if (b64 && b64.length === ED25519_SIGNATURE_BYTES) return b64;
|
|
6987
|
+
const hex = hexToBytes(trimmed);
|
|
6988
|
+
if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
|
|
6989
|
+
return null;
|
|
6990
|
+
}
|
|
6991
|
+
function wireSha512(ed, sha512) {
|
|
6992
|
+
const concat2 = (parts) => {
|
|
6993
|
+
if (parts.length === 1) return parts[0];
|
|
6994
|
+
let total = 0;
|
|
6995
|
+
for (const p of parts) total += p.length;
|
|
6996
|
+
const out = new Uint8Array(total);
|
|
6997
|
+
let off = 0;
|
|
6998
|
+
for (const p of parts) {
|
|
6999
|
+
out.set(p, off);
|
|
7000
|
+
off += p.length;
|
|
7001
|
+
}
|
|
7002
|
+
return out;
|
|
7003
|
+
};
|
|
7004
|
+
const sync = (...msgs) => sha512(concat2(msgs));
|
|
7005
|
+
const asyncFn = async (...msgs) => sha512(concat2(msgs));
|
|
7006
|
+
let wired = false;
|
|
7007
|
+
try {
|
|
7008
|
+
if (ed.hashes && typeof ed.hashes === "object") {
|
|
7009
|
+
if (typeof ed.hashes.sha512 !== "function") ed.hashes.sha512 = sync;
|
|
7010
|
+
if (typeof ed.hashes.sha512Async !== "function") ed.hashes.sha512Async = asyncFn;
|
|
7011
|
+
wired = typeof ed.hashes.sha512 === "function";
|
|
7012
|
+
}
|
|
7013
|
+
} catch {
|
|
7014
|
+
}
|
|
7015
|
+
try {
|
|
7016
|
+
if (ed.etc && typeof ed.etc === "object") {
|
|
7017
|
+
if (typeof ed.etc.sha512Sync !== "function") ed.etc.sha512Sync = sync;
|
|
7018
|
+
if (typeof ed.etc.sha512Async !== "function") ed.etc.sha512Async = asyncFn;
|
|
7019
|
+
wired = wired || typeof ed.etc.sha512Sync === "function";
|
|
7020
|
+
}
|
|
7021
|
+
} catch {
|
|
7022
|
+
}
|
|
7023
|
+
return wired;
|
|
7024
|
+
}
|
|
7025
|
+
function getBuiltinVerifier() {
|
|
7026
|
+
if (resolved !== void 0) return resolved;
|
|
7027
|
+
const ed = loadEd25519();
|
|
7028
|
+
const sha512 = loadSha512();
|
|
7029
|
+
if (!ed || !sha512) {
|
|
7030
|
+
resolved = null;
|
|
7031
|
+
return null;
|
|
7032
|
+
}
|
|
7033
|
+
if (!wireSha512(ed, sha512)) {
|
|
7034
|
+
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.");
|
|
7035
|
+
resolved = null;
|
|
7036
|
+
return null;
|
|
7037
|
+
}
|
|
7038
|
+
resolved = async ({
|
|
7039
|
+
messageHex,
|
|
7040
|
+
signature,
|
|
7041
|
+
publicKey
|
|
7042
|
+
}) => {
|
|
7043
|
+
const message = hexToBytes(messageHex);
|
|
7044
|
+
const pub = hexToBytes(publicKey);
|
|
7045
|
+
const sig = decodeSignature(signature);
|
|
7046
|
+
if (!message) {
|
|
7047
|
+
logger.error("[OTA] Bundle SHA-256 is not valid hex \u2014 refusing to stage.");
|
|
7048
|
+
return false;
|
|
7049
|
+
}
|
|
7050
|
+
if (!pub || pub.length !== ED25519_PUBLIC_KEY_BYTES) {
|
|
7051
|
+
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"}.`);
|
|
7052
|
+
return false;
|
|
7053
|
+
}
|
|
7054
|
+
if (!sig) {
|
|
7055
|
+
logger.error("[OTA] Bundle signature is not a 64-byte hex/base64 value.");
|
|
7056
|
+
return false;
|
|
7057
|
+
}
|
|
7058
|
+
try {
|
|
7059
|
+
if (typeof ed.verify === "function") return ed.verify(sig, message, pub);
|
|
7060
|
+
if (typeof ed.verifyAsync === "function") return await ed.verifyAsync(sig, message, pub);
|
|
7061
|
+
return false;
|
|
7062
|
+
} catch {
|
|
7063
|
+
return false;
|
|
7064
|
+
}
|
|
7065
|
+
};
|
|
7066
|
+
__DEV__ && logger.debug("[OTA] Using built-in ed25519 verifier (@noble/ed25519 detected).");
|
|
7067
|
+
return resolved;
|
|
7068
|
+
}
|
|
7069
|
+
function _resetBuiltinVerifier() {
|
|
7070
|
+
resolved = void 0;
|
|
7071
|
+
}
|
|
7072
|
+
var ED25519_SIGNATURE_BYTES, ED25519_PUBLIC_KEY_BYTES, B64_ALPHABET2, resolved;
|
|
7073
|
+
var init_builtinVerifier = __esm({
|
|
7074
|
+
"lib/module/features/ota/crypto/builtinVerifier.js"() {
|
|
7075
|
+
"use strict";
|
|
7076
|
+
init_loadEd25519();
|
|
7077
|
+
init_loadSha512();
|
|
7078
|
+
init_internalLogger();
|
|
7079
|
+
ED25519_SIGNATURE_BYTES = 64;
|
|
7080
|
+
ED25519_PUBLIC_KEY_BYTES = 32;
|
|
7081
|
+
B64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7082
|
+
}
|
|
7083
|
+
});
|
|
7084
|
+
|
|
7085
|
+
// lib/module/features/ota/crypto/nativeVerifier.js
|
|
7086
|
+
function bytesToHex(bytes) {
|
|
7087
|
+
let out = "";
|
|
7088
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
7089
|
+
return out;
|
|
7090
|
+
}
|
|
7091
|
+
function normalizeHex(value, expectedChars) {
|
|
7092
|
+
const clean2 = value.trim().toLowerCase().replace(/^0x/, "");
|
|
7093
|
+
if (clean2.length !== expectedChars || /[^0-9a-f]/.test(clean2)) return null;
|
|
7094
|
+
return clean2;
|
|
7095
|
+
}
|
|
7096
|
+
function getNativeVerifier() {
|
|
7097
|
+
if (resolved2 !== void 0) return resolved2;
|
|
7098
|
+
const native = NativeScaleBunOta_default;
|
|
7099
|
+
if (!native || typeof native.verifyEd25519 !== "function") {
|
|
7100
|
+
resolved2 = null;
|
|
7101
|
+
return null;
|
|
7102
|
+
}
|
|
7103
|
+
resolved2 = async ({
|
|
7104
|
+
messageHex,
|
|
7105
|
+
signature,
|
|
7106
|
+
publicKey
|
|
7107
|
+
}) => {
|
|
7108
|
+
const message = normalizeHex(messageHex, 64);
|
|
7109
|
+
const key = normalizeHex(publicKey, ED25519_PUBLIC_KEY_HEX_CHARS);
|
|
7110
|
+
const sigBytes = decodeSignature(signature);
|
|
7111
|
+
if (!message || !key || !sigBytes) {
|
|
7112
|
+
logger.error("[OTA] Signature inputs malformed (bundle hash, signature, or public key) \u2014 refusing to stage.");
|
|
7113
|
+
return false;
|
|
7114
|
+
}
|
|
7115
|
+
let verdict;
|
|
7116
|
+
try {
|
|
7117
|
+
verdict = await native.verifyEd25519(message, bytesToHex(sigBytes), key);
|
|
7118
|
+
} catch (err) {
|
|
7119
|
+
logger.warn(`[OTA] Native ed25519 verify failed to run: ${err?.message ?? err}`);
|
|
7120
|
+
verdict = "unavailable";
|
|
7121
|
+
}
|
|
7122
|
+
if (verdict === "valid") return true;
|
|
7123
|
+
if (verdict === "invalid") return false;
|
|
7124
|
+
const builtin = getBuiltinVerifier();
|
|
7125
|
+
if (builtin) {
|
|
7126
|
+
return builtin({
|
|
7127
|
+
messageHex,
|
|
7128
|
+
signature,
|
|
7129
|
+
publicKey
|
|
7130
|
+
});
|
|
7131
|
+
}
|
|
7132
|
+
throw new Error("ed25519 unavailable: this OS has no native implementation (Android < 13) and the optional peers @noble/ed25519 + @noble/hashes are not installed.");
|
|
7133
|
+
};
|
|
7134
|
+
__DEV__ && logger.debug("[OTA] Using native ed25519 verifier (platform crypto).");
|
|
7135
|
+
return resolved2;
|
|
7136
|
+
}
|
|
7137
|
+
var ED25519_PUBLIC_KEY_HEX_CHARS, resolved2;
|
|
7138
|
+
var init_nativeVerifier = __esm({
|
|
7139
|
+
"lib/module/features/ota/crypto/nativeVerifier.js"() {
|
|
7140
|
+
"use strict";
|
|
7141
|
+
init_NativeScaleBunOta();
|
|
7142
|
+
init_internalLogger();
|
|
7143
|
+
init_builtinVerifier();
|
|
7144
|
+
ED25519_PUBLIC_KEY_HEX_CHARS = 64;
|
|
7145
|
+
}
|
|
7146
|
+
});
|
|
7147
|
+
|
|
6880
7148
|
// lib/module/features/ota/signature.js
|
|
6881
7149
|
async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
6882
|
-
const
|
|
6883
|
-
|
|
7150
|
+
const rawKey = config?.publicKey;
|
|
7151
|
+
const keys = (Array.isArray(rawKey) ? rawKey : rawKey ? [rawKey] : []).filter((k) => typeof k === "string" && k.trim().length > 0);
|
|
7152
|
+
if (rawKey === void 0 || rawKey === "" || rawKey === null) {
|
|
6884
7153
|
if (!warnedNotConfigured) {
|
|
6885
7154
|
warnedNotConfigured = true;
|
|
6886
7155
|
logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
|
|
@@ -6890,6 +7159,13 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
|
6890
7159
|
reason: "not_configured"
|
|
6891
7160
|
};
|
|
6892
7161
|
}
|
|
7162
|
+
if (keys.length === 0) {
|
|
7163
|
+
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.");
|
|
7164
|
+
return {
|
|
7165
|
+
ok: false,
|
|
7166
|
+
reason: "no_keys"
|
|
7167
|
+
};
|
|
7168
|
+
}
|
|
6893
7169
|
if (!signature) {
|
|
6894
7170
|
logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
|
|
6895
7171
|
return {
|
|
@@ -6897,43 +7173,52 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
|
6897
7173
|
reason: "missing_signature"
|
|
6898
7174
|
};
|
|
6899
7175
|
}
|
|
6900
|
-
|
|
6901
|
-
|
|
7176
|
+
const verifier = typeof config?.verifier === "function" ? config.verifier : getNativeVerifier() ?? getBuiltinVerifier();
|
|
7177
|
+
if (!verifier) {
|
|
7178
|
+
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`.");
|
|
6902
7179
|
return {
|
|
6903
7180
|
ok: false,
|
|
6904
7181
|
reason: "no_verifier"
|
|
6905
7182
|
};
|
|
6906
7183
|
}
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
7184
|
+
let sawThrow = false;
|
|
7185
|
+
let sawCompletion = false;
|
|
7186
|
+
for (const key of keys) {
|
|
7187
|
+
try {
|
|
7188
|
+
const valid = await verifier({
|
|
7189
|
+
messageHex: bundleSha256,
|
|
7190
|
+
signature,
|
|
7191
|
+
publicKey: key
|
|
7192
|
+
});
|
|
7193
|
+
sawCompletion = true;
|
|
7194
|
+
if (valid) return {
|
|
7195
|
+
ok: true,
|
|
7196
|
+
reason: "verified"
|
|
6918
7197
|
};
|
|
7198
|
+
} catch (err) {
|
|
7199
|
+
sawThrow = true;
|
|
7200
|
+
logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
|
|
6919
7201
|
}
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
reason: "verified"
|
|
6923
|
-
};
|
|
6924
|
-
} catch (err) {
|
|
6925
|
-
logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
|
|
7202
|
+
}
|
|
7203
|
+
if (sawThrow && !sawCompletion) {
|
|
6926
7204
|
return {
|
|
6927
7205
|
ok: false,
|
|
6928
7206
|
reason: "verifier_threw"
|
|
6929
7207
|
};
|
|
6930
7208
|
}
|
|
7209
|
+
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.");
|
|
7210
|
+
return {
|
|
7211
|
+
ok: false,
|
|
7212
|
+
reason: "invalid_signature"
|
|
7213
|
+
};
|
|
6931
7214
|
}
|
|
6932
7215
|
var warnedNotConfigured;
|
|
6933
7216
|
var init_signature = __esm({
|
|
6934
7217
|
"lib/module/features/ota/signature.js"() {
|
|
6935
7218
|
"use strict";
|
|
6936
7219
|
init_internalLogger();
|
|
7220
|
+
init_builtinVerifier();
|
|
7221
|
+
init_nativeVerifier();
|
|
6937
7222
|
warnedNotConfigured = false;
|
|
6938
7223
|
}
|
|
6939
7224
|
});
|
|
@@ -7753,6 +8038,35 @@ var init_OtaOrchestrator = __esm({
|
|
|
7753
8038
|
}
|
|
7754
8039
|
});
|
|
7755
8040
|
|
|
8041
|
+
// lib/module/features/journey/uiState.js
|
|
8042
|
+
var uiState_exports = {};
|
|
8043
|
+
__export(uiState_exports, {
|
|
8044
|
+
clearUiState: () => clearUiState,
|
|
8045
|
+
setUiState: () => setUiState,
|
|
8046
|
+
uiStateSignature: () => uiStateSignature
|
|
8047
|
+
});
|
|
8048
|
+
function setUiState(name, value) {
|
|
8049
|
+
const n = clean(name);
|
|
8050
|
+
const v = clean(value);
|
|
8051
|
+
if (n && v) declared.set(n, v);
|
|
8052
|
+
}
|
|
8053
|
+
function clearUiState(name) {
|
|
8054
|
+
if (name === void 0) declared.clear();
|
|
8055
|
+
else declared.delete(clean(name));
|
|
8056
|
+
}
|
|
8057
|
+
function uiStateSignature() {
|
|
8058
|
+
if (!declared.size) return void 0;
|
|
8059
|
+
return [...declared.keys()].sort().map((k) => `${k}:${declared.get(k)}`).join(";").slice(0, 96);
|
|
8060
|
+
}
|
|
8061
|
+
var declared, clean;
|
|
8062
|
+
var init_uiState = __esm({
|
|
8063
|
+
"lib/module/features/journey/uiState.js"() {
|
|
8064
|
+
"use strict";
|
|
8065
|
+
declared = /* @__PURE__ */ new Map();
|
|
8066
|
+
clean = (s) => typeof s === "string" ? s.replace(/[;:|]/g, "").trim().slice(0, 32) : "";
|
|
8067
|
+
}
|
|
8068
|
+
});
|
|
8069
|
+
|
|
7756
8070
|
// lib/module/features/engage/gestureTriggerDetector.js
|
|
7757
8071
|
var gestureTriggerDetector_exports = {};
|
|
7758
8072
|
__export(gestureTriggerDetector_exports, {
|
|
@@ -9720,6 +10034,14 @@ var SHAPE = {
|
|
|
9720
10034
|
kind: "str",
|
|
9721
10035
|
opt: true
|
|
9722
10036
|
},
|
|
10037
|
+
// Rotation support: several pinned keys, any of which may verify a
|
|
10038
|
+
// bundle. Ship a build pinning [old, new], re-sign server-side with
|
|
10039
|
+
// new, drop old next release — no emergency store submission when a
|
|
10040
|
+
// key must be replaced. Merged with publicSigningKey by the facade.
|
|
10041
|
+
publicSigningKeys: {
|
|
10042
|
+
kind: "strArr",
|
|
10043
|
+
opt: true
|
|
10044
|
+
},
|
|
9723
10045
|
mandatoryBlocksUi: bool(false)
|
|
9724
10046
|
}
|
|
9725
10047
|
}
|
|
@@ -9755,7 +10077,7 @@ function parseField(field, input, path, issues) {
|
|
|
9755
10077
|
value: field.def,
|
|
9756
10078
|
present: true
|
|
9757
10079
|
};
|
|
9758
|
-
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");
|
|
10080
|
+
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");
|
|
9759
10081
|
}
|
|
9760
10082
|
switch (field.kind) {
|
|
9761
10083
|
case "any":
|
|
@@ -9781,6 +10103,27 @@ function parseField(field, input, path, issues) {
|
|
|
9781
10103
|
value: input,
|
|
9782
10104
|
present: true
|
|
9783
10105
|
};
|
|
10106
|
+
case "strArr": {
|
|
10107
|
+
if (!Array.isArray(input)) return fail(`Expected array, received ${typeofName(input)}`);
|
|
10108
|
+
let allOk = true;
|
|
10109
|
+
for (let i = 0; i < input.length; i++) {
|
|
10110
|
+
if (typeof input[i] !== "string") {
|
|
10111
|
+
issues.push({
|
|
10112
|
+
path: [...path, i],
|
|
10113
|
+
message: `Expected string, received ${typeofName(input[i])}`
|
|
10114
|
+
});
|
|
10115
|
+
allOk = false;
|
|
10116
|
+
}
|
|
10117
|
+
}
|
|
10118
|
+
return allOk ? {
|
|
10119
|
+
ok: true,
|
|
10120
|
+
value: [...input],
|
|
10121
|
+
present: true
|
|
10122
|
+
} : {
|
|
10123
|
+
ok: false,
|
|
10124
|
+
present: true
|
|
10125
|
+
};
|
|
10126
|
+
}
|
|
9784
10127
|
case "num":
|
|
9785
10128
|
if (typeof input !== "number" || Number.isNaN(input)) {
|
|
9786
10129
|
return fail(`Expected number, received ${typeofName(input)}`);
|
|
@@ -12428,16 +12771,16 @@ function base64ToBytes(base64) {
|
|
|
12428
12771
|
return decodeManual(base64);
|
|
12429
12772
|
}
|
|
12430
12773
|
function decodeManual(base64) {
|
|
12431
|
-
const
|
|
12432
|
-
const pad =
|
|
12433
|
-
const outLen = Math.floor(
|
|
12774
|
+
const clean2 = base64.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
12775
|
+
const pad = clean2.endsWith("==") ? 2 : clean2.endsWith("=") ? 1 : 0;
|
|
12776
|
+
const outLen = Math.floor(clean2.length * 3 / 4) - pad;
|
|
12434
12777
|
const out = new Uint8Array(outLen);
|
|
12435
12778
|
let p = 0;
|
|
12436
|
-
for (let i = 0; i <
|
|
12437
|
-
const c0 = B64_ALPHABET.indexOf(
|
|
12438
|
-
const c1 = B64_ALPHABET.indexOf(
|
|
12439
|
-
const c2 = B64_ALPHABET.indexOf(
|
|
12440
|
-
const c3 = B64_ALPHABET.indexOf(
|
|
12779
|
+
for (let i = 0; i < clean2.length; i += 4) {
|
|
12780
|
+
const c0 = B64_ALPHABET.indexOf(clean2[i]);
|
|
12781
|
+
const c1 = B64_ALPHABET.indexOf(clean2[i + 1]);
|
|
12782
|
+
const c2 = B64_ALPHABET.indexOf(clean2[i + 2]);
|
|
12783
|
+
const c3 = B64_ALPHABET.indexOf(clean2[i + 3]);
|
|
12441
12784
|
const n = c0 << 18 | c1 << 12 | (c2 & 63) << 6 | c3 & 63;
|
|
12442
12785
|
if (p < outLen) out[p++] = n >> 16 & 255;
|
|
12443
12786
|
if (p < outLen) out[p++] = n >> 8 & 255;
|
|
@@ -14198,6 +14541,7 @@ var SDKBootstrapper = class {
|
|
|
14198
14541
|
|
|
14199
14542
|
// lib/module/public/ScaleBunFacade.js
|
|
14200
14543
|
init_EventTracker();
|
|
14544
|
+
init_bridgeAdapter();
|
|
14201
14545
|
init_automaticEvents();
|
|
14202
14546
|
|
|
14203
14547
|
// lib/module/analytics/eventLane.js
|
|
@@ -16623,12 +16967,17 @@ var ScaleBunFacade = class {
|
|
|
16623
16967
|
/**
|
|
16624
16968
|
* Boot the OTA orchestrator when the init config asks for it.
|
|
16625
16969
|
*
|
|
16626
|
-
* `publicSigningKey`
|
|
16627
|
-
* from configuration alone. The
|
|
16628
|
-
*
|
|
16629
|
-
*
|
|
16630
|
-
*
|
|
16631
|
-
*
|
|
16970
|
+
* `publicSigningKey` / `publicSigningKeys` are honoured here so signature
|
|
16971
|
+
* enforcement is reachable from configuration alone. The list form exists
|
|
16972
|
+
* for key ROTATION: a build pinning [old, new] keeps verifying while the
|
|
16973
|
+
* server moves to the new key, so replacing a key never needs an emergency
|
|
16974
|
+
* store release. Both fields merge (deduplicated) into one pinned set.
|
|
16975
|
+
*
|
|
16976
|
+
* Verification comes from `ota.verifySignature` when supplied, else the
|
|
16977
|
+
* built-in @noble-based verifier (optional peers). Pinning keys with
|
|
16978
|
+
* NEITHER available is fail-CLOSED by design (signature.ts) — an update
|
|
16979
|
+
* that cannot be verified is not installed. We say that out loud rather
|
|
16980
|
+
* than letting the app discover it as a silent no-update condition.
|
|
16632
16981
|
*/
|
|
16633
16982
|
_maybeStartOta(rawConfig) {
|
|
16634
16983
|
const ota = rawConfig?.ota;
|
|
@@ -16637,15 +16986,25 @@ var ScaleBunFacade = class {
|
|
|
16637
16986
|
const {
|
|
16638
16987
|
otaOrchestrator: otaOrchestrator2
|
|
16639
16988
|
} = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
|
|
16640
|
-
const
|
|
16989
|
+
const rawSingle = ota.publicSigningKey;
|
|
16990
|
+
const rawList = ota.publicSigningKeys;
|
|
16991
|
+
const publicKeys = Array.from(new Set([...typeof rawSingle === "string" && rawSingle ? [rawSingle] : [], ...Array.isArray(rawList) ? rawList : []].filter((k) => typeof k === "string" && k.trim().length > 0)));
|
|
16992
|
+
const signingRequested = publicKeys.length > 0 || Array.isArray(rawList);
|
|
16641
16993
|
const verifier = ota.verifySignature;
|
|
16642
|
-
if (
|
|
16643
|
-
|
|
16994
|
+
if (signingRequested && typeof verifier !== "function") {
|
|
16995
|
+
const {
|
|
16996
|
+
getBuiltinVerifier: getBuiltinVerifier2
|
|
16997
|
+
} = (init_builtinVerifier(), __toCommonJS(builtinVerifier_exports));
|
|
16998
|
+
if (!getBuiltinVerifier2()) {
|
|
16999
|
+
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`.");
|
|
17000
|
+
}
|
|
16644
17001
|
}
|
|
16645
17002
|
otaOrchestrator2.init({
|
|
16646
|
-
...
|
|
17003
|
+
...signingRequested ? {
|
|
16647
17004
|
signature: {
|
|
16648
|
-
|
|
17005
|
+
// Single key stays a plain string — the shape every
|
|
17006
|
+
// existing consumer and test already handles.
|
|
17007
|
+
publicKey: publicKeys.length === 1 ? publicKeys[0] : publicKeys,
|
|
16649
17008
|
verifier
|
|
16650
17009
|
}
|
|
16651
17010
|
} : {},
|
|
@@ -16714,6 +17073,8 @@ var ScaleBunFacade = class {
|
|
|
16714
17073
|
* the whole time. The SDK knows the device; the integrator is guessing at it.
|
|
16715
17074
|
* Everything outside the detected keys is still theirs.
|
|
16716
17075
|
*/
|
|
17076
|
+
// Everything Platform.constants knows. On iOS that is neither the model nor the
|
|
17077
|
+
// manufacturer, so the bridge completes it below once it answers.
|
|
16717
17078
|
context: mergeDeviceContext(rawConfig?.context),
|
|
16718
17079
|
// Use the same foreground id as replay/journey capture. The
|
|
16719
17080
|
// callback is resolved per event because the session subsystem
|
|
@@ -16727,6 +17088,7 @@ var ScaleBunFacade = class {
|
|
|
16727
17088
|
});
|
|
16728
17089
|
this._eventTracker.start();
|
|
16729
17090
|
__DEV__ && logger.info("[ScaleBun] Envelope event tracking started \u2192 /v1/batch.");
|
|
17091
|
+
this._applyNativeDeviceContext(this._eventTracker);
|
|
16730
17092
|
this._captureInstallReferrer(this._eventTracker);
|
|
16731
17093
|
const tracker = this._eventTracker;
|
|
16732
17094
|
this._configManager = new ConfigManager({
|
|
@@ -16745,6 +17107,25 @@ var ScaleBunFacade = class {
|
|
|
16745
17107
|
logger.warn("[ScaleBun] Failed to start envelope event tracking:", err?.message);
|
|
16746
17108
|
}
|
|
16747
17109
|
}
|
|
17110
|
+
/**
|
|
17111
|
+
* Fold the native bridge's device facts into the analytics context once it answers.
|
|
17112
|
+
*
|
|
17113
|
+
* Detected values never overwrite a populated one with an empty one — `nativeDeviceContext`
|
|
17114
|
+
* drops absent keys and `applyDetectedContext` ignores an empty patch — so a bridge that
|
|
17115
|
+
* cannot answer leaves whatever `Platform.constants` already found.
|
|
17116
|
+
*/
|
|
17117
|
+
_applyNativeDeviceContext(tracker) {
|
|
17118
|
+
Promise.resolve().then(() => bridgeAdapter.getDeviceInfo()).then((info) => {
|
|
17119
|
+
if (!info) return;
|
|
17120
|
+
tracker.applyDetectedContext(nativeDeviceContext({
|
|
17121
|
+
osVersion: info.osVersion,
|
|
17122
|
+
deviceModel: info.deviceModel
|
|
17123
|
+
// iOS ships no manufacturer over the bridge; it is a constant there and the
|
|
17124
|
+
// static path already set it. Android's arrives via Platform.constants.
|
|
17125
|
+
}));
|
|
17126
|
+
}).catch(() => {
|
|
17127
|
+
});
|
|
17128
|
+
}
|
|
16748
17129
|
/**
|
|
16749
17130
|
* Android-only: read the Play Install Referrer once per install and route it into
|
|
16750
17131
|
* attribution. `scalebun_click_id` → the deterministic click path; the full
|
|
@@ -16835,6 +17216,46 @@ var ScaleBunFacade = class {
|
|
|
16835
17216
|
};
|
|
16836
17217
|
}
|
|
16837
17218
|
/** Convenience: emit a Phase 1 purchase event (revenue + currency + transaction_id). */
|
|
17219
|
+
/**
|
|
17220
|
+
* Declare which UI state the user is looking at, so taps are attributed to the surface they
|
|
17221
|
+
* happened on rather than averaged across every variant of the screen.
|
|
17222
|
+
*
|
|
17223
|
+
* ScaleBun.setUiState('filter-sheet', 'open');
|
|
17224
|
+
* ScaleBun.setUiState('checkout-step', 'payment');
|
|
17225
|
+
*
|
|
17226
|
+
* Call it when the state CHANGES — the value is read at tap time, so it only has to be right by
|
|
17227
|
+
* the time the next tap lands. On React Native this is the ONLY source of UI state: unlike the web,
|
|
17228
|
+
* there is no queryable accessibility tree that says a sheet is up, and guessing would produce a
|
|
17229
|
+
* state key that is right on some apps and silently wrong on others.
|
|
17230
|
+
*
|
|
17231
|
+
* Names and values are identifiers, not content: they become query keys in the dashboard, so a
|
|
17232
|
+
* person's name or a cart total does not belong in one. `;`, `:` and `|` are stripped and both
|
|
17233
|
+
* halves are capped at 32 characters.
|
|
17234
|
+
*/
|
|
17235
|
+
setUiState(name, value) {
|
|
17236
|
+
try {
|
|
17237
|
+
const {
|
|
17238
|
+
setUiState: setUiState2
|
|
17239
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
17240
|
+
setUiState2(name, value);
|
|
17241
|
+
} catch {
|
|
17242
|
+
}
|
|
17243
|
+
}
|
|
17244
|
+
/**
|
|
17245
|
+
* Stop reporting a UI state dimension — or, with no argument, all of them.
|
|
17246
|
+
*
|
|
17247
|
+
* Clear on screen unmount. A declaration left behind follows the user onto a surface where it means
|
|
17248
|
+
* nothing, and every tap there is filed under a state that was not on screen.
|
|
17249
|
+
*/
|
|
17250
|
+
clearUiState(name) {
|
|
17251
|
+
try {
|
|
17252
|
+
const {
|
|
17253
|
+
clearUiState: clearUiState2
|
|
17254
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
17255
|
+
clearUiState2(name);
|
|
17256
|
+
} catch {
|
|
17257
|
+
}
|
|
17258
|
+
}
|
|
16838
17259
|
trackPurchase(input) {
|
|
16839
17260
|
noThrow(() => this._eventTracker?.trackPurchase(input));
|
|
16840
17261
|
}
|
|
@@ -20458,11 +20879,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
|
|
|
20458
20879
|
function resolveWheelOutcome(segments, source) {
|
|
20459
20880
|
const identity = wheelOutcomeIdentity(source);
|
|
20460
20881
|
const index = findWheelOutcomeIndex(segments, identity);
|
|
20461
|
-
const
|
|
20882
|
+
const resolved3 = index >= 0;
|
|
20462
20883
|
return {
|
|
20463
|
-
index:
|
|
20464
|
-
label: identity.resultLabel || (
|
|
20465
|
-
resolved
|
|
20884
|
+
index: resolved3 ? index : 0,
|
|
20885
|
+
label: identity.resultLabel || (resolved3 ? segments[index]?.fullLabel ?? "" : ""),
|
|
20886
|
+
resolved: resolved3
|
|
20466
20887
|
};
|
|
20467
20888
|
}
|
|
20468
20889
|
function SpinWheelInApp(props) {
|
|
@@ -29321,8 +29742,8 @@ function EngagePromptProvider({
|
|
|
29321
29742
|
__DEV__ && logger.debug("[Engage] no eligible in-app message to render");
|
|
29322
29743
|
return false;
|
|
29323
29744
|
}
|
|
29324
|
-
const
|
|
29325
|
-
activeInAppVariantRef.current = message.experiment?.enabled ?
|
|
29745
|
+
const resolved3 = selectInAppVariant(message, getDeviceId());
|
|
29746
|
+
activeInAppVariantRef.current = message.experiment?.enabled ? resolved3 : null;
|
|
29326
29747
|
store.recordImpression(message.id, Date.now(), message.revision);
|
|
29327
29748
|
setActiveInApp(message);
|
|
29328
29749
|
return true;
|
|
@@ -30030,9 +30451,22 @@ function ScaleBunDebugRoot({
|
|
|
30030
30451
|
screen = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
|
|
30031
30452
|
} catch {
|
|
30032
30453
|
}
|
|
30454
|
+
let ui;
|
|
30455
|
+
try {
|
|
30456
|
+
const {
|
|
30457
|
+
uiStateSignature: uiStateSignature2
|
|
30458
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
30459
|
+
ui = uiStateSignature2();
|
|
30460
|
+
} catch {
|
|
30461
|
+
}
|
|
30033
30462
|
emitAutomaticEvent("element_interacted", {
|
|
30034
30463
|
gesture_type: gestureType,
|
|
30035
30464
|
screen_name: screen,
|
|
30465
|
+
/* THIS MAP ENUMERATES. A field added to the payload and forgotten here reaches the
|
|
30466
|
+
backend as undefined with no error anywhere — the recurring defect class in this
|
|
30467
|
+
codebase. `ui` is the key the grid aggregate reads for state, byte-identical to
|
|
30468
|
+
the web SDK's, so one dashboard control queries both platforms. */
|
|
30469
|
+
ui,
|
|
30036
30470
|
// testID/nativeID is an author-controlled stable identifier.
|
|
30037
30471
|
// Accessibility labels and rendered text are deliberately omitted.
|
|
30038
30472
|
target_id: targetInfo?.testId,
|