react-native-webview / react-native-webview

React Native Cross-Platform WebView

Home Page:https://github.com/react-native-community/discussions-and-proposals/pull/3

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Documentation] Clarify migration path for this.webView.postMessage removal

luisnaranjo733 opened this issue · comments

Is your feature request related to a problem? If so, Please describe.
Not really. The README on this project very clearly states the following upcoming removal: "this.webView.postMessage() removal (never documented and less flexible than injectJavascript)"

That seems straightforward enough. Stop using this.webView.postMessage() and start using injectJavascript. It seems like anywhere you use postMessage you can just swap to injectJavascript. Would be nice to call that out in the README so it's less scary for people who have code that is using postMessage(). If there are any significant behavioral differences between postMessage and injectJavascript, would definitely be good to call them out so people know about them.

Describe the solutions you came up with
Add a few sentences explaining how to replace the usage of postMessage with injectJavascript. Basically, provide a migration path for people who are using the undocumented postMessage

I wonder how to trigger onMessage without postMessage()

When you use injectJavascript, the passed string gets evaluated in the browser window so you could use something like:

const generateOnMessageFunction = (data: string) =>
  `(function() {
    window.whateverYourListenerIsNamed.onMessage(${JSON.stringify(data)});
  })()`;

And then call

webViewRef.current.injectJavaScript(
            generateOnMessageFunction(message),
          );

The idea here is that injectJavascript allows for much more possibility, while not removing anything from postMessage

@Titozzz what is window.whateverYourListenerIsNamed? The docs seem to imply that the injected global variable is always called window.ReactNativeWebView. Is this something different?

Also, the docs talk about window.ReactNativeWebView.postMessage for web -> native communication.
How does that relate to window.whateverYourListenerIsNamed.onMessage?

/**
 * This method is called whenever JavaScript running within the web view calls:
 *   - window.webkit.messageHandlers[MessageHandlerName].postMessage
 */
- (void)userContentController:(WKUserContentController *)userContentController
       didReceiveScriptMessage:(WKScriptMessage *)message
{
  if (_onMessage != nil) {
    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
    [event addEntriesFromDictionary: @{@"data": message.body}];
    _onMessage(event);
  }
}

@Titozzz Is is suggested to use window.webkit.messageHandlers.notification.postMessage("whatever");
to trigger the onMessage method?

Sorry my response was not clear.

Please use this to keep the current behavior

const generateOnMessageFunction = (data: string) =>
  `(function() {
	window.dispatchEvent(new MessageEvent('message', ${JSON.stringify(data)}));
  })()`;

Sorry my response was not clear.

Please use this to keep the current behavior

const generateOnMessageFunction = (data: string) =>
  `(function() {
	window.dispatchEvent(new MessageEvent('message', ${JSON.stringify(data)}));
  })()`;

@Titozzz A quick question on this. I tried to pass an object into generateOnMessageFunction, I got an null data in the event, but if I tried to pass an string (JSON.stringify(myObject)) I got a type Error in the console. Any ideas?

Until today I used this construction:

My app.js:

componentDidUpdate() {
   this.webView.postMessage(
                this.serialize(this.props.options, true)
   );
}

and in my index.html

document.addEventListener('message', function (data) {
    alert('message received');
    
});

But when I replaced the this.webView.postMessage(...) with

this.webView.injectJavaScript(
                `(function() {
                    window.dispatchEvent(new MessageEvent('message', ${JSON.stringify(this.props.options)}));
                  })()`
            );

nothing works. Any idea? As I see its in development process. Correct me if Im wrong and how should I use that?

Until today I used this construction:

My app.js:

componentDidUpdate() {
   this.webView.postMessage(
                this.serialize(this.props.options, true)
   );
}

and in my index.html

document.addEventListener('message', function (data) {
    alert('message received');
    
});

But when I replaced the this.webView.postMessage(...) with

this.webView.injectJavaScript(
                `(function() {
                    window.dispatchEvent(new MessageEvent('message', ${JSON.stringify(this.props.options)}));
                  })()`
            );

nothing works. Any idea? As I see its in development process. Correct me if Im wrong and how should I use that?

What I did is:

    this.webview.injectJavaScript(`
    (function(){
      window.postMessage(${JSON.stringify(data)},'*');
    })();
    true;
    `)

Seems to be working for me, hopefully that would help.

As per https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/MessageEvent, the second parameter of MessageEvent should be an object, not a string.

The code should look like:

// In index.html
window.addEventListener('message', function (event) {
    alert('message received: ' + event.data);
});

// In app.js
const generateOnMessageFunction = (data: any) =>
  `(function() {
    window.dispatchEvent(new MessageEvent('message', {data: ${JSON.stringify(data)}}));
  })()`;

webViewRef.current.injectJavaScript(generateOnMessageFunction(message));

PS: if the event listener is added to document, then dispatchEvent should be called on document.

commented

Hello, I have seen all the examples where i can send message with injectJavascript from react-native to WebView(HTML content), should we use the same approach when doing it backwards?
I mean, sending message back to react-native from WebView(HTML content)

Hello, I have seen all the examples where i can send message with injectJavascript from react-native to WebView(HTML content), should we use the same approach when doing it backwards?
I mean, sending message back to react-native from WebView(HTML content)

window.ReactNativeWebView.postMessage(dataToPass, '*') should take care of that. And you need to setup the onMessage method on the RN side of course.

// In app.js
const generateOnMessageFunction = (data: any) =>
(function() { window.dispatchEvent(new MessageEvent('message', {data: ${JSON.stringify(data)}})); })();

