Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
luisnaranjo733 opened this issue Aug 28, 2019 · 24 comments
Labels
Type: feature request New feature or request

Comments

@luisnaranjo733
Copy link

luisnaranjo733 commented Aug 28, 2019

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

@luisnaranjo733 luisnaranjo733 added the Type: feature request New feature or request label Aug 28, 2019
@horstleung
Copy link

I wonder how to trigger onMessage without postMessage()

@Titozzz
Copy link
Collaborator

Titozzz commented Sep 2, 2019

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),
          );

@Titozzz Titozzz closed this as completed Sep 2, 2019
@Titozzz
Copy link
Collaborator

Titozzz commented Sep 2, 2019

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

@luisnaranjo733
Copy link
Author

@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?

@horstleung
Copy link

/**
 * 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?

@Titozzz
Copy link
Collaborator

Titozzz commented Sep 23, 2019

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)}));
  })()`;

@DevArenaCN
Copy link

DevArenaCN commented Oct 10, 2019

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?

@sebastianbochan
Copy link

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?

@DevArenaCN
Copy link

DevArenaCN commented Oct 17, 2019

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.

@xdmnl
Copy link

xdmnl commented Oct 30, 2019

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.

@adspinto
Copy link

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)

@DevArenaCN
Copy link

DevArenaCN commented Oct 30, 2019

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.

@philnova
Copy link

philnova commented Dec 3, 2019

// 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?

@philnova
Copy link

philnova commented Dec 3, 2019

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.

@aperomingo
Copy link

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)

@aperomingo
Copy link

aperomingo commented Feb 24, 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.

@JamesTrickeyD
Copy link

JamesTrickeyD commented Apr 17, 2020

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.

@normanzb
Copy link

normanzb commented Jul 22, 2020

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

@JamesTrickeyD
Copy link

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

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

@dluc
Copy link

dluc commented Oct 21, 2020

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.

@vgm8
Copy link

vgm8 commented Feb 25, 2021

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?

@horstleung
Copy link

@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

@vermadivyanshu
Copy link

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 ?

@cyberphone
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: feature request New feature or request
Projects
None yet
Development

No branches or pull requests