rampkit-expo-dev 0.0.55 → 0.0.56
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/build/RampkitOverlay.js +45 -90
- package/package.json +1 -1
package/build/RampkitOverlay.js
CHANGED
|
@@ -1025,6 +1025,10 @@ function Overlay(props) {
|
|
|
1025
1025
|
const lastVarsSendTimeRef = (0, react_1.useRef)([]);
|
|
1026
1026
|
// Stale value window in milliseconds - matches iOS SDK (600ms)
|
|
1027
1027
|
const STALE_VALUE_WINDOW_MS = 600;
|
|
1028
|
+
// Track which screens have completed initial setup (to avoid repeated onLoadEnd processing)
|
|
1029
|
+
const initializedScreensRef = (0, react_1.useRef)(new Set());
|
|
1030
|
+
// Track the currently active screen index (matches iOS SDK's activeScreenIndex)
|
|
1031
|
+
const activeScreenIndexRef = (0, react_1.useRef)(0);
|
|
1028
1032
|
// ============================================================================
|
|
1029
1033
|
// Navigation Resolution Helpers (matches iOS SDK behavior)
|
|
1030
1034
|
// ============================================================================
|
|
@@ -1205,6 +1209,8 @@ function Overlay(props) {
|
|
|
1205
1209
|
// Slide animation: use PagerView's built-in animated page change
|
|
1206
1210
|
// and skip the fade curtain overlay.
|
|
1207
1211
|
if (animationType === "slide") {
|
|
1212
|
+
// Update active screen index FIRST
|
|
1213
|
+
activeScreenIndexRef.current = nextIndex;
|
|
1208
1214
|
// @ts-ignore: methods exist on PagerView instance
|
|
1209
1215
|
const pager = pagerRef.current;
|
|
1210
1216
|
if (!pager)
|
|
@@ -1216,7 +1222,6 @@ function Overlay(props) {
|
|
|
1216
1222
|
pager.setPageWithoutAnimation(nextIndex);
|
|
1217
1223
|
}
|
|
1218
1224
|
// Explicitly send vars to the new page after setting it
|
|
1219
|
-
// This ensures the webview receives the latest state
|
|
1220
1225
|
requestAnimationFrame(() => {
|
|
1221
1226
|
sendVarsToWebView(nextIndex);
|
|
1222
1227
|
sendOnboardingStateToWebView(nextIndex);
|
|
@@ -1227,6 +1232,8 @@ function Overlay(props) {
|
|
|
1227
1232
|
// Animates the PagerView container out, switches page, then animates back in
|
|
1228
1233
|
if (animationType === "slidefade") {
|
|
1229
1234
|
setIsTransitioning(true);
|
|
1235
|
+
// Update active screen index FIRST
|
|
1236
|
+
activeScreenIndexRef.current = nextIndex;
|
|
1230
1237
|
// Determine direction: forward (nextIndex > index) or backward
|
|
1231
1238
|
const isForward = nextIndex > index;
|
|
1232
1239
|
const direction = isForward ? 1 : -1;
|
|
@@ -1278,6 +1285,8 @@ function Overlay(props) {
|
|
|
1278
1285
|
}
|
|
1279
1286
|
// Default fade animation: uses a white curtain overlay
|
|
1280
1287
|
setIsTransitioning(true);
|
|
1288
|
+
// Update active screen index FIRST
|
|
1289
|
+
activeScreenIndexRef.current = nextIndex;
|
|
1281
1290
|
react_native_1.Animated.timing(fadeOpacity, {
|
|
1282
1291
|
toValue: 1,
|
|
1283
1292
|
duration: 160,
|
|
@@ -1415,34 +1424,6 @@ function Overlay(props) {
|
|
|
1415
1424
|
// because the WebView echoes back variables which triggers another sendVarsToWebView.
|
|
1416
1425
|
// Onboarding state is sent separately in onLoadEnd and onPageSelected.
|
|
1417
1426
|
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Broadcast variables to all WebViews, optionally excluding one.
|
|
1420
|
-
* This mirrors the iOS SDK's broadcastVariables(excluding:) pattern.
|
|
1421
|
-
* @param excludeIndex - Optional index of WebView to skip (typically the source of the update)
|
|
1422
|
-
*/
|
|
1423
|
-
function broadcastVars(excludeIndex) {
|
|
1424
|
-
if (__DEV__)
|
|
1425
|
-
console.log("[Rampkit] broadcastVars", {
|
|
1426
|
-
recipients: webviewsRef.current.length,
|
|
1427
|
-
excludeIndex,
|
|
1428
|
-
vars: varsRef.current,
|
|
1429
|
-
});
|
|
1430
|
-
const script = buildDirectVarsScript(varsRef.current);
|
|
1431
|
-
const now = Date.now();
|
|
1432
|
-
for (let i = 0; i < webviewsRef.current.length; i++) {
|
|
1433
|
-
// Skip the source WebView to prevent echo loops
|
|
1434
|
-
if (excludeIndex !== undefined && i === excludeIndex) {
|
|
1435
|
-
continue;
|
|
1436
|
-
}
|
|
1437
|
-
const wv = webviewsRef.current[i];
|
|
1438
|
-
if (wv) {
|
|
1439
|
-
// Track send time for stale value filtering
|
|
1440
|
-
lastVarsSendTimeRef.current[i] = now;
|
|
1441
|
-
// @ts-ignore: injectJavaScript exists on WebView instance
|
|
1442
|
-
wv.injectJavaScript(script);
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
1427
|
react_1.default.useEffect(() => {
|
|
1447
1428
|
const sub = react_native_1.BackHandler.addEventListener("hardwareBackPress", () => {
|
|
1448
1429
|
if (index > 0) {
|
|
@@ -1487,25 +1468,15 @@ function Overlay(props) {
|
|
|
1487
1468
|
const onPageSelected = (e) => {
|
|
1488
1469
|
const pos = e.nativeEvent.position;
|
|
1489
1470
|
setIndex(pos);
|
|
1490
|
-
//
|
|
1471
|
+
// Update active screen index FIRST (before any other processing)
|
|
1472
|
+
activeScreenIndexRef.current = pos;
|
|
1491
1473
|
if (__DEV__)
|
|
1492
|
-
console.log("[Rampkit] onPageSelected", pos);
|
|
1493
|
-
// Send vars
|
|
1494
|
-
// receives them. The first send might fail if the webview isn't fully ready,
|
|
1495
|
-
// so we retry a few times.
|
|
1474
|
+
console.log("[Rampkit] onPageSelected - activating screen", pos);
|
|
1475
|
+
// Send vars and onboarding state to the newly active screen
|
|
1496
1476
|
requestAnimationFrame(() => {
|
|
1497
1477
|
sendVarsToWebView(pos);
|
|
1498
|
-
// Send onboarding state once after vars
|
|
1499
1478
|
sendOnboardingStateToWebView(pos);
|
|
1500
1479
|
});
|
|
1501
|
-
// Retry after a short delay in case the first send didn't work
|
|
1502
|
-
setTimeout(() => {
|
|
1503
|
-
sendVarsToWebView(pos);
|
|
1504
|
-
}, 50);
|
|
1505
|
-
// Final retry to catch any edge cases
|
|
1506
|
-
setTimeout(() => {
|
|
1507
|
-
sendVarsToWebView(pos);
|
|
1508
|
-
}, 150);
|
|
1509
1480
|
// Track screen change event
|
|
1510
1481
|
if (props.onScreenChange && props.screens[pos]) {
|
|
1511
1482
|
props.onScreenChange(pos, props.screens[pos].id);
|
|
@@ -1606,7 +1577,7 @@ function Overlay(props) {
|
|
|
1606
1577
|
(_b = props.onNotificationPermissionResult) === null || _b === void 0 ? void 0 : _b.call(props, !!(result === null || result === void 0 ? void 0 : result.granted));
|
|
1607
1578
|
}
|
|
1608
1579
|
catch (_) { }
|
|
1609
|
-
// Save to shared vars and
|
|
1580
|
+
// Save to shared vars and send to active screen only
|
|
1610
1581
|
try {
|
|
1611
1582
|
varsRef.current = {
|
|
1612
1583
|
...varsRef.current,
|
|
@@ -1617,7 +1588,8 @@ function Overlay(props) {
|
|
|
1617
1588
|
ios: result === null || result === void 0 ? void 0 : result.ios,
|
|
1618
1589
|
},
|
|
1619
1590
|
};
|
|
1620
|
-
|
|
1591
|
+
// Only send to active screen to avoid broadcast loops
|
|
1592
|
+
sendVarsToWebView(activeScreenIndexRef.current);
|
|
1621
1593
|
}
|
|
1622
1594
|
catch (_) { }
|
|
1623
1595
|
}
|
|
@@ -1632,6 +1604,13 @@ function Overlay(props) {
|
|
|
1632
1604
|
transform: [{ translateX: pagerTranslateX }],
|
|
1633
1605
|
},
|
|
1634
1606
|
], children: (0, jsx_runtime_1.jsx)(react_native_pager_view_1.default, { ref: pagerRef, style: react_native_1.StyleSheet.absoluteFill, scrollEnabled: false, initialPage: 0, onPageSelected: onPageSelected, offscreenPageLimit: props.screens.length, overScrollMode: "never", children: docs.map((doc, i) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.page, renderToHardwareTextureAndroid: true, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { ref: (r) => (webviewsRef.current[i] = r), style: styles.webview, originWhitelist: ["*"], source: { html: doc }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening + exports.injectedDynamicTapHandler + exports.injectedButtonAnimations, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler + exports.injectedButtonAnimations, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, overScrollMode: "never", scalesPageToFit: false, showsHorizontalScrollIndicator: false, dataDetectorTypes: "none", allowsLinkPreview: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, javaScriptEnabled: true, domStorageEnabled: true, hideKeyboardAccessoryView: true, onLoadEnd: () => {
|
|
1607
|
+
// Only initialize each screen ONCE to avoid repeated processing
|
|
1608
|
+
if (initializedScreensRef.current.has(i)) {
|
|
1609
|
+
if (__DEV__)
|
|
1610
|
+
console.log(`[Rampkit] onLoadEnd skipped (already initialized): ${i}`);
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
initializedScreensRef.current.add(i);
|
|
1635
1614
|
setLoadedCount((c) => c + 1);
|
|
1636
1615
|
if (i === 0) {
|
|
1637
1616
|
setFirstPageLoaded(true);
|
|
@@ -1641,12 +1620,14 @@ function Overlay(props) {
|
|
|
1641
1620
|
props.onScreenChange(0, props.screens[0].id);
|
|
1642
1621
|
}
|
|
1643
1622
|
}
|
|
1644
|
-
// Initialize this page with current vars
|
|
1623
|
+
// Initialize this page with current vars
|
|
1645
1624
|
if (__DEV__)
|
|
1646
|
-
console.log("[Rampkit] onLoadEnd
|
|
1625
|
+
console.log("[Rampkit] onLoadEnd initializing screen", i);
|
|
1647
1626
|
sendVarsToWebView(i, true);
|
|
1648
|
-
//
|
|
1649
|
-
|
|
1627
|
+
// Only send onboarding state to the ACTIVE screen (index 0 on initial load)
|
|
1628
|
+
if (i === activeScreenIndexRef.current) {
|
|
1629
|
+
sendOnboardingStateToWebView(i);
|
|
1630
|
+
}
|
|
1650
1631
|
}, onMessage: (ev) => {
|
|
1651
1632
|
var _a, _b, _c, _d;
|
|
1652
1633
|
const raw = ev.nativeEvent.data;
|
|
@@ -1655,55 +1636,32 @@ function Overlay(props) {
|
|
|
1655
1636
|
try {
|
|
1656
1637
|
// JSON path
|
|
1657
1638
|
const data = JSON.parse(raw);
|
|
1658
|
-
// 1) Variables from a page → update shared
|
|
1659
|
-
//
|
|
1639
|
+
// 1) Variables from a page → update shared state
|
|
1640
|
+
// CRITICAL: Only accept variable updates from the ACTIVE screen
|
|
1641
|
+
// This prevents inactive screens from causing infinite broadcast loops
|
|
1660
1642
|
if ((data === null || data === void 0 ? void 0 : data.type) === "rampkit:variables" &&
|
|
1661
1643
|
(data === null || data === void 0 ? void 0 : data.vars) &&
|
|
1662
1644
|
typeof data.vars === "object") {
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
const isWithinStaleWindow = timeSinceSend < STALE_VALUE_WINDOW_MS;
|
|
1671
|
-
if (__DEV__) {
|
|
1672
|
-
console.log("[Rampkit] stale check:", {
|
|
1673
|
-
pageIndex: i,
|
|
1674
|
-
isWithinStaleWindow,
|
|
1675
|
-
timeSinceSend,
|
|
1676
|
-
});
|
|
1645
|
+
// CRITICAL: Ignore variable updates from non-active screens
|
|
1646
|
+
// Only the currently visible screen should be able to update variables
|
|
1647
|
+
if (i !== activeScreenIndexRef.current) {
|
|
1648
|
+
if (__DEV__) {
|
|
1649
|
+
console.log(`[Rampkit] ignoring variables from inactive screen ${i} (active: ${activeScreenIndexRef.current})`);
|
|
1650
|
+
}
|
|
1651
|
+
return;
|
|
1677
1652
|
}
|
|
1653
|
+
if (__DEV__)
|
|
1654
|
+
console.log("[Rampkit] received variables from ACTIVE page", i, data.vars);
|
|
1678
1655
|
let changed = false;
|
|
1679
1656
|
const newVars = {};
|
|
1680
1657
|
for (const [key, value] of Object.entries(data.vars)) {
|
|
1681
1658
|
// CRITICAL: Filter out onboarding.* variables
|
|
1682
|
-
// These are read-only from the WebView's perspective
|
|
1683
|
-
// controlled by the SDK. Accepting them back creates infinite loops.
|
|
1659
|
+
// These are read-only from the WebView's perspective
|
|
1684
1660
|
if (key.startsWith('onboarding.')) {
|
|
1685
|
-
if (__DEV__) {
|
|
1686
|
-
console.log(`[Rampkit] ignoring read-only onboarding variable: ${key}`);
|
|
1687
|
-
}
|
|
1688
1661
|
continue;
|
|
1689
1662
|
}
|
|
1690
1663
|
const hasHostVal = Object.prototype.hasOwnProperty.call(varsRef.current, key);
|
|
1691
1664
|
const hostVal = varsRef.current[key];
|
|
1692
|
-
// Stale value filtering (matches iOS SDK behavior):
|
|
1693
|
-
// If we're within the stale window, don't let empty/default values
|
|
1694
|
-
// overwrite existing non-empty host values.
|
|
1695
|
-
// This prevents pages from clobbering user input with cached defaults
|
|
1696
|
-
// when they first become active/visible.
|
|
1697
|
-
if (isWithinStaleWindow && hasHostVal) {
|
|
1698
|
-
const hostIsNonEmpty = hostVal !== "" && hostVal !== null && hostVal !== undefined;
|
|
1699
|
-
const incomingIsEmpty = value === "" || value === null || value === undefined;
|
|
1700
|
-
if (hostIsNonEmpty && incomingIsEmpty) {
|
|
1701
|
-
if (__DEV__) {
|
|
1702
|
-
console.log(`[Rampkit] filtering stale empty value for key "${key}": keeping "${hostVal}"`);
|
|
1703
|
-
}
|
|
1704
|
-
continue; // Skip this key, keep host value
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
1665
|
// Accept the update if value is different
|
|
1708
1666
|
if (!hasHostVal || hostVal !== value) {
|
|
1709
1667
|
newVars[key] = value;
|
|
@@ -1712,12 +1670,9 @@ function Overlay(props) {
|
|
|
1712
1670
|
}
|
|
1713
1671
|
if (changed) {
|
|
1714
1672
|
varsRef.current = { ...varsRef.current, ...newVars };
|
|
1715
|
-
//
|
|
1716
|
-
//
|
|
1717
|
-
broadcastVars(i);
|
|
1673
|
+
// NOTE: Don't broadcast to all screens - just update shared state
|
|
1674
|
+
// Other screens will get updated vars when they become active
|
|
1718
1675
|
}
|
|
1719
|
-
// NOTE: Do NOT send vars back to source page - it already has them
|
|
1720
|
-
// and would just echo them back again, creating a ping-pong loop
|
|
1721
1676
|
return;
|
|
1722
1677
|
}
|
|
1723
1678
|
// 2) A page asked for current vars → send only to that page
|
package/package.json
CHANGED