@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
@@ -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 resolved3 = LEVEL_BY_NAME[name];
96
- if (resolved3 !== void 0) {
97
- this.level = resolved3;
95
+ const resolved = LEVEL_BY_NAME[name];
96
+ if (resolved !== void 0) {
97
+ this.level = resolved;
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.11.1";
603
+ SDK_VERSION = "2.0.0";
604
604
  }
605
605
  });
606
606
 
@@ -6993,322 +6993,6 @@ var init_deviceAttributes = __esm({
6993
6993
  }
6994
6994
  });
6995
6995
 
6996
- // lib/module/features/ota/crypto/loadEd25519.js
6997
- function loadEd25519() {
6998
- try {
6999
- const mod = require("@noble/ed25519");
7000
- const ed = mod?.default ?? mod;
7001
- if (typeof ed?.verifyAsync === "function" || typeof ed?.verify === "function") {
7002
- return ed;
7003
- }
7004
- return null;
7005
- } catch {
7006
- return null;
7007
- }
7008
- }
7009
- var init_loadEd25519 = __esm({
7010
- "lib/module/features/ota/crypto/loadEd25519.js"() {
7011
- "use strict";
7012
- }
7013
- });
7014
-
7015
- // lib/module/features/ota/crypto/loadSha512.js
7016
- function loadSha512() {
7017
- try {
7018
- const mod = require("@noble/hashes/sha2.js");
7019
- const sha512 = mod?.sha512 ?? mod?.default?.sha512;
7020
- return typeof sha512 === "function" ? sha512 : null;
7021
- } catch {
7022
- return null;
7023
- }
7024
- }
7025
- var init_loadSha512 = __esm({
7026
- "lib/module/features/ota/crypto/loadSha512.js"() {
7027
- "use strict";
7028
- }
7029
- });
7030
-
7031
- // lib/module/features/ota/crypto/builtinVerifier.js
7032
- var builtinVerifier_exports = {};
7033
- __export(builtinVerifier_exports, {
7034
- _resetBuiltinVerifier: () => _resetBuiltinVerifier,
7035
- decodeSignature: () => decodeSignature,
7036
- getBuiltinVerifier: () => getBuiltinVerifier
7037
- });
7038
- function hexToBytes(hex) {
7039
- const clean2 = hex.trim().toLowerCase().replace(/^0x/, "");
7040
- if (clean2.length === 0 || clean2.length % 2 !== 0 || /[^0-9a-f]/.test(clean2)) return null;
7041
- const out = new Uint8Array(clean2.length / 2);
7042
- for (let i = 0; i < out.length; i++) {
7043
- out[i] = parseInt(clean2.slice(i * 2, i * 2 + 2), 16);
7044
- }
7045
- return out;
7046
- }
7047
- function base64ToBytes2(b64) {
7048
- const s = b64.trim().replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
7049
- if (s.length === 0) return null;
7050
- const out = [];
7051
- let buffer = 0;
7052
- let bits = 0;
7053
- for (const ch of s) {
7054
- const idx = B64_ALPHABET2.indexOf(ch);
7055
- if (idx === -1) return null;
7056
- buffer = buffer << 6 | idx;
7057
- bits += 6;
7058
- if (bits >= 8) {
7059
- bits -= 8;
7060
- out.push(buffer >> bits & 255);
7061
- }
7062
- }
7063
- return Uint8Array.from(out);
7064
- }
7065
- function decodeSignature(sig) {
7066
- const trimmed = sig.trim();
7067
- if (/^(0x)?[0-9a-fA-F]{128}$/.test(trimmed)) {
7068
- const hex2 = hexToBytes(trimmed);
7069
- if (hex2 && hex2.length === ED25519_SIGNATURE_BYTES) return hex2;
7070
- }
7071
- const b64 = base64ToBytes2(trimmed);
7072
- if (b64 && b64.length === ED25519_SIGNATURE_BYTES) return b64;
7073
- const hex = hexToBytes(trimmed);
7074
- if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
7075
- return null;
7076
- }
7077
- function wireSha512(ed, sha512) {
7078
- const concat2 = (parts) => {
7079
- if (parts.length === 1) return parts[0];
7080
- let total = 0;
7081
- for (const p of parts) total += p.length;
7082
- const out = new Uint8Array(total);
7083
- let off = 0;
7084
- for (const p of parts) {
7085
- out.set(p, off);
7086
- off += p.length;
7087
- }
7088
- return out;
7089
- };
7090
- const sync = (...msgs) => sha512(concat2(msgs));
7091
- const asyncFn = async (...msgs) => sha512(concat2(msgs));
7092
- let wired = false;
7093
- try {
7094
- if (ed.hashes && typeof ed.hashes === "object") {
7095
- if (typeof ed.hashes.sha512 !== "function") ed.hashes.sha512 = sync;
7096
- if (typeof ed.hashes.sha512Async !== "function") ed.hashes.sha512Async = asyncFn;
7097
- wired = typeof ed.hashes.sha512 === "function";
7098
- }
7099
- } catch {
7100
- }
7101
- try {
7102
- if (ed.etc && typeof ed.etc === "object") {
7103
- if (typeof ed.etc.sha512Sync !== "function") ed.etc.sha512Sync = sync;
7104
- if (typeof ed.etc.sha512Async !== "function") ed.etc.sha512Async = asyncFn;
7105
- wired = wired || typeof ed.etc.sha512Sync === "function";
7106
- }
7107
- } catch {
7108
- }
7109
- return wired;
7110
- }
7111
- function getBuiltinVerifier() {
7112
- if (resolved !== void 0) return resolved;
7113
- const ed = loadEd25519();
7114
- const sha512 = loadSha512();
7115
- if (!ed || !sha512) {
7116
- resolved = null;
7117
- return null;
7118
- }
7119
- if (!wireSha512(ed, sha512)) {
7120
- 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.");
7121
- resolved = null;
7122
- return null;
7123
- }
7124
- resolved = async ({
7125
- messageHex,
7126
- signature,
7127
- publicKey
7128
- }) => {
7129
- const message = hexToBytes(messageHex);
7130
- const pub = hexToBytes(publicKey);
7131
- const sig = decodeSignature(signature);
7132
- if (!message) {
7133
- logger.error("[OTA] Bundle SHA-256 is not valid hex \u2014 refusing to stage.");
7134
- return false;
7135
- }
7136
- if (!pub || pub.length !== ED25519_PUBLIC_KEY_BYTES) {
7137
- 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"}.`);
7138
- return false;
7139
- }
7140
- if (!sig) {
7141
- logger.error("[OTA] Bundle signature is not a 64-byte hex/base64 value.");
7142
- return false;
7143
- }
7144
- try {
7145
- if (typeof ed.verify === "function") return ed.verify(sig, message, pub);
7146
- if (typeof ed.verifyAsync === "function") return await ed.verifyAsync(sig, message, pub);
7147
- return false;
7148
- } catch {
7149
- return false;
7150
- }
7151
- };
7152
- __DEV__ && logger.debug("[OTA] Using built-in ed25519 verifier (@noble/ed25519 detected).");
7153
- return resolved;
7154
- }
7155
- function _resetBuiltinVerifier() {
7156
- resolved = void 0;
7157
- }
7158
- var ED25519_SIGNATURE_BYTES, ED25519_PUBLIC_KEY_BYTES, B64_ALPHABET2, resolved;
7159
- var init_builtinVerifier = __esm({
7160
- "lib/module/features/ota/crypto/builtinVerifier.js"() {
7161
- "use strict";
7162
- init_loadEd25519();
7163
- init_loadSha512();
7164
- init_internalLogger();
7165
- ED25519_SIGNATURE_BYTES = 64;
7166
- ED25519_PUBLIC_KEY_BYTES = 32;
7167
- B64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7168
- }
7169
- });
7170
-
7171
- // lib/module/features/ota/crypto/nativeVerifier.js
7172
- function bytesToHex(bytes) {
7173
- let out = "";
7174
- for (const b of bytes) out += b.toString(16).padStart(2, "0");
7175
- return out;
7176
- }
7177
- function normalizeHex(value, expectedChars) {
7178
- const clean2 = value.trim().toLowerCase().replace(/^0x/, "");
7179
- if (clean2.length !== expectedChars || /[^0-9a-f]/.test(clean2)) return null;
7180
- return clean2;
7181
- }
7182
- function getNativeVerifier() {
7183
- if (resolved2 !== void 0) return resolved2;
7184
- const native = NativeScaleBunOta_default;
7185
- if (!native || typeof native.verifyEd25519 !== "function") {
7186
- resolved2 = null;
7187
- return null;
7188
- }
7189
- resolved2 = async ({
7190
- messageHex,
7191
- signature,
7192
- publicKey
7193
- }) => {
7194
- const message = normalizeHex(messageHex, 64);
7195
- const key = normalizeHex(publicKey, ED25519_PUBLIC_KEY_HEX_CHARS);
7196
- const sigBytes = decodeSignature(signature);
7197
- if (!message || !key || !sigBytes) {
7198
- logger.error("[OTA] Signature inputs malformed (bundle hash, signature, or public key) \u2014 refusing to stage.");
7199
- return false;
7200
- }
7201
- let verdict;
7202
- try {
7203
- verdict = await native.verifyEd25519(message, bytesToHex(sigBytes), key);
7204
- } catch (err) {
7205
- logger.warn(`[OTA] Native ed25519 verify failed to run: ${err?.message ?? err}`);
7206
- verdict = "unavailable";
7207
- }
7208
- if (verdict === "valid") return true;
7209
- if (verdict === "invalid") return false;
7210
- const builtin = getBuiltinVerifier();
7211
- if (builtin) {
7212
- return builtin({
7213
- messageHex,
7214
- signature,
7215
- publicKey
7216
- });
7217
- }
7218
- throw new Error("ed25519 unavailable: this OS has no native implementation (Android < 13) and the optional peers @noble/ed25519 + @noble/hashes are not installed.");
7219
- };
7220
- __DEV__ && logger.debug("[OTA] Using native ed25519 verifier (platform crypto).");
7221
- return resolved2;
7222
- }
7223
- var ED25519_PUBLIC_KEY_HEX_CHARS, resolved2;
7224
- var init_nativeVerifier = __esm({
7225
- "lib/module/features/ota/crypto/nativeVerifier.js"() {
7226
- "use strict";
7227
- init_NativeScaleBunOta();
7228
- init_internalLogger();
7229
- init_builtinVerifier();
7230
- ED25519_PUBLIC_KEY_HEX_CHARS = 64;
7231
- }
7232
- });
7233
-
7234
- // lib/module/features/ota/signature.js
7235
- async function verifyBundleSignature(bundleSha256, signature, config) {
7236
- const rawKey = config?.publicKey;
7237
- const keys = (Array.isArray(rawKey) ? rawKey : rawKey ? [rawKey] : []).filter((k) => typeof k === "string" && k.trim().length > 0);
7238
- if (rawKey === void 0 || rawKey === "" || rawKey === null) {
7239
- if (!warnedNotConfigured) {
7240
- warnedNotConfigured = true;
7241
- logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
7242
- }
7243
- return {
7244
- ok: true,
7245
- reason: "not_configured"
7246
- };
7247
- }
7248
- if (keys.length === 0) {
7249
- 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.");
7250
- return {
7251
- ok: false,
7252
- reason: "no_keys"
7253
- };
7254
- }
7255
- if (!signature) {
7256
- logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
7257
- return {
7258
- ok: false,
7259
- reason: "missing_signature"
7260
- };
7261
- }
7262
- const verifier = typeof config?.verifier === "function" ? config.verifier : getNativeVerifier() ?? getBuiltinVerifier();
7263
- if (!verifier) {
7264
- 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`.");
7265
- return {
7266
- ok: false,
7267
- reason: "no_verifier"
7268
- };
7269
- }
7270
- let sawThrow = false;
7271
- let sawCompletion = false;
7272
- for (const key of keys) {
7273
- try {
7274
- const valid = await verifier({
7275
- messageHex: bundleSha256,
7276
- signature,
7277
- publicKey: key
7278
- });
7279
- sawCompletion = true;
7280
- if (valid) return {
7281
- ok: true,
7282
- reason: "verified"
7283
- };
7284
- } catch (err) {
7285
- sawThrow = true;
7286
- logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
7287
- }
7288
- }
7289
- if (sawThrow && !sawCompletion) {
7290
- return {
7291
- ok: false,
7292
- reason: "verifier_threw"
7293
- };
7294
- }
7295
- 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.");
7296
- return {
7297
- ok: false,
7298
- reason: "invalid_signature"
7299
- };
7300
- }
7301
- var warnedNotConfigured;
7302
- var init_signature = __esm({
7303
- "lib/module/features/ota/signature.js"() {
7304
- "use strict";
7305
- init_internalLogger();
7306
- init_builtinVerifier();
7307
- init_nativeVerifier();
7308
- warnedNotConfigured = false;
7309
- }
7310
- });
7311
-
7312
6996
  // lib/module/features/ota/environment.js
7313
6997
  function compare(a, b) {
7314
6998
  if (a.major !== b.major) return a.major - b.major;
@@ -7554,7 +7238,6 @@ var init_OtaOrchestrator = __esm({
7554
7238
  init_geoCountry();
7555
7239
  init_deviceAttributes();
7556
7240
  init_installationId();
7557
- init_signature();
7558
7241
  init_environment();
7559
7242
  init_retry();
7560
7243
  init_StorageBackend();
@@ -7582,15 +7265,15 @@ var init_OtaOrchestrator = __esm({
7582
7265
  /**
7583
7266
  * Initialize the orchestrator. Checks for boot-guard recovery state.
7584
7267
  *
7585
- * `signature` is optional: an app that has not adopted signing keeps working
7586
- * on SHA-256 integrity alone. Once a public key IS configured, a bundle that
7587
- * cannot be verified is refused rather than installed (OTA-03).
7268
+ * Signature enforcement is no longer a JS concern: signed releases are
7269
+ * verified and staged in NATIVE code against RSA keys embedded in the app
7270
+ * package (SB-OTA-RSA-SHA256-V1), and unsigned releases keep working on
7271
+ * SHA-256 integrity alone. There is nothing for the host to configure here.
7588
7272
  */
7589
7273
  init(config) {
7590
7274
  noThrow(() => {
7591
7275
  if (this.initialized) {
7592
7276
  this.bootGuardConfig = config ?? this.bootGuardConfig;
7593
- this.signatureConfig = config?.signature ?? this.signatureConfig;
7594
7277
  return;
7595
7278
  }
7596
7279
  if (!NativeScaleBunOta_default) {
@@ -7612,7 +7295,6 @@ var init_OtaOrchestrator = __esm({
7612
7295
  this.initialized = true;
7613
7296
  this.enabled = true;
7614
7297
  this.bootGuardConfig = config ?? {};
7615
- this.signatureConfig = config?.signature;
7616
7298
  __DEV__ && logger.debug(`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? "unknown"}${this.environment.bridgeless ? ", bridgeless" : ""}${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? "?"}` : ""})`);
