18
votes

I have web page where I have Button that either opens app (if it installed) or directs to App store if app isn't installed. It all works if App is installed (I call into "MYAPP://"). However, if app is not installed Safari shows error message "Can not open URL" and that's it. Is there way to disable that message from JScript or is there another way to find out from JScript if app installed (instead of hitting app URL)?

To MODERATOR: I saw someone asked similar question and Moderator wrongly marked it as duplicate. Please understand that question was specifically about doing it from Browser.

Found somewhat suitable solution here

BTW if someone interested in how to do same thing for Android, here is code. We are using Dojo library:

  dojo.io.iframe.send({
    url: "yourApp://foo/bar",
    load: function(resp) {
      // nothing to do since it will automagically open App
    },
    error: function () {
      window.location = "go to Android market";
    }
  });
5

5 Answers

14
votes

At Branch we use a form of the code below--note that the iframe works on more browsers. Simply substitute in your app's URI and your App Store link. By the way, using the iframe silences the error if they don't have the app installed. It's great!

<!DOCTYPE html>
<html>
    <body>
        <script type="text/javascript">
            window.onload = function() {
                // Deep link to your app goes here
                document.getElementById("l").src = "my_app://";

                setTimeout(function() {
                    // Link to the App Store should go here -- only fires if deep link fails                
                    window.location = "https://itunes.apple.com/us/app/my.app/id123456789?ls=1&mt=8";
                }, 500);
            };
        </script>
        <iframe id="l" width="1" height="1" style="visibility:hidden"></iframe>
    </body>
</html>

If others have better solutions to detect whether the URI scheme call actually failed, please post! I haven't seen one, and I've spent a ton of time looking. All existing solutions just rely on the user still being on the page and the setTimeout firing.

4
votes

here is a code that works on iOs, even if the "Can not open URL" still show.

    window.location = "yourApp://foo/bar";      
    clickedAt = +new Date;
    setTimeout(function() {
        if (+new Date - clickedAt < 2000) {
            window.location = "go to Android market";
        }
    }, 500);

Thanks for the android solution.

0
votes

I think you can still use the app url as a test. Try wrapping it in a try...catch block,

try {
  //run code that normally breaks the script or throws error
}
catch(e) {
  //do nothing
}
0
votes

I've combined a few things and used the following code to check if it's an iOS device before using the try/catch method from chazbot. Unfortunately, the device still throws a pop-up box to the user saying the address is invalid...anyone know if this is expected behavior for trying to open an invalid URL within a "try" block?

    var i = 0,
    iOS = false,
    iDevice = ['iPad', 'iPhone', 'iPod'];

    for ( ; i < iDevice.length ; i++ ) {
        if( navigator.platform === iDevice[i] ){ iOS = true; break; }
    }

    try {
        //run code that normally breaks the script or throws error
        if (iOS) { window.location = "myApp://open";}
    }
    catch(e) {
        //do nothing
    }
0
votes

There are a few things you can do to improve on other answers. Since iOS 9, a link can be opened in a UIWebView or in a SFSafariViewController. You might want to handle them differently.

The SFSafariViewController shares cookies across apps, and with the built in Safari. So in your app you can make a request through a SFSafariViewController that will set a cookie that says "my app was installed". For instance you open your website asking your server to set such cookie. Then anytime you get a request from a SFSafariViewController you can check for that cookie and redirect to MYAPP:// if you find it, or to the app store if you don't. No need to open a webpage and do a javascript redirection, you can do a 301 from your server. Apps like Messages or Safari share those cookies.

The UIWebView is very tricky since it is totally sandboxed and shared no cookies with anything else. So you'll have to fallback to what has been described in other answers:

window.onload = function() {
  var iframe = document.createElement("iframe");
  var uri = 'MYAPP://';
  var interval = setInterval(function() {
      // Link to the App Store should go here -- only fires if deep link fails                
      window.location = "https://itunes.apple.com/us/app/my.app/id123456789?ls=1&mt=8";
  }, 500);
  iframe.onload = function() {
      clearInterval(interval);
      iframe.parentNode.removeChild(iframe);
      window.location.href = uri;
  };
  iframe.src = uri;
  iframe.setAttribute("style", "display:none;");
  document.body.appendChild(iframe);  
};

I've found it annoying that this will prompt the user if they want to leave the current app (to go to your app) even when your app is not installed. (empirically that seems only true from a UIWebView, if you do that from the normal Safari for instance that wont happen) but that's all we got!

You can differentiate the UIWebView from the SFSafariViewController from your server since they have different user agent header: the SFSafariViewController contains Safari while the UIWebView doesn't. For instance:

Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E269
-> UIWebView

Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E269 Safari/602.1
-> SFSafariViewController

Other considerations:

  • in the first approach, you might want to handle uninstalls: if the user uninstalls your app, you still have a cookie that says that the app is there but it's not, so you might end up with the "Can not open URL" message. I've handled it by removing the cookie after a few tries that didn't end up opening the app (which I know because at every app open I'm resetting this failed tries cookie)
  • In the second case, it's unclear if you're better off using a setInterval or setTimeout. The issue with the timeout is that if it triggers while a prompt is on, it will be ignored. For instance if you open the link from Messenger, the os will ask you "Leave Messenger? You're about to open another app" when the iframe tries to load your app. If you don't respond either way within the 500ms of the timeout, the redirection in the timeout will be ignored.
  • Finally even though the UIWebView is sandboxed, you can give it a cookie to identify it, pass it in your deeplink, and save this id as corresponding to device with your app in your server when your app opens. Next time, if you see such a cookie in the request coming from the UIWebView, you can check if it matches a known device with the app and redirect directly with a 301 as before.