-
Notifications
You must be signed in to change notification settings - Fork 3k
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
[DISCUSS] Cross platform API for RN<>WebView 2-way RPC/message passing #66
Comments
Thanks @fungilation ! One other interesting API I found when perusing Github was this one: https://github.com/pinqy520/react-native-webview-invoke It exposes an API where you can call an async function directly, as long as you control both the web and mobile side of the connection. Under the hood, it still uses |
Interesting. Although requiring below on the Web side is no good. New API should support any WebView src, not only local js: import invoke from 'react-native-webview-invoke/browser' |
@fungilation So I was using a fork of alinz/react-native-webview-bridge until I started using this package. I wrote a simple piece of code that allowed me to be 100% non breaking with what was there before. This does not mean it's safe regarding postMessage naming and such but I'll just post it here in case anyone needs it.
Then you just need to use onMessage prop to listen and sendMessageToWebView to send. |
I totally agree this is a requirement for even considering React Native, with a WebView, as a suitable replacement for Cordova (without using the native components, rather than their web counterparts). I created the following schematic and we will be investing some time in working up an example, any advice on improving this implementation strategy would be appreciated! Also some idea of whether we would want to move this functionality into this package (I believe it should be included) or maintain it as a separate package would be nice. |
Well that is what I'm doing already (excepted for the callback array, as I'm more in the just dispatching actions without waiting for answers mind) |
@Titozzz my end goal is to allow calling any react-native package directly from the WebView as if we didn't even have to cross the boundary between the two. I have a simple (in a good way) working example of a Bridge which does the following; ReactNativeBridge.call('functionName', arg1, arg2, arg..., callback) where you can pass as many arguments as you want and callback is of the form (err, res). On the native side you define what functionName does (and whether it is sync/async), quite similar to the invoke package. @fungilation I also forked this package and modified the Android implementation of postMessage (in the webview) to no longer overwrite the window.postMessage (this caused issues for our application). I didn't implement this using injectedJavascript yet, as I actually don't mind importing a package on both sides (native & web), but will work on that soon. Are there any other use cases I should take into account? It is my intention to create a PR for this feature which could be enabled by passing bridge={true} to the Webview component and add documentation on how to use it in the WebView. One limitation of my approach, which could be easily resolved, is that it currently only allows calling from WebView to Native, not the other way around. Happy to hear your thoughts! |
Okay I've updated all the repository issues regarding postMessage 😄 . Let's keep the discussion focused in this thread only, or we will get lost! 😢 I think everyone agrees that using postMessage I not a good idea for several reasons! Listing what we need:
Nice to have ..?
|
@Titozzz for our use-case async/callback return values are a must-have. The BridgedWebview repository (https://github.com/KoenLav/bridged-webview) I created already fulfills the first two checks on your list and covers async/callback returns values. I'm not sure however how you interpret two-way communication? The approach I took is pretty much centered around the WebView having the 'lead' (dictating what is happening), but I can imagine use-cases where one would want the native side of things to have the lead, and dictate what happens in the WebView. What's your take on that? I could quite easily extend the classes to also cover this use-case, but if we don't expect anyone to use it like that it could just make things confusing. |
Yeah, I actually have a use-case were both side are reacting to each others, so I don't really have one master but both are emmiting events that the other is free to listen to, which is why I was not using return values, as I'm not waiting for anything. Would not hurt having it tho 👍 |
So I've read your code (super fast, sorry it's late here 😴). I'm however not sure that we want to implement something that is THAT specific. What I mean is that you defined a 'RFC' to communicate and do function calls and all that, but in the end what I think this repo should provid is a solid communication (eg: just posting JSON payload) both ways and then leave the implementation back to the users, as you did in your repository. I don't know if i'm clear. I'll try to explain better tomorrow ! |
I see (with regards to the use-case). The reason I didn't invest any time into Native => WebView yet is also because it's pretty easy to solve once WebView => Native works. For example: injectJavascript(const result = doSomethingInWebview(); ReactNativeBridge.call('anyNativeFunction', result, someOtherVariables...); ===== Given the use-case you are describing (just sending data from one side and having it picked up on the other side) I can imagine this might seem specific. What do you mean by solid communication? I think this repo already does that just fine (on Android, at least, not sure of the status of the WKWebView included at the moment). If there is still work to do in making sure that postMessage/injectJavascript work fine on all supported platforms then that definitely needs to happen first! In our use-case we actually want to port a (Meteor) web-app from Cordova to React Native, but for some functionality we rely on native code (getting the unique device ID, communicating via TCP sockets, etc.). What I was going for is providing a way to do anything from the WebView about as easily as you could from the native side of things. I don't think this is THAT specific; it can also be used to simply pass JSON from one side to the other, as it doesn't require setting a callback and the underlying postMessage function is still available as well, for users wanting more fine-grained control. I simply defined a standardized way of instructing the native side how to handle specific messages, which I think is much easier to understand for a new user than what is currently out there (react-native-webview-bridge, react-native-webview-messaging, react-native-webview-invoke) AND supports all use-cases the packages I just mentioned support. Looking through the code sample you posted earlier I did however notice that I didn't implement the onMessage function within the WebView yet, if I add that then the statement above (supporting all use-cases) is actually true, as far as I can tell. Happy to hear your thoughts! |
Meteor/Cordova! I tried and failed with my last mobile app project using it, good to see another migrant to RN. I'm copying over my comment from the other issue as I think it's useful for discussion of what 2-way communication means to me, and what I think a good API looks like based on the tried and true in existing libraries: I'm for a breaking change in API. Especially if that means sidestepping entirely security issues with For starter, instead of proposing completely new API, what about just borrow one from existing, working library? react-native-wkwebview's API looks comprehensive, but they are using the same react-native-webview-bridge I use actively with my own fork for my own app. It's not the best API but it works, and it works for both iOS and Android. We could start with that as far as API definition? With one exception, on calling from RN to WebView: react-native-webview-bridge uses this to send string to WebView: webviewbridge.sendToBridge("hello from react-native") but react-native-wkwebview's way is better, since users can directly evaluate JS, and define separate functions for different purposes injected in webview. String sending in react-native-webview-bridge means a nest of conditionals to handle a list of strings sent from RN, and it gets messy. this.webview.evaluateJavaScript('receivedMessageFromReactNative("Hello from the other side.")') |
@fungilation I read your comments to most issues before I started working on this and I think the package linked in my latest PR, and the API it suggests, will suit your use case. Any comments are much appreciated! |
So about that. This is the part I mean to address in that repository and that really belongs here imho, the current bridge is not OK, as it is using postMessage and this has major drawbacks. We will introduce a breaking change to implement something like react-nativew-webview-bridge (more or less), that will allow for a safer, more reliable communication.
Regarding this, I'm not sure that this should belong directly inside the repository and be loaded for everyone. As I said once the bridge is reliable, I feel like (and it's my opinion) that most webview users have very specific use cases, and I think we should not decide how to implement this for them. As shown above, once the bridge is strong, you can easily with a few lines implement whatever API you need. One thing we could however do it add some example in the docs (something like your code) to give a more beginner user a template to follow / understand to do whatever he needs. |
@Titozzz I looked into the implementation of react-native-webview-bridge and I don't think it is much/any safer than the currently implemented solution here. Can you identify where their implementation is better and/or what is not 'strong' about the current implementation in this repository? |
Sure I gave the example only because they are not using Issue with the current implem as stated in the docs:
Plus when you instantiate the webview it messes with window.postMessage that was previously defined. This is why we will probably define something else than postMessage 😄 |
@Titozzz my PR already resolves the second issue. The cross site scripting one I think is a more difficult one, but I could also take a look at that. Do you have any suggestions? Oh wait, I don't think it's that difficult... We could simply define which origins are accepted on the React Native side. |
Yeah, we need to stop using postMessage behind the scenes ! |
I'm not sure I understand you correctly: in my opinion we need to stop using window.postMessage, but a AnyGlobal.postMessage function would be fine, agreed? The AnyGlobal.postMessage function is simply a function which is inserted into the window and enables us to send messages to the React Native side. We could name the function anyhow we wanted as it simply makes use of a JavascriptInterface on Android: https://developer.android.com/reference/android/webkit/JavascriptInterface Or a WKScriptMessageHandler on iOS WKWebView: https://spin.atomicobject.com/2016/09/01/sharing-web-data-wkwebview/ It does appear however, when reading the iOS example, that in iOS it is only possible to implement a variable in window.webkit.messageHandlers.SomeName.postMessage. So I guess, for consistency, we should do the same for Android (which lets us insert the bridge in any place). |
yeah sure when I say stop using post message I'm talking about the one inside the window 😛 . |
Oh my god, this is exactly what I want and wish ❤️ ❤️ ❤️ |
I think the beauty of a WebView custom implemented of RPC is it can share the same memory representation of objects in both the JS runtimes: RN and WebView as they use the same core. This will make performance much better than postMessage can offer at the moment. |
Might not be applicable, but beware of JS runtime differences within WebView and RN. Things like Array.from() is available in RN but not within WebView, currently. |
Yes but primitives and base classes are shared |
@iddan During debugging, the React Native Javascript runs on the desktop PC, but the WebView runs on the phone. The only connection between them is a WebSocket. In other words, the two can only communicate using strings (unless you want to break debugging). The safest way to improve the I think |
Cool |
@swansontec do you have any ideas about extending the current postMessage implementation with the structured clone algorithm? Perhaps something like this? https://www.npmjs.com/package/realistic-structured-clone Your first suggestion is already implemented in my PR (window.webkit.messageHandlers.ReactNative), to make the behavior between iOS and Android consistent. Your second suggestion is already possible, I think the WebView already exposes a postMessage command on the React Native side of things. Just took a look at the code and it does. |
@KoenLav Awesome! I didn't realize these fixes were already in the works. As for the structured clone algorithm, I don't have any particular ideas on the implementation. This was more of a pie-in-the-sky wishlist item for me. Using an NPM module like the one you pointed out might be a reasonable approach. |
This comment has been minimized.
This comment has been minimized.
Wow, thanks @Titozzz for making this happen! 🎉 |
…entation (#303) fixes #29 fixes #272 fixes #221 fixes #105 fixes #66 BREAKING CHANGE: Communication from webview to react-native has been completely rewritten. React-native-webview will not use or override window.postMessage anymore. Reasons behind these changes can be found throughout so many issues that it made sense to go that way. Instead of using window.postMessage(data, *), please now use window.ReactNativeWebView.postMessage(data). Side note: if you wish to keep compatibility with the old version when you upgrade, you can use the injectedJavascript prop to do that: const injectedJavascript = `(function() { window.postMessage = function(data) { window.ReactNativeWebView.postMessage(data); }; })()`; Huge thanks to @jordansexton and @KoenLav!
# [5.0.0](v4.1.0...v5.0.0) (2019-02-01) ### Features * **Android/iOS postMessage:** refactoring the old postMessage implementation ([#303](#303)) ([f3bdab5](f3bdab5)), closes [#29](#29) [#272](#272) [#221](#221) [#105](#105) [#66](#66) ### BREAKING CHANGES * **Android/iOS postMessage:** Communication from webview to react-native has been completely rewritten. React-native-webview will not use or override window.postMessage anymore. Reasons behind these changes can be found throughout so many issues that it made sense to go that way. Instead of using window.postMessage(data, *), please now use window.ReactNativeWebView.postMessage(data). Side note: if you wish to keep compatibility with the old version when you upgrade, you can use the injectedJavascript prop to do that: const injectedJavascript = `(function() { window.postMessage = function(data) { window.ReactNativeWebView.postMessage(data); }; })()`; Huge thanks to @jordansexton and @KoenLav!
🎉 This issue has been resolved in version 5.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Awesome! Thanks |
I have previously implemented an RPC bridge based on the |
…entation (react-native-webview#303) fixes react-native-webview#29 fixes react-native-webview#272 fixes react-native-webview#221 fixes react-native-webview#105 fixes react-native-webview#66 BREAKING CHANGE: Communication from webview to react-native has been completely rewritten. React-native-webview will not use or override window.postMessage anymore. Reasons behind these changes can be found throughout so many issues that it made sense to go that way. Instead of using window.postMessage(data, *), please now use window.ReactNativeWebView.postMessage(data). Side note: if you wish to keep compatibility with the old version when you upgrade, you can use the injectedJavascript prop to do that: const injectedJavascript = `(function() { window.postMessage = function(data) { window.ReactNativeWebView.postMessage(data); }; })()`; Huge thanks to @jordansexton and @KoenLav!
# [5.0.0](react-native-webview/react-native-webview@v4.1.0...v5.0.0) (2019-02-01) ### Features * **Android/iOS postMessage:** refactoring the old postMessage implementation ([react-native-webview#303](react-native-webview#303)) ([f3bdab5](react-native-webview@f3bdab5)), closes [react-native-webview#29](react-native-webview#29) [react-native-webview#272](react-native-webview#272) [react-native-webview#221](react-native-webview#221) [react-native-webview#105](react-native-webview#105) [react-native-webview#66](react-native-webview#66) ### BREAKING CHANGES * **Android/iOS postMessage:** Communication from webview to react-native has been completely rewritten. React-native-webview will not use or override window.postMessage anymore. Reasons behind these changes can be found throughout so many issues that it made sense to go that way. Instead of using window.postMessage(data, *), please now use window.ReactNativeWebView.postMessage(data). Side note: if you wish to keep compatibility with the old version when you upgrade, you can use the injectedJavascript prop to do that: const injectedJavascript = `(function() { window.postMessage = function(data) { window.ReactNativeWebView.postMessage(data); }; })()`; Huge thanks to @jordansexton and @KoenLav!
# [5.0.0](react-native-webview/react-native-webview@v4.1.0...v5.0.0) (2019-02-01) ### Features * **Android/iOS postMessage:** refactoring the old postMessage implementation ([#303](react-native-webview/react-native-webview#303)) ([f3bdab5](react-native-webview/react-native-webview@f3bdab5)), closes [#29](react-native-webview/react-native-webview#29) [#272](react-native-webview/react-native-webview#272) [#221](react-native-webview/react-native-webview#221) [#105](react-native-webview/react-native-webview#105) [#66](react-native-webview/react-native-webview#66) ### BREAKING CHANGES * **Android/iOS postMessage:** Communication from webview to react-native has been completely rewritten. React-native-webview will not use or override window.postMessage anymore. Reasons behind these changes can be found throughout so many issues that it made sense to go that way. Instead of using window.postMessage(data, *), please now use window.ReactNativeWebView.postMessage(data). Side note: if you wish to keep compatibility with the old version when you upgrade, you can use the injectedJavascript prop to do that: const injectedJavascript = `(function() { window.postMessage = function(data) { window.ReactNativeWebView.postMessage(data); }; })()`; Huge thanks to @jordansexton and @KoenLav!
Referencing (my) prior discussions regarding this, for context and so we don't repeat here. In reverse chronological order:
This consistent, robust, cross platform API is important for any app that blends native and webviews. I list mine as one. A requirement of this API is that an injected
postMessage
for purpose of RN message passing, not be overridden by a website's JS in the WebView. Which is currently the case with RN core's WebView, and presumably this forked react-native-webview. It could be achieved (and have been attempted in prior discussions and in RN core's WebView) by loading RN's injectedpostMessage
last, but that's unreliable should the website create after onLoad a function of the same name, alongside iframe issues. This is not hypothetical as it does get overridden for a fact, when loading google.com in WebView for example.A simple function rename that doesn't use the web standard of
postMessage
would sidestep what is effectively a name collision issue. Call itrnPostMessage
or anything else. Open to suggestions on a better alternative to renaming that actually has a guarantee for not being overridden by non-locally loaded JS within WebView.This new API should consider/port code (if tested as reliable) from react-native-wkwebview, which is iOS only. And implement the same API for other platforms. There is also working RN<>WebView bridge API at react-native-webview-bridge that's well established (old but fully functional, with google.com loaded in webview, and for Android and iOS), but that's based on UIWebView and not WKWebView on the iOS side.
The text was updated successfully, but these errors were encountered: