66

We are using software that registers its own protocol. We can run application from browser then by link like:

customprotocol://do_this.

but is there a way to check is such custom protocol supported by user`s system? If not we would like to ask user to install software first.

E.g:

if (canHandle ('customprotocol')) {
     // run software
}
else {
    // ask to install
}

Edit I know about protocolLong property but it works only in IE.

chown
  • 51,908
  • 16
  • 134
  • 170
Piotr Pankowski
  • 2,396
  • 1
  • 15
  • 18
  • 4
    You might want to have a read of http://stackoverflow.com/questions/836777/how-to-detect-browsers-protocol-handlers – Naeem Sarfraz May 20 '10 at 08:39
  • 1
    Thx, already tried most of methods described there. It seems that there is no nice way to achieve this in all popular browsers without alerts or other issues. – Piotr Pankowski May 20 '10 at 08:50

8 Answers8

52

Unfortunately, there's no easy way of achieving this. There's certainly no method of pre-determining whether or not the protocol handler is installed.

Internet Explorer, as you mentioned, has the protocolLong property but I'm having trouble getting it to return anything other than "Unknown Protocol" for all custom protocol handlers -- if anyone knows how to get IE to return the correct value please let me know so I can update this section. The best solution I've found with IE is to append to the user agent string or install a browser extension along with your app that exposes a Javascript accessible property.

Firefox is by far the easiest of the major browsers, as it will allow you to try and catch a navigation attempt that fails. The error object returned contains a name property whose value is NS_ERROR_UNKNOWN_PROTOCOL:

try {
    iframe.contentWindow.location.href = "randomprotocolstring://test/";
} catch(e) {
    if (e.name == "NS_ERROR_UNKNOWN_PROTOCOL")
        window.location = "/download/";
}

Firefox will pop up with its own alert box:

Firefox doesn't know how to open this address, because the protocol (randomprotocolstring) isn't associated with any program.

Once you close this box, the catch block will execute and you have a working fallback.

Second is Opera, which allows you to employ the laws of predictability to detect success of a custom protocol link clicked. If a custom protocol click works, the page will remain the same location. If there is no handler installed, Opera will navigate to an error page. This makes it rather easy to detect with an iframe:

   iframe.contentWindow.location = "randomprotocolstring://test/";
   window.setTimeout(function () {
       try {
           alert(ifr.contentWindow.location); 
       } catch (e) { window.location = "/download/"; }
   }, 0);

The setTimeout here is to make sure we check the location after navigation. It's important to note that if you try and access the page, Opera throws a ReferenceException (cross-domain security error). That doesn't matter, because all we need to know is that the location changed from about:blank, so a try...catch works just fine.

Chrome officially sucks with this regard. If a custom protocol handler fails, it does absolutely zip. If the handler works... you guessed it... it does absolutely zip. No way of differentiating between the two, I'm afraid.

I haven't tested Safari but I fear it would be the same as Chrome.

You're welcome to try the test code I wrote whilst investigating this (I had a vested interest in it myself). It's Opera and Firefox cross compatible but currently does nothing in IE and Chrome.

Andy E
  • 338,112
  • 86
  • 474
  • 445
14

Here's an off-the-wall answer: Install an unusual font at the time you register your custom protocol. Then use javascript to check whether that font exists, using something like this.

Sure it's a hack, but unlike the other answers it would work across browsers and operating systems.

Mark
  • 4,749
  • 7
  • 44
  • 53
12

Just to chime in with our own experience, we used FireBreath to create a simple cross-platform plugin. Once installed this plugin registers a mime type which can be detected from the browser javascript after a page refresh. Detection of the mime type indicates that the protocol handler is installed.

if(IE) { //This bastard always needs special treatment
    try {
        var flash = new ActiveXObject("Plugin.Name");
    } catch (e) {
        //not installed
    }
else { //firefox,chrome,opera
    navigator.plugins.refresh(true);
    var mimeTypes = navigator.mimeTypes;
    var mime = navigator.mimeTypes['application/x-plugin-name'];
    if(mime) {
        //installed
    } else {
        //not installed
    }
}
Gearoid Murphy
  • 11,834
  • 17
  • 68
  • 86
  • 1
    +1―whilst not a "cloaked" detection method (IE will ask the user to allow ActiveXObjects to be run), this is still a clever approach. Specifically for Chrome, which has no other workaround, browser plugins might be the only option for some people. – Andy E Apr 02 '13 at 19:33
  • FireBreath is a really good way to address this and is really simple if this is all you need. I've used it for this (after trying to find a solution to the same problem). – Iain Collins Apr 19 '14 at 23:32
  • 1
    Just so people aware, Mozilla and Google consider plugins to be a legacy technology, Chrome started to phase NPAPI out, https://developer.chrome.com/extensions/npapi – Burjua Mar 23 '15 at 16:07
  • Hi Gearoid, Could you describe how you create your plugin using FireBreath? – yo2011 Apr 24 '16 at 09:10
9

Internet Explorer 10 on Windows 8 introduced the very useful navigator.msLaunchUri method for launching a custom protocol URL and detecting the success or failure. For example:

        if (typeof (navigator.msLaunchUri) == typeof (Function)) {
            navigator.msLaunchUri(witchUrl,
                function () { /* Success */ },
                function () { /* Failure */ showError(); });

            return;
        }

Windows 7 / IE 9 and below support conditional comments as suggested by @mark-kahn.

antmeehan
  • 865
  • 9
  • 11
  • 1
    Please note that the `msLaunchUri` method only exists in IE >= 10 **on Windows 8**. – aaronk6 Jan 05 '15 at 13:46
  • @aaronk6. I'm not sure that is correct, as I was using this method on Windows 7. – antmeehan Jan 20 '15 at 06:53
  • 2
    I checked IE 10 and IE 11 on Windows 7. In both cases, `typeof navigator.msLaunchUri` returns `"undefined"`. Also, Microsoft has confirmed that this API hasn’t been added to Windows 7: [Documented API function 'navigator.msLaunchUri' not present in Windows 7](https://connect.microsoft.com/IE/feedback/details/864863/documented-api-function-navigator-mslaunchuri-not-present-in-windows-7) (see comment from 5/5/2014 at 12:27 PM) – aaronk6 Jan 20 '15 at 08:50
  • Thanks for the details @aaronk6. I must have never noticed that it wasn't actually using this call (and falling back to alternative methods). I'll update my answer. – antmeehan Jan 21 '15 at 03:11
  • msLaunchUri only exists on +win8 – MJB Sep 12 '17 at 13:21
4

For Internet Explorer, the best solution I've found is to use Conditionl comments & Version Vector (application must write something to registry while installing protocol, see http://msdn.microsoft.com/en-us/library/ms537512.aspx#Version_Vectors). protocolLong doesn't work for custom protocol.

Mark Kahn
  • 1,130
  • 10
  • 8
  • 1
    For Chrome, maybe it is possible to register a MIME time during the installation, and check it using `window.navigator.mimeTypes[i]`. I couldn't find an easy way to do it. – Mark Kahn Feb 08 '12 at 14:57
  • 2
    +1, this is a good workaround for IE 9 and lower, Mark. Unfortunately, IE 10 no longer supports conditional comments, but perhaps they fixed the `protocolLong` issue. – Andy E Apr 02 '13 at 19:36
3

On mobile you can use an embedded iframe to auto switch between the custom protocol and a known one (web or app store), see https://gist.github.com/2662899

David W. Keith
  • 2,246
  • 17
  • 20
2

I just want to explain more previous Mark's answer (some people did not understand for example user7892745).

1) When you launch you web-page or web-application it checks for an unusual font (something like Chinese Konfuciuz font http://www.fontspace.com/apostrophic-lab/konfuciuz).

Below is the code of sample web-page with function which checks the font (called isFontAvailable):

<!DOCTYPE html>
<html>
<head>

</head>
<body>

<script>
/**
* Checks if a font is available to be used on a web page.
*
* @param {String} fontName The name of the font to check
* @return {Boolean}
* @license MIT
* @copyright Sam Clarke 2013
* @author Sam Clarke <sam@samclarke.com>
*/
(function (document) {
   var width;
   var body = document.body;

                    var container = document.createElement('span');
                    container.innerHTML = Array(100).join('wi');
                    container.style.cssText = [
       'position:absolute',
       'width:auto',
       'font-size:128px',
       'left:-99999px'
   ].join(' !important;');

   var getWidth = function (fontFamily) {
       container.style.fontFamily = fontFamily;

       body.appendChild(container);
       width = container.clientWidth;
       body.removeChild(container);

       return width;
   };

   // Pre compute the widths of monospace, serif & sans-serif
   // to improve performance.
   var monoWidth  = getWidth('monospace');
   var serifWidth = getWidth('serif');
   var sansWidth  = getWidth('sans-serif');

   window.isFontAvailable = function (font) {
       return monoWidth !== getWidth(font + ',monospace') ||
           sansWidth !== getWidth(font + ',sans-serif') ||
           serifWidth !== getWidth(font + ',serif');
   };
})(document);



function isProtocolAvailable()
{
    if (isFontAvailable('Konfuciuz')) 
    {
        return true;
    } 
    else 
    {
        return false;
    }
}

function checkProtocolAvail()
{
    if (isProtocolAvailable()) 
    {
        alert('Custom protocol is available!');
    } 
    else 
    {
        alert('Please run executable to install protocol!');
    }
}
</script>

<h3>Check if custom protocol was installed or not</h3>

<pre>


<input type="button" value="Check if custom protocol was installed!" onclick="checkProtocolAvail()">

</body>
</html>

2) First time when user opens this page, font will not be installed so he will get a message saying "Please run executable to install custom protocol...".

3) He will run executable which will install the font. Your exe can just copy the font file (in my case it is KONFUC__.ttf) into C:\Windows directory or using a code like this (example on Delphi):

// Adding the font ..

AddFontResource(PChar('XXXFont.TTF'));
SendMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);

4) After that, when user runs web app again, he gets "Custom protocol is available!" message because font was installed this time.

Tested on Google Chrome, Internet Explorer and Firefox - working great!

  • 1
    FWIW, this approach is likely to break in the future, as the Privacy impact of font detection is very problematic (fingerprinting vector). – EricLaw Nov 07 '19 at 22:14
0

For Firefox, most of articles I googled, including Andy E 's answer here, and this gist Cross-browser implementation of navigator.msLaunchUri or https://github.com/ismailhabib/custom-protocol-detection using

iframe.contentWindow.location.href = uri

But it has stopped working since Firefox 64, e.g here https://github.com/ismailhabib/custom-protocol-detection/issues/37 also confirmed that.

So FF 64+ I found I can either using Chrome's method, blurHandler or using the post there https://github.com/ismailhabib/custom-protocol-detection/issues/37#issuecomment-617962659

try {
        iframe.contentWindow.location.href = uri;
        setTimeout(function () {
             try {
                    if (iframe.contentWindow.location.protocol === "about:") {
                         successCb();
                     } else {
                         failCb();
                     }
             } catch (e) {
                if (e.name === "NS_ERROR_UNKNOWN_PROTOCOL" || 
                 e.name === "NS_ERROR_FAILURE" || e.name === "SecurityError") {
                     failCb();
               }
             }
        }, 500);
} catch (e) {
   if (e.name === "NS_ERROR_UNKNOWN_PROTOCOL" || e.name === "NS_ERROR_FAILURE" 
    || e.name === "SecurityError") {
       failCb();
   }
}

For Chrome 86+ it also fails to work, check my answer for details Detect Custom Protocol handler in chrome 86

BTW, I find most of answers/articles are outdated in some cases.

Qiulang
  • 10,295
  • 11
  • 80
  • 129
  • What is failCb here? Where it is declared? – Ambuj Khanna Oct 27 '20 at 11:32
  • That is callback you provide, check the original code https://github.com/ismailhabib/custom-protocol-detection/blob/master/protocolcheck.js – Qiulang Oct 27 '20 at 12:05
  • Please check and try that github project yourself. It will be much more effective than keeping asking me. I will answer it for the last time, failBc can be provided (by you) as ()=> alert("protocol not recognized"); – Qiulang Oct 28 '20 at 03:44