@scalebun/react-native 1.13.0 → 2.0.1
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.
- package/android/build.gradle +8 -0
- package/android/src/androidTest/java/com/scalebun/rn/ota/ScaleBunOtaVerifierInstrumentedTest.kt +112 -0
- package/android/src/main/java/com/scalebun/rn/ota/BundleDownloader.kt +10 -0
- package/android/src/main/java/com/scalebun/rn/ota/DeviceIntegrity.kt +75 -0
- package/android/src/main/java/com/scalebun/rn/ota/OtaProtocol.kt +244 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaKeyRegistry.kt +100 -0
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaModule.kt +132 -62
- package/android/src/main/java/com/scalebun/rn/ota/ScaleBunOtaReleaseVerifier.kt +129 -0
- package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +80 -1
- package/android/src/main/java/com/scalebun/rn/ota/TlsPinning.kt +118 -0
- package/android/src/oldarch/java/com/scalebun/rn/ota/ScaleBunOtaSpec.kt +6 -8
- package/dist/scalebun.full.js +964 -533
- package/dist/scalebun.slim.js +962 -531
- package/ios/Ota/OtaProtocol.swift +242 -0
- package/ios/Ota/OtaSlotManager.swift +26 -0
- package/ios/Ota/ScaleBunOtaBridge.mm +6 -3
- package/ios/Ota/ScaleBunOtaKeyRegistry.swift +147 -0
- package/ios/Ota/ScaleBunOtaModule.swift +125 -53
- package/ios/Ota/ScaleBunOtaReleaseVerifier.swift +115 -0
- package/ios/Ota/ScaleBunOtaVerifierTests.swift +92 -0
- package/ios/ReplaySdk.swift +65 -2
- package/lib/commonjs/bootstrap/SDKBootstrapper.js +30 -0
- package/lib/commonjs/core/config/schema.js +19 -12
- package/lib/commonjs/core/constants/version.js +1 -1
- package/lib/commonjs/features/journey/ScaleBunDebugRoot.js +293 -5
- package/lib/commonjs/features/journey/ScaleBunScrollView.js +28 -0
- package/lib/commonjs/features/journey/autoInstrumentScroll.js +161 -0
- package/lib/commonjs/features/journey/interactionProtocol.js +131 -10
- package/lib/commonjs/features/journey/scrollContext.js +146 -0
- package/lib/commonjs/features/journey/targetGeometry.js +164 -0
- package/lib/commonjs/features/journey/touchTarget.js +152 -24
- package/lib/commonjs/features/journey/uiState.js +81 -1
- package/lib/commonjs/features/navigation/AutoScreenDetector.js +74 -5
- package/lib/commonjs/features/network/NetworkFeature.js +39 -5
- package/lib/commonjs/features/network/thirdParty.js +90 -0
- package/lib/commonjs/features/ota/OtaOrchestrator.js +175 -80
- package/lib/commonjs/features/session/SessionManager.js +63 -0
- package/lib/commonjs/public/ScaleBunFacade.js +23 -57
- package/lib/module/bootstrap/SDKBootstrapper.js +30 -0
- package/lib/module/core/config/schema.js +19 -12
- package/lib/module/core/constants/version.js +1 -1
- package/lib/module/features/journey/ScaleBunDebugRoot.js +295 -7
- package/lib/module/features/journey/ScaleBunScrollView.js +28 -0
- package/lib/module/features/journey/autoInstrumentScroll.js +155 -0
- package/lib/module/features/journey/interactionProtocol.js +128 -8
- package/lib/module/features/journey/scrollContext.js +135 -0
- package/lib/module/features/journey/targetGeometry.js +155 -0
- package/lib/module/features/journey/touchTarget.js +151 -24
- package/lib/module/features/journey/uiState.js +78 -1
- package/lib/module/features/navigation/AutoScreenDetector.js +74 -5
- package/lib/module/features/network/NetworkFeature.js +38 -4
- package/lib/module/features/network/thirdParty.js +83 -0
- package/lib/module/features/ota/OtaOrchestrator.js +175 -80
- package/lib/module/features/session/SessionManager.js +63 -0
- package/lib/module/public/ScaleBunFacade.js +23 -57
- package/lib/typescript/core/config/schema.d.ts +2 -3
- package/lib/typescript/core/constants/version.d.ts +1 -1
- package/lib/typescript/features/journey/autoInstrumentScroll.d.ts +46 -0
- package/lib/typescript/features/journey/interactionProtocol.d.ts +139 -1
- package/lib/typescript/features/journey/scrollContext.d.ts +85 -0
- package/lib/typescript/features/journey/targetGeometry.d.ts +114 -0
- package/lib/typescript/features/journey/touchTarget.d.ts +148 -15
- package/lib/typescript/features/journey/uiState.d.ts +39 -0
- package/lib/typescript/features/navigation/AutoScreenDetector.d.ts +48 -2
- package/lib/typescript/features/network/index.d.ts +18 -0
- package/lib/typescript/features/network/thirdParty.d.ts +65 -0
- package/lib/typescript/features/ota/OtaOrchestrator.d.ts +12 -8
- package/lib/typescript/features/ota/OtaTypes.d.ts +22 -0
- package/lib/typescript/features/session/SessionManager.d.ts +55 -0
- package/lib/typescript/public/ScaleBunFacade.d.ts +5 -11
- package/lib/typescript/public/types.d.ts +10 -0
- package/lib/typescript/specs/NativeScaleBunOta.d.ts +32 -20
- package/package.json +3 -13
- package/scalebun-react-native.podspec +4 -0
- package/src/bootstrap/SDKBootstrapper.ts +40 -0
- package/src/core/config/schema.ts +21 -9
- package/src/core/constants/version.ts +1 -1
- package/src/features/journey/ScaleBunDebugRoot.tsx +288 -7
- package/src/features/journey/ScaleBunScrollView.tsx +29 -0
- package/src/features/journey/autoInstrumentScroll.ts +155 -0
- package/src/features/journey/interactionProtocol.ts +188 -10
- package/src/features/journey/scrollContext.ts +137 -0
- package/src/features/journey/targetGeometry.ts +176 -0
- package/src/features/journey/touchTarget.ts +237 -26
- package/src/features/journey/uiState.ts +80 -1
- package/src/features/navigation/AutoScreenDetector.ts +65 -5
- package/src/features/network/NetworkFeature.ts +41 -4
- package/src/features/network/index.ts +18 -0
- package/src/features/network/thirdParty.ts +92 -0
- package/src/features/ota/OtaOrchestrator.ts +190 -90
- package/src/features/ota/OtaTypes.ts +23 -1
- package/src/features/session/SessionManager.ts +113 -0
- package/src/public/ScaleBunFacade.ts +23 -69
- package/src/public/types.ts +10 -0
- package/src/specs/NativeScaleBunOta.ts +35 -20
- package/lib/commonjs/features/ota/crypto/builtinVerifier.js +0 -248
- package/lib/commonjs/features/ota/crypto/loadEd25519.js +0 -40
- package/lib/commonjs/features/ota/crypto/loadSha512.js +0 -40
- package/lib/commonjs/features/ota/crypto/nativeVerifier.js +0 -121
- package/lib/commonjs/features/ota/signature.js +0 -175
- package/lib/module/features/ota/crypto/builtinVerifier.js +0 -240
- package/lib/module/features/ota/crypto/loadEd25519.js +0 -34
- package/lib/module/features/ota/crypto/loadSha512.js +0 -34
- package/lib/module/features/ota/crypto/nativeVerifier.js +0 -113
- package/lib/module/features/ota/signature.js +0 -169
- package/lib/typescript/features/ota/crypto/builtinVerifier.d.ts +0 -53
- package/lib/typescript/features/ota/crypto/loadEd25519.d.ts +0 -30
- package/lib/typescript/features/ota/crypto/loadSha512.d.ts +0 -15
- package/lib/typescript/features/ota/crypto/nativeVerifier.d.ts +0 -35
- package/lib/typescript/features/ota/signature.d.ts +0 -81
- package/src/features/ota/crypto/builtinVerifier.ts +0 -257
- package/src/features/ota/crypto/loadEd25519.ts +0 -41
- package/src/features/ota/crypto/loadSha512.ts +0 -35
- package/src/features/ota/crypto/nativeVerifier.ts +0 -117
- package/src/features/ota/signature.ts +0 -206
package/dist/scalebun.slim.js
CHANGED
|
@@ -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
|
|
96
|
-
if (
|
|
97
|
-
this.level =
|
|
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 = "
|
|
603
|
+
SDK_VERSION = "2.0.1";
|
|
604
604
|
}
|
|
605
605
|
});
|
|
606
606
|
|
|
@@ -1353,6 +1353,15 @@ var init_AutoScreenDetector = __esm({
|
|
|
1353
1353
|
_manualScreen = null;
|
|
1354
1354
|
_manualScreenTs = 0;
|
|
1355
1355
|
_autoScreen = null;
|
|
1356
|
+
/**
|
|
1357
|
+
* When `_autoScreen` was last written.
|
|
1358
|
+
*
|
|
1359
|
+
* Needed so `getCurrentScreen()` can prefer whichever source spoke MOST RECENTLY.
|
|
1360
|
+
* Without a timestamp the only comparison available was "is the manual value still
|
|
1361
|
+
* fresh", which is a different question and gave the wrong answer — see the note on
|
|
1362
|
+
* `_currentScreen()`.
|
|
1363
|
+
*/
|
|
1364
|
+
_autoScreenTs = 0;
|
|
1356
1365
|
/** `name#key` of the last auto-detected screen VISIT — see _extractActiveVisit. */
|
|
1357
1366
|
_autoVisitSig = null;
|
|
1358
1367
|
_navigationRef = null;
|
|
@@ -1420,6 +1429,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
1420
1429
|
if (this.isManualScreenActive()) return;
|
|
1421
1430
|
if (name !== this._autoScreen) {
|
|
1422
1431
|
this._autoScreen = name;
|
|
1432
|
+
this._autoScreenTs = Date.now();
|
|
1423
1433
|
this._autoVisitSig = name;
|
|
1424
1434
|
this._emitScreenChange(name, "heuristic");
|
|
1425
1435
|
}
|
|
@@ -1427,8 +1437,19 @@ var init_AutoScreenDetector = __esm({
|
|
|
1427
1437
|
}
|
|
1428
1438
|
}
|
|
1429
1439
|
/**
|
|
1430
|
-
*
|
|
1431
|
-
*
|
|
1440
|
+
* Should a WRITER defer to the manual screen right now?
|
|
1441
|
+
*
|
|
1442
|
+
* THIS IS A PRECEDENCE GATE, NOT A VALIDITY TEST, and conflating the two was a real
|
|
1443
|
+
* bug. Its job is what the cooldown constant says: for a short window after a manual
|
|
1444
|
+
* beacon fires, an auto source must not overwrite it — the poller runs every 1500 ms
|
|
1445
|
+
* and would otherwise clobber a fresh declaration with whatever it inferred.
|
|
1446
|
+
*
|
|
1447
|
+
* `getCurrentScreen()` used to reuse this as "is the manual screen still the current
|
|
1448
|
+
* screen", which made a declared screen READABLE for only 2000 ms. Nothing called
|
|
1449
|
+
* `setManualScreen` in production, so the effect was latent — but the moment the
|
|
1450
|
+
* `<ScaleBunScreen>` beacon was wired to it (RN-8), every tap more than two seconds
|
|
1451
|
+
* after a screen mounted would have lost its name, which is very nearly every tap. A
|
|
1452
|
+
* fix that introduced that would have been worse than the gap it closed.
|
|
1432
1453
|
*/
|
|
1433
1454
|
isManualScreenActive() {
|
|
1434
1455
|
if (!this._manualScreen) return false;
|
|
@@ -1474,6 +1495,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
1474
1495
|
const visit = this._extractActiveVisit(state);
|
|
1475
1496
|
if (visit && this._visitSig(visit) !== this._autoVisitSig) {
|
|
1476
1497
|
this._autoScreen = visit.name;
|
|
1498
|
+
this._autoScreenTs = Date.now();
|
|
1477
1499
|
this._autoVisitSig = this._visitSig(visit);
|
|
1478
1500
|
this._emitScreenChange(visit.name, "navRef");
|
|
1479
1501
|
}
|
|
@@ -1492,6 +1514,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
1492
1514
|
const visit = this._extractActiveVisit(state);
|
|
1493
1515
|
if (visit) {
|
|
1494
1516
|
this._autoScreen = visit.name;
|
|
1517
|
+
this._autoScreenTs = Date.now();
|
|
1495
1518
|
this._autoVisitSig = this._visitSig(visit);
|
|
1496
1519
|
this._emitScreenChange(visit.name, "navRef");
|
|
1497
1520
|
}
|
|
@@ -1522,6 +1545,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
1522
1545
|
const visit = this._extractActiveVisit(state);
|
|
1523
1546
|
if (visit && this._visitSig(visit) !== this._autoVisitSig) {
|
|
1524
1547
|
this._autoScreen = visit.name;
|
|
1548
|
+
this._autoScreenTs = Date.now();
|
|
1525
1549
|
this._autoVisitSig = this._visitSig(visit);
|
|
1526
1550
|
this._emitScreenChange(visit.name, "heuristic");
|
|
1527
1551
|
}
|
|
@@ -1620,7 +1644,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
1620
1644
|
if (!reactNav || reactNav.__scalebunAutoHooked) return;
|
|
1621
1645
|
const detector = this;
|
|
1622
1646
|
let installed2 = 0;
|
|
1623
|
-
const
|
|
1647
|
+
const redefine2 = (key, value) => {
|
|
1624
1648
|
try {
|
|
1625
1649
|
Object.defineProperty(reactNav, key, {
|
|
1626
1650
|
value,
|
|
@@ -1670,7 +1694,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
1670
1694
|
});
|
|
1671
1695
|
});
|
|
1672
1696
|
Wrapped.displayName = "ScaleBunNavigationContainer";
|
|
1673
|
-
if (
|
|
1697
|
+
if (redefine2("NavigationContainer", Wrapped)) installed2++;
|
|
1674
1698
|
}
|
|
1675
1699
|
if (typeof reactNav.createNavigationContainerRef === "function") {
|
|
1676
1700
|
const originalCreate = reactNav.createNavigationContainerRef;
|
|
@@ -1682,7 +1706,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
1682
1706
|
}
|
|
1683
1707
|
return ref;
|
|
1684
1708
|
};
|
|
1685
|
-
if (
|
|
1709
|
+
if (redefine2("createNavigationContainerRef", patchedCreate)) installed2++;
|
|
1686
1710
|
}
|
|
1687
1711
|
if (typeof reactNav.useNavigationContainerRef === "function") {
|
|
1688
1712
|
const originalHook = reactNav.useNavigationContainerRef;
|
|
@@ -1694,7 +1718,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
1694
1718
|
}
|
|
1695
1719
|
return ref;
|
|
1696
1720
|
};
|
|
1697
|
-
if (
|
|
1721
|
+
if (redefine2("useNavigationContainerRef", patchedHook)) installed2++;
|
|
1698
1722
|
}
|
|
1699
1723
|
this._autoHookInstalled = installed2 > 0;
|
|
1700
1724
|
try {
|
|
@@ -1743,15 +1767,58 @@ var init_AutoScreenDetector = __esm({
|
|
|
1743
1767
|
this._listeners = [];
|
|
1744
1768
|
this._manualScreen = null;
|
|
1745
1769
|
this._autoScreen = null;
|
|
1770
|
+
this._autoScreenTs = 0;
|
|
1746
1771
|
this._navigationRef = null;
|
|
1747
1772
|
_instance = null;
|
|
1748
1773
|
}
|
|
1749
1774
|
/** Get the current best-known screen name */
|
|
1750
1775
|
getCurrentScreen() {
|
|
1751
|
-
|
|
1752
|
-
|
|
1776
|
+
return this._currentScreen().name;
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* WHERE the current screen name came from, or 'none'.
|
|
1780
|
+
*
|
|
1781
|
+
* Recorded on every interaction (RN-8) so low screen coverage is DIAGNOSABLE rather
|
|
1782
|
+
* than merely visible. Production shows 13.8% of Android interactions carrying a
|
|
1783
|
+
* screen and 0.5% on iOS, and those numbers cannot today distinguish three different
|
|
1784
|
+
* causes: the app never instrumented anything, the app instrumented manually and the
|
|
1785
|
+
* value never reached interactions, or auto-detection ran and failed. Those have
|
|
1786
|
+
* different owners and different fixes, and guessing between them is how a P0 stays
|
|
1787
|
+
* open.
|
|
1788
|
+
*/
|
|
1789
|
+
getCurrentScreenSource() {
|
|
1790
|
+
return this._currentScreen().source;
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* LAST WRITER WINS between the manual beacon and auto-detection.
|
|
1794
|
+
*
|
|
1795
|
+
* A screen declared by `<ScaleBunScreen name>` stays current until something else
|
|
1796
|
+
* declares a different one — that is what mounting a screen means. It does not
|
|
1797
|
+
* expire, which is the correction here: the previous rule returned the manual name
|
|
1798
|
+
* only while it was under 2000 ms old and then silently fell back to the auto value,
|
|
1799
|
+
* which in a manually-instrumented app is `null`.
|
|
1800
|
+
*
|
|
1801
|
+
* Comparing timestamps rather than hard-coding a priority also handles the real
|
|
1802
|
+
* interleaving: an app can use React Navigation for most screens AND drop a manual
|
|
1803
|
+
* beacon on one modal, and whichever spoke last is the truth.
|
|
1804
|
+
*/
|
|
1805
|
+
_currentScreen() {
|
|
1806
|
+
const manual = this._manualScreen;
|
|
1807
|
+
const auto = this._autoScreen;
|
|
1808
|
+
if (manual && (!auto || this._manualScreenTs >= this._autoScreenTs)) {
|
|
1809
|
+
return {
|
|
1810
|
+
name: manual,
|
|
1811
|
+
source: "manual"
|
|
1812
|
+
};
|
|
1753
1813
|
}
|
|
1754
|
-
return
|
|
1814
|
+
if (auto) return {
|
|
1815
|
+
name: auto,
|
|
1816
|
+
source: "navigation"
|
|
1817
|
+
};
|
|
1818
|
+
return {
|
|
1819
|
+
name: null,
|
|
1820
|
+
source: "none"
|
|
1821
|
+
};
|
|
1755
1822
|
}
|
|
1756
1823
|
// ─── Internal ───────────────────────────────────────────────────────
|
|
1757
1824
|
_emitScreenChange(name, source) {
|
|
@@ -3632,17 +3699,54 @@ var init_automaticEvents = __esm({
|
|
|
3632
3699
|
function generateInteractionId() {
|
|
3633
3700
|
return `ixj-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
3634
3701
|
}
|
|
3635
|
-
function
|
|
3636
|
-
|
|
3637
|
-
let
|
|
3638
|
-
|
|
3702
|
+
function correlateInteraction(starts, occurredAt, toleranceMs = CORRELATION_TOLERANCE_MS, marginMs = CORRELATION_MARGIN_MS) {
|
|
3703
|
+
const candidates = [];
|
|
3704
|
+
for (let i = 0; i < starts.length; i++) {
|
|
3705
|
+
const start = starts[i];
|
|
3706
|
+
if (!start) continue;
|
|
3639
3707
|
const delta = Math.abs(start.occurredAt - occurredAt);
|
|
3640
|
-
if (delta
|
|
3641
|
-
|
|
3642
|
-
|
|
3708
|
+
if (delta <= toleranceMs) candidates.push({
|
|
3709
|
+
index: i,
|
|
3710
|
+
delta
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3713
|
+
if (candidates.length === 0) {
|
|
3714
|
+
return {
|
|
3715
|
+
index: -1,
|
|
3716
|
+
outcome: "none",
|
|
3717
|
+
deltaMs: null,
|
|
3718
|
+
runnerUpDeltaMs: null
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
candidates.sort((a, b) => a.delta - b.delta || a.index - b.index);
|
|
3722
|
+
const best = candidates[0];
|
|
3723
|
+
const runnerUp = candidates[1];
|
|
3724
|
+
if (runnerUp && runnerUp.delta - best.delta < marginMs) {
|
|
3725
|
+
return {
|
|
3726
|
+
index: -1,
|
|
3727
|
+
outcome: "ambiguous",
|
|
3728
|
+
deltaMs: best.delta,
|
|
3729
|
+
runnerUpDeltaMs: runnerUp.delta
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
return {
|
|
3733
|
+
start: starts[best.index],
|
|
3734
|
+
index: best.index,
|
|
3735
|
+
outcome: "exact",
|
|
3736
|
+
deltaMs: best.delta,
|
|
3737
|
+
runnerUpDeltaMs: runnerUp ? runnerUp.delta : null
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
function pruneInteractionStarts(starts, now2, toleranceMs = CORRELATION_TOLERANCE_MS) {
|
|
3741
|
+
let removed = 0;
|
|
3742
|
+
for (let i = starts.length - 1; i >= 0; i--) {
|
|
3743
|
+
const start = starts[i];
|
|
3744
|
+
if (!start || now2 - start.occurredAt > toleranceMs) {
|
|
3745
|
+
starts.splice(i, 1);
|
|
3746
|
+
removed++;
|
|
3643
3747
|
}
|
|
3644
3748
|
}
|
|
3645
|
-
return
|
|
3749
|
+
return removed;
|
|
3646
3750
|
}
|
|
3647
3751
|
function automaticInteractionProperties(payload, screenName, canonicalMirror) {
|
|
3648
3752
|
return {
|
|
@@ -3660,11 +3764,13 @@ function automaticInteractionProperties(payload, screenName, canonicalMirror) {
|
|
|
3660
3764
|
canonical_mirror: canonicalMirror || void 0
|
|
3661
3765
|
};
|
|
3662
3766
|
}
|
|
3663
|
-
var INTERACTION_PROTOCOL_VERSION;
|
|
3767
|
+
var INTERACTION_PROTOCOL_VERSION, CORRELATION_TOLERANCE_MS, CORRELATION_MARGIN_MS;
|
|
3664
3768
|
var init_interactionProtocol = __esm({
|
|
3665
3769
|
"lib/module/features/journey/interactionProtocol.js"() {
|
|
3666
3770
|
"use strict";
|
|
3667
3771
|
INTERACTION_PROTOCOL_VERSION = 1;
|
|
3772
|
+
CORRELATION_TOLERANCE_MS = 1500;
|
|
3773
|
+
CORRELATION_MARGIN_MS = 100;
|
|
3668
3774
|
}
|
|
3669
3775
|
});
|
|
3670
3776
|
|
|
@@ -4407,6 +4513,69 @@ var init_SessionManager = __esm({
|
|
|
4407
4513
|
state_status: stateStatus,
|
|
4408
4514
|
ui: details?.ui,
|
|
4409
4515
|
target_id: details?.targetId,
|
|
4516
|
+
/**
|
|
4517
|
+
* RN-6 — shipped with the row so the join is auditable instead of assumed.
|
|
4518
|
+
*
|
|
4519
|
+
* Without it, an interaction with no `target_id` is ambiguous in the worst way:
|
|
4520
|
+
* it could mean the control genuinely had no identity, or that the correlation
|
|
4521
|
+
* refused and the identity exists but could not be attributed. Those are
|
|
4522
|
+
* different defects with different owners, and the accounting lane cannot give
|
|
4523
|
+
* an honest unplaceable REASON without knowing which.
|
|
4524
|
+
*
|
|
4525
|
+
* Undefined on the JS-fallback path (no native event, so nothing was joined),
|
|
4526
|
+
* which is itself the correct answer there rather than a fabricated 'exact'.
|
|
4527
|
+
*/
|
|
4528
|
+
correlation: details?.correlation,
|
|
4529
|
+
correlation_delta_ms: details?.correlationDeltaMs,
|
|
4530
|
+
correlation_runner_up_delta_ms: details?.correlationRunnerUpDeltaMs,
|
|
4531
|
+
/**
|
|
4532
|
+
* RN-1/RN-2 — stored alongside `target_id` so a reader can tell an authored
|
|
4533
|
+
* identifier from a structural inference. 'path' means the app authored no testID
|
|
4534
|
+
* and the identity was derived from component names, which is useful but will
|
|
4535
|
+
* change under refactoring; a dashboard that presents the two identically invites
|
|
4536
|
+
* someone to trust a key that is about to move.
|
|
4537
|
+
*/
|
|
4538
|
+
target_source: details?.targetSource,
|
|
4539
|
+
/**
|
|
4540
|
+
* RN-8 — stored so "13.8% of interactions have a screen" becomes answerable.
|
|
4541
|
+
* 'none' means the app instrumented nothing the SDK could see; 'manual' means a
|
|
4542
|
+
* <ScaleBunScreen> beacon; 'navigation' means the React Navigation hook. Three
|
|
4543
|
+
* different owners behind one number that today has none.
|
|
4544
|
+
*/
|
|
4545
|
+
screen_source: details?.screenSource,
|
|
4546
|
+
/**
|
|
4547
|
+
* RN-13 — what makes "the heat is on this control" checkable instead of assumed.
|
|
4548
|
+
* Absent when the control could not be measured synchronously, which is a fact worth
|
|
4549
|
+
* storing: an unmeasurable control and a mis-placed tap are different findings.
|
|
4550
|
+
*/
|
|
4551
|
+
target_rect: details?.targetRect,
|
|
4552
|
+
/**
|
|
4553
|
+
* RN-3 — the backend already reads scrollX/scrollY off the payload into the fact
|
|
4554
|
+
* table's own columns (it has done since the web SDK, which sends `sx`/`sy`), so
|
|
4555
|
+
* these need no migration. Mobile simply never sent them.
|
|
4556
|
+
*
|
|
4557
|
+
* `scroll_source` says whether an offset was available at all: 'none' means the app
|
|
4558
|
+
* uses a plain <ScrollView> rather than the SDK's wrapper, which is a coverage gap in
|
|
4559
|
+
* the integration and not a tap at the top of a list.
|
|
4560
|
+
*/
|
|
4561
|
+
scrollX: details?.scrollX,
|
|
4562
|
+
scrollY: details?.scrollY,
|
|
4563
|
+
scroll_source: details?.scrollSource,
|
|
4564
|
+
/**
|
|
4565
|
+
* RN-4 — what stops a recycled native view merging two different rows into one
|
|
4566
|
+
* hotspot. The key is the app's own keyExtractor output, so it is stable across
|
|
4567
|
+
* scrolling and across sessions; the index is carried so a recycled slot (same index,
|
|
4568
|
+
* different key) can be proven rather than inferred.
|
|
4569
|
+
*/
|
|
4570
|
+
item_key: details?.itemKey,
|
|
4571
|
+
item_index: details?.itemIndex,
|
|
4572
|
+
/**
|
|
4573
|
+
* RN-15 — where in the control, not just which control. Turns "they tapped Add to
|
|
4574
|
+
* Cart" into "they tapped its left edge", which is what tells you whether a hit area
|
|
4575
|
+
* is right. Outside 0..1 is a hitSlop tap and is kept as such.
|
|
4576
|
+
*/
|
|
4577
|
+
local_u: details?.localU,
|
|
4578
|
+
local_v: details?.localV,
|
|
4410
4579
|
gestureType,
|
|
4411
4580
|
x: details?.x,
|
|
4412
4581
|
y: details?.y,
|
|
@@ -5498,10 +5667,10 @@ function requestExport(transport, enabled, presetOrOptions) {
|
|
|
5498
5667
|
return;
|
|
5499
5668
|
}
|
|
5500
5669
|
const timer = setTimeout(() => {
|
|
5501
|
-
const
|
|
5502
|
-
if (
|
|
5670
|
+
const pending5 = _pendingExports.get(requestId);
|
|
5671
|
+
if (pending5) {
|
|
5503
5672
|
_pendingExports.delete(requestId);
|
|
5504
|
-
|
|
5673
|
+
pending5.resolve({
|
|
5505
5674
|
success: false,
|
|
5506
5675
|
error: `Export timed out after ${EXPORT_TIMEOUT_MS / 1e3}s`
|
|
5507
5676
|
});
|
|
@@ -5543,14 +5712,14 @@ function handleExportResult(args) {
|
|
|
5543
5712
|
try {
|
|
5544
5713
|
const requestId = args.requestId;
|
|
5545
5714
|
if (!requestId) return;
|
|
5546
|
-
const
|
|
5547
|
-
if (!
|
|
5715
|
+
const pending5 = _pendingExports.get(requestId);
|
|
5716
|
+
if (!pending5) {
|
|
5548
5717
|
logger.warn(`[ExportBridge] Received result for unknown requestId: ${requestId}`);
|
|
5549
5718
|
return;
|
|
5550
5719
|
}
|
|
5551
|
-
clearTimeout(
|
|
5720
|
+
clearTimeout(pending5.timer);
|
|
5552
5721
|
_pendingExports.delete(requestId);
|
|
5553
|
-
|
|
5722
|
+
pending5.resolve({
|
|
5554
5723
|
success: args.success ?? false,
|
|
5555
5724
|
filePath: args.filePath,
|
|
5556
5725
|
error: args.error,
|
|
@@ -5561,9 +5730,9 @@ function handleExportResult(args) {
|
|
|
5561
5730
|
}
|
|
5562
5731
|
}
|
|
5563
5732
|
function teardownExports() {
|
|
5564
|
-
for (const [,
|
|
5565
|
-
clearTimeout(
|
|
5566
|
-
|
|
5733
|
+
for (const [, pending5] of _pendingExports) {
|
|
5734
|
+
clearTimeout(pending5.timer);
|
|
5735
|
+
pending5.reject(new Error("Debug disabled \u2014 export cancelled"));
|
|
5567
5736
|
}
|
|
5568
5737
|
_pendingExports.clear();
|
|
5569
5738
|
}
|
|
@@ -6785,6 +6954,174 @@ var init_installationId = __esm({
|
|
|
6785
6954
|
}
|
|
6786
6955
|
});
|
|
6787
6956
|
|
|
6957
|
+
// lib/module/features/journey/scrollContext.js
|
|
6958
|
+
var scrollContext_exports = {};
|
|
6959
|
+
__export(scrollContext_exports, {
|
|
6960
|
+
SCROLL_OFFSET_TTL_MS: () => SCROLL_OFFSET_TTL_MS,
|
|
6961
|
+
clearScrollOffsets: () => clearScrollOffsets,
|
|
6962
|
+
nativeTagOf: () => nativeTagOf,
|
|
6963
|
+
nearestRecordedOffset: () => nearestRecordedOffset,
|
|
6964
|
+
recordScrollOffset: () => recordScrollOffset,
|
|
6965
|
+
trackedScrollerCount: () => trackedScrollerCount
|
|
6966
|
+
});
|
|
6967
|
+
function nativeTagOf(node) {
|
|
6968
|
+
if (!node || typeof node !== "object") return null;
|
|
6969
|
+
const n = node;
|
|
6970
|
+
const tag = typeof n.__nativeTag === "number" ? n.__nativeTag : n._nativeTag;
|
|
6971
|
+
return typeof tag === "number" && Number.isFinite(tag) ? tag : null;
|
|
6972
|
+
}
|
|
6973
|
+
function recordScrollOffset(tag, x, y, now2 = Date.now()) {
|
|
6974
|
+
if (tag === null || !Number.isFinite(x) || !Number.isFinite(y)) return;
|
|
6975
|
+
offsets.set(tag, {
|
|
6976
|
+
x,
|
|
6977
|
+
y,
|
|
6978
|
+
at: now2
|
|
6979
|
+
});
|
|
6980
|
+
if (offsets.size > MAX_TRACKED_SCROLLERS) {
|
|
6981
|
+
let oldestTag = null;
|
|
6982
|
+
let oldestAt = Infinity;
|
|
6983
|
+
for (const [k, v] of offsets) {
|
|
6984
|
+
if (v.at < oldestAt) {
|
|
6985
|
+
oldestAt = v.at;
|
|
6986
|
+
oldestTag = k;
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
if (oldestTag !== null) offsets.delete(oldestTag);
|
|
6990
|
+
}
|
|
6991
|
+
}
|
|
6992
|
+
function nearestRecordedOffset(ancestorTags, now2 = Date.now()) {
|
|
6993
|
+
for (const tag of ancestorTags) {
|
|
6994
|
+
if (tag === null) continue;
|
|
6995
|
+
const hit = offsets.get(tag);
|
|
6996
|
+
if (!hit) continue;
|
|
6997
|
+
if (now2 - hit.at > SCROLL_OFFSET_TTL_MS) {
|
|
6998
|
+
offsets.delete(tag);
|
|
6999
|
+
continue;
|
|
7000
|
+
}
|
|
7001
|
+
return {
|
|
7002
|
+
x: hit.x,
|
|
7003
|
+
y: hit.y,
|
|
7004
|
+
tag
|
|
7005
|
+
};
|
|
7006
|
+
}
|
|
7007
|
+
return null;
|
|
7008
|
+
}
|
|
7009
|
+
function clearScrollOffsets() {
|
|
7010
|
+
offsets.clear();
|
|
7011
|
+
}
|
|
7012
|
+
function trackedScrollerCount() {
|
|
7013
|
+
return offsets.size;
|
|
7014
|
+
}
|
|
7015
|
+
var SCROLL_OFFSET_TTL_MS, MAX_TRACKED_SCROLLERS, offsets;
|
|
7016
|
+
var init_scrollContext = __esm({
|
|
7017
|
+
"lib/module/features/journey/scrollContext.js"() {
|
|
7018
|
+
"use strict";
|
|
7019
|
+
SCROLL_OFFSET_TTL_MS = 5 * 6e4;
|
|
7020
|
+
MAX_TRACKED_SCROLLERS = 32;
|
|
7021
|
+
offsets = /* @__PURE__ */ new Map();
|
|
7022
|
+
}
|
|
7023
|
+
});
|
|
7024
|
+
|
|
7025
|
+
// lib/module/features/journey/autoInstrumentScroll.js
|
|
7026
|
+
var autoInstrumentScroll_exports = {};
|
|
7027
|
+
__export(autoInstrumentScroll_exports, {
|
|
7028
|
+
autoInstrumentScrollViews: () => autoInstrumentScrollViews,
|
|
7029
|
+
resetAutoInstrumentForTests: () => resetAutoInstrumentForTests
|
|
7030
|
+
});
|
|
7031
|
+
function redefine(target, key, value) {
|
|
7032
|
+
try {
|
|
7033
|
+
Object.defineProperty(target, key, {
|
|
7034
|
+
configurable: true,
|
|
7035
|
+
enumerable: true,
|
|
7036
|
+
writable: true,
|
|
7037
|
+
value
|
|
7038
|
+
});
|
|
7039
|
+
return target[key] === value;
|
|
7040
|
+
} catch {
|
|
7041
|
+
return false;
|
|
7042
|
+
}
|
|
7043
|
+
}
|
|
7044
|
+
function instrument(Original, React14) {
|
|
7045
|
+
const Wrapped = React14.forwardRef((props, ref) => {
|
|
7046
|
+
const onScroll = (event) => {
|
|
7047
|
+
try {
|
|
7048
|
+
const target = event?.nativeEvent?.target;
|
|
7049
|
+
const offset = event?.nativeEvent?.contentOffset;
|
|
7050
|
+
if (offset) {
|
|
7051
|
+
recordScrollOffset(typeof target === "number" ? target : nativeTagOf(target), offset.x, offset.y);
|
|
7052
|
+
}
|
|
7053
|
+
} catch {
|
|
7054
|
+
}
|
|
7055
|
+
try {
|
|
7056
|
+
props?.onScroll?.(event);
|
|
7057
|
+
} catch (e) {
|
|
7058
|
+
setTimeout(() => {
|
|
7059
|
+
throw e;
|
|
7060
|
+
}, 0);
|
|
7061
|
+
}
|
|
7062
|
+
};
|
|
7063
|
+
const throttle = props?.scrollEventThrottle ?? 16;
|
|
7064
|
+
return React14.createElement(Original, {
|
|
7065
|
+
...props,
|
|
7066
|
+
scrollEventThrottle: throttle,
|
|
7067
|
+
onScroll,
|
|
7068
|
+
ref
|
|
7069
|
+
});
|
|
7070
|
+
});
|
|
7071
|
+
Wrapped.displayName = "ScaleBunAutoScrollView";
|
|
7072
|
+
return Wrapped;
|
|
7073
|
+
}
|
|
7074
|
+
function autoInstrumentScrollViews() {
|
|
7075
|
+
if (alreadyPatched) return {
|
|
7076
|
+
installed: true
|
|
7077
|
+
};
|
|
7078
|
+
try {
|
|
7079
|
+
const RN = require("react-native");
|
|
7080
|
+
const React14 = require("react");
|
|
7081
|
+
const Original = RN.ScrollView;
|
|
7082
|
+
if (typeof Original !== "function" && typeof Original !== "object") {
|
|
7083
|
+
return {
|
|
7084
|
+
installed: false,
|
|
7085
|
+
reason: "react-native has no ScrollView export to wrap"
|
|
7086
|
+
};
|
|
7087
|
+
}
|
|
7088
|
+
if (Original.__scalebunInstrumented) {
|
|
7089
|
+
alreadyPatched = true;
|
|
7090
|
+
return {
|
|
7091
|
+
installed: true
|
|
7092
|
+
};
|
|
7093
|
+
}
|
|
7094
|
+
const Wrapped = instrument(Original, React14);
|
|
7095
|
+
Wrapped.__scalebunInstrumented = true;
|
|
7096
|
+
if (!redefine(RN, "ScrollView", Wrapped)) {
|
|
7097
|
+
return {
|
|
7098
|
+
installed: false,
|
|
7099
|
+
reason: "react-native's ScrollView export could not be replaced \u2014 it is a getter with no configurable descriptor on this bundler. Use ScaleBunScrollView explicitly instead."
|
|
7100
|
+
};
|
|
7101
|
+
}
|
|
7102
|
+
alreadyPatched = true;
|
|
7103
|
+
return {
|
|
7104
|
+
installed: true
|
|
7105
|
+
};
|
|
7106
|
+
} catch (e) {
|
|
7107
|
+
return {
|
|
7108
|
+
installed: false,
|
|
7109
|
+
reason: e?.message ?? "unknown failure"
|
|
7110
|
+
};
|
|
7111
|
+
}
|
|
7112
|
+
}
|
|
7113
|
+
function resetAutoInstrumentForTests() {
|
|
7114
|
+
alreadyPatched = false;
|
|
7115
|
+
}
|
|
7116
|
+
var alreadyPatched;
|
|
7117
|
+
var init_autoInstrumentScroll = __esm({
|
|
7118
|
+
"lib/module/features/journey/autoInstrumentScroll.js"() {
|
|
7119
|
+
"use strict";
|
|
7120
|
+
init_scrollContext();
|
|
7121
|
+
alreadyPatched = false;
|
|
7122
|
+
}
|
|
7123
|
+
});
|
|
7124
|
+
|
|
6788
7125
|
// lib/module/features/engage/engageSignals.js
|
|
6789
7126
|
var EngageSignalBus, engageSignals;
|
|
6790
7127
|
var init_engageSignals = __esm({
|
|
@@ -6993,322 +7330,6 @@ var init_deviceAttributes = __esm({
|
|
|
6993
7330
|
}
|
|
6994
7331
|
});
|
|
6995
7332
|
|
|
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
7333
|
// lib/module/features/ota/environment.js
|
|
7313
7334
|
function compare(a, b) {
|
|
7314
7335
|
if (a.major !== b.major) return a.major - b.major;
|
|
@@ -7536,10 +7557,10 @@ async function deliverOtaEvents(params) {
|
|
|
7536
7557
|
})
|
|
7537
7558
|
});
|
|
7538
7559
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
7539
|
-
|
|
7560
|
+
logger.debug(`[OTA] Delivered ${items.length} telemetry event(s)`);
|
|
7540
7561
|
} catch (err) {
|
|
7541
7562
|
for (const e of events) otaEventEmitter.emit(e);
|
|
7542
|
-
|
|
7563
|
+
logger.debug(`[OTA] Telemetry delivery failed (will retry next sync): ${err?.message}`);
|
|
7543
7564
|
}
|
|
7544
7565
|
}
|
|
7545
7566
|
var import_react_native33, PROGRESS_POST_INTERVAL_MS, DEFAULT_HEALTHY_AFTER_MS, OtaOrchestrator, otaOrchestrator;
|
|
@@ -7554,7 +7575,6 @@ var init_OtaOrchestrator = __esm({
|
|
|
7554
7575
|
init_geoCountry();
|
|
7555
7576
|
init_deviceAttributes();
|
|
7556
7577
|
init_installationId();
|
|
7557
|
-
init_signature();
|
|
7558
7578
|
init_environment();
|
|
7559
7579
|
init_retry();
|
|
7560
7580
|
init_StorageBackend();
|
|
@@ -7573,6 +7593,8 @@ var init_OtaOrchestrator = __esm({
|
|
|
7573
7593
|
return this.currentBundle;
|
|
7574
7594
|
}
|
|
7575
7595
|
isRestartRequiredState = false;
|
|
7596
|
+
/** Root/jailbreak indicators observed at init; empty when none found. */
|
|
7597
|
+
integrityIndicators = [];
|
|
7576
7598
|
healthyTimer = null;
|
|
7577
7599
|
bootGuardConfig = {};
|
|
7578
7600
|
environment = null;
|
|
@@ -7582,15 +7604,15 @@ var init_OtaOrchestrator = __esm({
|
|
|
7582
7604
|
/**
|
|
7583
7605
|
* Initialize the orchestrator. Checks for boot-guard recovery state.
|
|
7584
7606
|
*
|
|
7585
|
-
*
|
|
7586
|
-
*
|
|
7587
|
-
*
|
|
7607
|
+
* Signature enforcement is no longer a JS concern: signed releases are
|
|
7608
|
+
* verified and staged in NATIVE code against RSA keys embedded in the app
|
|
7609
|
+
* package (SB-OTA-RSA-SHA256-V1), and unsigned releases keep working on
|
|
7610
|
+
* SHA-256 integrity alone. There is nothing for the host to configure here.
|
|
7588
7611
|
*/
|
|
7589
7612
|
init(config) {
|
|
7590
7613
|
noThrow(() => {
|
|
7591
7614
|
if (this.initialized) {
|
|
7592
7615
|
this.bootGuardConfig = config ?? this.bootGuardConfig;
|
|
7593
|
-
this.signatureConfig = config?.signature ?? this.signatureConfig;
|
|
7594
7616
|
return;
|
|
7595
7617
|
}
|
|
7596
7618
|
if (!NativeScaleBunOta_default) {
|
|
@@ -7607,18 +7629,27 @@ var init_OtaOrchestrator = __esm({
|
|
|
7607
7629
|
logger.warn("[OTA] Running bridgeless but the React Native version could not be determined. If this app is below RN 0.76.1, OTA updates will install and never load.");
|
|
7608
7630
|
}
|
|
7609
7631
|
if (this.environment.requiresReactHostIntegration) {
|
|
7610
|
-
|
|
7632
|
+
logger.debug("[OTA] React Native >= 0.82 detected. The only valid Android integration is getDefaultReactHost(..., jsBundleFilePath = ScaleBunOtaModule.getJSBundleFile(ctx)) \u2014 ReactNativeHost and its getJSBundleFile() override no longer exist.");
|
|
7611
7633
|
}
|
|
7612
7634
|
this.initialized = true;
|
|
7613
7635
|
this.enabled = true;
|
|
7636
|
+
try {
|
|
7637
|
+
const raw = NativeScaleBunOta_default?.getDeviceIntegrity?.();
|
|
7638
|
+
this.integrityIndicators = raw ? JSON.parse(raw) : [];
|
|
7639
|
+
if (this.integrityIndicators.length > 0) {
|
|
7640
|
+
logger.warn(`[OTA] Device integrity indicators present: ${this.integrityIndicators.join(", ")}. Updates still install and are still signature-verified, but an installed bundle on this device can be modified by other privileged processes.`);
|
|
7641
|
+
}
|
|
7642
|
+
} catch {
|
|
7643
|
+
this.integrityIndicators = [];
|
|
7644
|
+
}
|
|
7614
7645
|
this.bootGuardConfig = config ?? {};
|
|
7615
|
-
this.
|
|
7616
|
-
__DEV__ && logger.debug(`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? "unknown"}${this.environment.bridgeless ? ", bridgeless" : ""}${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? "?"}` : ""})`);
|
|
7646
|
+
logger.debug(`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? "unknown"}${this.environment.bridgeless ? ", bridgeless" : ""}${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? "?"}` : ""})`);
|
|
7617
7647
|
const slotState = this.readSlotState();
|
|
7618
7648
|
this.hydrateCurrentBundleFromSlots(slotState);
|
|
7619
7649
|
void prefetchDeviceCountry();
|
|
7620
7650
|
this.checkBootGuardRecovery(slotState);
|
|
7621
7651
|
this.verifyRunningBundleIdentity();
|
|
7652
|
+
this.startBootGuardHeartbeat(slotState);
|
|
7622
7653
|
});
|
|
7623
7654
|
}
|
|
7624
7655
|
/**
|
|
@@ -7649,13 +7680,13 @@ var init_OtaOrchestrator = __esm({
|
|
|
7649
7680
|
releaseId: record4.releaseId ?? void 0,
|
|
7650
7681
|
sha256: record4.sha256
|
|
7651
7682
|
};
|
|
7652
|
-
|
|
7683
|
+
logger.debug(`[OTA] Running bundle v${record4.version} (${record4.bundleId})`);
|
|
7653
7684
|
return;
|
|
7654
7685
|
}
|
|
7655
7686
|
this.currentBundle = {
|
|
7656
7687
|
sha256: current.sha256
|
|
7657
7688
|
};
|
|
7658
|
-
|
|
7689
|
+
logger.debug(`[OTA] Running an OTA bundle (sha ${String(current.sha256).slice(0, 12)}\u2026) with no local install record.`);
|
|
7659
7690
|
} catch {
|
|
7660
7691
|
}
|
|
7661
7692
|
}
|
|
@@ -7685,7 +7716,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7685
7716
|
}
|
|
7686
7717
|
if (!record4.identityToken) return;
|
|
7687
7718
|
if (running === record4.identityToken) {
|
|
7688
|
-
|
|
7719
|
+
logger.debug("[OTA] Install verified \u2014 running bundle matches what was installed.");
|
|
7689
7720
|
this.retireIdentityToken(record4);
|
|
7690
7721
|
return;
|
|
7691
7722
|
}
|
|
@@ -7788,6 +7819,13 @@ var init_OtaOrchestrator = __esm({
|
|
|
7788
7819
|
isEnabled() {
|
|
7789
7820
|
return this.enabled;
|
|
7790
7821
|
}
|
|
7822
|
+
/**
|
|
7823
|
+
* Root/jailbreak indicators detected on this device, as stable reason strings.
|
|
7824
|
+
* Empty means nothing was detected — not proof the device is clean.
|
|
7825
|
+
*/
|
|
7826
|
+
deviceIntegrityIndicators() {
|
|
7827
|
+
return [...this.integrityIndicators];
|
|
7828
|
+
}
|
|
7791
7829
|
isRestartRequired() {
|
|
7792
7830
|
return this.isRestartRequiredState;
|
|
7793
7831
|
}
|
|
@@ -7799,12 +7837,22 @@ var init_OtaOrchestrator = __esm({
|
|
|
7799
7837
|
const env = this.environment ?? detectOtaEnvironment();
|
|
7800
7838
|
const mobileOS = resolveMobileOS();
|
|
7801
7839
|
if (!mobileOS) throw new Error("OTA is not supported on this platform");
|
|
7840
|
+
let trustedKeyIds;
|
|
7841
|
+
try {
|
|
7842
|
+
const raw = NativeScaleBunOta_default?.getTrustedKeyIds?.();
|
|
7843
|
+
if (raw) {
|
|
7844
|
+
const arr2 = JSON.parse(raw);
|
|
7845
|
+
if (Array.isArray(arr2) && arr2.length) trustedKeyIds = arr2.slice(0, 16);
|
|
7846
|
+
}
|
|
7847
|
+
} catch {
|
|
7848
|
+
}
|
|
7802
7849
|
const payload = {
|
|
7803
7850
|
appVersion: requestParams.appVersion,
|
|
7804
7851
|
platform: mobileOS,
|
|
7805
7852
|
installationId: requestParams.installationId,
|
|
7806
7853
|
currentBundleId: this.currentBundle?.id,
|
|
7807
7854
|
currentBundleHash: this.currentBundle?.sha256,
|
|
7855
|
+
trustedKeyIds,
|
|
7808
7856
|
channelName: requestParams.channelName,
|
|
7809
7857
|
// Host-supplied country wins; otherwise the cached edge-Worker lookup
|
|
7810
7858
|
// fills in (see geoCountry.ts). Undefined when neither knows — the
|
|
@@ -7871,7 +7919,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7871
7919
|
error: "OTA native module disabled or missing"
|
|
7872
7920
|
};
|
|
7873
7921
|
}
|
|
7874
|
-
|
|
7922
|
+
logger.debug("[OTA] Sync started\u2026");
|
|
7875
7923
|
if (this.currentBundle?.id) {
|
|
7876
7924
|
otaEventEmitter.emitSimple("CHECK", this.currentBundle.id, {
|
|
7877
7925
|
releaseId: this.currentBundle.releaseId,
|
|
@@ -7880,7 +7928,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
7880
7928
|
}
|
|
7881
7929
|
const checkRes = await this.checkForUpdate(params);
|
|
7882
7930
|
if (checkRes.action === "NONE") {
|
|
7883
|
-
|
|
7931
|
+
logger.debug("[OTA] App is up to date");
|
|
7884
7932
|
this.startBootGuardHeartbeat();
|
|
7885
7933
|
return {
|
|
7886
7934
|
status: "UP_TO_DATE"
|
|
@@ -7918,25 +7966,12 @@ var init_OtaOrchestrator = __esm({
|
|
|
7918
7966
|
releaseId: bundle.releaseId,
|
|
7919
7967
|
version: bundle.version
|
|
7920
7968
|
});
|
|
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
7969
|
otaEventEmitter.emitSimple("DOWNLOAD_STARTED", bundle.id, {
|
|
7935
7970
|
releaseId: bundle.releaseId,
|
|
7936
7971
|
version: bundle.version
|
|
7937
7972
|
});
|
|
7938
7973
|
const downloadStart = Date.now();
|
|
7939
|
-
|
|
7974
|
+
logger.debug(`[OTA] Downloading update v${bundle.version}\u2026`);
|
|
7940
7975
|
let lastPostAt = 0;
|
|
7941
7976
|
const postProgress = (bytesRead, status) => {
|
|
7942
7977
|
const total = bundle.size > 0 ? bundle.size : Math.max(bytesRead, 1);
|
|
@@ -7978,39 +8013,75 @@ var init_OtaOrchestrator = __esm({
|
|
|
7978
8013
|
}
|
|
7979
8014
|
});
|
|
7980
8015
|
let staged = false;
|
|
7981
|
-
if (bundle.
|
|
7982
|
-
|
|
8016
|
+
if (bundle.envelope) {
|
|
8017
|
+
const envelopeJson = JSON.stringify({
|
|
8018
|
+
...bundle.envelope,
|
|
8019
|
+
downloadUrl: bundle.url
|
|
8020
|
+
});
|
|
8021
|
+
let verdict;
|
|
7983
8022
|
try {
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
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`);
|
|
8023
|
+
const verdictJson = await NativeScaleBunOta_default.verifyAndStageRelease(envelopeJson);
|
|
8024
|
+
verdict = JSON.parse(verdictJson);
|
|
8025
|
+
} catch (err) {
|
|
8026
|
+
verdict = {
|
|
8027
|
+
ok: false,
|
|
8028
|
+
code: "verify_stage_threw",
|
|
8029
|
+
message: err?.message ?? String(err)
|
|
8030
|
+
};
|
|
7998
8031
|
}
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
label: `download v${bundle.version}`,
|
|
8010
|
-
attempts: 3
|
|
8032
|
+
if (!verdict.ok) {
|
|
8033
|
+
unsubscribeProgress();
|
|
8034
|
+
postProgress(0, "FAILED");
|
|
8035
|
+
logger.error(`[OTA] Native verify/stage refused the release: ${verdict.code ?? "unknown"}${verdict.message ? ` \u2014 ${verdict.message}` : ""}`);
|
|
8036
|
+
otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
|
|
8037
|
+
releaseId: bundle.releaseId,
|
|
8038
|
+
// `error` is mapped onto the wire `errorCode` by deliverOtaEvents —
|
|
8039
|
+
// carry the stable native verdict code there.
|
|
8040
|
+
error: verdict.code ?? "verify_and_stage_release_failed",
|
|
8041
|
+
version: bundle.version
|
|
8011
8042
|
});
|
|
8012
|
-
|
|
8013
|
-
|
|
8043
|
+
return {
|
|
8044
|
+
status: "ERROR",
|
|
8045
|
+
error: `Release verification failed (${verdict.code ?? "unknown"})`,
|
|
8046
|
+
bundle
|
|
8047
|
+
};
|
|
8048
|
+
}
|
|
8049
|
+
staged = true;
|
|
8050
|
+
} else {
|
|
8051
|
+
if (bundle.patch && NativeScaleBunOta_default.stagePatch) {
|
|
8052
|
+
logger.debug(`[OTA] Patch available (${(bundle.patch.size / 1024).toFixed(0)} KB vs ${(bundle.size / 1024).toFixed(0)} KB full) \u2014 trying patch\u2026`);
|
|
8053
|
+
try {
|
|
8054
|
+
staged = await NativeScaleBunOta_default.stagePatch(
|
|
8055
|
+
bundle.patch.url,
|
|
8056
|
+
bundle.patch.sha256,
|
|
8057
|
+
bundle.sha256
|
|
8058
|
+
// expected full-bundle hash after patching
|
|
8059
|
+
);
|
|
8060
|
+
if (staged) {
|
|
8061
|
+
patchUsed = true;
|
|
8062
|
+
logger.debug("[OTA] Patch applied successfully");
|
|
8063
|
+
} else {
|
|
8064
|
+
logger.warn("[OTA] Patch staging returned false \u2014 falling back to full bundle");
|
|
8065
|
+
}
|
|
8066
|
+
} catch (patchErr) {
|
|
8067
|
+
logger.warn(`[OTA] Patch staging failed: ${patchErr.message} \u2014 falling back to full bundle`);
|
|
8068
|
+
}
|
|
8069
|
+
}
|
|
8070
|
+
if (!staged) {
|
|
8071
|
+
try {
|
|
8072
|
+
staged = await retryWithBackoff(async () => {
|
|
8073
|
+
const ok = await NativeScaleBunOta_default.stageBundle(bundle.url, bundle.sha256);
|
|
8074
|
+
if (!ok) {
|
|
8075
|
+
throw new Error("stageBundle returned false");
|
|
8076
|
+
}
|
|
8077
|
+
return ok;
|
|
8078
|
+
}, {
|
|
8079
|
+
label: `download v${bundle.version}`,
|
|
8080
|
+
attempts: 3
|
|
8081
|
+
});
|
|
8082
|
+
} catch {
|
|
8083
|
+
staged = false;
|
|
8084
|
+
}
|
|
8014
8085
|
}
|
|
8015
8086
|
}
|
|
8016
8087
|
unsubscribeProgress();
|
|
@@ -8057,7 +8128,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
8057
8128
|
releaseId: bundle.releaseId,
|
|
8058
8129
|
version: bundle.version
|
|
8059
8130
|
});
|
|
8060
|
-
|
|
8131
|
+
logger.debug(`[OTA] Update v${bundle.version} installed successfully!`);
|
|
8061
8132
|
await deliverOtaEvents(params);
|
|
8062
8133
|
const installMode = bundle.installMode ?? "ON_NEXT_RESTART";
|
|
8063
8134
|
if (params.autoRestart || installMode === "IMMEDIATE") {
|
|
@@ -8088,24 +8159,23 @@ var init_OtaOrchestrator = __esm({
|
|
|
8088
8159
|
* native boot-guard marker. If JS crashes before the timer fires, the
|
|
8089
8160
|
* marker stays set and native code reverts on next cold start.
|
|
8090
8161
|
*/
|
|
8091
|
-
startBootGuardHeartbeat() {
|
|
8162
|
+
startBootGuardHeartbeat(preRead) {
|
|
8092
8163
|
if (!NativeScaleBunOta_default) return;
|
|
8093
8164
|
if (this.healthyTimer) {
|
|
8094
8165
|
clearTimeout(this.healthyTimer);
|
|
8095
8166
|
this.healthyTimer = null;
|
|
8096
8167
|
}
|
|
8097
8168
|
try {
|
|
8098
|
-
const
|
|
8099
|
-
|
|
8100
|
-
if (!state.bootMarkerPresent) {
|
|
8169
|
+
const state = preRead !== void 0 ? preRead : JSON.parse(NativeScaleBunOta_default.getSlotState());
|
|
8170
|
+
if (!state?.bootMarkerPresent) {
|
|
8101
8171
|
return;
|
|
8102
8172
|
}
|
|
8103
8173
|
const healthyMs = this.bootGuardConfig.healthyAfterMs ?? DEFAULT_HEALTHY_AFTER_MS;
|
|
8104
|
-
|
|
8174
|
+
logger.debug(`[OTA] Boot guard active \u2014 marking healthy after ${healthyMs}ms`);
|
|
8105
8175
|
this.healthyTimer = setTimeout(() => {
|
|
8106
8176
|
noThrow(() => {
|
|
8107
8177
|
NativeScaleBunOta_default?.markHealthy();
|
|
8108
|
-
|
|
8178
|
+
logger.info("[OTA] Boot guard cleared \u2014 bundle marked healthy \u2713");
|
|
8109
8179
|
this.healthyTimer = null;
|
|
8110
8180
|
const running = this.currentBundle;
|
|
8111
8181
|
if (running?.id) {
|
|
@@ -8151,11 +8221,11 @@ var init_OtaOrchestrator = __esm({
|
|
|
8151
8221
|
} catch {
|
|
8152
8222
|
}
|
|
8153
8223
|
this.resumeRestartArmed = false;
|
|
8154
|
-
|
|
8224
|
+
logger.info("[OTA] App resumed \u2014 activating the staged update now");
|
|
8155
8225
|
this.restart();
|
|
8156
8226
|
}
|
|
8157
8227
|
});
|
|
8158
|
-
|
|
8228
|
+
logger.debug("[OTA] Update staged \u2014 will activate on next resume (ON_NEXT_RESUME)");
|
|
8159
8229
|
} catch {
|
|
8160
8230
|
this.resumeRestartArmed = false;
|
|
8161
8231
|
}
|
|
@@ -8172,7 +8242,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
8172
8242
|
*/
|
|
8173
8243
|
restart() {
|
|
8174
8244
|
noThrow(() => {
|
|
8175
|
-
|
|
8245
|
+
logger.info("[OTA] Triggering app restart\u2026");
|
|
8176
8246
|
if (NativeScaleBunOta_default && typeof NativeScaleBunOta_default.restartApp === "function") {
|
|
8177
8247
|
NativeScaleBunOta_default.restartApp();
|
|
8178
8248
|
} else if (import_react_native33.DevSettings && typeof import_react_native33.DevSettings.reload === "function") {
|
|
@@ -8191,17 +8261,25 @@ var init_OtaOrchestrator = __esm({
|
|
|
8191
8261
|
var uiState_exports = {};
|
|
8192
8262
|
__export(uiState_exports, {
|
|
8193
8263
|
clearUiState: () => clearUiState,
|
|
8264
|
+
onUiStateChange: () => onUiStateChange,
|
|
8265
|
+
resetUiStateListeners: () => resetUiStateListeners,
|
|
8194
8266
|
setUiState: () => setUiState,
|
|
8267
|
+
uiStateListenerCount: () => uiStateListenerCount,
|
|
8195
8268
|
uiStateSignature: () => uiStateSignature
|
|
8196
8269
|
});
|
|
8197
8270
|
function setUiState(name, value) {
|
|
8198
8271
|
const n = clean(name);
|
|
8199
8272
|
const v = clean(value);
|
|
8200
|
-
if (n
|
|
8273
|
+
if (!n || !v) return;
|
|
8274
|
+
if (declared.get(n) === v) return;
|
|
8275
|
+
declared.set(n, v);
|
|
8276
|
+
notifyChanged();
|
|
8201
8277
|
}
|
|
8202
8278
|
function clearUiState(name) {
|
|
8279
|
+
const before = declared.size;
|
|
8203
8280
|
if (name === void 0) declared.clear();
|
|
8204
8281
|
else declared.delete(clean(name));
|
|
8282
|
+
if (declared.size !== before) notifyChanged();
|
|
8205
8283
|
}
|
|
8206
8284
|
function uiStateSignature() {
|
|
8207
8285
|
if (!declared.size) return void 0;
|
|
@@ -8214,12 +8292,42 @@ function uiStateSignature() {
|
|
|
8214
8292
|
}
|
|
8215
8293
|
return signature || void 0;
|
|
8216
8294
|
}
|
|
8217
|
-
|
|
8295
|
+
function onUiStateChange(listener) {
|
|
8296
|
+
listeners2.add(listener);
|
|
8297
|
+
return () => {
|
|
8298
|
+
listeners2.delete(listener);
|
|
8299
|
+
};
|
|
8300
|
+
}
|
|
8301
|
+
function resetUiStateListeners() {
|
|
8302
|
+
listeners2.clear();
|
|
8303
|
+
if (pending4 !== null) {
|
|
8304
|
+
clearTimeout(pending4);
|
|
8305
|
+
pending4 = null;
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
function uiStateListenerCount() {
|
|
8309
|
+
return listeners2.size;
|
|
8310
|
+
}
|
|
8311
|
+
function notifyChanged() {
|
|
8312
|
+
if (pending4 !== null) return;
|
|
8313
|
+
pending4 = setTimeout(() => {
|
|
8314
|
+
pending4 = null;
|
|
8315
|
+
for (const listener of listeners2) {
|
|
8316
|
+
try {
|
|
8317
|
+
listener();
|
|
8318
|
+
} catch {
|
|
8319
|
+
}
|
|
8320
|
+
}
|
|
8321
|
+
}, 0);
|
|
8322
|
+
}
|
|
8323
|
+
var declared, clean, listeners2, pending4;
|
|
8218
8324
|
var init_uiState = __esm({
|
|
8219
8325
|
"lib/module/features/journey/uiState.js"() {
|
|
8220
8326
|
"use strict";
|
|
8221
8327
|
declared = /* @__PURE__ */ new Map();
|
|
8222
8328
|
clean = (s) => typeof s === "string" ? s.replace(/[;:|]/g, "").trim().slice(0, 32) : "";
|
|
8329
|
+
listeners2 = /* @__PURE__ */ new Set();
|
|
8330
|
+
pending4 = null;
|
|
8223
8331
|
}
|
|
8224
8332
|
});
|
|
8225
8333
|
|
|
@@ -10170,6 +10278,22 @@ var SHAPE = {
|
|
|
10170
10278
|
}
|
|
10171
10279
|
},
|
|
10172
10280
|
captureInteractionHeatmap: bool(true),
|
|
10281
|
+
/**
|
|
10282
|
+
* RN-3 zero-config: patch React Native's ScrollView export so EVERY scroll view reports its
|
|
10283
|
+
* offset, not just ones wrapped in ScaleBunScrollView.
|
|
10284
|
+
*
|
|
10285
|
+
* DEFAULT FALSE, unlike captureInteractionHeatmap above, and the difference is deliberate.
|
|
10286
|
+
* That flag changes what the SDK records about the host; this one changes what the HOST
|
|
10287
|
+
* RENDERS — every scroll view in the app, including ones inside third-party libraries the app
|
|
10288
|
+
* did not write, starts routing through an SDK component. The failure mode is not a wrong
|
|
10289
|
+
* number in a dashboard, it is the host's UI behaving differently because an analytics SDK
|
|
10290
|
+
* replaced a primitive. That is a decision an integrator makes deliberately, not one they
|
|
10291
|
+
* discover afterwards.
|
|
10292
|
+
*
|
|
10293
|
+
* With it off, a plain <ScrollView> yields no content depth and the tap says so
|
|
10294
|
+
* (scroll_source: 'none') rather than defaulting its offset to 0.
|
|
10295
|
+
*/
|
|
10296
|
+
autoInstrumentScrollViews: bool(false),
|
|
10173
10297
|
// Opt-in: intercept console.* and ship the lines to the Diagnose → Logs lane in SaaS
|
|
10174
10298
|
// mode. OFF by default — console output can carry PII/secrets and adds ingest volume,
|
|
10175
10299
|
// so capturing it is the host's explicit choice, not a silent default. When true and a
|
|
@@ -10186,18 +10310,9 @@ var SHAPE = {
|
|
|
10186
10310
|
kind: "str",
|
|
10187
10311
|
opt: true
|
|
10188
10312
|
},
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
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
|
-
},
|
|
10313
|
+
// No signing-key fields: signed releases are verified natively against
|
|
10314
|
+
// RSA keys embedded in the app package (SB-OTA-RSA-SHA256-V1). JS must
|
|
10315
|
+
// not supply a trusted key.
|
|
10201
10316
|
mandatoryBlocksUi: bool(false)
|
|
10202
10317
|
}
|
|
10203
10318
|
}
|
|
@@ -10999,6 +11114,23 @@ function installNetworkMonitoring(onEvent, options = {}) {
|
|
|
10999
11114
|
});
|
|
11000
11115
|
}
|
|
11001
11116
|
|
|
11117
|
+
// lib/module/features/network/thirdParty.js
|
|
11118
|
+
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";
|
|
11119
|
+
function isThirdPartyBeacon(url, hosts) {
|
|
11120
|
+
if (!url) return false;
|
|
11121
|
+
let host = "";
|
|
11122
|
+
try {
|
|
11123
|
+
host = new URL(url).hostname.toLowerCase();
|
|
11124
|
+
} catch {
|
|
11125
|
+
return false;
|
|
11126
|
+
}
|
|
11127
|
+
if (!host) return false;
|
|
11128
|
+
for (const suffix of (hosts ?? THIRD_PARTY_BEACON_HOSTS).split(",")) {
|
|
11129
|
+
if (host === suffix || host.endsWith("." + suffix)) return true;
|
|
11130
|
+
}
|
|
11131
|
+
return false;
|
|
11132
|
+
}
|
|
11133
|
+
|
|
11002
11134
|
// lib/module/features/network/NetworkFeature.js
|
|
11003
11135
|
init_networkAdapter();
|
|
11004
11136
|
init_SessionManager();
|
|
@@ -11014,10 +11146,12 @@ var NetworkFeature = class {
|
|
|
11014
11146
|
if (this.active) return;
|
|
11015
11147
|
installNetworkMonitoring((data) => {
|
|
11016
11148
|
const isInternal = isSdkInternalUrl(data.url, this.options.sdkBaseUrl);
|
|
11017
|
-
|
|
11149
|
+
const isBeaconOrDenied = this.options.captureThirdParty !== true && isThirdPartyBeacon(data.url, this.options.thirdPartyHosts) || matchesDenyUrl(data.url, this.options.denyUrls);
|
|
11150
|
+
const isExcluded = isInternal || isBeaconOrDenied;
|
|
11151
|
+
if (!isExcluded) {
|
|
11018
11152
|
this.trackFn("$network_request", data);
|
|
11019
11153
|
}
|
|
11020
|
-
if (data.type === "network") {
|
|
11154
|
+
if (data.type === "network" && !isBeaconOrDenied) {
|
|
11021
11155
|
reportNetworkRequest({
|
|
11022
11156
|
method: data.method,
|
|
11023
11157
|
url: data.url,
|
|
@@ -11033,7 +11167,7 @@ var NetworkFeature = class {
|
|
|
11033
11167
|
requestBody: data.requestBody,
|
|
11034
11168
|
responseBody: data.responseBody
|
|
11035
11169
|
});
|
|
11036
|
-
if (!
|
|
11170
|
+
if (!isExcluded) {
|
|
11037
11171
|
const sessionManager = SessionManager.getExistingInstance();
|
|
11038
11172
|
if (sessionManager) {
|
|
11039
11173
|
const backendTransport = sessionManager.getBackendTransport();
|
|
@@ -11064,6 +11198,13 @@ var NetworkFeature = class {
|
|
|
11064
11198
|
return this.active;
|
|
11065
11199
|
}
|
|
11066
11200
|
};
|
|
11201
|
+
function matchesDenyUrl(url, patterns) {
|
|
11202
|
+
if (!patterns || !patterns.length || !url) return false;
|
|
11203
|
+
for (const p of patterns) {
|
|
11204
|
+
if (typeof p === "string" ? url.includes(p) : p.test(url)) return true;
|
|
11205
|
+
}
|
|
11206
|
+
return false;
|
|
11207
|
+
}
|
|
11067
11208
|
function isSdkInternalUrl(url, sdkBaseUrl) {
|
|
11068
11209
|
if (!url) return false;
|
|
11069
11210
|
try {
|
|
@@ -12095,8 +12236,8 @@ var PerformanceTransport = class {
|
|
|
12095
12236
|
setSink(sink) {
|
|
12096
12237
|
this.sink = sink;
|
|
12097
12238
|
if (sink && this.buffer.length > 0) {
|
|
12098
|
-
const
|
|
12099
|
-
for (const msg of
|
|
12239
|
+
const pending5 = this.buffer.splice(0);
|
|
12240
|
+
for (const msg of pending5) {
|
|
12100
12241
|
try {
|
|
12101
12242
|
sink.send(msg);
|
|
12102
12243
|
} catch {
|
|
@@ -13811,22 +13952,22 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
|
|
|
13811
13952
|
* lose the snapshot entirely. `imageData.length` is the base64 length, which is within ~1% of the
|
|
13812
13953
|
* serialized cost and needs no extra pass.
|
|
13813
13954
|
*/
|
|
13814
|
-
static _selectFramesToPersist(
|
|
13955
|
+
static _selectFramesToPersist(pending5) {
|
|
13815
13956
|
const isKeyframe = (b) => b.frame.frameType !== "delta";
|
|
13816
13957
|
const size = (b) => b.imageData?.length ?? 0;
|
|
13817
13958
|
const keep = [];
|
|
13818
13959
|
let budget = _BackendSessionAdapter.MAX_PERSIST_BYTES;
|
|
13819
13960
|
const maxCount = _BackendSessionAdapter.MAX_FRAMES_PER_BATCH;
|
|
13820
|
-
for (let i =
|
|
13821
|
-
const b =
|
|
13961
|
+
for (let i = pending5.length - 1; i >= 0; i--) {
|
|
13962
|
+
const b = pending5[i];
|
|
13822
13963
|
if (!isKeyframe(b)) continue;
|
|
13823
13964
|
const cost = size(b);
|
|
13824
13965
|
if (cost > budget || keep.length >= maxCount) continue;
|
|
13825
13966
|
budget -= cost;
|
|
13826
13967
|
keep.push(b);
|
|
13827
13968
|
}
|
|
13828
|
-
for (let i =
|
|
13829
|
-
const b =
|
|
13969
|
+
for (let i = pending5.length - 1; i >= 0; i--) {
|
|
13970
|
+
const b = pending5[i];
|
|
13830
13971
|
if (isKeyframe(b)) continue;
|
|
13831
13972
|
const cost = size(b);
|
|
13832
13973
|
if (cost > budget || keep.length >= maxCount) continue;
|
|
@@ -14259,6 +14400,10 @@ var SDKBootstrapper = class {
|
|
|
14259
14400
|
return null;
|
|
14260
14401
|
}
|
|
14261
14402
|
this.config = result.data;
|
|
14403
|
+
const requestedApiBaseUrl = rawConfig?.apiBaseUrl;
|
|
14404
|
+
if (typeof requestedApiBaseUrl === "string" && requestedApiBaseUrl && requestedApiBaseUrl !== DEFAULT_API_BASE_URL) {
|
|
14405
|
+
logger.warn(`[ScaleBun] Ignoring apiBaseUrl '${requestedApiBaseUrl}' \u2014 the endpoint is locked at build time and this build targets ${DEFAULT_API_BASE_URL}. To target another backend, change DEFAULT_API_BASE_URL in the SDK source and rebuild.`);
|
|
14406
|
+
}
|
|
14262
14407
|
this.config.apiBaseUrl = DEFAULT_API_BASE_URL;
|
|
14263
14408
|
if (this.config.logLevel) {
|
|
14264
14409
|
logger.setLevelByName(this.config.logLevel);
|
|
@@ -14423,6 +14568,20 @@ var SDKBootstrapper = class {
|
|
|
14423
14568
|
}));
|
|
14424
14569
|
}
|
|
14425
14570
|
await this.featureRegistry.initializeAll(featureContext);
|
|
14571
|
+
if (this.config.autoInstrumentScrollViews === true) {
|
|
14572
|
+
try {
|
|
14573
|
+
const {
|
|
14574
|
+
autoInstrumentScrollViews: autoInstrumentScrollViews2
|
|
14575
|
+
} = (init_autoInstrumentScroll(), __toCommonJS(autoInstrumentScroll_exports));
|
|
14576
|
+
const outcome = autoInstrumentScrollViews2();
|
|
14577
|
+
if (!outcome.installed) {
|
|
14578
|
+
logger.warn("[Bootstrap] autoInstrumentScrollViews was requested but could not be installed: " + outcome.reason + " \u2014 taps will carry no content depth.");
|
|
14579
|
+
} else {
|
|
14580
|
+
__DEV__ && logger.debug("[Bootstrap] ScrollView auto-instrumentation installed");
|
|
14581
|
+
}
|
|
14582
|
+
} catch {
|
|
14583
|
+
}
|
|
14584
|
+
}
|
|
14426
14585
|
if (features.replay !== false) {
|
|
14427
14586
|
try {
|
|
14428
14587
|
__DEV__ && logger.debug("[Bootstrap] Initializing unified SessionManager");
|
|
@@ -17065,14 +17224,14 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
17065
17224
|
this._apiBaseUrl = result.config.apiBaseUrl;
|
|
17066
17225
|
this._maybeStartEventTracker(rawConfig);
|
|
17067
17226
|
if (this._pendingIdentity) {
|
|
17068
|
-
const
|
|
17227
|
+
const pending5 = this._pendingIdentity;
|
|
17069
17228
|
this._pendingIdentity = null;
|
|
17070
|
-
this.identify(
|
|
17229
|
+
this.identify(pending5.userId, pending5.traits);
|
|
17071
17230
|
}
|
|
17072
17231
|
const realQueue = coreContainer.get("PersistentQueue");
|
|
17073
17232
|
const buffered = this._preInitBuffer.popBatch(this._preInitBuffer.length());
|
|
17074
17233
|
if (buffered.length > 0) {
|
|
17075
|
-
|
|
17234
|
+
logger.debug(`[ScaleBun] Draining ${buffered.length} pre-init events into persistent queue`);
|
|
17076
17235
|
for (const envelope of buffered) {
|
|
17077
17236
|
realQueue.add(envelope);
|
|
17078
17237
|
}
|
|
@@ -17100,17 +17259,11 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
17100
17259
|
/**
|
|
17101
17260
|
* Boot the OTA orchestrator when the init config asks for it.
|
|
17102
17261
|
*
|
|
17103
|
-
*
|
|
17104
|
-
*
|
|
17105
|
-
*
|
|
17106
|
-
*
|
|
17107
|
-
*
|
|
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.
|
|
17262
|
+
* Signature enforcement is no longer configured from JS. Signed releases are
|
|
17263
|
+
* verified and staged in NATIVE code against RSA keys embedded in the app
|
|
17264
|
+
* package (SB-OTA-RSA-SHA256-V1); unsigned releases keep working on SHA-256
|
|
17265
|
+
* integrity. There are no signing-key or `verifySignature` options to pass
|
|
17266
|
+
* through — the host embeds its trusted keys natively.
|
|
17114
17267
|
*/
|
|
17115
17268
|
_maybeStartOta(rawConfig) {
|
|
17116
17269
|
const ota = rawConfig?.ota;
|
|
@@ -17119,29 +17272,8 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
17119
17272
|
const {
|
|
17120
17273
|
otaOrchestrator: otaOrchestrator2
|
|
17121
17274
|
} = (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
17275
|
const healthyAfterMs = typeof ota.healthyAfterMs === "number" ? ota.healthyAfterMs : typeof ota.healthyTimeoutMs === "number" ? ota.healthyTimeoutMs : void 0;
|
|
17136
17276
|
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
17277
|
...healthyAfterMs !== void 0 ? {
|
|
17146
17278
|
healthyAfterMs
|
|
17147
17279
|
} : {}
|
|
@@ -17190,7 +17322,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
17190
17322
|
}
|
|
17191
17323
|
const clientKey = this._clientKey;
|
|
17192
17324
|
const apiUrl = this._apiBaseUrl;
|
|
17193
|
-
if (!clientKey || !apiUrl)
|
|
17325
|
+
if (!clientKey || !apiUrl) {
|
|
17326
|
+
return;
|
|
17327
|
+
}
|
|
17194
17328
|
this._otaCheckInFlight = true;
|
|
17195
17329
|
try {
|
|
17196
17330
|
const info = await bridgeAdapter.getDeviceInfo();
|
|
@@ -17241,7 +17375,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
17241
17375
|
if (!dbgConfig) return;
|
|
17242
17376
|
dbgConfig.__skipDeprecationWarning = true;
|
|
17243
17377
|
this.enableDebug(dbgConfig);
|
|
17244
|
-
|
|
17378
|
+
logger.info("[ScaleBun] Debug auto-connected via init({ debug }).");
|
|
17245
17379
|
} catch (err) {
|
|
17246
17380
|
logger.warn("[ScaleBun] Auto debug connection failed:", err?.message);
|
|
17247
17381
|
}
|
|
@@ -17255,7 +17389,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
17255
17389
|
const apiBaseUrl = this._apiBaseUrl;
|
|
17256
17390
|
if (!enabled || !appId || !clientKey || !apiBaseUrl) {
|
|
17257
17391
|
if (enabled && !appId) {
|
|
17258
|
-
|
|
17392
|
+
logger.info("[ScaleBun] Envelope event tracking disabled \u2014 no appId in init config.");
|
|
17259
17393
|
}
|
|
17260
17394
|
return;
|
|
17261
17395
|
}
|
|
@@ -17305,7 +17439,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
17305
17439
|
onEvent: (eventName, revenue) => this._skanManager?.onEvent(eventName, revenue)
|
|
17306
17440
|
});
|
|
17307
17441
|
this._eventTracker.start();
|
|
17308
|
-
|
|
17442
|
+
logger.info("[ScaleBun] Envelope event tracking started \u2192 /v1/batch.");
|
|
17309
17443
|
this._applyNativeDeviceContext(this._eventTracker);
|
|
17310
17444
|
this._captureInstallReferrer(this._eventTracker);
|
|
17311
17445
|
const tracker = this._eventTracker;
|
|
@@ -17939,7 +18073,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
17939
18073
|
const cfg = await (this._getEngageTransport()?.fetchEngageConfig(context) ?? Promise.resolve(null));
|
|
17940
18074
|
const messages = cfg?.inAppMessages ?? [];
|
|
17941
18075
|
const previews = messages.filter((m) => m.preview === true || m.isTest === true);
|
|
17942
|
-
|
|
18076
|
+
logger.info(`[Engage] debugFetchInAppMessages \u2192 ${messages.length} message(s), ${previews.length} test preview(s)`);
|
|
17943
18077
|
return messages;
|
|
17944
18078
|
},
|
|
17945
18079
|
/** Upload a survey/NPS/feedback response (idempotent, rides the outbox). */
|
|
@@ -18215,7 +18349,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
18215
18349
|
logger.warn("SDK not initialized. Event dropped:", name);
|
|
18216
18350
|
return;
|
|
18217
18351
|
}
|
|
18218
|
-
|
|
18352
|
+
logger.debug("Tracking event:", name);
|
|
18219
18353
|
engageSignals.emit(name, payload);
|
|
18220
18354
|
const adapter = SessionManager.getExistingInstance()?.getBackendTransport();
|
|
18221
18355
|
if (!isUploadedSessionEvent(name)) {
|
|
@@ -18344,7 +18478,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
18344
18478
|
await Promise.all([this._eventTracker?.flush() ?? Promise.resolve(), adapter?.flushAnalytics() ?? Promise.resolve()]);
|
|
18345
18479
|
return;
|
|
18346
18480
|
}
|
|
18347
|
-
|
|
18481
|
+
logger.debug("Flushing events from persistent queue...");
|
|
18348
18482
|
const httpClient = coreContainer.get("HttpClient");
|
|
18349
18483
|
await flushQueue(this._getQueue(), httpClient, getDeviceId());
|
|
18350
18484
|
});
|
|
@@ -18366,9 +18500,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
18366
18500
|
const bootstrapPerfFeature = getPerformanceFeature();
|
|
18367
18501
|
if (bootstrapPerfFeature) {
|
|
18368
18502
|
this._performanceFeature = bootstrapPerfFeature;
|
|
18369
|
-
|
|
18503
|
+
logger.debug("Performance feature linked from bootstrap");
|
|
18370
18504
|
}
|
|
18371
|
-
|
|
18505
|
+
logger.info("Debug enabled");
|
|
18372
18506
|
});
|
|
18373
18507
|
}
|
|
18374
18508
|
/** Disable the debug subsystem and clean up */
|
|
@@ -18511,14 +18645,14 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
18511
18645
|
return false;
|
|
18512
18646
|
}
|
|
18513
18647
|
sm.startSession(metadata);
|
|
18514
|
-
|
|
18648
|
+
logger.info("session.start() \u2705 sessionId:", sm.currentSessionId);
|
|
18515
18649
|
return true;
|
|
18516
18650
|
},
|
|
18517
18651
|
end: (reason) => {
|
|
18518
18652
|
const sm = SessionManager.getExistingInstance();
|
|
18519
18653
|
if (!sm || !sm.isActive) return false;
|
|
18520
18654
|
sm.endSession(reason);
|
|
18521
|
-
|
|
18655
|
+
logger.info("session.end() \u2705");
|
|
18522
18656
|
return true;
|
|
18523
18657
|
},
|
|
18524
18658
|
emitEvent: (type, data) => {
|
|
@@ -18571,7 +18705,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
18571
18705
|
...this._privacyConfig,
|
|
18572
18706
|
...config
|
|
18573
18707
|
};
|
|
18574
|
-
|
|
18708
|
+
logger.debug("[report] Privacy config updated");
|
|
18575
18709
|
},
|
|
18576
18710
|
/** Set branding for exported reports. */
|
|
18577
18711
|
setBranding: (config) => {
|
|
@@ -18579,7 +18713,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
18579
18713
|
...this._brandingConfig,
|
|
18580
18714
|
...config
|
|
18581
18715
|
};
|
|
18582
|
-
|
|
18716
|
+
logger.debug("[report] Branding config updated");
|
|
18583
18717
|
},
|
|
18584
18718
|
/** Get current privacy config. */
|
|
18585
18719
|
getPrivacy: () => ({
|
|
@@ -18626,9 +18760,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
18626
18760
|
AutoScreenDetector: AutoScreenDetector2
|
|
18627
18761
|
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
18628
18762
|
AutoScreenDetector2.getInstance().setNavigationRef(ref);
|
|
18629
|
-
|
|
18763
|
+
logger.info("[ScaleBun] Navigation ref set \u2192 auto screen detection active");
|
|
18630
18764
|
} catch (err) {
|
|
18631
|
-
|
|
18765
|
+
logger.debug("[ScaleBun] Navigation ref stashed (detector not ready):", err?.message);
|
|
18632
18766
|
}
|
|
18633
18767
|
});
|
|
18634
18768
|
}
|
|
@@ -18935,8 +19069,8 @@ function installEngageTriggerEngine(deps) {
|
|
|
18935
19069
|
if (!cachedConfig) return;
|
|
18936
19070
|
configReady = true;
|
|
18937
19071
|
const now2 = Date.now();
|
|
18938
|
-
const
|
|
18939
|
-
for (const e of
|
|
19072
|
+
const pending5 = recent.splice(0);
|
|
19073
|
+
for (const e of pending5) {
|
|
18940
19074
|
const age = now2 - e.at;
|
|
18941
19075
|
if (age > RECENT_TTL_MS) continue;
|
|
18942
19076
|
const remaining = delayMsForTrigger(cachedConfig, e.options.trigger) - age;
|
|
@@ -20201,11 +20335,11 @@ var keyOf = (value) => value.trim();
|
|
|
20201
20335
|
var validRect = (rect) => [rect.x, rect.y, rect.width, rect.height].every(Number.isFinite) && rect.width > 0 && rect.height > 0;
|
|
20202
20336
|
function createEngageAnchorRegistry() {
|
|
20203
20337
|
const entries = /* @__PURE__ */ new Map();
|
|
20204
|
-
const
|
|
20338
|
+
const listeners3 = /* @__PURE__ */ new Map();
|
|
20205
20339
|
const notify = (rawKey) => {
|
|
20206
20340
|
const key = keyOf(rawKey);
|
|
20207
20341
|
if (!key) return;
|
|
20208
|
-
|
|
20342
|
+
listeners3.get(key)?.forEach((listener) => listener());
|
|
20209
20343
|
};
|
|
20210
20344
|
return {
|
|
20211
20345
|
register(rawKey, node) {
|
|
@@ -20247,12 +20381,12 @@ function createEngageAnchorRegistry() {
|
|
|
20247
20381
|
const key = keyOf(rawKey);
|
|
20248
20382
|
if (!key) return () => {
|
|
20249
20383
|
};
|
|
20250
|
-
const set =
|
|
20384
|
+
const set = listeners3.get(key) ?? /* @__PURE__ */ new Set();
|
|
20251
20385
|
set.add(listener);
|
|
20252
|
-
|
|
20386
|
+
listeners3.set(key, set);
|
|
20253
20387
|
return () => {
|
|
20254
20388
|
set.delete(listener);
|
|
20255
|
-
if (set.size === 0)
|
|
20389
|
+
if (set.size === 0) listeners3.delete(key);
|
|
20256
20390
|
};
|
|
20257
20391
|
},
|
|
20258
20392
|
notify
|
|
@@ -21099,11 +21233,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
|
|
|
21099
21233
|
function resolveWheelOutcome(segments, source) {
|
|
21100
21234
|
const identity = wheelOutcomeIdentity(source);
|
|
21101
21235
|
const index = findWheelOutcomeIndex(segments, identity);
|
|
21102
|
-
const
|
|
21236
|
+
const resolved = index >= 0;
|
|
21103
21237
|
return {
|
|
21104
|
-
index:
|
|
21105
|
-
label: identity.resultLabel || (
|
|
21106
|
-
resolved
|
|
21238
|
+
index: resolved ? index : 0,
|
|
21239
|
+
label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
|
|
21240
|
+
resolved
|
|
21107
21241
|
};
|
|
21108
21242
|
}
|
|
21109
21243
|
function SpinWheelInApp(props) {
|
|
@@ -29361,10 +29495,10 @@ var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
|
29361
29495
|
var keyOf2 = (value) => value.trim();
|
|
29362
29496
|
function createEngageInlinePlacementRegistry() {
|
|
29363
29497
|
const slots = /* @__PURE__ */ new Map();
|
|
29364
|
-
const
|
|
29365
|
-
const
|
|
29498
|
+
const pending5 = /* @__PURE__ */ new Map();
|
|
29499
|
+
const listeners3 = /* @__PURE__ */ new Map();
|
|
29366
29500
|
const notify = (key) => {
|
|
29367
|
-
|
|
29501
|
+
listeners3.get(key)?.forEach((listener) => listener());
|
|
29368
29502
|
};
|
|
29369
29503
|
return {
|
|
29370
29504
|
register(rawKey, sink) {
|
|
@@ -29381,7 +29515,7 @@ function createEngageInlinePlacementRegistry() {
|
|
|
29381
29515
|
sink,
|
|
29382
29516
|
token
|
|
29383
29517
|
});
|
|
29384
|
-
sink(
|
|
29518
|
+
sink(pending5.get(key) ?? null);
|
|
29385
29519
|
if (!previous) notify(key);
|
|
29386
29520
|
return () => {
|
|
29387
29521
|
if (slots.get(key)?.token !== token) return;
|
|
@@ -29392,7 +29526,7 @@ function createEngageInlinePlacementRegistry() {
|
|
|
29392
29526
|
deliver(rawKey, delivery) {
|
|
29393
29527
|
const key = keyOf2(rawKey);
|
|
29394
29528
|
if (!key) return false;
|
|
29395
|
-
|
|
29529
|
+
pending5.set(key, delivery);
|
|
29396
29530
|
const slot = slots.get(key);
|
|
29397
29531
|
slot?.sink(delivery);
|
|
29398
29532
|
return slot !== void 0;
|
|
@@ -29400,9 +29534,9 @@ function createEngageInlinePlacementRegistry() {
|
|
|
29400
29534
|
clear(rawKey, messageId) {
|
|
29401
29535
|
const key = keyOf2(rawKey);
|
|
29402
29536
|
if (!key) return;
|
|
29403
|
-
const current =
|
|
29537
|
+
const current = pending5.get(key);
|
|
29404
29538
|
if (!current || messageId !== void 0 && current.message?.id !== messageId) return;
|
|
29405
|
-
|
|
29539
|
+
pending5.delete(key);
|
|
29406
29540
|
slots.get(key)?.sink(null);
|
|
29407
29541
|
},
|
|
29408
29542
|
has(rawKey) {
|
|
@@ -29413,12 +29547,12 @@ function createEngageInlinePlacementRegistry() {
|
|
|
29413
29547
|
const key = keyOf2(rawKey);
|
|
29414
29548
|
if (!key) return () => {
|
|
29415
29549
|
};
|
|
29416
|
-
const set =
|
|
29550
|
+
const set = listeners3.get(key) ?? /* @__PURE__ */ new Set();
|
|
29417
29551
|
set.add(listener);
|
|
29418
|
-
|
|
29552
|
+
listeners3.set(key, set);
|
|
29419
29553
|
return () => {
|
|
29420
29554
|
set.delete(listener);
|
|
29421
|
-
if (set.size === 0)
|
|
29555
|
+
if (set.size === 0) listeners3.delete(key);
|
|
29422
29556
|
};
|
|
29423
29557
|
}
|
|
29424
29558
|
};
|
|
@@ -29962,8 +30096,8 @@ function EngagePromptProvider({
|
|
|
29962
30096
|
__DEV__ && logger.debug("[Engage] no eligible in-app message to render");
|
|
29963
30097
|
return false;
|
|
29964
30098
|
}
|
|
29965
|
-
const
|
|
29966
|
-
activeInAppVariantRef.current = message.experiment?.enabled ?
|
|
30099
|
+
const resolved = selectInAppVariant(message, getDeviceId());
|
|
30100
|
+
activeInAppVariantRef.current = message.experiment?.enabled ? resolved : null;
|
|
29967
30101
|
store.recordImpression(message.id, Date.now(), message.revision);
|
|
29968
30102
|
setActiveInApp(message);
|
|
29969
30103
|
return true;
|
|
@@ -30207,6 +30341,7 @@ var import_react_native42 = require("react-native");
|
|
|
30207
30341
|
// lib/module/features/journey/touchTarget.js
|
|
30208
30342
|
var MAX_DEPTH = 12;
|
|
30209
30343
|
var MAX_LEN = 60;
|
|
30344
|
+
var MAX_PATH_SEGMENTS = 4;
|
|
30210
30345
|
function str2(v) {
|
|
30211
30346
|
if (typeof v !== "string") return void 0;
|
|
30212
30347
|
const s = v.trim();
|
|
@@ -30238,41 +30373,136 @@ function fiberOf(event) {
|
|
|
30238
30373
|
const e = event;
|
|
30239
30374
|
const direct = e._targetInst;
|
|
30240
30375
|
if (direct && typeof direct === "object") return direct;
|
|
30241
|
-
const target = e.target;
|
|
30242
|
-
if (target
|
|
30243
|
-
const handle = target.
|
|
30376
|
+
const target = e.target ?? e.nativeEvent?.target;
|
|
30377
|
+
if (target) {
|
|
30378
|
+
const handle = target._internalInstanceHandle ?? target._targetInst;
|
|
30244
30379
|
if (handle && typeof handle === "object") return handle;
|
|
30245
30380
|
}
|
|
30246
30381
|
return null;
|
|
30247
30382
|
}
|
|
30248
|
-
function resolveTouchTarget(event,
|
|
30383
|
+
function resolveTouchTarget(event, optsOrMaxDepth = {}) {
|
|
30384
|
+
const opts = typeof optsOrMaxDepth === "number" ? {
|
|
30385
|
+
maxDepth: optsOrMaxDepth
|
|
30386
|
+
} : optsOrMaxDepth;
|
|
30387
|
+
const maxDepth = opts.maxDepth ?? MAX_DEPTH;
|
|
30249
30388
|
let f = fiberOf(event);
|
|
30250
30389
|
if (!f) return null;
|
|
30251
30390
|
const out = {};
|
|
30391
|
+
const segments = [];
|
|
30392
|
+
const ancestors = [];
|
|
30252
30393
|
let depth = 0;
|
|
30253
30394
|
try {
|
|
30254
30395
|
while (f && depth < maxDepth) {
|
|
30255
30396
|
const props = f.memoizedProps;
|
|
30256
30397
|
if (props) {
|
|
30257
|
-
|
|
30398
|
+
const beforeTestId = out.testID;
|
|
30258
30399
|
out.testID ??= str2(props.testID);
|
|
30259
|
-
out.
|
|
30400
|
+
if (!beforeTestId && out.testID) out.node = f.stateNode;
|
|
30401
|
+
if (typeof props.cellKey === "string" && props.cellKey) {
|
|
30402
|
+
out.itemKey ??= str2(props.cellKey);
|
|
30403
|
+
if (typeof props.index === "number" && Number.isFinite(props.index)) {
|
|
30404
|
+
out.itemIndex ??= props.index;
|
|
30405
|
+
}
|
|
30406
|
+
}
|
|
30407
|
+
if (opts.includeText) {
|
|
30408
|
+
out.accessibilityLabel ??= str2(props.accessibilityLabel);
|
|
30409
|
+
out.text ??= textChild(props);
|
|
30410
|
+
}
|
|
30411
|
+
}
|
|
30412
|
+
if (f.stateNode) ancestors.push(f.stateNode);
|
|
30413
|
+
const name = componentName(f);
|
|
30414
|
+
if (name) {
|
|
30415
|
+
out.component ??= name;
|
|
30416
|
+
if (segments[segments.length - 1] !== name && segments.length < MAX_PATH_SEGMENTS) {
|
|
30417
|
+
segments.push(name);
|
|
30418
|
+
}
|
|
30260
30419
|
}
|
|
30261
|
-
out.
|
|
30262
|
-
if (out.accessibilityLabel && out.testID) break;
|
|
30420
|
+
if (out.testID && segments.length >= MAX_PATH_SEGMENTS) break;
|
|
30263
30421
|
f = f.return ?? null;
|
|
30264
30422
|
depth++;
|
|
30265
30423
|
}
|
|
30266
30424
|
} catch {
|
|
30267
30425
|
}
|
|
30268
|
-
|
|
30426
|
+
if (segments.length) out.path = segments.join("<");
|
|
30427
|
+
if (!out.node) out.node = fiberOf(event)?.stateNode;
|
|
30428
|
+
if (ancestors.length) out.ancestors = ancestors;
|
|
30429
|
+
return out.testID || out.path || out.component || out.accessibilityLabel || out.text || out.itemKey ? out : null;
|
|
30269
30430
|
}
|
|
30270
30431
|
function describeTouchTarget(t) {
|
|
30271
30432
|
if (!t) return void 0;
|
|
30272
|
-
return t.
|
|
30433
|
+
return t.testID || t.path || t.component;
|
|
30434
|
+
}
|
|
30435
|
+
function targetIdentity(t) {
|
|
30436
|
+
if (!t) return {
|
|
30437
|
+
source: "none"
|
|
30438
|
+
};
|
|
30439
|
+
if (t.testID) return {
|
|
30440
|
+
id: t.testID,
|
|
30441
|
+
source: "testID"
|
|
30442
|
+
};
|
|
30443
|
+
if (t.path) return {
|
|
30444
|
+
id: t.path,
|
|
30445
|
+
source: "path"
|
|
30446
|
+
};
|
|
30447
|
+
if (t.component) return {
|
|
30448
|
+
id: t.component,
|
|
30449
|
+
source: "component"
|
|
30450
|
+
};
|
|
30451
|
+
return {
|
|
30452
|
+
source: "none"
|
|
30453
|
+
};
|
|
30454
|
+
}
|
|
30455
|
+
|
|
30456
|
+
// lib/module/features/journey/targetGeometry.js
|
|
30457
|
+
function readRectSync(node) {
|
|
30458
|
+
if (!node || typeof node !== "object") return null;
|
|
30459
|
+
const n = node;
|
|
30460
|
+
const fn = typeof n.unstable_getBoundingClientRect === "function" ? n.unstable_getBoundingClientRect : typeof n.getBoundingClientRect === "function" ? n.getBoundingClientRect : null;
|
|
30461
|
+
if (!fn) return null;
|
|
30462
|
+
try {
|
|
30463
|
+
const r = fn.call(n);
|
|
30464
|
+
if (!r) return null;
|
|
30465
|
+
const {
|
|
30466
|
+
x,
|
|
30467
|
+
y,
|
|
30468
|
+
width,
|
|
30469
|
+
height
|
|
30470
|
+
} = r;
|
|
30471
|
+
if (![x, y, width, height].every((v) => typeof v === "number" && Number.isFinite(v))) return null;
|
|
30472
|
+
if (width <= 0 || height <= 0) return null;
|
|
30473
|
+
return {
|
|
30474
|
+
x,
|
|
30475
|
+
y,
|
|
30476
|
+
width,
|
|
30477
|
+
height
|
|
30478
|
+
};
|
|
30479
|
+
} catch {
|
|
30480
|
+
return null;
|
|
30481
|
+
}
|
|
30482
|
+
}
|
|
30483
|
+
function normalizeRect(rect, base) {
|
|
30484
|
+
if (!rect || !base || base.w <= 0 || base.h <= 0) return null;
|
|
30485
|
+
const out = {
|
|
30486
|
+
x: rect.x / base.w,
|
|
30487
|
+
y: rect.y / base.h,
|
|
30488
|
+
width: rect.width / base.w,
|
|
30489
|
+
height: rect.height / base.h
|
|
30490
|
+
};
|
|
30491
|
+
if (![out.x, out.y, out.width, out.height].every((v) => Number.isFinite(v))) return null;
|
|
30492
|
+
return out;
|
|
30493
|
+
}
|
|
30494
|
+
function localPoint(rect, nx, ny) {
|
|
30495
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return null;
|
|
30496
|
+
if (typeof nx !== "number" || typeof ny !== "number") return null;
|
|
30497
|
+
if (!Number.isFinite(nx) || !Number.isFinite(ny)) return null;
|
|
30498
|
+
return {
|
|
30499
|
+
u: (nx - rect.x) / rect.width,
|
|
30500
|
+
v: (ny - rect.y) / rect.height
|
|
30501
|
+
};
|
|
30273
30502
|
}
|
|
30274
30503
|
|
|
30275
30504
|
// lib/module/features/journey/ScaleBunDebugRoot.js
|
|
30505
|
+
init_scrollContext();
|
|
30276
30506
|
init_device();
|
|
30277
30507
|
init_interactionProtocol();
|
|
30278
30508
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
@@ -30284,6 +30514,7 @@ function getBootstrap() {
|
|
|
30284
30514
|
return _bootstrap;
|
|
30285
30515
|
}
|
|
30286
30516
|
var JourneyContext = /* @__PURE__ */ (0, import_react9.createContext)(null);
|
|
30517
|
+
var UI_STATE_CAPTURE_MIN_INTERVAL_MS = 1e3;
|
|
30287
30518
|
function ScaleBunDebugRoot({
|
|
30288
30519
|
children,
|
|
30289
30520
|
navigationRef,
|
|
@@ -30462,6 +30693,28 @@ function ScaleBunDebugRoot({
|
|
|
30462
30693
|
clearTimeout(timer);
|
|
30463
30694
|
};
|
|
30464
30695
|
}, [navigationRef]);
|
|
30696
|
+
(0, import_react9.useEffect)(() => {
|
|
30697
|
+
let lastCaptureTs = 0;
|
|
30698
|
+
const {
|
|
30699
|
+
onUiStateChange: onUiStateChange2
|
|
30700
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
30701
|
+
const unsubscribe = onUiStateChange2(() => {
|
|
30702
|
+
const now2 = Date.now();
|
|
30703
|
+
if (now2 - lastCaptureTs < UI_STATE_CAPTURE_MIN_INTERVAL_MS) return;
|
|
30704
|
+
lastCaptureTs = now2;
|
|
30705
|
+
try {
|
|
30706
|
+
const {
|
|
30707
|
+
SessionManager: SessionManager2
|
|
30708
|
+
} = (init_SessionManager(), __toCommonJS(SessionManager_exports));
|
|
30709
|
+
const sm = SessionManager2.getExistingInstance();
|
|
30710
|
+
if (sm && typeof sm.captureManualFrame === "function") {
|
|
30711
|
+
void sm.captureManualFrame();
|
|
30712
|
+
}
|
|
30713
|
+
} catch {
|
|
30714
|
+
}
|
|
30715
|
+
});
|
|
30716
|
+
return unsubscribe;
|
|
30717
|
+
}, []);
|
|
30465
30718
|
const lastNativeTouchTsRef = (0, import_react9.useRef)(0);
|
|
30466
30719
|
const nativeTrackingConfirmedRef = (0, import_react9.useRef)(false);
|
|
30467
30720
|
const touchStartRef = (0, import_react9.useRef)(null);
|
|
@@ -30478,10 +30731,15 @@ function ScaleBunDebugRoot({
|
|
|
30478
30731
|
lastNativeTouchTsRef.current = Date.now();
|
|
30479
30732
|
nativeTrackingConfirmedRef.current = true;
|
|
30480
30733
|
const nativeOccurredAt = typeof event.occurredAt === "number" ? event.occurredAt : (typeof event.timestamp === "number" ? event.timestamp : Date.now()) - (typeof event.durationMs === "number" ? event.durationMs : 0);
|
|
30481
|
-
|
|
30734
|
+
pruneInteractionStarts(interactionStartsRef.current, nativeOccurredAt);
|
|
30735
|
+
const correlation = correlateInteraction(interactionStartsRef.current, nativeOccurredAt);
|
|
30736
|
+
const start = correlation.start;
|
|
30737
|
+
if (correlation.index >= 0) {
|
|
30738
|
+
interactionStartsRef.current.splice(correlation.index, 1);
|
|
30739
|
+
}
|
|
30482
30740
|
if (start) {
|
|
30483
|
-
const
|
|
30484
|
-
if (
|
|
30741
|
+
const pending5 = pendingJsEmitsRef.current.get(start.interactionId);
|
|
30742
|
+
if (pending5 !== void 0) clearTimeout(pending5);
|
|
30485
30743
|
pendingJsEmitsRef.current.delete(start.interactionId);
|
|
30486
30744
|
}
|
|
30487
30745
|
const {
|
|
@@ -30504,11 +30762,62 @@ function ScaleBunDebugRoot({
|
|
|
30504
30762
|
interactionId: event.interactionId || start?.interactionId || generateInteractionId(),
|
|
30505
30763
|
interactionProtocol: event.interactionProtocol || INTERACTION_PROTOCOL_VERSION,
|
|
30506
30764
|
occurredAt: nativeOccurredAt,
|
|
30765
|
+
/**
|
|
30766
|
+
* RN-6 — THESE FIVE FIELDS ARE THE ONLY ONES THE JOIN CARRIES, AND
|
|
30767
|
+
* THEREFORE THE ONLY ONES A BAD JOIN CAN FALSIFY.
|
|
30768
|
+
*
|
|
30769
|
+
* Coordinates below come from native and are right regardless. So a
|
|
30770
|
+
* mispairing does not produce visibly broken data: it produces heat
|
|
30771
|
+
* in the correct position attributed to the wrong control, in the
|
|
30772
|
+
* wrong UI state, on the wrong screen. Every field is individually
|
|
30773
|
+
* well-formed and nothing downstream can tell.
|
|
30774
|
+
*
|
|
30775
|
+
* When the correlation is not 'exact' they are left UNDEFINED rather
|
|
30776
|
+
* than filled from a best guess. An interaction with no target is
|
|
30777
|
+
* honest and visibly incomplete — the accounting lane can count it as
|
|
30778
|
+
* unplaceable and say why. An interaction with the WRONG target is
|
|
30779
|
+
* indistinguishable from a correct one and silently corrupts every
|
|
30780
|
+
* aggregate it enters. `null` is not `0`, and a guess is not a
|
|
30781
|
+
* measurement.
|
|
30782
|
+
*/
|
|
30507
30783
|
ui: start?.ui,
|
|
30508
30784
|
stateStatus: start?.stateStatus ?? "not_captured",
|
|
30509
30785
|
target: start?.target,
|
|
30510
30786
|
targetId: start?.targetId,
|
|
30787
|
+
targetSource: start?.targetSource,
|
|
30511
30788
|
screenName: start?.screenName,
|
|
30789
|
+
screenSource: start?.screenSource ?? "none",
|
|
30790
|
+
targetRect: start?.targetRect,
|
|
30791
|
+
/**
|
|
30792
|
+
* RN-15 — WHERE IN THE CONTROL, computed where both halves exist: the
|
|
30793
|
+
* point comes from the native event and the rect was measured at
|
|
30794
|
+
* finger-down, in the same capture base. Pairing them here rather than
|
|
30795
|
+
* downstream stops anything later reaching for a different rect.
|
|
30796
|
+
*/
|
|
30797
|
+
...(() => {
|
|
30798
|
+
const lp = localPoint(start?.targetRect ?? null, event.normalizedX, event.normalizedY);
|
|
30799
|
+
return lp ? {
|
|
30800
|
+
localU: lp.u,
|
|
30801
|
+
localV: lp.v
|
|
30802
|
+
} : {};
|
|
30803
|
+
})(),
|
|
30804
|
+
scrollX: start?.scrollX,
|
|
30805
|
+
scrollY: start?.scrollY,
|
|
30806
|
+
scrollSource: start?.scrollSource ?? "none",
|
|
30807
|
+
itemKey: start?.itemKey,
|
|
30808
|
+
itemIndex: start?.itemIndex,
|
|
30809
|
+
/**
|
|
30810
|
+
* The join's own verdict, shipped with the row so it is auditable
|
|
30811
|
+
* rather than a claim made in a comment. 'ambiguous' and 'none' are
|
|
30812
|
+
* deliberately distinguished: 'none' means the JS handler never fired
|
|
30813
|
+
* (a real capture gap on this platform), 'ambiguous' means it fired
|
|
30814
|
+
* more than once too close together to attribute. Those have
|
|
30815
|
+
* different fixes, and collapsing them would hide which one is
|
|
30816
|
+
* actually happening in production.
|
|
30817
|
+
*/
|
|
30818
|
+
correlation: correlation.outcome,
|
|
30819
|
+
correlationDeltaMs: correlation.deltaMs,
|
|
30820
|
+
correlationRunnerUpDeltaMs: correlation.runnerUpDeltaMs,
|
|
30512
30821
|
emitAutomaticAnalytics: start?.emitAutomaticAnalytics ?? captureAutomaticInteractions,
|
|
30513
30822
|
x: event.rawX,
|
|
30514
30823
|
y: event.rawY,
|
|
@@ -30580,6 +30889,7 @@ function ScaleBunDebugRoot({
|
|
|
30580
30889
|
const target = resolveTouchTarget(e);
|
|
30581
30890
|
let ui;
|
|
30582
30891
|
let screenName;
|
|
30892
|
+
let screenSource = "none";
|
|
30583
30893
|
try {
|
|
30584
30894
|
const {
|
|
30585
30895
|
uiStateSignature: uiStateSignature2
|
|
@@ -30591,7 +30901,9 @@ function ScaleBunDebugRoot({
|
|
|
30591
30901
|
const {
|
|
30592
30902
|
AutoScreenDetector: AutoScreenDetector2
|
|
30593
30903
|
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
30594
|
-
|
|
30904
|
+
const detector = AutoScreenDetector2.getInstance();
|
|
30905
|
+
screenName = detector.getCurrentScreen() || void 0;
|
|
30906
|
+
screenSource = screenName ? detector.getCurrentScreenSource() : "none";
|
|
30595
30907
|
} catch {
|
|
30596
30908
|
}
|
|
30597
30909
|
const start = {
|
|
@@ -30601,9 +30913,91 @@ function ScaleBunDebugRoot({
|
|
|
30601
30913
|
y: touch.pageY,
|
|
30602
30914
|
locationX: touch.locationX,
|
|
30603
30915
|
locationY: touch.locationY,
|
|
30916
|
+
/**
|
|
30917
|
+
* RN-1 + RN-2 — the identity that is persisted, and it is now code-derived.
|
|
30918
|
+
*
|
|
30919
|
+
* `describeTouchTarget` returns `testID` -> structural path -> component name,
|
|
30920
|
+
* and never a caption or an accessibilityLabel. Before RN-2 it preferred free
|
|
30921
|
+
* text, so this line was writing user data ("Pay EGP 1,442", an order row
|
|
30922
|
+
* labelled with an email address, a contact's name) into the analytics store —
|
|
30923
|
+
* and, because `target` is an aggregation key, splitting one button into one
|
|
30924
|
+
* bucket per person at the same time.
|
|
30925
|
+
*
|
|
30926
|
+
* `targetId` used to be `target?.testID` alone, which is `undefined` in the
|
|
30927
|
+
* overwhelming majority of apps — no testIDs authored — so the field the
|
|
30928
|
+
* backend keys on was empty almost always. It now falls back to the structural
|
|
30929
|
+
* path, with `targetSource` recording WHICH it got.
|
|
30930
|
+
*/
|
|
30604
30931
|
target: describeTouchTarget(target),
|
|
30605
|
-
targetId: target
|
|
30932
|
+
targetId: targetIdentity(target).id,
|
|
30933
|
+
/**
|
|
30934
|
+
* WHICH KIND of identity this is, because they are not equally trustworthy: a
|
|
30935
|
+
* `testID` is a promise from the app's authors that this control is this
|
|
30936
|
+
* control; a `path` is an inference that breaks when the tree is refactored.
|
|
30937
|
+
* Without this the accounting lane cannot distinguish "the control had no
|
|
30938
|
+
* identity" from "the identity is a structural guess", and both would render
|
|
30939
|
+
* as an equally confident target.
|
|
30940
|
+
*/
|
|
30941
|
+
targetSource: targetIdentity(target).source,
|
|
30942
|
+
/* RN-4 — row identity from the app's own keyExtractor; see touchTarget.ts. */
|
|
30943
|
+
itemKey: target?.itemKey,
|
|
30944
|
+
itemIndex: target?.itemIndex,
|
|
30945
|
+
/**
|
|
30946
|
+
* RN-13 — the control's rectangle, so `heat ∈ target` is decidable.
|
|
30947
|
+
*
|
|
30948
|
+
* Read SYNCHRONOUSLY here, in the same tick as the touch, and normalized against
|
|
30949
|
+
* the SAME measured root the tap's own coordinates use. Both halves matter:
|
|
30950
|
+
*
|
|
30951
|
+
* - A callback measurement (`measureInWindow`) would resolve after this handler
|
|
30952
|
+
* returns, so the rect attached to a tap could be the previous tap's, with
|
|
30953
|
+
* nothing afterwards able to tell. `targetGeometry` refuses to guess: it reads
|
|
30954
|
+
* now or reports null.
|
|
30955
|
+
* - Normalizing against a window size instead of the root view is off by the
|
|
30956
|
+
* status bar on most devices, which would read as "the heat is just outside
|
|
30957
|
+
* the button" everywhere rather than as a measurement bug.
|
|
30958
|
+
*
|
|
30959
|
+
* `measureRoot()` is called first because the cached root rect is populated
|
|
30960
|
+
* asynchronously at mount; without it the first taps of a session normalize
|
|
30961
|
+
* against nothing and get a null rect for a reason that has nothing to do with
|
|
30962
|
+
* the control.
|
|
30963
|
+
*/
|
|
30964
|
+
/**
|
|
30965
|
+
* RN-3 — WHERE IN THE CONTENT, not just where on the glass.
|
|
30966
|
+
*
|
|
30967
|
+
* Two taps at the same screen coordinate one viewport apart are two different
|
|
30968
|
+
* controls. Without an offset they merge into one hotspot, and the heatmap shows
|
|
30969
|
+
* heavy engagement at a position where, in content terms, nothing in particular
|
|
30970
|
+
* is. The offset is also a CACHE dimension — two scroll states of one route must
|
|
30971
|
+
* not share a backdrop — so its absence corrupts the picture as well as the count.
|
|
30972
|
+
*
|
|
30973
|
+
* `ancestors` is innermost-first, so this takes the NEAREST scroller that has a
|
|
30974
|
+
* recorded position: a tap inside a horizontal carousel belongs to the carousel's
|
|
30975
|
+
* content, not to how far the page behind it had scrolled.
|
|
30976
|
+
*
|
|
30977
|
+
* Null when no ancestor has one — never zero. Zero is a real scroll position (the
|
|
30978
|
+
* top of a list), and reporting it for "unknown" would place every unmeasured tap
|
|
30979
|
+
* at the top of its content, indistinguishable from a genuine tap there.
|
|
30980
|
+
*/
|
|
30981
|
+
...(() => {
|
|
30982
|
+
const hit = nearestRecordedOffset((target?.ancestors ?? []).map((n) => nativeTagOf(n)));
|
|
30983
|
+
return hit ? {
|
|
30984
|
+
scrollX: hit.x,
|
|
30985
|
+
scrollY: hit.y,
|
|
30986
|
+
scrollSource: "wrapper"
|
|
30987
|
+
} : {
|
|
30988
|
+
scrollSource: "none"
|
|
30989
|
+
};
|
|
30990
|
+
})(),
|
|
30991
|
+
targetRect: (() => {
|
|
30992
|
+
measureRoot();
|
|
30993
|
+
const root = rootRectRef.current;
|
|
30994
|
+
return normalizeRect(readRectSync(target?.node), root ? {
|
|
30995
|
+
w: root.w,
|
|
30996
|
+
h: root.h
|
|
30997
|
+
} : null) ?? void 0;
|
|
30998
|
+
})(),
|
|
30606
30999
|
screenName,
|
|
31000
|
+
screenSource,
|
|
30607
31001
|
ui,
|
|
30608
31002
|
stateStatus: ui ? "captured_nonempty" : "not_instrumented",
|
|
30609
31003
|
emitAutomaticAnalytics: captureAutomaticInteractions
|
|
@@ -30640,7 +31034,12 @@ function ScaleBunDebugRoot({
|
|
|
30640
31034
|
}
|
|
30641
31035
|
measureRoot();
|
|
30642
31036
|
const rootRect = rootRectRef.current;
|
|
30643
|
-
const
|
|
31037
|
+
const freshTarget = start ? null : resolveTouchTarget(e);
|
|
31038
|
+
const tapped = start?.target ?? describeTouchTarget(freshTarget);
|
|
31039
|
+
const identity = start ? {
|
|
31040
|
+
id: start.targetId,
|
|
31041
|
+
source: start.targetSource
|
|
31042
|
+
} : targetIdentity(freshTarget);
|
|
30644
31043
|
const gestureDetails = {
|
|
30645
31044
|
target: tapped,
|
|
30646
31045
|
interactionId: start?.interactionId ?? generateInteractionId(),
|
|
@@ -30648,8 +31047,16 @@ function ScaleBunDebugRoot({
|
|
|
30648
31047
|
occurredAt: start?.occurredAt ?? Date.now(),
|
|
30649
31048
|
ui: start?.ui,
|
|
30650
31049
|
stateStatus: start?.stateStatus ?? "not_captured",
|
|
30651
|
-
targetId:
|
|
31050
|
+
targetId: identity.id,
|
|
31051
|
+
targetSource: identity.source,
|
|
30652
31052
|
screenName: start?.screenName,
|
|
31053
|
+
screenSource: start?.screenSource ?? "none",
|
|
31054
|
+
targetRect: start?.targetRect,
|
|
31055
|
+
scrollX: start?.scrollX,
|
|
31056
|
+
scrollY: start?.scrollY,
|
|
31057
|
+
scrollSource: start?.scrollSource ?? "none",
|
|
31058
|
+
itemKey: start?.itemKey,
|
|
31059
|
+
itemIndex: start?.itemIndex,
|
|
30653
31060
|
emitAutomaticAnalytics: start?.emitAutomaticAnalytics ?? captureAutomaticInteractions,
|
|
30654
31061
|
x: start?.x ?? touch.pageX,
|
|
30655
31062
|
y: start?.y ?? touch.pageY,
|
|
@@ -30665,6 +31072,11 @@ function ScaleBunDebugRoot({
|
|
|
30665
31072
|
const ny = Math.max(0, Math.min(1, (start?.y ?? touch.pageY) / rootRect.h));
|
|
30666
31073
|
gestureDetails.normalizedX = nx;
|
|
30667
31074
|
gestureDetails.normalizedY = ny;
|
|
31075
|
+
const lp = localPoint(start?.targetRect ?? null, nx, ny);
|
|
31076
|
+
if (lp) {
|
|
31077
|
+
gestureDetails.localU = lp.u;
|
|
31078
|
+
gestureDetails.localV = lp.v;
|
|
31079
|
+
}
|
|
30668
31080
|
gestureDetails.normalizedPrecomputed = true;
|
|
30669
31081
|
gestureDetails.viewportWidth = rootRect.w;
|
|
30670
31082
|
gestureDetails.viewportHeight = rootRect.h;
|
|
@@ -30793,6 +31205,13 @@ function useScaleBunScreen(name) {
|
|
|
30793
31205
|
}
|
|
30794
31206
|
} catch {
|
|
30795
31207
|
}
|
|
31208
|
+
try {
|
|
31209
|
+
const {
|
|
31210
|
+
AutoScreenDetector: AutoScreenDetector2
|
|
31211
|
+
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
31212
|
+
AutoScreenDetector2.getInstance().setManualScreen(name);
|
|
31213
|
+
} catch {
|
|
31214
|
+
}
|
|
30796
31215
|
try {
|
|
30797
31216
|
const bs = getBootstrap();
|
|
30798
31217
|
const perfFeature = bs?.getPerformanceFeature?.();
|
|
@@ -30943,6 +31362,18 @@ var ScaleBunScrollView = /* @__PURE__ */ import_react12.default.forwardRef(funct
|
|
|
30943
31362
|
}
|
|
30944
31363
|
} catch {
|
|
30945
31364
|
}
|
|
31365
|
+
try {
|
|
31366
|
+
const {
|
|
31367
|
+
recordScrollOffset: recordScrollOffset2,
|
|
31368
|
+
nativeTagOf: nativeTagOf2
|
|
31369
|
+
} = (init_scrollContext(), __toCommonJS(scrollContext_exports));
|
|
31370
|
+
const {
|
|
31371
|
+
contentOffset,
|
|
31372
|
+
target
|
|
31373
|
+
} = event.nativeEvent;
|
|
31374
|
+
recordScrollOffset2(typeof target === "number" ? target : nativeTagOf2(target), contentOffset.x, contentOffset.y);
|
|
31375
|
+
} catch {
|
|
31376
|
+
}
|
|
30946
31377
|
if (userOnScroll) {
|
|
30947
31378
|
userOnScroll(event);
|
|
30948
31379
|
}
|