7617
7299
  const slotState = this.readSlotState();
7618
7300
  this.hydrateCurrentBundleFromSlots(slotState);
@@ -7799,12 +7481,22 @@ var init_OtaOrchestrator = __esm({
7799
7481
  const env = this.environment ?? detectOtaEnvironment();
7800
7482
  const mobileOS = resolveMobileOS();
7801
7483
  if (!mobileOS) throw new Error("OTA is not supported on this platform");
7484
+ let trustedKeyIds;
7485
+ try {
7486
+ const raw = NativeScaleBunOta_default?.getTrustedKeyIds?.();
7487
+ if (raw) {
7488
+ const arr2 = JSON.parse(raw);
7489
+ if (Array.isArray(arr2) && arr2.length) trustedKeyIds = arr2.slice(0, 16);
7490
+ }
7491
+ } catch {
7492
+ }
7802
7493
  const payload = {
7803
7494
  appVersion: requestParams.appVersion,
7804
7495
  platform: mobileOS,
7805
7496
  installationId: requestParams.installationId,
7806
7497
  currentBundleId: this.currentBundle?.id,
7807
7498
  currentBundleHash: this.currentBundle?.sha256,
7499
+ trustedKeyIds,
7808
7500
  channelName: requestParams.channelName,
7809
7501
  // Host-supplied country wins; otherwise the cached edge-Worker lookup
7810
7502
  // fills in (see geoCountry.ts). Undefined when neither knows — the
@@ -7918,19 +7610,6 @@ var init_OtaOrchestrator = __esm({
7918
7610
  releaseId: bundle.releaseId,
7919
7611
  version: bundle.version
7920
7612
  });
7921
- const signatureOutcome = await verifyBundleSignature(bundle.sha256, bundle.signature, this.signatureConfig);
7922
- if (!signatureOutcome.ok) {
7923
- otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
7924
- releaseId: bundle.releaseId,
7925
- error: `Signature check failed: ${signatureOutcome.reason}`,
7926
- version: bundle.version
7927
- });
7928
- return {
7929
- status: "ERROR",
7930
- error: `Bundle signature check failed (${signatureOutcome.reason})`,
7931
- bundle
7932
- };
7933
- }
7934
7613
  otaEventEmitter.emitSimple("DOWNLOAD_STARTED", bundle.id, {
7935
7614
  releaseId: bundle.releaseId,
7936
7615
  version: bundle.version
@@ -7978,39 +7657,75 @@ var init_OtaOrchestrator = __esm({
7978
7657
  }
7979
7658
  });
7980
7659
  let staged = false;
7981
- if (bundle.patch && NativeScaleBunOta_default.stagePatch) {
7982
- __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`);
7660
+ if (bundle.envelope) {
7661
+ const envelopeJson = JSON.stringify({
7662
+ ...bundle.envelope,
7663
+ downloadUrl: bundle.url
7664
+ });
7665
+ let verdict;
7983
7666
  try {
7984
- staged = await NativeScaleBunOta_default.stagePatch(
7985
- bundle.patch.url,
7986
- bundle.patch.sha256,
7987
- bundle.sha256
7988
- // expected full-bundle hash after patching
7989
- );
7990
- if (staged) {
7991
- patchUsed = true;
7992
- __DEV__ && logger.debug("[OTA] Patch applied successfully");
7993
- } else {
7994
- logger.warn("[OTA] Patch staging returned false \u2014 falling back to full bundle");
7995
- }
7996
- } catch (patchErr) {
7997
- logger.warn(`[OTA] Patch staging failed: ${patchErr.message} \u2014 falling back to full bundle`);
7667
+ const verdictJson = await NativeScaleBunOta_default.verifyAndStageRelease(envelopeJson);
7668
+ verdict = JSON.parse(verdictJson);
7669
+ } catch (err) {
7670
+ verdict = {
7671
+ ok: false,
7672
+ code: "verify_stage_threw",
7673
+ message: err?.message ?? String(err)
7674
+ };
7998
7675
  }
7999
- }
8000
- if (!staged) {
8001
- try {
8002
- staged = await retryWithBackoff(async () => {
8003
- const ok = await NativeScaleBunOta_default.stageBundle(bundle.url, bundle.sha256);
8004
- if (!ok) {
8005
- throw new Error("stageBundle returned false");
8006
- }
8007
- return ok;
8008
- }, {
8009
- label: `download v${bundle.version}`,
8010
- attempts: 3
7676
+ if (!verdict.ok) {
7677
+ unsubscribeProgress();
7678
+ postProgress(0, "FAILED");
7679
+ logger.error(`[OTA] Native verify/stage refused the release: ${verdict.code ?? "unknown"}${verdict.message ? ` \u2014 ${verdict.message}` : ""}`);
7680
+ otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
7681
+ releaseId: bundle.releaseId,
7682
+ // `error` is mapped onto the wire `errorCode` by deliverOtaEvents —
7683
+ // carry the stable native verdict code there.
7684
+ error: verdict.code ?? "verify_and_stage_release_failed",
7685
+ version: bundle.version
8011
7686
  });
8012
- } catch {
8013
- staged = false;
7687
+ return {
7688
+ status: "ERROR",
7689
+ error: `Release verification failed (${verdict.code ?? "unknown"})`,
7690
+ bundle
7691
+ };
7692
+ }
7693
+ staged = true;
7694
+ } else {
7695
+ if (bundle.patch && NativeScaleBunOta_default.stagePatch) {
7696
+ __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`);
7697
+ try {
7698
+ staged = await NativeScaleBunOta_default.stagePatch(
7699
+ bundle.patch.url,
7700
+ bundle.patch.sha256,
7701
+ bundle.sha256
7702
+ // expected full-bundle hash after patching
7703
+ );
7704
+ if (staged) {
7705
+ patchUsed = true;
7706
+ __DEV__ && logger.debug("[OTA] Patch applied successfully");
7707
+ } else {
7708
+ logger.warn("[OTA] Patch staging returned false \u2014 falling back to full bundle");
7709
+ }
7710
+ } catch (patchErr) {
7711
+ logger.warn(`[OTA] Patch staging failed: ${patchErr.message} \u2014 falling back to full bundle`);
7712
+ }
7713
+ }
7714
+ if (!staged) {
7715
+ try {
7716
+ staged = await retryWithBackoff(async () => {
7717
+ const ok = await NativeScaleBunOta_default.stageBundle(bundle.url, bundle.sha256);
7718
+ if (!ok) {
7719
+ throw new Error("stageBundle returned false");
7720
+ }
7721
+ return ok;
7722
+ }, {
7723
+ label: `download v${bundle.version}`,
7724
+ attempts: 3
7725
+ });
7726
+ } catch {
7727
+ staged = false;
7728
+ }
8014
7729
  }
8015
7730
  }
