@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.
Files changed (53) hide show
  1. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +75 -0
  2. package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +12 -0
  3. package/dist/scalebun.full.js +432 -51
  4. package/dist/scalebun.slim.js +432 -51
  5. package/ios/Ota/ScaleBunOtaBridge.mm +6 -0
  6. package/ios/Ota/ScaleBunOtaModule.swift +67 -0
  7. package/lib/commonjs/core/config/schema.js +34 -1
  8. package/lib/commonjs/core/constants/version.js +1 -1
  9. package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +24 -0
  10. package/lib/commonjs/features/journey/uiState.js +87 -0
  11. package/lib/commonjs/features/ota/crypto/builtinVerifier.js +248 -0
  12. package/lib/commonjs/features/ota/crypto/loadEd25519.js +40 -0
  13. package/lib/commonjs/features/ota/crypto/loadSha512.js +40 -0
  14. package/lib/commonjs/features/ota/crypto/nativeVerifier.js +121 -0
  15. package/lib/commonjs/features/ota/signature.js +87 -27
  16. package/lib/commonjs/metro/serializerCompose.js +32 -0
  17. package/lib/commonjs/public/ScaleBunFacade.js +74 -11
  18. package/lib/module/core/config/schema.js +34 -1
  19. package/lib/module/core/constants/version.js +1 -1
  20. package/lib/module/features/journey/ScaleBunDebugRoot.js +24 -0
  21. package/lib/module/features/journey/uiState.js +79 -0
  22. package/lib/module/features/ota/crypto/builtinVerifier.js +240 -0
  23. package/lib/module/features/ota/crypto/loadEd25519.js +34 -0
  24. package/lib/module/features/ota/crypto/loadSha512.js +34 -0
  25. package/lib/module/features/ota/crypto/nativeVerifier.js +113 -0
  26. package/lib/module/features/ota/signature.js +87 -27
  27. package/lib/module/metro/serializerCompose.js +32 -0
  28. package/lib/module/public/ScaleBunFacade.js +74 -11
  29. package/lib/typescript/core/config/schema.d.ts +2 -0
  30. package/lib/typescript/core/constants/version.d.ts +1 -1
  31. package/lib/typescript/features/journey/uiState.d.ts +53 -0
  32. package/lib/typescript/features/ota/OtaTypes.d.ts +50 -0
  33. package/lib/typescript/features/ota/crypto/builtinVerifier.d.ts +53 -0
  34. package/lib/typescript/features/ota/crypto/loadEd25519.d.ts +30 -0
  35. package/lib/typescript/features/ota/crypto/loadSha512.d.ts +15 -0
  36. package/lib/typescript/features/ota/crypto/nativeVerifier.d.ts +35 -0
  37. package/lib/typescript/features/ota/signature.d.ts +22 -7
  38. package/lib/typescript/public/ScaleBunFacade.d.ts +35 -6
  39. package/lib/typescript/specs/NativeScaleBunOta.d.ts +23 -0
  40. package/package.json +18 -3
  41. package/src/core/config/schema.ts +30 -3
  42. package/src/core/constants/version.ts +1 -1
  43. package/src/features/journey/ScaleBunDebugRoot.tsx +22 -0
  44. package/src/features/journey/uiState.ts +84 -0
  45. package/src/features/ota/OtaTypes.ts +51 -0
  46. package/src/features/ota/crypto/builtinVerifier.ts +257 -0
  47. package/src/features/ota/crypto/loadEd25519.ts +41 -0
  48. package/src/features/ota/crypto/loadSha512.ts +35 -0
  49. package/src/features/ota/crypto/nativeVerifier.ts +117 -0
  50. package/src/features/ota/signature.ts +108 -25
  51. package/src/metro/serializerCompose.ts +38 -2
  52. package/src/public/ScaleBunFacade.ts +87 -13
  53. package/src/specs/NativeScaleBunOta.ts +24 -0
@@ -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 resolved = LEVEL_BY_NAME[name];
96
- if (resolved !== void 0) {
97
- this.level = resolved;
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.6";
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 publicKey = config?.publicKey;
6913
- if (!publicKey) {
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
- if (typeof config?.verifier !== "function") {
6931
- logger.error("[OTA] A signing key is configured but no signature verifier is available \u2014 refusing to stage. Provide `ota.verifySignature` so signatures can be checked.");
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
- try {
6938
- const valid = await config.verifier({
6939
- messageHex: bundleSha256,
6940
- signature,
6941
- publicKey
6942
- });
6943
- if (!valid) {
6944
- logger.error("[OTA] Bundle signature is INVALID \u2014 refusing to stage.");
6945
- return {
6946
- ok: false,
6947
- reason: "invalid_signature"
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
- return {
6951
- ok: true,
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 clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
12462
- const pad = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
12463
- const outLen = Math.floor(clean.length * 3 / 4) - pad;
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 < clean.length; i += 4) {
12467
- const c0 = B64_ALPHABET.indexOf(clean[i]);
12468
- const c1 = B64_ALPHABET.indexOf(clean[i + 1]);
12469
- const c2 = B64_ALPHABET.indexOf(clean[i + 2]);
12470
- const c3 = B64_ALPHABET.indexOf(clean[i + 3]);
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` is honoured here so signature enforcement is reachable
16658
- * from configuration alone. The SDK ships no ed25519 implementation, so a
16659
- * host that pins a key must also supply `ota.verifySignature`; pinning a key
16660
- * without a verifier is fail-CLOSED by design (signature.ts) an update
16661
- * that cannot be verified is not installed. We say that out loud rather than
16662
- * letting the app discover it as a silent no-update condition.
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 publicKey = ota.publicSigningKey;
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 (publicKey && typeof verifier !== "function") {
16674
- logger.warn("[ScaleBun] ota.publicSigningKey is set but ota.verifySignature is not a function. Signature checking is fail-closed: updates will be REJECTED until a verifier is supplied.");
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
- ...publicKey ? {
17003
+ ...signingRequested ? {
16678
17004
  signature: {
16679
- publicKey,
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 resolved = index >= 0;
20882
+ const resolved3 = index >= 0;
20515
20883
  return {
20516
- index: resolved ? index : 0,
20517
- label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
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 resolved = selectInAppVariant(message, getDeviceId());
29378
- activeInAppVariantRef.current = message.experiment?.enabled ? resolved : null;
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