@scalebun/react-native 1.10.6 → 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 +432 -51
- package/dist/scalebun.slim.js +432 -51
- package/ios/Ota/ScaleBunOtaBridge.mm +6 -0
- package/ios/Ota/ScaleBunOtaModule.swift +67 -0
- package/lib/commonjs/core/config/schema.js +34 -1
- package/lib/commonjs/core/constants/version.js +1 -1
- 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 +74 -11
- package/lib/module/core/config/schema.js +34 -1
- package/lib/module/core/constants/version.js +1 -1
- 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 +74 -11
- package/lib/typescript/core/config/schema.d.ts +2 -0
- package/lib/typescript/core/constants/version.d.ts +1 -1
- 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 +35 -6
- package/lib/typescript/specs/NativeScaleBunOta.d.ts +23 -0
- package/package.json +18 -3
- package/src/core/config/schema.ts +30 -3
- package/src/core/constants/version.ts +1 -1
- 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 +87 -13
- 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
|
|
|
@@ -6907,10 +6907,249 @@ var init_deviceAttributes = __esm({
|
|
|
6907
6907
|
}
|
|
6908
6908
|
});
|
|
6909
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
|
+
|
|
6910
7148
|
// lib/module/features/ota/signature.js
|
|
6911
7149
|
async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
6912
|
-
const
|
|
6913
|
-
|
|
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) {
|
|
6914
7153
|
if (!warnedNotConfigured) {
|
|
6915
7154
|
warnedNotConfigured = true;
|
|
6916
7155
|
logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
|
|
@@ -6920,6 +7159,13 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
|
6920
7159
|
reason: "not_configured"
|
|
6921
7160
|
};
|
|
6922
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
|
+
}
|
|
6923
7169
|
if (!signature) {
|
|
6924
7170
|
logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
|
|
6925
7171
|
return {
|
|
@@ -6927,43 +7173,52 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
|
6927
7173
|
reason: "missing_signature"
|
|
6928
7174
|
};
|
|
6929
7175
|
}
|
|
6930
|
-
|
|
6931
|
-
|
|
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`.");
|
|
6932
7179
|
return {
|
|
6933
7180
|
ok: false,
|
|
6934
7181
|
reason: "no_verifier"
|
|
6935
7182
|
};
|
|
6936
7183
|
}
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
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"
|
|
6948
7197
|
};
|
|
7198
|
+
} catch (err) {
|
|
7199
|
+
sawThrow = true;
|
|
7200
|
+
logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
|
|
6949
7201
|
}
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
reason: "verified"
|
|
6953
|
-
};
|
|
6954
|
-
} catch (err) {
|
|
6955
|
-
logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
|
|
7202
|
+
}
|
|
7203
|
+
if (sawThrow && !sawCompletion) {
|
|
6956
7204
|
return {
|
|
6957
7205
|
ok: false,
|
|
6958
7206
|
reason: "verifier_threw"
|
|
6959
7207
|
};
|
|
6960
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
|
+
};
|
|
6961
7214
|
}
|
|
6962
7215
|
var warnedNotConfigured;
|
|
6963
7216
|
var init_signature = __esm({
|
|
6964
7217
|
"lib/module/features/ota/signature.js"() {
|
|
6965
7218
|
"use strict";
|
|
6966
7219
|
init_internalLogger();
|
|
7220
|
+
init_builtinVerifier();
|
|
7221
|
+
init_nativeVerifier();
|
|
6967
7222
|
warnedNotConfigured = false;
|
|
6968
7223
|
}
|
|
6969
7224
|
});
|
|
@@ -7783,6 +8038,35 @@ var init_OtaOrchestrator = __esm({
|
|
|
7783
8038
|
}
|
|
7784
8039
|
});
|
|
7785
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
|
+
|
|
7786
8070
|
// lib/module/features/engage/gestureTriggerDetector.js
|
|
7787
8071
|
var gestureTriggerDetector_exports = {};
|
|
7788
8072
|
__export(gestureTriggerDetector_exports, {
|
|
@@ -9750,6 +10034,14 @@ var SHAPE = {
|
|
|
9750
10034
|
kind: "str",
|
|
9751
10035
|
opt: true
|
|
9752
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
|
+
},
|
|
9753
10045
|
mandatoryBlocksUi: bool(false)
|
|
9754
10046
|
}
|
|
9755
10047
|
}
|
|
@@ -9785,7 +10077,7 @@ function parseField(field, input, path, issues) {
|
|
|
9785
10077
|
value: field.def,
|
|
9786
10078
|
present: true
|
|
9787
10079
|
};
|
|
9788
|
-
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");
|
|
9789
10081
|
}
|
|
9790
10082
|
switch (field.kind) {
|
|
9791
10083
|
case "any":
|
|
@@ -9811,6 +10103,27 @@ function parseField(field, input, path, issues) {
|
|
|
9811
10103
|
value: input,
|
|
9812
10104
|
present: true
|
|
9813
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
|
+
}
|
|
9814
10127
|
case "num":
|
|
9815
10128
|
if (typeof input !== "number" || Number.isNaN(input)) {
|
|
9816
10129
|
return fail(`Expected number, received ${typeofName(input)}`);
|
|
@@ -12458,16 +12771,16 @@ function base64ToBytes(base64) {
|
|
|
12458
12771
|
return decodeManual(base64);
|
|
12459
12772
|
}
|
|
12460
12773
|
function decodeManual(base64) {
|
|
12461
|
-
const
|
|
12462
|
-
const pad =
|
|
12463
|
-
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;
|
|
12464
12777
|
const out = new Uint8Array(outLen);
|
|
12465
12778
|
let p = 0;
|
|
12466
|
-
for (let i = 0; i <
|
|
12467
|
-
const c0 = B64_ALPHABET.indexOf(
|
|
12468
|
-
const c1 = B64_ALPHABET.indexOf(
|
|
12469
|
-
const c2 = B64_ALPHABET.indexOf(
|
|
12470
|
-
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]);
|
|
12471
12784
|
const n = c0 << 18 | c1 << 12 | (c2 & 63) << 6 | c3 & 63;
|
|
12472
12785
|
if (p < outLen) out[p++] = n >> 16 & 255;
|
|
12473
12786
|
if (p < outLen) out[p++] = n >> 8 & 255;
|
|
@@ -16654,12 +16967,17 @@ var ScaleBunFacade = class {
|
|
|
16654
16967
|
/**
|
|
16655
16968
|
* Boot the OTA orchestrator when the init config asks for it.
|
|
16656
16969
|
*
|
|
16657
|
-
* `publicSigningKey`
|
|
16658
|
-
* from configuration alone. The
|
|
16659
|
-
*
|
|
16660
|
-
*
|
|
16661
|
-
*
|
|
16662
|
-
*
|
|
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.
|
|
16663
16981
|
*/
|
|
16664
16982
|
_maybeStartOta(rawConfig) {
|
|
16665
16983
|
const ota = rawConfig?.ota;
|
|
@@ -16668,15 +16986,25 @@ var ScaleBunFacade = class {
|
|
|
16668
16986
|
const {
|
|
16669
16987
|
otaOrchestrator: otaOrchestrator2
|
|
16670
16988
|
} = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
|
|
16671
|
-
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);
|
|
16672
16993
|
const verifier = ota.verifySignature;
|
|
16673
|
-
if (
|
|
16674
|
-
|
|
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
|
+
}
|
|
16675
17001
|
}
|
|
16676
17002
|
otaOrchestrator2.init({
|
|
16677
|
-
...
|
|
17003
|
+
...signingRequested ? {
|
|
16678
17004
|
signature: {
|
|
16679
|
-
|
|
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,
|
|
16680
17008
|
verifier
|
|
16681
17009
|
}
|
|
16682
17010
|
} : {},
|
|
@@ -16888,6 +17216,46 @@ var ScaleBunFacade = class {
|
|
|
16888
17216
|
};
|
|
16889
17217
|
}
|
|
16890
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
|
+
}
|
|
16891
17259
|
trackPurchase(input) {
|
|
16892
17260
|
noThrow(() => this._eventTracker?.trackPurchase(input));
|
|
16893
17261
|
}
|
|
@@ -20511,11 +20879,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
|
|
|
20511
20879
|
function resolveWheelOutcome(segments, source) {
|
|
20512
20880
|
const identity = wheelOutcomeIdentity(source);
|
|
20513
20881
|
const index = findWheelOutcomeIndex(segments, identity);
|
|
20514
|
-
const
|
|
20882
|
+
const resolved3 = index >= 0;
|
|
20515
20883
|
return {
|
|
20516
|
-
index:
|
|
20517
|
-
label: identity.resultLabel || (
|
|
20518
|
-
resolved
|
|
20884
|
+
index: resolved3 ? index : 0,
|
|
20885
|
+
label: identity.resultLabel || (resolved3 ? segments[index]?.fullLabel ?? "" : ""),
|
|
20886
|
+
resolved: resolved3
|
|
20519
20887
|
};
|
|
20520
20888
|
}
|
|
20521
20889
|
function SpinWheelInApp(props) {
|
|
@@ -29374,8 +29742,8 @@ function EngagePromptProvider({
|
|
|
29374
29742
|
__DEV__ && logger.debug("[Engage] no eligible in-app message to render");
|
|
29375
29743
|
return false;
|
|
29376
29744
|
}
|
|
29377
|
-
const
|
|
29378
|
-
activeInAppVariantRef.current = message.experiment?.enabled ?
|
|
29745
|
+
const resolved3 = selectInAppVariant(message, getDeviceId());
|
|
29746
|
+
activeInAppVariantRef.current = message.experiment?.enabled ? resolved3 : null;
|
|
29379
29747
|
store.recordImpression(message.id, Date.now(), message.revision);
|
|
29380
29748
|
setActiveInApp(message);
|
|
29381
29749
|
return true;
|
|
@@ -30083,9 +30451,22 @@ function ScaleBunDebugRoot({
|
|
|
30083
30451
|
screen = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
|
|
30084
30452
|
} catch {
|
|
30085
30453
|
}
|
|
30454
|
+
let ui;
|
|
30455
|
+
try {
|
|
30456
|
+
const {
|
|
30457
|
+
uiStateSignature: uiStateSignature2
|
|
30458
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
30459
|
+
ui = uiStateSignature2();
|
|
30460
|
+
} catch {
|
|
30461
|
+
}
|
|
30086
30462
|
emitAutomaticEvent("element_interacted", {
|
|
30087
30463
|
gesture_type: gestureType,
|
|
30088
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,
|
|
30089
30470
|
// testID/nativeID is an author-controlled stable identifier.
|
|
30090
30471
|
// Accessibility labels and rendered text are deliberately omitted.
|
|
30091
30472
|
target_id: targetInfo?.testId,
|
|
@@ -40,6 +40,12 @@ RCT_EXTERN_METHOD(revertToPrevious:(RCTPromiseResolveBlock)resolve
|
|
|
40
40
|
|
|
41
41
|
RCT_EXTERN_METHOD(restartApp)
|
|
42
42
|
|
|
43
|
+
RCT_EXTERN_METHOD(verifyEd25519:(NSString *)messageHex
|
|
44
|
+
signatureHex:(NSString *)signatureHex
|
|
45
|
+
publicKeyHex:(NSString *)publicKeyHex
|
|
46
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
47
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
48
|
+
|
|
43
49
|
@end
|
|
44
50
|
|
|
45
51
|
// Download-progress event channel. Swift subclass of RCTEventEmitter; only
|