8016
7731
  unsubscribeProgress();
@@ -10186,18 +9901,9 @@ var SHAPE = {
10186
9901
  kind: "str",
10187
9902
  opt: true
10188
9903
  },
10189
- publicSigningKey: {
10190
- kind: "str",
10191
- opt: true
10192
- },
10193
- // Rotation support: several pinned keys, any of which may verify a
10194
- // bundle. Ship a build pinning [old, new], re-sign server-side with
10195
- // new, drop old next release — no emergency store submission when a
10196
- // key must be replaced. Merged with publicSigningKey by the facade.
10197
- publicSigningKeys: {
10198
- kind: "strArr",
10199
- opt: true
10200
- },
9904
+ // No signing-key fields: signed releases are verified natively against
9905
+ // RSA keys embedded in the app package (SB-OTA-RSA-SHA256-V1). JS must
9906
+ // not supply a trusted key.
10201
9907
  mandatoryBlocksUi: bool(false)
10202
9908
  }
10203
9909
  }
@@ -10999,6 +10705,23 @@ function installNetworkMonitoring(onEvent, options = {}) {
10999
10705
  });
11000
10706
  }
11001
10707
 
10708
+ // lib/module/features/network/thirdParty.js
10709
+ 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";
10710
+ function isThirdPartyBeacon(url, hosts) {
10711
+ if (!url) return false;
10712
+ let host = "";
10713
+ try {
10714
+ host = new URL(url).hostname.toLowerCase();
10715
+ } catch {
10716
+ return false;
10717
+ }
10718
+ if (!host) return false;
10719
+ for (const suffix of (hosts ?? THIRD_PARTY_BEACON_HOSTS).split(",")) {
10720
+ if (host === suffix || host.endsWith("." + suffix)) return true;
10721
+ }
10722
+ return false;
10723
+ }
10724
+
11002
10725
  // lib/module/features/network/NetworkFeature.js
