@scalebun/react-native 1.11.1 → 2.0.0

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 (68) hide show
  1. package/android/build.gradle +8 -0
  2. package/android/src/androidTest/java/com/scalebun/rn/ota/ScaleBunOtaVerifierInstrumentedTest.kt +112 -0
  3. package/android/src/main/java/com/scalebun/rn/ota/OtaProtocol.kt +244 -0
  4. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaKeyRegistry.kt +100 -0
  5. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +115 -60
  6. package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaReleaseVerifier.kt +129 -0
  7. package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +18 -0
  8. package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +4 -8
  9. package/dist/scalebun.full.js +127 -422
  10. package/dist/scalebun.slim.js +127 -422
  11. package/ios/Ota/OtaProtocol.swift +242 -0
  12. package/ios/Ota/OtaSlotManager.swift +26 -0
  13. package/ios/Ota/ScaleBunOtaBridge.mm +6 -3
  14. package/ios/Ota/ScaleBunOtaKeyRegistry.swift +147 -0
  15. package/ios/Ota/ScaleBunOtaModule.swift +125 -53
  16. package/ios/Ota/ScaleBunOtaReleaseVerifier.swift +115 -0
  17. package/ios/Ota/ScaleBunOtaVerifierTests.swift +92 -0
  18. package/lib/commonjs/core/config/schema.js +3 -12
  19. package/lib/commonjs/core/constants/version.js +1 -1
  20. package/lib/commonjs/features/network/NetworkFeature.js +39 -5
  21. package/lib/commonjs/features/network/thirdParty.js +90 -0
  22. package/lib/commonjs/features/ota/OtaOrchestrator.js +108 -60
  23. package/lib/commonjs/public/ScaleBunFacade.js +5 -41
  24. package/lib/module/core/config/schema.js +3 -12
  25. package/lib/module/core/constants/version.js +1 -1
  26. package/lib/module/features/network/NetworkFeature.js +38 -4
  27. package/lib/module/features/network/thirdParty.js +83 -0
  28. package/lib/module/features/ota/OtaOrchestrator.js +108 -60
  29. package/lib/module/public/ScaleBunFacade.js +5 -41
  30. package/lib/typescript/core/config/schema.d.ts +0 -3
  31. package/lib/typescript/core/constants/version.d.ts +1 -1
  32. package/lib/typescript/features/network/index.d.ts +18 -0
  33. package/lib/typescript/features/network/thirdParty.d.ts +65 -0
  34. package/lib/typescript/features/ota/OtaOrchestrator.d.ts +5 -8
  35. package/lib/typescript/features/ota/OtaTypes.d.ts +22 -0
  36. package/lib/typescript/public/ScaleBunFacade.d.ts +5 -11
  37. package/lib/typescript/specs/NativeScaleBunOta.d.ts +30 -20
  38. package/package.json +3 -13
  39. package/scalebun-react-native.podspec +4 -0
  40. package/src/core/config/schema.ts +3 -9
  41. package/src/core/constants/version.ts +1 -1
  42. package/src/features/network/NetworkFeature.ts +41 -4
  43. package/src/features/network/index.ts +18 -0
  44. package/src/features/network/thirdParty.ts +92 -0
  45. package/src/features/ota/OtaOrchestrator.ts +116 -70
  46. package/src/features/ota/OtaTypes.ts +23 -1
  47. package/src/public/ScaleBunFacade.ts +5 -53
  48. package/src/specs/NativeScaleBunOta.ts +32 -20
  49. package/lib/commonjs/features/ota/crypto/builtinVerifier.js +0 -248
  50. package/lib/commonjs/features/ota/crypto/loadEd25519.js +0 -40
  51. package/lib/commonjs/features/ota/crypto/loadSha512.js +0 -40
  52. package/lib/commonjs/features/ota/crypto/nativeVerifier.js +0 -121
  53. package/lib/commonjs/features/ota/signature.js +0 -175
  54. package/lib/module/features/ota/crypto/builtinVerifier.js +0 -240
  55. package/lib/module/features/ota/crypto/loadEd25519.js +0 -34
  56. package/lib/module/features/ota/crypto/loadSha512.js +0 -34
  57. package/lib/module/features/ota/crypto/nativeVerifier.js +0 -113
  58. package/lib/module/features/ota/signature.js +0 -169
  59. package/lib/typescript/features/ota/crypto/builtinVerifier.d.ts +0 -53
  60. package/lib/typescript/features/ota/crypto/loadEd25519.d.ts +0 -30
  61. package/lib/typescript/features/ota/crypto/loadSha512.d.ts +0 -15
  62. package/lib/typescript/features/ota/crypto/nativeVerifier.d.ts +0 -35
  63. package/lib/typescript/features/ota/signature.d.ts +0 -81
  64. package/src/features/ota/crypto/builtinVerifier.ts +0 -257
  65. package/src/features/ota/crypto/loadEd25519.ts +0 -41
  66. package/src/features/ota/crypto/loadSha512.ts +0 -35
  67. package/src/features/ota/crypto/nativeVerifier.ts +0 -117
  68. package/src/features/ota/signature.ts +0 -206
