GoogleChrome / android-browser-helper

The Android Browser Helper library helps developers use Custom Tabs and Trusted Web Activities on top of the AndroidX browser support library.
Apache License 2.0
700 stars 289 forks source link

Add a postMessage demo. #56

Open PEConn opened 4 years ago

PEConn commented 4 years ago

Using postMessage is pretty confusing, we should add a demo for it.

(This is for CCTs, not for TWAs)

andreban commented 4 years ago

My suggestion is to prefix the demo with cct, to keep it consistent with the TWA demos, which are prefixed with twa. Eg: cct-postmessage.

FluorescentHallucinogen commented 4 years ago

@andreban @PEConn Any news on postMessage support in TWA? This is an essential (and one of the most requested) feature. See my rationale here: https://github.com/GoogleChromeLabs/svgomg-twa/issues/84.

andreban commented 4 years ago

We don't have an update on postMessage for Trusted Web Activity.

Even though we constantly re-evaluate postMessage for Trusted Web Activity, the overall the goal is to use Web Platform APIs where one is available or planned. Where a capability is not available / planned, we're looking into adding those to the platform.

The advantage here is that by doing it this way we ensure implementations will work the same way across platforms and browsers and avoid a situation where developers need to have multiple codebases. This does indeed take longer than just adding postMessage but is better in the long-term.

Nevertheless, we're looking for feedback for features and capabilities that might not make sense as Web APIs but would make sense as postMessage.

Project Fugu already has issues open for both geofencing and geotracking, the use cases mentioned on the rationale. The Chrome issue for geofencing is crbug.com/898533 and the issue for geotracking is crbug.com/898536

@b1tr0t FYI

FluorescentHallucinogen commented 4 years ago

The advantage here is that by doing it this way we ensure implementations will work the same way across platforms and browsers and avoid a situation where developers need to have multiple codebases. This does indeed take longer than just adding postMessage but is better in the long-term.

TWA is an Android app, so there is only one codebase (native Android code + Web code).

TWA is not only for Chrome. Other browsers like Firefox may implement some Web APIs unacceptably long or even not implement at all, which lead to bad UX and fragmentation (multiple codebases for different browsers, but that use kludges instead of postMessage).

ghost commented 3 years ago

It is now a year later and Web API's are still limiting development in many ways. If it is a Trusted Web Activity, then it seems to me that Android should trust the website postMessage's completely and expose all of Android's API's, a place you (Google) can exert API control outside of Chrome.

My use case is that I am implementing a Suicide Prevention PWA. To place a call to the national suicide hotline, I have to go through the WebAPIs by rendering html like the following: <a href="tel:18002738255">Call Hotline</a>. This does issue a proper call prompt to the user, but because you've exerted so much control over the WebAPI, the user has to click the link, perhaps click "Always" or "Just once" to confirm which phone app they want to use, and manually place the call in their phone app. These extra barriers likely cause "call abandonment" that may contribute to suicide. I would much prefer the ability to eagerly and automatically place a call to the hotline without multiple layers of confirmation by calling native code and falling back to the WebAPI if I don't get a proper response in a timely manner.

Don't get me wrong, I'd love a WebAPI to come out with a feature that allows calls to happen instantly, but there have to be countless other use-cases like this that one just won't be able to think of, and while I wait for WebAPIs that probably aren't coming for security reasons, I could've launched my trusted app and hopefully saved some lives.

Please reconsider turning postMessage on.

PEConn commented 3 years ago

For your use case, would it not work to have a native Android Activity as part of your TWA? The link on your webpage could then be to a custom scheme that your Activity has an Intent filter for (more on that here).

When your Activity is launched, you can then place your call directly - taking us down to just a single click to place the call (if your app also has the android.permission.CALL_PHONE permission).