11003
10726
  init_networkAdapter();
11004
10727
  init_SessionManager();
@@ -11014,10 +10737,12 @@ var NetworkFeature = class {
11014
10737
  if (this.active) return;
11015
10738
  installNetworkMonitoring((data) => {
11016
10739
  const isInternal = isSdkInternalUrl(data.url, this.options.sdkBaseUrl);
11017
- if (!isInternal) {
10740
+ const isBeaconOrDenied = this.options.captureThirdParty !== true && isThirdPartyBeacon(data.url, this.options.thirdPartyHosts) || matchesDenyUrl(data.url, this.options.denyUrls);
10741
+ const isExcluded = isInternal || isBeaconOrDenied;
10742
+ if (!isExcluded) {
11018
10743
  this.trackFn("$network_request", data);
11019
10744
  }
11020
- if (data.type === "network") {
10745
+ if (data.type === "network" && !isBeaconOrDenied) {
11021
10746
  reportNetworkRequest({
11022
10747
  method: data.method,
11023
10748
  url: data.url,
@@ -11033,7 +10758,7 @@ var NetworkFeature = class {
11033
10758
  requestBody: data.requestBody,
11034
10759
  responseBody: data.responseBody
11035
10760
  });
11036
- if (!isInternal) {
10761
+ if (!isExcluded) {
11037
10762
  const sessionManager = SessionManager.getExistingInstance();
11038
10763
  if (sessionManager) {
11039
10764
  const backendTransport = sessionManager.getBackendTransport();
@@ -11064,6 +10789,13 @@ var NetworkFeature = class {
11064
10789
  return this.active;
11065
10790
  }
11066
10791
  };
10792
+ function matchesDenyUrl(url, patterns) {
10793
+ if (!patterns || !patterns.length || !url) return false;
10794
+ for (const p of patterns) {
10795
+ if (typeof p === "string" ? url.includes(p) : p.test(url)) return true;
10796
+ }
10797
+ return false;
10798
+ }
11067
10799
  function isSdkInternalUrl(url, sdkBaseUrl) {
11068
10800
  if (!url) return false;
11069
10801
  try {
@@ -17100,17 +16832,11 @@ var ScaleBunFacade = class _ScaleBunFacade {
17100
16832
  /**
17101
16833
  * Boot the OTA orchestrator when the init config asks for it.
17102
16834
  *
17103
- * `publicSigningKey` / `publicSigningKeys` are honoured here so signature
17104
- * enforcement is reachable from configuration alone. The list form exists
17105
- * for key ROTATION: a build pinning [old, new] keeps verifying while the
17106
- * server moves to the new key, so replacing a key never needs an emergency
17107
- * store release. Both fields merge (deduplicated) into one pinned set.
17108
- *
17109
- * Verification comes from `ota.verifySignature` when supplied, else the
17110
- * built-in @noble-based verifier (optional peers). Pinning keys with
17111
- * NEITHER available is fail-CLOSED by design (signature.ts) — an update
17112
- * that cannot be verified is not installed. We say that out loud rather
17113
- * than letting the app discover it as a silent no-update condition.
16835
+ * Signature enforcement is no longer configured from JS. Signed releases are
16836
+ * verified and staged in NATIVE code against RSA keys embedded in the app
16837
+ * package (SB-OTA-RSA-SHA256-V1); unsigned releases keep working on SHA-256
16838
+ * integrity. There are no signing-key or `verifySignature` options to pass
16839
+ * through the host embeds its trusted keys natively.
17114
16840
  */
17115
16841
  _maybeStartOta(rawConfig) {
17116
16842
  const ota = rawConfig?.ota;
@@ -17119,29 +16845,8 @@ var ScaleBunFacade = class _ScaleBunFacade {
17119
16845
  const {
17120
16846
  otaOrchestrator: otaOrchestrator2
17121
16847
  } = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
17122
- const rawSingle = ota.publicSigningKey;
17123
- const rawList = ota.publicSigningKeys;
17124
- const publicKeys = Array.from(new Set([...typeof rawSingle === "string" && rawSingle ? [rawSingle] : [], ...Array.isArray(rawList) ? rawList : []].filter((k) => typeof k === "string" && k.trim().length > 0)));
17125
- const signingRequested = publicKeys.length > 0 || Array.isArray(rawList);
17126
- const verifier = ota.verifySignature;
17127
- if (signingRequested && typeof verifier !== "function") {
17128
- const {
17129
- getBuiltinVerifier: getBuiltinVerifier2
17130
- } = (init_builtinVerifier(), __toCommonJS(builtinVerifier_exports));
17131
- if (!getBuiltinVerifier2()) {
17132
- 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`.");
17133
- }
17134
- }
17135
16848
  const healthyAfterMs = typeof ota.healthyAfterMs === "number" ? ota.healthyAfterMs : typeof ota.healthyTimeoutMs === "number" ? ota.healthyTimeoutMs : void 0;
17136
16849
  otaOrchestrator2.init({
17137
- ...signingRequested ? {
17138
- signature: {
17139
- // Single key stays a plain string — the shape every
17140
- // existing consumer and test already handles.
17141
- publicKey: publicKeys.length === 1 ? publicKeys[0] : publicKeys,
17142
- verifier
17143
- }
17144
- } : {},
17145
16850
  ...healthyAfterMs !== void 0 ? {
17146
16851
  healthyAfterMs
17147
16852
  } : {}
@@ -21099,11 +20804,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
21099
20804
  function resolveWheelOutcome(segments, source) {
21100
20805
  const identity = wheelOutcomeIdentity(source);
21101
20806
  const index = findWheelOutcomeIndex(segments, identity);
21102
- const resolved3 = index >= 0;
20807
+ const resolved = index >= 0;
21103
20808
  return {
21104
- index: resolved3 ? index : 0,
21105
- label: identity.resultLabel || (resolved3 ? segments[index]?.fullLabel ?? "" : ""),
21106
- resolved: resolved3
20809
+ index: resolved ? index : 0,
20810
+ label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
20811
+ resolved
21107
20812
  };
21108
20813
  }
21109
20814
  function SpinWheelInApp(props) {
@@ -29962,8 +29667,8 @@ function EngagePromptProvider({
29962
29667
  __DEV__ && logger.debug("[Engage] no eligible in-app message to render");
29963
29668
  return false;
29964
29669
  }
29965
- const resolved3 = selectInAppVariant(message, getDeviceId());
29966
- activeInAppVariantRef.current = message.experiment?.enabled ? resolved3 : null;
29670
+ const resolved = selectInAppVariant(message, getDeviceId());
29671
+ activeInAppVariantRef.current = message.experiment?.enabled ? resolved : null;
29967
29672
  store.recordImpression(message.id, Date.now(), message.revision);
29968
29673
  setActiveInApp(message);
29969
29674
  return true;