Hyperlinking Beyond the Web

Publikováno: 11.7.2018

Hyperlinks are the oldest and the most popular feature of the web. The word hypertext (which is the ht in http/s) means text having hyperlinks. The ability to link to other people’s hypertext made the web, a web — a set of connected pages. This fundamental feature has made the web a very powerful platform and it is obvious that the world of apps needs this feature. All modern platforms support a way for apps to register a URI

The post Hyperlinking Beyond the Web appeared first on CSS-Tricks.

Celý článek

Hyperlinks are the oldest and the most popular feature of the web. The word hypertext (which is the ht in http/s) means text having hyperlinks. The ability to link to other people’s hypertext made the web, a web — a set of connected pages. This fundamental feature has made the web a very powerful platform and it is obvious that the world of apps needs this feature. All modern platforms support a way for apps to register a URI (custom protocol) and also have universal links (handling web links in an app).

Let’s see why we’d want to take advantage of this feature and how to do it.

Why have app links at all?

Creating URIs that apps can open provides a unique set of benefits. A URL encapsulates the entire state of the webpage (it used to before the advent of Single Page Applications (SPAs) heavy with JavaScript and even in them, it is best practice to maintain this functionality) or a web application and it is advisable to have routing in applications so that a URL navigation is never broken. URLs have allowed browsers to have consistent back and forward buttons and reload and bookmark pages. And things like Google’s search depend on webpage addresses and web applications to navigate users to the correct place. Allowing applications to open URLs provides a way to link content and functionality in your application from other applications, websites and even internally within the application, like help/tutorial pages.

Trivia! Desktop applications have had hyperlinks even before the world wide web like Hypercard on the Mac in 1987 and Windows Help in 1990. The revolutionary thing about hyperlinks in the web is that you can link content that was created and is owned by others. This concept of cross app linking was not popular in traditional platforms until the rise of smartphones.

App links over the command line

There is already a shell for the command line interface and it supports passing arguments, redirecting the results from one application to anther, and even scripting. Still, the need for having support for hyperlinks is desirable for various reasons:

  • Security: The command line script is too complicated to get right. It is very risky to expect users of technology (who are not developers) to understand how to sanitize a command line script and make sure that the script does not accidentally call something, like rm -rf ~/Documents. The command line is not sand-boxed and though it provides a power, it puts responsibility on users and is prone to exploits.
  • Browser support: The browser cannot provide the command line interface. It is built to run untrusted third party code in a sandbox and that cannot be broken. And, if we don’t respect the rules of web technology, the app would silo itself out of the Internet. That is too much to lose.
  • Mobile: In the world of mobile, a touch keyboard is not as good and intuitive as the physical ones used on the desktop. Therefore, even if command line was present, it would be very difficult to use. All the power of redirection and chaining is not as effective as it is over a keyboard-based command line. A simpler solution like URL is more desirable in this context.
  • State: Theoretically, the command line interface can provide a way to have the application available in any state. But, practically, it was built for a separate purpose: to launch the application to perform a task and then return with a code. Additionally, it was de-prioritized by the GUIs in Windows and Mac. Many applications (like Microsoft Word and Adobe Photoshop) never had full-fledged command line support. They are not even present in the environment PATH and need the full path to be launched. URIs provide a fresh way to look at connecting information as well as functionality between applications. They are a secure, modern way of inter-app communication where the user does not have to think.

Linking Terminology

Deep Link

Deep link is the concept where a specific page/functionality within the website/application can be linked to. For example, https://css-tricks.com/link/to/this/page is a deep link for this page within the broader https://css-tricks.com link. Unlike superficial links that open applications, deep links open the application in a specific state, with passed data, ready to be consumed. Custom URI (described below) were the first ways to achieve deep linking within the app and "deep linking" in many contexts is now synonymous with custom URI, though it can also mean Universal links.

Custom URI