You'll have to add some logic during the launch process to detect whether or not the device you're on can handle phone calls (eg, make sure it's not a tablet) and pass that along to your website.

ghost commented 3 years ago

The presence of a workaround implies the presence of a bug, or at least lack of feature, but thank you nonetheless! I'll give that a try.

arash16 commented 3 years ago

@andreban It's already possible for native and browser to talk, by creating a localhost server on native-side, and connecting to it with websockets from browser side (I did a simple test with Ktor). It's not hard to abstract it away to behave like a real postMessage, but needs extra care to be secure and not letting some other website to talk with that server.

Also there are legitimate use cases that cannot be implemented with WebApis. My use-case is to implement native push messages (with all capabilities that android provides, specifically message-style notifications and notifications grouping), and once user clicks on a notification, I need to open a modal without reloading the page (it's a SPA). And I don't like to maintain a completely separate android project for such enhanced user experience.

Right now it's super hard to even share fcm_token inside native-code with web (native talks to fcm, but browser authenticates the user, both tokens needs to be sent to our server alongside each other). To connect these two there are many solutions, each with it's own issues:

  1. Provide fcm_token as a query-string: it becomes an issue if an attacker sends a link containing his own fcm_token to another user. To prevent that we have to check for document.referrer and make sure fcm_token will be stripped-off from deep-links once opened and only provided by native code. And yet still it's possible within the app to give such links to other users (it's a messenger) which needs much more care to prevent.

  2. To pass user's authentication token to native-code through intents: Such intents are already android-specific, and needs a lot of edge case handling to check if device is android and browser supports such intents and check if we are running as twa, and it's our own verified pwa (using something like getInstalledRelatedApps).

  3. Pass fcm_token through http-headers: not secure on older chrome browsers where another app could do the same without being verified, also since it's a SPA, it needs to be handled through service worker trickeries (handling navigation routes and mirroring the token from request back to response).

And all such messes and workarounds just because there's no postMessage. People are already falling into such security issues because there's no easy and safe way for native and browser to talk.

What's the point of two sides trusting each other, but having no easy means to talk to each other? Someone who needs something, he finds the way and he won't care to have multiple codebases. But through illegitimate means where it could lead to security issues.

beerphilipp commented 2 years ago

Has there been any progress on a demo for using postMessage in CCTs?

anurao commented 2 years ago

Any update on postMessage with CCT? Or any hints on how to get this to work? I feel like maybe I am missing something obvious, but the documentation does not make the order of steps clear.

anurao commented 2 years ago

Wanted to pick this back up - so we have setup up digital assets links and got the verification working but it looks like the post message channel is still not created. Any pointers what else might be missing?

robertwt7 commented 1 year ago

hey all, i'm also just wondering if its as simple as calling window.postMessage() on the website opened within the CustomTab ?

The way i handled this previously is by calling MyWebView.postMessage() via a js interface that i injected to the webview but now i'm just planning to use CustomTabs

class MyWebViewJavascriptInterface constructor(
  private val webEvents: Channel<MyWebViewBridgeEvent>,
) {
  @android.webkit.JavascriptInterface @JvmOverloads
  fun postMessage(data: String? = null, origin: String? = null) {
    data?.let {
      webEvents.trySend(MyWebViewFlowJsonEvents(data = it)).getOrThrow()
    }
  }

  companion object {
    const val INTERFACE_NAME = "MyWebView"
  }
}
anurao commented 1 year ago

For what's it's worth our team got it working but found it did not work with Samsung Browser or Firefox. Unfortunately using custom tabs has just been an exercise in finding which browsers don't support features.

RoLfBOT commented 1 year ago

@anurao can you post a demo solution as to how you achieved communication from native to web and vice versa (if any)? The app we are building will only support chrome and edge for now so if your solution does work it will really benefit us.

Thanks :)

bdhanoa commented 1 year ago

We don't have an update on postMessage for Trusted Web Activity.

Even though we constantly re-evaluate postMessage for Trusted Web Activity, the overall the goal is to use Web Platform APIs where one is available or planned. Where a capability is not available / planned, we're looking into adding those to the platform.

The advantage here is that by doing it this way we ensure implementations will work the same way across platforms and browsers and avoid a situation where developers need to have multiple codebases. This does indeed take longer than just adding postMessage but is better in the long-term.

Nevertheless, we're looking for feedback for features and capabilities that might not make sense as Web APIs but would make sense as postMessage.

Project Fugu already has issues open for both geofencing and geotracking, the use cases mentioned on the rationale. The Chrome issue for geofencing is crbug.com/898533 and the issue for geotracking is crbug.com/898536

@b1tr0t FYI

There is still no movement on the functionality to support geofencing in Chrome in the last three years. This is a requirement for my project. Is there a workaround or any movement on supporting postMessage in TWAs? Please advise.

b1tr0t commented 1 year ago

Hi! Good news, postMessage will be supported in chrome 115. You should be able to start testing in dev channels now. I'll check on docs status.

