Jasonette / JASONETTE-Android

📡 Native App over HTTP, on Android
https://www.jasonette.com
Other
1.61k stars 270 forks source link

Webcontainer => Jasonette IPC #164

Open gliechtenstein opened 7 years ago

gliechtenstein commented 7 years ago

Should work the same way it works for the iOS branch

https://github.com/Jasonette/JASONETTE-iOS/pull/253

maks commented 7 years ago

@gliechtenstein I'm happy to work on this as I've just done the work on #162 so am in the webview head space atm.

gliechtenstein commented 7 years ago

@maks sounds great! 👍👍

maks commented 7 years ago

This will be implemented per the doc issue: https://github.com/Jasonette/documentation/issues/66

maks commented 7 years ago

@gliechtenstein I've got an initial working implementation: https://github.com/maks/JASONETTE-Android/commit/5ed6938c822507d8c391faecf1e6806a9f1563f5

BUT I haven't done the PR yet due to the interface for objects added to a Webview in Android via addJavaInterface() only allowing simple types we need to pass the Jason from Webview JS to Java via JSON string (JSON.stringify()) not an actual JS object. Would you be able to do it this way on iOS to be consistent?

gliechtenstein commented 7 years ago

@maks I was thinking about that issue too. I guess we could do that in the worst case, but it feels a bit too inconvenient because then the user would have to write the code in escaped JSON string everytime and it's really cumbersome and not intuitive.

Would it be possible to inject an additional javascript on the Android side so that it automatically interprets a JS object into a JSON string before evaluating?

maks commented 7 years ago

@gliechtenstein I guess we can load a bit of JS shim code to do this as long as we DONT target N where this will no longer work, [per this compatibility note](https://developer.android.com/reference/android/webkit/WebView.html#evaluateJavascript(java.lang.String, android.webkit.ValueCallback))

The problem with doing the above is we permentantly stop Jasonette from ever targeting Android N or higher.

An alternative to that is to require webcontainer content that wants to use JASON to do so via loading a JS library (eg. via CDN or locally) which could then hide this and any other future compatibility issues.

maks commented 7 years ago

@gliechtenstein I saw you merged the iOS implementation for this. Are you ok then for me todo PR with implementation I have for Andriod with the caveat of needing to stringify() the return value?

gliechtenstein commented 7 years ago

Oops sorry i totally dropped the ball on this one, shouldn't have merged that iOS one before this issue was resolved in this world. Thankfully an API doesn't exist until it's documented so I think we have some room for now.

An alternative to that is to require webcontainer content that wants to use JASON to do so via loading a JS library (eg. via CDN or locally) which could then hide this and any other future compatibility issues.

Could you elaborate a bit on this? Do you mean like injecting some JavaScript into the webview? And how is this different from the JS shim approach? I guess I didn't fully understand the JS shim approach to begin with...

maks commented 7 years ago

@gliechtenstein no worries. What I meant would be to have content in a webview include a js file: <script src="https://jasonette.org/webview-shim.js"></script>

and then webview-shim.js would expose whatever API we are going to document and the JS code in that would take care of doing anything needed to make it compatible across iOS and ANdroid, eg. needing to stringify() on Android. Hope that makes sense?

gliechtenstein commented 7 years ago

@maks I think this is kind of related to https://github.com/Jasonette/Jasonette/issues/4 we're discussing. I guess now that we're going to be injecting JS files into JS execution context I guess there's not much holding us back from doing it this way?

maks commented 7 years ago

@gliechtenstein no sorry its different, there we are talking about a webview whos content jasonette "controls" whereas here its any html content supplied be the user.

gliechtenstein commented 7 years ago

@maks OK I've just realized my confusion stems from the fact that the anchor tag part of the link you pasted is broken and simply opens up at the top of the https://developer.android.com/reference/android/webkit/WebView.html page. I didn't understand because I didn't realize you were talking about the valueCallback part specifically.

Anyway, now that I understand I can make a more constructive comment hopefully.

Basically the way I've implemented this logic on iOS side is

  1. Create a javascript function called JASON.call and inject it to the webview context
  2. Instead of having JASON.call run some JS-to-native binding logic, the JASON.call simply creates an <iframe> element with its src attribute set to the JSON object parameter we pass in to JASON.call https://github.com/Jasonette/JASONETTE-iOS/pull/253/files#diff-f22c800ecb5b9ecf974c06bdcb66ad7cR1881

    NSString *summon = @"var JASON={call: function(e){var n=document.createElement(\"IFRAME\");n.setAttribute(\"src\",\"jason:\"+JSON.stringify(e)),document.documentElement.appendChild(n),n.parentNode.removeChild(n),n=null}};";
    [webView stringByEvaluatingJavaScriptFromString:summon];

    This JS creates an iframe that looks like this:

    <iframe src="jason://{\"trigger\": \"some_action\"}">

    (Note that we also make sure the iframe is cleaned up after this function is triggered. If you look at the script it calls removeChild to get rid of the created iframe right after execution)

  3. Then we make sure the webview monitors a URL navigation event. When we encounter a url request attempt that starts with jason://, we extract out the JSON and parse it into dictionary and use it: https://github.com/Jasonette/JASONETTE-iOS/pull/253/files#diff-f22c800ecb5b9ecf974c06bdcb66ad7cR1886

Basically iOS has the same challenge as the challenge you brought up for higher version Androids. There is no way to directly make native calls from webview, and this is how we achieve it.

And since this problem is not unique to Android, I was thinking maybe we could do the same thing for Android.

  1. Create a similar JS logic and inject it into webview, like we do for iOS
  2. Implement shouldOverrideUrlLoading and watch out for the jason:// url, and when we come across one, parse it into JSONObject and use it.

How does that sound?

maks commented 7 years ago

@gliechtenstein sorry about the broken link. I think we may have our wires crossed here...

On Android you can just "native calls" into Java directly. You use addJavascriptInterace() to inject a Java object into the global JS scope of the webview, then html content can call public methods of that object, there is no need at all for the iframe dance you need to use on iOS.

BUT there are constraints on the above, one of them is that the public Java methods you expose can only have "primitives" as parameter types, so you can pass in only numbers, strings, booleans but not JS objects. Hence the need to JSON.stringify().

So on Android its very straightforward to inject a Java object named JASON into the webviews global JS scope, its just not possible to pass it a object as a parameter, but a json string is fine.

To be honest to my mind this seems like a very small requirement to place on anyone making use of this interface, its just it would be nice for it to be consistent across iOS and Android.

lehha commented 6 years ago

Will there are any solution to do calls from JS to JASON?