How to make your web app accept sharing

·

3 min read

I am building a web app that helps people manage frequently used links and notes (called easyy.click), I have added several ways to make collecting links/notes easyy, for example:

  • People can add multiple links on the creation page;

  • People can import their browser bookmarks (it still works if you have thousands of links, takes some time though);

  • Created a browser extension, so people can save the current tab, or any link / selected text by right clicking it, this makes collecting really easyy on laptop;

The next step is, is it possible to accept shared link / text from other apps on the phone? So it’s also easyy to collect things on the phone.

I have the question, GPT has the answer, and the answer is Yes (partially). You may guessed it, it works with Android, but not iOS, details here.

I will show you how I made it work, to accept sharing for my web app on Android.

1). Add share_target to manifest.json

Your web app needs to be a PWA, so you have to have a valid manifest.json, and it needs to have a share_target field, like this:

{
  "share_target": {
    "action": "/easyy-share",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "title": "title",
      "text": "text",
      "url": "url"
    }
  }
}

This tell Android, if people share a link/text to your web app, it will make a POST request to ${appDomain}/easyy-share (in my case https://app.easyy.click/easyy-share), with the payload defined by enctype and params.

(If your web app is a SPA, you don’t need to prepare a /easyy-share route, this request will handled by service worker, see below)

This is my full manifest.json file: https://github.com/penghuili/easyy.click/blob/master/public/manifest.json

2). Register a service worker

You can process the request with a real backend endpoint (see the MDN doc), or, you can process it within service worker.

My web app is a SPA, and I need to end-to-end encrypt the link/note before sending it to backend, so I process the request in service worker.

When my SPA starts, I register a service worker:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').catch(registrationError => {
    console.log('SW registration failed: ', registrationError);
  });
}

The sw.js file has the logic to process the request.

3). Process request in sw.js

The sw.js needs to have a listener for the fetch event, like this

self.addEventListener('fetch', event => {
  if (event.request.method === 'POST' && event.request.url.includes('/easyy-share')) {
    event.respondWith(handleShare(event.request));
  }
});

We only process it if the request method is POST and the path is /easyy-share. All other fetch requests will behave normally.

Now let’s see how the handleShare() looks:

async function handleShare(request) {
  const formData = await request.formData();
  const title = formData.get('title');
  const content = formData.get('url') || formData.get('text');

  if (!content) {
    return Response.redirect('/shared', 303);
  }

  saveContent(title, content);

  return Response.redirect(`/shared?shared=${isValidUrl(content) ? 'link' : 'text'}`, 303);
}

Very straightforward, we get the payload from request, save it, and redirect to a /shared page with some query params. Some notes:

  1. saveContent() is an async function, but here we don’t need to await it, the requests will finish in the background, and users will see the success page quickly;

  2. In 1) we said we don’t need a /easyy-share route, but here we need a /shared route, because Response.redirect('/shared', 303) will open our app, and open the /shared page. This is our good chance to tell users, that the share is done;

  3. service worker doesn’t have access to localStorage, but it has access to indexDB, so if your saveContent() needs to read something from the localStorage (access token maybe?), you need to save that thing also in indexDB in your main app, so service worker can get the value;

Check the full code of my sw.js: https://github.com/penghuili/easyy.click/blob/master/src/sw.js

This is the end result:

Let me know if you have any feedback or question.