react native html postMessage can not reach to WebView #11594

Closed
opened this Issue Dec 22, 2016 · 46 comments

Projects
None yet

beiming commented Dec 22, 2016

 I use React Native webview to show index.html, and HTML will post messge to the app. The app will then receive the message and write it to console. The problem is the app cannot receive messages, when postMessage is immediately run on head. I think it maybe related to HTML not finished loading. I then used a delay with setTimeout, and it worked. Now I want to know: Is there better way to solve this problem? Why the delay 100 milliscond did not work, but delay 200 milliscond did? I am using React Native version 0.39.0, and Node version 7.2.0. Here is the code I have so far: index.html  Index  index.js // can not be received postMessage('send to react native from index.js, no delay', '*'); // can not be received setTimeout(function() { postMessage('send to react native from index.js, delay 100 milliscond', '*') }, 100); // can received setTimeout(function() { postMessage('send to react native from index.js, delay 200 milliscond', '*') }, 200);  React Native web_view_page.js return ( console.log('onMessage:', event.nativeEvent.data)}/> );  Chrome console log 2016-12-21 11:45:02.367 web_view.js:147 onMessage: send to react native from index inline after onload, delay 0 milliscond 2016-12-21 11:45:02.491 web_view.js:147 onMessage: send to react native from index inline, delay 100 milliscond 2016-12-21 11:45:02.628 web_view.js:147 onMessage: send to react native from index.js, delay 200 milliscond 

anttu commented Jan 8, 2017 • edited

 Without setTimeout() I'm getting: Failed to execute 'postMessage' on 'Window': 2 arguments required, but only 1 present. However it does seem to work with the delay.
Contributor

farazs commented Jan 11, 2017

 Having the same issue. If a message is sent right when the page is loaded and javascript is executed it fails with that error. However, if it's executed later it seems to work. Any update on this?

Dryymoon commented Jan 24, 2017 • edited

 You can wait for RN postMessage bridge ready, by running this function once before any window.postMessage call in yours code, tested on RN 0.39 function awaitPostMessage() { let isReactNativePostMessageReady = !!window.originalPostMessage; const queue = []; let currentPostMessageFn = function store(message) { if (queue.length > 100) queue.shift(); queue.push(message); }; if (!isReactNativePostMessageReady) { // const originalPostMessage = window.postMessage; Object.defineProperty(window, 'postMessage', { configurable: true, enumerable: true, get() { return currentPostMessageFn; }, set(fn) { currentPostMessageFn = fn; isReactNativePostMessageReady = true; setTimeout(sendQueue, 0); } }); } function sendQueue() { while (queue.length > 0) window.postMessage(queue.shift()); } } Usage: awaitPostMessage(); // Call this only once in your Web Code. /* After you can call window.postMessage many times, and as soon as RN Message Bridge is ready - messadges delivered */ window.postMessage('Once :) '); window.postMessage('Twice :) '); Notice: In my code i limit queue for 100 message, fell free to increase this, or remove limit at all.

sylvainbaronnet commented Jan 27, 2017

 Thanks @Dryymoon, Do you know if it works in Android too ?

artdevgame commented Jan 29, 2017

 @Dryymoon Thanks for your solution. Do you know if this is intended behaviour? Seems a little odd to have to wait on the bridge to be ready to use before calling it. I would expect the queueing behaviour to be taken care of automatically by react-native.

jqn commented Jan 31, 2017 • edited

 @beiming In my case I wanted to trigger postMessage on click and I wasn't able to trigger the message until I added event.perventDefault() in my click event like this in the WebView page:  $(".my-btn").on("click", function(event){ event.preventDefault(); postMessage("send message"); });  Also I'm able to trigger postMessage by wrapping it with: $(document).ready(function(){ postMessage("ready"); }); 

gp3gp3gp3 commented Feb 8, 2017

 @beiming Thanks for this workaround. Setting the 0 millisecond timeout on window.onload works in iOS but doesn't seem to work on android unless I set it to 200. This still feels flimsy though, as I'm banking on I'm guessing the native bridge to be good to go at 200 milliseconds? I don't really know why this behavior is happening, but setting timeouts doesn't feel right.

andreibarabas commented Feb 21, 2017

 guys, this works on Android (i haven't tested it on iOS yet).  

Merged

Contributor

farazs commented Mar 1, 2017

 I think for Android you can use window.__REACT_WEB_VIEW_BRIDGE.postMessage instead. the delay for Android is in executing this: window.postMessage = function(data) { __REACT_WEB_VIEW_BRIDGE.postMessage(String(data)); };  You can use the javascript interface directly instead for Android based on the implementation. However, this could break in a future version if they update it.

chuyik commented Mar 2, 2017

 @farazs Man you really saved my day.

Closed

doxiaodong commented Mar 15, 2017 • edited

 @Dryymoon' s method is good, but in some env, you'd better use es5 code:  function awaitPostMessage() { var isReactNativePostMessageReady = false; var queue = []; var currentPostMessageFn = function store(message) { queue.push(message); }; if (!isReactNativePostMessageReady) { Object.defineProperty(window, "postMessage", { configurable: true, enumerable: true, get() { return currentPostMessageFn; }, set(fn) { currentPostMessageFn = fn; isReactNativePostMessageReady = true; setTimeout(sendQueue, 0); } }); } function sendQueue() { while (queue.length > 0) window.postMessage(queue.shift()); } } awaitPostMessage(); 
Contributor

farazs commented Apr 11, 2017

 @Dryymoon I'm curious why the setTimeout is needed? Once the set is called we update the postMessage and it should work right? It seems not to work without it but I'm not sure why.

kyle-ilantzis commented Apr 12, 2017

 For Android, Dryymoon's solution works when using the debug version. However, the release version causes the error "__REACT_WEB_VIEW_BRIDGE.postMessage is not a function"

chriscohoat commented Apr 21, 2017

 @doxiaodong I think you need to replace the get/set portion with the following: ... get: function () { return currentPostMessageFn; }, set: function (fn) { currentPostMessageFn = fn; isReactNativePostMessageReady = true; setTimeout(sendQueue, 0); } ...  Otherwise Webstorm is telling me that ECMAScript 5.1 doesn't support shorthand generator methods .

Closed

shenlq commented Apr 27, 2017

 For Android, the release version causes the error "__REACT_WEB_VIEW_BRIDGE.postMessage is not a function"！！！

kyle-ilantzis commented Apr 27, 2017

 @shenlq if you disable proguard then it should work again In android/app/build.gradle def enableProguardInReleaseBuilds = false

AshokICreate commented May 2, 2017 • edited

 @Dryymoon code works but now i am getting below error. any idea? "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined"

pcstl commented May 2, 2017

 I had the same issue here. I managed to "solve it" as my code needs to be ran only when the user changes page, so I can bind it to window.onbeforeunload, but there definitely needs to be a canonical way to check if the WebView Bridge is ready.
Contributor

farazs commented May 2, 2017

 For Android there's the __REACT_WEB_VIEW_BRIDGE workaround I mentioned. For iOS I ended up using https://github.com/CRAlpha/react-native-wkwebview and it doesn't have this issue with the bridge. UIWebView also has a lot of issues that are fixed in WKWebView. Hope this helps.

AshokICreate commented May 3, 2017

 @pcstl could you please provide examples?
Contributor

joenoon commented May 3, 2017

 I ran into "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined" the other day using the awaitPostMessage trick. What ended up working for me ended up being pretty simple: Give your js an entrypoint function like main() Add a ref to your WebView in componentDidMount: this.webView.injectJavaScript('main()')

Dryymoon commented May 3, 2017

 "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined" - is normal usecase in DEV mode, but in PRODUCTION this check isnt running. Die to code https://github.com/facebook/react-native/blob/master/React/Views/RCTWebView.m#L283

Dryymoon commented May 3, 2017 • edited

 Maybe solve problem, but check for native code in RN: https://github.com/facebook/react-native/blob/master/React/Views/RCTWebView.m#L285 is Wrong, because String(window.postMessage) === 'function () { [native code] }' and String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage') === 'function postMessage() { [native code] }' And it not equal But Try THIS code: function awaitPostMessage() { var isReactNativePostMessageReady = !!window.originalPostMessage; var queue = []; var currentPostMessageFn = function store(message) { if (queue.length > 100) queue.shift(); queue.push(message); }; if (!isReactNativePostMessageReady) { var originalPostMessage = window.postMessage; Object.defineProperty(window, 'postMessage', { configurable: true, enumerable: true, get: function () { return currentPostMessageFn; }, set: function (fn) { currentPostMessageFn = fn; isReactNativePostMessageReady = true; setTimeout(sendQueue, 0); } }); window.postMessage.toString = function () { return String(originalPostMessage); }; } function sendQueue() { while (queue.length > 0) window.postMessage(queue.shift()); } } Usage: awaitPostMessage(); // Call this only once in your Web Code. /* After you can call window.postMessage many times, and as soon as RN Message Bridge is ready - messages delivered */ window.postMessage('Once :) '); window.postMessage('Twice :) '); Notice: In my code i limit queue for 100 message, fell free to increase this, or remove limit at all.

Noitidart commented May 28, 2017 • edited

 The solution @andreibarabas tested on Android works great for me on iOS. How come we are going to so many extremes and not just using the postMessage.length method as @andreibarabas did? This one is so simple. I load my webview with this:   And usage of it:  

Contributor

farazs commented Jun 6, 2017

 Why was this closed? It hasn't been resolved and the current implementation is flawed requiring complex workarounds.

Closed

jjzazuet commented Nov 18, 2017

 I can confirm that the solution from @andreibarabas works for me in RN 0.50 under iOS and Android. To be explicit: import React, {Component} from 'react'; import {View, WebView} from 'react-native'; export default class WebViewTest extends Component { constructor(props) { super(props); this.state = { bridgeJs:  (function ready() { function whenRNPostMessageReady(cb) { if (postMessage.length === 1) cb(); else setTimeout(function() { whenRNPostMessageReady(cb) }, 100); } whenRNPostMessageReady(function() { postMessage('hi react native!!!'); }); })(); }; } onMessage(m) { alert(m.nativeEvent.data); } render() { return ( { this.webView = wv; }} injectedJavaScript={this.state.bridgeJs} onMessage={m => this.onMessage(m)} javaScriptEnabled /> ); } }  Thanks!

Noitidart commented Nov 18, 2017 • edited

 Just an update to verify the code I got from above - #11594 (comment) - has been working perfectly for me on both, iOS and Android, since I started using it, in Jun 2017: function init() { // ready to talk via postMessage } function whenRNPostMessageReady(cb) { if (postMessage.length === 1) cb(); else setTimeout(function() { whenRNPostMessageReady(cb) }, 1000); } window.addEventListener('DOMContentLoaded', function() { whenRNPostMessageReady(init); }, false);  Thanks @andreibarabas!!

jjzazuet commented Nov 19, 2017

Also, just to reiterate.

Placing comments inside the injectedJavaScript property of WebView breaks bridge communication on Android.

So don't do that I guess :P.

Noitidart commented Nov 19, 2017 • edited

 I verify @jjzazuet comment - comments work on iOS but not on Android - thanks for that

Open

nirpeled commented Nov 27, 2017

 Can anyone please explain me why is it okay to do these "hacks" instead of just fixing RN's WebView so it won't override the global postMessage in a way that it's breaking the API? What am I missing here? Thanks!

andrew09 commented Nov 30, 2017

 I used @Dryymoon solution and it has worked really well. Experiencing some issues in ios9 but I am working on supporting that now. I will post an update with an updated solution.

vvavepacket commented Jan 3, 2018

 guys, do you have any idea why a webview post message back and forth in a release build is super slow yet fast in a debug build?

tranduykhanh commented Jan 15, 2018

 @Dryymoon This method still works. Thank a lot. But there a lot of tricky here 😢

mattbachman31 commented Jan 19, 2018

 @jqn We also ran into that issue on our app, and the fix that is in RN 0.53-rc here fixes issues on page load, but not on form submit. I filed a separate bug for this issue here if anyone else is dealing with that issue

willhlaw commented Jan 19, 2018

 I second @vvavepacket question. Why does webview post message seem much slower in production than in a debug build (at least in expo comparing development mode (~0.5s) vs production mode (>5s)?

Merged

Thomsos commented Mar 22, 2018

 @willhlaw @vvavepacket I am experience the same issue in production build. Any work around or update on this? I am using react-native 0.54.2 and react-native-wkwebview-reborn 1.16.0

YusufHussein commented May 21, 2018

 apparently injected javascript code doesn’t work in android unless compressed (you can use this online tool http://javascriptcompressor.com/ )

Noitidart commented May 21, 2018

 @YusufHussein it doesn't have to be compressed. You just cannot use any comments.

YusufHussein commented May 22, 2018

 @Noitidart well I tried it without comments but it didn't work untill I compressed the code I am using react native 0.54.2 and Android simulator 6.0 Marshmallow

Open

Closed

stale bot commented Aug 20, 2018

 Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

kirillpisarev commented Aug 21, 2018

 Still no luck posting a message on document ready within a WebView page without a dirty hack, suggested above. Is any hope that the problem will be solved without this mess, cuz it exists for about 2.5 years... function RNPostMessage(message) { if (window.postMessage.length === 1) window.postMessage(message); else setTimeout(RNPostMessage.bind(null, message), 100); } 

Open

jamonholmgren commented Sep 9, 2018

 Migrated issue to react-native-community/react-native-webview#5. This one can be closed.

Merged

Merged