@scalebun/react-native 1.10.5 → 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 (64) 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 +485 -51
  4. package/dist/scalebun.slim.js +485 -51
  5. package/ios/Core/DeviceIdentity.swift +50 -0
  6. package/ios/Ota/ScaleBunOtaBridge.mm +6 -0
  7. package/ios/Ota/ScaleBunOtaModule.swift +67 -0
  8. package/ios/Profiler/ScaleBunProfilerModule.swift +2 -8
  9. package/ios/ReplaySdk.swift +3 -1
  10. package/lib/commonjs/analytics/EventTracker.js +22 -0
  11. package/lib/commonjs/core/config/schema.js +34 -1
  12. package/lib/commonjs/core/constants/version.js +1 -1
  13. package/lib/commonjs/core/context/device.js +5 -0
  14. package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +24 -0
  15. package/lib/commonjs/features/journey/uiState.js +87 -0
  16. package/lib/commonjs/features/ota/crypto/builtinVerifier.js +248 -0
  17. package/lib/commonjs/features/ota/crypto/loadEd25519.js +40 -0
  18. package/lib/commonjs/features/ota/crypto/loadSha512.js +40 -0
  19. package/lib/commonjs/features/ota/crypto/nativeVerifier.js +121 -0
  20. package/lib/commonjs/features/ota/signature.js +87 -27
  21. package/lib/commonjs/metro/serializerCompose.js +32 -0
  22. package/lib/commonjs/public/ScaleBunFacade.js +108 -11
  23. package/lib/module/analytics/EventTracker.js +22 -0
  24. package/lib/module/core/config/schema.js +34 -1
  25. package/lib/module/core/constants/version.js +1 -1
  26. package/lib/module/core/context/device.js +5 -0
  27. package/lib/module/features/journey/ScaleBunDebugRoot.js +24 -0
  28. package/lib/module/features/journey/uiState.js +79 -0
  29. package/lib/module/features/ota/crypto/builtinVerifier.js +240 -0
  30. package/lib/module/features/ota/crypto/loadEd25519.js +34 -0
  31. package/lib/module/features/ota/crypto/loadSha512.js +34 -0
  32. package/lib/module/features/ota/crypto/nativeVerifier.js +113 -0
  33. package/lib/module/features/ota/signature.js +87 -27
  34. package/lib/module/metro/serializerCompose.js +32 -0
  35. package/lib/module/public/ScaleBunFacade.js +109 -12
  36. package/lib/typescript/analytics/EventTracker.d.ts +15 -0
  37. package/lib/typescript/core/config/schema.d.ts +2 -0
  38. package/lib/typescript/core/constants/version.d.ts +1 -1
  39. package/lib/typescript/core/context/device.d.ts +1 -0
  40. package/lib/typescript/features/journey/uiState.d.ts +53 -0
  41. package/lib/typescript/features/ota/OtaTypes.d.ts +50 -0
  42. package/lib/typescript/features/ota/crypto/builtinVerifier.d.ts +53 -0
  43. package/lib/typescript/features/ota/crypto/loadEd25519.d.ts +30 -0
  44. package/lib/typescript/features/ota/crypto/loadSha512.d.ts +15 -0
  45. package/lib/typescript/features/ota/crypto/nativeVerifier.d.ts +35 -0
  46. package/lib/typescript/features/ota/signature.d.ts +22 -7
  47. package/lib/typescript/public/ScaleBunFacade.d.ts +43 -6
  48. package/lib/typescript/specs/NativeScaleBunOta.d.ts +23 -0
  49. package/package.json +18 -3
  50. package/src/analytics/EventTracker.ts +19 -0
  51. package/src/core/config/schema.ts +30 -3
  52. package/src/core/constants/version.ts +1 -1
  53. package/src/core/context/device.ts +6 -0
  54. package/src/features/journey/ScaleBunDebugRoot.tsx +22 -0
  55. package/src/features/journey/uiState.ts +84 -0
  56. package/src/features/ota/OtaTypes.ts +51 -0
  57. package/src/features/ota/crypto/builtinVerifier.ts +257 -0
  58. package/src/features/ota/crypto/loadEd25519.ts +41 -0
  59. package/src/features/ota/crypto/loadSha512.ts +35 -0
  60. package/src/features/ota/crypto/nativeVerifier.ts +117 -0
  61. package/src/features/ota/signature.ts +108 -25
  62. package/src/metro/serializerCompose.ts +38 -2
  63. package/src/public/ScaleBunFacade.ts +128 -14
  64. 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.5";
608
+ SDK_VERSION = "1.10.7";
609
609
  }
