@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
@@ -89,9 +89,9 @@ var init_internalLogger = __esm({
89
89
  }
90
90
  /** Set level from the public string name used in config (e.g. 'warn'). */
91
91
  setLevelByName(name) {
92
- const resolved = LEVEL_BY_NAME[name];
93
- if (resolved !== void 0) {
94
- this.level = resolved;
92
+ const resolved3 = LEVEL_BY_NAME[name];
93
+ if (resolved3 !== void 0) {
94
+ this.level = resolved3;
95
95
  }
96
96
  }
97
97
  /** Current numeric level — exposed for callers that branch on dev verbosity. */
@@ -605,7 +605,7 @@ var SDK_VERSION;
605
605
  var init_version = __esm({
606
606
  "lib/module/core/constants/version.js"() {
607
607
  "use strict";
608
- SDK_VERSION = "1.10.6";
608
+ SDK_VERSION = "1.10.7";
609
609
  }
610
610
  });
611
611
 
@@ -11611,10 +11611,249 @@ var init_deviceAttributes = __esm({
11611
11611
  }
11612
11612
  });
11613
11613
 
11614
+ // lib/module/features/ota/crypto/loadEd25519.js
11615
+ function loadEd25519() {
11616
+ try {
11617
+ const mod = require("@noble/ed25519");
11618
+ const ed = mod?.default ?? mod;
11619
+ if (typeof ed?.verifyAsync === "function" || typeof ed?.verify === "function") {
11620
+ return ed;
11621
+ }
11622
+ return null;
11623
+ } catch {
11624
+ return null;
11625
+ }
11626
+ }
11627
+ var init_loadEd25519 = __esm({
11628
+ "lib/module/features/ota/crypto/loadEd25519.js"() {
11629
+ "use strict";
11630
+ }
11631
+ });
11632
+
11633
+ // lib/module/features/ota/crypto/loadSha512.js
11634
+ function loadSha512() {
11635
+ try {
11636
+ const mod = require("@noble/hashes/sha2.js");
11637
+ const sha512 = mod?.sha512 ?? mod?.default?.sha512;
11638
+ return typeof sha512 === "function" ? sha512 : null;
11639
+ } catch {
11640
+ return null;
11641
+ }
11642
+ }
11643
+ var init_loadSha512 = __esm({
11644
+ "lib/module/features/ota/crypto/loadSha512.js"() {
11645
+ "use strict";
11646
+ }
11647
+ });
11648
+
11649
+ // lib/module/features/ota/crypto/builtinVerifier.js
11650
+ var builtinVerifier_exports = {};
11651
+ __export(builtinVerifier_exports, {
11652
+ _resetBuiltinVerifier: () => _resetBuiltinVerifier,
11653
+ decodeSignature: () => decodeSignature,
11654
+ getBuiltinVerifier: () => getBuiltinVerifier
11655
+ });
11656
+ function hexToBytes(hex) {
11657
+ const clean2 = hex.trim().toLowerCase().replace(/^0x/, "");
11658
+ if (clean2.length === 0 || clean2.length % 2 !== 0 || /[^0-9a-f]/.test(clean2)) return null;
11659
+ const out = new Uint8Array(clean2.length / 2);
11660
+ for (let i = 0; i < out.length; i++) {
11661
+ out[i] = parseInt(clean2.slice(i * 2, i * 2 + 2), 16);
11662
+ }
11663
+ return out;
11664
+ }
11665
+ function base64ToBytes2(b64) {
11666
+ const s = b64.trim().replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
11667
+ if (s.length === 0) return null;
11668
+ const out = [];
11669
+ let buffer = 0;
11670
+ let bits = 0;
11671
+ for (const ch of s) {
11672
+ const idx = B64_ALPHABET2.indexOf(ch);
11673
+ if (idx === -1) return null;
11674
+ buffer = buffer << 6 | idx;
11675
+ bits += 6;
11676
+ if (bits >= 8) {
11677
+ bits -= 8;
11678
+ out.push(buffer >> bits & 255);
11679
+ }
11680
+ }
11681
+ return Uint8Array.from(out);
11682
+ }
11683
+ function decodeSignature(sig) {
11684
+ const trimmed = sig.trim();
11685
+ if (/^(0x)?[0-9a-fA-F]{128}$/.test(trimmed)) {
11686
+ const hex2 = hexToBytes(trimmed);
11687
+ if (hex2 && hex2.length === ED25519_SIGNATURE_BYTES) return hex2;
11688
+ }
11689
+ const b64 = base64ToBytes2(trimmed);
11690
+ if (b64 && b64.length === ED25519_SIGNATURE_BYTES) return b64;
11691
+ const hex = hexToBytes(trimmed);
11692
+ if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
11693
+ return null;
11694
+ }
11695
+ function wireSha512(ed, sha512) {
11696
+ const concat2 = (parts) => {
11697
+ if (parts.length === 1) return parts[0];
11698
+ let total = 0;
11699
+ for (const p of parts) total += p.length;
11700
+ const out = new Uint8Array(total);
11701
+ let off = 0;
11702
+ for (const p of parts) {
11703
+ out.set(p, off);
11704
+ off += p.length;
11705
+ }
11706
+ return out;
11707
+ };
11708
+ const sync = (...msgs) => sha512(concat2(msgs));
11709
+ const asyncFn = async (...msgs) => sha512(concat2(msgs));
11710
+ let wired = false;
11711
+ try {
11712
+ if (ed.hashes && typeof ed.hashes === "object") {
11713
+ if (typeof ed.hashes.sha512 !== "function") ed.hashes.sha512 = sync;
11714
+ if (typeof ed.hashes.sha512Async !== "function") ed.hashes.sha512Async = asyncFn;
11715
+ wired = typeof ed.hashes.sha512 === "function";
11716
+ }
11717
+ } catch {
11718
+ }
11719
+ try {
11720
+ if (ed.etc && typeof ed.etc === "object") {
11721
+ if (typeof ed.etc.sha512Sync !== "function") ed.etc.sha512Sync = sync;
11722
+ if (typeof ed.etc.sha512Async !== "function") ed.etc.sha512Async = asyncFn;
11723
+ wired = wired || typeof ed.etc.sha512Sync === "function";
11724
+ }
11725
+ } catch {
11726
+ }
11727
+ return wired;
11728
+ }
11729
+ function getBuiltinVerifier() {
11730
+ if (resolved !== void 0) return resolved;
11731
+ const ed = loadEd25519();
11732
+ const sha512 = loadSha512();
11733
+ if (!ed || !sha512) {
11734
+ resolved = null;
11735
+ return null;
11736
+ }
11737
+ if (!wireSha512(ed, sha512)) {
11738
+ logger.error("[OTA] @noble/ed25519 is installed but its SHA-512 provider could not be wired (unrecognised version shape). Supply `ota.verifySignature` yourself, or pin a @noble/ed25519 version this SDK supports.");
11739
+ resolved = null;
11740
+ return null;
11741
+ }
11742
+ resolved = async ({
11743
+ messageHex,
11744
+ signature,
11745
+ publicKey
11746
+ }) => {
11747
+ const message = hexToBytes(messageHex);
11748
+ const pub = hexToBytes(publicKey);
11749
+ const sig = decodeSignature(signature);
11750
+ if (!message) {
11751
+ logger.error("[OTA] Bundle SHA-256 is not valid hex \u2014 refusing to stage.");
11752
+ return false;
11753
+ }
11754
+ if (!pub || pub.length !== ED25519_PUBLIC_KEY_BYTES) {
11755
+ logger.error(`[OTA] ota.publicSigningKey must be ${ED25519_PUBLIC_KEY_BYTES} bytes of hex (${ED25519_PUBLIC_KEY_BYTES * 2} characters) \u2014 got ${pub ? `${pub.length} bytes` : "a value that is not hex"}.`);
11756
+ return false;
11757
+ }
11758
+ if (!sig) {
11759
+ logger.error("[OTA] Bundle signature is not a 64-byte hex/base64 value.");
11760
+ return false;
11761
+ }
11762
+ try {
11763
+ if (typeof ed.verify === "function") return ed.verify(sig, message, pub);
11764
+ if (typeof ed.verifyAsync === "function") return await ed.verifyAsync(sig, message, pub);
11765
+ return false;
11766
+ } catch {
11767
+ return false;
11768
+ }
11769
+ };
11770
+ __DEV__ && logger.debug("[OTA] Using built-in ed25519 verifier (@noble/ed25519 detected).");
11771
+ return resolved;
11772
+ }
11773
+ function _resetBuiltinVerifier() {
11774
+ resolved = void 0;
11775
+ }
11776
+ var ED25519_SIGNATURE_BYTES, ED25519_PUBLIC_KEY_BYTES, B64_ALPHABET2, resolved;
11777
+ var init_builtinVerifier = __esm({
11778
+ "lib/module/features/ota/crypto/builtinVerifier.js"() {
11779
+ "use strict";
11780
+ init_loadEd25519();
11781
+ init_loadSha512();
11782
+ init_internalLogger();
11783
+ ED25519_SIGNATURE_BYTES = 64;
11784
+ ED25519_PUBLIC_KEY_BYTES = 32;
11785
+ B64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
11786
+ }
11787
+ });
11788
+
11789
+ // lib/module/features/ota/crypto/nativeVerifier.js
11790
+ function bytesToHex(bytes) {
11791
+ let out = "";
11792
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
11793
+ return out;
11794
+ }
11795
+ function normalizeHex(value, expectedChars) {
11796
+ const clean2 = value.trim().toLowerCase().replace(/^0x/, "");
11797
+ if (clean2.length !== expectedChars || /[^0-9a-f]/.test(clean2)) return null;
11798
+ return clean2;
11799
+ }
11800
+ function getNativeVerifier() {
11801
+ if (resolved2 !== void 0) return resolved2;
11802
+ const native = NativeScaleBunOta_default;
11803
+ if (!native || typeof native.verifyEd25519 !== "function") {
11804
+ resolved2 = null;
11805
+ return null;
11806
+ }
11807
+ resolved2 = async ({
11808
+ messageHex,
11809
+ signature,
11810
+ publicKey
11811
+ }) => {
11812
+ const message = normalizeHex(messageHex, 64);
11813
+ const key = normalizeHex(publicKey, ED25519_PUBLIC_KEY_HEX_CHARS);
11814
+ const sigBytes = decodeSignature(signature);
11815
+ if (!message || !key || !sigBytes) {
11816
+ logger.error("[OTA] Signature inputs malformed (bundle hash, signature, or public key) \u2014 refusing to stage.");
11817
+ return false;
11818
+ }
11819
+ let verdict;
11820
+ try {
11821
+ verdict = await native.verifyEd25519(message, bytesToHex(sigBytes), key);
11822
+ } catch (err) {
11823
+ logger.warn(`[OTA] Native ed25519 verify failed to run: ${err?.message ?? err}`);
11824
+ verdict = "unavailable";
11825
+ }
11826
+ if (verdict === "valid") return true;
11827
+ if (verdict === "invalid") return false;
11828
+ const builtin = getBuiltinVerifier();
11829
+ if (builtin) {
11830
+ return builtin({
11831
+ messageHex,
11832
+ signature,
11833
+ publicKey
11834
+ });
11835
+ }
11836
+ throw new Error("ed25519 unavailable: this OS has no native implementation (Android < 13) and the optional peers @noble/ed25519 + @noble/hashes are not installed.");
11837
+ };
11838
+ __DEV__ && logger.debug("[OTA] Using native ed25519 verifier (platform crypto).");
11839
+ return resolved2;
11840
+ }
11841
+ var ED25519_PUBLIC_KEY_HEX_CHARS, resolved2;
11842
+ var init_nativeVerifier = __esm({
11843
+ "lib/module/features/ota/crypto/nativeVerifier.js"() {
11844
+ "use strict";
11845
+ init_NativeScaleBunOta();
11846
+ init_internalLogger();
11847
+ init_builtinVerifier();
11848
+ ED25519_PUBLIC_KEY_HEX_CHARS = 64;
11849
+ }
11850
+ });
11851
+
11614
11852
  // lib/module/features/ota/signature.js
11615
11853
  async function verifyBundleSignature(bundleSha256, signature, config) {
11616
- const publicKey = config?.publicKey;
11617
- if (!publicKey) {
11854
+ const rawKey = config?.publicKey;
11855
+ const keys = (Array.isArray(rawKey) ? rawKey : rawKey ? [rawKey] : []).filter((k) => typeof k === "string" && k.trim().length > 0);
11856
+ if (rawKey === void 0 || rawKey === "" || rawKey === null) {
11618
11857
  if (!warnedNotConfigured) {
11619
11858
  warnedNotConfigured = true;
11620
11859
  logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
@@ -11624,6 +11863,13 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
11624
11863
  reason: "not_configured"
11625
11864
  };
11626
11865
  }
11866
+ if (keys.length === 0) {
11867
+ logger.error("[OTA] Signing key config resolves to zero usable keys (empty array or blank strings) \u2014 refusing to stage. Remove the config to disable signing, or pin at least one real key.");
11868
+ return {
11869
+ ok: false,
11870
+ reason: "no_keys"
11871
+ };
11872
+ }
11627
11873
  if (!signature) {
11628
11874
  logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
11629
11875
  return {
@@ -11631,43 +11877,52 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
11631
11877
  reason: "missing_signature"
11632
11878
  };
11633
11879
  }
11634
- if (typeof config?.verifier !== "function") {
11635
- 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.");
11880
+ const verifier = typeof config?.verifier === "function" ? config.verifier : getNativeVerifier() ?? getBuiltinVerifier();
11881
+ if (!verifier) {
11882
+ logger.error("[OTA] A signing key is configured but no signature verifier is available \u2014 refusing to stage. Either install the optional peers `@noble/ed25519` and `@noble/hashes` (the SDK then verifies with no further code), or provide your own `ota.verifySignature`.");
11636
11883
  return {
11637
11884
  ok: false,
11638
11885
  reason: "no_verifier"
11639
11886
  };
11640
11887
  }
11641
- try {
11642
- const valid = await config.verifier({
11643
- messageHex: bundleSha256,
11644
- signature,
11645
- publicKey
11646
- });
11647
- if (!valid) {
11648
- logger.error("[OTA] Bundle signature is INVALID \u2014 refusing to stage.");
11649
- return {
11650
- ok: false,
11651
- reason: "invalid_signature"
11888
+ let sawThrow = false;
11889
+ let sawCompletion = false;
11890
+ for (const key of keys) {
11891
+ try {
11892
+ const valid = await verifier({
11893
+ messageHex: bundleSha256,
11894
+ signature,
11895
+ publicKey: key
11896
+ });
11897
+ sawCompletion = true;
11898
+ if (valid) return {
11899
+ ok: true,
11900
+ reason: "verified"
11652
11901
  };
11902
+ } catch (err) {
11903
+ sawThrow = true;
11904
+ logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
11653
11905
  }
11654
- return {
11655
- ok: true,
11656
- reason: "verified"
11657
- };
11658
- } catch (err) {
11659
- logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
11906
+ }
11907
+ if (sawThrow && !sawCompletion) {
11660
11908
  return {
11661
11909
  ok: false,
11662
11910
  reason: "verifier_threw"
11663
11911
  };
11664
11912
  }
11913
+ logger.error(keys.length > 1 ? `[OTA] Bundle signature is INVALID under all ${keys.length} pinned keys \u2014 refusing to stage.` : "[OTA] Bundle signature is INVALID \u2014 refusing to stage.");
11914
+ return {
11915
+ ok: false,
11916
+ reason: "invalid_signature"
11917
+ };
11665
11918
  }
11666
11919
  var warnedNotConfigured;
11667
11920
  var init_signature = __esm({
11668
11921
  "lib/module/features/ota/signature.js"() {
11669
11922
  "use strict";
11670
11923
  init_internalLogger();
11924
+ init_builtinVerifier();
11925
+ init_nativeVerifier();
11671
11926
  warnedNotConfigured = false;
11672
11927
  }
11673
11928
  });
@@ -12487,6 +12742,35 @@ var init_OtaOrchestrator = __esm({
12487
12742
  }
12488
12743
  });
12489
12744
 
12745
+ // lib/module/features/journey/uiState.js
12746
+ var uiState_exports = {};
12747
+ __export(uiState_exports, {
12748
+ clearUiState: () => clearUiState,
12749
+ setUiState: () => setUiState,
12750
+ uiStateSignature: () => uiStateSignature
12751
+ });
12752
+ function setUiState(name, value) {
12753
+ const n = clean(name);
12754
+ const v = clean(value);
12755
+ if (n && v) declared.set(n, v);
12756
+ }
12757
+ function clearUiState(name) {
12758
+ if (name === void 0) declared.clear();
12759
+ else declared.delete(clean(name));
12760
+ }
12761
+ function uiStateSignature() {
12762
+ if (!declared.size) return void 0;
12763
+ return [...declared.keys()].sort().map((k) => `${k}:${declared.get(k)}`).join(";").slice(0, 96);
12764
+ }
12765
+ var declared, clean;
12766
+ var init_uiState = __esm({
12767
+ "lib/module/features/journey/uiState.js"() {
12768
+ "use strict";
12769
+ declared = /* @__PURE__ */ new Map();
12770
+ clean = (s) => typeof s === "string" ? s.replace(/[;:|]/g, "").trim().slice(0, 32) : "";
12771
+ }
12772
+ });
12773
+
12490
12774
  // lib/module/features/engage/gestureTriggerDetector.js
12491
12775
  var gestureTriggerDetector_exports = {};
12492
12776
  __export(gestureTriggerDetector_exports, {
@@ -14404,6 +14688,14 @@ var SHAPE = {
14404
14688
  kind: "str",
14405
14689
  opt: true
14406
14690
  },
14691
+ // Rotation support: several pinned keys, any of which may verify a
14692
+ // bundle. Ship a build pinning [old, new], re-sign server-side with
14693
+ // new, drop old next release — no emergency store submission when a
14694
+ // key must be replaced. Merged with publicSigningKey by the facade.
14695
+ publicSigningKeys: {
14696
+ kind: "strArr",
14697
+ opt: true
14698
+ },
14407
14699
  mandatoryBlocksUi: bool(false)
14408
14700
  }
14409
14701
  }
@@ -14439,7 +14731,7 @@ function parseField(field, input, path, issues) {
14439
14731
  value: field.def,
14440
14732
  present: true
14441
14733
  };
14442
- return fail(field.kind === "bool" ? "Expected boolean, received undefined" : field.kind === "num" ? "Expected number, received undefined" : field.kind === "str" ? "Expected string, received undefined" : field.kind === "enum" ? "Invalid enum value. Expected " + field.values.map((v) => `'${v}'`).join(" | ") : "Expected object, received undefined");
14734
+ return fail(field.kind === "bool" ? "Expected boolean, received undefined" : field.kind === "num" ? "Expected number, received undefined" : field.kind === "str" ? "Expected string, received undefined" : field.kind === "strArr" ? "Expected array, received undefined" : field.kind === "enum" ? "Invalid enum value. Expected " + field.values.map((v) => `'${v}'`).join(" | ") : "Expected object, received undefined");
14443
14735
  }
14444
14736
  switch (field.kind) {
14445
14737
  case "any":
@@ -14465,6 +14757,27 @@ function parseField(field, input, path, issues) {
14465
14757
  value: input,
14466
14758
  present: true
14467
14759
  };
14760
+ case "strArr": {
14761
+ if (!Array.isArray(input)) return fail(`Expected array, received ${typeofName2(input)}`);
14762
+ let allOk = true;
14763
+ for (let i = 0; i < input.length; i++) {
14764
+ if (typeof input[i] !== "string") {
14765
+ issues.push({
14766
+ path: [...path, i],
14767
+ message: `Expected string, received ${typeofName2(input[i])}`
14768
+ });
14769
+ allOk = false;
14770
+ }
14771
+ }
14772
+ return allOk ? {
14773
+ ok: true,
14774
+ value: [...input],
14775
+ present: true
14776
+ } : {
14777
+ ok: false,
14778
+ present: true
14779
+ };
14780
+ }
14468
14781
  case "num":
14469
14782
  if (typeof input !== "number" || Number.isNaN(input)) {
14470
14783
  return fail(`Expected number, received ${typeofName2(input)}`);
@@ -15668,16 +15981,16 @@ function base64ToBytes(base64) {
15668
15981
  return decodeManual(base64);
15669
15982
  }
15670
15983
  function decodeManual(base64) {
15671
- const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
15672
- const pad = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
15673
- const outLen = Math.floor(clean.length * 3 / 4) - pad;
15984
+ const clean2 = base64.replace(/[^A-Za-z0-9+/=]/g, "");
15985
+ const pad = clean2.endsWith("==") ? 2 : clean2.endsWith("=") ? 1 : 0;
15986
+ const outLen = Math.floor(clean2.length * 3 / 4) - pad;
15674
15987
  const out = new Uint8Array(outLen);
15675
15988
  let p = 0;
15676
- for (let i = 0; i < clean.length; i += 4) {
15677
- const c0 = B64_ALPHABET.indexOf(clean[i]);
15678
- const c1 = B64_ALPHABET.indexOf(clean[i + 1]);
15679
- const c2 = B64_ALPHABET.indexOf(clean[i + 2]);
15680
- const c3 = B64_ALPHABET.indexOf(clean[i + 3]);
15989
+ for (let i = 0; i < clean2.length; i += 4) {
15990
+ const c0 = B64_ALPHABET.indexOf(clean2[i]);
15991
+ const c1 = B64_ALPHABET.indexOf(clean2[i + 1]);
15992
+ const c2 = B64_ALPHABET.indexOf(clean2[i + 2]);
15993
+ const c3 = B64_ALPHABET.indexOf(clean2[i + 3]);
15681
15994
  const n = c0 << 18 | c1 << 12 | (c2 & 63) << 6 | c3 & 63;
15682
15995
  if (p < outLen) out[p++] = n >> 16 & 255;
15683
15996
  if (p < outLen) out[p++] = n >> 8 & 255;
@@ -19865,12 +20178,17 @@ var ScaleBunFacade = class {
19865
20178
  /**
19866
20179
  * Boot the OTA orchestrator when the init config asks for it.
19867
20180
  *
19868
- * `publicSigningKey` is honoured here so signature enforcement is reachable
19869
- * from configuration alone. The SDK ships no ed25519 implementation, so a
19870
- * host that pins a key must also supply `ota.verifySignature`; pinning a key
19871
- * without a verifier is fail-CLOSED by design (signature.ts) an update
19872
- * that cannot be verified is not installed. We say that out loud rather than
19873
- * letting the app discover it as a silent no-update condition.
20181
+ * `publicSigningKey` / `publicSigningKeys` are honoured here so signature
20182
+ * enforcement is reachable from configuration alone. The list form exists
20183
+ * for key ROTATION: a build pinning [old, new] keeps verifying while the
20184
+ * server moves to the new key, so replacing a key never needs an emergency
20185
+ * store release. Both fields merge (deduplicated) into one pinned set.
20186
+ *
20187
+ * Verification comes from `ota.verifySignature` when supplied, else the
20188
+ * built-in @noble-based verifier (optional peers). Pinning keys with
20189
+ * NEITHER available is fail-CLOSED by design (signature.ts) — an update
20190
+ * that cannot be verified is not installed. We say that out loud rather
20191
+ * than letting the app discover it as a silent no-update condition.
19874
20192
  */
19875
20193
  _maybeStartOta(rawConfig) {
19876
20194
  const ota = rawConfig?.ota;
@@ -19879,15 +20197,25 @@ var ScaleBunFacade = class {
19879
20197
  const {
19880
20198
  otaOrchestrator: otaOrchestrator2
19881
20199
  } = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
19882
- const publicKey = ota.publicSigningKey;
20200
+ const rawSingle = ota.publicSigningKey;
20201
+ const rawList = ota.publicSigningKeys;
20202
+ const publicKeys = Array.from(new Set([...typeof rawSingle === "string" && rawSingle ? [rawSingle] : [], ...Array.isArray(rawList) ? rawList : []].filter((k) => typeof k === "string" && k.trim().length > 0)));
20203
+ const signingRequested = publicKeys.length > 0 || Array.isArray(rawList);
19883
20204
  const verifier = ota.verifySignature;
19884
- if (publicKey && typeof verifier !== "function") {
19885
- 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.");
20205
+ if (signingRequested && typeof verifier !== "function") {
20206
+ const {
20207
+ getBuiltinVerifier: getBuiltinVerifier2
20208
+ } = (init_builtinVerifier(), __toCommonJS(builtinVerifier_exports));
20209
+ if (!getBuiltinVerifier2()) {
20210
+ logger.warn("[ScaleBun] A signing key is pinned but no signature verifier is available. Signature checking is fail-closed: updates will be REJECTED until one exists. Install the optional peers `@noble/ed25519` + `@noble/hashes` (no further code needed), or supply `ota.verifySignature`.");
20211
+ }
19886
20212
  }
19887
20213
  otaOrchestrator2.init({
19888
- ...publicKey ? {
20214
+ ...signingRequested ? {
19889
20215
  signature: {
19890
- publicKey,
20216
+ // Single key stays a plain string — the shape every
20217
+ // existing consumer and test already handles.
20218
+ publicKey: publicKeys.length === 1 ? publicKeys[0] : publicKeys,
19891
20219
  verifier
19892
20220
  }
19893
20221
  } : {},
@@ -20099,6 +20427,46 @@ var ScaleBunFacade = class {
20099
20427
  };
20100
20428
  }
20101
20429
  /** Convenience: emit a Phase 1 purchase event (revenue + currency + transaction_id). */
20430
+ /**
20431
+ * Declare which UI state the user is looking at, so taps are attributed to the surface they
20432
+ * happened on rather than averaged across every variant of the screen.
20433
+ *
20434
+ * ScaleBun.setUiState('filter-sheet', 'open');
20435
+ * ScaleBun.setUiState('checkout-step', 'payment');
20436
+ *
20437
+ * Call it when the state CHANGES — the value is read at tap time, so it only has to be right by
20438
+ * the time the next tap lands. On React Native this is the ONLY source of UI state: unlike the web,
20439
+ * there is no queryable accessibility tree that says a sheet is up, and guessing would produce a
20440
+ * state key that is right on some apps and silently wrong on others.
20441
+ *
20442
+ * Names and values are identifiers, not content: they become query keys in the dashboard, so a
20443
+ * person's name or a cart total does not belong in one. `;`, `:` and `|` are stripped and both
20444
+ * halves are capped at 32 characters.
20445
+ */
20446
+ setUiState(name, value) {
20447
+ try {
20448
+ const {
20449
+ setUiState: setUiState2
20450
+ } = (init_uiState(), __toCommonJS(uiState_exports));
20451
+ setUiState2(name, value);
20452
+ } catch {
20453
+ }
20454
+ }
20455
+ /**
20456
+ * Stop reporting a UI state dimension — or, with no argument, all of them.
20457
+ *
20458
+ * Clear on screen unmount. A declaration left behind follows the user onto a surface where it means
20459
+ * nothing, and every tap there is filed under a state that was not on screen.
20460
+ */
20461
+ clearUiState(name) {
20462
+ try {
20463
+ const {
20464
+ clearUiState: clearUiState2
20465
+ } = (init_uiState(), __toCommonJS(uiState_exports));
20466
+ clearUiState2(name);
20467
+ } catch {
20468
+ }
20469
+ }
20102
20470
  trackPurchase(input) {
20103
20471
  noThrow(() => this._eventTracker?.trackPurchase(input));
20104
20472
  }
@@ -23722,11 +24090,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
23722
24090
  function resolveWheelOutcome(segments, source) {
23723
24091
  const identity = wheelOutcomeIdentity(source);
23724
24092
  const index = findWheelOutcomeIndex(segments, identity);
23725
- const resolved = index >= 0;
24093
+ const resolved3 = index >= 0;
23726
24094
  return {
23727
- index: resolved ? index : 0,
23728
- label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
23729
- resolved
24095
+ index: resolved3 ? index : 0,
24096
+ label: identity.resultLabel || (resolved3 ? segments[index]?.fullLabel ?? "" : ""),
24097
+ resolved: resolved3
23730
24098
  };
23731
24099
  }
23732
24100
  function SpinWheelInApp(props) {
@@ -32585,8 +32953,8 @@ function EngagePromptProvider({
32585
32953
  __DEV__ && logger.debug("[Engage] no eligible in-app message to render");
32586
32954
  return false;
32587
32955
  }
32588
- const resolved = selectInAppVariant(message, getDeviceId());
32589
- activeInAppVariantRef.current = message.experiment?.enabled ? resolved : null;
32956
+ const resolved3 = selectInAppVariant(message, getDeviceId());
32957
+ activeInAppVariantRef.current = message.experiment?.enabled ? resolved3 : null;
32590
32958
  store.recordImpression(message.id, Date.now(), message.revision);
32591
32959
  setActiveInApp(message);
32592
32960
  return true;
@@ -33294,9 +33662,22 @@ function ScaleBunDebugRoot({
33294
33662
  screen = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
33295
33663
  } catch {
33296
33664
  }
33665
+ let ui;
33666
+ try {
33667
+ const {
33668
+ uiStateSignature: uiStateSignature2
33669
+ } = (init_uiState(), __toCommonJS(uiState_exports));
33670
+ ui = uiStateSignature2();
33671
+ } catch {
33672
+ }
33297
33673
  emitAutomaticEvent("element_interacted", {
33298
33674
  gesture_type: gestureType,
33299
33675
  screen_name: screen,
33676
+ /* THIS MAP ENUMERATES. A field added to the payload and forgotten here reaches the
33677
+ backend as undefined with no error anywhere — the recurring defect class in this
33678
+ codebase. `ui` is the key the grid aggregate reads for state, byte-identical to
33679
+ the web SDK's, so one dashboard control queries both platforms. */
33680
+ ui,
33300
33681
  // testID/nativeID is an author-controlled stable identifier.
33301
33682
  // Accessibility labels and rendered text are deliberately omitted.
33302
33683
  target_id: targetInfo?.testId,