bdhanoa commented 1 year ago

Hi! Good news, postMessage will be supported in chrome 115. You should be able to start testing in dev channels now. I'll check on docs status.

Thank you for the update. If possible, please direct me to documentation on how to use postMessage in a TWA.

RoLfBOT commented 1 year ago

Hi @b1tr0t any update on the postMessage documentation? I see chrome 115 is already out in production.

FluorescentHallucinogen commented 1 year ago

@RoLfBOT See https://developer.chrome.com/articles/post-message-twa/. šŸ˜‰

jeffsmale90 commented 1 year ago

I am confused by what seems to be a lot of conflicting information on this: is it possible to use postMessage without assetlinks.json?

The blog linked above implies yes.

andreban commented 1 year ago

The post says assetlinks.json is required:

In order for the postMessage to work it requires a valid relationship between a website and the Trusted Web Activity app launching this site, this can be done with Digital Asset Links (DAL) by adding the appā€™s package name in your assetlinks.json

jeffsmale90 commented 1 year ago

Yeah, but it also says that you can communicate with any other sites šŸ¤”

Note that setup on the origin associated with the TWA, it is required to provide an origin for theĀ MessageEvent.originĀ field, butĀ postMessageĀ can be used to communicate with other sites that donā€™t include the Digital Assets Link. For example, if you ownĀ www.example.comĀ you will have to prove that through DAL but you can communicate with any other websites,Ā www.wikipedia.orgĀ for example.

jeffsmale90 commented 1 year ago

My confusion comes from the distinction between TWA and Custom tabs.

My understanding is that this issue was to create example code of how to use postMessage with Custom tabs not Trusted Web Activity, and that the Assetlinks is required to validate the relationship for the TWA.

Am I missing something here?

PEConn commented 1 year ago

You need Digital Asset Links both for postMessage and for TWAs, and in each case it works slightly differently.

For TWAs, you need a Digital Asset Link relationship set up with the site you want to display in your TWA. If this verification fails (or the user navigates to an unverified origin) the top bar will be shown again.

For postMessage, you need a Digital Asset Link relationship set up with a site in order to provide an origin for the message to come from. So for example, I could:

  1. Set up a Digital Asset Link with example.com.
  2. Load wikipedia.org.
  3. Send wikipedia.org a message.

The MessageEvent.origin of the message that Wikipedia gets will be example.com.

Now, if you're using postMessage for your TWA the distinction doesn't matter too much - you'll be setting up Digital Asset Links with your site anyway, and the messages from the Android app will have your own site as the origin.