@@ -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 resolved3 = LEVEL_BY_NAME[name];
93
- if (resolved3 !== void 0) {
94
- this.level = resolved3;
92
+ const resolved = LEVEL_BY_NAME[name];
93
+ if (resolved !== void 0) {
94
+ this.level = resolved;
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.11.1";
608
+ SDK_VERSION = "2.0.0";
609
609
  }
610
610
  });
611
611
 
@@ -11697,322 +11697,6 @@ var init_deviceAttributes = __esm({
11697
11697
  }
11698
11698
  });
11699
11699
 
11700
- // lib/module/features/ota/crypto/loadEd25519.js
11701
- function loadEd25519() {
11702
- try {
11703
- const mod = require("@noble/ed25519");
11704
- const ed = mod?.default ?? mod;
11705
- if (typeof ed?.verifyAsync === "function" || typeof ed?.verify === "function") {
11706
- return ed;
11707
- }
11708
- return null;
11709
- } catch {
11710
- return null;
11711
- }
11712
- }
11713
- var init_loadEd25519 = __esm({
11714
- "lib/module/features/ota/crypto/loadEd25519.js"() {
11715
- "use strict";
11716
- }
11717
- });
11718
-
11719
- // lib/module/features/ota/crypto/loadSha512.js
11720
- function loadSha512() {
11721
- try {
11722
- const mod = require("@noble/hashes/sha2.js");
11723
- const sha512 = mod?.sha512 ?? mod?.default?.sha512;
11724
- return typeof sha512 === "function" ? sha512 : null;
11725
- } catch {
11726
- return null;
11727
- }
11728
- }
11729
- var init_loadSha512 = __esm({
11730
- "lib/module/features/ota/crypto/loadSha512.js"() {
11731
- "use strict";
11732
- }
11733
- });
11734
-
11735
- // lib/module/features/ota/crypto/builtinVerifier.js
11736
- var builtinVerifier_exports = {};
11737
- __export(builtinVerifier_exports, {
11738
- _resetBuiltinVerifier: () => _resetBuiltinVerifier,
11739
- decodeSignature: () => decodeSignature,
11740
- getBuiltinVerifier: () => getBuiltinVerifier
11741
- });
11742
- function hexToBytes(hex) {
11743
- const clean2 = hex.trim().toLowerCase().replace(/^0x/, "");
11744
- if (clean2.length === 0 || clean2.length % 2 !== 0 || /[^0-9a-f]/.test(clean2)) return null;
11745
- const out = new Uint8Array(clean2.length / 2);
11746
- for (let i = 0; i < out.length; i++) {
11747
- out[i] = parseInt(clean2.slice(i * 2, i * 2 + 2), 16);
11748
- }
11749
- return out;
11750
- }
11751
- function base64ToBytes2(b64) {
11752
- const s = b64.trim().replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
11753
- if (s.length === 0) return null;
11754
- const out = [];
11755
- let buffer = 0;
11756
- let bits = 0;
11757
- for (const ch of s) {
11758
- const idx = B64_ALPHABET2.indexOf(ch);
11759
- if (idx === -1) return null;
11760
- buffer = buffer << 6 | idx;
11761
- bits += 6;
11762
- if (bits >= 8) {
11763
- bits -= 8;
11764
- out.push(buffer >> bits & 255);
11765
- }
11766
- }
11767
- return Uint8Array.from(out);
11768
- }
11769
- function decodeSignature(sig) {
11770
- const trimmed = sig.trim();
11771
- if (/^(0x)?[0-9a-fA-F]{128}$/.test(trimmed)) {
11772
- const hex2 = hexToBytes(trimmed);
11773
- if (hex2 && hex2.length === ED25519_SIGNATURE_BYTES) return hex2;
11774
- }
11775
- const b64 = base64ToBytes2(trimmed);
11776
- if (b64 && b64.length === ED25519_SIGNATURE_BYTES) return b64;
11777
- const hex = hexToBytes(trimmed);
11778
- if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
11779
- return null;
11780
- }
11781
- function wireSha512(ed, sha512) {
11782
- const concat2 = (parts) => {
11783
- if (parts.length === 1) return parts[0];
11784
- let total = 0;
11785
- for (const p of parts) total += p.length;
11786
- const out = new Uint8Array(total);
11787
- let off = 0;
11788
- for (const p of parts) {
11789
- out.set(p, off);
11790
- off += p.length;
11791
- }
11792
- return out;
11793
- };
11794
- const sync = (...msgs) => sha512(concat2(msgs));
11795
- const asyncFn = async (...msgs) => sha512(concat2(msgs));
11796
- let wired = false;
11797
- try {
11798
- if (ed.hashes && typeof ed.hashes === "object") {
11799
- if (typeof ed.hashes.sha512 !== "function") ed.hashes.sha512 = sync;
11800
- if (typeof ed.hashes.sha512Async !== "function") ed.hashes.sha512Async = asyncFn;
11801
- wired = typeof ed.hashes.sha512 === "function";
11802
- }
11803
- } catch {
11804
- }
11805
- try {
11806
- if (ed.etc && typeof ed.etc === "object") {
11807
- if (typeof ed.etc.sha512Sync !== "function") ed.etc.sha512Sync = sync;
11808
- if (typeof ed.etc.sha512Async !== "function") ed.etc.sha512Async = asyncFn;
11809
- wired = wired || typeof ed.etc.sha512Sync === "function";
11810
- }
11811
- } catch {
11812
- }
11813
- return wired;
11814
- }
11815
- function getBuiltinVerifier() {
11816
- if (resolved !== void 0) return resolved;
11817
- const ed = loadEd25519();
11818
- const sha512 = loadSha512();
11819
- if (!ed || !sha512) {
11820
- resolved = null;
11821
- return null;
11822
- }
11823
- if (!wireSha512(ed, sha512)) {
11824
- 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.");
11825
- resolved = null;
11826
- return null;
11827
- }
11828
- resolved = async ({
11829
- messageHex,
11830
- signature,
11831
- publicKey
11832
- }) => {
11833
- const message = hexToBytes(messageHex);
11834
- const pub = hexToBytes(publicKey);
11835
- const sig = decodeSignature(signature);
11836
- if (!message) {
11837
- logger.error("[OTA] Bundle SHA-256 is not valid hex \u2014 refusing to stage.");
11838
- return false;
11839
- }
11840
- if (!pub || pub.length !== ED25519_PUBLIC_KEY_BYTES) {
11841
- 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"}.`);
11842
- return false;
11843
- }
11844
- if (!sig) {
11845
- logger.error("[OTA] Bundle signature is not a 64-byte hex/base64 value.");
11846
- return false;
11847
- }
11848
- try {
11849
- if (typeof ed.verify === "function") return ed.verify(sig, message, pub);
11850
- if (typeof ed.verifyAsync === "function") return await ed.verifyAsync(sig, message, pub);
11851
- return false;
11852
- } catch {
11853
- return false;
11854
- }
11855
- };
11856
- __DEV__ && logger.debug("[OTA] Using built-in ed25519 verifier (@noble/ed25519 detected).");
11857
- return resolved;
11858
- }
11859
- function _resetBuiltinVerifier() {
11860
- resolved = void 0;
11861
- }
11862
- var ED25519_SIGNATURE_BYTES, ED25519_PUBLIC_KEY_BYTES, B64_ALPHABET2, resolved;
11863
- var init_builtinVerifier = __esm({
11864
- "lib/module/features/ota/crypto/builtinVerifier.js"() {
11865
- "use strict";
11866
- init_loadEd25519();
11867
- init_loadSha512();
11868
- init_internalLogger();
11869
- ED25519_SIGNATURE_BYTES = 64;
11870
- ED25519_PUBLIC_KEY_BYTES = 32;
11871
- B64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
11872
- }
11873
- });
11874
-
11875
- // lib/module/features/ota/crypto/nativeVerifier.js
11876
- function bytesToHex(bytes) {
11877
- let out = "";
11878
- for (const b of bytes) out += b.toString(16).padStart(2, "0");
11879
- return out;
11880
- }
11881
- function normalizeHex(value, expectedChars) {
11882
- const clean2 = value.trim().toLowerCase().replace(/^0x/, "");
11883
- if (clean2.length !== expectedChars || /[^0-9a-f]/.test(clean2)) return null;
11884
- return clean2;
11885
- }
11886
- function getNativeVerifier() {
11887
- if (resolved2 !== void 0) return resolved2;
11888
- const native = NativeScaleBunOta_default;
11889
- if (!native || typeof native.verifyEd25519 !== "function") {
11890
- resolved2 = null;
11891
- return null;
11892
- }
11893
- resolved2 = async ({
11894
- messageHex,
11895
- signature,
11896
- publicKey
11897
- }) => {
11898
- const message = normalizeHex(messageHex, 64);
11899
- const key = normalizeHex(publicKey, ED25519_PUBLIC_KEY_HEX_CHARS);
11900
- const sigBytes = decodeSignature(signature);
11901
- if (!message || !key || !sigBytes) {
11902
- logger.error("[OTA] Signature inputs malformed (bundle hash, signature, or public key) \u2014 refusing to stage.");
11903
- return false;
11904
- }
11905
- let verdict;
11906
- try {
11907
- verdict = await native.verifyEd25519(message, bytesToHex(sigBytes), key);
11908
- } catch (err) {
11909
- logger.warn(`[OTA] Native ed25519 verify failed to run: ${err?.message ?? err}`);
11910
- verdict = "unavailable";
11911
- }
11912
- if (verdict === "valid") return true;
11913
- if (verdict === "invalid") return false;
11914
- const builtin = getBuiltinVerifier();
11915
- if (builtin) {
11916
- return builtin({
11917
- messageHex,
11918
- signature,
11919
- publicKey
11920
- });
11921
- }
11922
- throw new Error("ed25519 unavailable: this OS has no native implementation (Android < 13) and the optional peers @noble/ed25519 + @noble/hashes are not installed.");
11923
- };
11924
- __DEV__ && logger.debug("[OTA] Using native ed25519 verifier (platform crypto).");
11925
- return resolved2;
11926
- }
11927
- var ED25519_PUBLIC_KEY_HEX_CHARS, resolved2;
11928
- var init_nativeVerifier = __esm({
11929
- "lib/module/features/ota/crypto/nativeVerifier.js"() {
11930
- "use strict";
11931
- init_NativeScaleBunOta();
11932
- init_internalLogger();
11933
- init_builtinVerifier();
11934
- ED25519_PUBLIC_KEY_HEX_CHARS = 64;
11935
- }
11936
- });
11937
-
11938
- // lib/module/features/ota/signature.js
11939
- async function verifyBundleSignature(bundleSha256, signature, config) {
11940
- const rawKey = config?.publicKey;
11941
- const keys = (Array.isArray(rawKey) ? rawKey : rawKey ? [rawKey] : []).filter((k) => typeof k === "string" && k.trim().length > 0);
11942
- if (rawKey === void 0 || rawKey === "" || rawKey === null) {
11943
- if (!warnedNotConfigured) {
11944
- warnedNotConfigured = true;
11945
- logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
11946
- }
11947
- return {
11948
- ok: true,
11949
- reason: "not_configured"
11950
- };
11951
- }
11952
- if (keys.length === 0) {
11953
- 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.");
11954
- return {
11955
- ok: false,
11956
- reason: "no_keys"
11957
- };
11958
- }
11959
- if (!signature) {
11960
- logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
11961
- return {
11962
- ok: false,
11963
- reason: "missing_signature"
11964
- };
11965
- }
11966
- const verifier = typeof config?.verifier === "function" ? config.verifier : getNativeVerifier() ?? getBuiltinVerifier();
11967
- if (!verifier) {
11968
- 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`.");
11969
- return {
11970
- ok: false,
11971
- reason: "no_verifier"
11972
- };
11973
- }
11974
- let sawThrow = false;
11975
- let sawCompletion = false;
11976
- for (const key of keys) {
11977
- try {
11978
- const valid = await verifier({
11979
- messageHex: bundleSha256,
11980
- signature,
11981
- publicKey: key
11982
- });
11983
- sawCompletion = true;
11984
- if (valid) return {
11985
- ok: true,
11986
- reason: "verified"
11987
- };
11988
- } catch (err) {
11989
- sawThrow = true;
11990
- logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
11991
- }
11992
- }
11993
- if (sawThrow && !sawCompletion) {
11994
- return {
11995
- ok: false,
11996
- reason: "verifier_threw"
11997
- };
11998
- }
11999
- 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.");
12000
- return {
12001
- ok: false,
12002
- reason: "invalid_signature"
12003
- };
12004
- }
12005
- var warnedNotConfigured;
12006
- var init_signature = __esm({
12007
- "lib/module/features/ota/signature.js"() {
12008
- "use strict";
12009
- init_internalLogger();
12010
- init_builtinVerifier();
12011
- init_nativeVerifier();
12012
- warnedNotConfigured = false;
12013
- }
12014
- });
12015
-
12016
11700
  // lib/module/features/ota/environment.js
12017
11701
  function compare(a, b) {
12018
11702
  if (a.major !== b.major) return a.major - b.major;
@@ -12258,7 +11942,6 @@ var init_OtaOrchestrator = __esm({
12258
11942
  init_geoCountry();
12259
11943
  init_deviceAttributes();
12260
11944
  init_installationId();
12261
- init_signature();
12262
11945
  init_environment();
12263
11946
  init_retry();
12264
11947
  init_StorageBackend();
@@ -12286,15 +11969,15 @@ var init_OtaOrchestrator = __esm({
12286
11969
  /**
12287
11970
  * Initialize the orchestrator. Checks for boot-guard recovery state.
12288
11971
  *
12289
- * `signature` is optional: an app that has not adopted signing keeps working
12290
- * on SHA-256 integrity alone. Once a public key IS configured, a bundle that
12291
- * cannot be verified is refused rather than installed (OTA-03).
11972
+ * Signature enforcement is no longer a JS concern: signed releases are
11973
+ * verified and staged in NATIVE code against RSA keys embedded in the app
11974
+ * package (SB-OTA-RSA-SHA256-V1), and unsigned releases keep working on
11975
+ * SHA-256 integrity alone. There is nothing for the host to configure here.
12292
11976
  */
12293
11977
  init(config) {
12294
11978
  noThrow(() => {
12295
11979
  if (this.initialized) {
12296
11980
  this.bootGuardConfig = config ?? this.bootGuardConfig;
12297
- this.signatureConfig = config?.signature ?? this.signatureConfig;
12298
11981
  return;
12299
11982
  }
12300
11983
  if (!NativeScaleBunOta_default) {
@@ -12316,7 +11999,6 @@ var init_OtaOrchestrator = __esm({
12316
11999
  this.initialized = true;
12317
12000
  this.enabled = true;
12318
12001
  this.bootGuardConfig = config ?? {};
12319
- this.signatureConfig = config?.signature;
12320
12002
  __DEV__ && logger.debug(`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? "unknown"}${this.environment.bridgeless ? ", bridgeless" : ""}${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? "?"}` : ""})`);
