Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Launch app if installed or redirect to download

On every repo, GitHub has a button that is labelled "Clone in Desktop" (example: https://github.com/github/developer.github.com). If you have GitHub for Mac installed, the href is something like:

github-mac://openRepo/https://github.com/github/developer.github.com

This opens GitHub for Mac and offers to clone the repo. If you don't, the href is:

http://mac.github.io`

This is a download page for GitHub for Mac. I would like to do something similar on my website: open my app if installed and redirect to download if not. How can this be best accomplished?

like image 427
Stack Overflown Avatar asked Feb 08 '15 01:02

Stack Overflown


1 Answers

How does GitHub do it?

The GitHub for Mac client includes a local service called GitHub Conduit that runs in the background. The GitHub page communicates with this service through the the URL https://ghconduit.com:25035/status.

Excerpt from the GitHub Conduit help page:

For example, Conduit is behind the Clone in Desktop button on repository pages and the Open button on file pages. Conduit listens for queries from the browser about GitHub for Mac actions. To see this in action, visit https://ghconduit.com:25035/status. It should look something like this:

{"capabilities":["status","unique_id","url-parameter-filepath"],"running":true,"server_version":"5"}

If you have the GitHub Conduit service running, JavaScript on the page fetches data from this URL and give the Clone in Desktop button a github-mac:// URL. Otherwise, the URL returns a 404 response and it assumes you do not have GitHub for Mac installed, and gives you the link to download it.


How to implement functionality like this?

Unfortunately, there is no JavaScript API to do this in the browser. Protocols the browser does not recognize are handled by the OS itself. I tried my best, but I was only able to hack-up a decent JavaScript-only solution for Firefox on Mac, and an ugly half-baked solution for Safari. Both hacks hinge on undefined behavior, and neither work for Chrome. You can see my research code below.

If you want to do it the GitHub way, you will have to create a local HTTP server that runs as a service over a known port on your user's machines. Then you can use JavaScript to connect to it and retrieve information about the installed application. Doing this would not be trivial, and unless it provides some amazing functionality, I would advise against doing this.

The JavaScript code to do this would be fairly simple though. Assuming you return the appropriate CORS headers, you could just make a simple AJAX request. Here's a jQuery-based example.

$.ajax({
    url: 'http://127.0.0.1:1337',
    dataType: 'json',
    success: function(jqXHR) {
        //Replace links to app protocol URLs.
    }
});


Research Code:

The following code is my super-hacky and rather fragile code for Firefox and Safari. While it is working on my end, I make absolutely no guarantee that it will work as expected, or will continue to work in the future. It relies on browser-specific undefined behavior, and should be considered unstable. I also have no idea what this code will do on non-OS X systems.

Firefox:

This code relies on opening the link in an iframe that will trigger an error when a protocol is unrecognized (on success it will open the URL as normal).

function openAppFirefox(url, failure) {
    var iframe = document.createElement('iframe');
    //Firefox will fire an error if protocol fails to open.
    iframe.onerror = function() {
        failure();
    };
    //Hide the iframe.
    iframe.style.width = 0;
    iframe.style.height = 0;
    iframe.style.visibility = "hidden";
    iframe.style.position = "fixed";
    //Load the URL.
    iframe.src = url;
    document.body.appendChild(iframe);
    //Clean up the iframe.
    setTimeout(function() {
        document.body.removeChild(iframe);
    }, 1000);
}

Example Usage:

//Will work.
//var url = 'itmss://itunes.apple.com/us/app/stack-exchange/id871299723';
//Will fail.
var url = 'wat://bummer';
someElment.addEventListener('click', function() {
    openAppFirefox(url, function() {
        alert('Download my app!');
    });
});

Safari:

This code relies on opening the URL in a new tab, whose win.location.href will before undefined within a second (but probably less time) if the URL was not recognized. The "There is no application set to open the URL" dialog will still open if it fails to open the protocol unfortunately.

function openAppSafari(url, failure) {
    var win = window.open(url);
        var done = function(failed) {
            win.close();
            clearInterval(checkFail);
            clearTimeout(giveup);
            if (failed) {
                failure();
            }
        };
        //Chck for failure.
        var checkFail = setInterval(function() {
        //In Safari, location.href becomes undefined on failure.
        if (!win.location.href) {
            done(true);
        }
    });
    //After a second, assume success.
    var giveup = setTimeout(function() {
        done(false);
    }, 1000);
}

Example Usage:

//Will work.
//var url = 'itmss://itunes.apple.com/us/app/stack-exchange/id871299723';
//Will fail.
var url = 'wat://bummer';
someElment.addEventListener('click', function() {
    openAppSafari(url, function() {
        alert('Download my app!');
    });
});
like image 97
Alexander O'Mara Avatar answered Sep 21 '22 00:09

Alexander O'Mara