rampkit-expo-dev 0.0.32 → 0.0.34

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.
@@ -432,8 +432,11 @@ function Overlay(props) {
432
432
  const varsRef = (0, react_1.useRef)({});
433
433
  // hold refs for injection
434
434
  const webviewsRef = (0, react_1.useRef)([]);
435
- // track when we last initialized a given page with host vars (to filter stale defaults)
436
- const lastInitSendRef = (0, react_1.useRef)([]);
435
+ // Track when we last SENT vars to each page (for stale value filtering)
436
+ // This helps filter out default/cached values that pages send back after receiving updates
437
+ const lastVarsSendTimeRef = (0, react_1.useRef)([]);
438
+ // Stale value window in milliseconds - matches iOS SDK (600ms)
439
+ const STALE_VALUE_WINDOW_MS = 600;
437
440
  // Fade-in when overlay becomes visible
438
441
  react_1.default.useEffect(() => {
439
442
  if (visible && !isClosing) {
@@ -567,28 +570,37 @@ function Overlay(props) {
567
570
  return;
568
571
  if (__DEV__)
569
572
  console.log("[Rampkit] sendVarsToWebView", i, varsRef.current, { isInitialLoad });
570
- // Only update the stale filter timestamp during initial page load,
571
- // not when syncing vars on page selection. This prevents the filter
572
- // from incorrectly rejecting legitimate user interactions that happen
573
- // immediately after navigating to a screen.
574
- if (isInitialLoad) {
575
- lastInitSendRef.current[i] = Date.now();
576
- }
573
+ // Track when we send vars to this page for stale value filtering
574
+ // This helps us ignore default/cached values that pages echo back
575
+ lastVarsSendTimeRef.current[i] = Date.now();
577
576
  // Use direct variable setting instead of MessageEvent dispatch
578
577
  // This is more reliable as it doesn't depend on event listeners being set up
579
578
  // @ts-ignore: injectJavaScript exists on WebView instance
580
579
  wv.injectJavaScript(buildDirectVarsScript(varsRef.current));
581
580
  }
582
- function broadcastVars() {
581
+ /**
582
+ * Broadcast variables to all WebViews, optionally excluding one.
583
+ * This mirrors the iOS SDK's broadcastVariables(excluding:) pattern.
584
+ * @param excludeIndex - Optional index of WebView to skip (typically the source of the update)
585
+ */
586
+ function broadcastVars(excludeIndex) {
583
587
  if (__DEV__)
584
588
  console.log("[Rampkit] broadcastVars", {
585
589
  recipients: webviewsRef.current.length,
590
+ excludeIndex,
586
591
  vars: varsRef.current,
587
592
  });
588
593
  const script = buildDirectVarsScript(varsRef.current);
594
+ const now = Date.now();
589
595
  for (let i = 0; i < webviewsRef.current.length; i++) {
596
+ // Skip the source WebView to prevent echo loops
597
+ if (excludeIndex !== undefined && i === excludeIndex) {
598
+ continue;
599
+ }
590
600
  const wv = webviewsRef.current[i];
591
601
  if (wv) {
602
+ // Track send time for stale value filtering
603
+ lastVarsSendTimeRef.current[i] = now;
592
604
  // @ts-ignore: injectJavaScript exists on WebView instance
593
605
  wv.injectJavaScript(script);
594
606
  }
@@ -757,21 +769,50 @@ function Overlay(props) {
757
769
  try {
758
770
  // JSON path
759
771
  const data = JSON.parse(raw);
760
- // 1) Variables from a page → update shared + broadcast
772
+ // 1) Variables from a page → update shared + broadcast to OTHER pages
773
+ // This mirrors the iOS SDK pattern with stale value filtering.
761
774
  if ((data === null || data === void 0 ? void 0 : data.type) === "rampkit:variables" &&
762
775
  (data === null || data === void 0 ? void 0 : data.vars) &&
763
776
  typeof data.vars === "object") {
764
777
  if (__DEV__)
765
778
  console.log("[Rampkit] received variables from page", i, data.vars);
766
- // Accept all variable updates from pages without filtering.
767
- // The previous filter was too aggressive and blocked legitimate
768
- // user interactions that happened within 600ms of page load.
769
- // We now trust that pages send correct variable updates.
779
+ // Check if this page is within the stale value window
780
+ // (we recently sent vars to it and it's echoing back defaults)
781
+ const now = Date.now();
782
+ const lastSendTime = lastVarsSendTimeRef.current[i] || 0;
783
+ const timeSinceSend = now - lastSendTime;
784
+ const isWithinStaleWindow = timeSinceSend < STALE_VALUE_WINDOW_MS;
785
+ const isActiveScreen = i === index;
786
+ if (__DEV__) {
787
+ console.log("[Rampkit] stale check:", {
788
+ pageIndex: i,
789
+ isActiveScreen,
790
+ isWithinStaleWindow,
791
+ timeSinceSend,
792
+ });
793
+ }
770
794
  let changed = false;
771
795
  const newVars = {};
772
796
  for (const [key, value] of Object.entries(data.vars)) {
773
797
  const hasHostVal = Object.prototype.hasOwnProperty.call(varsRef.current, key);
774
798
  const hostVal = varsRef.current[key];
799
+ // Stale value filtering (matches iOS SDK behavior):
800
+ // If we're within the stale window AND this is NOT the active screen,
801
+ // don't let empty/default values overwrite existing host values.
802
+ // This prevents preloaded pages from clobbering user input with cached defaults.
803
+ if (isWithinStaleWindow && !isActiveScreen && hasHostVal) {
804
+ // If host has a non-empty value and page is sending empty/default,
805
+ // keep the host value (don't overwrite with stale data)
806
+ const hostIsNonEmpty = hostVal !== "" && hostVal !== null && hostVal !== undefined;
807
+ const incomingIsEmpty = value === "" || value === null || value === undefined;
808
+ if (hostIsNonEmpty && incomingIsEmpty) {
809
+ if (__DEV__) {
810
+ console.log(`[Rampkit] filtering stale empty value for key "${key}": keeping "${hostVal}"`);
811
+ }
812
+ continue; // Skip this key, keep host value
813
+ }
814
+ }
815
+ // Accept the update if value is different
775
816
  if (!hasHostVal || hostVal !== value) {
776
817
  newVars[key] = value;
777
818
  changed = true;
@@ -779,7 +820,9 @@ function Overlay(props) {
779
820
  }
780
821
  if (changed) {
781
822
  varsRef.current = { ...varsRef.current, ...newVars };
782
- broadcastVars();
823
+ // Broadcast to all WebViews EXCEPT the source (index i)
824
+ // This prevents echo loops and matches iOS SDK behavior
825
+ broadcastVars(i);
783
826
  }
784
827
  return;
785
828
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rampkit-expo-dev",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "description": "The Expo SDK for RampKit. Build, test, and personalize app onboardings with instant updates.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",