The web always had certain custom URIs that linked to default applications, like mailto:username@host.com and tel:1234567890 for email and telephone, respectively. It was an obvious choice to extend this and all major platforms provide a way for an app to register a URI protocol — like css-tricks://<link details> — that can open a deep linked native application. It should encapsulate the entire state and provide all routing benefits to open the app if it is available on the system. These provide a good interface for inter-app communication when the application is already installed and the desire is to open the user’s preferred application (like the browser or email client) that implements the desired protocol.

Custom URIs are great for the class of applications where the user wants to have a choice to select an app to perform a certain choice, but they are not great for the task of linking the website to the native application due to the following reasons:

  • Installation: Unlike the web, native apps need installation and, if they are not installed, you have two options: send the user to the app store (which also provides a custom URI) or properly fall back. Both these tasks require additional code which the URI does not encapsulate. The app store URI would be another separate URI that we need to store. Additionally, we have to detect whether the app is installed, which requires navigating to the URI and handling the error in navigation akin to a 404 since the protocol is not registered. This is not as simple as an anchor tag (<a href="{URL}"></a>) and, therefore, is a frequent source of developer complaints.
  • Lack of a central registry: Even if everything is done perfectly right, users can still be thrown into an application that they should not have been linked to. Unlike the DNS system (which guarantees uniqueness of domain names), application platforms do not have a central repository. So, multiple apps could register csstricks as a custom URI. Now, someone else could also register the same URI as one app and if the other application is installed on the system instead of the desired one, it could launch instead. The freedom of a custom URI for app selection works against the case where we always want to open a specific application.
  • Third Party Linking: Giving a custom URI to third parties because of the issues we’ve covered above is cumbersome and verifying them is painful. It is also a new protocol. Every website has links to the web content and updating the entirety of the Internet is not possible. It may not even be desired for SEO purposes.

Therefore, if we want to give the user the experience where the app is the preferred way to view content, custom URIs are not the perfect solution. This can be mitigated with smart app banners to some extent, where the banner would be displayed when the browser gets the custom URI and the app store link to identify the application. But this will not be seamless.

Trivia! URI and URLs are slightly different by definition, although they are used interchangeably. URI stands for Uniform Resource Identifier which means that it encapsulates everything required to get a resource. URL (Uniform Resource Locator) is a special type of URI that identifies the resource on the web. So, technically, a web address is a URL but something like csstricks:// would be just a URI.

These are all possible use cases where a custom URI might make sense:

  • You need to provide a URI that many third party client apps can register. Say you have a git:// in your app and the user’s favorite git client could register that URI and clone the repo when the the link is clicked.
  • You do not have a fully featured website.
  • You need a URI that is easy for users to remember.
  • You are willing to handle all edge cases related to navigation.

You can use custom URIs in parallel to the Universal link we’ll cover next and they can benefit from exposing the custom URL for inter-app communication while leaving the Universal link for the special case of web-to-app navigation. Android officially calls custom URIs deep links.

Control flows for Custom URI, Universal Link and Instant Apps
Universal link, App Link or App URI

The solution to the problem of web-to-app redirection can be solved with the Universal link (as it’s called in Mac and iOS), App Link (as it’s called in Android) or App URI (as it’s called in Windows UWP), which are different names of the same concept. This encapsulates the logic that every website needs to write in order to detect installed apps, launch correct pages and handle navigation failures in the case of custom URLs. It is very similar to the smart app banner in the sense that you need to provide an app store link to the application on your website for verification but it removes all the redundancy. Since the existing http(s) URL already contains the entire state, registering the custom URI is redundant. The original URL could itself navigate to the app with the website providing a fallback experience in case the application is not installed.

Once you register your app with a universal link, when the app is installed, the OS goes to the Internet to figure out the set of links that the app supports. Whenever any of those links get clicked, the native app gets launched instead of the browser. Full addressing support is now available in the application where a more customized experience can be provided falling back to the browser if the application is not installed. On important distinction with universal links is that they do affect regular browsing and therefore the OS providers keep then under tight lock and key.

A few good use cases for Universal links include:

  • You have a fully featured website for a fallback.
  • The desired flow for users is from the website to the app.
  • You have already built up a lot of karma by having content from your website linked around the web.

Instant Link or Deferred Deep Link

Deferred deep links provide the missing piece to the deep links if the user goes on to install the app when the link is opened. The user can be forwarded to the app store and the app store takes care of maintaining the context when the app is eventually launched after installation. This provides continuity in the case where an app is installed. Currently this method is only supported by Android (as Google Play Instant) and it is the option where you want to require users to move from the app to get the desired functionality. The hyperlink system on the web is seamless and clicking on a link gets you to the destination almost instantly (though a lot of things happen behind the scenes). Deferred deep links provide the same functionality for apps where clicking on a link can download the app and launch it with the correct page making all the tasks on app installation as seamless as possible.

You might consider using instant links if:

  • You need the users to use the app and not the website, even if they come from the browser (except for rare cases where they are low on disk space or have turned this feature off to save bandwidth).
  • Your key pages are always up-to-date in your application.
  • OK, always use them. With the extra SEO advantages that Google throws in for instant apps, there is no reason not to enable "instant" apps for an app that has Universal links.

Now that we have a summary of what each term means, let’s see how we can go about creating these on specific platforms.

Setting up application hyperlinks

MacOS and iOS

Custom URI

Registering a custom URI in MacOS and iOS is extremely easy. In Xcode, go to the Info.plist file of the project and select the option URL Types. Create an item of type URL Schemes inside the file where you can add all of the URL schemes you wish to support.

Note: The default URL Identifier filled in by Xcode within URL Types is optional.

URL Scheme in Xcode
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>css-tricks</string>
    </array>
  </dict>
</array>

For MacOS, you will receive calls to the AppDelegate where you can override the function:

func application(_ application: NSApplication, open urls: [URL]) {
  // Select the first URL in the list of URL to open
  let url = urls[0]; 
  // Log the entire URL
  NSLog("%@", url.absoluteString)
}
-(void)application:(NSApplication *)application openURLs:(NSArray<NSURL *> *)urls {
  NSLog(@"%@", urls[0].absoluteString);
}

In iOS, the function to receive the same call in AppDelegate is:

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
  NSLog("%@", url.absoluteString)
  return true
}
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options: (NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
  NSLog(@"%@", url.absoluteString);
  return true;
}

The URL type in all these cases contains the usual URL pieces like the path, the query and the search parameters.

Universal Links

Universal links are not supported in MacOS. To get universal links on iOS, you need to be registered with the Apple Developer Program or be an Apple Developer Enterprise Program member. Universal links on iOS are part of a wider concept of associated domains where the secrets of the website, like stored credentials, can be accessed from within the app and Apple does not allow regular Xcode users to dabble with universal links (if you accidentally enable the functionality for your App ID but do not put this in the entitlements, be prepared to get a validation error: ERROR ITMS-90046: "Invalid Code Signing Entitlements").

Trivia! Universal links are a new concept and many of the Mac and Windows desktop applications are built with their own network stack, do not use the app store and may have been released years ago. Therefore, the OS does not have the control required to force all applications to follow the Universal link concept and open the app. Apple has chosen to not implement Universal Links in MacOS yet, while on Windows they only work in the Microsoft Edge browser and other UWP apps (which use the same technology).

To register for Universal links on iOS, you need to do the following things:

  1. Enable the App ID on the Apple Developer website to get the feature for associated domains.
  2. Add the associated domains to the entitlements file.
  3. Add a apple-app-site-association file to the web domain to provide ownership verification for the application.
  4. Add code to handle clicks on Universal links.

Steps 1 and 2 can be performed together in the Capabilities tab on Xcode if you are signed in and have the right profile selected (which is from an Apple Developer or Enterprise account). The Xcode UI provides an indication that the both items have been completed.

