Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
NOTE: This was a great hack in days gone by, but now both Apple and Google have improved their support for custom protocol handlers. Licensed under the WFTPL http://www.wtfpl.net/txt/copying/
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>App Redirection</title>
</head>
<body>
<!--
NOTE: This was a great hack in days gone by, but now both Apple and Google have improved their support for custom
protocol handlers.
# References
* Handle Open URL: http://handleopenurl.com/
* iOS Smart App Banners: https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html
* Android Intents: https://developer.android.com/reference/android/content/Intent.html
* On the desktop you still need to make a custom plugin to do the right thing, or save some sort of user prefernce.
-->
<!-- iframe used for attempting to load a custom protocol -->
<iframe style="display:none" height="0" width="0" id="loader"></iframe>
<script>(function(){
// For desktop browser, remember to pass though any metadata on the link for deep linking
var fallbackLink = 'http://example.com/my-web-app/'+window.location.search+window.location.hash;
// Simple device detection
var isiOS = navigator.userAgent.match('iPad') || navigator.userAgent.match('iPhone') || navigator.userAgent.match('iPod'),
isAndroid = navigator.userAgent.match('Android');
// Mobile
if (isiOS || isAndroid) {
// Load our custom protocol in the iframe, for Chrome and Opera this burys the error dialog (which is actually HTML)
// for iOS we will get a popup error if this protocol is not supported, but it won't block javascript
document.getElementById('loader').src = 'custom-protocol://my-app'+window.location.search+window.location.hash;
// The fallback link for Android needs to be https:// rather than market:// or the device will try to
// load both URLs and only the last one will win. (Especially FireFox, where an "Are You Sure" dialog will appear)
// on iOS we can link directly to the App Store as our app switch will fire prior to the switch
// If you have a mobile web app, your fallback could be that instead.
fallbackLink = isAndroid ? 'https://play.google.com/store/apps/details?id=com.mycompany.myapp' :
'itms-apps://itunes.apple.com/app/my-app/idxxxxxxxx?mt=8' ;
}
// Now we just wait for everything to execute, if the user is redirected to your custom app
// the timeout below will never fire, if a custom app is not present (or the user is on the Desktop)
// we will replace the current URL with the fallbackLink (store URL or desktop URL as appropriate)
window.setTimeout(function (){ window.location.replace(fallbackLink); }, 1);
/*
Q&A
I have a native desktop app as well, how do I link to a custom protocol handler on the desktop?
IE Only: http://msdn.microsoft.com/en-us/library/ms537512.aspx#Version_Vectors
All Other Browsers: Use a custom plugin like iTunes does: http://ax.itunes.apple.com/detection/itmsCheck.js
*/
})();</script>
</body>
</html>
@sivicencio
Copy link

sivicencio commented Apr 29, 2013

This script really helped me to get a job done. An iframe was the key. Thanks!

@jcaveman
Copy link

jcaveman commented Jul 1, 2013

So far I can only get this to work in the non-Chrome default Android browser (pre Jelly Bean). In Chrome, the custom protocol is not picked up by the app when I set it as the source of the iframe. Any thoughts?

@cjse
Copy link

cjse commented Oct 24, 2013

This looks great, any chance you could add some license information to the gist?

@jimadamsss
Copy link

jimadamsss commented Jan 16, 2014

This method is similar to others I have seen but it has a major flaw in that the timer will still run when you come back to the page later. Thus you always end up on your fallback page which is, in the example, the app store. Not quite the best user experience. It would be better if we could figure out how to kill the timer totally once it complete's once.

@martin-sweeny
Copy link

martin-sweeny commented Jul 7, 2014

This answer on SO describes one way to avoid the problem @jimadamsss describes

@bsurtz
Copy link

bsurtz commented Jul 22, 2014

Did anyone find a fix for the =Chrome issue? thanks

@aaron-schneider
Copy link

aaron-schneider commented Aug 7, 2014

@bsurtz - Check out the Chrome page. You have to use something different for Chrome 25 and later.

@colaboradores
Copy link

colaboradores commented Sep 17, 2014

thanks for your comment @aaronschneiderbelkin. I'm now using two separated links:

  • procotol://host/ (for iOS)
  • intent://host/#Intent;scheme=protocol;package=com.domain.apppackage;end (for Android browsers)

On the end, I have something like this:

function openApp() {
            var isiOS = navigator.userAgent.match('iPhone') || navigator.userAgent.match('iPod');
            var isAndroid = navigator.userAgent.match('Android');

            if (isiOS) {
                document.getElementById('loader').src = "{{ mobileURI }}";
            }
            else if (isAndroid) {
                window.location = "{{ androidIntentURI }}";
            }
        }

        (function(){
            openApp();
        })();

@rahuljodhani
Copy link

rahuljodhani commented Oct 18, 2014

Hello colaboradores,
Iphone is working fine, but in android i want to check only the application is installed or not, in your code if not installed then redirect to play store. Is there any way to stay there if application is not installed

@fengyuanyang
Copy link

fengyuanyang commented Nov 3, 2014

I wonder same solution as rahuljodhani!!
any possibility to use intent and not redirect to google play if application is not installed???

@andresmafra
Copy link

andresmafra commented Jan 23, 2015

As our friends rahuljodhani and fengyuanyang asked, can we checked if the app is instaled on Android and iOS?

@delconis
Copy link

delconis commented Apr 17, 2015

For Chrome I have only found the following 2 ways to open the app

window.location = "customprotocol://"
window.location.assign('customprotocol://')

If the app is not installed it will land you on a About:Blank or a page that says the protocol isn't registered / does not exist.

What I found to work is setting the window.location to my protocol and then doing a settimeout for a very short time 5ms or less that will attempt the fallback url such as the apps Google Play listing. The issue I still encounter with this is the app will launch and the Default App launcher is started 50% of the time behind the app that just opened and the other 50% in front of the app asking what I want to do with the google play URL as in Open in the Google Play app or use the webbrowser.

While that works it disrupts UX which isn't good.

@delconis
Copy link

delconis commented Apr 17, 2015

@andresmafra You can via native code but via javascript I have not found a way.

@anil1712
Copy link

anil1712 commented May 5, 2015

I am getting the same issue @delconis in android (till KitKat) but in Andoird Lollipop version its not working any more. Its not opening our application first, its was asking to open the play store url and there is no way to skip this step. Please give me some solution for this.

FYI: I have tested it in Moto G 2nd generation(Lollipop)

@cedriclombardot
Copy link

cedriclombardot commented Jun 10, 2015

Hi, I have an issue with safari, when my app is not installed, i got an alert saying can't open page before redirecting to store url

@YogendraChauhan
Copy link

YogendraChauhan commented Jul 10, 2015

Hi Cedriclombardot,
I have modified the above code, which works fine in all mobile and desktop browsers including chrome & it won't give you any alert msg, kindly use the below piece of code 😄

<!doctype html>

<title>App Redirection</title> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script> <script> function openApp() { //see if our window is active window.isActive = true; $(window).focus(function() { this.isActive = true; }); $(window).blur(function() { this.isActive = false; });
    // For desktop browser, remember to pass though any metadata on the link for deep linking
    var fallbackLink = 'your website url';
    // Simple device detection
    var isiOS = navigator.userAgent.match('iPad') || navigator.userAgent.match('iPhone') || navigator.userAgent.match('iPod'),
        isAndroid = navigator.userAgent.match('Android'), isWindow = navigator.userAgent.match('Windows Phone');
        // Mobile
        if (isiOS || isAndroid || isWindow) {
            // Load our custom protocol in the iframe, for Chrome and Opera this burys the error dialog (which is actually HTML)
            // for iOS we will get a popup error if this protocol is not supported, but it won't block javascript

            var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
            if (isMobile) {
                var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
                var isSafari = /Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor);
                if (isChrome){
                    window.location  = 'custom-protocol://my-app';  
                }else
                {
                    document.getElementById('loader').src = 'custom-protocol://my-app';
                }
            }

            // The fallback link for Android needs to be https:// rather than market:// or the device will try to 
            // load both URLs and only the last one will win. (Especially FireFox, where an "Are You Sure" dialog will appear)
            // on iOS we can link directly to the App Store as our app switch will fire prior to the switch
            // If you have a mobile web app, your fallback could be that instead. 
                  fallbackLink = isAndroid ? 'https://play.google.com/store/apps/details?id=com.mycompany.myapp' :
                             'itms-apps://itunes.apple.com/app/my-app/idxxxxxxxx?mt=8' ;
        }

        // Now we just wait for everything to execute, if the user is redirected to your custom app
        // the timeout below will never fire, if a custom app is not present (or the user is on the Desktop)
        // we will replace the current URL with the fallbackLink (store URL or desktop URL as appropriate)
        window.setTimeout(function (){
            if (window.isActive) { 
                window.location.replace(fallbackLink);
            } 
        }, 1000);
        /*
          Q&A
        I have a native desktop app as well, how do I link to a custom protocol handler on the desktop?
        IE Only: http://msdn.microsoft.com/en-us/library/ms537512.aspx#Version_Vectors
        All Other Browsers: Use a custom plugin like iTunes does: http://ax.itunes.apple.com/detection/itmsCheck.js
        */
    };

    window.onload = function()
    {
        var button = document.getElementById("button");
        button.onclick = function()
        {
            openApp();
        };
    };
</script> <style type="text/css"> #button { border:1px solid #7c5b2b; -webkit-border-radius: 3px; -moz-border-radius: 3px;border-radius: 3px;font-size:12px;font-family:arial, helvetica, sans-serif; padding: 10px 10px 10px 10px; text-decoration:none; display:inline-block;text-shadow: -1px -1px 0 rgba(0,0,0,0.3);font-weight:bold; color: #FFFFFF; background-color: #a67939; background-image: -webkit-gradient(linear, left top, left bottom, from(#a67939), to(#845108)); background-image: -webkit-linear-gradient(top, #a67939, #845108); background-image: -moz-linear-gradient(top, #a67939, #845108); background-image: -ms-linear-gradient(top, #a67939, #845108); background-image: -o-linear-gradient(top, #a67939, #845108); background-image: linear-gradient(to bottom, #a67939, #845108);filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=#a67939, endColorstr=#845108); } </style> <iframe style="display:none" height="0" width="0" id="loader"></iframe>
Open App

@mdeora
Copy link

mdeora commented Sep 19, 2015

doesn't work with iOS9

@luigisaggese
Copy link

luigisaggese commented Sep 22, 2015

iOS 9 fail 👎

@curbsaleem
Copy link

curbsaleem commented Sep 28, 2015

doesn't work with iOS9

@ebsaral
Copy link

ebsaral commented Oct 27, 2015

document.getElementById('loader').src = '{{ url }}';

this does not work in iOS 9

@jonesmac
Copy link

jonesmac commented Jun 9, 2017

This mostly worked for me...major gotcha for iOS is that you cannot start your custom protocol with a number. Evidently that get's url encoded and the redirect will fail since it would find that registered with an app. Specifically, I noticed that 9 gets reencoded as %39

@maquannene
Copy link

maquannene commented Mar 12, 2018

Safair has already not support call up app by iframe after iOS9.

@yashwanthkumar1796
Copy link

yashwanthkumar1796 commented Oct 24, 2019

Safair has already not support call up app by iframe after iOS9.

@maquannene did you find any solution for this ??

@yashwanthkumar1796
Copy link

yashwanthkumar1796 commented Oct 24, 2019

document.getElementById('loader').src = '{{ url }}';

this does not work in iOS 9

Hi, Did you find any solution for this ??

@quantumpotato
Copy link

quantumpotato commented Apr 14, 2021

How do you pass data to the appstore - to the itms link?

@davidwkeith
Copy link
Author

davidwkeith commented Apr 14, 2021

@quantumpotato You really shouldn't be using this hack anymore. Use Smart App Banners on iOS and Intents on Android.

@quantumpotato
Copy link

quantumpotato commented Apr 16, 2021

@davidwkeith can you pass data to be loaded by the app when it boots up with smart banners?

@davidwkeith
Copy link
Author

davidwkeith commented May 18, 2021

yes, just provided an app-argument in the content parameters of the Smart App Banner meta tag.

See the official documentation here https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners

@quantumpotato
Copy link

quantumpotato commented May 18, 2021

Thank you

@jjj201200
Copy link

jjj201200 commented Dec 25, 2021

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment