@scalebun/react-native 2.0.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/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/ScaleBunOtaModule.kt +18 -3
- package/android/src/main/java/com/scalebun/rn/ota/SlotManager.kt +62 -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 +2 -0
- package/dist/scalebun.full.js +840 -114
- package/dist/scalebun.slim.js +838 -112
- package/ios/ReplaySdk.swift +65 -2
- package/lib/commonjs/bootstrap/SDKBootstrapper.js +30 -0
- package/lib/commonjs/core/config/schema.js +16 -0
- 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/ota/OtaOrchestrator.js +69 -22
- package/lib/commonjs/features/session/SessionManager.js +63 -0
- package/lib/commonjs/public/ScaleBunFacade.js +18 -16
- package/lib/module/bootstrap/SDKBootstrapper.js +30 -0
- package/lib/module/core/config/schema.js +16 -0
- 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/ota/OtaOrchestrator.js +69 -22
- package/lib/module/features/session/SessionManager.js +63 -0
- package/lib/module/public/ScaleBunFacade.js +18 -16
- package/lib/typescript/core/config/schema.d.ts +2 -0
- 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/ota/OtaOrchestrator.d.ts +7 -0
- package/lib/typescript/features/session/SessionManager.d.ts +55 -0
- package/lib/typescript/public/types.d.ts +10 -0
- package/lib/typescript/specs/NativeScaleBunOta.d.ts +2 -0
- package/package.json +2 -2
- package/src/bootstrap/SDKBootstrapper.ts +40 -0
- package/src/core/config/schema.ts +18 -0
- 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/ota/OtaOrchestrator.ts +76 -22
- package/src/features/session/SessionManager.ts +113 -0
- package/src/public/ScaleBunFacade.ts +18 -16
- package/src/public/types.ts +10 -0
- package/src/specs/NativeScaleBunOta.ts +3 -0
package/dist/scalebun.full.js
CHANGED
|
@@ -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 = "2.0.
|
|
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({
|
|
@@ -11924,10 +12261,10 @@ async function deliverOtaEvents(params) {
|
|
|
11924
12261
|
})
|
|
11925
12262
|
});
|
|
11926
12263
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
11927
|
-
|
|
12264
|
+
logger.debug(`[OTA] Delivered ${items.length} telemetry event(s)`);
|
|
11928
12265
|
} catch (err) {
|
|
11929
12266
|
for (const e of events) otaEventEmitter.emit(e);
|
|
11930
|
-
|
|
12267
|
+
logger.debug(`[OTA] Telemetry delivery failed (will retry next sync): ${err?.message}`);
|
|
11931
12268
|
}
|
|
11932
12269
|
}
|
|
11933
12270
|
var import_react_native34, PROGRESS_POST_INTERVAL_MS, DEFAULT_HEALTHY_AFTER_MS, OtaOrchestrator, otaOrchestrator;
|
|
@@ -11960,6 +12297,8 @@ var init_OtaOrchestrator = __esm({
|
|
|
11960
12297
|
return this.currentBundle;
|
|
11961
12298
|
}
|
|
11962
12299
|
isRestartRequiredState = false;
|
|
12300
|
+
/** Root/jailbreak indicators observed at init; empty when none found. */
|
|
12301
|
+
integrityIndicators = [];
|
|
11963
12302
|
healthyTimer = null;
|
|
11964
12303
|
bootGuardConfig = {};
|
|
11965
12304
|
environment = null;
|
|
@@ -11994,17 +12333,27 @@ var init_OtaOrchestrator = __esm({
|
|
|
11994
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.");
|
|
11995
12334
|
}
|
|
11996
12335
|
if (this.environment.requiresReactHostIntegration) {
|
|
11997
|
-
|
|
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.");
|
|
11998
12337
|
}
|
|
11999
12338
|
this.initialized = true;
|
|
12000
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
|
+
}
|
|
12001
12349
|
this.bootGuardConfig = config ?? {};
|
|
12002
|
-
|
|
12350
|
+
logger.debug(`[OTA] Orchestrator initialized (RN ${this.environment.rnVersionString ?? "unknown"}${this.environment.bridgeless ? ", bridgeless" : ""}${this.environment.hermes ? `, Hermes HBC v${this.environment.hermesBytecodeVersion ?? "?"}` : ""})`);
|
|
12003
12351
|
const slotState = this.readSlotState();
|
|
12004
12352
|
this.hydrateCurrentBundleFromSlots(slotState);
|
|
12005
12353
|
void prefetchDeviceCountry();
|
|
12006
12354
|
this.checkBootGuardRecovery(slotState);
|
|
12007
12355
|
this.verifyRunningBundleIdentity();
|
|
12356
|
+
this.startBootGuardHeartbeat(slotState);
|
|
12008
12357
|
});
|
|
12009
12358
|
}
|
|
12010
12359
|
/**
|
|
@@ -12035,13 +12384,13 @@ var init_OtaOrchestrator = __esm({
|
|
|
12035
12384
|
releaseId: record4.releaseId ?? void 0,
|
|
12036
12385
|
sha256: record4.sha256
|
|
12037
12386
|
};
|
|
12038
|
-
|
|
12387
|
+
logger.debug(`[OTA] Running bundle v${record4.version} (${record4.bundleId})`);
|
|
12039
12388
|
return;
|
|
12040
12389
|
}
|
|
12041
12390
|
this.currentBundle = {
|
|
12042
12391
|
sha256: current.sha256
|
|
12043
12392
|
};
|
|
12044
|
-
|
|
12393
|
+
logger.debug(`[OTA] Running an OTA bundle (sha ${String(current.sha256).slice(0, 12)}\u2026) with no local install record.`);
|
|
12045
12394
|
} catch {
|
|
12046
12395
|
}
|
|
12047
12396
|
}
|
|
@@ -12071,7 +12420,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12071
12420
|
}
|
|
12072
12421
|
if (!record4.identityToken) return;
|
|
12073
12422
|
if (running === record4.identityToken) {
|
|
12074
|
-
|
|
12423
|
+
logger.debug("[OTA] Install verified \u2014 running bundle matches what was installed.");
|
|
12075
12424
|
this.retireIdentityToken(record4);
|
|
12076
12425
|
return;
|
|
12077
12426
|
}
|
|
@@ -12174,6 +12523,13 @@ var init_OtaOrchestrator = __esm({
|
|
|
12174
12523
|
isEnabled() {
|
|
12175
12524
|
return this.enabled;
|
|
12176
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
|
+
}
|
|
12177
12533
|
isRestartRequired() {
|
|
12178
12534
|
return this.isRestartRequiredState;
|
|
12179
12535
|
}
|
|
@@ -12267,7 +12623,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12267
12623
|
error: "OTA native module disabled or missing"
|
|
12268
12624
|
};
|
|
12269
12625
|
}
|
|
12270
|
-
|
|
12626
|
+
logger.debug("[OTA] Sync started\u2026");
|
|
12271
12627
|
if (this.currentBundle?.id) {
|
|
12272
12628
|
otaEventEmitter.emitSimple("CHECK", this.currentBundle.id, {
|
|
12273
12629
|
releaseId: this.currentBundle.releaseId,
|
|
@@ -12276,7 +12632,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12276
12632
|
}
|
|
12277
12633
|
const checkRes = await this.checkForUpdate(params);
|
|
12278
12634
|
if (checkRes.action === "NONE") {
|
|
12279
|
-
|
|
12635
|
+
logger.debug("[OTA] App is up to date");
|
|
12280
12636
|
this.startBootGuardHeartbeat();
|
|
12281
12637
|
return {
|
|
12282
12638
|
status: "UP_TO_DATE"
|
|
@@ -12319,7 +12675,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12319
12675
|
version: bundle.version
|
|
12320
12676
|
});
|
|
12321
12677
|
const downloadStart = Date.now();
|
|
12322
|
-
|
|
12678
|
+
logger.debug(`[OTA] Downloading update v${bundle.version}\u2026`);
|
|
12323
12679
|
let lastPostAt = 0;
|
|
12324
12680
|
const postProgress = (bytesRead, status) => {
|
|
12325
12681
|
const total = bundle.size > 0 ? bundle.size : Math.max(bytesRead, 1);
|
|
@@ -12397,7 +12753,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12397
12753
|
staged = true;
|
|
12398
12754
|
} else {
|
|
12399
12755
|
if (bundle.patch && NativeScaleBunOta_default.stagePatch) {
|
|
12400
|
-
|
|
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`);
|
|
12401
12757
|
try {
|
|
12402
12758
|
staged = await NativeScaleBunOta_default.stagePatch(
|
|
12403
12759
|
bundle.patch.url,
|
|
@@ -12407,7 +12763,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12407
12763
|
);
|
|
12408
12764
|
if (staged) {
|
|
12409
12765
|
patchUsed = true;
|
|
12410
|
-
|
|
12766
|
+
logger.debug("[OTA] Patch applied successfully");
|
|
12411
12767
|
} else {
|
|
12412
12768
|
logger.warn("[OTA] Patch staging returned false \u2014 falling back to full bundle");
|
|
12413
12769
|
}
|
|
@@ -12476,7 +12832,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12476
12832
|
releaseId: bundle.releaseId,
|
|
12477
12833
|
version: bundle.version
|
|
12478
12834
|
});
|
|
12479
|
-
|
|
12835
|
+
logger.debug(`[OTA] Update v${bundle.version} installed successfully!`);
|
|
12480
12836
|
await deliverOtaEvents(params);
|
|
12481
12837
|
const installMode = bundle.installMode ?? "ON_NEXT_RESTART";
|
|
12482
12838
|
if (params.autoRestart || installMode === "IMMEDIATE") {
|
|
@@ -12507,24 +12863,23 @@ var init_OtaOrchestrator = __esm({
|
|
|
12507
12863
|
* native boot-guard marker. If JS crashes before the timer fires, the
|
|
12508
12864
|
* marker stays set and native code reverts on next cold start.
|
|
12509
12865
|
*/
|
|
12510
|
-
startBootGuardHeartbeat() {
|
|
12866
|
+
startBootGuardHeartbeat(preRead) {
|
|
12511
12867
|
if (!NativeScaleBunOta_default) return;
|
|
12512
12868
|
if (this.healthyTimer) {
|
|
12513
12869
|
clearTimeout(this.healthyTimer);
|
|
12514
12870
|
this.healthyTimer = null;
|
|
12515
12871
|
}
|
|
12516
12872
|
try {
|
|
12517
|
-
const
|
|
12518
|
-
|
|
12519
|
-
if (!state.bootMarkerPresent) {
|
|
12873
|
+
const state = preRead !== void 0 ? preRead : JSON.parse(NativeScaleBunOta_default.getSlotState());
|
|
12874
|
+
if (!state?.bootMarkerPresent) {
|
|
12520
12875
|
return;
|
|
12521
12876
|
}
|
|
12522
12877
|
const healthyMs = this.bootGuardConfig.healthyAfterMs ?? DEFAULT_HEALTHY_AFTER_MS;
|
|
12523
|
-
|
|
12878
|
+
logger.debug(`[OTA] Boot guard active \u2014 marking healthy after ${healthyMs}ms`);
|
|
12524
12879
|
this.healthyTimer = setTimeout(() => {
|
|
12525
12880
|
noThrow(() => {
|
|
12526
12881
|
NativeScaleBunOta_default?.markHealthy();
|
|
12527
|
-
|
|
12882
|
+
logger.info("[OTA] Boot guard cleared \u2014 bundle marked healthy \u2713");
|
|
12528
12883
|
this.healthyTimer = null;
|
|
12529
12884
|
const running = this.currentBundle;
|
|
12530
12885
|
if (running?.id) {
|
|
@@ -12570,11 +12925,11 @@ var init_OtaOrchestrator = __esm({
|
|
|
12570
12925
|
} catch {
|
|
12571
12926
|
}
|
|
12572
12927
|
this.resumeRestartArmed = false;
|
|
12573
|
-
|
|
12928
|
+
logger.info("[OTA] App resumed \u2014 activating the staged update now");
|
|
12574
12929
|
this.restart();
|
|
12575
12930
|
}
|
|
12576
12931
|
});
|
|
12577
|
-
|
|
12932
|
+
logger.debug("[OTA] Update staged \u2014 will activate on next resume (ON_NEXT_RESUME)");
|
|
12578
12933
|
} catch {
|
|
12579
12934
|
this.resumeRestartArmed = false;
|
|
12580
12935
|
}
|
|
@@ -12591,7 +12946,7 @@ var init_OtaOrchestrator = __esm({
|
|
|
12591
12946
|
*/
|
|
12592
12947
|
restart() {
|
|
12593
12948
|
noThrow(() => {
|
|
12594
|
-
|
|
12949
|
+
logger.info("[OTA] Triggering app restart\u2026");
|
|
12595
12950
|
if (NativeScaleBunOta_default && typeof NativeScaleBunOta_default.restartApp === "function") {
|
|
12596
12951
|
NativeScaleBunOta_default.restartApp();
|
|
12597
12952
|
} else if (import_react_native34.DevSettings && typeof import_react_native34.DevSettings.reload === "function") {
|
|
@@ -12610,17 +12965,25 @@ var init_OtaOrchestrator = __esm({
|
|
|
12610
12965
|
var uiState_exports = {};
|
|
12611
12966
|
__export(uiState_exports, {
|
|
12612
12967
|
clearUiState: () => clearUiState,
|
|
12968
|
+
onUiStateChange: () => onUiStateChange,
|
|
12969
|
+
resetUiStateListeners: () => resetUiStateListeners,
|
|
12613
12970
|
setUiState: () => setUiState,
|
|
12971
|
+
uiStateListenerCount: () => uiStateListenerCount,
|
|
12614
12972
|
uiStateSignature: () => uiStateSignature
|
|
12615
12973
|
});
|
|
12616
12974
|
function setUiState(name, value) {
|
|
12617
12975
|
const n = clean(name);
|
|
12618
12976
|
const v = clean(value);
|
|
12619
|
-
if (n
|
|
12977
|
+
if (!n || !v) return;
|
|
12978
|
+
if (declared.get(n) === v) return;
|
|
12979
|
+
declared.set(n, v);
|
|
12980
|
+
notifyChanged();
|
|
12620
12981
|
}
|
|
12621
12982
|
function clearUiState(name) {
|
|
12983
|
+
const before = declared.size;
|
|
12622
12984
|
if (name === void 0) declared.clear();
|
|
12623
12985
|
else declared.delete(clean(name));
|
|
12986
|
+
if (declared.size !== before) notifyChanged();
|
|
12624
12987
|
}
|
|
12625
12988
|
function uiStateSignature() {
|
|
12626
12989
|
if (!declared.size) return void 0;
|
|
@@ -12633,12 +12996,42 @@ function uiStateSignature() {
|
|
|
12633
12996
|
}
|
|
12634
12997
|
return signature || void 0;
|
|
12635
12998
|
}
|
|
12636
|
-
|
|
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;
|
|
12637
13028
|
var init_uiState = __esm({
|
|
12638
13029
|
"lib/module/features/journey/uiState.js"() {
|
|
12639
13030
|
"use strict";
|
|
12640
13031
|
declared = /* @__PURE__ */ new Map();
|
|
12641
13032
|
clean = (s) => typeof s === "string" ? s.replace(/[;:|]/g, "").trim().slice(0, 32) : "";
|
|
13033
|
+
listeners2 = /* @__PURE__ */ new Set();
|
|
13034
|
+
pending4 = null;
|
|
12642
13035
|
}
|
|
12643
13036
|
});
|
|
12644
13037
|
|
|
@@ -14539,6 +14932,22 @@ var SHAPE = {
|
|
|
14539
14932
|
}
|
|
14540
14933
|
},
|
|
14541
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),
|
|
14542
14951
|
// Opt-in: intercept console.* and ship the lines to the Diagnose → Logs lane in SaaS
|
|
14543
14952
|
// mode. OFF by default — console output can carry PII/secrets and adds ingest volume,
|
|
14544
14953
|
// so capturing it is the host's explicit choice, not a silent default. When true and a
|
|
@@ -16753,22 +17162,22 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
|
|
|
16753
17162
|
* lose the snapshot entirely. `imageData.length` is the base64 length, which is within ~1% of the
|
|
16754
17163
|
* serialized cost and needs no extra pass.
|
|
16755
17164
|
*/
|
|
16756
|
-
static _selectFramesToPersist(
|
|
17165
|
+
static _selectFramesToPersist(pending5) {
|
|
16757
17166
|
const isKeyframe = (b) => b.frame.frameType !== "delta";
|
|
16758
17167
|
const size = (b) => b.imageData?.length ?? 0;
|
|
16759
17168
|
const keep = [];
|
|
16760
17169
|
let budget = _BackendSessionAdapter.MAX_PERSIST_BYTES;
|
|
16761
17170
|
const maxCount = _BackendSessionAdapter.MAX_FRAMES_PER_BATCH;
|
|
16762
|
-
for (let i =
|
|
16763
|
-
const b =
|
|
17171
|
+
for (let i = pending5.length - 1; i >= 0; i--) {
|
|
17172
|
+
const b = pending5[i];
|
|
16764
17173
|
if (!isKeyframe(b)) continue;
|
|
16765
17174
|
const cost = size(b);
|
|
16766
17175
|
if (cost > budget || keep.length >= maxCount) continue;
|
|
16767
17176
|
budget -= cost;
|
|
16768
17177
|
keep.push(b);
|
|
16769
17178
|
}
|
|
16770
|
-
for (let i =
|
|
16771
|
-
const b =
|
|
17179
|
+
for (let i = pending5.length - 1; i >= 0; i--) {
|
|
17180
|
+
const b = pending5[i];
|
|
16772
17181
|
if (isKeyframe(b)) continue;
|
|
16773
17182
|
const cost = size(b);
|
|
16774
17183
|
if (cost > budget || keep.length >= maxCount) continue;
|
|
@@ -17201,6 +17610,10 @@ var SDKBootstrapper = class {
|
|
|
17201
17610
|
return null;
|
|
17202
17611
|
}
|
|
17203
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
|
+
}
|
|
17204
17617
|
this.config.apiBaseUrl = DEFAULT_API_BASE_URL;
|
|
17205
17618
|
if (this.config.logLevel) {
|
|
17206
17619
|
logger.setLevelByName(this.config.logLevel);
|
|
@@ -17365,6 +17778,20 @@ var SDKBootstrapper = class {
|
|
|
17365
17778
|
}));
|
|
17366
17779
|
}
|
|
17367
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
|
+
}
|
|
17368
17795
|
if (features.replay !== false) {
|
|
17369
17796
|
try {
|
|
17370
17797
|
__DEV__ && logger.debug("[Bootstrap] Initializing unified SessionManager");
|
|
@@ -20008,14 +20435,14 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20008
20435
|
this._apiBaseUrl = result.config.apiBaseUrl;
|
|
20009
20436
|
this._maybeStartEventTracker(rawConfig);
|
|
20010
20437
|
if (this._pendingIdentity) {
|
|
20011
|
-
const
|
|
20438
|
+
const pending5 = this._pendingIdentity;
|
|
20012
20439
|
this._pendingIdentity = null;
|
|
20013
|
-
this.identify(
|
|
20440
|
+
this.identify(pending5.userId, pending5.traits);
|
|
20014
20441
|
}
|
|
20015
20442
|
const realQueue = coreContainer.get("PersistentQueue");
|
|
20016
20443
|
const buffered = this._preInitBuffer.popBatch(this._preInitBuffer.length());
|
|
20017
20444
|
if (buffered.length > 0) {
|
|
20018
|
-
|
|
20445
|
+
logger.debug(`[ScaleBun] Draining ${buffered.length} pre-init events into persistent queue`);
|
|
20019
20446
|
for (const envelope of buffered) {
|
|
20020
20447
|
realQueue.add(envelope);
|
|
20021
20448
|
}
|
|
@@ -20106,7 +20533,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20106
20533
|
}
|
|
20107
20534
|
const clientKey = this._clientKey;
|
|
20108
20535
|
const apiUrl = this._apiBaseUrl;
|
|
20109
|
-
if (!clientKey || !apiUrl)
|
|
20536
|
+
if (!clientKey || !apiUrl) {
|
|
20537
|
+
return;
|
|
20538
|
+
}
|
|
20110
20539
|
this._otaCheckInFlight = true;
|
|
20111
20540
|
try {
|
|
20112
20541
|
const info = await bridgeAdapter.getDeviceInfo();
|
|
@@ -20157,7 +20586,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20157
20586
|
if (!dbgConfig) return;
|
|
20158
20587
|
dbgConfig.__skipDeprecationWarning = true;
|
|
20159
20588
|
this.enableDebug(dbgConfig);
|
|
20160
|
-
|
|
20589
|
+
logger.info("[ScaleBun] Debug auto-connected via init({ debug }).");
|
|
20161
20590
|
} catch (err) {
|
|
20162
20591
|
logger.warn("[ScaleBun] Auto debug connection failed:", err?.message);
|
|
20163
20592
|
}
|
|
@@ -20171,7 +20600,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20171
20600
|
const apiBaseUrl = this._apiBaseUrl;
|
|
20172
20601
|
if (!enabled || !appId || !clientKey || !apiBaseUrl) {
|
|
20173
20602
|
if (enabled && !appId) {
|
|
20174
|
-
|
|
20603
|
+
logger.info("[ScaleBun] Envelope event tracking disabled \u2014 no appId in init config.");
|
|
20175
20604
|
}
|
|
20176
20605
|
return;
|
|
20177
20606
|
}
|
|
@@ -20221,7 +20650,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20221
20650
|
onEvent: (eventName, revenue) => this._skanManager?.onEvent(eventName, revenue)
|
|
20222
20651
|
});
|
|
20223
20652
|
this._eventTracker.start();
|
|
20224
|
-
|
|
20653
|
+
logger.info("[ScaleBun] Envelope event tracking started \u2192 /v1/batch.");
|
|
20225
20654
|
this._applyNativeDeviceContext(this._eventTracker);
|
|
20226
20655
|
this._captureInstallReferrer(this._eventTracker);
|
|
20227
20656
|
const tracker = this._eventTracker;
|
|
@@ -20855,7 +21284,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
20855
21284
|
const cfg = await (this._getEngageTransport()?.fetchEngageConfig(context) ?? Promise.resolve(null));
|
|
20856
21285
|
const messages = cfg?.inAppMessages ?? [];
|
|
20857
21286
|
const previews = messages.filter((m) => m.preview === true || m.isTest === true);
|
|
20858
|
-
|
|
21287
|
+
logger.info(`[Engage] debugFetchInAppMessages \u2192 ${messages.length} message(s), ${previews.length} test preview(s)`);
|
|
20859
21288
|
return messages;
|
|
20860
21289
|
},
|
|
20861
21290
|
/** Upload a survey/NPS/feedback response (idempotent, rides the outbox). */
|
|
@@ -21131,7 +21560,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21131
21560
|
logger.warn("SDK not initialized. Event dropped:", name);
|
|
21132
21561
|
return;
|
|
21133
21562
|
}
|
|
21134
|
-
|
|
21563
|
+
logger.debug("Tracking event:", name);
|
|
21135
21564
|
engageSignals.emit(name, payload);
|
|
21136
21565
|
const adapter = SessionManager.getExistingInstance()?.getBackendTransport();
|
|
21137
21566
|
if (!isUploadedSessionEvent(name)) {
|
|
@@ -21260,7 +21689,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21260
21689
|
await Promise.all([this._eventTracker?.flush() ?? Promise.resolve(), adapter?.flushAnalytics() ?? Promise.resolve()]);
|
|
21261
21690
|
return;
|
|
21262
21691
|
}
|
|
21263
|
-
|
|
21692
|
+
logger.debug("Flushing events from persistent queue...");
|
|
21264
21693
|
const httpClient = coreContainer.get("HttpClient");
|
|
21265
21694
|
await flushQueue(this._getQueue(), httpClient, getDeviceId());
|
|
21266
21695
|
});
|
|
@@ -21282,9 +21711,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21282
21711
|
const bootstrapPerfFeature = getPerformanceFeature();
|
|
21283
21712
|
if (bootstrapPerfFeature) {
|
|
21284
21713
|
this._performanceFeature = bootstrapPerfFeature;
|
|
21285
|
-
|
|
21714
|
+
logger.debug("Performance feature linked from bootstrap");
|
|
21286
21715
|
}
|
|
21287
|
-
|
|
21716
|
+
logger.info("Debug enabled");
|
|
21288
21717
|
});
|
|
21289
21718
|
}
|
|
21290
21719
|
/** Disable the debug subsystem and clean up */
|
|
@@ -21427,14 +21856,14 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21427
21856
|
return false;
|
|
21428
21857
|
}
|
|
21429
21858
|
sm.startSession(metadata);
|
|
21430
|
-
|
|
21859
|
+
logger.info("session.start() \u2705 sessionId:", sm.currentSessionId);
|
|
21431
21860
|
return true;
|
|
21432
21861
|
},
|
|
21433
21862
|
end: (reason) => {
|
|
21434
21863
|
const sm = SessionManager.getExistingInstance();
|
|
21435
21864
|
if (!sm || !sm.isActive) return false;
|
|
21436
21865
|
sm.endSession(reason);
|
|
21437
|
-
|
|
21866
|
+
logger.info("session.end() \u2705");
|
|
21438
21867
|
return true;
|
|
21439
21868
|
},
|
|
21440
21869
|
emitEvent: (type, data) => {
|
|
@@ -21487,7 +21916,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21487
21916
|
...this._privacyConfig,
|
|
21488
21917
|
...config
|
|
21489
21918
|
};
|
|
21490
|
-
|
|
21919
|
+
logger.debug("[report] Privacy config updated");
|
|
21491
21920
|
},
|
|
21492
21921
|
/** Set branding for exported reports. */
|
|
21493
21922
|
setBranding: (config) => {
|
|
@@ -21495,7 +21924,7 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21495
21924
|
...this._brandingConfig,
|
|
21496
21925
|
...config
|
|
21497
21926
|
};
|
|
21498
|
-
|
|
21927
|
+
logger.debug("[report] Branding config updated");
|
|
21499
21928
|
},
|
|
21500
21929
|
/** Get current privacy config. */
|
|
21501
21930
|
getPrivacy: () => ({
|
|
@@ -21542,9 +21971,9 @@ var ScaleBunFacade = class _ScaleBunFacade {
|
|
|
21542
21971
|
AutoScreenDetector: AutoScreenDetector2
|
|
21543
21972
|
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
21544
21973
|
AutoScreenDetector2.getInstance().setNavigationRef(ref);
|
|
21545
|
-
|
|
21974
|
+
logger.info("[ScaleBun] Navigation ref set \u2192 auto screen detection active");
|
|
21546
21975
|
} catch (err) {
|
|
21547
|
-
|
|
21976
|
+
logger.debug("[ScaleBun] Navigation ref stashed (detector not ready):", err?.message);
|
|
21548
21977
|
}
|
|
21549
21978
|
});
|
|
21550
21979
|
}
|
|
@@ -21851,8 +22280,8 @@ function installEngageTriggerEngine(deps) {
|
|
|
21851
22280
|
if (!cachedConfig) return;
|
|
21852
22281
|
configReady = true;
|
|
21853
22282
|
const now2 = Date.now();
|
|
21854
|
-
const
|
|
21855
|
-
for (const e of
|
|
22283
|
+
const pending5 = recent.splice(0);
|
|
22284
|
+
for (const e of pending5) {
|
|
21856
22285
|
const age = now2 - e.at;
|
|
21857
22286
|
if (age > RECENT_TTL_MS) continue;
|
|
21858
22287
|
const remaining = delayMsForTrigger(cachedConfig, e.options.trigger) - age;
|
|
@@ -23117,11 +23546,11 @@ var keyOf = (value) => value.trim();
|
|
|
23117
23546
|
var validRect = (rect) => [rect.x, rect.y, rect.width, rect.height].every(Number.isFinite) && rect.width > 0 && rect.height > 0;
|
|
23118
23547
|
function createEngageAnchorRegistry() {
|
|
23119
23548
|
const entries = /* @__PURE__ */ new Map();
|
|
23120
|
-
const
|
|
23549
|
+
const listeners3 = /* @__PURE__ */ new Map();
|
|
23121
23550
|
const notify = (rawKey) => {
|
|
23122
23551
|
const key = keyOf(rawKey);
|
|
23123
23552
|
if (!key) return;
|
|
23124
|
-
|
|
23553
|
+
listeners3.get(key)?.forEach((listener) => listener());
|
|
23125
23554
|
};
|
|
23126
23555
|
return {
|
|
23127
23556
|
register(rawKey, node) {
|
|
@@ -23163,12 +23592,12 @@ function createEngageAnchorRegistry() {
|
|
|
23163
23592
|
const key = keyOf(rawKey);
|
|
23164
23593
|
if (!key) return () => {
|
|
23165
23594
|
};
|
|
23166
|
-
const set =
|
|
23595
|
+
const set = listeners3.get(key) ?? /* @__PURE__ */ new Set();
|
|
23167
23596
|
set.add(listener);
|
|
23168
|
-
|
|
23597
|
+
listeners3.set(key, set);
|
|
23169
23598
|
return () => {
|
|
23170
23599
|
set.delete(listener);
|
|
23171
|
-
if (set.size === 0)
|
|
23600
|
+
if (set.size === 0) listeners3.delete(key);
|
|
23172
23601
|
};
|
|
23173
23602
|
},
|
|
23174
23603
|
notify
|
|
@@ -32277,10 +32706,10 @@ var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
|
32277
32706
|
var keyOf2 = (value) => value.trim();
|
|
32278
32707
|
function createEngageInlinePlacementRegistry() {
|
|
32279
32708
|
const slots = /* @__PURE__ */ new Map();
|
|
32280
|
-
const
|
|
32281
|
-
const
|
|
32709
|
+
const pending5 = /* @__PURE__ */ new Map();
|
|
32710
|
+
const listeners3 = /* @__PURE__ */ new Map();
|
|
32282
32711
|
const notify = (key) => {
|
|
32283
|
-
|
|
32712
|
+
listeners3.get(key)?.forEach((listener) => listener());
|
|
32284
32713
|
};
|
|
32285
32714
|
return {
|
|
32286
32715
|
register(rawKey, sink) {
|
|
@@ -32297,7 +32726,7 @@ function createEngageInlinePlacementRegistry() {
|
|
|
32297
32726
|
sink,
|
|
32298
32727
|
token
|
|
32299
32728
|
});
|
|
32300
|
-
sink(
|
|
32729
|
+
sink(pending5.get(key) ?? null);
|
|
32301
32730
|
if (!previous) notify(key);
|
|
32302
32731
|
return () => {
|
|
32303
32732
|
if (slots.get(key)?.token !== token) return;
|
|
@@ -32308,7 +32737,7 @@ function createEngageInlinePlacementRegistry() {
|
|
|
32308
32737
|
deliver(rawKey, delivery) {
|
|
32309
32738
|
const key = keyOf2(rawKey);
|
|
32310
32739
|
if (!key) return false;
|
|
32311
|
-
|
|
32740
|
+
pending5.set(key, delivery);
|
|
32312
32741
|
const slot = slots.get(key);
|
|
32313
32742
|
slot?.sink(delivery);
|
|
32314
32743
|
return slot !== void 0;
|
|
@@ -32316,9 +32745,9 @@ function createEngageInlinePlacementRegistry() {
|
|
|
32316
32745
|
clear(rawKey, messageId) {
|
|
32317
32746
|
const key = keyOf2(rawKey);
|
|
32318
32747
|
if (!key) return;
|
|
32319
|
-
const current =
|
|
32748
|
+
const current = pending5.get(key);
|
|
32320
32749
|
if (!current || messageId !== void 0 && current.message?.id !== messageId) return;
|
|
32321
|
-
|
|
32750
|
+
pending5.delete(key);
|
|
32322
32751
|
slots.get(key)?.sink(null);
|
|
32323
32752
|
},
|
|
32324
32753
|
has(rawKey) {
|
|
@@ -32329,12 +32758,12 @@ function createEngageInlinePlacementRegistry() {
|
|
|
32329
32758
|
const key = keyOf2(rawKey);
|
|
32330
32759
|
if (!key) return () => {
|
|
32331
32760
|
};
|
|
32332
|
-
const set =
|
|
32761
|
+
const set = listeners3.get(key) ?? /* @__PURE__ */ new Set();
|
|
32333
32762
|
set.add(listener);
|
|
32334
|
-
|
|
32763
|
+
listeners3.set(key, set);
|
|
32335
32764
|
return () => {
|
|
32336
32765
|
set.delete(listener);
|
|
32337
|
-
if (set.size === 0)
|
|
32766
|
+
if (set.size === 0) listeners3.delete(key);
|
|
32338
32767
|
};
|
|
32339
32768
|
}
|
|
32340
32769
|
};
|
|
@@ -33123,6 +33552,7 @@ var import_react_native43 = require("react-native");
|
|
|
33123
33552
|
// lib/module/features/journey/touchTarget.js
|
|
33124
33553
|
var MAX_DEPTH = 12;
|
|
33125
33554
|
var MAX_LEN = 60;
|
|
33555
|
+
var MAX_PATH_SEGMENTS = 4;
|
|
33126
33556
|
function str2(v) {
|
|
33127
33557
|
if (typeof v !== "string") return void 0;
|
|
33128
33558
|
const s = v.trim();
|
|
@@ -33154,41 +33584,136 @@ function fiberOf(event) {
|
|
|
33154
33584
|
const e = event;
|
|
33155
33585
|
const direct = e._targetInst;
|
|
33156
33586
|
if (direct && typeof direct === "object") return direct;
|
|
33157
|
-
const target = e.target;
|
|
33158
|
-
if (target
|
|
33159
|
-
const handle = target.
|
|
33587
|
+
const target = e.target ?? e.nativeEvent?.target;
|
|
33588
|
+
if (target) {
|
|
33589
|
+
const handle = target._internalInstanceHandle ?? target._targetInst;
|
|
33160
33590
|
if (handle && typeof handle === "object") return handle;
|
|
33161
33591
|
}
|
|
33162
33592
|
return null;
|
|
33163
33593
|
}
|
|
33164
|
-
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;
|
|
33165
33599
|
let f = fiberOf(event);
|
|
33166
33600
|
if (!f) return null;
|
|
33167
33601
|
const out = {};
|
|
33602
|
+
const segments = [];
|
|
33603
|
+
const ancestors = [];
|
|
33168
33604
|
let depth = 0;
|
|
33169
33605
|
try {
|
|
33170
33606
|
while (f && depth < maxDepth) {
|
|
33171
33607
|
const props = f.memoizedProps;
|
|
33172
33608
|
if (props) {
|
|
33173
|
-
|
|
33609
|
+
const beforeTestId = out.testID;
|
|
33174
33610
|
out.testID ??= str2(props.testID);
|
|
33175
|
-
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
|
+
}
|
|
33622
|
+
}
|
|
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
|
+
}
|
|
33176
33630
|
}
|
|
33177
|
-
out.
|
|
33178
|
-
if (out.accessibilityLabel && out.testID) break;
|
|
33631
|
+
if (out.testID && segments.length >= MAX_PATH_SEGMENTS) break;
|
|
33179
33632
|
f = f.return ?? null;
|
|
33180
33633
|
depth++;
|
|
33181
33634
|
}
|
|
33182
33635
|
} catch {
|
|
33183
33636
|
}
|
|
33184
|
-
|
|
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;
|
|
33185
33641
|
}
|
|
33186
33642
|
function describeTouchTarget(t) {
|
|
33187
33643
|
if (!t) return void 0;
|
|
33188
|
-
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
|
+
};
|
|
33189
33713
|
}
|
|
33190
33714
|
|
|
33191
33715
|
// lib/module/features/journey/ScaleBunDebugRoot.js
|
|
33716
|
+
init_scrollContext();
|
|
33192
33717
|
init_device();
|
|
33193
33718
|
init_interactionProtocol();
|
|
33194
33719
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
@@ -33200,6 +33725,7 @@ function getBootstrap() {
|
|
|
33200
33725
|
return _bootstrap;
|
|
33201
33726
|
}
|
|
33202
33727
|
var JourneyContext = /* @__PURE__ */ (0, import_react9.createContext)(null);
|
|
33728
|
+
var UI_STATE_CAPTURE_MIN_INTERVAL_MS = 1e3;
|
|
33203
33729
|
function ScaleBunDebugRoot({
|
|
33204
33730
|
children,
|
|
33205
33731
|
navigationRef,
|
|
@@ -33378,6 +33904,28 @@ function ScaleBunDebugRoot({
|
|
|
33378
33904
|
clearTimeout(timer);
|
|
33379
33905
|
};
|
|
33380
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
|
+
}, []);
|
|
33381
33929
|
const lastNativeTouchTsRef = (0, import_react9.useRef)(0);
|
|
33382
33930
|
const nativeTrackingConfirmedRef = (0, import_react9.useRef)(false);
|
|
33383
33931
|
const touchStartRef = (0, import_react9.useRef)(null);
|
|
@@ -33394,10 +33942,15 @@ function ScaleBunDebugRoot({
|
|
|
33394
33942
|
lastNativeTouchTsRef.current = Date.now();
|
|
33395
33943
|
nativeTrackingConfirmedRef.current = true;
|
|
33396
33944
|
const nativeOccurredAt = typeof event.occurredAt === "number" ? event.occurredAt : (typeof event.timestamp === "number" ? event.timestamp : Date.now()) - (typeof event.durationMs === "number" ? event.durationMs : 0);
|
|
33397
|
-
|
|
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
|
+
}
|
|
33398
33951
|
if (start) {
|
|
33399
|
-
const
|
|
33400
|
-
if (
|
|
33952
|
+
const pending5 = pendingJsEmitsRef.current.get(start.interactionId);
|
|
33953
|
+
if (pending5 !== void 0) clearTimeout(pending5);
|
|
33401
33954
|
pendingJsEmitsRef.current.delete(start.interactionId);
|
|
33402
33955
|
}
|
|
33403
33956
|
const {
|
|
@@ -33420,11 +33973,62 @@ function ScaleBunDebugRoot({
|
|
|
33420
33973
|
interactionId: event.interactionId || start?.interactionId || generateInteractionId(),
|
|
33421
33974
|
interactionProtocol: event.interactionProtocol || INTERACTION_PROTOCOL_VERSION,
|
|
33422
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
|
+
*/
|
|
33423
33994
|
ui: start?.ui,
|
|
33424
33995
|
stateStatus: start?.stateStatus ?? "not_captured",
|
|
33425
33996
|
target: start?.target,
|
|
33426
33997
|
targetId: start?.targetId,
|
|
33998
|
+
targetSource: start?.targetSource,
|
|
33427
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,
|
|
33428
34032
|
emitAutomaticAnalytics: start?.emitAutomaticAnalytics ?? captureAutomaticInteractions,
|
|
33429
34033
|
x: event.rawX,
|
|
33430
34034
|
y: event.rawY,
|
|
@@ -33496,6 +34100,7 @@ function ScaleBunDebugRoot({
|
|
|
33496
34100
|
const target = resolveTouchTarget(e);
|
|
33497
34101
|
let ui;
|
|
33498
34102
|
let screenName;
|
|
34103
|
+
let screenSource = "none";
|
|
33499
34104
|
try {
|
|
33500
34105
|
const {
|
|
33501
34106
|
uiStateSignature: uiStateSignature2
|
|
@@ -33507,7 +34112,9 @@ function ScaleBunDebugRoot({
|
|
|
33507
34112
|
const {
|
|
33508
34113
|
AutoScreenDetector: AutoScreenDetector2
|
|
33509
34114
|
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
33510
|
-
|
|
34115
|
+
const detector = AutoScreenDetector2.getInstance();
|
|
34116
|
+
screenName = detector.getCurrentScreen() || void 0;
|
|
34117
|
+
screenSource = screenName ? detector.getCurrentScreenSource() : "none";
|
|
33511
34118
|
} catch {
|
|
33512
34119
|
}
|
|
33513
34120
|
const start = {
|
|
@@ -33517,9 +34124,91 @@ function ScaleBunDebugRoot({
|
|
|
33517
34124
|
y: touch.pageY,
|
|
33518
34125
|
locationX: touch.locationX,
|
|
33519
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
|
+
*/
|
|
33520
34142
|
target: describeTouchTarget(target),
|
|
33521
|
-
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
|
+
})(),
|
|
33522
34210
|
screenName,
|
|
34211
|
+
screenSource,
|
|
33523
34212
|
ui,
|
|
33524
34213
|
stateStatus: ui ? "captured_nonempty" : "not_instrumented",
|
|
33525
34214
|
emitAutomaticAnalytics: captureAutomaticInteractions
|
|
@@ -33556,7 +34245,12 @@ function ScaleBunDebugRoot({
|
|
|
33556
34245
|
}
|
|
33557
34246
|
measureRoot();
|
|
33558
34247
|
const rootRect = rootRectRef.current;
|
|
33559
|
-
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);
|
|
33560
34254
|
const gestureDetails = {
|
|
33561
34255
|
target: tapped,
|
|
33562
34256
|
interactionId: start?.interactionId ?? generateInteractionId(),
|
|
@@ -33564,8 +34258,16 @@ function ScaleBunDebugRoot({
|
|
|
33564
34258
|
occurredAt: start?.occurredAt ?? Date.now(),
|
|
33565
34259
|
ui: start?.ui,
|
|
33566
34260
|
stateStatus: start?.stateStatus ?? "not_captured",
|
|
33567
|
-
targetId:
|
|
34261
|
+
targetId: identity.id,
|
|
34262
|
+
targetSource: identity.source,
|
|
33568
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,
|
|
33569
34271
|
emitAutomaticAnalytics: start?.emitAutomaticAnalytics ?? captureAutomaticInteractions,
|
|
33570
34272
|
x: start?.x ?? touch.pageX,
|
|
33571
34273
|
y: start?.y ?? touch.pageY,
|
|
@@ -33581,6 +34283,11 @@ function ScaleBunDebugRoot({
|
|
|
33581
34283
|
const ny = Math.max(0, Math.min(1, (start?.y ?? touch.pageY) / rootRect.h));
|
|
33582
34284
|
gestureDetails.normalizedX = nx;
|
|
33583
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
|
+
}
|
|
33584
34291
|
gestureDetails.normalizedPrecomputed = true;
|
|
33585
34292
|
gestureDetails.viewportWidth = rootRect.w;
|
|
33586
34293
|
gestureDetails.viewportHeight = rootRect.h;
|
|
@@ -33709,6 +34416,13 @@ function useScaleBunScreen(name) {
|
|
|
33709
34416
|
}
|
|
33710
34417
|
} catch {
|
|
33711
34418
|
}
|
|
34419
|
+
try {
|
|
34420
|
+
const {
|
|
34421
|
+
AutoScreenDetector: AutoScreenDetector2
|
|
34422
|
+
} = (init_AutoScreenDetector(), __toCommonJS(AutoScreenDetector_exports));
|
|
34423
|
+
AutoScreenDetector2.getInstance().setManualScreen(name);
|
|
34424
|
+
} catch {
|
|
34425
|
+
}
|
|
33712
34426
|
try {
|
|
33713
34427
|
const bs = getBootstrap();
|
|
33714
34428
|
const perfFeature = bs?.getPerformanceFeature?.();
|
|
@@ -33859,6 +34573,18 @@ var ScaleBunScrollView = /* @__PURE__ */ import_react12.default.forwardRef(funct
|
|
|
33859
34573
|
}
|
|
33860
34574
|
} catch {
|
|
33861
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
|
+
}
|
|
33862
34588
|
if (userOnScroll) {
|
|
33863
34589
|
userOnScroll(event);
|
|
33864
34590
|
}
|