Skip to content

Instantly share code, notes, and snippets.

@kenchris
Last active September 22, 2017 01:39
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kenchris/0acec2790cd38dfdff0a7197ff00d1de to your computer and use it in GitHub Desktop.
Save kenchris/0acec2790cd38dfdff0a7197ff00d1de to your computer and use it in GitHub Desktop.

Shortcuts API Specification

This explainer is for the Shortcut API

Introduction

Some platforms like Windows and Android have ways to add menu items to the app launcher icon itself, here after called shortcuts. These can perform certain app actions. On Android in addition, you can drag these shortcuts to the homescreen.

The shortcut list (aka menu) contains a mixture of static and dynamic shortcuts and has a limit which may be different per platform or per launcher used. The amount of shortcuts on the homescreen is not limited so though the shortcut list may change overtime, existing shortcuts on the homescreen can still be updated (say change icon) though not removed.

Use-cases

The shortcuts are quite useful as /shortcuts/ to common actions, like starting a chat with the person you chat with the most, or say launch the web app to a specific place inside the app (deep link).

These are becoming quite popular, like for instance in GMaps you can add a shortcut to your contributions page.

API proposal

The ShortcutInfo dictionary is reusing name, short_name and icons from the manifest specification and can itself be embedded in the manifest.json inside the shortcuts member which takes an array of ShortcutInfo.

dictionary ShortcutInfo {
  DOMString id;
  DOMString name;
  DOMString short_name;
  sequence icons;
  dictionary data;
}

partial interface Navigator {
    readonly attribute int maxVisibleShortcuts;
    sequence<ShortcutInfo> getManifestShortcuts();
    sequence<ShortcutInfo> getDynamicShortcuts();
    Promise addShortcut(ShortcutInfo);
    Promise removeShortcut(ShortcutInfo);
    Promise setShortcut(ShortcutInfo); 
    Promise requestPinShortcut(ShortcutInfo);
};

[Constructor(DOMString type, optional ShortcutEventInit eventInitDict), Exposed=ServiceWorker]
Interface ShortcutEvent : ExtendableEvent {
  readonly attribute DOMString id;
  readonly attribute any data;
};

Examples

Static shortcuts inside a link manifest file

{
  "name": "Cool Email",
  "start_url": "https://coolemail.com",

  …

  "shortcuts": [{
     "name": "Email Mom",
     "id": "send-email",
     "data": { "email": "mom@coolemail.com" },
     "icons": [{
        "src": "icon/hd_hi.svg",
        "sizes": "any"
     }]
  }]
}

Responding to a shortcut activation

Similarly to the Push API, when shortcuts are clicked, the shortcutclick event is fired in the Service Worker.

At that point you have access to the id and data of the shortcut and can easily access any existing open window for the PWA or open the app using openWindow. postMessage can be used to talk to the open instance.

This is quite flexible. Using a Service Worker, the app can load URLs, execute JavaScript and communicate with a running instance of the PWA, even without giving it focus.

self.addEventListener('shortcutclick', function(event) {
  console.log('[Service Worker] Shortcut click Received.');

  if (event.id != "send-email) {
    return;
  }

  async function getWindow() {
    const client = await clients.matchAll(options).then(function(clientList) {
      if (clients.length > 0) {
        return clients[0];
      }
      return clients.openWindow('https://inbox.coolemail.com'); 
    });

    client.postMessage({ action: event.id, data: event.data });
    await client.focus();
  }

  event.waitUntil(getWindow());
});

Manually add a /dynamic/ shortcut

navigator.requestPinShortcut({
  name: "Email Mom",
  id: "send-email",
  data: { email: "mom@coolemail.com" },
  icons: [{
    src: "icon/hd_hi.svg",
    sizes: "any"
  }]
}).then(info => {
  console.log(`Added ${info.title} shortcut`); 
})

Opens

As it is quite a common case to just load different URLs (deep links into the app), it makes sense to add start_url to the ShortcutInfo. Questions is how to handle that. Without Service Worker it could just load the URL direclty.

  • Should shortcutclick be dispatched when a start_url is available as the URL might already be part of fetch event if in scope?

Relevant links

https://developer.android.com/guide/topics/ui/shortcuts.html https://developer.android.com/preview/features/pinning-shortcuts-widgets.html https://docs.microsoft.com/en-us/uwp/api/windows.ui.startscreen.jumplist https://github.com/android/platform_frameworks_base/blob/master/core/java/android/content/pm/ShortcutManager.java https://developer.android.com/reference/android/content/pm/ShortcutManager.html https://developer.android.com/reference/android/content/pm/ShortcutInfo.html https://developer.apple.com/ios/3d-touch/ https://developer.apple.com/documentation/uikit/uiapplicationshortcutitem https://electron.atom.io/docs/tutorial/desktop-environment-integration/

