-->

Launch app if installed or redirect to download

2020-06-17 07:05发布

问题:

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?

回答1:

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!');
    });
});


回答2:

This could be achieved with Javascript or PHP...

For Javascript you have a cool library is.js that you can use for environment checks. See here: http://arasatasaygin.github.io/is.js/#environment-checks

You just create a div in your HTML and than use javascript to populate it with content depending on the result of functions like: is_windows() or !is_windows()

You can also use PHP. For this you can use the code in the first answer of this question: Get users OS and version number

<?php

$user_agent     =   $_SERVER['HTTP_USER_AGENT'];

function getOS() { 

global $user_agent;

$os_platform    =   "Unknown OS Platform";

$os_array       =   array(
                        '/windows nt 6.2/i'     =>  'Windows 8',
                        '/windows nt 6.1/i'     =>  'Windows 7',
                        '/windows nt 6.0/i'     =>  'Windows Vista',
                        '/windows nt 5.2/i'     =>  'Windows Server 2003/XP x64',
                        '/windows nt 5.1/i'     =>  'Windows XP',
                        '/windows xp/i'         =>  'Windows XP',
                        '/windows nt 5.0/i'     =>  'Windows 2000',
                        '/windows me/i'         =>  'Windows ME',
                        '/win98/i'              =>  'Windows 98',
                        '/win95/i'              =>  'Windows 95',
                        '/win16/i'              =>  'Windows 3.11',
                        '/macintosh|mac os x/i' =>  'Mac OS X',
                        '/mac_powerpc/i'        =>  'Mac OS 9',
                        '/linux/i'              =>  'Linux',
                        '/ubuntu/i'             =>  'Ubuntu',
                        '/iphone/i'             =>  'iPhone',
                        '/ipod/i'               =>  'iPod',
                        '/ipad/i'               =>  'iPad',
                        '/android/i'            =>  'Android',
                        '/blackberry/i'         =>  'BlackBerry',
                        '/webos/i'              =>  'Mobile'
                    );

    foreach ($os_array as $regex => $value) { 

    if (preg_match($regex, $user_agent)) {
        $os_platform    =   $value;
    }

}   

return $os_platform;

}

function getBrowser() {

global $user_agent;

$browser        =   "Unknown Browser";

$browser_array  =   array(
                        '/msie/i'       =>  'Internet Explorer',
                        '/firefox/i'    =>  'Firefox',
                        '/safari/i'     =>  'Safari',
                        '/chrome/i'     =>  'Chrome',
                        '/opera/i'      =>  'Opera',
                        '/netscape/i'   =>  'Netscape',
                        '/maxthon/i'    =>  'Maxthon',
                        '/konqueror/i'  =>  'Konqueror',
                        '/mobile/i'     =>  'Handheld Browser'
                    );

foreach ($browser_array as $regex => $value) { 

    if (preg_match($regex, $user_agent)) {
        $browser    =   $value;
    }

}

return $browser;

}


$user_os        =   getOS();
$user_browser   =   getBrowser();

$device_details =   "<strong>Browser: </strong>".$user_browser."<br /><strong>Operating System: </strong>".$user_os."";

print_r($device_details);

echo("<br /><br /><br />".$_SERVER['HTTP_USER_AGENT']."");

?>

And now based on the return of getOS() and/or getBrowser function you can present your users different content ... that's a simples if else block code :)

Enjoy!