@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
@@ -92,9 +92,9 @@ var init_internalLogger = __esm({
92
92
  }
93
93
  /** Set level from the public string name used in config (e.g. 'warn'). */
94
94
  setLevelByName(name) {
95
- const resolved = LEVEL_BY_NAME[name];
96
- if (resolved !== void 0) {
97
- this.level = resolved;
95
+ const resolved3 = LEVEL_BY_NAME[name];
96
+ if (resolved3 !== void 0) {
97
+ this.level = resolved3;
98
98
  }
99
99
  }
100
100
  /** Current numeric level — exposed for callers that branch on dev verbosity. */
@@ -600,7 +600,7 @@ var SDK_VERSION;
600
600
  var init_version = __esm({
601
601
  "lib/module/core/constants/version.js"() {
602
602
  "use strict";
603
- SDK_VERSION = "1.10.5";
603
+ SDK_VERSION = "1.10.7";
604
604
  }
605
605
  });
606
606
 
@@ -642,6 +642,15 @@ function staticDeviceContext() {
642
642
  if (c.Model) out.device_model = String(c.Model);
643
643
  if (c.Manufacturer) out.manufacturer = String(c.Manufacturer);
644
644
  else if (c.Brand) out.manufacturer = String(c.Brand);
645
+ else if (out.platform_os === "ios") out.manufacturer = "Apple";
646
+ return out;
647
+ }
648
+ function nativeDeviceContext(info) {
649
+ const out = {};
650
+ if (!info) return out;
651
+ if (info.osVersion) out.os_version = info.osVersion;
652
+ if (info.deviceModel) out.device_model = info.deviceModel;
653
+ if (info.manufacturer) out.manufacturer = info.manufacturer;
645
654
  return out;
646
655
  }
647
656
  function mergeDeviceContext(configured, detected = staticDeviceContext()) {
@@ -6528,6 +6537,27 @@ var init_EventTracker = __esm({
6528
6537
  }
6529
6538
  this.persistQueue();
6530
6539
  }
6540
+ /**
6541
+ * Fold in device facts that were not knowable at init.
6542
+ *
6543
+ * `Platform.constants` answers Android synchronously, so its model and manufacturer ride the
6544
+ * very first event. iOS exposes neither there — only the native bridge knows, and the bridge is
6545
+ * async and may not be installed at all. Without this the iOS half of every install reported no
6546
+ * model whatsoever.
6547
+ *
6548
+ * `buildEnvelope` reads `cfg.context` per event, so applying it here reaches every event from
6549
+ * the next one onward. Detected keys WIN over whatever the integrator configured, for the same
6550
+ * reason `mergeDeviceContext` inverts the usual precedence: a measurement must not be
6551
+ * overridable by a guess. Empty patches are ignored so a bridge that answered with nothing
6552
+ * cannot blank a value the static path already found.
6553
+ */
6554
+ applyDetectedContext(patch) {
6555
+ if (!patch || Object.keys(patch).length === 0) return;
6556
+ this.cfg.context = {
6557
+ ...this.cfg.context ?? {},
6558
+ ...patch
6559
+ };
6560
+ }
6531
6561
  // ─── internals ─────────────────────────────────────────────────────────────
6532
6562
  buildEnvelope(eventName, properties) {
6533
6563
  const ctx = this.cfg.context ?? {};
@@ -6877,10 +6907,249 @@ var init_deviceAttributes = __esm({
6877
6907
  }
6878
6908
  });
6879
6909
 
6910
+ // lib/module/features/ota/crypto/loadEd25519.js
6911
+ function loadEd25519() {
6912
+ try {
6913
+ const mod = require("@noble/ed25519");
6914
+ const ed = mod?.default ?? mod;
6915
+ if (typeof ed?.verifyAsync === "function" || typeof ed?.verify === "function") {
6916
+ return ed;
6917
+ }
6918
+ return null;
6919
+ } catch {
6920
+ return null;
6921
+ }
6922
+ }
6923
+ var init_loadEd25519 = __esm({
6924
+ "lib/module/features/ota/crypto/loadEd25519.js"() {
6925
+ "use strict";
6926
+ }
6927
+ });
6928
+
6929
+ // lib/module/features/ota/crypto/loadSha512.js
6930
+ function loadSha512() {
6931
+ try {
6932
+ const mod = require("@noble/hashes/sha2.js");
6933
+ const sha512 = mod?.sha512 ?? mod?.default?.sha512;
6934
+ return typeof sha512 === "function" ? sha512 : null;
6935
+ } catch {
6936
+ return null;
6937
+ }
6938
+ }
6939
+ var init_loadSha512 = __esm({
6940
+ "lib/module/features/ota/crypto/loadSha512.js"() {
6941
+ "use strict";
6942
+ }
6943
+ });
6944
+
6945
+ // lib/module/features/ota/crypto/builtinVerifier.js
6946
+ var builtinVerifier_exports = {};
6947
+ __export(builtinVerifier_exports, {
6948
+ _resetBuiltinVerifier: () => _resetBuiltinVerifier,
6949
+ decodeSignature: () => decodeSignature,
6950
+ getBuiltinVerifier: () => getBuiltinVerifier
6951
+ });
6952
+ function hexToBytes(hex) {
6953
+ const clean2 = hex.trim().toLowerCase().replace(/^0x/, "");
6954
+ if (clean2.length === 0 || clean2.length % 2 !== 0 || /[^0-9a-f]/.test(clean2)) return null;
6955
+ const out = new Uint8Array(clean2.length / 2);
6956
+ for (let i = 0; i < out.length; i++) {
6957
+ out[i] = parseInt(clean2.slice(i * 2, i * 2 + 2), 16);
6958
+ }
6959
+ return out;
6960
+ }
6961
+ function base64ToBytes2(b64) {
6962
+ const s = b64.trim().replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
6963
+ if (s.length === 0) return null;
6964
+ const out = [];
6965
+ let buffer = 0;
6966
+ let bits = 0;
6967
+ for (const ch of s) {
6968
+ const idx = B64_ALPHABET2.indexOf(ch);
6969
+ if (idx === -1) return null;
6970
+ buffer = buffer << 6 | idx;
6971
+ bits += 6;
6972
+ if (bits >= 8) {
6973
+ bits -= 8;
6974
+ out.push(buffer >> bits & 255);
6975
+ }
6976
+ }
6977
+ return Uint8Array.from(out);
6978
+ }
6979
+ function decodeSignature(sig) {
6980
+ const trimmed = sig.trim();
6981
+ if (/^(0x)?[0-9a-fA-F]{128}$/.test(trimmed)) {
6982
+ const hex2 = hexToBytes(trimmed);
6983
+ if (hex2 && hex2.length === ED25519_SIGNATURE_BYTES) return hex2;
6984
+ }
6985
+ const b64 = base64ToBytes2(trimmed);
6986
+ if (b64 && b64.length === ED25519_SIGNATURE_BYTES) return b64;
6987
+ const hex = hexToBytes(trimmed);
6988
+ if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
6989
+ return null;
6990
+ }
6991
+ function wireSha512(ed, sha512) {
6992
+ const concat2 = (parts) => {
6993
+ if (parts.length === 1) return parts[0];
6994
+ let total = 0;
6995
+ for (const p of parts) total += p.length;
6996
+ const out = new Uint8Array(total);
6997
+ let off = 0;
6998
+ for (const p of parts) {
6999
+ out.set(p, off);
7000
+ off += p.length;
7001
+ }
7002
+ return out;
7003
+ };
7004
+ const sync = (...msgs) => sha512(concat2(msgs));
7005
+ const asyncFn = async (...msgs) => sha512(concat2(msgs));
7006
+ let wired = false;
7007
+ try {
7008
+ if (ed.hashes && typeof ed.hashes === "object") {
7009
+ if (typeof ed.hashes.sha512 !== "function") ed.hashes.sha512 = sync;
7010
+ if (typeof ed.hashes.sha512Async !== "function") ed.hashes.sha512Async = asyncFn;
7011
+ wired = typeof ed.hashes.sha512 === "function";
7012
+ }
7013
+ } catch {
7014
+ }
7015
+ try {
7016
+ if (ed.etc && typeof ed.etc === "object") {
7017
+ if (typeof ed.etc.sha512Sync !== "function") ed.etc.sha512Sync = sync;
7018
+ if (typeof ed.etc.sha512Async !== "function") ed.etc.sha512Async = asyncFn;
7019
+ wired = wired || typeof ed.etc.sha512Sync === "function";
7020
+ }
7021
+ } catch {
7022
+ }
7023
+ return wired;
7024
+ }
7025
+ function getBuiltinVerifier() {
7026
+ if (resolved !== void 0) return resolved;
7027
+ const ed = loadEd25519();
7028
+ const sha512 = loadSha512();
7029
+ if (!ed || !sha512) {
7030
+ resolved = null;
7031
+ return null;
7032
+ }
7033
+ if (!wireSha512(ed, sha512)) {
7034
+ logger.error("[OTA] @noble/ed25519 is installed but its SHA-512 provider could not be wired (unrecognised version shape). Supply `ota.verifySignature` yourself, or pin a @noble/ed25519 version this SDK supports.");
7035
+ resolved = null;
7036
+ return null;
7037
+ }
7038
+ resolved = async ({
7039
+ messageHex,
7040
+ signature,
7041
+ publicKey
7042
+ }) => {
7043
+ const message = hexToBytes(messageHex);
7044
+ const pub = hexToBytes(publicKey);
7045
+ const sig = decodeSignature(signature);
7046
+ if (!message) {
7047
+ logger.error("[OTA] Bundle SHA-256 is not valid hex \u2014 refusing to stage.");
7048
+ return false;
7049
+ }
7050
+ if (!pub || pub.length !== ED25519_PUBLIC_KEY_BYTES) {
7051
+ logger.error(`[OTA] ota.publicSigningKey must be ${ED25519_PUBLIC_KEY_BYTES} bytes of hex (${ED25519_PUBLIC_KEY_BYTES * 2} characters) \u2014 got ${pub ? `${pub.length} bytes` : "a value that is not hex"}.`);
7052
+ return false;
7053
+ }
7054
+ if (!sig) {
7055
+ logger.error("[OTA] Bundle signature is not a 64-byte hex/base64 value.");
7056
+ return false;
7057
+ }
7058
+ try {
7059
+ if (typeof ed.verify === "function") return ed.verify(sig, message, pub);
7060
+ if (typeof ed.verifyAsync === "function") return await ed.verifyAsync(sig, message, pub);
7061
+ return false;
7062
+ } catch {
7063
+ return false;
7064
+ }
7065
+ };
7066
+ __DEV__ && logger.debug("[OTA] Using built-in ed25519 verifier (@noble/ed25519 detected).");
7067
+ return resolved;
7068
+ }
7069
+ function _resetBuiltinVerifier() {
7070
+ resolved = void 0;
7071
+ }
7072
+ var ED25519_SIGNATURE_BYTES, ED25519_PUBLIC_KEY_BYTES, B64_ALPHABET2, resolved;
7073
+ var init_builtinVerifier = __esm({
7074
+ "lib/module/features/ota/crypto/builtinVerifier.js"() {
7075
+ "use strict";
7076
+ init_loadEd25519();
7077
+ init_loadSha512();
7078
+ init_internalLogger();
7079
+ ED25519_SIGNATURE_BYTES = 64;
7080
+ ED25519_PUBLIC_KEY_BYTES = 32;
7081
+ B64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7082
+ }
7083
+ });
7084
+
7085
+ // lib/module/features/ota/crypto/nativeVerifier.js
7086
+ function bytesToHex(bytes) {
7087
+ let out = "";
7088
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
7089
+ return out;
7090
+ }
7091
+ function normalizeHex(value, expectedChars) {
7092
+ const clean2 = value.trim().toLowerCase().replace(/^0x/, "");
7093
+ if (clean2.length !== expectedChars || /[^0-9a-f]/.test(clean2)) return null;
7094
+ return clean2;
7095
+ }
7096
+ function getNativeVerifier() {
7097
+ if (resolved2 !== void 0) return resolved2;
7098
+ const native = NativeScaleBunOta_default;
7099
+ if (!native || typeof native.verifyEd25519 !== "function") {
7100
+ resolved2 = null;
7101
+ return null;
7102
+ }
7103
+ resolved2 = async ({
7104
+ messageHex,
7105
+ signature,
7106
+ publicKey
7107
+ }) => {
7108
+ const message = normalizeHex(messageHex, 64);
7109
+ const key = normalizeHex(publicKey, ED25519_PUBLIC_KEY_HEX_CHARS);
7110
+ const sigBytes = decodeSignature(signature);
7111
+ if (!message || !key || !sigBytes) {
7112
+ logger.error("[OTA] Signature inputs malformed (bundle hash, signature, or public key) \u2014 refusing to stage.");
7113
+ return false;
7114
+ }
7115
+ let verdict;
7116
+ try {
7117
+ verdict = await native.verifyEd25519(message, bytesToHex(sigBytes), key);
7118
+ } catch (err) {
7119
+ logger.warn(`[OTA] Native ed25519 verify failed to run: ${err?.message ?? err}`);
7120
+ verdict = "unavailable";
7121
+ }
7122
+ if (verdict === "valid") return true;
7123
+ if (verdict === "invalid") return false;
7124
+ const builtin = getBuiltinVerifier();
7125
+ if (builtin) {
7126
+ return builtin({
7127
+ messageHex,
7128
+ signature,
7129
+ publicKey
7130
+ });
7131
+ }
7132
+ throw new Error("ed25519 unavailable: this OS has no native implementation (Android < 13) and the optional peers @noble/ed25519 + @noble/hashes are not installed.");
7133
+ };
7134
+ __DEV__ && logger.debug("[OTA] Using native ed25519 verifier (platform crypto).");
7135
+ return resolved2;
7136
+ }
7137
+ var ED25519_PUBLIC_KEY_HEX_CHARS, resolved2;
7138
+ var init_nativeVerifier = __esm({
7139
+ "lib/module/features/ota/crypto/nativeVerifier.js"() {
7140
+ "use strict";
7141
+ init_NativeScaleBunOta();
7142
+ init_internalLogger();
7143
+ init_builtinVerifier();
7144
+ ED25519_PUBLIC_KEY_HEX_CHARS = 64;
7145
+ }
7146
+ });
7147
+
6880
7148
  // lib/module/features/ota/signature.js
6881
7149
  async function verifyBundleSignature(bundleSha256, signature, config) {
6882
- const publicKey = config?.publicKey;
6883
- if (!publicKey) {
7150
+ const rawKey = config?.publicKey;
7151
+ const keys = (Array.isArray(rawKey) ? rawKey : rawKey ? [rawKey] : []).filter((k) => typeof k === "string" && k.trim().length > 0);
7152
+ if (rawKey === void 0 || rawKey === "" || rawKey === null) {
6884
7153
  if (!warnedNotConfigured) {
6885
7154
  warnedNotConfigured = true;
6886
7155
  logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
@@ -6890,6 +7159,13 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
6890
7159
  reason: "not_configured"
6891
7160
  };
6892
7161
  }
7162
+ if (keys.length === 0) {
7163
+ logger.error("[OTA] Signing key config resolves to zero usable keys (empty array or blank strings) \u2014 refusing to stage. Remove the config to disable signing, or pin at least one real key.");
7164
+ return {
7165
+ ok: false,
7166
+ reason: "no_keys"
7167
+ };
7168
+ }
6893
7169
  if (!signature) {
6894
7170
  logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
6895
7171
  return {
@@ -6897,43 +7173,52 @@ async function verifyBundleSignature(bundleSha256, signature, config) {
6897
7173
  reason: "missing_signature"
6898
7174
  };
6899
7175
  }
6900
- if (typeof config?.verifier !== "function") {
6901
- logger.error("[OTA] A signing key is configured but no signature verifier is available \u2014 refusing to stage. Provide `ota.verifySignature` so signatures can be checked.");
7176
+ const verifier = typeof config?.verifier === "function" ? config.verifier : getNativeVerifier() ?? getBuiltinVerifier();
7177
+ if (!verifier) {
7178
+ logger.error("[OTA] A signing key is configured but no signature verifier is available \u2014 refusing to stage. Either install the optional peers `@noble/ed25519` and `@noble/hashes` (the SDK then verifies with no further code), or provide your own `ota.verifySignature`.");
6902
7179
  return {
6903
7180
  ok: false,
6904
7181
  reason: "no_verifier"
6905
7182
  };
6906
7183
  }
6907
- try {
6908
- const valid = await config.verifier({
6909
- messageHex: bundleSha256,
6910
- signature,
6911
- publicKey
6912
- });
6913
- if (!valid) {
6914
- logger.error("[OTA] Bundle signature is INVALID \u2014 refusing to stage.");
6915
- return {
6916
- ok: false,
6917
- reason: "invalid_signature"
7184
+ let sawThrow = false;
7185
+ let sawCompletion = false;
7186
+ for (const key of keys) {
7187
+ try {
7188
+ const valid = await verifier({
7189
+ messageHex: bundleSha256,
7190
+ signature,
7191
+ publicKey: key
7192
+ });
7193
+ sawCompletion = true;
7194
+ if (valid) return {
7195
+ ok: true,
7196
+ reason: "verified"
6918
7197
  };
7198
+ } catch (err) {
7199
+ sawThrow = true;
7200
+ logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
6919
7201
  }
6920
- return {
6921
- ok: true,
6922
- reason: "verified"
6923
- };
6924
- } catch (err) {
6925
- logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
7202
+ }
7203
+ if (sawThrow && !sawCompletion) {
6926
7204
  return {
6927
7205
  ok: false,
6928
7206
  reason: "verifier_threw"
6929
7207
  };
6930
7208
  }
7209
+ logger.error(keys.length > 1 ? `[OTA] Bundle signature is INVALID under all ${keys.length} pinned keys \u2014 refusing to stage.` : "[OTA] Bundle signature is INVALID \u2014 refusing to stage.");
7210
+ return {
7211
+ ok: false,
7212
+ reason: "invalid_signature"
7213
+ };
6931
7214
  }
6932
7215
  var warnedNotConfigured;
6933
7216
  var init_signature = __esm({
6934
7217
  "lib/module/features/ota/signature.js"() {
6935
7218
  "use strict";
6936
7219
  init_internalLogger();
7220
+ init_builtinVerifier();
7221
+ init_nativeVerifier();
6937
7222
  warnedNotConfigured = false;
6938
7223
  }
6939
7224
  });
@@ -7753,6 +8038,35 @@ var init_OtaOrchestrator = __esm({
7753
8038
  }
7754
8039
  });
7755
8040
 
8041
+ // lib/module/features/journey/uiState.js
8042
+ var uiState_exports = {};
8043
+ __export(uiState_exports, {
8044
+ clearUiState: () => clearUiState,
8045
+ setUiState: () => setUiState,
8046
+ uiStateSignature: () => uiStateSignature
8047
+ });
8048
+ function setUiState(name, value) {
8049
+ const n = clean(name);
8050
+ const v = clean(value);
8051
+ if (n && v) declared.set(n, v);
8052
+ }
8053
+ function clearUiState(name) {
8054
+ if (name === void 0) declared.clear();
8055
+ else declared.delete(clean(name));
8056
+ }
8057
+ function uiStateSignature() {
8058
+ if (!declared.size) return void 0;
8059
+ return [...declared.keys()].sort().map((k) => `${k}:${declared.get(k)}`).join(";").slice(0, 96);
8060
+ }
8061
+ var declared, clean;
8062
+ var init_uiState = __esm({
8063
+ "lib/module/features/journey/uiState.js"() {
8064
+ "use strict";
8065
+ declared = /* @__PURE__ */ new Map();
8066
+ clean = (s) => typeof s === "string" ? s.replace(/[;:|]/g, "").trim().slice(0, 32) : "";
8067
+ }
8068
+ });
8069
+
7756
8070
  // lib/module/features/engage/gestureTriggerDetector.js
7757
8071
  var gestureTriggerDetector_exports = {};
7758
8072
  __export(gestureTriggerDetector_exports, {
@@ -9720,6 +10034,14 @@ var SHAPE = {
9720
10034
  kind: "str",
9721
10035
  opt: true
9722
10036
  },
10037
+ // Rotation support: several pinned keys, any of which may verify a
10038
+ // bundle. Ship a build pinning [old, new], re-sign server-side with
10039
+ // new, drop old next release — no emergency store submission when a
10040
+ // key must be replaced. Merged with publicSigningKey by the facade.
10041
+ publicSigningKeys: {
10042
+ kind: "strArr",
10043
+ opt: true
10044
+ },
9723
10045
  mandatoryBlocksUi: bool(false)
9724
10046
  }
9725
10047
  }
@@ -9755,7 +10077,7 @@ function parseField(field, input, path, issues) {
9755
10077
  value: field.def,
9756
10078
  present: true
9757
10079
  };
9758
- return fail(field.kind === "bool" ? "Expected boolean, received undefined" : field.kind === "num" ? "Expected number, received undefined" : field.kind === "str" ? "Expected string, received undefined" : field.kind === "enum" ? "Invalid enum value. Expected " + field.values.map((v) => `'${v}'`).join(" | ") : "Expected object, received undefined");
10080
+ return fail(field.kind === "bool" ? "Expected boolean, received undefined" : field.kind === "num" ? "Expected number, received undefined" : field.kind === "str" ? "Expected string, received undefined" : field.kind === "strArr" ? "Expected array, received undefined" : field.kind === "enum" ? "Invalid enum value. Expected " + field.values.map((v) => `'${v}'`).join(" | ") : "Expected object, received undefined");
9759
10081
  }
9760
10082
  switch (field.kind) {
9761
10083
  case "any":
@@ -9781,6 +10103,27 @@ function parseField(field, input, path, issues) {
9781
10103
  value: input,
9782
10104
  present: true
9783
10105
  };
10106
+ case "strArr": {
10107
+ if (!Array.isArray(input)) return fail(`Expected array, received ${typeofName(input)}`);
10108
+ let allOk = true;
10109
+ for (let i = 0; i < input.length; i++) {
10110
+ if (typeof input[i] !== "string") {
10111
+ issues.push({
10112
+ path: [...path, i],
10113
+ message: `Expected string, received ${typeofName(input[i])}`
10114
+ });
10115
+ allOk = false;
10116
+ }
10117
+ }
10118
+ return allOk ? {
10119
+ ok: true,
10120
+ value: [...input],
10121
+ present: true
10122
+ } : {
10123
+ ok: false,
10124
+ present: true
10125
+ };
10126
+ }
9784
10127
  case "num":
9785
10128
  if (typeof input !== "number" || Number.isNaN(input)) {
9786
10129
  return fail(`Expected number, received ${typeofName(input)}`);
@@ -12428,16 +12771,16 @@ function base64ToBytes(base64) {
12428
12771
  return decodeManual(base64);
12429
12772
  }
12430
12773
  function decodeManual(base64) {
12431
- const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
12432
- const pad = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
12433
- const outLen = Math.floor(clean.length * 3 / 4) - pad;
12774
+ const clean2 = base64.replace(/[^A-Za-z0-9+/=]/g, "");
12775
+ const pad = clean2.endsWith("==") ? 2 : clean2.endsWith("=") ? 1 : 0;
12776
+ const outLen = Math.floor(clean2.length * 3 / 4) - pad;
12434
12777
  const out = new Uint8Array(outLen);
12435
12778
  let p = 0;
12436
- for (let i = 0; i < clean.length; i += 4) {
12437
- const c0 = B64_ALPHABET.indexOf(clean[i]);
12438
- const c1 = B64_ALPHABET.indexOf(clean[i + 1]);
12439
- const c2 = B64_ALPHABET.indexOf(clean[i + 2]);
12440
- const c3 = B64_ALPHABET.indexOf(clean[i + 3]);
12779
+ for (let i = 0; i < clean2.length; i += 4) {
12780
+ const c0 = B64_ALPHABET.indexOf(clean2[i]);
12781
+ const c1 = B64_ALPHABET.indexOf(clean2[i + 1]);
12782
+ const c2 = B64_ALPHABET.indexOf(clean2[i + 2]);
12783
+ const c3 = B64_ALPHABET.indexOf(clean2[i + 3]);
12441
12784
  const n = c0 << 18 | c1 << 12 | (c2 & 63) << 6 | c3 & 63;
12442
12785
  if (p < outLen) out[p++] = n >> 16 & 255;
12443
12786
  if (p < outLen) out[p++] = n >> 8 & 255;
@@ -14198,6 +14541,7 @@ var SDKBootstrapper = class {
14198
14541
 
14199
14542
  // lib/module/public/ScaleBunFacade.js
14200
14543
  init_EventTracker();
14544
+ init_bridgeAdapter();
14201
14545
  init_automaticEvents();
14202
14546
 
14203
14547
  // lib/module/analytics/eventLane.js
@@ -16623,12 +16967,17 @@ var ScaleBunFacade = class {
16623
16967
  /**
16624
16968
  * Boot the OTA orchestrator when the init config asks for it.
16625
16969
  *
16626
- * `publicSigningKey` is honoured here so signature enforcement is reachable
16627
- * from configuration alone. The SDK ships no ed25519 implementation, so a
16628
- * host that pins a key must also supply `ota.verifySignature`; pinning a key
16629
- * without a verifier is fail-CLOSED by design (signature.ts) an update
16630
- * that cannot be verified is not installed. We say that out loud rather than
16631
- * letting the app discover it as a silent no-update condition.
16970
+ * `publicSigningKey` / `publicSigningKeys` are honoured here so signature
16971
+ * enforcement is reachable from configuration alone. The list form exists
16972
+ * for key ROTATION: a build pinning [old, new] keeps verifying while the
16973
+ * server moves to the new key, so replacing a key never needs an emergency
16974
+ * store release. Both fields merge (deduplicated) into one pinned set.
16975
+ *
16976
+ * Verification comes from `ota.verifySignature` when supplied, else the
16977
+ * built-in @noble-based verifier (optional peers). Pinning keys with
16978
+ * NEITHER available is fail-CLOSED by design (signature.ts) — an update
16979
+ * that cannot be verified is not installed. We say that out loud rather
16980
+ * than letting the app discover it as a silent no-update condition.
16632
16981
  */
16633
16982
  _maybeStartOta(rawConfig) {
16634
16983
  const ota = rawConfig?.ota;
@@ -16637,15 +16986,25 @@ var ScaleBunFacade = class {
16637
16986
  const {
16638
16987
  otaOrchestrator: otaOrchestrator2
16639
16988
  } = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
16640
- const publicKey = ota.publicSigningKey;
16989
+ const rawSingle = ota.publicSigningKey;
16990
+ const rawList = ota.publicSigningKeys;
16991
+ const publicKeys = Array.from(new Set([...typeof rawSingle === "string" && rawSingle ? [rawSingle] : [], ...Array.isArray(rawList) ? rawList : []].filter((k) => typeof k === "string" && k.trim().length > 0)));
16992
+ const signingRequested = publicKeys.length > 0 || Array.isArray(rawList);
16641
16993
  const verifier = ota.verifySignature;
16642
- if (publicKey && typeof verifier !== "function") {
16643
- logger.warn("[ScaleBun] ota.publicSigningKey is set but ota.verifySignature is not a function. Signature checking is fail-closed: updates will be REJECTED until a verifier is supplied.");
16994
+ if (signingRequested && typeof verifier !== "function") {
16995
+ const {
16996
+ getBuiltinVerifier: getBuiltinVerifier2
16997
+ } = (init_builtinVerifier(), __toCommonJS(builtinVerifier_exports));
16998
+ if (!getBuiltinVerifier2()) {
16999
+ logger.warn("[ScaleBun] A signing key is pinned but no signature verifier is available. Signature checking is fail-closed: updates will be REJECTED until one exists. Install the optional peers `@noble/ed25519` + `@noble/hashes` (no further code needed), or supply `ota.verifySignature`.");
17000
+ }
16644
17001
  }
16645
17002
  otaOrchestrator2.init({
16646
- ...publicKey ? {
17003
+ ...signingRequested ? {
16647
17004
  signature: {
16648
- publicKey,
17005
+ // Single key stays a plain string — the shape every
17006
+ // existing consumer and test already handles.
17007
+ publicKey: publicKeys.length === 1 ? publicKeys[0] : publicKeys,
16649
17008
  verifier
16650
17009
  }
16651
17010
  } : {},
@@ -16714,6 +17073,8 @@ var ScaleBunFacade = class {
16714
17073
  * the whole time. The SDK knows the device; the integrator is guessing at it.
16715
17074
  * Everything outside the detected keys is still theirs.
16716
17075
  */
17076
+ // Everything Platform.constants knows. On iOS that is neither the model nor the
17077
+ // manufacturer, so the bridge completes it below once it answers.
16717
17078
  context: mergeDeviceContext(rawConfig?.context),
16718
17079
  // Use the same foreground id as replay/journey capture. The
16719
17080
  // callback is resolved per event because the session subsystem
@@ -16727,6 +17088,7 @@ var ScaleBunFacade = class {
16727
17088
  });
16728
17089
  this._eventTracker.start();
16729
17090
  __DEV__ && logger.info("[ScaleBun] Envelope event tracking started \u2192 /v1/batch.");
17091
+ this._applyNativeDeviceContext(this._eventTracker);
16730
17092
  this._captureInstallReferrer(this._eventTracker);
16731
17093
  const tracker = this._eventTracker;
16732
17094
  this._configManager = new ConfigManager({
@@ -16745,6 +17107,25 @@ var ScaleBunFacade = class {
16745
17107
  logger.warn("[ScaleBun] Failed to start envelope event tracking:", err?.message);
16746
17108
  }
16747
17109
  }
17110
+ /**
17111
+ * Fold the native bridge's device facts into the analytics context once it answers.
17112
+ *
17113
+ * Detected values never overwrite a populated one with an empty one — `nativeDeviceContext`
17114
+ * drops absent keys and `applyDetectedContext` ignores an empty patch — so a bridge that
17115
+ * cannot answer leaves whatever `Platform.constants` already found.
17116
+ */
17117
+ _applyNativeDeviceContext(tracker) {
17118
+ Promise.resolve().then(() => bridgeAdapter.getDeviceInfo()).then((info) => {
17119
+ if (!info) return;
17120
+ tracker.applyDetectedContext(nativeDeviceContext({
17121
+ osVersion: info.osVersion,
17122
+ deviceModel: info.deviceModel
17123
+ // iOS ships no manufacturer over the bridge; it is a constant there and the
17124
+ // static path already set it. Android's arrives via Platform.constants.
17125
+ }));
17126
+ }).catch(() => {
17127
+ });
17128
+ }
16748
17129
  /**
16749
17130
  * Android-only: read the Play Install Referrer once per install and route it into
16750
17131
  * attribution. `scalebun_click_id` → the deterministic click path; the full
@@ -16835,6 +17216,46 @@ var ScaleBunFacade = class {
16835
17216
  };
16836
17217
  }
16837
17218
  /** Convenience: emit a Phase 1 purchase event (revenue + currency + transaction_id). */
17219
+ /**
17220
+ * Declare which UI state the user is looking at, so taps are attributed to the surface they
17221
+ * happened on rather than averaged across every variant of the screen.
17222
+ *
17223
+ * ScaleBun.setUiState('filter-sheet', 'open');
17224
+ * ScaleBun.setUiState('checkout-step', 'payment');
17225
+ *
17226
+ * Call it when the state CHANGES — the value is read at tap time, so it only has to be right by
17227
+ * the time the next tap lands. On React Native this is the ONLY source of UI state: unlike the web,
17228
+ * there is no queryable accessibility tree that says a sheet is up, and guessing would produce a
17229
+ * state key that is right on some apps and silently wrong on others.
17230
+ *
17231
+ * Names and values are identifiers, not content: they become query keys in the dashboard, so a
17232
+ * person's name or a cart total does not belong in one. `;`, `:` and `|` are stripped and both
17233
+ * halves are capped at 32 characters.
17234
+ */
17235
+ setUiState(name, value) {
17236
+ try {
17237
+ const {
17238
+ setUiState: setUiState2
17239
+ } = (init_uiState(), __toCommonJS(uiState_exports));
17240
+ setUiState2(name, value);
17241
+ } catch {
17242
+ }
17243
+ }
17244
+ /**
17245
+ * Stop reporting a UI state dimension — or, with no argument, all of them.
17246
+ *
17247
+ * Clear on screen unmount. A declaration left behind follows the user onto a surface where it means
17248
+ * nothing, and every tap there is filed under a state that was not on screen.
17249
+ */
17250
+ clearUiState(name) {
17251
+ try {
17252
+ const {
17253
+ clearUiState: clearUiState2
17254
+ } = (init_uiState(), __toCommonJS(uiState_exports));
17255
+ clearUiState2(name);
17256
+ } catch {
17257
+ }
17258
+ }
16838
17259
  trackPurchase(input) {
16839
17260
  noThrow(() => this._eventTracker?.trackPurchase(input));
16840
17261
  }
@@ -20458,11 +20879,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
20458
20879
  function resolveWheelOutcome(segments, source) {
20459
20880
  const identity = wheelOutcomeIdentity(source);
20460
20881
  const index = findWheelOutcomeIndex(segments, identity);
20461
- const resolved = index >= 0;
20882
+ const resolved3 = index >= 0;
20462
20883
  return {
20463
- index: resolved ? index : 0,
20464
- label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
20465
- resolved
20884
+ index: resolved3 ? index : 0,
20885
+ label: identity.resultLabel || (resolved3 ? segments[index]?.fullLabel ?? "" : ""),
20886
+ resolved: resolved3
20466
20887
  };
20467
20888
  }
20468
20889
  function SpinWheelInApp(props) {
@@ -29321,8 +29742,8 @@ function EngagePromptProvider({
29321
29742
  __DEV__ && logger.debug("[Engage] no eligible in-app message to render");
29322
29743
  return false;
29323
29744
  }
29324
- const resolved = selectInAppVariant(message, getDeviceId());
29325
- activeInAppVariantRef.current = message.experiment?.enabled ? resolved : null;
29745
+ const resolved3 = selectInAppVariant(message, getDeviceId());
29746
+ activeInAppVariantRef.current = message.experiment?.enabled ? resolved3 : null;
29326
29747
  store.recordImpression(message.id, Date.now(), message.revision);
29327
29748
  setActiveInApp(message);
29328
29749
  return true;
@@ -30030,9 +30451,22 @@ function ScaleBunDebugRoot({
30030
30451
  screen = AutoScreenDetector2.getInstance().getCurrentScreen() || void 0;
30031
30452
  } catch {
30032
30453
  }
30454
+ let ui;
30455
+ try {
30456
+ const {
30457
+ uiStateSignature: uiStateSignature2
30458
+ } = (init_uiState(), __toCommonJS(uiState_exports));
30459
+ ui = uiStateSignature2();
30460
+ } catch {
30461
+ }
30033
30462
  emitAutomaticEvent("element_interacted", {
30034
30463
  gesture_type: gestureType,
30035
30464
  screen_name: screen,
30465
+ /* THIS MAP ENUMERATES. A field added to the payload and forgotten here reaches the
30466
+ backend as undefined with no error anywhere — the recurring defect class in this
30467
+ codebase. `ui` is the key the grid aggregate reads for state, byte-identical to
30468
+ the web SDK's, so one dashboard control queries both platforms. */
30469
+ ui,
30036
30470
  // testID/nativeID is an author-controlled stable identifier.
30037
30471
  // Accessibility labels and rendered text are deliberately omitted.
30038
30472
  target_id: targetInfo?.testId,