Cómo recibir archivos compartidos

Palances Liao
Palances Liao

La forma moderna

Usa la API de Web Share Target

Primero, declara un share_target en el manifiesto de tu app web que enumera una action (la URL que controla los archivos compartidos), un method ("POST" para archivos) y un enctype ("multipart/form-data" para archivos), y un objeto params que contenga una propiedad files con un array de objetos con una propiedad name y accept que enumeran los tipos de archivos que se pueden compartir y el nombre para obtenerlos.

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

Luego, debes controlar las solicitudes POST entrantes en el service worker. El archivo se almacena de forma temporal en una caché de contenido multimedia, por lo que se puede consumir en el cliente. Para ello, redirecciona la app a una URL con un parámetro de búsqueda especial del marcador, como share-target.

self.addEventListener('fetch', (fetchEvent) => {
  if (fetchEvent.request.url.endsWith('/receive-files/') && fetchEvent.request.method === 'POST') {
    return fetchEvent.respondWith(
      (async () => {
        const formData = await fetchEvent.request.formData();
        const image = formData.get('image');
        const keys = await caches.keys();
        const mediaCache = await caches.open(keys.filter((key) => key.startsWith('media'))[0]);
        await mediaCache.put('shared-image', new Response(image));
        return Response.redirect('./?share-target', 303);
      })(),
    );
  }
});

Por último, debes consumir el archivo en el cliente.

window.addEventListener('load', async () => {
  if (location.search.includes('share-target')) {
    const keys = await caches.keys();
    const mediaCache = await caches.open(
      keys.filter((key) => key.startsWith('media'))[0],
    );
    const image = await mediaCache.match('shared-image');
    if (image) {
      const blob = await image.blob();
      await mediaCache.delete('shared-image');
      // Handle the shared file somehow.
    }
  }
});

Navegadores compatibles

  • 89
  • 89
  • x
  • x

Origen

Lecturas adicionales

Demostración

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="manifest" href="manifest.json" />
    <title>How to receive shared files</title>
    <link rel="stylesheet" href="style.css" />
    <!-- TODO: Devsite - Removed inline handlers -->
    <!-- <script>
      if ('serviceWorker' in navigator) {
        window.addEventListener('load', async () => {
          const registration = await navigator.serviceWorker.register(
            'sw.js',
          );
          console.log(
            'Service worker registered for scope',
            registration.scope,
          );
        });
      }
    </script>
    <script src="script.js" type="module"></script> -->
  </head>
  <body>
    <h1>How to receive shared files</h1>
    <p>Install the app. After the installation, try sharing an image to it from another app.
  </body>
</html>

CSS


        html {
  box-sizing: border-box;
  font-family: system-ui, sans-serif;
  color-scheme: dark light;
}

*, *:before, *:after {
  box-sizing: inherit;
}

body {
  margin: 1rem;
}

img {
  height: auto;
  max-width: 100%;
  display: block;
}
        

JS


        window.addEventListener('load', async () => {
  if (location.search.includes('share-target')) {
    const keys = await caches.keys();
    const mediaCache = await caches.open(
      keys.filter((key) => key.startsWith('media'))[0],
    );
    const image = await mediaCache.match('shared-image');
    if (image) {
      const blob = await image.blob();
      await mediaCache.delete('shared-image');
      // Handle the shared file somehow.
    }
  }
});