@mgiuca
Copy link

mgiuca commented Aug 4, 2017

I have some initial thoughts:

  1. It's a bit unclear what the difference is between "static" and "dynamic" shortcuts here. I guess maybe "static" is the ones on the home screen whereas "dynamic" is like the menus contained within the main app shortcut? You should probably flesh this out and explain it more.
  2. I'd like to see more discussion about non-Android use cases (particularly how this would work on desktop).
  3. I'm interested in how this fits in with beforeinstallprompt / "add to home screen". There seems to be a large cross-over between creating a shortcut to a deep link in an app, and "installing" the app itself. The two APIs are totally different. Would this work if the app wasn't already added to home screen? How will the UI differentiate the two? (Will the user be confused by similar-looking UI to "add" the app to home screen vs add a shortcut to a deep link?)
  4. Removing shortcuts isn't possible on Android AFAIK (programmatically). In Chrome, we are not able to even tell if a shortcut is on the home screen, let alone remove it. I think this should just be a one-way thing. (Just like how the beforeinstallprompt API lets you add an app, but not remove it.) I think the whole API could be simplified to just a "create shortcut" method.

A bigger deal:

I'm concerned that these "deep link shortcuts" are not URLs. The shortcut contains an opaque JavaScript object that will be sent into the SW event upon opening the shortcut, which feels very un-webby given that the web is glued together with URLs. Essentially these shortcuts are deep links into the app, but rather than being URLs (the web's preferred method of deep linking), they are opaque objects.

Why can't the shortcut just be a URL within the app's manifest scope? That means the "P" in PWA is working correctly: you design your app with a sensible URL scheme, and links from the web can deep link into your app. As a progressive enhancement, you can also create OS shortcuts to those same deep places in your app.

This would limit the capabilities (it would have to open a new page, rather than be magically handled by a service worker), but I think that's a good starting point. If we want links into the app to be grabbed by the SW and not open a new browser tab (something we're thinking about now in a bunch of places: a) for regular web links into an installed app, and b) for Web Share Target), we should solve that generally, maybe adding a SW event similar to fetch, but at a UI level (not a network level), that lets you intercept a navigation, stop it from opening a new tab, and do something in the background. WDYT?

@kenchris
Copy link
Author

kenchris commented Aug 9, 2017

Anyone, how to get notified when people add comments here?

@PaulKinlan: You are right, we should not have Manifest in the name, and a property seems much better.
Regarding addShortCut/removeShortCut. As far as I understand on Android, these persists. I assume they are written to some kind of Android settings.

Currently no fallback to meta tags. I think we can require manifest for this advanced feature - the idea was never to make the manifest 100% optional on the web platform.

@justinribeiro The idea was never that they were queried out of the manifest itself, but out of the system. So if Android adds the shortcuts from the manifest (potentially ignoring stuff it doesn't understand), the queried values would come from the system itself. And that might even differ depending on the launcher (that is how I understand that Android works), ie. one launcher might only show 3 items, and another might show up to 10.

I am not sure the disableShortcuts it necessary. You can disable old URLs in your service worker and redirect to a 404 or similar error page. That seems to work just like it does for native apps.

@kenchris
Copy link
Author

kenchris commented Aug 9, 2017

@mgiuca Static are the ones that are described in the manifest and only gets updated when the manifest is updated. That is how it works for Android apps as well - these can not be tampered with at runtime.

Regarding

  1. I added a few more links to how something similar works on iOS - I will have to look at that more.

  2. Static will only work when added to the home screen (and we might make that a requirement for dynamic as well) as there needs to be an icon to long press on to show the list (which can then be dragged to the homescreen for a regular shortcut). I do think it makes sense to always require the app to be added to the homescreen so that links can lunch /within/ the app, respecting the display-mode etc.

  3. On Android you can remove shortcuts from the long press menu (only dynamic ones, static ones needs an app update) - shortcuts on the homescreen itself cannot be removed, but you can intercept their urls and show an error page, like for hangouts.google.com/session/32828s2923ds829ahs -> "This hangout chat group no longer exists"

You are probably right, these can probably be just URLs, but we need the id as part of the payload so that we can update the long press menu list etc.

@mgiuca
Copy link

mgiuca commented Sep 22, 2017

I've written up a counter-proposal here. The major points are:

  1. URL-based, not event based, as discussed above.
  2. Focus on static first (much less complexity), and punt dynamic shortcuts to later.

The second point means it can be entirely done in the manifest spec, not introducing any new JavaScript APIs (which at least the two of us are most comfortable working with, spec-wise).

If anyone else wants to comment on that, please do so, then I think the next steps are perhaps just drafting a CL for the Manifest spec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment