Skip to content

Instantly share code, notes, and snippets.

@davidwkeith
Last active April 10, 2024 12:01
Show Gist options
  • Save davidwkeith/2662899 to your computer and use it in GitHub Desktop.
Save davidwkeith/2662899 to your computer and use it in GitHub Desktop.
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>
@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

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

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

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

@colaboradores
Copy link

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

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

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

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

@delconis
Copy link

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

@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

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

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

iOS 9 fail 👎

@curbsaleem
Copy link

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

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

@yashwanthkumar1796
Copy link

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

@maquannene did you find any solution for this ??

@yashwanthkumar1796
Copy link

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

this does not work in iOS 9

Hi, Did you find any solution for this ??

@quantumpotato
Copy link

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

@davidwkeith
Copy link
Author

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

@quantumpotato
Copy link

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

@davidwkeith
Copy link
Author

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

Thank you

@jjj201200
Copy link

Thank you

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