📜 ⬆️ ⬇️

URL Scheme: Checking the presence of an installed application in Javascript

Recently I encountered the need to determine whether the URL Scheme is registered in the browser in order, depending on the result, to show either the application download button or the direct URL to launch it.

It turned out that there are no standard mechanisms for this. But since users did not want to pay attention to the Download button and the red inscription about the need to pre-install the application, I had to look for options. This will be discussed below.


')
I just want to warn you that there was not a single method that worked in at least two browsers consistently the same. Therefore, lovers of beautiful cross-browser solutions will be disappointed - the article is full of hacks (please do not minus it, it’s sickening, but the task is the task).

First, create our links.

<a class="runlink" href="myapp://command_line_parameters">Run</a> <a class="downloadlink" style="display:none;" href="http://mysite.com/download/app.exe">Download</a> 


Now we will create a common function that will determine the browser and launch the corresponding handler. Well, another auxiliary, which will display a request to download the application and download it in case of a positive response.

 function initApplicationLink(runlink, downloadlink) { //   var func = null; if (navigator.userAgent.indexOf('Firefox')>=0 ) func = checkFirefox; else if (navigator.userAgent.indexOf('Opera')>=0 ) func = checkOpera; else if (navigator.userAgent.indexOf('Chrome')>=0 ) func = checkChrome; else if (navigator.userAgent.indexOf('Safari')>=0 ) func = checkSafari; if ( func!=null ) { //   Download $(downloadlink).hide(); //          $(runlink).on('click', function(){ func(runlink, downloadlink); //   ,        return false; }); } else { //        Download $(downloadlink).show(); } } function downloadConfirmation(downloadlink) { if ( confirm('You need to install our application first. Do you really want to download it now?') ) document.location = $(downloadlink).attr('href'); } 


Let's start with Firefox and Opera, as they provide a 100% reliable and beautiful exclusion mechanism in this matter. But, unfortunately, the implementation is still different.

Firefox has the simplest solution.

 function checkFirefox(runlink, downloadlink) { //   ,      URL var f = createFrame(); try { f.contentWindow.location = $(runlink).attr('href'); } catch (e) { //  URL   ,      downloadConfirmation(downloadlink); } //     deleteFrame(f); } 


Opera is not much different from Firefox, except that it catches the exception not at the time of an attempt to load a URL with an unregistered protocol into the frame, but at the time of an attempt to access an undefined frame attribute (contentWindow.location).

 function checkOpera(runlink, downloadlink) { var f = createFrame(); f.contentWindow.location = $(runlink).attr('href'); setTimeout(function (){ try { //        // ( something   -) if ( f.contentWindow.location!='something' ) {} } catch (e) { downloadConfirmation(downloadlink); } deleteFrame(f); }, 0); } 


The createFrame () and deleteFrame () functions for Firefox and Opera:

 function createFrame() { var f = document.createElement('iframe'); f.style.display = 'none'; return document.body.appendChild(f); } function deleteFrame(f) { document.body.removeChild(f); } 


Safari for Windows also knows how to catch exceptions, but with a hidden frame this number failed. Alternatively, you can use a regular window. The solution is not elegant, but working.

Safari for MacOS with exceptions, apparently, is not friendly. Therefore, it is necessary to apply the purely crutch method here. The trick is to check the focus of our browser window after launching the application. If the application was successfully launched, the window lost focus, and we recorded this fact in the handler of this event. If the application did not open, then the window still has focus.

Unfortunately, this method is not 100% stable. For example, if during the timeout the browser did not have time to open the application, or vice versa - the user managed to close the application before the timeout occurred, and the window again received focus. As a result, such options translate into the next unpleasant picture. A window pops up with a download suggestion, and after a second the application starts. Or vice versa - the application starts, the user immediately closes it, and a window appears with a download suggestion. Therefore, increasing or decreasing the timeout is almost equally bad (the increase is still slightly better, because in most cases the user will not instantly close the newly opened application). In practice, the most acceptable timeout is one second.

 function checkSafari(runlink, downloadlink) { if ( navigator.userAgent.indexOf('Windows')>=0 ) { //        URL var w = window.open($(runlink).attr('href'), '', 'width=50, height=50'); setTimeout(function(){ try { //        if ( w.location!='about:blank' ) {} w.close(); window.focus(); } catch (e) { w.close(); window.focus(); downloadConfirmation(downloadlink); } }, 1000); } else { //  MacOS    iOS document.location = $(runlink).attr('href'); setTimeout(function(){ //   -  ,     if ( window.isFocused ) downloadConfirmation(downloadlink); }, 1000); } } //         window.isFocused = true; $(window).on('focus', function(){ window.isFocused = true; }) .on('blur', function(){ window.isFocused = false; }); 


Chrome turned out to be fully compatible with Safari under MacOS, except for a timeout (Safari took almost a second to launch the application, while Chrome launched it in less than 250 milliseconds).

(In practice, the timeout method is very much dependent on the load on the computer when the application is started. I got into situations with Chrome and Safari, when the application did not have time to start, and a window was displayed prompting you to download, after which the application was started. Again, the problem with timeout described above).

 function checkChrome(runlink, downloadlink) { document.location = $(runlink).attr('href'); setTimeout(function(){ if ( window.isFocused ) downloadConfirmation(downloadlink); }, 1000); } 


Well, finally, Internet Explorer did not show a stable result with all the efforts. Safari for Windows is technically suitable for IE (with the opening of a small window). But it was not possible to get at least 50% stability. Therefore, IE went to the “other browsers” branch, which does not do any checks, but simply displays both links — to launch and load the application. (I would be grateful if someone could tell me how to use IE).

Now we just need to initialize our links.

 initApplicationLink('.runlink', '.downloadlink'); 


Note: The solution was tested in the latest versions of Chrome, Firefox, Opera, Safari and IE on Windows and MacOS platforms. In mobile browsers, tests were not conducted, but the option with a timeout may well be workable on Android and iOS.

Source: https://habr.com/ru/post/182484/


All Articles