12321
12003
  const slotState = this.readSlotState();
12322
12004
  this.hydrateCurrentBundleFromSlots(slotState);
@@ -12503,12 +12185,22 @@ var init_OtaOrchestrator = __esm({
12503
12185
  const env = this.environment ?? detectOtaEnvironment();
12504
12186
  const mobileOS = resolveMobileOS();
12505
12187
  if (!mobileOS) throw new Error("OTA is not supported on this platform");
12188
+ let trustedKeyIds;
12189
+ try {
12190
+ const raw = NativeScaleBunOta_default?.getTrustedKeyIds?.();
12191
+ if (raw) {
12192
+ const arr2 = JSON.parse(raw);
12193
+ if (Array.isArray(arr2) && arr2.length) trustedKeyIds = arr2.slice(0, 16);
12194
+ }
12195
+ } catch {
12196
+ }
12506
12197
  const payload = {
12507
12198
  appVersion: requestParams.appVersion,
12508
12199
  platform: mobileOS,
12509
12200
  installationId: requestParams.installationId,
12510
12201
  currentBundleId: this.currentBundle?.id,
12511
12202
  currentBundleHash: this.currentBundle?.sha256,
12203
+ trustedKeyIds,
12512
12204
  channelName: requestParams.channelName,
12513
12205
  // Host-supplied country wins; otherwise the cached edge-Worker lookup
12514
12206
  // fills in (see geoCountry.ts). Undefined when neither knows — the
@@ -12622,19 +12314,6 @@ var init_OtaOrchestrator = __esm({
12622
12314
  releaseId: bundle.releaseId,
12623
12315
  version: bundle.version
12624
12316
  });
12625
- const signatureOutcome = await verifyBundleSignature(bundle.sha256, bundle.signature, this.signatureConfig);
12626
- if (!signatureOutcome.ok) {
12627
- otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
12628
- releaseId: bundle.releaseId,
12629
- error: `Signature check failed: ${signatureOutcome.reason}`,
12630
- version: bundle.version
12631
- });
12632
- return {
12633
- status: "ERROR",
12634
- error: `Bundle signature check failed (${signatureOutcome.reason})`,
12635
- bundle
12636
- };
12637
- }
12638
12317
  otaEventEmitter.emitSimple("DOWNLOAD_STARTED", bundle.id, {
12639
12318
  releaseId: bundle.releaseId,
12640
12319
  version: bundle.version
@@ -12682,39 +12361,75 @@ var init_OtaOrchestrator = __esm({
12682
12361
  }
12683
12362
  });
12684
12363
  let staged = false;
12685
- if (bundle.patch && NativeScaleBunOta_default.stagePatch) {
12686
- __DEV__ && logger.debug(`[OTA] Patch available (${(bundle.patch.size / 1024).toFixed(0)} KB vs ${(bundle.size / 1024).toFixed(0)} KB full) \u2014 trying patch\u2026`);
12364
+ if (bundle.envelope) {
12365
+ const envelopeJson = JSON.stringify({
12366
+ ...bundle.envelope,
12367
+ downloadUrl: bundle.url
12368
+ });
12369
+ let verdict;
12687
12370
  try {
12688
- staged = await NativeScaleBunOta_default.stagePatch(
12689
- bundle.patch.url,
12690
- bundle.patch.sha256,
12691
- bundle.sha256
12692
- // expected full-bundle hash after patching
12693
- );
12694
- if (staged) {
12695
- patchUsed = true;
12696
- __DEV__ && logger.debug("[OTA] Patch applied successfully");
12697
- } else {
12698
- logger.warn("[OTA] Patch staging returned false \u2014 falling back to full bundle");
12699
- }
12700
- } catch (patchErr) {
12701
- logger.warn(`[OTA] Patch staging failed: ${patchErr.message} \u2014 falling back to full bundle`);
12371
+ const verdictJson = await NativeScaleBunOta_default.verifyAndStageRelease(envelopeJson);
12372
+ verdict = JSON.parse(verdictJson);
12373
+ } catch (err) {
12374
+ verdict = {
12375
+ ok: false,
12376
+ code: "verify_stage_threw",
12377
+ message: err?.message ?? String(err)
12378
+ };
12702
12379
  }
12703
- }
12704
- if (!staged) {
12705
- try {
12706
- staged = await retryWithBackoff(async () => {
12707
- const ok = await NativeScaleBunOta_default.stageBundle(bundle.url, bundle.sha256);
12708
- if (!ok) {
12709
- throw new Error("stageBundle returned false");
12710
- }
12711
- return ok;
12712
- }, {
12713
- label: `download v${bundle.version}`,
12714
- attempts: 3
12380
+ if (!verdict.ok) {
12381
+ unsubscribeProgress();
12382
+ postProgress(0, "FAILED");
12383
+ logger.error(`[OTA] Native verify/stage refused the release: ${verdict.code ?? "unknown"}${verdict.message ? ` \u2014 ${verdict.message}` : ""}`);
12384
+ otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
12385
+ releaseId: bundle.releaseId,
12386
+ // `error` is mapped onto the wire `errorCode` by deliverOtaEvents —
12387
+ // carry the stable native verdict code there.
12388
+ error: verdict.code ?? "verify_and_stage_release_failed",
12389
+ version: bundle.version
12715
12390
  });
12716
- } catch {
12717
- staged = false;
12391
+ return {
12392
+ status: "ERROR",
12393
+ error: `Release verification failed (${verdict.code ?? "unknown"})`,
12394
+ bundle
12395
+ };
12396
+ }
12397
+ staged = true;
12398
+ } else {
12399
+ if (bundle.patch && NativeScaleBunOta_default.stagePatch) {
12400
+ __DEV__ && logger.debug(`[OTA] Patch available (${(bundle.patch.size / 1024).toFixed(0)} KB vs ${(bundle.size / 1024).toFixed(0)} KB full) \u2014 trying patch\u2026`);
12401
+ try {
12402
+ staged = await NativeScaleBunOta_default.stagePatch(
12403
+ bundle.patch.url,
12404
+ bundle.patch.sha256,
12405
+ bundle.sha256
12406
+ // expected full-bundle hash after patching
12407
+ );
12408
+ if (staged) {
12409
+ patchUsed = true;
12410
+ __DEV__ && logger.debug("[OTA] Patch applied successfully");
12411
+ } else {
12412
+ logger.warn("[OTA] Patch staging returned false \u2014 falling back to full bundle");
12413
+ }
12414
+ } catch (patchErr) {
12415
+ logger.warn(`[OTA] Patch staging failed: ${patchErr.message} \u2014 falling back to full bundle`);
12416
+ }
12417
+ }
12418
+ if (!staged) {
12419
+ try {
12420
+ staged = await retryWithBackoff(async () => {
12421
+ const ok = await NativeScaleBunOta_default.stageBundle(bundle.url, bundle.sha256);
12422
+ if (!ok) {
12423
+ throw new Error("stageBundle returned false");
12424
+ }
12425
+ return ok;
12426
+ }, {
12427
+ label: `download v${bundle.version}`,
12428
+ attempts: 3
12429
+ });
12430
+ } catch {
12431
+ staged = false;
12432
+ }
12718
12433
  }
12719
12434
  }
12720
12435
  unsubscribeProgress();
@@ -14840,18 +14555,9 @@ var SHAPE = {
14840
14555
  kind: "str",
14841
14556
  opt: true
14842
14557
  },
14843
- publicSigningKey: {
14844
- kind: "str",
14845
- opt: true
14846
- },
14847
- // Rotation support: several pinned keys, any of which may verify a
14848
- // bundle. Ship a build pinning [old, new], re-sign server-side with
14849
- // new, drop old next release — no emergency store submission when a
14850
- // key must be replaced. Merged with publicSigningKey by the facade.
14851
- publicSigningKeys: {
14852
- kind: "strArr",
14853
- opt: true
14854
- },
14558
+ // No signing-key fields: signed releases are verified natively against
14559
+ // RSA keys embedded in the app package (SB-OTA-RSA-SHA256-V1). JS must
14560
+ // not supply a trusted key.
14855
14561
  mandatoryBlocksUi: bool(false)
14856
14562
  }
14857
14563
  }
@@ -15654,6 +15360,23 @@ function installNetworkMonitoring(onEvent, options = {}) {
15654
15360
  });
15655
15361
  }