I've taken this approach, but when I receive the event the data payload is undefined. The whole nativeEvent object is just { isTrusted: false }.

Does anyone know what is causing the data payload to be stripped off?

Leaving for posterity in case anyone else has my same issue:

The data payload isn't being stripped off; see this issue. In my case, the issue was caused because the old postMessage method sent the data payload as a string, but injectJavaScript seems to parse the payload when it's executed. So the message listener was trying to do JSON.parse on the payload and failing because it was already an object.

Any update?

This problem exists from 5.0.1 version...
What happen? window.postMessage() was deprecated since then, that's OK, but his supposed solution is to use (I have tried all kind of combinations):

window.ReactNativeWebView.postMessage("Im a message")
window.ReactNativeWebView.postMessage("Im a message", "*")
window.ReactNativeWebView.postMessage("{ data: 'Im a message' }")
window.ReactNativeWebView.postMessage("{ data: 'Im a message' }", "*")
window.ReactNativeWebView.postMessage(JSON.stringify("{ data: 'Im a message' }"))
window.ReactNativeWebView.postMessage(JSON.stringify("{ data: 'Im a message' }"), "*")

but both targets are always undefined (window.postMessage() and window.ReactNativeWebView).

So, how can I send data from WebView to React Native?

"react-native": "0.58.6"
"react-native-webview": "^8.1.0" (the last at the moment 24/02/2020)

This solution is for send data from WebView to React Native

SOLVED WITH setTimeout function on Web (JS) side using next function:

WEB:

setTimeout(() => {
    window.ReactNativeWebView &&
      window.ReactNativeWebView.postMessage(
        JSON.stringify({ type: "token", data: "kbadsfi74ty59y47934875v93ruhfekrt983457" })
      );
  }, 0);

REACT NATIVE:

<WebView
          ref={ref => (this.webview = ref)}
          source={{ uri: DASHBOARD_URL }}
          onNavigationStateChange={this.handleWebViewNavigationStateChange}
          onMessage={this.handleOnMessage}
          renderLoading={() => this.renderLoading()}
          startInLoadingState
          incognitotimes
        />

handleOnMessage(event) {
    const { type, data } = JSON.parse(event.nativeEvent.data)
    if (type === "token") {
      console.log('token', data)
    }
  }

In my opinion, this is horrible and the unique solution i have found.

Good luck.

I'm having huge trouble with this. I wonder if anyone can help...

I implemented our payment 3ds challenge with UIWebView. It loaded an iframe containing a form which, when submitted, loaded the bank's challenge. We then receive data via postMessage which I pass back to React Native.

Since moving to WKWebView I cannot use the srcdoc property on ios as it gives the following error: add about to LSApplicationQueriesSchemes in your Info.plist (which doesn't work BTW)

So now i am getting rid of the iframe and trying to use the form submission method directly in the WebView. But as the form effectively loads a new page, I have no opportunity to inject javascript that will intercept the postMessage.

Sorry - it's quite complicated, I hope that made some sense.

Injecting javascript string means those code in string will not be lint automatically by linter, while postMessage avoids it. I failed to understand how is it always more flexible and better than postMessage

BTW - the way we fixed the iFrame problem was by setting source this way instead:

iframe.open(); iframe.write(iframeSource); iframe.close();

What happens when RN needs to send a lot of messages to a single page application (which is never reloaded), e.g. imagine thousands of messages, each taking KBs? isn't the growing JS size going to increase the memory footprint of the app inside the webview and possibly crash the RN app?

It seems to me that postMessage is much cleaner, use the message and discard it, without memory concerns, and without worrying about the JS space, e.g. injecting vars or function calls increases the risk of destabilizing an app hosted in the webview.

commented

I've been reading this post and i can not understand exactly how to change the postMessage in my code. Im adding a callback in the webview html like this

generateTheWebViewContent = () => {
  return `<!DOCTYPE html>
  <html>
  <head> 
    <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge"> 
    <script type="text/javascript"> 
      var onDataCallback = function(response) { 
        window.ReactNativeWebView.postMessage(response);  
      };  
   </script> 
  </head>
  <body> 
    ...
  </body>
</html>`
}

return (
    <WebView
      originWhitelist={['*']}
      mixedContentMode={'always'}
      onMessage={onMessage}
      javaScriptEnabled
      automaticallyAdjustContentInsets
      source={{
        html: generateTheWebViewContent(),
        baseUrl: `${url}`,
      }}
    />
  );

When calling onDataCallback I will be calling window.ReactNativeWebView.postMessage(response); to receive the response. I've tried window.dispatchEvent(new MessageEvent('message', {response: ${JSON.stringify(response)}})); But it is not working. Anyone can help me?

@vgm8 I am not sure how you call the onDataCallback.
Here is my two cents.
I tried to inject a callback into webview:

			logEvent = (event, params) => {
				window.ReactNativeWebView.postMessage(JSON.stringify({event, params, type: "${WEBVIEW_BRIDGE_TYPES.LOG_EVENT}"}));
			}

But the web side cannot call the function.
So I set it to window

window.logEvent = (event, params) => { ... }

Then the web side can call window.logEvent

for me using window.dispatchEvent(new MessageEvent('message', ${JSON.stringify(data)})); does not seem to trigger the method passed to onMessage prop, however, if i use window.ReactNativeWebView.postMessage('hello') it works. Is there something i am missing ?

@Titozzz this modification has certain downsides as well: #2273