How to handle images offline in a PWA share target

2020-06-16 05:33发布

问题:

I can register my Progressive Web App as a share target for images (supported from Chrome Android 76):

"share_target": {
    "action": "/share-target",
    "method": "POST",
    "enctype": "multipart/form-data",
    "params": {
      "files": [
        {
          "name": "myImage",
          "accept": ["image/jpeg", "image/png"]
        }
      ]
    }
  }

I can then intercept attempts to share images to the app in a service worker:

self.addEventListener('fetch', e => {
  if (e.request.method === 'POST' && e.request.url.endsWith('/share-target')) {
    // todo
  }
})

How would I display the shared image in my offline PWA?

回答1:

There are a few different steps to take here.

I put together a working example at https://web-share-offline.glitch.me/, with the source at https://glitch.com/edit/#!/web-share-offline

Ensure your web app works offline

This is a prerequisite, and I accomplished it by generating a service worker that would precache my HTML, JS, and CSS using Workbox.

The JS that runs when the home page is loaded uses the Cache Storage API to read a list of image URLs that have been cached, to creates <img> elements on the page corresponding to each one.

Create a share target handler that will cache images

I also used Workbox for this, but it's a bit more involved. The salient points are:

  • Make sure that you intercept POST requests for the configured share target URL.
  • It's up to you to read in the body of the shared images and to write them to your local cache using the Cache Storage API.
  • After you save the shared image to cache, it's a good idea to respond to the POST with a HTTP 303 redirected response, so that the browser will display the actual home page for your web app.

Here's the Workbox configuration code that I used to handle this:

const shareTargetHandler = async ({event}) => {
  const formData = await event.request.formData();
  const cache = await caches.open('images');

  await cache.put(
      // TODO: Come up with a more meaningful cache key.
      `/images/${Date.now()}`,
      // TODO: Get more meaningful metadata and use it
      // to construct the response.
      new Response(formData.get('image'))
  );

  // After the POST succeeds, redirect to the main page.
  return Response.redirect('/', 303);
};

module.exports = {
  // ... other Workbox config ...
  runtimeCaching: [{
    // Create a 'fake' route to handle the incoming POST.
    urlPattern: '/share-target',
    method: 'POST',
    handler: shareTargetHandler,
  }, {
    // Create a route to serve the cached images.
    urlPattern: new RegExp('/images/\\d+'),
    handler: 'CacheOnly',
    options: {
      cacheName: 'images',
    },
  }],
};