15656
15362
 
15363
+ // lib/module/features/network/thirdParty.js
15364
+ var THIRD_PARTY_BEACON_HOSTS = "google-analytics.com,googletagmanager.com,analytics.google.com,doubleclick.net,googlesyndication.com,googleadservices.com,app-measurement.com,facebook.net,analytics.tiktok.com,ads-twitter.com,analytics.twitter.com,ct.pinterest.com,bat.bing.com,px.ads.linkedin.com,tr.snapchat.com,criteo.com,criteo.net,taboola.com,outbrain.com,adroll.com,adsrvr.org,quantserve.com,scorecardresearch.com,hotjar.com,hotjar.io,clarity.ms,fullstory.com,mouseflow.com,crazyegg.com,luckyorange.com,inspectlet.com,segment.io,mixpanel.com,amplitude.com,heap.io,heapanalytics.com,rudderstack.com,posthog.com,matomo.cloud,plausible.io,simpleanalytics.com,statcounter.com,appsflyer.com,adjust.com,branch.io,ipify.org,ipinfo.io,ipapi.co,ip-api.com,geolocation-db.com,cloudflareinsights.com,nr-data.net,browser-intake-datadoghq.com,sentry.io,bugsnag.com,rollbar.com";
15365
+ function isThirdPartyBeacon(url, hosts) {
15366
+ if (!url) return false;
15367
+ let host = "";
15368
+ try {
15369
+ host = new URL(url).hostname.toLowerCase();
15370
+ } catch {
15371
+ return false;
15372
+ }
15373
+ if (!host) return false;
15374
+ for (const suffix of (hosts ?? THIRD_PARTY_BEACON_HOSTS).split(",")) {
15375
+ if (host === suffix || host.endsWith("." + suffix)) return true;
15376
+ }
15377
+ return false;
15378
+ }
15379
+
15657
15380
  // lib/module/features/network/NetworkFeature.js