610
610
  });
611
611
 
@@ -647,6 +647,15 @@ function staticDeviceContext() {
647
647
  if (c.Model) out.device_model = String(c.Model);
648
648
  if (c.Manufacturer) out.manufacturer = String(c.Manufacturer);
649
649
  else if (c.Brand) out.manufacturer = String(c.Brand);
650
+ else if (out.platform_os === "ios") out.manufacturer = "Apple";
651
+ return out;
652
+ }
653
+ function nativeDeviceContext(info) {
654
+ const out = {};
655
+ if (!info) return out;
656
+ if (info.osVersion) out.os_version = info.osVersion;
657
+ if (info.deviceModel) out.device_model = info.deviceModel;
658
+ if (info.manufacturer) out.manufacturer = info.manufacturer;
650
659
  return out;
651
660
  }
652
661
  function mergeDeviceContext(configured, detected = staticDeviceContext()) {
@@ -11232,6 +11241,27 @@ var init_EventTracker = __esm({
11232
11241
  }
11233
11242
  this.persistQueue();
11234
11243
  }
11244
+ /**
11245
+ * Fold in device facts that were not knowable at init.
11246
+ *
11247
+ * `Platform.constants` answers Android synchronously, so its model and manufacturer ride the
11248
+ * very first event. iOS exposes neither there — only the native bridge knows, and the bridge is
11249
+ * async and may not be installed at all. Without this the iOS half of every install reported no
11250
+ * model whatsoever.
11251
+ *
11252
+ * `buildEnvelope` reads `cfg.context` per event, so applying it here reaches every event from
11253
+ * the next one onward. Detected keys WIN over whatever the integrator configured, for the same
11254
+ * reason `mergeDeviceContext` inverts the usual precedence: a measurement must not be
11255
+ * overridable by a guess. Empty patches are ignored so a bridge that answered with nothing
11256
+ * cannot blank a value the static path already found.
11257
+ */
11258
+ applyDetectedContext(patch) {
11259
+ if (!patch || Object.keys(patch).length === 0) return;
11260
+ this.cfg.context = {
11261
+ ...this.cfg.context ?? {},
11262
+ ...patch
11263
+ };
11264
+ }
11235
11265
  // ─── internals ─────────────────────────────────────────────────────────────
11236
11266
  buildEnvelope(eventName, properties) {
11237
11267
  const ctx = this.cfg.context ?? {};
@@ -11581,10 +11611,249 @@ var init_deviceAttributes = __esm({
11581
11611
  }
11582
11612
  });
11583
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
+
11584
11852
  // lib/module/features/ota/signature.js
11585
11853
  async function verifyBundleSignature(bundleSha256, signature, config) {
11586
- const publicKey = config?.publicKey;
11587
- 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) {
11588
11857
  if (!warnedNotConfigured) {
11589
11858
  warnedNotConfigured = true;
11590
11859
  logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
@@ -11594,6 +11863,13 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
11594
11863
  reason: "not_configured"
11595
11864
  };
11596
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
+ }
11597
11873
  if (!signature) {
11598
11874
  logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
11599
11875
  return {
@@ -11601,43 +11877,52 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
11601
11877
  reason: "missing_signature"
11602
11878
  };
11603
11879
  }
11604
- if (typeof config?.verifier !== "function") {
11605
- 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`.");
11606
11883
  return {
11607
11884
  ok: false,
11608
11885
  reason: "no_verifier"
11609
11886
  };
11610
11887
  }
11611
- try {
11612
- const valid = await config.verifier({
11613
- messageHex: bundleSha256,
11614
- signature,
11615
- publicKey
11616
- });
11617
- if (!valid) {
11618
- logger.error("[OTA] Bundle signature is INVALID \u2014 refusing to stage.");
11619
- return {
11620
- ok: false,
11621
- 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"
11622
11901
  };
11902
+ } catch (err) {
11903
+ sawThrow = true;
11904
+ logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
11623
11905
  }
11624
- return {
11625
- ok: true,
11626
- reason: "verified"
11627
- };
11628
- } catch (err) {
11629
- logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
11906
+ }
11907
+ if (sawThrow && !sawCompletion) {
11630
11908
  return {
11631
11909
  ok: false,
11632
11910
  reason: "verifier_threw"
11633
11911
  };
11634
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
+ };
11635
11918
  }
11636
11919
  var warnedNotConfigured;
11637
11920
  var init_signature = __esm({
11638
11921
  "lib/module/features/ota/signature.js"() {
11639
11922
  "use strict";
11640
11923
  init_internalLogger();
11924
+ init_builtinVerifier();
11925
+ init_nativeVerifier();
11641
11926
  warnedNotConfigured = false;
11642
11927
  }
11643
11928
  });
@@ -12457,6 +12742,35 @@ var init_OtaOrchestrator = __esm({
12457
12742
  }
12458
12743
  });
12459
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
+
12460
12774
  // lib/module/features/engage/gestureTriggerDetector.js
12461
12775
  var gestureTriggerDetector_exports = {};
12462
12776
  __export(gestureTriggerDetector_exports, {
@@ -14374,6 +14688,14 @@ var SHAPE = {
14374
14688
  kind: "str",
14375
14689
  opt: true
14376
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
+ },
14377
14699
  mandatoryBlocksUi: bool(false)
14378
14700
  }
14379
14701
  }
@@ -14409,7 +14731,7 @@ function parseField(field, input, path, issues) {
14409
14731
  value: field.def,
14410
14732
  present: true
14411
14733
  };
14412
- 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");
14413
14735
  }
14414
14736
  switch (field.kind) {
14415
14737
  case "any":
@@ -14435,6 +14757,27 @@ function parseField(field, input, path, issues) {
14435
14757
  value: input,
14436
14758
  present: true
14437
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
+ }
14438
14781
  case "num":
14439
14782
  if (typeof input !== "number" || Number.isNaN(input)) {
14440
14783
  return fail(`Expected number, received ${typeofName2(input)}`);
@@ -15638,16 +15981,16 @@ function base64ToBytes(base64) {
15638
15981
  return decodeManual(base64);
15639
15982
  }
15640
15983
  function decodeManual(base64) {
15641
- const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
15642
- const pad = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
15643
- 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;
15644
15987
  const out = new Uint8Array(outLen);
15645
15988
  let p = 0;
15646
- for (let i = 0; i < clean.length; i += 4) {
15647
- const c0 = B64_ALPHABET.indexOf(clean[i]);
15648
- const c1 = B64_ALPHABET.indexOf(clean[i + 1]);
15649
- const c2 = B64_ALPHABET.indexOf(clean[i + 2]);
15650
- 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]);
15651
15994
  const n = c0 << 18 | c1 << 12 | (c2 & 63) << 6 | c3 & 63;
15652
15995
  if (p < outLen) out[p++] = n >> 16 & 255;
15653
15996
  if (p < outLen) out[p++] = n >> 8 & 255;
@@ -17408,6 +17751,7 @@ var SDKBootstrapper = class {
17408
17751
 
17409
17752
  // lib/module/public/ScaleBunFacade.js
17410
17753
  init_EventTracker();
17754
+ init_bridgeAdapter();
17411
17755
  init_automaticEvents();
17412
17756
 
17413
17757
  // lib/module/analytics/eventLane.js
@@ -19834,12 +20178,17 @@ var ScaleBunFacade = class {
19834
20178
  /**
19835
20179
  * Boot the OTA orchestrator when the init config asks for it.
19836
20180
  *
19837
- * `publicSigningKey` is honoured here so signature enforcement is reachable
19838
- * from configuration alone. The SDK ships no ed25519 implementation, so a
19839
- * host that pins a key must also supply `ota.verifySignature`; pinning a key
19840
- * without a verifier is fail-CLOSED by design (signature.ts) an update
19841
- * that cannot be verified is not installed. We say that out loud rather than
19842
- * 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.
19843
20192
  */
19844
20193
  _maybeStartOta(rawConfig) {
19845
20194
  const ota = rawConfig?.ota;
@@ -19848,15 +20197,25 @@ var ScaleBunFacade = class {
19848
20197
  const {
19849
20198
  otaOrchestrator: otaOrchestrator2
19850
20199
  } = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
19851
- 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);
19852
20204
  const verifier = ota.verifySignature;
19853
- if (publicKey && typeof verifier !== "function") {
19854
- 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
+ }
19855
20212
  }
19856
20213
  otaOrchestrator2.init({
19857
- ...publicKey ? {
20214
+ ...signingRequested ? {
19858
20215
  signature: {
19859
- 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,
19860
20219
  verifier
19861
20220
  }
19862
20221
  } : {},
@@ -19925,6 +20284,8 @@ var ScaleBunFacade = class {
19925
20284
  * the whole time. The SDK knows the device; the integrator is guessing at it.
19926
20285
  * Everything outside the detected keys is still theirs.
19927
20286
  */
20287
+ // Everything Platform.constants knows. On iOS that is neither the model nor the
20288
+ // manufacturer, so the bridge completes it below once it answers.
19928
20289
  context: mergeDeviceContext(rawConfig?.context),
19929
20290
  // Use the same foreground id as replay/journey capture. The
19930
20291
  // callback is resolved per event because the session subsystem
@@ -19938,6 +20299,7 @@ var ScaleBunFacade = class {
19938
20299
  });
19939
20300
  this._eventTracker.start();
19940
20301
  __DEV__ && logger.info("[ScaleBun] Envelope event tracking started \u2192 /v1/batch.");
20302
+ this._applyNativeDeviceContext(this._eventTracker);
19941
20303
  this._captureInstallReferrer(this._eventTracker);
19942
20304
  const tracker = this._eventTracker;
19943
20305
  this._configManager = new ConfigManager({
@@ -19956,6 +20318,25 @@ var ScaleBunFacade = class {
19956
20318
  logger.warn("[ScaleBun] Failed to start envelope event tracking:", err?.message);
19957
20319
  }
19958
20320
  }
20321
+ /**
20322
+ * Fold the native bridge's device facts into the analytics context once it answers.
20323
+ *
20324
+ * Detected values never overwrite a populated one with an empty one — `nativeDeviceContext`
20325
+ * drops absent keys and `applyDetectedContext` ignores an empty patch — so a bridge that
20326
+ * cannot answer leaves whatever `Platform.constants` already found.
20327
+ */
20328
+ _applyNativeDeviceContext(tracker) {
20329
+ Promise.resolve().then(() => bridgeAdapter.getDeviceInfo()).then((info) => {
20330
+ if (!info) return;
20331
+ tracker.applyDetectedContext(nativeDeviceContext({
20332
+ osVersion: info.osVersion,
20333
+ deviceModel: info.deviceModel
20334
+ // iOS ships no manufacturer over the bridge; it is a constant there and the
20335
+ // static path already set it. Android's arrives via Platform.constants.
20336
+ }));
20337
+ }).catch(() => {
20338
+ });
20339
+ }
19959
20340
  /**
19960
20341
  * Android-only: read the Play Install Referrer once per install and route it into
19961
20342
  * attribution. `scalebun_click_id` → the deterministic click path; the full
@@ -20046,6 +20427,46 @@ var ScaleBunFacade = class {
20046
20427
  };
20047
20428
  }
20048
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
+ }
20049
20470
  trackPurchase(input) {
20050
20471
  noThrow(() => this._eventTracker?.trackPurchase(input));
20051
20472
  }
@@ -23669,11 +24090,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
23669
24090
  function resolveWheelOutcome(segments, source) {
23670
24091
  const identity = wheelOutcomeIdentity(source);
23671
24092
  const index = findWheelOutcomeIndex(segments, identity);
23672
- const resolved = index >= 0;
24093
+ const resolved3 = index >= 0;
23673
24094
  return {
23674
- index: resolved ? index : 0,
23675
- label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
23676
- resolved
24095
+ index: resolved3 ? index : 0,
24096
+ label: identity.resultLabel || (resolved3 ? segments[index]?.fullLabel ?? "" : ""),
24097
+ resolved: resolved3
23677
24098
  };
23678
24099
  }
23679
24100
  function SpinWheelInApp(props) {
@@ -32532,8 +32953,8 @@ function EngagePromptProvider({
32532
32953
  __DEV__ && logger.debug("[Engage] no eligible in-app message to render");
32533
32954
  return false;
32534
32955
  }
32535
- const resolved = selectInAppVariant(message, getDeviceId());
32536
- activeInAppVariantRef.current = message.experiment?.enabled ? resolved : null;
32956
+ const resolved3 = selectInAppVariant(message, getDeviceId());
32957
+ activeInAppVariantRef.current = message.experiment?.enabled ? resolved3 : null;
32537
32958
  store.recordImpression(message.id, Date.now(), message.revision);
32538
32959
  setActiveInApp(message);
32539
32960
  return true;
@@ -33241,9 +33662,22 @@ function ScaleBunDebugRoot({
33241
33662
  screen = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
33242
33663
  } catch {
33243
33664
  }
33665
+ let ui;
33666
+ try {
33667
+ const {
33668
+ uiStateSignature: uiStateSignature2
33669
+ } = (init_uiState(), __toCommonJS(uiState_exports));
33670
+ ui = uiStateSignature2();
33671
+ } catch {
33672
+ }
33244
33673
  emitAutomaticEvent("element_interacted", {
33245
33674
  gesture_type: gestureType,
33246
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,
33247
33681
  // testID/nativeID is an author-controlled stable identifier.
33248
33682
  // Accessibility labels and rendered text are deliberately omitted.
33249
33683
  target_id: targetInfo?.testId,