But for the Custom Tabs case (or if you're doing something really creative with TWAs), you still need a Digital Asset Link set up so that we can represent your message as coming from an origin that you prove you are associated with.

jeffsmale90 commented 1 year ago

You need Digital Asset Links both for postMessage and for TWAs, and in each case it works slightly differently.

For TWAs, you need a Digital Asset Link relationship set up with the site you want to display in your TWA. If this verification fails (or the user navigates to an unverified origin) the top bar will be shown again.

For postMessage, you need a Digital Asset Link relationship set up with a site in order to provide an origin for the message to come from. So for example, I could:

  1. Set up a Digital Asset Link with example.com.
  2. Load wikipedia.org.
  3. Send wikipedia.org a message.

The MessageEvent.origin of the message that Wikipedia gets will be example.com.

Now, if you're using postMessage for your TWA the distinction doesn't matter too much - you'll be setting up Digital Asset Links with your site anyway, and the messages from the Android app will have your own site as the origin.

But for the Custom Tabs case (or if you're doing something really creative with TWAs), you still need a Digital Asset Link set up so that we can represent your message as coming from an origin that you prove you are associated with.

Thanks so much for the clarification. That's super helpful.

jeffsmale90 commented 1 year ago

@PEConn thanks again for the clarification.

My use case is that we are publishing an SDK for android, that opens a website that we (SDK publisher) owns and controls and needs to communicate between the android app and webapp.

Because we don't own the published android app (and hence don't control the publisher key) it seems to me very clear that this will not work for us. Is that your view also?

Do you know of an alternative?

andreas-niemoeller-qhr commented 11 months ago

Unfortunately the demo for TWA PostMessage (including the instructions from this link) appears not to work out of the box for me. DAL relationship seems to be ok since the site is displayed without address bar. Debugging the demo application I noticed that calling requestPostMessageChannel would result in a callback for onRelationshipValidationResult with a false. If I manually request validation through mSession.validateRelationship(CustomTabsService.RELATION_USE_AS_ORIGIN, TARGET_ORIGIN, null); I receive a true validation result but following another requestPostMessageChannel call I get another false callback. I never receive a onMessageChannelReady call. Any clues why this setup might be failing?

tushe-tv commented 9 months ago

Unfortunately the demo for TWA PostMessage (including the instructions from this link) appears not to work out of the box for me. DAL relationship seems to be ok since the site is displayed without address bar. Debugging the demo application I noticed that calling requestPostMessageChannel would result in a callback for onRelationshipValidationResult with a false. If I manually request validation through mSession.validateRelationship(CustomTabsService.RELATION_USE_AS_ORIGIN, TARGET_ORIGIN, null); I receive a true validation result but following another requestPostMessageChannel call I get another false callback. I never receive a onMessageChannelReady call. Any clues why this setup might be failing?

I'm also having the same issue: the requestPostMessageChannel result is true, the onRelationshipValidationResult is false and onMessageChannelReady is not called.

merdok13 commented 9 months ago
            boolean result = mSession.requestPostMessageChannel(SOURCE_ORIGIN, TARGET_ORIGIN,
merdok13 commented 9 months ago
            boolean result = mSession.requestPostMessageChannel(SOURCE_ORIGIN, TARGET_ORIGIN,
merdok13 commented 9 months ago
        boolean result = mSession.requestPostMessageChannel(SOURCE_ORIGIN, TARGET_ORIGIN,
shunxing commented 9 months ago

We have the postMessage demo in here https://developer.chrome.com/docs/android/post-message-twa

However on the function onRelationshipValidationResult, it looks that the result is false which means that it's not validated. Therefore the postMessage cannot be used.

Any idea why or how to debug that ?

shunxing commented 9 months ago

@PEConn could you give a hand on that ?

I have my DAL that is set : my TWA doesn't show the address bar.

I followed the article here and set everything up https://developer.chrome.com/docs/android/post-message-twa

But I'm having the same issue as @tushe-tv and @andreas-niemoeller-qhr

DanGavrielov commented 8 months ago

I'm also having the same issue as @shunxing and the others, any updates on this?

DanGavrielov commented 8 months ago

I finally managed to get it to work, I've changed this: boolean result = mSession.requestPostMessageChannel(SOURCE_ORIGIN, TARGET_ORIGIN, new Bundle()); to this: boolean result = mSession.requestPostMessageChannel(TARGET_ORIGIN);

After this change the message channel is created and postMessage works, I don't really understand why Can someone please explain this behavior? @PEConn

mattdsteele commented 8 months ago

@DanGavrielov What value do you have set for TARGET_ORIGIN? And how are you verifying postMessage works - are you using the demo pointing to https://peconn.github.io, or are you trying it with an app of your creation?

My understanding between the two postMessage APIs is the the one with three parameters (source, target, bundle) more closely reflects how the API is intended to work - a postMessage should have the source defined, so the recipient of the message can verify the request is coming from a known domain. https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsSession#requestPostMessageChannel(android.net.Uri,android.net.Uri,android.os.Bundle)

https://android-review.googlesource.com/c/platform/frameworks/support/+/2594270

DanGavrielov commented 8 months ago

@mattdsteele I am currently researching TWAs as an alternative to WebView for a client, I have set up a tiny Ktor server and testing a demo app i created pointing to it the TARGET_ORIGIN is the site's address, basically the URL i am launch through the TWA After the change I can send and receive data

Further attempts show that this also works:

val result = mSession?.requestPostMessageChannel(targetOrigin, targetOrigin, Bundle())

but this does not:

 // i'm using my actual package name in my project
val sourceOrigin = Uri.parse("https://com.my.package.name")
val result = mSession?.requestPostMessageChannel(sourceOrigin, targetOrigin, Bundle())

I do understand the intended API is probably for the TWA to be able to identify a message from the app, but it just doesn't work for me using the code in the demo app, the TWA is presented with no address bar, so the DAL is working correctly but creation of a message channel always fails, and I have no idea why.

mattdsteele commented 8 months ago

@DanGavrielov Thanks for the context. Unfortunately my demo app still fails to send messages to the TWA, either using the requestPostMessageChannel(targetOrigin, targetOrigin, Bundle()), or requestPostMessageChannel(targetOrigin).

In my demo app, I have made some progress; my onRelationshipValidationResult callback returns a result of true, and I can invoke postMessage('data', null) and it returns a value of [RESULT_SUCCESS](https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsService#RESULT_SUCCESS()). However, I still can't see postMessage events in the TWA; using the window.addEventListener("message", callback) code as described on https://developer.chrome.com/docs/android/post-message-twa#communicating_from_the_web

If you're able, would you be able to post your demo app to a new Github repo (with any sensitive data scrubbed), so folks could compare your working example with theirs?

DanGavrielov commented 8 months ago

@mattdsteele sure here you go

A simple Ktor server i have built to serve some website to test against: https://github.com/DanGavrielov/Ktor-PWA-Server-Demo/

A simple demo Android TWA application: https://github.com/DanGavrielov/TWA-Application-Demo/

If you use this code don't forget to replace all placeholders throughout the code with you specific information Hope this help :)

mattdsteele commented 8 months ago

Thanks @DanGavrielov! For posterity, I was able to get this working as well with my site's specific information; and it helped me figure out the issue in my codebase (in the web code, you need to add the message eventListener prior to the Android app calls session.requestPostMessageChannel; if the listener is added later, it won't capture messages).

aashutoshshr commented 8 months ago

@DanGavrielov I followed your demo, but the onRelationshipValidationResult callback still returns a result of false. Is there anything I need to change?

Screenshot 2024-03-12 at 09 17 17
DanGavrielov commented 8 months ago

@aashutoshshr I don't really know, this whole thing was a trial and error thing I don't really understand it make sure you replace all of the placeholders throughout the code with actual values, if it still doesn't work then maybe support from more knowledgeable people is needed

SayedElabady commented 7 months ago

I finally managed to get it to work, I've changed this: boolean result = mSession.requestPostMessageChannel(SOURCE_ORIGIN, TARGET_ORIGIN, new Bundle()); to this: boolean result = mSession.requestPostMessageChannel(TARGET_ORIGIN);

After this change the message channel is created and postMessage works, I don't really understand why Can someone please explain this behavior? @PEConn

Please use the API that specifies the TARGET_ORIGIN as the other one will be deprecated soon.

There was a bit missing from the comment on the demo, the SOURCE_ORIGIN get checked to be a valid origin and it has to either start with http:// or https:// as these are the supported ones for now, see #439, a51ecb7809534aadbc76de73586bc4ece270767f

DanGavrielov commented 7 months ago

@SayedElabady The code also worked for me when calling:

session.requestPostMessageChannel(TARGET_ORIGIN, TARGET_ORIGIN, Bundle())

Where TARGET_ORIGIN is my web url

It didn't work when trying the same code with my package name starting with https:// like so:

val SOURCE_ORIGIN = "https://com.example.package.name"
session.requestPostMessageChannel(SOURCE_ORIGIN, TARGET_ORIGIN, Bundle())

Do you see any issue with how the SOURCE_ORIGIN is defined?

shunxing commented 6 months ago

@mattdsteele I am currently researching TWAs as an alternative to WebView for a client, I have set up a tiny Ktor server and testing a demo app i created pointing to it the TARGET_ORIGIN is the site's address, basically the URL i am launch through the TWA After the change I can send and receive data

Further attempts show that this also works:

val result = mSession?.requestPostMessageChannel(targetOrigin, targetOrigin, Bundle())

but this does not:

 // i'm using my actual package name in my project
val sourceOrigin = Uri.parse("https://com.my.package.name")
val result = mSession?.requestPostMessageChannel(sourceOrigin, targetOrigin, Bundle())

I do understand the intended API is probably for the TWA to be able to identify a message from the app, but it just doesn't work for me using the code in the demo app, the TWA is presented with no address bar, so the DAL is working correctly but creation of a message channel always fails, and I have no idea why.

That worked for me @DanGavrielov

However I still haven't figured out why it should be like that, why does it mention SOURCE_ORIGIN in the documentation... maybe @SayedElabady you could give us a hand for explaining why using TARGET_ORIGIN as SOURCE_ORIGIN is actually working

zlj1572 commented 3 months ago

@mattdsteele Thanks, that worked for me.