@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.full.js
CHANGED
|
@@ -89,9 +89,9 @@ var init_internalLogger = __esm({
|
|
|
89
89
|
}
|
|
90
90
|
/** Set level from the public string name used in config (e.g. 'warn'). */
|
|
91
91
|
setLevelByName(name) {
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
this.level =
|
|
92
|
+
const resolved = LEVEL_BY_NAME[name];
|
|
93
|
+
if (resolved !== void 0) {
|
|
94
|
+
this.level = resolved;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
/** Current numeric level — exposed for callers that branch on dev verbosity. */
|
|
@@ -605,7 +605,7 @@ var SDK_VERSION;
|
|
|
605
605
|
var init_version = __esm({
|
|
606
606
|
"lib/module/core/constants/version.js"() {
|
|
607
607
|
"use strict";
|
|
608
|
-
SDK_VERSION = "
|
|
608
|
+
SDK_VERSION = "2.0.1";
|
|
609
609
|
}
|
|
610
610
|
});
|
|
611
611
|
|
|
@@ -3461,6 +3461,15 @@ var init_AutoScreenDetector = __esm({
|
|
|
3461
3461
|
_manualScreen = null;
|
|
3462
3462
|
_manualScreenTs = 0;
|
|
3463
3463
|
_autoScreen = null;
|
|
3464
|
+
/**
|
|
3465
|
+
* When `_autoScreen` was last written.
|
|
3466
|
+
*
|
|
3467
|
+
* Needed so `getCurrentScreen()` can prefer whichever source spoke MOST RECENTLY.
|
|
3468
|
+
* Without a timestamp the only comparison available was "is the manual value still
|
|
3469
|
+
* fresh", which is a different question and gave the wrong answer — see the note on
|
|
3470
|
+
* `_currentScreen()`.
|
|
3471
|
+
*/
|
|
3472
|
+
_autoScreenTs = 0;
|
|
3464
3473
|
/** `name#key` of the last auto-detected screen VISIT — see _extractActiveVisit. */
|
|
3465
3474
|
_autoVisitSig = null;
|
|
3466
3475
|
_navigationRef = null;
|
|
@@ -3528,6 +3537,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
3528
3537
|
if (this.isManualScreenActive()) return;
|
|
3529
3538
|
if (name !== this._autoScreen) {
|
|
3530
3539
|
this._autoScreen = name;
|
|
3540
|
+
this._autoScreenTs = Date.now();
|
|
3531
3541
|
this._autoVisitSig = name;
|
|
3532
3542
|
this._emitScreenChange(name, "heuristic");
|
|
3533
3543
|
}
|
|
@@ -3535,8 +3545,19 @@ var init_AutoScreenDetector = __esm({
|
|
|
3535
3545
|
}
|
|
3536
3546
|
}
|
|
3537
3547
|
/**
|
|
3538
|
-
*
|
|
3539
|
-
*
|
|
3548
|
+
* Should a WRITER defer to the manual screen right now?
|
|
3549
|
+
*
|
|
3550
|
+
* THIS IS A PRECEDENCE GATE, NOT A VALIDITY TEST, and conflating the two was a real
|
|
3551
|
+
* bug. Its job is what the cooldown constant says: for a short window after a manual
|
|
3552
|
+
* beacon fires, an auto source must not overwrite it — the poller runs every 1500 ms
|
|
3553
|
+
* and would otherwise clobber a fresh declaration with whatever it inferred.
|
|
3554
|
+
*
|
|
3555
|
+
* `getCurrentScreen()` used to reuse this as "is the manual screen still the current
|
|
3556
|
+
* screen", which made a declared screen READABLE for only 2000 ms. Nothing called
|
|
3557
|
+
* `setManualScreen` in production, so the effect was latent — but the moment the
|
|
3558
|
+
* `<ScaleBunScreen>` beacon was wired to it (RN-8), every tap more than two seconds
|
|
3559
|
+
* after a screen mounted would have lost its name, which is very nearly every tap. A
|
|
3560
|
+
* fix that introduced that would have been worse than the gap it closed.
|
|
3540
3561
|
*/
|
|
3541
3562
|
isManualScreenActive() {
|
|
3542
3563
|
if (!this._manualScreen) return false;
|
|
@@ -3582,6 +3603,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
3582
3603
|
const visit = this._extractActiveVisit(state);
|
|
3583
3604
|
if (visit && this._visitSig(visit) !== this._autoVisitSig) {
|
|
3584
3605
|
this._autoScreen = visit.name;
|
|
3606
|
+
this._autoScreenTs = Date.now();
|
|
3585
3607
|
this._autoVisitSig = this._visitSig(visit);
|
|
3586
3608
|
this._emitScreenChange(visit.name, "navRef");
|
|
3587
3609
|
}
|
|
@@ -3600,6 +3622,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
3600
3622
|
const visit = this._extractActiveVisit(state);
|
|
3601
3623
|
if (visit) {
|
|
3602
3624
|
this._autoScreen = visit.name;
|
|
3625
|
+
this._autoScreenTs = Date.now();
|
|
3603
3626
|
this._autoVisitSig = this._visitSig(visit);
|
|
3604
3627
|
this._emitScreenChange(visit.name, "navRef");
|
|
3605
3628
|
}
|
|
@@ -3630,6 +3653,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
3630
3653
|
const visit = this._extractActiveVisit(state);
|
|
3631
3654
|
if (visit && this._visitSig(visit) !== this._autoVisitSig) {
|
|
3632
3655
|
this._autoScreen = visit.name;
|
|
3656
|
+
this._autoScreenTs = Date.now();
|
|
3633
3657
|
this._autoVisitSig = this._visitSig(visit);
|
|
3634
3658
|
this._emitScreenChange(visit.name, "heuristic");
|
|
3635
3659
|
}
|
|
@@ -3728,7 +3752,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
3728
3752
|
if (!reactNav || reactNav.__scalebunAutoHooked) return;
|
|
3729
3753
|
const detector = this;
|
|
3730
3754
|
let installed2 = 0;
|
|
3731
|
-
const
|
|
3755
|
+
const redefine2 = (key, value) => {
|
|
3732
3756
|
try {
|
|
3733
3757
|
Object.defineProperty(reactNav, key, {
|
|
3734
3758
|
value,
|
|
@@ -3778,7 +3802,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
3778
3802
|
});
|
|
3779
3803
|
});
|
|
3780
3804
|
Wrapped.displayName = "ScaleBunNavigationContainer";
|
|
3781
|
-
if (
|
|
3805
|
+
if (redefine2("NavigationContainer", Wrapped)) installed2++;
|
|
3782
3806
|
}
|
|
3783
3807
|
if (typeof reactNav.createNavigationContainerRef === "function") {
|
|
3784
3808
|
const originalCreate = reactNav.createNavigationContainerRef;
|
|
@@ -3790,7 +3814,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
3790
3814
|
}
|
|
3791
3815
|
return ref;
|
|
3792
3816
|
};
|
|
3793
|
-
if (
|
|
3817
|
+
if (redefine2("createNavigationContainerRef", patchedCreate)) installed2++;
|
|
3794
3818
|
}
|
|
3795
3819
|
if (typeof reactNav.useNavigationContainerRef === "function") {
|
|
3796
3820
|
const originalHook = reactNav.useNavigationContainerRef;
|
|
@@ -3802,7 +3826,7 @@ var init_AutoScreenDetector = __esm({
|
|
|
3802
3826
|
}
|
|
3803
3827
|
return ref;
|
|
3804
3828
|
};
|
|
3805
|
-
if (
|
|
3829
|
+
if (redefine2("useNavigationContainerRef", patchedHook)) installed2++;
|
|
3806
3830
|
}
|
|
3807
3831
|
this._autoHookInstalled = installed2 > 0;
|
|
3808
3832
|
try {
|
|
@@ -3851,15 +3875,58 @@ var init_AutoScreenDetector = __esm({
|
|
|
3851
3875
|
this._listeners = [];
|
|
3852
3876
|
this._manualScreen = null;
|
|
3853
3877
|
this._autoScreen = null;
|
|
3878
|
+
this._autoScreenTs = 0;
|
|
3854
3879
|
this._navigationRef = null;
|
|
3855
3880
|
_instance = null;
|
|
3856
3881
|
}
|
|
3857
3882
|
/** Get the current best-known screen name */
|
|
3858
3883
|
getCurrentScreen() {
|
|
3859
|
-
|
|
3860
|
-
|
|
3884
|
+
return this._currentScreen().name;
|
|
3885
|
+
}
|
|
3886
|
+
/**
|
|
3887
|
+
* WHERE the current screen name came from, or 'none'.
|
|
3888
|
+
*
|
|
3889
|
+
* Recorded on every interaction (RN-8) so low screen coverage is DIAGNOSABLE rather
|
|
3890
|
+
* than merely visible. Production shows 13.8% of Android interactions carrying a
|
|
3891
|
+
* screen and 0.5% on iOS, and those numbers cannot today distinguish three different
|
|
3892
|
+
* causes: the app never instrumented anything, the app instrumented manually and the
|
|
3893
|
+
* value never reached interactions, or auto-detection ran and failed. Those have
|
|
3894
|
+
* different owners and different fixes, and guessing between them is how a P0 stays
|
|
3895
|
+
* open.
|
|
3896
|
+
*/
|
|
3897
|
+
getCurrentScreenSource() {
|
|
3898
|
+
return this._currentScreen().source;
|
|
3899
|
+
}
|
|
3900
|
+
/**
|
|
3901
|
+
* LAST WRITER WINS between the manual beacon and auto-detection.
|
|
3902
|
+
*
|
|
3903
|
+
* A screen declared by `<ScaleBunScreen name>` stays current until something else
|
|
3904
|
+
* declares a different one — that is what mounting a screen means. It does not
|
|
3905
|
+
* expire, which is the correction here: the previous rule returned the manual name
|
|
3906
|
+
* only while it was under 2000 ms old and then silently fell back to the auto value,
|
|
3907
|
+
* which in a manually-instrumented app is `null`.
|
|
3908
|
+
*
|
|
3909
|
+
* Comparing timestamps rather than hard-coding a priority also handles the real
|
|
3910
|
+
* interleaving: an app can use React Navigation for most screens AND drop a manual
|
|
3911
|
+
* beacon on one modal, and whichever spoke last is the truth.
|
|
3912
|
+
*/
|
|
3913
|
+
_currentScreen() {
|
|
3914
|
+
const manual = this._manualScreen;
|
|
3915
|
+
const auto = this._autoScreen;
|
|
3916
|
+
if (manual && (!auto || this._manualScreenTs >= this._autoScreenTs)) {
|
|
3917
|
+
return {
|
|
3918
|
+
name: manual,
|
|
3919
|
+
source: "manual"
|
|
3920
|
+
};
|
|
3861
3921
|
}
|
|
3862
|
-
return
|
|
3922
|
+
if (auto) return {
|
|
3923
|
+
name: auto,
|
|
3924
|
+
source: "navigation"
|
|
3925
|
+
};
|
|
3926
|
+
return {
|
|
3927
|
+
name: null,
|
|
3928
|
+
source: "none"
|
|
3929
|
+
};
|
|
3863
3930
|
}
|
|
3864
3931
|
// ─── Internal ───────────────────────────────────────────────────────
|
|
3865
3932
|
_emitScreenChange(name, source) {
|
|
@@ -5740,17 +5807,54 @@ var init_automaticEvents = __esm({
|
|
|
5740
5807
|
function generateInteractionId() {
|
|
5741
5808
|
return `ixj-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
5742
5809
|
}
|
|
5743
|
-
function
|
|
5744
|
-
|
|
5745
|
-
let
|
|
5746
|
-
|
|
5810
|
+
function correlateInteraction(starts, occurredAt, toleranceMs = CORRELATION_TOLERANCE_MS, marginMs = CORRELATION_MARGIN_MS) {
|
|
5811
|
+
const candidates = [];
|
|
5812
|
+
for (let i = 0; i < starts.length; i++) {
|
|
5813
|
+
const start = starts[i];
|
|
5814
|
+
if (!start) continue;
|
|
5747
5815
|
const delta = Math.abs(start.occurredAt - occurredAt);
|
|
5748
|
-
if (delta
|
|
5749
|
-
|
|
5750
|
-
|
|
5816
|
+
if (delta <= toleranceMs) candidates.push({
|
|
5817
|
+
index: i,
|
|
5818
|
+
delta
|
|
5819
|
+
});
|
|
5820
|
+
}
|
|
5821
|
+
if (candidates.length === 0) {
|
|
5822
|
+
return {
|
|
5823
|
+
index: -1,
|
|
5824
|
+
outcome: "none",
|
|
5825
|
+
deltaMs: null,
|
|
5826
|
+
runnerUpDeltaMs: null
|
|
5827
|
+
};
|
|
5828
|
+
}
|
|
5829
|
+
candidates.sort((a, b) => a.delta - b.delta || a.index - b.index);
|
|
5830
|
+
const best = candidates[0];
|
|
5831
|
+
const runnerUp = candidates[1];
|
|
5832
|
+
if (runnerUp && runnerUp.delta - best.delta < marginMs) {
|
|
5833
|
+
return {
|
|
5834
|
+
index: -1,
|
|
5835
|
+
outcome: "ambiguous",
|
|
5836
|
+
deltaMs: best.delta,
|
|
5837
|
+
runnerUpDeltaMs: runnerUp.delta
|
|
5838
|
+
};
|
|
5839
|
+
}
|
|
5840
|
+
return {
|
|
5841
|
+
start: starts[best.index],
|
|
5842
|
+
index: best.index,
|
|
5843
|
+
outcome: "exact",
|
|
5844
|
+
deltaMs: best.delta,
|
|
5845
|
+
runnerUpDeltaMs: runnerUp ? runnerUp.delta : null
|
|
5846
|
+
};
|
|
5847
|
+
}
|
|
5848
|
+
function pruneInteractionStarts(starts, now2, toleranceMs = CORRELATION_TOLERANCE_MS) {
|
|
5849
|
+
let removed = 0;
|
|
5850
|
+
for (let i = starts.length - 1; i >= 0; i--) {
|
|
5851
|
+
const start = starts[i];
|
|
5852
|
+
if (!start || now2 - start.occurredAt > toleranceMs) {
|
|
5853
|
+
starts.splice(i, 1);
|
|
5854
|
+
removed++;
|
|
5751
5855
|
}
|
|
5752
5856
|
}
|
|
5753
|
-
return
|
|
5857
|
+
return removed;
|
|
5754
5858
|
}
|
|
5755
5859
|
function automaticInteractionProperties(payload, screenName, canonicalMirror) {
|
|
5756
5860
|
return {
|
|
@@ -5768,11 +5872,13 @@ function automaticInteractionProperties(payload, screenName, canonicalMirror) {
|
|
|
5768
5872
|
canonical_mirror: canonicalMirror || void 0
|
|
5769
5873
|
};
|
|
5770
5874
|
}
|
|
5771
|
-
var INTERACTION_PROTOCOL_VERSION;
|
|
5875
|
+
var INTERACTION_PROTOCOL_VERSION, CORRELATION_TOLERANCE_MS, CORRELATION_MARGIN_MS;
|
|
5772
5876
|
var init_interactionProtocol = __esm({
|
|
5773
5877
|
"lib/module/features/journey/interactionProtocol.js"() {
|
|
5774
5878
|
"use strict";
|
|
5775
5879
|
INTERACTION_PROTOCOL_VERSION = 1;
|
|
5880
|
+
CORRELATION_TOLERANCE_MS = 1500;
|
|
5881
|
+
CORRELATION_MARGIN_MS = 100;
|
|
5776
5882
|
}
|
|
5777
5883
|
});
|
|
5778
5884
|
|
|
@@ -6515,6 +6621,69 @@ var init_SessionManager = __esm({
|
|
|
6515
6621
|
state_status: stateStatus,
|
|
6516
6622
|
ui: details?.ui,
|
|
6517
6623
|
target_id: details?.targetId,
|
|
6624
|
+
/**
|
|
6625
|
+
* RN-6 — shipped with the row so the join is auditable instead of assumed.
|
|
6626
|
+
*
|
|
6627
|
+
* Without it, an interaction with no `target_id` is ambiguous in the worst way:
|
|
6628
|
+
* it could mean the control genuinely had no identity, or that the correlation
|
|
6629
|
+
* refused and the identity exists but could not be attributed. Those are
|
|
6630
|
+
* different defects with different owners, and the accounting lane cannot give
|
|
6631
|
+
* an honest unplaceable REASON without knowing which.
|
|
6632
|
+
*
|
|
6633
|
+
* Undefined on the JS-fallback path (no native event, so nothing was joined),
|
|
6634
|
+
* which is itself the correct answer there rather than a fabricated 'exact'.
|
|
6635
|
+
*/
|
|
6636
|
+
correlation: details?.correlation,
|
|
6637
|
+
correlation_delta_ms: details?.correlationDeltaMs,
|
|
6638
|
+
correlation_runner_up_delta_ms: details?.correlationRunnerUpDeltaMs,
|
|
6639
|
+
/**
|
|
6640
|
+
* RN-1/RN-2 — stored alongside `target_id` so a reader can tell an authored
|
|
6641
|
+
* identifier from a structural inference. 'path' means the app authored no testID
|
|
6642
|
+
* and the identity was derived from component names, which is useful but will
|
|
6643
|
+
* change under refactoring; a dashboard that presents the two identically invites
|
|
6644
|
+
* someone to trust a key that is about to move.
|
|
6645
|
+
*/
|
|
6646
|
+
target_source: details?.targetSource,
|
|
6647
|
+
/**
|
|
6648
|
+
* RN-8 — stored so "13.8% of interactions have a screen" becomes answerable.
|
|
6649
|
+
* 'none' means the app instrumented nothing the SDK could see; 'manual' means a
|
|
6650
|
+
* <ScaleBunScreen> beacon; 'navigation' means the React Navigation hook. Three
|
|
6651
|
+
* different owners behind one number that today has none.
|
|
6652
|
+
*/
|
|
6653
|
+
screen_source: details?.screenSource,
|
|
6654
|
+
/**
|
|
6655
|
+
* RN-13 — what makes "the heat is on this control" checkable instead of assumed.
|
|
6656
|
+
* Absent when the control could not be measured synchronously, which is a fact worth
|
|
6657
|
+
* storing: an unmeasurable control and a mis-placed tap are different findings.
|
|
6658
|
+
*/
|
|
6659
|
+
target_rect: details?.targetRect,
|
|
6660
|
+
/**
|
|
6661
|
+
* RN-3 — the backend already reads scrollX/scrollY off the payload into the fact
|
|
6662
|
+
* table's own columns (it has done since the web SDK, which sends `sx`/`sy`), so
|
|
6663
|
+
* these need no migration. Mobile simply never sent them.
|
|
6664
|
+
*
|
|
6665
|
+
* `scroll_source` says whether an offset was available at all: 'none' means the app
|
|
6666
|
+
* uses a plain <ScrollView> rather than the SDK's wrapper, which is a coverage gap in
|
|
6667
|
+
* the integration and not a tap at the top of a list.
|
|
6668
|
+
*/
|
|
6669
|
+
scrollX: details?.scrollX,
|
|
6670
|
+
scrollY: details?.scrollY,
|
|
6671
|
+
scroll_source: details?.scrollSource,
|
|
6672
|
+
/**
|
|
6673
|
+
* RN-4 — what stops a recycled native view merging two different rows into one
|
|
6674
|
+
* hotspot. The key is the app's own keyExtractor output, so it is stable across
|
|
6675
|
+
* scrolling and across sessions; the index is carried so a recycled slot (same index,
|
|
6676
|
+
* different key) can be proven rather than inferred.
|
|
6677
|
+
*/
|
|
6678
|
+
item_key: details?.itemKey,
|
|
6679
|
+
item_index: details?.itemIndex,
|
|
6680
|
+
/**
|
|
6681
|
+
* RN-15 — where in the control, not just which control. Turns "they tapped Add to
|
|
6682
|
+
* Cart" into "they tapped its left edge", which is what tells you whether a hit area
|
|
6683
|
+
* is right. Outside 0..1 is a hitSlop tap and is kept as such.
|
|
6684
|
+
*/
|
|
6685
|
+
local_u: details?.localU,
|
|
6686
|
+
local_v: details?.localV,
|
|
6518
6687
|
gestureType,
|
|
6519
6688
|
x: details?.x,
|
|
6520
6689
|
y: details?.y,
|
|
@@ -8553,8 +8722,8 @@ var init_PerformanceTransport = __esm({
|
|
|
8553
8722
|
setSink(sink) {
|
|
8554
8723
|
this.sink = sink;
|
|
8555
8724
|
if (sink && this.buffer.length > 0) {
|
|
8556
|
-
const
|
|
8557
|
-
for (const msg of
|
|
8725
|
+
const pending5 = this.buffer.splice(0);
|
|
8726
|
+
for (const msg of pending5) {
|
|
8558
8727
|
try {
|
|
8559
8728
|
sink.send(msg);
|
|
8560
8729
|
} catch {
|
|
@@ -9327,8 +9496,8 @@ var init_ProfilerTransport = __esm({
|
|
|
9327
9496
|
setSink(sink) {
|
|
9328
9497
|
this.sink = sink;
|
|
9329
9498
|
if (sink && this.buffer.length > 0) {
|
|
9330
|
-
const
|
|
9331
|
-
for (const msg of
|
|
9499
|
+
const pending5 = this.buffer.splice(0);
|
|
9500
|
+
for (const msg of pending5) {
|
|
9332
9501
|
try {
|
|
9333
9502
|
sink.send(msg);
|
|
9334
9503
|
} catch {
|
|
@@ -10216,10 +10385,10 @@ function requestExport(transport, enabled, presetOrOptions) {
|
|
|
10216
10385
|
return;
|
|
10217
10386
|
}
|
|
10218
10387
|
const timer = setTimeout(() => {
|
|
10219
|
-
const
|
|
10220
|
-
if (
|
|
10388
|
+
const pending5 = _pendingExports.get(requestId);
|
|
10389
|
+
if (pending5) {
|
|
10221
10390
|
_pendingExports.delete(requestId);
|
|
10222
|
-
|
|
10391
|
+
pending5.resolve({
|
|
10223
10392
|
success: false,
|
|
10224
10393
|
error: `Export timed out after ${EXPORT_TIMEOUT_MS / 1e3}s`
|
|
10225
10394
|
});
|
|
@@ -10261,14 +10430,14 @@ function handleExportResult(args) {
|
|
|
10261
10430
|
try {
|
|
10262
10431
|
const requestId = args.requestId;
|
|
10263
10432
|
if (!requestId) return;
|
|
10264
|
-
const
|
|
10265
|
-
if (!
|
|
10433
|
+
const pending5 = _pendingExports.get(requestId);
|
|
10434
|
+
if (!pending5) {
|
|
10266
10435
|
logger.warn(`[ExportBridge] Received result for unknown requestId: ${requestId}`);
|
|
10267
10436
|
return;
|
|
10268
10437
|
}
|
|
10269
|
-
clearTimeout(
|
|
10438
|
+
clearTimeout(pending5.timer);
|
|
10270
10439
|
_pendingExports.delete(requestId);
|
|
10271
|
-
|
|
10440
|
+
pending5.resolve({
|
|
10272
10441
|
success: args.success ?? false,
|
|
10273
10442
|
filePath: args.filePath,
|
|
10274
10443
|
error: args.error,
|
|
@@ -10279,9 +10448,9 @@ function handleExportResult(args) {
|
|
|
10279
10448
|
}
|
|
10280
10449
|
}
|
|
10281
10450
|
function teardownExports() {
|
|
10282
|
-
for (const [,
|
|
10283
|
-
clearTimeout(
|
|
10284
|
-
|
|
10451
|
+
for (const [, pending5] of _pendingExports) {
|
|
10452
|
+
clearTimeout(pending5.timer);
|
|
10453
|
+
pending5.reject(new Error("Debug disabled \u2014 export cancelled"));
|
|
10285
10454
|
}
|
|
10286
10455
|
_pendingExports.clear();
|
|
10287
10456
|
}
|
|
@@ -11489,6 +11658,174 @@ var init_installationId = __esm({
|
|
|
11489
11658
|
}
|
|
11490
11659
|
});
|
|
11491
11660
|
|
|
11661
|
+
// lib/module/features/journey/scrollContext.js
|
|
11662
|
+
var scrollContext_exports = {};
|
|
11663
|
+
__export(scrollContext_exports, {
|
|
11664
|
+
SCROLL_OFFSET_TTL_MS: () => SCROLL_OFFSET_TTL_MS,
|
|
11665
|
+
clearScrollOffsets: () => clearScrollOffsets,
|
|
11666
|
+
nativeTagOf: () => nativeTagOf,
|
|
11667
|
+
nearestRecordedOffset: () => nearestRecordedOffset,
|
|
11668
|
+
recordScrollOffset: () => recordScrollOffset,
|
|
11669
|
+
trackedScrollerCount: () => trackedScrollerCount
|
|
11670
|
+
});
|
|
11671
|
+
function nativeTagOf(node) {
|
|
11672
|
+
if (!node || typeof node !== "object") return null;
|
|
11673
|
+
const n = node;
|
|
11674
|
+
const tag = typeof n.__nativeTag === "number" ? n.__nativeTag : n._nativeTag;
|
|
11675
|
+
return typeof tag === "number" && Number.isFinite(tag) ? tag : null;
|
|
11676
|
+
}
|
|
11677
|
+
function recordScrollOffset(tag, x, y, now2 = Date.now()) {
|
|
11678
|
+
if (tag === null || !Number.isFinite(x) || !Number.isFinite(y)) return;
|
|
11679
|
+
offsets.set(tag, {
|
|
11680
|
+
x,
|
|
11681
|
+
y,
|
|
11682
|
+
at: now2
|
|
11683
|
+
});
|
|
11684
|
+
if (offsets.size > MAX_TRACKED_SCROLLERS) {
|
|
11685
|
+
let oldestTag = null;
|
|
11686
|
+
let oldestAt = Infinity;
|
|
11687
|
+
for (const [k, v] of offsets) {
|
|
11688
|
+
if (v.at < oldestAt) {
|
|
11689
|
+
oldestAt = v.at;
|
|
11690
|
+
oldestTag = k;
|
|
11691
|
+
}
|
|
11692
|
+
}
|
|
11693
|
+
if (oldestTag !== null) offsets.delete(oldestTag);
|
|
11694
|
+
}
|
|
11695
|
+
}
|
|
11696
|
+
function nearestRecordedOffset(ancestorTags, now2 = Date.now()) {
|
|
11697
|
+
for (const tag of ancestorTags) {
|
|
11698
|
+
if (tag === null) continue;
|
|
11699
|
+
const hit = offsets.get(tag);
|
|
11700
|
+
if (!hit) continue;
|
|
11701
|
+
if (now2 - hit.at > SCROLL_OFFSET_TTL_MS) {
|
|
11702
|
+
offsets.delete(tag);
|
|
11703
|
+
continue;
|
|
11704
|
+
}
|
|
11705
|
+
return {
|
|
11706
|
+
x: hit.x,
|
|
11707
|
+
y: hit.y,
|
|
11708
|
+
tag
|
|
11709
|
+
};
|
|
11710
|
+
}
|
|
11711
|
+
return null;
|
|
11712
|
+
}
|
|
11713
|
+
function clearScrollOffsets() {
|
|
11714
|
+
offsets.clear();
|
|
11715
|
+
}
|
|
11716
|
+
function trackedScrollerCount() {
|
|
11717
|
+
return offsets.size;
|
|
11718
|
+
}
|
|
11719
|
+
var SCROLL_OFFSET_TTL_MS, MAX_TRACKED_SCROLLERS, offsets;
|
|
11720
|
+
var init_scrollContext = __esm({
|
|
11721
|
+
"lib/module/features/journey/scrollContext.js"() {
|
|
11722
|
+
"use strict";
|
|
11723
|
+
SCROLL_OFFSET_TTL_MS = 5 * 6e4;
|
|
11724
|
+
MAX_TRACKED_SCROLLERS = 32;
|
|
11725
|
+
offsets = /* @__PURE__ */ new Map();
|
|
11726
|
+
}
|
|
11727
|
+
});
|
|
11728
|
+
|
|
11729
|
+
// lib/module/features/journey/autoInstrumentScroll.js
|
|
11730
|
+
var autoInstrumentScroll_exports = {};
|
|
11731
|
+
__export(autoInstrumentScroll_exports, {
|
|
11732
|
+
autoInstrumentScrollViews: () => autoInstrumentScrollViews,
|
|
11733
|
+
resetAutoInstrumentForTests: () => resetAutoInstrumentForTests
|
|
11734
|
+
});
|
|
11735
|
+
function redefine(target, key, value) {
|
|
11736
|
+
try {
|
|
11737
|
+
Object.defineProperty(target, key, {
|
|
11738
|
+
configurable: true,
|
|
11739
|
+
enumerable: true,
|
|
11740
|
+
writable: true,
|
|
11741
|
+
value
|
|
11742
|
+
});
|
|
11743
|
+
return target[key] === value;
|
|
11744
|
+
} catch {
|
|
11745
|
+
return false;
|
|
11746
|
+
}
|
|
11747
|
+
}
|
|
11748
|
+
function instrument(Original, React14) {
|
|
11749
|
+
const Wrapped = React14.forwardRef((props, ref) => {
|
|
11750
|
+
const onScroll = (event) => {
|
|
11751
|
+
try {
|
|
11752
|
+
const target = event?.nativeEvent?.target;
|
|
11753
|
+
const offset = event?.nativeEvent?.contentOffset;
|
|
11754
|
+
if (offset) {
|
|
11755
|
+
recordScrollOffset(typeof target === "number" ? target : nativeTagOf(target), offset.x, offset.y);
|
|
11756
|
+
}
|
|
11757
|
+
} catch {
|
|
11758
|
+
}
|
|
11759
|
+
try {
|
|
11760
|
+
props?.onScroll?.(event);
|
|
11761
|
+
} catch (e) {
|
|
11762
|
+
setTimeout(() => {
|
|
11763
|
+
throw e;
|
|
11764
|
+
}, 0);
|
|
11765
|
+
}
|
|
11766
|
+
};
|
|
11767
|
+
const throttle = props?.scrollEventThrottle ?? 16;
|
|
11768
|
+
return React14.createElement(Original, {
|
|
11769
|
+
...props,
|
|
11770
|
+
scrollEventThrottle: throttle,
|
|
11771
|
+
onScroll,
|
|
11772
|
+
ref
|
|
11773
|
+
});
|
|
11774
|
+
});
|
|
11775
|
+
Wrapped.displayName = "ScaleBunAutoScrollView";
|
|
11776
|
+
return Wrapped;
|
|
11777
|
+
}
|
|
11778
|
+
function autoInstrumentScrollViews() {
|
|
11779
|
+
if (alreadyPatched) return {
|
|
11780
|
+
installed: true
|
|
11781
|
+
};
|
|
11782
|
+
try {
|
|
11783
|
+
const RN = require("react-native");
|
|
11784
|
+
const React14 = require("react");
|
|
11785
|
+
const Original = RN.ScrollView;
|
|
11786
|
+
if (typeof Original !== "function" && typeof Original !== "object") {
|
|
11787
|
+
return {
|
|
11788
|
+
installed: false,
|
|
11789
|
+
reason: "react-native has no ScrollView export to wrap"
|
|
11790
|
+
};
|
|
11791
|
+
}
|
|
11792
|
+
if (Original.__scalebunInstrumented) {
|
|
11793
|
+
alreadyPatched = true;
|
|
11794
|
+
return {
|
|
11795
|
+
installed: true
|
|
11796
|
+
};
|
|
11797
|
+
}
|
|
11798
|
+
const Wrapped = instrument(Original, React14);
|
|
11799
|
+
Wrapped.__scalebunInstrumented = true;
|
|
11800
|
+
if (!redefine(RN, "ScrollView", Wrapped)) {
|
|
11801
|
+
return {
|
|
11802
|
+
installed: false,
|
|
11803
|
+
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."
|
|
11804
|
+
};
|
|
11805
|
+
}
|
|
11806
|
+
alreadyPatched = true;
|
|
11807
|
+
return {
|
|
11808
|
+
installed: true
|
|
11809
|
+
};
|
|
11810
|
+
} catch (e) {
|
|
11811
|
+
return {
|
|
11812
|
+
installed: false,
|
|
11813
|
+
reason: e?.message ?? "unknown failure"
|
|
11814
|
+
};
|
|
11815
|
+
}
|
|
11816
|
+
}
|
|
11817
|
+
function resetAutoInstrumentForTests() {
|
|
11818
|
+
alreadyPatched = false;
|
|
11819
|
+
}
|
|
11820
|
+
var alreadyPatched;
|
|
11821
|
+
var init_autoInstrumentScroll = __esm({
|
|
11822
|
+
"lib/module/features/journey/autoInstrumentScroll.js"() {
|
|
11823
|
+
"use strict";
|
|
11824
|
+
init_scrollContext();
|
|
11825
|
+
alreadyPatched = false;
|
|
11826
|
+
}
|
|
11827
|
+
});
|
|
11828
|
+
|
|
11492
11829
|
// lib/module/features/engage/engageSignals.js
|
|
11493
11830
|
var EngageSignalBus, engageSignals;
|
|
11494
11831
|
var init_engageSignals = __esm({
|
|
@@ -11697,322 +12034,6 @@ var init_deviceAttributes = __esm({
|
|
|
11697
12034
|
}
|
|
11698
12035
|
});
|
|
11699
12036
|
|
|
11700
|
-
// lib/module/features/ota/crypto/loadEd25519.js
|
|
11701
|
-
function loadEd25519() {
|
|
11702
|
-
try {
|
|
11703
|
-
const mod = require("@noble/ed25519");
|
|
11704
|
-
const ed = mod?.default ?? mod;
|
|
11705
|
-
if (typeof ed?.verifyAsync === "function" || typeof ed?.verify === "function") {
|
|
11706
|
-
return ed;
|
|
11707
|
-
}
|
|
11708
|
-
return null;
|
|
11709
|
-
} catch {
|
|
11710
|
-
return null;
|
|
11711
|
-
}
|
|
11712
|
-
}
|
|
11713
|
-
var init_loadEd25519 = __esm({
|
|
11714
|
-
"lib/module/features/ota/crypto/loadEd25519.js"() {
|
|
11715
|
-
"use strict";
|
|
11716
|
-
}
|
|
11717
|
-
});
|
|
11718
|
-
|
|
11719
|
-
// lib/module/features/ota/crypto/loadSha512.js
|
|
11720
|
-
function loadSha512() {
|
|
11721
|
-
try {
|
|
11722
|
-
const mod = require("@noble/hashes/sha2.js");
|
|
11723
|
-
const sha512 = mod?.sha512 ?? mod?.default?.sha512;
|
|
11724
|
-
return typeof sha512 === "function" ? sha512 : null;
|
|
11725
|
-
} catch {
|
|
11726
|
-
return null;
|
|
11727
|
-
}
|
|
11728
|
-
}
|
|
11729
|
-
var init_loadSha512 = __esm({
|
|
11730
|
-
"lib/module/features/ota/crypto/loadSha512.js"() {
|
|
11731
|
-
"use strict";
|
|
11732
|
-
}
|
|
11733
|
-
});
|
|
11734
|
-
|
|
11735
|
-
// lib/module/features/ota/crypto/builtinVerifier.js
|
|
11736
|
-
var builtinVerifier_exports = {};
|
|
11737
|
-
__export(builtinVerifier_exports, {
|
|
11738
|
-
_resetBuiltinVerifier: () => _resetBuiltinVerifier,
|
|
11739
|
-
decodeSignature: () => decodeSignature,
|
|
11740
|
-
getBuiltinVerifier: () => getBuiltinVerifier
|
|
11741
|
-
});
|
|
11742
|
-
function hexToBytes(hex) {
|
|
11743
|
-
const clean2 = hex.trim().toLowerCase().replace(/^0x/, "");
|
|
11744
|
-
if (clean2.length === 0 || clean2.length % 2 !== 0 || /[^0-9a-f]/.test(clean2)) return null;
|
|
11745
|
-
const out = new Uint8Array(clean2.length / 2);
|
|
11746
|
-
for (let i = 0; i < out.length; i++) {
|
|
11747
|
-
out[i] = parseInt(clean2.slice(i * 2, i * 2 + 2), 16);
|
|
11748
|
-
}
|
|
11749
|
-
return out;
|
|
11750
|
-
}
|
|
11751
|
-
function base64ToBytes2(b64) {
|
|
11752
|
-
const s = b64.trim().replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
|
|
11753
|
-
if (s.length === 0) return null;
|
|
11754
|
-
const out = [];
|
|
11755
|
-
let buffer = 0;
|
|
11756
|
-
let bits = 0;
|
|
11757
|
-
for (const ch of s) {
|
|
11758
|
-
const idx = B64_ALPHABET2.indexOf(ch);
|
|
11759
|
-
if (idx === -1) return null;
|
|
11760
|
-
buffer = buffer << 6 | idx;
|
|
11761
|
-
bits += 6;
|
|
11762
|
-
if (bits >= 8) {
|
|
11763
|
-
bits -= 8;
|
|
11764
|
-
out.push(buffer >> bits & 255);
|
|
11765
|
-
}
|
|
11766
|
-
}
|
|
11767
|
-
return Uint8Array.from(out);
|
|
11768
|
-
}
|
|
11769
|
-
function decodeSignature(sig) {
|
|
11770
|
-
const trimmed = sig.trim();
|
|
11771
|
-
if (/^(0x)?[0-9a-fA-F]{128}$/.test(trimmed)) {
|
|
11772
|
-
const hex2 = hexToBytes(trimmed);
|
|
11773
|
-
if (hex2 && hex2.length === ED25519_SIGNATURE_BYTES) return hex2;
|
|
11774
|
-
}
|
|
11775
|
-
const b64 = base64ToBytes2(trimmed);
|
|
11776
|
-
if (b64 && b64.length === ED25519_SIGNATURE_BYTES) return b64;
|
|
11777
|
-
const hex = hexToBytes(trimmed);
|
|
11778
|
-
if (hex && hex.length === ED25519_SIGNATURE_BYTES) return hex;
|
|
11779
|
-
return null;
|
|
11780
|
-
}
|
|
11781
|
-
function wireSha512(ed, sha512) {
|
|
11782
|
-
const concat2 = (parts) => {
|
|
11783
|
-
if (parts.length === 1) return parts[0];
|
|
11784
|
-
let total = 0;
|
|
11785
|
-
for (const p of parts) total += p.length;
|
|
11786
|
-
const out = new Uint8Array(total);
|
|
11787
|
-
let off = 0;
|
|
11788
|
-
for (const p of parts) {
|
|
11789
|
-
out.set(p, off);
|
|
11790
|
-
off += p.length;
|
|
11791
|
-
}
|
|
11792
|
-
return out;
|
|
11793
|
-
};
|
|
11794
|
-
const sync = (...msgs) => sha512(concat2(msgs));
|
|
11795
|
-
const asyncFn = async (...msgs) => sha512(concat2(msgs));
|
|
11796
|
-
let wired = false;
|
|
11797
|
-
try {
|
|
11798
|
-
if (ed.hashes && typeof ed.hashes === "object") {
|
|
11799
|
-
if (typeof ed.hashes.sha512 !== "function") ed.hashes.sha512 = sync;
|
|
11800
|
-
if (typeof ed.hashes.sha512Async !== "function") ed.hashes.sha512Async = asyncFn;
|
|
11801
|
-
wired = typeof ed.hashes.sha512 === "function";
|
|
11802
|
-
}
|
|
11803
|
-
} catch {
|
|
11804
|
-
}
|
|
11805
|
-
try {
|
|
11806
|
-
if (ed.etc && typeof ed.etc === "object") {
|
|
11807
|
-
if (typeof ed.etc.sha512Sync !== "function") ed.etc.sha512Sync = sync;
|
|
11808
|
-
if (typeof ed.etc.sha512Async !== "function") ed.etc.sha512Async = asyncFn;
|
|
11809
|
-
wired = wired || typeof ed.etc.sha512Sync === "function";
|
|
11810
|
-
}
|
|
11811
|
-
} catch {
|
|
11812
|
-
}
|
|
11813
|
-
return wired;
|
|
11814
|
-
}
|
|
11815
|
-
function getBuiltinVerifier() {
|
|
11816
|
-
if (resolved !== void 0) return resolved;
|
|
11817
|
-
const ed = loadEd25519();
|
|
11818
|
-
const sha512 = loadSha512();
|
|
11819
|
-
if (!ed || !sha512) {
|
|
11820
|
-
resolved = null;
|
|
11821
|
-
return null;
|
|
11822
|
-
}
|
|
11823
|
-
if (!wireSha512(ed, sha512)) {
|
|
11824
|
-
logger.error("[OTA] @noble/ed25519 is installed but its SHA-512 provider could not be wired (unrecognised version shape). Supply `ota.verifySignature` yourself, or pin a @noble/ed25519 version this SDK supports.");
|
|
11825
|
-
resolved = null;
|
|
11826
|
-
return null;
|
|
11827
|
-
}
|
|
11828
|
-
resolved = async ({
|
|
11829
|
-
messageHex,
|
|
11830
|
-
signature,
|
|
11831
|
-
publicKey
|
|
11832
|
-
}) => {
|
|
11833
|
-
const message = hexToBytes(messageHex);
|
|
11834
|
-
const pub = hexToBytes(publicKey);
|
|
11835
|
-
const sig = decodeSignature(signature);
|
|
11836
|
-
if (!message) {
|
|
11837
|
-
logger.error("[OTA] Bundle SHA-256 is not valid hex \u2014 refusing to stage.");
|
|
11838
|
-
return false;
|
|
11839
|
-
}
|
|
11840
|
-
if (!pub || pub.length !== ED25519_PUBLIC_KEY_BYTES) {
|
|
11841
|
-
logger.error(`[OTA] ota.publicSigningKey must be ${ED25519_PUBLIC_KEY_BYTES} bytes of hex (${ED25519_PUBLIC_KEY_BYTES * 2} characters) \u2014 got ${pub ? `${pub.length} bytes` : "a value that is not hex"}.`);
|
|
11842
|
-
return false;
|
|
11843
|
-
}
|
|
11844
|
-
if (!sig) {
|
|
11845
|
-
logger.error("[OTA] Bundle signature is not a 64-byte hex/base64 value.");
|
|
11846
|
-
return false;
|
|
11847
|
-
}
|
|
11848
|
-
try {
|
|
11849
|
-
if (typeof ed.verify === "function") return ed.verify(sig, message, pub);
|
|
11850
|
-
if (typeof ed.verifyAsync === "function") return await ed.verifyAsync(sig, message, pub);
|
|
11851
|
-
return false;
|
|
11852
|
-
} catch {
|
|
11853
|
-
return false;
|
|
11854
|
-
}
|
|
11855
|
-
};
|
|
11856
|
-
__DEV__ && logger.debug("[OTA] Using built-in ed25519 verifier (@noble/ed25519 detected).");
|
|
11857
|
-
return resolved;
|
|
11858
|
-
}
|
|
11859
|
-
function _resetBuiltinVerifier() {
|
|
11860
|
-
resolved = void 0;
|
|
11861
|
-
}
|
|
11862
|
-
var ED25519_SIGNATURE_BYTES, ED25519_PUBLIC_KEY_BYTES, B64_ALPHABET2, resolved;
|
|
11863
|
-
var init_builtinVerifier = __esm({
|
|
11864
|
-
"lib/module/features/ota/crypto/builtinVerifier.js"() {
|
|
11865
|
-
"use strict";
|
|
11866
|
-
init_loadEd25519();
|
|
11867
|
-
init_loadSha512();
|
|
11868
|
-
init_internalLogger();
|
|
11869
|
-
ED25519_SIGNATURE_BYTES = 64;
|
|
11870
|
-
ED25519_PUBLIC_KEY_BYTES = 32;
|
|
11871
|
-
B64_ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
11872
|
-
}
|
|
11873
|
-
});
|
|
11874
|
-
|
|
11875
|
-
// lib/module/features/ota/crypto/nativeVerifier.js
|
|
11876
|
-
function bytesToHex(bytes) {
|
|
11877
|
-
let out = "";
|
|
11878
|
-
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
11879
|
-
return out;
|
|
11880
|
-
}
|
|
11881
|
-
function normalizeHex(value, expectedChars) {
|
|
11882
|
-
const clean2 = value.trim().toLowerCase().replace(/^0x/, "");
|
|
11883
|
-
if (clean2.length !== expectedChars || /[^0-9a-f]/.test(clean2)) return null;
|
|
11884
|
-
return clean2;
|
|
11885
|
-
}
|
|
11886
|
-
function getNativeVerifier() {
|
|
11887
|
-
if (resolved2 !== void 0) return resolved2;
|
|
11888
|
-
const native = NativeScaleBunOta_default;
|
|
11889
|
-
if (!native || typeof native.verifyEd25519 !== "function") {
|
|
11890
|
-
resolved2 = null;
|
|
11891
|
-
return null;
|
|
11892
|
-
}
|
|
11893
|
-
resolved2 = async ({
|
|
11894
|
-
messageHex,
|
|
11895
|
-
signature,
|
|
11896
|
-
publicKey
|
|
11897
|
-
}) => {
|
|
11898
|
-
const message = normalizeHex(messageHex, 64);
|
|
11899
|
-
const key = normalizeHex(publicKey, ED25519_PUBLIC_KEY_HEX_CHARS);
|
|
11900
|
-
const sigBytes = decodeSignature(signature);
|
|
11901
|
-
if (!message || !key || !sigBytes) {
|
|
11902
|
-
logger.error("[OTA] Signature inputs malformed (bundle hash, signature, or public key) \u2014 refusing to stage.");
|
|
11903
|
-
return false;
|
|
11904
|
-
}
|
|
11905
|
-
let verdict;
|
|
11906
|
-
try {
|
|
11907
|
-
verdict = await native.verifyEd25519(message, bytesToHex(sigBytes), key);
|
|
11908
|
-
} catch (err) {
|
|
11909
|
-
logger.warn(`[OTA] Native ed25519 verify failed to run: ${err?.message ?? err}`);
|
|
11910
|
-
verdict = "unavailable";
|
|
11911
|
-
}
|
|
11912
|
-
if (verdict === "valid") return true;
|
|
11913
|
-
if (verdict === "invalid") return false;
|
|
11914
|
-
const builtin = getBuiltinVerifier();
|
|
11915
|
-
if (builtin) {
|
|
11916
|
-
return builtin({
|
|
11917
|
-
messageHex,
|
|
11918
|
-
signature,
|
|
11919
|
-
publicKey
|
|
11920
|
-
});
|
|
11921
|
-
}
|
|
11922
|
-
throw new Error("ed25519 unavailable: this OS has no native implementation (Android < 13) and the optional peers @noble/ed25519 + @noble/hashes are not installed.");
|
|
11923
|
-
};
|
|
11924
|
-
__DEV__ && logger.debug("[OTA] Using native ed25519 verifier (platform crypto).");
|
|
11925
|
-
return resolved2;
|
|
11926
|
-
}
|
|
11927
|
-
var ED25519_PUBLIC_KEY_HEX_CHARS, resolved2;
|
|
11928
|
-
var init_nativeVerifier = __esm({
|
|
11929
|
-
"lib/module/features/ota/crypto/nativeVerifier.js"() {
|
|
11930
|
-
"use strict";
|
|
11931
|
-
init_NativeScaleBunOta();
|
|
11932
|
-
init_internalLogger();
|
|
11933
|
-
init_builtinVerifier();
|
|
11934
|
-
ED25519_PUBLIC_KEY_HEX_CHARS = 64;
|
|
11935
|
-
}
|
|
11936
|
-
});
|
|
11937
|
-
|
|
11938
|
-
// lib/module/features/ota/signature.js
|
|
11939
|
-
async function verifyBundleSignature(bundleSha256, signature, config) {
|
|
11940
|
-
const rawKey = config?.publicKey;
|
|
11941
|
-
const keys = (Array.isArray(rawKey) ? rawKey : rawKey ? [rawKey] : []).filter((k) => typeof k === "string" && k.trim().length > 0);
|
|
11942
|
-
if (rawKey === void 0 || rawKey === "" || rawKey === null) {
|
|
11943
|
-
if (!warnedNotConfigured) {
|
|
11944
|
-
warnedNotConfigured = true;
|
|
11945
|
-
logger.warn("[OTA] No signing public key configured \u2014 bundles are accepted on SHA-256 integrity alone. Configure `ota.publicSigningKey` to enforce authenticity.");
|
|
11946
|
-
}
|
|
11947
|
-
return {
|
|
11948
|
-
ok: true,
|
|
11949
|
-
reason: "not_configured"
|
|
11950
|
-
};
|
|
11951
|
-
}
|
|
11952
|
-
if (keys.length === 0) {
|
|
11953
|
-
logger.error("[OTA] Signing key config resolves to zero usable keys (empty array or blank strings) \u2014 refusing to stage. Remove the config to disable signing, or pin at least one real key.");
|
|
11954
|
-
return {
|
|
11955
|
-
ok: false,
|
|
11956
|
-
reason: "no_keys"
|
|
11957
|
-
};
|
|
11958
|
-
}
|
|
11959
|
-
if (!signature) {
|
|
11960
|
-
logger.error("[OTA] Bundle has no signature but a signing key is configured \u2014 refusing to stage.");
|
|
11961
|
-
return {
|
|
11962
|
-
ok: false,
|
|
11963
|
-
reason: "missing_signature"
|
|
11964
|
-
};
|
|
11965
|
-
}
|
|
11966
|
-
const verifier = typeof config?.verifier === "function" ? config.verifier : getNativeVerifier() ?? getBuiltinVerifier();
|
|
11967
|
-
if (!verifier) {
|
|
11968
|
-
logger.error("[OTA] A signing key is configured but no signature verifier is available \u2014 refusing to stage. Either install the optional peers `@noble/ed25519` and `@noble/hashes` (the SDK then verifies with no further code), or provide your own `ota.verifySignature`.");
|
|
11969
|
-
return {
|
|
11970
|
-
ok: false,
|
|
11971
|
-
reason: "no_verifier"
|
|
11972
|
-
};
|
|
11973
|
-
}
|
|
11974
|
-
let sawThrow = false;
|
|
11975
|
-
let sawCompletion = false;
|
|
11976
|
-
for (const key of keys) {
|
|
11977
|
-
try {
|
|
11978
|
-
const valid = await verifier({
|
|
11979
|
-
messageHex: bundleSha256,
|
|
11980
|
-
signature,
|
|
11981
|
-
publicKey: key
|
|
11982
|
-
});
|
|
11983
|
-
sawCompletion = true;
|
|
11984
|
-
if (valid) return {
|
|
11985
|
-
ok: true,
|
|
11986
|
-
reason: "verified"
|
|
11987
|
-
};
|
|
11988
|
-
} catch (err) {
|
|
11989
|
-
sawThrow = true;
|
|
11990
|
-
logger.error(`[OTA] Signature verifier threw: ${err?.message ?? err}`);
|
|
11991
|
-
}
|
|
11992
|
-
}
|
|
11993
|
-
if (sawThrow && !sawCompletion) {
|
|
11994
|
-
return {
|
|
11995
|
-
ok: false,
|
|
11996
|
-
reason: "verifier_threw"
|
|
11997
|
-
};
|
|
11998
|
-
}
|
|
11999
|
-
logger.error(keys.length > 1 ? `[OTA] Bundle signature is INVALID under all ${keys.length} pinned keys \u2014 refusing to stage.` : "[OTA] Bundle signature is INVALID \u2014 refusing to stage.");
|
|
12000
|
-
return {
|
|
12001
|
-
ok: false,
|
|
12002
|
-
reason: "invalid_signature"
|
|
12003
|
-
};
|
|
12004
|
-
}
|
|
12005
|
-
var warnedNotConfigured;
|
|
12006
|
-
var init_signature = __esm({
|
|
12007
|
-
"lib/module/features/ota/signature.js"() {
|
|
12008
|
-
"use strict";
|
|
12009
|
-
init_internalLogger();
|
|
12010
|
-
init_builtinVerifier();
|
|
12011
|
-
init_nativeVerifier();
|
|
12012
|
-
warnedNotConfigured = false;
|
|
12013
|
-
}
|
|
12014
|
-
});
|
|
12015
|
-
|
|
12016
12037
|
// lib/module/features/ota/environment.js
|
|
12017
12038
|
function compare(a, b) {
|
|
12018
12039
|
if (a.major !== b.major) return a.major - b.major;
|
|
@@ -12240,10 +12261,10 @@ async function deliverOtaEvents(params) {
|
|
|
12240
12261
|
})
|
|
12241
12262
|
});
|
|
12242
12263
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
12243
|
-
|
|
12264
|
+
logger.debug(`[OTA] Delivered ${items.length} telemetry event(s)`);
|
|
12244
12265
|
} catch (err) {
|
|
12245
12266
|
for (const e of events) otaEventEmitter.emit(e);
|
|
12246
|
-
|
|
12267
|
+
logger.debug(`[OTA] Telemetry delivery failed (will retry next sync): ${err?.message}`);
|
|
12247
12268
|
}
|
|
12248
12269
|
}
|
|
12249
12270
|
var import_react_native34, PROGRESS_POST_INTERVAL_MS, DEFAULT_HEALTHY_AFTER_MS, OtaOrchestrator, otaOrchestrator;
|
|
@@ -12258,7 +12279,6 @@ var init_OtaOrchestrator = __esm({
|
|
|
12258
12279
|
init_geoCountry();
|
|
12259
12280
|
init_deviceAttributes();
|
|
12260
12281
|
init_installationId();
|
|
12261
|
-
init_signature();
|
|
12262
12282
|
init_environment();
|
|
12263
12283
|
init_retry();
|
|
12264
12284
|
init_StorageBackend();
|
|
@@ -12277,6 +12297,8 @@ var init_OtaOrchestrator = __esm({
|
|
|
12277
12297
|
return this.currentBundle;
|
|
12278
12298
|
}
|
|
12279
12299
|
isRestartRequiredState = false;
|
|
12300
|
+
/** Root/jailbreak indicators observed at init; empty when none found. */
|
|
12301
|
+
integrityIndicators = [];
|
|
12280
12302
|
healthyTimer = null;
|
|
12281
12303
|
bootGuardConfig = {};
|
|
12282
12304
|
environment = null;
|
|
@@ -12286,15 +12308,15 @@ var init_OtaOrchestrator = __esm({
|
|
|
12286
12308
|
/**
|
|
12287
12309
|
* Initialize the orchestrator. Checks for boot-guard recovery state.
|
|
12288
12310
|
*
|
|
12289
|
-
*
|
|
12290
|
-
*
|
|
12291
|
-
*
|
|
12311
|
+
* Signature enforcement is no longer a JS concern: signed releases are
|
|
12312
|
+
* verified and staged in NATIVE code against RSA keys embedded in the app
|
|
12313
|
+
* package (SB-OTA-RSA-SHA256-V1), and unsigned releases keep working on
|
|
12314
|
+
* SHA-256 integrity alone. There is nothing for the host to configure here.
|
|
12292
12315
|
*/
|
|
12293
12316
|
init(config) {
|
|
12294
12317
|
noThrow(() => {
|
|
12295
12318
|
if (this.initialized) {
|
|
12296
12319
|
this.bootGuardConfig = config ?? this.bootGuardConfig;
|
|
12297
|
-
this.signatureConfig = config?.signature ?? this.signatureConfig;
|
|
12298
12320
|
return;
|
|
12299
12321
|
}
|
|
12300
12322
|
if (!NativeScaleBunOta_default) {
|
|
@@ -12311,18 +12333,27 @@ var init_OtaOrchestrator = __esm({
|
|
|
12311
12333
|
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.");
|
|
12312
12334
|
}
|
|
12313
12335
|
if (this.environment.requiresReactHostIntegration) {
|
|
12314
|
-
|
|
12336
|
+
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.");
|
|
12315
12337
|
}
|
|
12316
12338
|
this.initialized = true;
|
|
12317
12339
|
this.enabled = true;
|
|
12340
|
+
try {
|
|
12341
|
+
const raw = NativeScaleBunOta_default?.getDeviceIntegrity?.();
|
|
12342
|
+
this.integrityIndicators = raw ? JSON.parse(raw) : [];
|
|
12343
|
+
if (this.integrityIndicators.length > 0) {
|
|
12344
|
+
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.`);
|
|
12345
|
+
}
|
|
12346
|
+
} catch {
|
|
12347
|
+
this.integrityIndicators = [];
|
|
12348
|
+
}
|
|
12318
12349
|
this.bootGuardConfig = config ?? {};
|
|
12319
|
-
this.
|
|
12320
|
-
__DEV__ && logger.debug(`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? "unknown"}${this.environment.bridgeless ? ", bridgeless" : ""}${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? "?"}` : ""})`);
|
|
12350
|
+
logger.debug(`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? "unknown"}${this.environment.bridgeless ? ", bridgeless" : ""}${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? "?"}` : ""})`);
|
|
12321
12351
|
const slotState = this.readSlotState();
|
|
12322
12352
|
this.hydrateCurrentBundleFromSlots(slotState);
|
|
12323
12353
|
void prefetchDeviceCountry();
|
|
12324
12354
|
this.checkBootGuardRecovery(slotState);
|
|
12325
12355
|
this.verifyRunningBundleIdentity();
|
|
12356
|
+
this.startBootGuardHeartbeat(slotState);
|
|
12326
12357
|
});
|
|
12327
12358
|
}
|
|
12328
12359
|
/**
|
|
@@ -12353,13 +12384,13 @@ var init_OtaOrchestrator = __esm({
|
|
|
12353
12384
|
releaseId: record4.releaseId ?? void 0,
|
|
12354
12385
|
sha256: record4.sha256
|
|
12355
12386
|
};
|
|
12356
|
-
|
|
12387
|
+
logger.debug(`[OTA] Running bundle v${record4.version} (${record4.bundleId})`);
|
|
12357
12388
|
return;
|
|
12358
12389
|
}
|
|
12359
12390
|
this.currentBundle = {
|
|
12360
12391
|
sha256: current.sha256
|
|
12361
12392
|
};
|
|
12362
|
-
|
|
12393
|
+
logger.debug(`[OTA] Running an OTA bundle (sha ${String(current.sha256).slice(0, 12)}\u2026) with no local install record.`);
|
|
12363
12394
|
} catch {
|
|
12364
12395
|
}
|
|
12365
12396
|
}
|
|
@@ -12389,7 +12420,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12389
12420
|
}
|
|
12390
12421
|
if (!record4.identityToken) return;
|
|
12391
12422
|
if (running === record4.identityToken) {
|
|
12392
|
-
|
|
12423
|
+
logger.debug("[OTA] Install verified \u2014 running bundle matches what was installed.");
|
|
12393
12424
|
this.retireIdentityToken(record4);
|
|
12394
12425
|
return;
|
|
12395
12426
|
}
|
|
@@ -12492,6 +12523,13 @@ var init_OtaOrchestrator = __esm({
|
|
|
12492
12523
|
isEnabled() {
|
|
12493
12524
|
return this.enabled;
|
|
12494
12525
|
}
|
|
12526
|
+
/**
|
|
12527
|
+
* Root/jailbreak indicators detected on this device, as stable reason strings.
|
|
12528
|
+
* Empty means nothing was detected — not proof the device is clean.
|
|
12529
|
+
*/
|
|
12530
|
+
deviceIntegrityIndicators() {
|
|
12531
|
+
return [...this.integrityIndicators];
|
|
12532
|
+
}
|
|
12495
12533
|
isRestartRequired() {
|
|
12496
12534
|
return this.isRestartRequiredState;
|
|
12497
12535
|
}
|
|
@@ -12503,12 +12541,22 @@ var init_OtaOrchestrator = __esm({
|
|
|
12503
12541
|
const env = this.environment ?? detectOtaEnvironment();
|
|
12504
12542
|
const mobileOS = resolveMobileOS();
|
|
12505
12543
|
if (!mobileOS) throw new Error("OTA is not supported on this platform");
|
|
12544
|
+
let trustedKeyIds;
|
|
12545
|
+
try {
|
|
12546
|
+
const raw = NativeScaleBunOta_default?.getTrustedKeyIds?.();
|
|
12547
|
+
if (raw) {
|
|
12548
|
+
const arr2 = JSON.parse(raw);
|
|
12549
|
+
if (Array.isArray(arr2) && arr2.length) trustedKeyIds = arr2.slice(0, 16);
|
|
12550
|
+
}
|
|
12551
|
+
} catch {
|
|
12552
|
+
}
|
|
12506
12553
|
const payload = {
|
|
12507
12554
|
appVersion: requestParams.appVersion,
|
|
12508
12555
|
platform: mobileOS,
|
|
12509
12556
|
installationId: requestParams.installationId,
|
|
12510
12557
|
currentBundleId: this.currentBundle?.id,
|
|
12511
12558
|
currentBundleHash: this.currentBundle?.sha256,
|
|
12559
|
+
trustedKeyIds,
|
|
12512
12560
|
channelName: requestParams.channelName,
|
|
12513
12561
|
// Host-supplied country wins; otherwise the cached edge-Worker lookup
|
|
12514
12562
|
// fills in (see geoCountry.ts). Undefined when neither knows — the
|
|
@@ -12575,7 +12623,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12575
12623
|
error: "OTA native module disabled or missing"
|
|
12576
12624
|
};
|
|
12577
12625
|
}
|
|
12578
|
-
|
|
12626
|
+
logger.debug("[OTA] Sync started\u2026");
|
|
12579
12627
|
if (this.currentBundle?.id) {
|
|
12580
12628
|
otaEventEmitter.emitSimple("CHECK", this.currentBundle.id, {
|
|
12581
12629
|
releaseId: this.currentBundle.releaseId,
|
|
@@ -12584,7 +12632,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12584
12632
|
}
|
|
12585
12633
|
const checkRes = await this.checkForUpdate(params);
|
|
12586
12634
|
if (checkRes.action === "NONE") {
|
|
12587
|
-
|
|
12635
|
+
logger.debug("[OTA] App is up to date");
|
|
12588
12636
|
this.startBootGuardHeartbeat();
|
|
12589
12637
|
return {
|
|
12590
12638
|
status: "UP_TO_DATE"
|
|
@@ -12622,25 +12670,12 @@ var init_OtaOrchestrator = __esm({
|
|
|
12622
12670
|
releaseId: bundle.releaseId,
|
|
12623
12671
|
version: bundle.version
|
|
12624
12672
|
});
|
|
12625
|
-
const signatureOutcome = await verifyBundleSignature(bundle.sha256, bundle.signature, this.signatureConfig);
|
|
12626
|
-
if (!signatureOutcome.ok) {
|
|
12627
|
-
otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
|
|
12628
|
-
releaseId: bundle.releaseId,
|
|
12629
|
-
error: `Signature check failed: ${signatureOutcome.reason}`,
|
|
12630
|
-
version: bundle.version
|
|
12631
|
-
});
|
|
12632
|
-
return {
|
|
12633
|
-
status: "ERROR",
|
|
12634
|
-
error: `Bundle signature check failed (${signatureOutcome.reason})`,
|
|
12635
|
-
bundle
|
|
12636
|
-
};
|
|
12637
|
-
}
|
|
12638
12673
|
otaEventEmitter.emitSimple("DOWNLOAD_STARTED", bundle.id, {
|
|
12639
12674
|
releaseId: bundle.releaseId,
|
|
12640
12675
|
version: bundle.version
|
|
12641
12676
|
});
|
|
12642
12677
|
const downloadStart = Date.now();
|
|
12643
|
-
|
|
12678
|
+
logger.debug(`[OTA] Downloading update v${bundle.version}\u2026`);
|
|
12644
12679
|
let lastPostAt = 0;
|
|
12645
12680
|
const postProgress = (bytesRead, status) => {
|
|
12646
12681
|
const total = bundle.size > 0 ? bundle.size : Math.max(bytesRead, 1);
|
|
@@ -12682,39 +12717,75 @@ var init_OtaOrchestrator = __esm({
|
|
|
12682
12717
|
}
|
|
12683
12718
|
});
|
|
12684
12719
|
let staged = false;
|
|
12685
|
-
if (bundle.
|
|
12686
|
-
|
|
12720
|
+
if (bundle.envelope) {
|
|
12721
|
+
const envelopeJson = JSON.stringify({
|
|
12722
|
+
...bundle.envelope,
|
|
12723
|
+
downloadUrl: bundle.url
|
|
12724
|
+
});
|
|
12725
|
+
let verdict;
|
|
12687
12726
|
try {
|
|
12688
|
-
|
|
12689
|
-
|
|
12690
|
-
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
__DEV__ && logger.debug("[OTA] Patch applied successfully");
|
|
12697
|
-
} else {
|
|
12698
|
-
logger.warn("[OTA] Patch staging returned false \u2014 falling back to full bundle");
|
|
12699
|
-
}
|
|
12700
|
-
} catch (patchErr) {
|
|
12701
|
-
logger.warn(`[OTA] Patch staging failed: ${patchErr.message} \u2014 falling back to full bundle`);
|
|
12727
|
+
const verdictJson = await NativeScaleBunOta_default.verifyAndStageRelease(envelopeJson);
|
|
12728
|
+
verdict = JSON.parse(verdictJson);
|
|
12729
|
+
} catch (err) {
|
|
12730
|
+
verdict = {
|
|
12731
|
+
ok: false,
|
|
12732
|
+
code: "verify_stage_threw",
|
|
12733
|
+
message: err?.message ?? String(err)
|
|
12734
|
+
};
|
|
12702
12735
|
}
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
label: `download v${bundle.version}`,
|
|
12714
|
-
attempts: 3
|
|
12736
|
+
if (!verdict.ok) {
|
|
12737
|
+
unsubscribeProgress();
|
|
12738
|
+
postProgress(0, "FAILED");
|
|
12739
|
+
logger.error(`[OTA] Native verify/stage refused the release: ${verdict.code ?? "unknown"}${verdict.message ? ` \u2014 ${verdict.message}` : ""}`);
|
|
12740
|
+
otaEventEmitter.emitSimple("APPLY_FAILED", bundle.id, {
|
|
12741
|
+
releaseId: bundle.releaseId,
|
|
12742
|
+
// `error` is mapped onto the wire `errorCode` by deliverOtaEvents —
|
|
12743
|
+
// carry the stable native verdict code there.
|
|
12744
|
+
error: verdict.code ?? "verify_and_stage_release_failed",
|
|
12745
|
+
version: bundle.version
|
|
12715
12746
|
});
|
|
12716
|
-
|
|
12717
|
-
|
|
12747
|
+
return {
|
|
12748
|
+
status: "ERROR",
|
|
12749
|
+
error: `Release verification failed (${verdict.code ?? "unknown"})`,
|
|
12750
|
+
bundle
|
|
12751
|
+
};
|
|
12752
|
+
}
|
|
12753
|
+
staged = true;
|
|
12754
|
+
} else {
|
|
12755
|
+
if (bundle.patch && NativeScaleBunOta_default.stagePatch) {
|
|
12756
|
+
logger.debug(`[OTA] Patch available (${(bundle.patch.size / 1024).toFixed(0)} KB vs ${(bundle.size / 1024).toFixed(0)} KB full) \u2014 trying patch\u2026`);
|
|
12757
|
+
try {
|
|
12758
|
+
staged = await NativeScaleBunOta_default.stagePatch(
|
|
12759
|
+
bundle.patch.url,
|
|
12760
|
+
bundle.patch.sha256,
|
|
12761
|
+
bundle.sha256
|
|
12762
|
+
// expected full-bundle hash after patching
|
|
12763
|
+
);
|
|
12764
|
+
if (staged) {
|
|
12765
|
+
patchUsed = true;
|
|
12766
|
+
logger.debug("[OTA] Patch applied successfully");
|
|
12767
|
+
} else {
|
|
12768
|
+
logger.warn("[OTA] Patch staging returned false \u2014 falling back to full bundle");
|
|
12769
|
+
}
|
|
12770
|
+
} catch (patchErr) {
|
|
12771
|
+
logger.warn(`[OTA] Patch staging failed: ${patchErr.message} \u2014 falling back to full bundle`);
|
|
12772
|
+
}
|
|
12773
|
+
}
|
|
12774
|
+
if (!staged) {
|
|
12775
|
+
try {
|
|
12776
|
+
staged = await retryWithBackoff(async () => {
|
|
12777
|
+
const ok = await NativeScaleBunOta_default.stageBundle(bundle.url, bundle.sha256);
|
|
12778
|
+
if (!ok) {
|
|
12779
|
+
throw new Error("stageBundle returned false");
|
|
12780
|
+
}
|
|
12781
|
+
return ok;
|
|
12782
|
+
}, {
|
|
12783
|
+
label: `download v${bundle.version}`,
|
|
12784
|
+
attempts: 3
|
|
12785
|
+
});
|
|
12786
|
+
} catch {
|
|
12787
|
+
staged = false;
|
|
12788
|
+
}
|
|
12718
12789
|
}
|
|
12719
12790
|
}
|
|
12720
12791
|
unsubscribeProgress();
|
|
@@ -12761,7 +12832,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12761
12832
|
releaseId: bundle.releaseId,
|
|
12762
12833
|
version: bundle.version
|
|
12763
12834
|
});
|
|
12764
|
-
|
|
12835
|
+
logger.debug(`[OTA] Update v${bundle.version} installed successfully!`);
|
|
12765
12836
|
await deliverOtaEvents(params);
|
|
12766
12837
|
const installMode = bundle.installMode ?? "ON_NEXT_RESTART";
|
|
12767
12838
|
if (params.autoRestart || installMode === "IMMEDIATE") {
|
|
@@ -12792,24 +12863,23 @@ var init_OtaOrchestrator = __esm({
|
|
|
12792
12863
|
* native boot-guard marker. If JS crashes before the timer fires, the
|
|
12793
12864
|
* marker stays set and native code reverts on next cold start.
|
|
12794
12865
|
*/
|
|
12795
|
-
startBootGuardHeartbeat() {
|
|
12866
|
+
startBootGuardHeartbeat(preRead) {
|
|
12796
12867
|
if (!NativeScaleBunOta_default) return;
|
|
12797
12868
|
if (this.healthyTimer) {
|
|
12798
12869
|
clearTimeout(this.healthyTimer);
|
|
12799
12870
|
this.healthyTimer = null;
|
|
12800
12871
|
}
|
|
12801
12872
|
try {
|
|
12802
|
-
const
|
|
12803
|
-
|
|
12804
|
-
if (!state.bootMarkerPresent) {
|
|
12873
|
+
const state = preRead !== void 0 ? preRead : JSON.parse(NativeScaleBunOta_default.getSlotState());
|
|
12874
|
+
if (!state?.bootMarkerPresent) {
|
|
12805
12875
|
return;
|
|
12806
12876
|
}
|
|
12807
12877
|
const healthyMs = this.bootGuardConfig.healthyAfterMs ?? DEFAULT_HEALTHY_AFTER_MS;
|
|
12808
|
-
|
|
12878
|
+
logger.debug(`[OTA] Boot guard active \u2014 marking healthy after ${healthyMs}ms`);
|
|
12809
12879
|
this.healthyTimer = setTimeout(() => {
|
|
12810
12880
|
noThrow(() => {
|
|
12811
12881
|
NativeScaleBunOta_default?.markHealthy();
|
|
12812
|
-
|
|
12882
|
+
logger.info("[OTA] Boot guard cleared \u2014 bundle marked healthy \u2713");
|
|
12813
12883
|
this.healthyTimer = null;
|
|
12814
12884
|
const running = this.currentBundle;
|
|
12815
12885
|
if (running?.id) {
|
|
@@ -12855,11 +12925,11 @@ var init_OtaOrchestrator = __esm({
|
|
|
12855
12925
|
} catch {
|
|
12856
12926
|
}
|
|
12857
12927
|
this.resumeRestartArmed = false;
|
|
12858
|
-
|
|
12928
|
+
logger.info("[OTA] App resumed \u2014 activating the staged update now");
|
|
12859
12929
|
this.restart();
|
|
12860
12930
|
}
|
|
12861
12931
|
});
|
|
12862
|
-
|
|
12932
|
+
logger.debug("[OTA] Update staged \u2014 will activate on next resume (ON_NEXT_RESUME)");
|
|
12863
12933
|
} catch {
|
|
12864
12934
|
this.resumeRestartArmed = false;
|
|
12865
12935
|
}
|
|
@@ -12876,7 +12946,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12876
12946
|
*/
|
|
12877
12947
|
restart() {
|
|
12878
12948
|
noThrow(() => {
|
|
12879
|
-
|
|
12949
|
+
logger.info("[OTA] Triggering app restart\u2026");
|
|
12880
12950
|
if (NativeScaleBunOta_default && typeof NativeScaleBunOta_default.restartApp === "function") {
|
|
12881
12951
|
NativeScaleBunOta_default.restartApp();
|
|
12882
12952
|
} else if (import_react_native34.DevSettings && typeof import_react_native34.DevSettings.reload === "function") {
|
|
@@ -12895,17 +12965,25 @@ var init_OtaOrchestrator = __esm({
|
|
|
12895
12965
|
var uiState_exports = {};
|
|
12896
12966
|
__export(uiState_exports, {
|
|
12897
12967
|
clearUiState: () => clearUiState,
|
|
12968
|
+
onUiStateChange: () => onUiStateChange,
|
|
12969
|
+
resetUiStateListeners: () => resetUiStateListeners,
|
|
12898
12970
|
setUiState: () => setUiState,
|
|
12971
|
+
uiStateListenerCount: () => uiStateListenerCount,
|
|
12899
12972
|
uiStateSignature: () => uiStateSignature
|
|
12900
12973
|
});
|
|
12901
12974
|
function setUiState(name, value) {
|
|
12902
12975
|
const n = clean(name);
|
|
12903
12976
|
const v = clean(value);
|
|
12904
|
-
if (n
|
|
12977
|
+
if (!n || !v) return;
|
|
12978
|
+
if (declared.get(n) === v) return;
|
|
12979
|
+
declared.set(n, v);
|
|
12980
|
+
notifyChanged();
|
|
12905
12981
|
}
|
|
12906
12982
|
function clearUiState(name) {
|
|
12983
|
+
const before = declared.size;
|
|
12907
12984
|
if (name === void 0) declared.clear();
|
|
12908
12985
|
else declared.delete(clean(name));
|
|
12986
|
+
if (declared.size !== before) notifyChanged();
|
|
12909
12987
|
}
|
|
12910
12988
|
function uiStateSignature() {
|
|
12911
12989
|
if (!declared.size) return void 0;
|
|
@@ -12918,12 +12996,42 @@ function uiStateSignature() {
|
|
|
12918
12996
|
}
|
|
12919
12997
|
return signature || void 0;
|
|
12920
12998
|
}
|
|
12921
|
-
|
|
12999
|
+
function onUiStateChange(listener) {
|
|
13000
|
+
listeners2.add(listener);
|
|
13001
|
+
return () => {
|
|
13002
|
+
listeners2.delete(listener);
|
|
13003
|
+
};
|
|
13004
|
+
}
|
|
13005
|
+
function resetUiStateListeners() {
|
|
13006
|
+
listeners2.clear();
|
|
13007
|
+
if (pending4 !== null) {
|
|
13008
|
+
clearTimeout(pending4);
|
|
13009
|
+
pending4 = null;
|
|
13010
|
+
}
|
|
13011
|
+
}
|
|
13012
|
+
function uiStateListenerCount() {
|
|
13013
|
+
return listeners2.size;
|
|
13014
|
+
}
|
|
13015
|
+
function notifyChanged() {
|
|
13016
|
+
if (pending4 !== null) return;
|
|
13017
|
+
pending4 = setTimeout(() => {
|
|
13018
|
+
pending4 = null;
|
|
13019
|
+
for (const listener of listeners2) {
|
|
13020
|
+
try {
|
|
13021
|
+
listener();
|
|
13022
|
+
} catch {
|
|
13023
|
+
}
|
|
13024
|
+
}
|
|
13025
|
+
}, 0);
|
|
13026
|
+
}
|
|
13027
|
+
var declared, clean, listeners2, pending4;
|
|
12922
13028
|
var init_uiState = __esm({
|
|
12923
13029
|
"lib/module/features/journey/uiState.js"() {
|
|
12924
13030
|
"use strict";
|
|
12925
13031
|
declared = /* @__PURE__ */ new Map();
|
|
12926
13032
|
clean = (s) => typeof s === "string" ? s.replace(/[;:|]/g, "").trim().slice(0, 32) : "";
|
|
13033
|
+
listeners2 = /* @__PURE__ */ new Set();
|
|
13034
|
+
pending4 = null;
|
|
12927
13035
|
}
|
|
12928
13036
|
});
|
|
12929
13037
|
|
|
@@ -14824,6 +14932,22 @@ var SHAPE = {
|
|
|
14824
14932
|
}
|
|
14825
14933
|
},
|
|
14826
14934
|
captureInteractionHeatmap: bool(true),
|
|
14935
|
+
/**
|
|
14936
|
+
* RN-3 zero-config: patch React Native's ScrollView export so EVERY scroll view reports its
|
|
14937
|
+
* offset, not just ones wrapped in ScaleBunScrollView.
|
|
14938
|
+
*
|
|
14939
|
+
* DEFAULT FALSE, unlike captureInteractionHeatmap above, and the difference is deliberate.
|
|
14940
|
+
* That flag changes what the SDK records about the host; this one changes what the HOST
|
|
14941
|
+
* RENDERS — every scroll view in the app, including ones inside third-party libraries the app
|
|
14942
|
+
* did not write, starts routing through an SDK component. The failure mode is not a wrong
|
|
14943
|
+
* number in a dashboard, it is the host's UI behaving differently because an analytics SDK
|
|
14944
|
+
* replaced a primitive. That is a decision an integrator makes deliberately, not one they
|
|
14945
|
+
* discover afterwards.
|
|
14946
|
+
*
|
|
14947
|
+
* With it off, a plain <ScrollView> yields no content depth and the tap says so
|
|
14948
|
+
* (scroll_source: 'none') rather than defaulting its offset to 0.
|
|
14949
|
+
*/
|
|
14950
|
+
autoInstrumentScrollViews: bool(false),
|
|
14827
14951
|
// Opt-in: intercept console.* and ship the lines to the Diagnose → Logs lane in SaaS
|
|
14828
14952
|
// mode. OFF by default — console output can carry PII/secrets and adds ingest volume,
|
|
14829
14953
|
// so capturing it is the host's explicit choice, not a silent default. When true and a
|
|
@@ -14840,18 +14964,9 @@ var SHAPE = {
|
|
|
14840
14964
|
kind: "str",
|
|
14841
14965
|
opt: true
|
|
14842
14966
|
},
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
},
|
|
14847
|
-
// Rotation support: several pinned keys, any of which may verify a
|
|
14848
|
-
// bundle. Ship a build pinning [old, new], re-sign server-side with
|
|
14849
|
-
// new, drop old next release — no emergency store submission when a
|
|
14850
|
-
// key must be replaced. Merged with publicSigningKey by the facade.
|
|
14851
|
-
publicSigningKeys: {
|
|
14852
|
-
kind: "strArr",
|
|
14853
|
-
opt: true
|
|
14854
|
-
},
|
|
14967
|
+
// No signing-key fields: signed releases are verified natively against
|
|
14968
|
+
// RSA keys embedded in the app package (SB-OTA-RSA-SHA256-V1). JS must
|
|
14969
|
+
// not supply a trusted key.
|
|
14855
14970
|
mandatoryBlocksUi: bool(false)
|
|
14856
14971
|
}
|
|
14857
14972
|
}
|
|
@@ -15654,6 +15769,23 @@ function installNetworkMonitoring(onEvent, options = {}) {
|
|
|
15654
15769
|
});
|
|
15655
15770
|
}
|
|
15656
15771
|
|
|
15772
|
+
// lib/module/features/network/thirdParty.js
|
|
15773
|
+
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";
|
|
15774
|
+
function isThirdPartyBeacon(url, hosts) {
|
|
15775
|
+
if (!url) return false;
|
|
15776
|
+
let host = "";
|
|
15777
|
+
try {
|
|
15778
|
+
host = new URL(url).hostname.toLowerCase();
|
|
15779
|
+
} catch {
|
|
15780
|
+
return false;
|
|
15781
|
+
}
|
|
15782
|
+
if (!host) return false;
|
|
15783
|
+
for (const suffix of (hosts ?? THIRD_PARTY_BEACON_HOSTS).split(",")) {
|
|
15784
|
+
if (host === suffix || host.endsWith("." + suffix)) return true;
|
|
15785
|
+
}
|
|
15786
|
+
return false;
|
|
15787
|
+
}
|
|
15788
|
+
|
|
15657
15789
|
// lib/module/features/network/NetworkFeature.js
|
|
15658
15790
|
init_networkAdapter();
|
|
15659
15791
|
init_SessionManager();
|
|
@@ -15669,10 +15801,12 @@ var NetworkFeature = class {
|
|
|
15669
15801
|
if (this.active) return;
|
|
15670
15802
|
installNetworkMonitoring((data) => {
|
|
15671
15803
|
const isInternal = isSdkInternalUrl(data.url, this.options.sdkBaseUrl);
|
|
15672
|
-
|
|
15804
|
+
const isBeaconOrDenied = this.options.captureThirdParty !== true && isThirdPartyBeacon(data.url, this.options.thirdPartyHosts) || matchesDenyUrl(data.url, this.options.denyUrls);
|
|
15805
|
+
const isExcluded = isInternal || isBeaconOrDenied;
|
|
15806
|
+
if (!isExcluded) {
|
|
15673
15807
|
this.trackFn("$network_request", data);
|
|
15674
15808
|
}
|
|
15675
|
-
if (data.type === "network") {
|
|
15809
|
+
if (data.type === "network" && !isBeaconOrDenied) {
|
|
15676
15810
|
reportNetworkRequest({
|
|
15677
15811
|
method: data.method,
|
|
15678
15812
|
url: data.url,
|
|
@@ -15688,7 +15822,7 @@ var NetworkFeature = class {
|
|
|
15688
15822
|
requestBody: data.requestBody,
|
|
15689
15823
|
responseBody: data.responseBody
|
|
15690
15824
|
});
|
|
15691
|
-
if (!
|
|
15825
|
+
if (!isExcluded) {
|
|
15692
15826
|
const sessionManager = SessionManager.getExistingInstance();
|
|
15693
15827
|
if (sessionManager) {
|
|
15694
15828
|
const backendTransport = sessionManager.getBackendTransport();
|
|
@@ -15719,6 +15853,13 @@ var NetworkFeature = class {
|
|
|
15719
15853
|
return this.active;
|
|
15720
15854
|
}
|
|
15721
15855
|
};
|
|
15856
|
+
function matchesDenyUrl(url, patterns) {
|
|
15857
|
+
if (!patterns || !patterns.length || !url) return false;
|
|
15858
|
+
for (const p of patterns) {
|
|
15859
|
+
if (typeof p === "string" ? url.includes(p) : p.test(url)) return true;
|
|
15860
|
+
}
|
|
15861
|
+
return false;
|
|
15862
|
+
}
|
|
15722
15863
|
function isSdkInternalUrl(url, sdkBaseUrl) {
|
|
15723
15864
|
if (!url) return false;
|
|
15724
15865
|
try {
|
|
@@ -17021,22 +17162,22 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
|
|
|
17021
17162
|
* lose the snapshot entirely. `imageData.length` is the base64 length, which is within ~1% of the
|
|
17022
17163
|
* serialized cost and needs no extra pass.
|
|
17023
17164
|
*/
|
|
17024
|
-
static _selectFramesToPersist(
|
|
17165
|
+
static _selectFramesToPersist(pending5) {
|
|
17025
17166
|
const isKeyframe = (b) => b.frame.frameType !== "delta";
|
|
17026
17167
|
const size = (b) => b.imageData?.length ?? 0;
|
|
17027
17168
|
const keep = [];
|
|
17028
17169
|
let budget = _BackendSessionAdapter.MAX_PERSIST_BYTES;
|
|
17029
17170
|
const maxCount = _BackendSessionAdapter.MAX_FRAMES_PER_BATCH;
|
|
17030
|
-
for (let i =
|
|
17031
|
-
const b =
|
|
17171
|
+
for (let i = pending5.length - 1; i >= 0; i--) {
|
|
17172
|
+
const b = pending5[i];
|
|
17032
17173
|
if (!isKeyframe(b)) continue;
|
|
17033
17174
|
const cost = size(b);
|
|
17034
17175
|
if (cost > budget || keep.length >= maxCount) continue;
|
|
17035
17176
|
budget -= cost;
|
|
17036
17177
|
keep.push(b);
|
|
17037
17178
|
}
|
|
17038
|
-
for (let i =
|
|
17039
|
-
const b =
|
|
17179
|
+
for (let i = pending5.length - 1; i >= 0; i--) {
|
|
17180
|
+
const b = pending5[i];
|
|
17040
17181
|
if (isKeyframe(b)) continue;
|
|
17041
17182
|
const cost = size(b);
|
|
17042
17183
|
if (cost > budget || keep.length >= maxCount) continue;
|
|
@@ -17469,6 +17610,10 @@ var SDKBootstrapper = class {
|
|
|
17469
17610
|
return null;
|
|
17470
17611
|
}
|
|
17471
17612
|
this.config = result.data;
|
|
17613
|
+
const requestedApiBaseUrl = rawConfig?.apiBaseUrl;
|
|
17614
|
+
if (typeof requestedApiBaseUrl === "string" && requestedApiBaseUrl && requestedApiBaseUrl !== DEFAULT_API_BASE_URL) {
|
|
17615
|
+
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.`);
|
|
17616
|
+
}
|
|
17472
17617
|
this.config.apiBaseUrl = DEFAULT_API_BASE_URL;
|
|
17473
17618
|
if (this.config.logLevel) {
|
|
17474
17619
|
logger.setLevelByName(this.config.logLevel);
|
|
@@ -17633,6 +17778,20 @@ var SDKBootstrapper = class {
|
|
|
17633
17778
|
}));
|
|
17634
17779
|
}
|
|
17635
17780
|
await this.featureRegistry.initializeAll(featureContext);
|
|
17781
|
+
if (this.config.autoInstrumentScrollViews === true) {
|
|
17782
|
+
try {
|
|
17783
|
+
const {
|
|
17784
|
+
autoInstrumentScrollViews: autoInstrumentScrollViews2
|
|
17785
|
+
} = (init_autoInstrumentScroll(), __toCommonJS(autoInstrumentScroll_exports));
|
|
17786
|
+
const outcome = autoInstrumentScrollViews2();
|
|
17787
|
+
if (!outcome.installed) {
|
|
17788
|
+
logger.warn("[Bootstrap] autoInstrumentScrollViews was requested but could not be installed: " + outcome.reason + " \u2014 taps will carry no content depth.");
|
|
17789
|
+
} else {
|
|
17790
|
+
__DEV__ && logger.debug("[Bootstrap] ScrollView auto-instrumentation installed");
|
|
17791
|
+
}
|
|
17792
|
+
} catch {
|
|
17793
|
+
}
|
|
17794
|
+
}
|
|
17636
17795
|
if (features.replay !== false) {
|
|
17637
17796
|
try {
|
|
17638
17797
|
__DEV__ && logger.debug("[Bootstrap] Initializing unified SessionManager");
|
|
@@ -20276,14 +20435,14 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20276
20435
|
this._apiBaseUrl = result.config.apiBaseUrl;
|
|
20277
20436
|
this._maybeStartEventTracker(rawConfig);
|
|
20278
20437
|
if (this._pendingIdentity) {
|
|
20279
|
-
const
|
|
20438
|
+
const pending5 = this._pendingIdentity;
|
|
20280
20439
|
this._pendingIdentity = null;
|
|
20281
|
-
this.identify(
|
|
20440
|
+
this.identify(pending5.userId, pending5.traits);
|
|
20282
20441
|
}
|
|
20283
20442
|
const realQueue = coreContainer.get("PersistentQueue");
|
|
20284
20443
|
const buffered = this._preInitBuffer.popBatch(this._preInitBuffer.length());
|
|
20285
20444
|
if (buffered.length > 0) {
|
|
20286
|
-
|
|
20445
|
+
logger.debug(`[ScaleBun] Draining ${buffered.length} pre-init events into persistent queue`);
|
|
20287
20446
|
for (const envelope of buffered) {
|
|
20288
20447
|
realQueue.add(envelope);
|
|
20289
20448
|
}
|
|
@@ -20311,17 +20470,11 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20311
20470
|
/**
|
|
20312
20471
|
* Boot the OTA orchestrator when the init config asks for it.
|
|
20313
20472
|
*
|
|
20314
|
-
*
|
|
20315
|
-
*
|
|
20316
|
-
*
|
|
20317
|
-
*
|
|
20318
|
-
*
|
|
20319
|
-
*
|
|
20320
|
-
* Verification comes from `ota.verifySignature` when supplied, else the
|
|
20321
|
-
* built-in @noble-based verifier (optional peers). Pinning keys with
|
|
20322
|
-
* NEITHER available is fail-CLOSED by design (signature.ts) — an update
|
|
20323
|
-
* that cannot be verified is not installed. We say that out loud rather
|
|
20324
|
-
* than letting the app discover it as a silent no-update condition.
|
|
20473
|
+
* Signature enforcement is no longer configured from JS. Signed releases are
|
|
20474
|
+
* verified and staged in NATIVE code against RSA keys embedded in the app
|
|
20475
|
+
* package (SB-OTA-RSA-SHA256-V1); unsigned releases keep working on SHA-256
|
|
20476
|
+
* integrity. There are no signing-key or `verifySignature` options to pass
|
|
20477
|
+
* through — the host embeds its trusted keys natively.
|
|
20325
20478
|
*/
|
|
20326
20479
|
_maybeStartOta(rawConfig) {
|
|
20327
20480
|
const ota = rawConfig?.ota;
|
|
@@ -20330,29 +20483,8 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20330
20483
|
const {
|
|
20331
20484
|
otaOrchestrator: otaOrchestrator2
|
|
20332
20485
|
} = (init_OtaOrchestrator(), __toCommonJS(OtaOrchestrator_exports));
|
|
20333
|
-
const rawSingle = ota.publicSigningKey;
|
|
20334
|
-
const rawList = ota.publicSigningKeys;
|
|
20335
|
-
const publicKeys = Array.from(new Set([...typeof rawSingle === "string" && rawSingle ? [rawSingle] : [], ...Array.isArray(rawList) ? rawList : []].filter((k) => typeof k === "string" && k.trim().length > 0)));
|
|
20336
|
-
const signingRequested = publicKeys.length > 0 || Array.isArray(rawList);
|
|
20337
|
-
const verifier = ota.verifySignature;
|
|
20338
|
-
if (signingRequested && typeof verifier !== "function") {
|
|
20339
|
-
const {
|
|
20340
|
-
getBuiltinVerifier: getBuiltinVerifier2
|
|
20341
|
-
} = (init_builtinVerifier(), __toCommonJS(builtinVerifier_exports));
|
|
20342
|
-
if (!getBuiltinVerifier2()) {
|
|
20343
|
-
logger.warn("[ScaleBun] A signing key is pinned but no signature verifier is available. Signature checking is fail-closed: updates will be REJECTED until one exists. Install the optional peers `@noble/ed25519` + `@noble/hashes` (no further code needed), or supply `ota.verifySignature`.");
|
|
20344
|
-
}
|
|
20345
|
-
}
|
|
20346
20486
|
const healthyAfterMs = typeof ota.healthyAfterMs === "number" ? ota.healthyAfterMs : typeof ota.healthyTimeoutMs === "number" ? ota.healthyTimeoutMs : void 0;
|
|
20347
20487
|
otaOrchestrator2.init({
|
|
20348
|
-
...signingRequested ? {
|
|
20349
|
-
signature: {
|
|
20350
|
-
// Single key stays a plain string — the shape every
|
|
20351
|
-
// existing consumer and test already handles.
|
|
20352
|
-
publicKey: publicKeys.length === 1 ? publicKeys[0] : publicKeys,
|
|
20353
|
-
verifier
|
|
20354
|
-
}
|
|
20355
|
-
} : {},
|
|
20356
20488
|
...healthyAfterMs !== void 0 ? {
|
|
20357
20489
|
healthyAfterMs
|
|
20358
20490
|
} : {}
|
|
@@ -20401,7 +20533,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20401
20533
|
}
|
|
20402
20534
|
const clientKey = this._clientKey;
|
|
20403
20535
|
const apiUrl = this._apiBaseUrl;
|
|
20404
|
-
if (!clientKey || !apiUrl)
|
|
20536
|
+
if (!clientKey || !apiUrl) {
|
|
20537
|
+
return;
|
|
20538
|
+
}
|
|
20405
20539
|
this._otaCheckInFlight = true;
|
|
20406
20540
|
try {
|
|
20407
20541
|
const info = await bridgeAdapter.getDeviceInfo();
|
|
@@ -20452,7 +20586,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20452
20586
|
if (!dbgConfig) return;
|
|
20453
20587
|
dbgConfig.__skipDeprecationWarning = true;
|
|
20454
20588
|
this.enableDebug(dbgConfig);
|
|
20455
|
-
|
|
20589
|
+
logger.info("[ScaleBun] Debug auto-connected via init({ debug }).");
|
|
20456
20590
|
} catch (err) {
|
|
20457
20591
|
logger.warn("[ScaleBun] Auto debug connection failed:", err?.message);
|
|
20458
20592
|
}
|
|
@@ -20466,7 +20600,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20466
20600
|
const apiBaseUrl = this._apiBaseUrl;
|
|
20467
20601
|
if (!enabled || !appId || !clientKey || !apiBaseUrl) {
|
|
20468
20602
|
if (enabled && !appId) {
|
|
20469
|
-
|
|
20603
|
+
logger.info("[ScaleBun] Envelope event tracking disabled \u2014 no appId in init config.");
|
|
20470
20604
|
}
|
|
20471
20605
|
return;
|
|
20472
20606
|
}
|
|
@@ -20516,7 +20650,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20516
20650
|
onEvent: (eventName, revenue) => this._skanManager?.onEvent(eventName, revenue)
|
|
20517
20651
|
});
|
|
20518
20652
|
this._eventTracker.start();
|
|
20519
|
-
|
|
20653
|
+
logger.info("[ScaleBun] Envelope event tracking started \u2192 /v1/batch.");
|
|
20520
20654
|
this._applyNativeDeviceContext(this._eventTracker);
|
|
20521
20655
|
this._captureInstallReferrer(this._eventTracker);
|
|
20522
20656
|
const tracker = this._eventTracker;
|
|
@@ -21150,7 +21284,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21150
21284
|
const cfg = await (this._getEngageTransport()?.fetchEngageConfig(context) ?? Promise.resolve(null));
|
|
21151
21285
|
const messages = cfg?.inAppMessages ?? [];
|
|
21152
21286
|
const previews = messages.filter((m) => m.preview === true || m.isTest === true);
|
|
21153
|
-
|
|
21287
|
+
logger.info(`[Engage] debugFetchInAppMessages \u2192 ${messages.length} message(s), ${previews.length} test preview(s)`);
|
|
21154
21288
|
return messages;
|
|
21155
21289
|
},
|
|
21156
21290
|
/** Upload a survey/NPS/feedback response (idempotent, rides the outbox). */
|
|
@@ -21426,7 +21560,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21426
21560
|
logger.warn("SDK not initialized. Event dropped:", name);
|
|
21427
21561
|
return;
|
|
21428
21562
|
}
|
|
21429
|
-
|
|
21563
|
+
logger.debug("Tracking event:", name);
|
|
21430
21564
|
engageSignals.emit(name, payload);
|
|
21431
21565
|
const adapter = SessionManager.getExistingInstance()?.getBackendTransport();
|
|
21432
21566
|
if (!isUploadedSessionEvent(name)) {
|
|
@@ -21555,7 +21689,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21555
21689
|
await Promise.all([this._eventTracker?.flush() ?? Promise.resolve(), adapter?.flushAnalytics() ?? Promise.resolve()]);
|
|
21556
21690
|
return;
|
|
21557
21691
|
}
|
|
21558
|
-
|
|
21692
|
+
logger.debug("Flushing events from persistent queue...");
|
|
21559
21693
|
const httpClient = coreContainer.get("HttpClient");
|
|
21560
21694
|
await flushQueue(this._getQueue(), httpClient, getDeviceId());
|
|
21561
21695
|
});
|
|
@@ -21577,9 +21711,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21577
21711
|
const bootstrapPerfFeature = getPerformanceFeature();
|
|
21578
21712
|
if (bootstrapPerfFeature) {
|
|
21579
21713
|
this._performanceFeature = bootstrapPerfFeature;
|
|
21580
|
-
|
|
21714
|
+
logger.debug("Performance feature linked from bootstrap");
|
|
21581
21715
|
}
|
|
21582
|
-
|
|
21716
|
+
logger.info("Debug enabled");
|
|
21583
21717
|
});
|
|
21584
21718
|
}
|
|
21585
21719
|
/** Disable the debug subsystem and clean up */
|
|
@@ -21722,14 +21856,14 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21722
21856
|
return false;
|
|
21723
21857
|
}
|
|
21724
21858
|
sm.startSession(metadata);
|
|
21725
|
-
|
|
21859
|
+
logger.info("session.start() \u2705 sessionId:", sm.currentSessionId);
|
|
21726
21860
|
return true;
|
|
21727
21861
|
},
|
|
21728
21862
|
end: (reason) => {
|
|
21729
21863
|
const sm = SessionManager.getExistingInstance();
|
|
21730
21864
|
if (!sm || !sm.isActive) return false;
|
|
21731
21865
|
sm.endSession(reason);
|
|
21732
|
-
|
|
21866
|
+
logger.info("session.end() \u2705");
|
|
21733
21867
|
return true;
|
|
21734
21868
|
},
|
|
21735
21869
|
emitEvent: (type, data) => {
|
|
@@ -21782,7 +21916,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21782
21916
|
...this._privacyConfig,
|
|
21783
21917
|
...config
|
|
21784
21918
|
};
|
|
21785
|
-
|
|
21919
|
+
logger.debug("[report] Privacy config updated");
|
|
21786
21920
|
},
|
|
21787
21921
|
/** Set branding for exported reports. */
|
|
21788
21922
|
setBranding: (config) => {
|
|
@@ -21790,7 +21924,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21790
21924
|
...this._brandingConfig,
|
|
21791
21925
|
...config
|
|
21792
21926
|
};
|
|
21793
|
-
|
|
21927
|
+
logger.debug("[report] Branding config updated");
|
|
21794
21928
|
},
|
|
21795
21929
|
/** Get current privacy config. */
|
|
21796
21930
|
getPrivacy: () => ({
|
|
@@ -21837,9 +21971,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21837
21971
|
AutoScreenDetector: AutoScreenDetector2
|
|
21838
21972
|
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
21839
21973
|
AutoScreenDetector2.getInstance().setNavigationRef(ref);
|
|
21840
|
-
|
|
21974
|
+
logger.info("[ScaleBun] Navigation ref set \u2192 auto screen detection active");
|
|
21841
21975
|
} catch (err) {
|
|
21842
|
-
|
|
21976
|
+
logger.debug("[ScaleBun] Navigation ref stashed (detector not ready):", err?.message);
|
|
21843
21977
|
}
|
|
21844
21978
|
});
|
|
21845
21979
|
}
|
|
@@ -22146,8 +22280,8 @@ function installEngageTriggerEngine(deps) {
|
|
|
22146
22280
|
if (!cachedConfig) return;
|
|
22147
22281
|
configReady = true;
|
|
22148
22282
|
const now2 = Date.now();
|
|
22149
|
-
const
|
|
22150
|
-
for (const e of
|
|
22283
|
+
const pending5 = recent.splice(0);
|
|
22284
|
+
for (const e of pending5) {
|
|
22151
22285
|
const age = now2 - e.at;
|
|
22152
22286
|
if (age > RECENT_TTL_MS) continue;
|
|
22153
22287
|
const remaining = delayMsForTrigger(cachedConfig, e.options.trigger) - age;
|
|
@@ -23412,11 +23546,11 @@ var keyOf = (value) => value.trim();
|
|
|
23412
23546
|
var validRect = (rect) => [rect.x, rect.y, rect.width, rect.height].every(Number.isFinite) && rect.width > 0 && rect.height > 0;
|
|
23413
23547
|
function createEngageAnchorRegistry() {
|
|
23414
23548
|
const entries = /* @__PURE__ */ new Map();
|
|
23415
|
-
const
|
|
23549
|
+
const listeners3 = /* @__PURE__ */ new Map();
|
|
23416
23550
|
const notify = (rawKey) => {
|
|
23417
23551
|
const key = keyOf(rawKey);
|
|
23418
23552
|
if (!key) return;
|
|
23419
|
-
|
|
23553
|
+
listeners3.get(key)?.forEach((listener) => listener());
|
|
23420
23554
|
};
|
|
23421
23555
|
return {
|
|
23422
23556
|
register(rawKey, node) {
|
|
@@ -23458,12 +23592,12 @@ function createEngageAnchorRegistry() {
|
|
|
23458
23592
|
const key = keyOf(rawKey);
|
|
23459
23593
|
if (!key) return () => {
|
|
23460
23594
|
};
|
|
23461
|
-
const set =
|
|
23595
|
+
const set = listeners3.get(key) ?? /* @__PURE__ */ new Set();
|
|
23462
23596
|
set.add(listener);
|
|
23463
|
-
|
|
23597
|
+
listeners3.set(key, set);
|
|
23464
23598
|
return () => {
|
|
23465
23599
|
set.delete(listener);
|
|
23466
|
-
if (set.size === 0)
|
|
23600
|
+
if (set.size === 0) listeners3.delete(key);
|
|
23467
23601
|
};
|
|
23468
23602
|
},
|
|
23469
23603
|
notify
|
|
@@ -24310,11 +24444,11 @@ var normalizedWheelValue = (value) => value.toLowerCase().replace(/\s+/g, " ").t
|
|
|
24310
24444
|
function resolveWheelOutcome(segments, source) {
|
|
24311
24445
|
const identity = wheelOutcomeIdentity(source);
|
|
24312
24446
|
const index = findWheelOutcomeIndex(segments, identity);
|
|
24313
|
-
const
|
|
24447
|
+
const resolved = index >= 0;
|
|
24314
24448
|
return {
|
|
24315
|
-
index:
|
|
24316
|
-
label: identity.resultLabel || (
|
|
24317
|
-
resolved
|
|
24449
|
+
index: resolved ? index : 0,
|
|
24450
|
+
label: identity.resultLabel || (resolved ? segments[index]?.fullLabel ?? "" : ""),
|
|
24451
|
+
resolved
|
|
24318
24452
|
};
|
|
24319
24453
|
}
|
|
24320
24454
|
function SpinWheelInApp(props) {
|
|
@@ -32572,10 +32706,10 @@ var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
|
32572
32706
|
var keyOf2 = (value) => value.trim();
|
|
32573
32707
|
function createEngageInlinePlacementRegistry() {
|
|
32574
32708
|
const slots = /* @__PURE__ */ new Map();
|
|
32575
|
-
const
|
|
32576
|
-
const
|
|
32709
|
+
const pending5 = /* @__PURE__ */ new Map();
|
|
32710
|
+
const listeners3 = /* @__PURE__ */ new Map();
|
|
32577
32711
|
const notify = (key) => {
|
|
32578
|
-
|
|
32712
|
+
listeners3.get(key)?.forEach((listener) => listener());
|
|
32579
32713
|
};
|
|
32580
32714
|
return {
|
|
32581
32715
|
register(rawKey, sink) {
|
|
@@ -32592,7 +32726,7 @@ function createEngageInlinePlacementRegistry() {
|
|
|
32592
32726
|
sink,
|
|
32593
32727
|
token
|
|
32594
32728
|
});
|
|
32595
|
-
sink(
|
|
32729
|
+
sink(pending5.get(key) ?? null);
|
|
32596
32730
|
if (!previous) notify(key);
|
|
32597
32731
|
return () => {
|
|
32598
32732
|
if (slots.get(key)?.token !== token) return;
|
|
@@ -32603,7 +32737,7 @@ function createEngageInlinePlacementRegistry() {
|
|
|
32603
32737
|
deliver(rawKey, delivery) {
|
|
32604
32738
|
const key = keyOf2(rawKey);
|
|
32605
32739
|
if (!key) return false;
|
|
32606
|
-
|
|
32740
|
+
pending5.set(key, delivery);
|
|
32607
32741
|
const slot = slots.get(key);
|
|
32608
32742
|
slot?.sink(delivery);
|
|
32609
32743
|
return slot !== void 0;
|
|
@@ -32611,9 +32745,9 @@ function createEngageInlinePlacementRegistry() {
|
|
|
32611
32745
|
clear(rawKey, messageId) {
|
|
32612
32746
|
const key = keyOf2(rawKey);
|
|
32613
32747
|
if (!key) return;
|
|
32614
|
-
const current =
|
|
32748
|
+
const current = pending5.get(key);
|
|
32615
32749
|
if (!current || messageId !== void 0 && current.message?.id !== messageId) return;
|
|
32616
|
-
|
|
32750
|
+
pending5.delete(key);
|
|
32617
32751
|
slots.get(key)?.sink(null);
|
|
32618
32752
|
},
|
|
32619
32753
|
has(rawKey) {
|
|
@@ -32624,12 +32758,12 @@ function createEngageInlinePlacementRegistry() {
|
|
|
32624
32758
|
const key = keyOf2(rawKey);
|
|
32625
32759
|
if (!key) return () => {
|
|
32626
32760
|
};
|
|
32627
|
-
const set =
|
|
32761
|
+
const set = listeners3.get(key) ?? /* @__PURE__ */ new Set();
|
|
32628
32762
|
set.add(listener);
|
|
32629
|
-
|
|
32763
|
+
listeners3.set(key, set);
|
|
32630
32764
|
return () => {
|
|
32631
32765
|
set.delete(listener);
|
|
32632
|
-
if (set.size === 0)
|
|
32766
|
+
if (set.size === 0) listeners3.delete(key);
|
|
32633
32767
|
};
|
|
32634
32768
|
}
|
|
32635
32769
|
};
|
|
@@ -33173,8 +33307,8 @@ function EngagePromptProvider({
|
|
|
33173
33307
|
__DEV__ && logger.debug("[Engage] no eligible in-app message to render");
|
|
33174
33308
|
return false;
|
|
33175
33309
|
}
|
|
33176
|
-
const
|
|
33177
|
-
activeInAppVariantRef.current = message.experiment?.enabled ?
|
|
33310
|
+
const resolved = selectInAppVariant(message, getDeviceId());
|
|
33311
|
+
activeInAppVariantRef.current = message.experiment?.enabled ? resolved : null;
|
|
33178
33312
|
store.recordImpression(message.id, Date.now(), message.revision);
|
|
33179
33313
|
setActiveInApp(message);
|
|
33180
33314
|
return true;
|
|
@@ -33418,6 +33552,7 @@ var import_react_native43 = require("react-native");
|
|
|
33418
33552
|
// lib/module/features/journey/touchTarget.js
|
|
33419
33553
|
var MAX_DEPTH = 12;
|
|
33420
33554
|
var MAX_LEN = 60;
|
|
33555
|
+
var MAX_PATH_SEGMENTS = 4;
|
|
33421
33556
|
function str2(v) {
|
|
33422
33557
|
if (typeof v !== "string") return void 0;
|
|
33423
33558
|
const s = v.trim();
|
|
@@ -33449,41 +33584,136 @@ function fiberOf(event) {
|
|
|
33449
33584
|
const e = event;
|
|
33450
33585
|
const direct = e._targetInst;
|
|
33451
33586
|
if (direct && typeof direct === "object") return direct;
|
|
33452
|
-
const target = e.target;
|
|
33453
|
-
if (target
|
|
33454
|
-
const handle = target.
|
|
33587
|
+
const target = e.target ?? e.nativeEvent?.target;
|
|
33588
|
+
if (target) {
|
|
33589
|
+
const handle = target._internalInstanceHandle ?? target._targetInst;
|
|
33455
33590
|
if (handle && typeof handle === "object") return handle;
|
|
33456
33591
|
}
|
|
33457
33592
|
return null;
|
|
33458
33593
|
}
|
|
33459
|
-
function resolveTouchTarget(event,
|
|
33594
|
+
function resolveTouchTarget(event, optsOrMaxDepth = {}) {
|
|
33595
|
+
const opts = typeof optsOrMaxDepth === "number" ? {
|
|
33596
|
+
maxDepth: optsOrMaxDepth
|
|
33597
|
+
} : optsOrMaxDepth;
|
|
33598
|
+
const maxDepth = opts.maxDepth ?? MAX_DEPTH;
|
|
33460
33599
|
let f = fiberOf(event);
|
|
33461
33600
|
if (!f) return null;
|
|
33462
33601
|
const out = {};
|
|
33602
|
+
const segments = [];
|
|
33603
|
+
const ancestors = [];
|
|
33463
33604
|
let depth = 0;
|
|
33464
33605
|
try {
|
|
33465
33606
|
while (f && depth < maxDepth) {
|
|
33466
33607
|
const props = f.memoizedProps;
|
|
33467
33608
|
if (props) {
|
|
33468
|
-
|
|
33609
|
+
const beforeTestId = out.testID;
|
|
33469
33610
|
out.testID ??= str2(props.testID);
|
|
33470
|
-
out.
|
|
33611
|
+
if (!beforeTestId && out.testID) out.node = f.stateNode;
|
|
33612
|
+
if (typeof props.cellKey === "string" && props.cellKey) {
|
|
33613
|
+
out.itemKey ??= str2(props.cellKey);
|
|
33614
|
+
if (typeof props.index === "number" && Number.isFinite(props.index)) {
|
|
33615
|
+
out.itemIndex ??= props.index;
|
|
33616
|
+
}
|
|
33617
|
+
}
|
|
33618
|
+
if (opts.includeText) {
|
|
33619
|
+
out.accessibilityLabel ??= str2(props.accessibilityLabel);
|
|
33620
|
+
out.text ??= textChild(props);
|
|
33621
|
+
}
|
|
33471
33622
|
}
|
|
33472
|
-
|
|
33473
|
-
|
|
33623
|
+
if (f.stateNode) ancestors.push(f.stateNode);
|
|
33624
|
+
const name = componentName(f);
|
|
33625
|
+
if (name) {
|
|
33626
|
+
out.component ??= name;
|
|
33627
|
+
if (segments[segments.length - 1] !== name && segments.length < MAX_PATH_SEGMENTS) {
|
|
33628
|
+
segments.push(name);
|
|
33629
|
+
}
|
|
33630
|
+
}
|
|
33631
|
+
if (out.testID && segments.length >= MAX_PATH_SEGMENTS) break;
|
|
33474
33632
|
f = f.return ?? null;
|
|
33475
33633
|
depth++;
|
|
33476
33634
|
}
|
|
33477
33635
|
} catch {
|
|
33478
33636
|
}
|
|
33479
|
-
|
|
33637
|
+
if (segments.length) out.path = segments.join("<");
|
|
33638
|
+
if (!out.node) out.node = fiberOf(event)?.stateNode;
|
|
33639
|
+
if (ancestors.length) out.ancestors = ancestors;
|
|
33640
|
+
return out.testID || out.path || out.component || out.accessibilityLabel || out.text || out.itemKey ? out : null;
|
|
33480
33641
|
}
|
|
33481
33642
|
function describeTouchTarget(t) {
|
|
33482
33643
|
if (!t) return void 0;
|
|
33483
|
-
return t.
|
|
33644
|
+
return t.testID || t.path || t.component;
|
|
33645
|
+
}
|
|
33646
|
+
function targetIdentity(t) {
|
|
33647
|
+
if (!t) return {
|
|
33648
|
+
source: "none"
|
|
33649
|
+
};
|
|
33650
|
+
if (t.testID) return {
|
|
33651
|
+
id: t.testID,
|
|
33652
|
+
source: "testID"
|
|
33653
|
+
};
|
|
33654
|
+
if (t.path) return {
|
|
33655
|
+
id: t.path,
|
|
33656
|
+
source: "path"
|
|
33657
|
+
};
|
|
33658
|
+
if (t.component) return {
|
|
33659
|
+
id: t.component,
|
|
33660
|
+
source: "component"
|
|
33661
|
+
};
|
|
33662
|
+
return {
|
|
33663
|
+
source: "none"
|
|
33664
|
+
};
|
|
33665
|
+
}
|
|
33666
|
+
|
|
33667
|
+
// lib/module/features/journey/targetGeometry.js
|
|
33668
|
+
function readRectSync(node) {
|
|
33669
|
+
if (!node || typeof node !== "object") return null;
|
|
33670
|
+
const n = node;
|
|
33671
|
+
const fn = typeof n.unstable_getBoundingClientRect === "function" ? n.unstable_getBoundingClientRect : typeof n.getBoundingClientRect === "function" ? n.getBoundingClientRect : null;
|
|
33672
|
+
if (!fn) return null;
|
|
33673
|
+
try {
|
|
33674
|
+
const r = fn.call(n);
|
|
33675
|
+
if (!r) return null;
|
|
33676
|
+
const {
|
|
33677
|
+
x,
|
|
33678
|
+
y,
|
|
33679
|
+
width,
|
|
33680
|
+
height
|
|
33681
|
+
} = r;
|
|
33682
|
+
if (![x, y, width, height].every((v) => typeof v === "number" && Number.isFinite(v))) return null;
|
|
33683
|
+
if (width <= 0 || height <= 0) return null;
|
|
33684
|
+
return {
|
|
33685
|
+
x,
|
|
33686
|
+
y,
|
|
33687
|
+
width,
|
|
33688
|
+
height
|
|
33689
|
+
};
|
|
33690
|
+
} catch {
|
|
33691
|
+
return null;
|
|
33692
|
+
}
|
|
33693
|
+
}
|
|
33694
|
+
function normalizeRect(rect, base) {
|
|
33695
|
+
if (!rect || !base || base.w <= 0 || base.h <= 0) return null;
|
|
33696
|
+
const out = {
|
|
33697
|
+
x: rect.x / base.w,
|
|
33698
|
+
y: rect.y / base.h,
|
|
33699
|
+
width: rect.width / base.w,
|
|
33700
|
+
height: rect.height / base.h
|
|
33701
|
+
};
|
|
33702
|
+
if (![out.x, out.y, out.width, out.height].every((v) => Number.isFinite(v))) return null;
|
|
33703
|
+
return out;
|
|
33704
|
+
}
|
|
33705
|
+
function localPoint(rect, nx, ny) {
|
|
33706
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return null;
|
|
33707
|
+
if (typeof nx !== "number" || typeof ny !== "number") return null;
|
|
33708
|
+
if (!Number.isFinite(nx) || !Number.isFinite(ny)) return null;
|
|
33709
|
+
return {
|
|
33710
|
+
u: (nx - rect.x) / rect.width,
|
|
33711
|
+
v: (ny - rect.y) / rect.height
|
|
33712
|
+
};
|
|
33484
33713
|
}
|
|
33485
33714
|
|
|
33486
33715
|
// lib/module/features/journey/ScaleBunDebugRoot.js
|
|
33716
|
+
init_scrollContext();
|
|
33487
33717
|
init_device();
|
|
33488
33718
|
init_interactionProtocol();
|
|
33489
33719
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
@@ -33495,6 +33725,7 @@ function getBootstrap() {
|
|
|
33495
33725
|
return _bootstrap;
|
|
33496
33726
|
}
|
|
33497
33727
|
var JourneyContext = /* @__PURE__ */ (0, import_react9.createContext)(null);
|
|
33728
|
+
var UI_STATE_CAPTURE_MIN_INTERVAL_MS = 1e3;
|
|
33498
33729
|
function ScaleBunDebugRoot({
|
|
33499
33730
|
children,
|
|
33500
33731
|
navigationRef,
|
|
@@ -33673,6 +33904,28 @@ function ScaleBunDebugRoot({
|
|
|
33673
33904
|
clearTimeout(timer);
|
|
33674
33905
|
};
|
|
33675
33906
|
}, [navigationRef]);
|
|
33907
|
+
(0, import_react9.useEffect)(() => {
|
|
33908
|
+
let lastCaptureTs = 0;
|
|
33909
|
+
const {
|
|
33910
|
+
onUiStateChange: onUiStateChange2
|
|
33911
|
+
} = (init_uiState(), __toCommonJS(uiState_exports));
|
|
33912
|
+
const unsubscribe = onUiStateChange2(() => {
|
|
33913
|
+
const now2 = Date.now();
|
|
33914
|
+
if (now2 - lastCaptureTs < UI_STATE_CAPTURE_MIN_INTERVAL_MS) return;
|
|
33915
|
+
lastCaptureTs = now2;
|
|
33916
|
+
try {
|
|
33917
|
+
const {
|
|
33918
|
+
SessionManager: SessionManager2
|
|
33919
|
+
} = (init_SessionManager(), __toCommonJS(SessionManager_exports));
|
|
33920
|
+
const sm = SessionManager2.getExistingInstance();
|
|
33921
|
+
if (sm && typeof sm.captureManualFrame === "function") {
|
|
33922
|
+
void sm.captureManualFrame();
|
|
33923
|
+
}
|
|
33924
|
+
} catch {
|
|
33925
|
+
}
|
|
33926
|
+
});
|
|
33927
|
+
return unsubscribe;
|
|
33928
|
+
}, []);
|
|
33676
33929
|
const lastNativeTouchTsRef = (0, import_react9.useRef)(0);
|
|
33677
33930
|
const nativeTrackingConfirmedRef = (0, import_react9.useRef)(false);
|
|
33678
33931
|
const touchStartRef = (0, import_react9.useRef)(null);
|
|
@@ -33689,10 +33942,15 @@ function ScaleBunDebugRoot({
|
|
|
33689
33942
|
lastNativeTouchTsRef.current = Date.now();
|
|
33690
33943
|
nativeTrackingConfirmedRef.current = true;
|
|
33691
33944
|
const nativeOccurredAt = typeof event.occurredAt === "number" ? event.occurredAt : (typeof event.timestamp === "number" ? event.timestamp : Date.now()) - (typeof event.durationMs === "number" ? event.durationMs : 0);
|
|
33692
|
-
|
|
33945
|
+
pruneInteractionStarts(interactionStartsRef.current, nativeOccurredAt);
|
|
33946
|
+
const correlation = correlateInteraction(interactionStartsRef.current, nativeOccurredAt);
|
|
33947
|
+
const start = correlation.start;
|
|
33948
|
+
if (correlation.index >= 0) {
|
|
33949
|
+
interactionStartsRef.current.splice(correlation.index, 1);
|
|
33950
|
+
}
|
|
33693
33951
|
if (start) {
|
|
33694
|
-
const
|
|
33695
|
-
if (
|
|
33952
|
+
const pending5 = pendingJsEmitsRef.current.get(start.interactionId);
|
|
33953
|
+
if (pending5 !== void 0) clearTimeout(pending5);
|
|
33696
33954
|
pendingJsEmitsRef.current.delete(start.interactionId);
|
|
33697
33955
|
}
|
|
33698
33956
|
const {
|
|
@@ -33715,11 +33973,62 @@ function ScaleBunDebugRoot({
|
|
|
33715
33973
|
interactionId: event.interactionId || start?.interactionId || generateInteractionId(),
|
|
33716
33974
|
interactionProtocol: event.interactionProtocol || INTERACTION_PROTOCOL_VERSION,
|
|
33717
33975
|
occurredAt: nativeOccurredAt,
|
|
33976
|
+
/**
|
|
33977
|
+
* RN-6 — THESE FIVE FIELDS ARE THE ONLY ONES THE JOIN CARRIES, AND
|
|
33978
|
+
* THEREFORE THE ONLY ONES A BAD JOIN CAN FALSIFY.
|
|
33979
|
+
*
|
|
33980
|
+
* Coordinates below come from native and are right regardless. So a
|
|
33981
|
+
* mispairing does not produce visibly broken data: it produces heat
|
|
33982
|
+
* in the correct position attributed to the wrong control, in the
|
|
33983
|
+
* wrong UI state, on the wrong screen. Every field is individually
|
|
33984
|
+
* well-formed and nothing downstream can tell.
|
|
33985
|
+
*
|
|
33986
|
+
* When the correlation is not 'exact' they are left UNDEFINED rather
|
|
33987
|
+
* than filled from a best guess. An interaction with no target is
|
|
33988
|
+
* honest and visibly incomplete — the accounting lane can count it as
|
|
33989
|
+
* unplaceable and say why. An interaction with the WRONG target is
|
|
33990
|
+
* indistinguishable from a correct one and silently corrupts every
|
|
33991
|
+
* aggregate it enters. `null` is not `0`, and a guess is not a
|
|
33992
|
+
* measurement.
|
|
33993
|
+
*/
|
|
33718
33994
|
ui: start?.ui,
|
|
33719
33995
|
stateStatus: start?.stateStatus ?? "not_captured",
|
|
33720
33996
|
target: start?.target,
|
|
33721
33997
|
targetId: start?.targetId,
|
|
33998
|
+
targetSource: start?.targetSource,
|
|
33722
33999
|
screenName: start?.screenName,
|
|
34000
|
+
screenSource: start?.screenSource ?? "none",
|
|
34001
|
+
targetRect: start?.targetRect,
|
|
34002
|
+
/**
|
|
34003
|
+
* RN-15 — WHERE IN THE CONTROL, computed where both halves exist: the
|
|
34004
|
+
* point comes from the native event and the rect was measured at
|
|
34005
|
+
* finger-down, in the same capture base. Pairing them here rather than
|
|
34006
|
+
* downstream stops anything later reaching for a different rect.
|
|
34007
|
+
*/
|
|
34008
|
+
...(() => {
|
|
34009
|
+
const lp = localPoint(start?.targetRect ?? null, event.normalizedX, event.normalizedY);
|
|
34010
|
+
return lp ? {
|
|
34011
|
+
localU: lp.u,
|
|
34012
|
+
localV: lp.v
|
|
34013
|
+
} : {};
|
|
34014
|
+
})(),
|
|
34015
|
+
scrollX: start?.scrollX,
|
|
34016
|
+
scrollY: start?.scrollY,
|
|
34017
|
+
scrollSource: start?.scrollSource ?? "none",
|
|
34018
|
+
itemKey: start?.itemKey,
|
|
34019
|
+
itemIndex: start?.itemIndex,
|
|
34020
|
+
/**
|
|
34021
|
+
* The join's own verdict, shipped with the row so it is auditable
|
|
34022
|
+
* rather than a claim made in a comment. 'ambiguous' and 'none' are
|
|
34023
|
+
* deliberately distinguished: 'none' means the JS handler never fired
|
|
34024
|
+
* (a real capture gap on this platform), 'ambiguous' means it fired
|
|
34025
|
+
* more than once too close together to attribute. Those have
|
|
34026
|
+
* different fixes, and collapsing them would hide which one is
|
|
34027
|
+
* actually happening in production.
|
|
34028
|
+
*/
|
|
34029
|
+
correlation: correlation.outcome,
|
|
34030
|
+
correlationDeltaMs: correlation.deltaMs,
|
|
34031
|
+
correlationRunnerUpDeltaMs: correlation.runnerUpDeltaMs,
|
|
33723
34032
|
emitAutomaticAnalytics: start?.emitAutomaticAnalytics ?? captureAutomaticInteractions,
|
|
33724
34033
|
x: event.rawX,
|
|
33725
34034
|
y: event.rawY,
|
|
@@ -33791,6 +34100,7 @@ function ScaleBunDebugRoot({
|
|
|
33791
34100
|
const target = resolveTouchTarget(e);
|
|
33792
34101
|
let ui;
|
|
33793
34102
|
let screenName;
|
|
34103
|
+
let screenSource = "none";
|
|
33794
34104
|
try {
|
|
33795
34105
|
const {
|
|
33796
34106
|
uiStateSignature: uiStateSignature2
|
|
@@ -33802,7 +34112,9 @@ function ScaleBunDebugRoot({
|
|
|
33802
34112
|
const {
|
|
33803
34113
|
AutoScreenDetector: AutoScreenDetector2
|
|
33804
34114
|
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
33805
|
-
|
|
34115
|
+
const detector = AutoScreenDetector2.getInstance();
|
|
34116
|
+
screenName = detector.getCurrentScreen() || void 0;
|
|
34117
|
+
screenSource = screenName ? detector.getCurrentScreenSource() : "none";
|
|
33806
34118
|
} catch {
|
|
33807
34119
|
}
|
|
33808
34120
|
const start = {
|
|
@@ -33812,9 +34124,91 @@ function ScaleBunDebugRoot({
|
|
|
33812
34124
|
y: touch.pageY,
|
|
33813
34125
|
locationX: touch.locationX,
|
|
33814
34126
|
locationY: touch.locationY,
|
|
34127
|
+
/**
|
|
34128
|
+
* RN-1 + RN-2 — the identity that is persisted, and it is now code-derived.
|
|
34129
|
+
*
|
|
34130
|
+
* `describeTouchTarget` returns `testID` -> structural path -> component name,
|
|
34131
|
+
* and never a caption or an accessibilityLabel. Before RN-2 it preferred free
|
|
34132
|
+
* text, so this line was writing user data ("Pay EGP 1,442", an order row
|
|
34133
|
+
* labelled with an email address, a contact's name) into the analytics store —
|
|
34134
|
+
* and, because `target` is an aggregation key, splitting one button into one
|
|
34135
|
+
* bucket per person at the same time.
|
|
34136
|
+
*
|
|
34137
|
+
* `targetId` used to be `target?.testID` alone, which is `undefined` in the
|
|
34138
|
+
* overwhelming majority of apps — no testIDs authored — so the field the
|
|
34139
|
+
* backend keys on was empty almost always. It now falls back to the structural
|
|
34140
|
+
* path, with `targetSource` recording WHICH it got.
|
|
34141
|
+
*/
|
|
33815
34142
|
target: describeTouchTarget(target),
|
|
33816
|
-
targetId: target
|
|
34143
|
+
targetId: targetIdentity(target).id,
|
|
34144
|
+
/**
|
|
34145
|
+
* WHICH KIND of identity this is, because they are not equally trustworthy: a
|
|
34146
|
+
* `testID` is a promise from the app's authors that this control is this
|
|
34147
|
+
* control; a `path` is an inference that breaks when the tree is refactored.
|
|
34148
|
+
* Without this the accounting lane cannot distinguish "the control had no
|
|
34149
|
+
* identity" from "the identity is a structural guess", and both would render
|
|
34150
|
+
* as an equally confident target.
|
|
34151
|
+
*/
|
|
34152
|
+
targetSource: targetIdentity(target).source,
|
|
34153
|
+
/* RN-4 — row identity from the app's own keyExtractor; see touchTarget.ts. */
|
|
34154
|
+
itemKey: target?.itemKey,
|
|
34155
|
+
itemIndex: target?.itemIndex,
|
|
34156
|
+
/**
|
|
34157
|
+
* RN-13 — the control's rectangle, so `heat ∈ target` is decidable.
|
|
34158
|
+
*
|
|
34159
|
+
* Read SYNCHRONOUSLY here, in the same tick as the touch, and normalized against
|
|
34160
|
+
* the SAME measured root the tap's own coordinates use. Both halves matter:
|
|
34161
|
+
*
|
|
34162
|
+
* - A callback measurement (`measureInWindow`) would resolve after this handler
|
|
34163
|
+
* returns, so the rect attached to a tap could be the previous tap's, with
|
|
34164
|
+
* nothing afterwards able to tell. `targetGeometry` refuses to guess: it reads
|
|
34165
|
+
* now or reports null.
|
|
34166
|
+
* - Normalizing against a window size instead of the root view is off by the
|
|
34167
|
+
* status bar on most devices, which would read as "the heat is just outside
|
|
34168
|
+
* the button" everywhere rather than as a measurement bug.
|
|
34169
|
+
*
|
|
34170
|
+
* `measureRoot()` is called first because the cached root rect is populated
|
|
34171
|
+
* asynchronously at mount; without it the first taps of a session normalize
|
|
34172
|
+
* against nothing and get a null rect for a reason that has nothing to do with
|
|
34173
|
+
* the control.
|
|
34174
|
+
*/
|
|
34175
|
+
/**
|
|
34176
|
+
* RN-3 — WHERE IN THE CONTENT, not just where on the glass.
|
|
34177
|
+
*
|
|
34178
|
+
* Two taps at the same screen coordinate one viewport apart are two different
|
|
34179
|
+
* controls. Without an offset they merge into one hotspot, and the heatmap shows
|
|
34180
|
+
* heavy engagement at a position where, in content terms, nothing in particular
|
|
34181
|
+
* is. The offset is also a CACHE dimension — two scroll states of one route must
|
|
34182
|
+
* not share a backdrop — so its absence corrupts the picture as well as the count.
|
|
34183
|
+
*
|
|
34184
|
+
* `ancestors` is innermost-first, so this takes the NEAREST scroller that has a
|
|
34185
|
+
* recorded position: a tap inside a horizontal carousel belongs to the carousel's
|
|
34186
|
+
* content, not to how far the page behind it had scrolled.
|
|
34187
|
+
*
|
|
34188
|
+
* Null when no ancestor has one — never zero. Zero is a real scroll position (the
|
|
34189
|
+
* top of a list), and reporting it for "unknown" would place every unmeasured tap
|
|
34190
|
+
* at the top of its content, indistinguishable from a genuine tap there.
|
|
34191
|
+
*/
|
|
34192
|
+
...(() => {
|
|
34193
|
+
const hit = nearestRecordedOffset((target?.ancestors ?? []).map((n) => nativeTagOf(n)));
|
|
34194
|
+
return hit ? {
|
|
34195
|
+
scrollX: hit.x,
|
|
34196
|
+
scrollY: hit.y,
|
|
34197
|
+
scrollSource: "wrapper"
|
|
34198
|
+
} : {
|
|
34199
|
+
scrollSource: "none"
|
|
34200
|
+
};
|
|
34201
|
+
})(),
|
|
34202
|
+
targetRect: (() => {
|
|
34203
|
+
measureRoot();
|
|
34204
|
+
const root = rootRectRef.current;
|
|
34205
|
+
return normalizeRect(readRectSync(target?.node), root ? {
|
|
34206
|
+
w: root.w,
|
|
34207
|
+
h: root.h
|
|
34208
|
+
} : null) ?? void 0;
|
|
34209
|
+
})(),
|
|
33817
34210
|
screenName,
|
|
34211
|
+
screenSource,
|
|
33818
34212
|
ui,
|
|
33819
34213
|
stateStatus: ui ? "captured_nonempty" : "not_instrumented",
|
|
33820
34214
|
emitAutomaticAnalytics: captureAutomaticInteractions
|
|
@@ -33851,7 +34245,12 @@ function ScaleBunDebugRoot({
|
|
|
33851
34245
|
}
|
|
33852
34246
|
measureRoot();
|
|
33853
34247
|
const rootRect = rootRectRef.current;
|
|
33854
|
-
const
|
|
34248
|
+
const freshTarget = start ? null : resolveTouchTarget(e);
|
|
34249
|
+
const tapped = start?.target ?? describeTouchTarget(freshTarget);
|
|
34250
|
+
const identity = start ? {
|
|
34251
|
+
id: start.targetId,
|
|
34252
|
+
source: start.targetSource
|
|
34253
|
+
} : targetIdentity(freshTarget);
|
|
33855
34254
|
const gestureDetails = {
|
|
33856
34255
|
target: tapped,
|
|
33857
34256
|
interactionId: start?.interactionId ?? generateInteractionId(),
|
|
@@ -33859,8 +34258,16 @@ function ScaleBunDebugRoot({
|
|
|
33859
34258
|
occurredAt: start?.occurredAt ?? Date.now(),
|
|
33860
34259
|
ui: start?.ui,
|
|
33861
34260
|
stateStatus: start?.stateStatus ?? "not_captured",
|
|
33862
|
-
targetId:
|
|
34261
|
+
targetId: identity.id,
|
|
34262
|
+
targetSource: identity.source,
|
|
33863
34263
|
screenName: start?.screenName,
|
|
34264
|
+
screenSource: start?.screenSource ?? "none",
|
|
34265
|
+
targetRect: start?.targetRect,
|
|
34266
|
+
scrollX: start?.scrollX,
|
|
34267
|
+
scrollY: start?.scrollY,
|
|
34268
|
+
scrollSource: start?.scrollSource ?? "none",
|
|
34269
|
+
itemKey: start?.itemKey,
|
|
34270
|
+
itemIndex: start?.itemIndex,
|
|
33864
34271
|
emitAutomaticAnalytics: start?.emitAutomaticAnalytics ?? captureAutomaticInteractions,
|
|
33865
34272
|
x: start?.x ?? touch.pageX,
|
|
33866
34273
|
y: start?.y ?? touch.pageY,
|
|
@@ -33876,6 +34283,11 @@ function ScaleBunDebugRoot({
|
|
|
33876
34283
|
const ny = Math.max(0, Math.min(1, (start?.y ?? touch.pageY) / rootRect.h));
|
|
33877
34284
|
gestureDetails.normalizedX = nx;
|
|
33878
34285
|
gestureDetails.normalizedY = ny;
|
|
34286
|
+
const lp = localPoint(start?.targetRect ?? null, nx, ny);
|
|
34287
|
+
if (lp) {
|
|
34288
|
+
gestureDetails.localU = lp.u;
|
|
34289
|
+
gestureDetails.localV = lp.v;
|
|
34290
|
+
}
|
|
33879
34291
|
gestureDetails.normalizedPrecomputed = true;
|
|
33880
34292
|
gestureDetails.viewportWidth = rootRect.w;
|
|
33881
34293
|
gestureDetails.viewportHeight = rootRect.h;
|
|
@@ -34004,6 +34416,13 @@ function useScaleBunScreen(name) {
|
|
|
34004
34416
|
}
|
|
34005
34417
|
} catch {
|
|
34006
34418
|
}
|
|
34419
|
+
try {
|
|
34420
|
+
const {
|
|
34421
|
+
AutoScreenDetector: AutoScreenDetector2
|
|
34422
|
+
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
34423
|
+
AutoScreenDetector2.getInstance().setManualScreen(name);
|
|
34424
|
+
} catch {
|
|
34425
|
+
}
|
|
34007
34426
|
try {
|
|
34008
34427
|
const bs = getBootstrap();
|
|
34009
34428
|
const perfFeature = bs?.getPerformanceFeature?.();
|
|
@@ -34154,6 +34573,18 @@ var ScaleBunScrollView = /* @__PURE__ */ import_react12.default.forwardRef(funct
|
|
|
34154
34573
|
}
|
|
34155
34574
|
} catch {
|
|
34156
34575
|
}
|
|
34576
|
+
try {
|
|
34577
|
+
const {
|
|
34578
|
+
recordScrollOffset: recordScrollOffset2,
|
|
34579
|
+
nativeTagOf: nativeTagOf2
|
|
34580
|
+
} = (init_scrollContext(), __toCommonJS(scrollContext_exports));
|
|
34581
|
+
const {
|
|
34582
|
+
contentOffset,
|
|
34583
|
+
target
|
|
34584
|
+
} = event.nativeEvent;
|
|
34585
|
+
recordScrollOffset2(typeof target === "number" ? target : nativeTagOf2(target), contentOffset.x, contentOffset.y);
|
|
34586
|
+
} catch {
|
|
34587
|
+
}
|
|
34157
34588
|
if (userOnScroll) {
|
|
34158
34589
|
userOnScroll(event);
|
|
34159
34590
|
}
|