Associated Domains in the Capabilities section in Xcode

In the image above, Step 2 has been successfully completed, while Step 1 is pending. You can click on the exclamation mark to find the issue. It is important to prefix the domain with applinks: (that identifies that you need app links functionality; the same is used for shared credentials). This identifies that the App Link capability has been enabled.

The above steps can also be accomplished individually.

For Step 1, enable Associated Domains in the App ID section on the developer website (shown at the bottom of the image):

Apple Developer website to enable associated domains

For Step 2, add the following to the entitlements file:

Entitlements plist
<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:css-tricks.com</string>
</array>

Now, for Step 3, host a association file apple-app-site-association like https://css-tricks.com/apple-app-site-association with mime type application/json with the following JSON data from the root of your website:

{
  "applinks": {
    "apps": [],
    "details": {
      "ABCDEFGHIJ.com.ccs-tricks.mobile": {
        "paths": [
          "*"
        ]
      }
    }
  }
}

The above snippet grants the app with ID ABCDEFGHIJ.com.ccs-tricks.mobile and provides access to all requests on the https://css-tricks.com domain. Note that this works with https but http is not supported. You can also use an alternative location like https://css-tricks.com/.well-known/apple-app-site-association for this. This URL is preferred as it can keep the Android, iOS and UWP association files in a safe separate folder that cannot be accidentally deleted.

Trivia! Universal links do not guarantee that the app is opened. If the user goes back to the website from the header in iOS, the OS decides to default to the website from then on until the user decides to use the app by clicking on the header again from Safari.

For Step 4, add the following code to the app:

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
  if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
    let url = userActivity.webpageURL!
    print(url.absoluteString)
  }
  return true
}
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
  if(userActivity.activityType == NSUserActivityTypeBrowsingWeb) {
    NSLog(@"%@", userActivity.webpageURL.absoluteString);
  }
  return true;
}

Smart app banners on iOS are much simpler and can be added via a simple meta HTML tag in the head section like this:

<meta name="apple-itunes-app" content="app-id=123456789, app-argument=https://css-tricks.com/linkto/this/article, affiliate-data=optionalAffiliateData">

They call the same method as Custom URIs we covered earlier.

Windows (Traditional)

On the traditional Windows platform (Win32/.NET), the custom URI (called Custom Pluggable Protocol Handler) is the only feature supported. Adding a custom URI in Windows involves adding a registry entry within HKEY_CLASSES_ROOT. The following will open the app with the absolute path given when clicking on a link to css-tricks://<text>. Due to using the command line, this always opens a new instance of the app and does not send the URL to the existing running application.

Registry Entry for providing application name
Registry entry for open command
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\css-tricks]
"URL Protocol"=""
@="CSS Tricks (Application name)"

[HKEY_CLASSES_ROOT\css-tricks\shell]

[HKEY_CLASSES_ROOT\css-tricks\shell\open]

[HKEY_CLASSES_ROOT\css-tricks\shell\open\command]
@=""C:\\Windows\\notepad.exe" "%1""

The above launches notepad with the command line argument corresponding to the URL supplied. Be aware that Notepad does not support custom URI and will not know what to do with the URL.

This behave is similar to passing this on the console and the application needs to make sure it properly distinguishes between the argument being a regular CLI or a custom URI, as shown here:

namespace ConsoleApplication1 {
  class Program {
    static void Main(string[] args) {
      if (args.Length > 0 && args[0].IndexOf("css-tricks:") == 0) {
        Console.Write(args[0]);
      }
    }
  }
}
int main(int argc, char*argv[]) {
  if (argc > 1) { // argv[0] is the file name.
    std::string word(argv[1]);
    if (word.compare(0, 11, "css-tricks:") == 0) {
      std::cout<<word;
    }
  }
  return 0;
}

Universal Windows Platform (UWP)