15658
15381
  init_networkAdapter();
15659
15382
  init_SessionManager();
@@ -15669,10 +15392,12 @@ var NetworkFeature = class {
15669
15392
  if (this.active) return;
15670
15393
  installNetworkMonitoring((data) => {
15671
15394
  const isInternal = isSdkInternalUrl(data.url, this.options.sdkBaseUrl);
15672
- if (!isInternal) {
15395
+ const isBeaconOrDenied = this.options.captureThirdParty !== true && isThirdPartyBeacon(data.url, this.options.thirdPartyHosts) || matchesDenyUrl(data.url, this.options.denyUrls);
15396
+ const isExcluded = isInternal || isBeaconOrDenied;
15397
+ if (!isExcluded) {
15673
15398
  this.trackFn("$network_request", data);
15674
15399
  }
15675
- if (data.type === "network") {
15400
+ if (data.type === "network" && !isBeaconOrDenied) {
15676
15401
  reportNetworkRequest({
15677
15402
  method: data.method,
15678
15403
  url: data.url,
@@ -15688,7 +15413,7 @@ var NetworkFeature = class {
15688
15413
  requestBody: data.requestBody,
15689
15414
  responseBody: data.responseBody
15690
15415
  });
15691
- if (!isInternal) {
15416
+ if (!isExcluded) {
15692
15417
  const sessionManager = SessionManager.getExistingInstance();
15693
15418
  if (sessionManager) {
15694
15419
  const backendTransport = sessionManager.getBackendTransport();
@@ -15719,6 +15444,13 @@ var NetworkFeature = class {
15719
15444
  return this.active;
15720
15445
  }
15721
15446
  };
15447
+ function matchesDenyUrl(url, patterns) {
15448
+ if (!patterns || !patterns.length || !url) return false;
15449
+ for (const p of patterns) {
15450
+ if (typeof p === "string" ? url.includes(p) : p.test(url)) return true;
15451
+ }
15452
+ return false;
15453
+ }
15722
15454
  function isSdkInternalUrl(url, sdkBaseUrl) {
15723
15455
  if (!url) return false;
15724
15456
  try {
@@ -20311,17 +20043,11 @@ var ScaleBunFacade = class _ScaleBunFacade {
20311
20043
  /**
20312
20044
  * Boot the OTA orchestrator when the init config asks for it.
20313
20045
  *
20314
- * `publicSigningKey` / `publicSigningKeys` are honoured here so signature
20315
- * enforcement is reachable from configuration alone. The list form exists
20316
- * for key ROTATION: a build pinning [old, new] keeps verifying while the
20317
- * server moves to the new key, so replacing a key never needs an emergency
20318
- * store release. Both fields merge (deduplicated) into one pinned set.
20319
- *
20320
- * Verification comes from `ota.verifySignature` when supplied, else the
20321
- * built-in @noble-based verifier (optional peers). Pinning keys with
20322
- * NEITHER available is fail-CLOSED by design (signature.ts) — an update
20323
- * that cannot be verified is not installed. We say that out loud rather
20324
- * than letting the app discover it as a silent no-update condition.
20046
+ * Signature enforcement is no longer configured from JS. Signed releases are
20047
+ * verified and staged in NATIVE code against RSA keys embedded in the app
20048
+ * package (SB-OTA-RSA-SHA256-V1); unsigned releases keep working on SHA-256
20049
+ * integrity. There are no signing-key or `verifySignature` options to pass
20050
+ * through the host embeds its trusted keys natively.
20325
20051
  */
20326
20052
  _maybeStartOta(rawConfig) {
20327
20053
  const ota = rawConfig?.ota;
@@ -20330,29 +20056,8 @@ var ScaleBunFacade = class _ScaleBunFacade {
20330
20056
  const {
20331
20057
  otaOrchestrator: otaOrchestrator2
20332
20058
  } = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
20333
- const rawSingle = ota.publicSigningKey;
20334
- const rawList = ota.publicSigningKeys;
20335
- const publicKeys = Array.from(new Set([...typeof rawSingle === "string" && rawSingle ? [rawSingle] : [], ...Array.isArray(rawList) ? rawList : []].filter((k) => typeof k === "string" && k.trim().length > 0)));
20336
- const signingRequested = publicKeys.length > 0 || Array.isArray(rawList);
20337
- const verifier = ota.verifySignature;
20338
- if (signingRequested && typeof verifier !== "function") {
20339
- const {
20340
- getBuiltinVerifier: getBuiltinVerifier2
20341
- } = (init_builtinVerifier(), __toCommonJS(builtinVerifier_exports));
20342
- if (!getBuiltinVerifier2()) {
20343
- 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`.");
20344
- }
20345
- }
20346
20059
  const healthyAfterMs = typeof ota.healthyAfterMs === "number" ? ota.healthyAfterMs : typeof ota.healthyTimeoutMs === "number" ? ota.healthyTimeoutMs : void 0;
20347
20060
  otaOrchestrator2.init({
20348
- ...signingRequested ? {
20349
- signature: {
20350
- // Single key stays a plain string — the shape every
20351
- // existing consumer and test already handles.
20352
- publicKey: publicKeys.length === 1 ? publicKeys[0] : publicKeys,
20353
- verifier
20354
- }
20355
- } : {},
20356
20061
  ...healthyAfterMs !== void 0 ? {
20357
20062
  healthyAfterMs
20358
20063
  } : {}
@@ -24310,11 +24015,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
24310
24015
  function resolveWheelOutcome(segments, source) {
24311
24016
  const identity = wheelOutcomeIdentity(source);
24312
24017
  const index = findWheelOutcomeIndex(segments, identity);
24313
- const resolved3 = index >= 0;
24018
+ const resolved = index >= 0;
24314
24019
  return {
24315
- index: resolved3 ? index : 0,
24316
- label: identity.resultLabel || (resolved3 ? segments[index]?.fullLabel ?? "" : ""),
24317
- resolved: resolved3
24020
+ index: resolved ? index : 0,
24021
+ label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
24022
+ resolved
24318
24023
  };
24319
24024
  }
24320
24025
  function SpinWheelInApp(props) {
@@ -33173,8 +32878,8 @@ function EngagePromptProvider({
33173
32878
  __DEV__ && logger.debug("[Engage] no eligible in-app message to render");
33174
32879
  return false;
33175
32880
  }
33176
- const resolved3 = selectInAppVariant(message, getDeviceId());
33177
- activeInAppVariantRef.current = message.experiment?.enabled ? resolved3 : null;
32881
+ const resolved = selectInAppVariant(message, getDeviceId());
32882
+ activeInAppVariantRef.current = message.experiment?.enabled ? resolved : null;
33178
32883
  store.recordImpression(message.id, Date.now(), message.revision);
33179
32884
  setActiveInApp(message);
33180
32885
  return true;