Detecting if a URL scheme can be handled

registerProtocolHandler is an under used API. I love this API, it allows a web app to become the default system handler for safe URLs such as 'mailto', 'irc', 'tel', 'sms' as well as custom web+* types.

It unfortunately has a few issues:

  1. It's not well supported on Mobile. PWAs in Chrome can register a protocol_handler, which is huge!
  2. People don't really understand the scheme part of urls, and prefer https (more on this at the end)
  3. Developers don't have the ability to determine if a custom scheme will resolve to anything useful for the user.

The last point is a big pain. If the user clicks a link on a web page you would expect it to do something. I think we can do something here, specifically present a common pattern that we can use when there is no app or web-site to handle the link.

A long time ago this was a solved issue. Chrome had an isProtocolHandlerRegistered method which would let you determine if there is something on the other end of the custom scheme. It was later removed from Chrome because it was removed from the Spec, and it was removed from the Spec for a variety of reasons including that the API could be as a finger-printing vector.

I'm opening up this can of worms again because of a challenge that I see with the "Follow" function in Mastodon and I think we can make it better.

Disclaimer: I know I will be conflating Mastodon and ActivityPub.

If you want to follow a person who is on a different Mastodon instance to you, you need to find their Follow page, enter your ID and instance name (i.e, @paul@status.kinlan.me) into the follow field, it will then redirect you back to your instance to let you follow that user.

It would be much nicer if you could click on a link to a Mastodon profile (say, on this web page) and it will take you directly to your instance to let you follow the user.

I could envisage a system where a link encoded as follows: <a href="web+follow:@paul@status.kinlan.me">Follow</a> (note web+follow instead of https), when clicked would take the user to their instance (or whatever ActivityPub service they have), with the @paul@status.kinlan.me populated so they can quickly follow me; and if the user doesn't have an ActivityPub app or site available we can do something specific.

I will save the specifics for web+follow for another post. The pattern I am attempting to document is a common one for using custom schemes:

  1. User clicks a link that has a custom scheme
  2. Detect if the navigation failed (because there is no app)
  3. Present a UI to tell the user to do something.

I'll give a preview: There is no solution that is as decisive isProtocolHandlerRegistered, all of the solutions are based on a heuristic.

What about the finger-printing concern? It might still be there. isProtocolHandlerRegistered didn't have a required User-Gesture constraint so you could in theory scan a large set of protocols to determine sites the user might have registered. Any solutions here are gated on a user gesture such as a navigation via a link click.

This code assumes updateUI_NoHandler is a function that will update the UI to tell the user there is no site or app installed that can handle web+follow: links.

Attempt 1: Individual Click Handler

HTML

<a href="web+follow:@paul@status.kinlan.me" id="follow">
  Follow
</a>

JavaScript

addEventListener("load", (loadEvent) => {
  const follow = document.getElementById("follow");
  follow.addEventListener("click", (clickEvent) => {
    setTimeout(() => updateUI_NoHandler(), 1000);
  });
});

Pros:

Cons:

Attempt 2: Global Click Handler

If you don't want to augment your anchor, you could intercept all link clicks.

HTML

<a href="web+follow:@paul@status.kinlan.me">Follow</a>

JavaScript

addEventListener("load", (loadEvent) => {
  document.body.addEventListener("click", (clickEvent) => {
    const { target } = clickEvent;
    if (target.nodeName == "A" && target.href.startsWith("web+follow:")) {
      setTimeout(() => updateUI_NoHandler(), 1000);
    }
  });
});

Pros:

Cons:

HTML

    <a href="web+follow:@paul@status.kinlan.me">Follow</a>

JavaScript

navigation.addEventListener("navigate", (event) => {
  if (event.destination.url.startsWith("web+follow")) {
    setTimeout(() => window.stop(), 1000)
  }
});

navigation.onnavigateerror = (event) => {
  if (event.error.message === "Navigation was aborted") {
    updateUI_NoHandler();
  }
};

Pros:

Cons:

Attempt 4: Winner, but more complex.

This one is a little complex because it requires a server, but it handles all the cases and preserves URLs as HTTPS URLs.

Thanks to James Henstridge who mentioned that you might be able to 302 redirect to a custom scheme (such as mailto - try it) and if there is no app or site to handle the custom URL scheme the request doesn't 404, it gets cancelled and the current page is left intact.

With this "no-op" side effect, you can then use a meta http-equiv="refresh" to attempt to redirect to the handler and if it's available it will be followed, and if it isn't you will remain on the current page.

<meta
 http-equiv="refresh"
 content="0; url=https://eastern-shimmering-car.glitch.me/web-follow"
/>
</head>
<body>
  You don't have a site installed to handle `web+follow`.
  Do x.y.z instead.
</body>

In this case "https://eastern-shimmering-car.glitch.me/web-follow"is a simple 302 redirect to web+follow:@paul@status.kinlan.me.

Pros:

Cons:

Wrap up

I got to the end of this and I realised that I don't think people will want custom schemes in their day to day lives, I wouldn't encode a web+follow link in an email, I'd prefer to send a person to a web page. However custom schemes are incredibly useful as a tool for developers to be able to direct people into the sites and apps of their choice without knowing what site or app they are using while handling the case when there is no choice available.

Now it's got me thinking that I could rebuild web intents off this.

I lead the Chrome Developer Relations team at Google.

We want people to have the best experience possible on the web without having to install a native app or produce content in a walled garden.

Our team tries to make it easier for developers to build on the web by supporting every Chrome release, creating great content to support developers on web.dev, contributing to MDN, helping to improve browser compatibility, and some of the best developer tools like Lighthouse, Workbox, Squoosh to name just a few.

I'm trialing a newsletter, you can subscribe below (thank you!)