On the Universal Windows Platform, you can use the package manifest to register both the custom URI and the Universal link (called App URI Handler).

Custom URI

Add a protocol declaration in the package.appxmanifest:

Protocol Declaration in package.appxmanifest

The same can be achieved in code:

<Extensions>
  <uap:Extension Category="windows.protocol">
    <uap:Protocol Name="css-tricks" DesiredView="default">
      <uap:DisplayName>CSS Tricks</uap:DisplayName>
    </uap:Protocol>
  </uap:Extension>
</Extensions>

...and this can now be handled within the app.

protected override void OnActivated(IActivatedEventArgs args) {
  base.OnActivated(args);
  if (args.Kind == ActivationKind.Protocol) {
    var e = args as ProtocolActivatedEventArgs;
    System.Diagnostics.Debug.WriteLine(e.Uri.AbsoluteUri);
  }
}
// In App.xaml.h
virtual void OnActivated(Windows::ApplicationModel::Activation::IActivatedEventArgs^ e) override;

// In App.xaml.cpp
void App::OnActivated(Windows::ApplicationModel::Activation::IActivatedEventArgs ^ e) {
  if (e->Kind == Windows::ApplicationModel::Activation::ActivationKind::Protocol) {
    auto args = (ProtocolActivatedEventArgs^)e;
    auto url = args->Uri->AbsoluteUri;
  }
}
Universal Links (App URI)

App URIs are only supported in the Microsoft Edge browser. They do not work in Internet Explorer, Chrome or Firefox. App URIs also have a package entry similar to the custom URI. It is not available in the UI of Visual Studio Code 2017. The package.appxmanifest entries are almost the same:

<Extensions>
  <uap3:Extension Category="windows.appUriHandler">
    <uap3:AppUriHandler>
      <uap3:Host Name="css-tricks.com" />
    </uap3:AppUriHandler>
  </uap3:Extension>
</Extensions>

If uap3 is not available, it can be added to the Package tag where uap is also defined:

xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"

App URI is a relatively new feature of Windows and many UWP projects target older versions of windows as the minimum version. You might need to bump that up in order to support this feature.

Just like iOS, the website needs to verify the ownership of the domain for this to function. The can be done by hosting a file with mime type application/json at the root of your website, like https://css-tricks.com/windows-app-web-link or https://css-tricks.com/.well-known/windows-app-web-link over https with the content:

{
  "packageFamilyName": "YourPackageFamilyNameHere",
  "paths": ["*"],
  "excludePaths": ["/none/*", "https://cdn.css-tricks.com/robot.txt"]
}

To get the package family name, run the following in Powershell and search for your package path:

Get-AppxPackage

Handling App URIs requires the same code as custom URIs on Windows. By design, all you need to do is see the protocol field in the provided URI and write the corresponding logic.

Just like iOS, Windows users have a choice to disable opening apps. Windows provides registry settings to force apps to open (used for testing) and also a validator tool (located at %windir%\system32\AppHostRegistrationVerifier.exe) to verify if the above settings are correct.

Android

Custom URI

Android has supported custom URIs from the very beginning. This can be done with code. In AndroidManifest.xml add:

<activity android:name=".CustomUriActivity">
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="css-tricks" />
  </intent-filter>
</activity>

The category DEFAULT is to ensure that there is no user action needed to enable it. BROWSABLE ensures that the custom URIs work in the browser.

This can then be handled in the CustomUriActivity.java on creation:

public class CustomUriActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Intent intent = getIntent();
    if (Intent.ACTION_VIEW.equals(intent.getAction())) {
      Uri uri = intent.getData();
    }
  }
} 
Universal Links

Universal links in Android are very similar to App URIs. Android Studio provides a GUI tool to create this. Go to Tools > App Link Assistant. This will provide the three tasks needed to create app links:

App Link Assistant in Android

For Step 1, enter the URL mapping editor and click on the + button to open the URL mapping dialog:

