rampkit-expo-dev 0.0.25 → 0.0.27

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.
@@ -1,5 +1,6 @@
1
1
  export declare const injectedHardening = "\n(function(){\n try {\n var meta = document.querySelector('meta[name=\"viewport\"]');\n if (!meta) { meta = document.createElement('meta'); meta.name = 'viewport'; document.head.appendChild(meta); }\n meta.setAttribute('content','width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover');\n var style = document.createElement('style');\n style.textContent='html,body{overflow-x:hidden!important;} html,body,*{-webkit-user-select:none!important;user-select:none!important;-webkit-touch-callout:none!important;-ms-user-select:none!important;touch-action: pan-y;} *{-webkit-tap-highlight-color: rgba(0,0,0,0)!important;} ::selection{background: transparent!important;} ::-moz-selection{background: transparent!important;} a,img{-webkit-user-drag:none!important;user-drag:none!important;-webkit-touch-callout:none!important} input,textarea{caret-color:transparent!important;-webkit-user-select:none!important;user-select:none!important}';\n document.head.appendChild(style);\n var prevent=function(e){e.preventDefault&&e.preventDefault();};\n document.addEventListener('gesturestart',prevent,{passive:false});\n document.addEventListener('gesturechange',prevent,{passive:false});\n document.addEventListener('gestureend',prevent,{passive:false});\n document.addEventListener('dblclick',prevent,{passive:false});\n document.addEventListener('wheel',function(e){ if(e.ctrlKey) e.preventDefault(); },{passive:false});\n document.addEventListener('touchmove',function(e){ if(e.scale && e.scale !== 1) e.preventDefault(); },{passive:false});\n document.addEventListener('selectstart',prevent,{passive:false,capture:true});\n document.addEventListener('contextmenu',prevent,{passive:false,capture:true});\n document.addEventListener('copy',prevent,{passive:false,capture:true});\n document.addEventListener('cut',prevent,{passive:false,capture:true});\n document.addEventListener('paste',prevent,{passive:false,capture:true});\n document.addEventListener('dragstart',prevent,{passive:false,capture:true});\n // Belt-and-suspenders: aggressively clear any attempted selection\n var clearSel=function(){\n try{var sel=window.getSelection&&window.getSelection(); if(sel&&sel.removeAllRanges) sel.removeAllRanges();}catch(_){} }\n document.addEventListener('selectionchange',clearSel,{passive:true,capture:true});\n document.onselectstart=function(){ clearSel(); return false; };\n try{ document.documentElement.style.webkitUserSelect='none'; document.documentElement.style.userSelect='none'; }catch(_){ }\n try{ document.body.style.webkitUserSelect='none'; document.body.style.userSelect='none'; }catch(_){ }\n var __selTimer = setInterval(clearSel, 160);\n window.addEventListener('pagehide',function(){ try{ clearInterval(__selTimer); }catch(_){} });\n // Continuously enforce no-select on all elements and new nodes\n var enforceNoSelect = function(el){\n try{\n el.style && (el.style.webkitUserSelect='none', el.style.userSelect='none', el.style.webkitTouchCallout='none');\n el.setAttribute && (el.setAttribute('unselectable','on'), el.setAttribute('contenteditable','false'));\n }catch(_){}\n }\n try{\n var all=document.getElementsByTagName('*');\n for(var i=0;i<all.length;i++){ enforceNoSelect(all[i]); }\n var obs = new MutationObserver(function(muts){\n for(var j=0;j<muts.length;j++){\n var m=muts[j];\n if(m.type==='childList'){\n m.addedNodes && m.addedNodes.forEach && m.addedNodes.forEach(function(n){ if(n && n.nodeType===1){ enforceNoSelect(n); var q=n.getElementsByTagName? n.getElementsByTagName('*'): []; for(var k=0;k<q.length;k++){ enforceNoSelect(q[k]); }}});\n } else if(m.type==='attributes'){\n enforceNoSelect(m.target);\n }\n }\n });\n obs.observe(document.documentElement,{ childList:true, subtree:true, attributes:true, attributeFilter:['contenteditable','style'] });\n }catch(_){ }\n } catch(_) {}\n})(); true;\n";
2
2
  export declare const injectedNoSelect = "\n(function(){\n try {\n if (window.__rkNoSelectApplied) return true;\n window.__rkNoSelectApplied = true;\n var style = document.getElementById('rk-no-select-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'rk-no-select-style';\n style.innerHTML = \"\n * {\n user-select: none !important;\n -webkit-user-select: none !important;\n -webkit-touch-callout: none !important;\n }\n ::selection {\n background: transparent !important;\n }\n \";\n document.head.appendChild(style);\n }\n var prevent = function(e){ if(e && e.preventDefault) e.preventDefault(); return false; };\n document.addEventListener('contextmenu', prevent, { passive: false, capture: true });\n document.addEventListener('selectstart', prevent, { passive: false, capture: true });\n } catch (_) {}\n true;\n})();\n";
3
+ export declare const injectedVarsHandler = "\n(function(){\n try {\n if (window.__rkVarsHandlerApplied) return true;\n window.__rkVarsHandlerApplied = true;\n \n // Handler function that updates variables and notifies the page\n window.__rkHandleVarsUpdate = function(vars) {\n if (!vars || typeof vars !== 'object') return;\n // Update the global variables object\n window.__rampkitVariables = vars;\n // Dispatch a custom event that the page's JS can listen to for re-rendering\n try {\n document.dispatchEvent(new CustomEvent('rampkit:vars-updated', { detail: vars }));\n } catch(e) {}\n // Also try calling a global handler if the page defined one\n try {\n if (typeof window.onRampkitVarsUpdate === 'function') {\n window.onRampkitVarsUpdate(vars);\n }\n } catch(e) {}\n };\n \n // Listen for message events from React Native\n document.addEventListener('message', function(event) {\n try {\n var data = event.data;\n if (data && data.type === 'rampkit:variables' && data.vars) {\n window.__rkHandleVarsUpdate(data.vars);\n }\n } catch(e) {}\n }, false);\n \n // Also listen on window for compatibility\n window.addEventListener('message', function(event) {\n try {\n var data = event.data;\n if (data && data.type === 'rampkit:variables' && data.vars) {\n window.__rkHandleVarsUpdate(data.vars);\n }\n } catch(e) {}\n }, false);\n } catch (_) {}\n true;\n})();\n";
3
4
  export type ScreenPayload = {
4
5
  id: string;
5
6
  html: string;
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.injectedNoSelect = exports.injectedHardening = void 0;
39
+ exports.injectedVarsHandler = exports.injectedNoSelect = exports.injectedHardening = void 0;
40
40
  exports.showRampkitOverlay = showRampkitOverlay;
41
41
  exports.hideRampkitOverlay = hideRampkitOverlay;
42
42
  exports.closeRampkitOverlay = closeRampkitOverlay;
@@ -125,6 +125,54 @@ exports.injectedNoSelect = `
125
125
  true;
126
126
  })();
127
127
  `;
128
+ // Robust variable handler that ensures variables are always received and applied
129
+ // This runs after content loads and sets up listeners for incoming variable updates
130
+ exports.injectedVarsHandler = `
131
+ (function(){
132
+ try {
133
+ if (window.__rkVarsHandlerApplied) return true;
134
+ window.__rkVarsHandlerApplied = true;
135
+
136
+ // Handler function that updates variables and notifies the page
137
+ window.__rkHandleVarsUpdate = function(vars) {
138
+ if (!vars || typeof vars !== 'object') return;
139
+ // Update the global variables object
140
+ window.__rampkitVariables = vars;
141
+ // Dispatch a custom event that the page's JS can listen to for re-rendering
142
+ try {
143
+ document.dispatchEvent(new CustomEvent('rampkit:vars-updated', { detail: vars }));
144
+ } catch(e) {}
145
+ // Also try calling a global handler if the page defined one
146
+ try {
147
+ if (typeof window.onRampkitVarsUpdate === 'function') {
148
+ window.onRampkitVarsUpdate(vars);
149
+ }
150
+ } catch(e) {}
151
+ };
152
+
153
+ // Listen for message events from React Native
154
+ document.addEventListener('message', function(event) {
155
+ try {
156
+ var data = event.data;
157
+ if (data && data.type === 'rampkit:variables' && data.vars) {
158
+ window.__rkHandleVarsUpdate(data.vars);
159
+ }
160
+ } catch(e) {}
161
+ }, false);
162
+
163
+ // Also listen on window for compatibility
164
+ window.addEventListener('message', function(event) {
165
+ try {
166
+ var data = event.data;
167
+ if (data && data.type === 'rampkit:variables' && data.vars) {
168
+ window.__rkHandleVarsUpdate(data.vars);
169
+ }
170
+ } catch(e) {}
171
+ }, false);
172
+ } catch (_) {}
173
+ true;
174
+ })();
175
+ `;
128
176
  function performRampkitHaptic(event) {
129
177
  if (!event || event.action !== "haptic") {
130
178
  // Backwards compatible default
@@ -225,7 +273,7 @@ function preloadRampkitOverlay(opts) {
225
273
  opacity: 0,
226
274
  top: -1000,
227
275
  left: -1000,
228
- }, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { originWhitelist: ["*"], source: { html: docs[0] || "<html></html>" }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening, injectedJavaScript: exports.injectedNoSelect, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, hideKeyboardAccessoryView: true }) }));
276
+ }, children: (0, jsx_runtime_1.jsx)(react_native_webview_1.WebView, { originWhitelist: ["*"], source: { html: docs[0] || "<html></html>" }, injectedJavaScriptBeforeContentLoaded: exports.injectedHardening, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler, automaticallyAdjustContentInsets: false, contentInsetAdjustmentBehavior: "never", bounces: false, scrollEnabled: false, allowsInlineMediaPlayback: true, mediaPlaybackRequiresUserAction: false, cacheEnabled: true, hideKeyboardAccessoryView: true }) }));
229
277
  preloadSibling = new react_native_root_siblings_1.default((0, jsx_runtime_1.jsx)(HiddenPreloader, {}));
230
278
  }
231
279
  catch (e) {
@@ -404,11 +452,38 @@ function Overlay(props) {
404
452
  .replace(/`/g, "\\`");
405
453
  return `(function(){try{document.dispatchEvent(new MessageEvent('message',{data:${json}}));}catch(e){}})();`;
406
454
  }
455
+ // Build a script that directly sets variables and triggers updates
456
+ // This is more reliable than dispatching events which may not be caught
457
+ function buildDirectVarsScript(vars) {
458
+ const json = JSON.stringify(vars)
459
+ .replace(/\\/g, "\\\\")
460
+ .replace(/`/g, "\\`");
461
+ return `(function(){
462
+ try {
463
+ var newVars = ${json};
464
+ // Directly update the global variables object
465
+ window.__rampkitVariables = newVars;
466
+ // Call the handler if available
467
+ if (typeof window.__rkHandleVarsUpdate === 'function') {
468
+ window.__rkHandleVarsUpdate(newVars);
469
+ }
470
+ // Dispatch custom event for any listeners
471
+ try {
472
+ document.dispatchEvent(new CustomEvent('rampkit:vars-updated', {detail: newVars}));
473
+ } catch(e) {}
474
+ // Call global callback if defined
475
+ if (typeof window.onRampkitVarsUpdate === 'function') {
476
+ window.onRampkitVarsUpdate(newVars);
477
+ }
478
+ } catch(e) {
479
+ console.log('[Rampkit] buildDirectVarsScript error:', e);
480
+ }
481
+ })();`;
482
+ }
407
483
  function sendVarsToWebView(i, isInitialLoad = false) {
408
484
  const wv = webviewsRef.current[i];
409
485
  if (!wv)
410
486
  return;
411
- const payload = { type: "rampkit:variables", vars: varsRef.current };
412
487
  if (__DEV__)
413
488
  console.log("[Rampkit] sendVarsToWebView", i, varsRef.current, { isInitialLoad });
414
489
  // Only update the stale filter timestamp during initial page load,
@@ -418,8 +493,10 @@ function Overlay(props) {
418
493
  if (isInitialLoad) {
419
494
  lastInitSendRef.current[i] = Date.now();
420
495
  }
496
+ // Use direct variable setting instead of MessageEvent dispatch
497
+ // This is more reliable as it doesn't depend on event listeners being set up
421
498
  // @ts-ignore: injectJavaScript exists on WebView instance
422
- wv.injectJavaScript(buildDispatchScript(payload));
499
+ wv.injectJavaScript(buildDirectVarsScript(varsRef.current));
423
500
  }
424
501
  function broadcastVars() {
425
502
  if (__DEV__)
@@ -427,14 +504,12 @@ function Overlay(props) {
427
504
  recipients: webviewsRef.current.length,
428
505
  vars: varsRef.current,
429
506
  });
507
+ const script = buildDirectVarsScript(varsRef.current);
430
508
  for (let i = 0; i < webviewsRef.current.length; i++) {
431
509
  const wv = webviewsRef.current[i];
432
510
  if (wv) {
433
511
  // @ts-ignore: injectJavaScript exists on WebView instance
434
- wv.injectJavaScript(buildDispatchScript({
435
- type: "rampkit:variables",
436
- vars: varsRef.current,
437
- }));
512
+ wv.injectJavaScript(script);
438
513
  }
439
514
  }
440
515
  }
@@ -485,12 +560,20 @@ function Overlay(props) {
485
560
  // ensure current page is synced with latest vars when selected
486
561
  if (__DEV__)
487
562
  console.log("[Rampkit] onPageSelected", pos);
488
- // Use requestAnimationFrame to ensure the webview is fully active and ready
489
- // to receive injected JS. Without this delay, the first navigation back
490
- // to a screen may not properly receive the updated variables.
563
+ // Send vars multiple times with increasing delays to ensure the webview
564
+ // receives them. The first send might fail if the webview isn't fully ready,
565
+ // so we retry a few times.
491
566
  requestAnimationFrame(() => {
492
567
  sendVarsToWebView(pos);
493
568
  });
569
+ // Retry after a short delay in case the first send didn't work
570
+ setTimeout(() => {
571
+ sendVarsToWebView(pos);
572
+ }, 50);
573
+ // Final retry to catch any edge cases
574
+ setTimeout(() => {
575
+ sendVarsToWebView(pos);
576
+ }, 150);
494
577
  // Track screen change event
495
578
  if (props.onScreenChange && props.screens[pos]) {
496
579
  props.onScreenChange(pos, props.screens[pos].id);
@@ -571,7 +654,7 @@ function Overlay(props) {
571
654
  styles.root,
572
655
  !visible && styles.invisible,
573
656
  visible && { opacity: overlayOpacity },
574
- ], pointerEvents: visible && !isClosing ? "auto" : "none", 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, injectedJavaScript: exports.injectedNoSelect, 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: () => {
657
+ ], pointerEvents: visible && !isClosing ? "auto" : "none", 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, injectedJavaScript: exports.injectedNoSelect + exports.injectedVarsHandler, 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: () => {
575
658
  setLoadedCount((c) => c + 1);
576
659
  if (i === 0) {
577
660
  setFirstPageLoaded(true);
@@ -599,28 +682,22 @@ function Overlay(props) {
599
682
  typeof data.vars === "object") {
600
683
  if (__DEV__)
601
684
  console.log("[Rampkit] received variables from page", i, data.vars);
602
- const now = Date.now();
603
- const lastInit = lastInitSendRef.current[i] || 0;
604
- const filtered = {};
685
+ // Accept all variable updates from pages without filtering.
686
+ // The previous filter was too aggressive and blocked legitimate
687
+ // user interactions that happened within 600ms of page load.
688
+ // We now trust that pages send correct variable updates.
605
689
  let changed = false;
690
+ const newVars = {};
606
691
  for (const [key, value] of Object.entries(data.vars)) {
607
692
  const hasHostVal = Object.prototype.hasOwnProperty.call(varsRef.current, key);
608
693
  const hostVal = varsRef.current[key];
609
- if (now - lastInit < 600 &&
610
- hasHostVal &&
611
- hostVal !== undefined &&
612
- value !== hostVal) {
613
- if (__DEV__)
614
- console.log("[Rampkit] ignore stale var from page", i, key, value, "kept", hostVal);
615
- continue;
616
- }
617
694
  if (!hasHostVal || hostVal !== value) {
618
- filtered[key] = value;
695
+ newVars[key] = value;
619
696
  changed = true;
620
697
  }
621
698
  }
622
699
  if (changed) {
623
- varsRef.current = { ...varsRef.current, ...filtered };
700
+ varsRef.current = { ...varsRef.current, ...newVars };
624
701
  broadcastVars();
625
702
  }
626
703
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rampkit-expo-dev",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
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",