hampusohlsson / browser-deeplink

Redirect mobile website users to your native iOS and/or Android app
MIT License
493 stars 173 forks source link

Several problems on Android #15

Open qwyzyx opened 8 years ago

qwyzyx commented 8 years ago

Hi! Cool script, but I've been doing investigation on how it interacts with Android and I've found a couple of issues.

First:

if (isAndroid() && !navigator.userAgent.match(/Firefox/)) {

This assumes that all Android browsers other than Firefox handle Intent-style links properly, but this doesn't seem to be the case. For example, in my testing Opera will launch an installed app from an Intent-style URI, but won't properly use either the browser or app-store fallback. So if the app isn't installed, Opera just breaks with a "protocol not supported" page when using Intents. I suspect the right approach is to change this to a positive match with Chrome, rather than a negative match with Firefox.

Second:

var iframe = document.createElement("iframe");
iframe.onload = function() {
    clearTimeout(timeout);
    iframe.parentNode.removeChild(iframe);
    window.location.href = uri;
};

The purpose of this section seems muddy. The iframe.onload handler only ever triggers when the iframe loads a "not supported" error page (and that only on some browsers). So the next step should be to redirect to the store. But instead you're redirecting to the same URI that just failed to load in the iframe.

This actually works in many cases, but sort of by accident. In the newest versions of Chrome, the Intent URI set in the iframe works, and the onload never fires. This is only in the case of a direct click, though... if the redirect happens during page load Chrome usually (although not always) interprets this as not being a "user gesture" and blocks the iframe app launch without firing the iframe onload. It ends up falling back to the timeout to redirect to the store, but that's not ideal since the Intent fallback is instant.

In medium-aged versions of Chrome (~19-40ish, which includes the vendor-specific browsers in many popular modern phones) the iframe load fails immediately and fires the onload, which redirects the window to the failed URI. But these versions of Chrome only fail in the iframe, so once you set the window location to the same thing, the regular Intent behavior kicks in and everything appears to work. So it works, but by accident.

In Firefox the onload is never fired, so this section is being ignored when the app isn't installed, and the timeout is catching it.

This does fail in non-Chrome/FF browsers though. Because when the URI is not Intent-based then this code basically says "if the iframe loads a 'not supported' page, set the window location to this thing we know is not supported". This is guaranteed to break execution and prevent the timeout ever firing. Instead you'd need to set the window to the store link in the onload handler.

But that brings me to the third problem. Once you fix the purpose of the onload handler for non-Chrome browsers to redirect to the store, you then break Chrome 19-40 which refuse to load the Intent URI in the iframe, and are relying on the onload handler to bounce the URI up to the top level for ALL cases, installed and not installed. To fix this you need to either make the iframe redirect conditional (URI if Intents, store link if not Intents) or avoid the iframe approach with Chrome entirely. I'm partial to the second, as it seems to play better with non-user-click cases as noted above.

Finally, it's worth noting that FF doesn't appear to halt script execution when the app is launched, so you need to use a visibilitychange handler or a polling loop on document["hidden"] to detect when it's been backgrounded instead. As it is, it just launches the store every time, even when the app launches successfully.

All together this would take a pretty big overhaul of the script, so I wanted to run it past you in an issue and see what you think.

dgcoffman commented 8 years ago

iOS 9.2 is a killer for the approach taken by this repo. It seems detecting that an app is not installed leaks information in a way that Apple and Google oppose.

It would be nice to have a package that provides the hodgepodge of hacks and workarounds necessary to open a mobile app from a web page for all mobile OS, OS versions, browsers, and browser versions.

I haven't found one or succeeded in writing my own.

hampusohlsson commented 8 years ago

@qwyzyx @dgcoffman This project was created before iOS 9 came into the picture and other mobile vendors started to upgrade their operating systems. Not sure if there is a solution anymore? I haven't been able to spend any amount time on this open source during the past year, as you guys probably noticed from the issues.. Feel free to contribute and overhaul as much as you want :)

dgcoffman commented 8 years ago

Unfortunately it seems the best you can do is choose to show an error for iOS users who don't have the app installed, or not try to open it at all.

Would love to be proven wrong tho =)