URL Mapping dialog

Once you click OK, it will show the code that could have been used alternatively in the AndroidManifest.xml:

<activity android:name=".CustomUriActivity">
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http" android:host="css-tricks.com"/>
  </intent-filter>
</activity>

Note: This exactly the same as as it is for a custom URI. In the Step 2, select the activity you assigned and it will add the code to load the action. The code is also exactly the same as was used for custom URIs above.

In the Step 3, it will generate the JSON file that needs to be added to the website as application/json mime at https://css-tricks.com/.well-known/assetlinks.json.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "css_tricks",
    "package_name": "com.css-tricks.android",
    "sha256_cert_fingerprints":
    ["17:CC:87:9C:C8:39:B1:89:48:E8:2E:9E:6F:FC:7D:03:69:4D:05:90:2A:84:B8:AE:5D:6B:30:97:F8:1C:2B:BF"]
  }
}]

Android Studio automates the fingerprint generation that verifies the application identity. This identity is different for debug and the release version of the application.

Instant Link

Instant apps on Android require minor changes from the App links we covered earlier. To enable instant apps, modify the intent declared above to look like this:

<activity android:name=".CustomUriActivity">
  <intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="http" android:host="css-tricks.com"/>
    <data android:scheme="https"/>
  </intent-filter>
</activity>

This allows both http and https URLs to be associated with the app, and the autoVerify tells the Android (Google Play) store to verify the manifest item automatically. Instant app is a Play Store/Google Chrome feature. The changes on the app are minimal to support this.

Electron

Electron apps are traditional MacOS and Windows apps with the chromium runtime. These platforms only support custom URIs that can be enabled without touching the compiled code.

For MacOS, edit info.plist as described in the MacOS section. The events are received by the app object as:

// In main.js
require('electron').app.on('open-url', (event, url) => {
  console.log(url);
});

For Windows, add the registry entry as defined in the Windows section. The app will receive the custom URL in process.argv:

// In main.js
console.log(process.argv[2])

In electron, you can load external web pages that can open URLs. If it is desired to handle only those URLs internally, that can be registered via an electron API:

require('electron').protocol.registerHttpProtocol('css-tricks', (req, cb) => {
  console.log(req.url)
});

Web

Going a complete circle, a website can also register custom URIs (like handling mailto: links via a website). Note that these only affect links provided in other websites and have nothing to do with links in other applications. The API is very restrictive and can be used only for a selected link type: web+<insert anything here>. Browsers allow a pre-defined list of top level protocols to be registered:

  • Chrome: mailto, mms, nntp, rtsp, webcal
  • Firefox: bitcoin, geo, im, irc, iris, magnet, mailto, mms, news, sip, sms, smite, ssh, tel, urn, webcal, wti, xmpp

To register a protocol, you need to provide the same domain as the website that registers it (i.e. https://www.css-tricks.com can register only https://www.css-tricks.com and not https://css-tricks.com).

if (navigator.registerProtocolHandler) {
  navigator.registerProtocolHandler("web+csstricks", "https://css-ticks.com/?s=%s", "CSS Tricks custom URI");
}

The custom URIs are not indexed by search engines and therefore there is not much use to these apart from handling the links like mailto, webcal etc., that are already present in the web at multiple places.

Wrapping Up

Adding hyperlinks to native apps is an easy and effective way to seamlessly move the user across applications with graceful handling in case the application is not installed. With these links, applications can have provide paths all over the Internet which can be an effective promotional mechanism. Links also give websites a way to provide data and launch the applications on a specific page without the security worries that plague the downloadable files or command line scripts allowing effective tutorials and bug reports. Routing and URLs provide for the core features of the World Wide Web like navigation, the back button, bookmarks and search. Hyperlinks in apps bring these learning from the (almost) 30 years of world wide web to native applications.

The post Hyperlinking Beyond the Web appeared first on CSS-Tricks.

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace