Cómo estimar el espacio de almacenamiento disponible

Resumen

Chrome 61, y otros navegadores que lo seguirán, ahora exponen una estimación de cuánto almacenamiento está usando una app web y cuánto está disponible a través de lo siguiente:

if ('storage' in navigator && 'estimate' in navigator.storage) {
  navigator.storage.estimate().then(({usage, quota}) => {
    console.log(`Using ${usage} out of ${quota} bytes.`);
  });
}

Apps web modernas y almacenamiento de datos

Cuando piensas en las necesidades de almacenamiento de una aplicación web moderna, es útil dividir lo que se almacena en dos categorías: los datos principales necesarios para cargar la aplicación web y los datos necesarios para una interacción significativa del usuario una vez que se carga la aplicación.

El primer tipo de datos, que se necesita para cargar tu app web, consiste en HTML, JavaScript, CSS y, tal vez, algunas imágenes. Los procesos de trabajo de servicio, junto con la API de Cache Storage, proporcionan la infraestructura necesaria para guardar esos recursos principales y, luego, usarlos para cargar rápidamente tu app web, lo ideal es que omitan la red por completo. (Las herramientas que se integran en el proceso de compilación de tu app web, como las bibliotecas nuevas de Workbox o las más antiguas de sw-precache, pueden automatizar por completo el proceso de almacenamiento, actualización y uso de este tipo de datos).

Pero ¿qué sucede con el otro tipo de datos? Estos son recursos que no son necesarios para cargar tu app web, pero que pueden desempeñar un papel fundamental en la experiencia general del usuario. Por ejemplo, si estás escribiendo una app web de edición de imágenes, es posible que desees guardar una o más copias locales de una imagen, lo que les permitirá a los usuarios cambiar entre las revisiones y deshacer su trabajo. O bien, si estás desarrollando una experiencia de reproducción de contenido multimedia sin conexión, guardar archivos de audio o video de forma local sería una función fundamental. Todas las apps web que se pueden personalizar terminan necesitando guardar algún tipo de información de estado. ¿Cómo sabes cuánto espacio hay disponible para este tipo de almacenamiento en el tiempo de ejecución y qué sucede cuando te quedas sin espacio?

En el pasado: window.webkitStorageInfo y navigator.webkitTemporaryStorage

Históricamente, los navegadores admiten este tipo de introspección a través de interfaces con prefijos, como window.webkitStorageInfo, que es muy antiguo (y obsoleto), y navigator.webkitTemporaryStorage, que no es tan antiguo, pero aún no es estándar. Si bien estas interfaces proporcionaron información útil, no tienen futuro como estándares web.

Ahí es donde entra en juego el Estándar de almacenamiento de WHATWG.

El futuro: navigator.storage

Como parte del trabajo en curso sobre el estándar de almacenamiento, algunas APIs útiles llegaron a la interfaz StorageManager, que se expone a los navegadores como navigator.storage. Al igual que muchas otras APIs web más recientes, navigator.storage solo está disponible en orígenes seguros (se entrega a través de HTTPS o localhost).

El año pasado, presentamos el método navigator.storage.persist(), que permite que tu aplicación web solicite que su almacenamiento esté exento de la limpieza automática.

Ahora se le une el método navigator.storage.estimate(), que sirve como reemplazo moderno de navigator.webkitTemporaryStorage.queryUsageAndQuota(). estimate() muestra información similar, pero expone una interfaz basada en promesas, que se mantiene al día con otras APIs asíncronas modernas. La promesa que muestra estimate() se resuelve con un objeto que contiene dos propiedades: usage, que representa la cantidad de bytes que se usan actualmente, y quota, que representa la cantidad máxima de bytes que puede almacenar el origen actual. (al igual que todo lo relacionado con el almacenamiento, la cuota se aplica a todo un origen).

Si una aplicación web intenta almacenar datos (por ejemplo, con IndexedDB o la API de Cache Storage) que sean lo suficientemente grandes como para que un origen determinado supere su cuota disponible, la solicitud fallará con una excepción QuotaExceededError.

Estimaciones de almacenamiento en acción

La forma exacta en que uses estimate() depende del tipo de datos que necesite almacenar la app. Por ejemplo, puedes actualizar un control en tu interfaz para que los usuarios sepan cuánto espacio se está usando después de que se completa cada operación de almacenamiento. Lo ideal sería que proporcionaras una interfaz que permita a los usuarios limpiar de forma manual los datos que ya no se necesitan. Puedes escribir código como el siguiente:

// For a primer on async/await, see
// https://developers.google.com/web/fundamentals/getting-started/primers/async-functions
async function storeDataAndUpdateUI(dataUrl) {
  // Pro-tip: The Cache Storage API is available outside of service workers!
  // See https://googlechrome.github.io/samples/service-worker/window-caches/
  const cache = await caches.open('data-cache');
  await cache.add(dataUrl);

  if ('storage' in navigator && 'estimate' in navigator.storage) {
    const {usage, quota} = await navigator.storage.estimate();
    const percentUsed = Math.round(usage / quota * 100);
    const usageInMib = Math.round(usage / (1024 * 1024));
    const quotaInMib = Math.round(quota / (1024 * 1024));

    const details = `${usageInMib} out of ${quotaInMib} MiB used (${percentUsed}%)`;

    // This assumes there's a <span id="storageEstimate"> or similar on the page.
    document.querySelector('#storageEstimate').innerText = details;
  }
}

¿Qué tan precisa es la estimación?

Es difícil pasar por alto el hecho de que los datos que obtienes de la función son solo una estimación del espacio que usa un origen. Está justo en el nombre de la función. Ni los valores de usage ni los de quota están diseñados para ser estables, por lo que se recomienda que tengas en cuenta lo siguiente:

  • usage refleja cuántos bytes usa efectivamente un origen determinado para los datos del mismo origen, que a su vez pueden verse afectados por técnicas de compresión internas, bloques de asignación de tamaño fijo que pueden incluir espacio sin usar y la presencia de registros de"llave de tumba" que se pueden crear temporalmente después de una eliminación. Para evitar la filtración de información de tamaño exacto, los recursos opacos entre orígenes que se guardan de forma local pueden agregar bytes de padding adicionales al valor general de usage.
  • quota refleja la cantidad de espacio reservada actualmente para un origen. El valor depende de algunos factores constantes, como el tamaño total del almacenamiento, pero también de una serie de factores potencialmente volátiles, incluida la cantidad de espacio de almacenamiento que no se usa actualmente. Por lo tanto, a medida que otras aplicaciones de un dispositivo escriben o borran datos, es probable que cambie la cantidad de espacio que el navegador está dispuesto a dedicar al origen de tu app web.

El presente: detección de funciones y resguardos

estimate() está habilitado de forma predeterminada a partir de Chrome 61. Firefox está experimentando con navigator.storage, pero, a partir de agosto de 2017, no está activado de forma predeterminada. Para probarla, debes habilitar la preferencia dom.storageManager.enabled.

Cuando se trabaja con funciones que aún no son compatibles con todos los navegadores, la detección de funciones es obligatoria. Puedes combinar la detección de componentes junto con un wrapper basado en promesas sobre los métodos navigator.webkitTemporaryStorage más antiguos para proporcionar una interfaz coherente en las siguientes líneas:

function storageEstimateWrapper() {
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    // We've got the real thing! Return its response.
    return navigator.storage.estimate();
  }

  if ('webkitTemporaryStorage' in navigator &&
      'queryUsageAndQuota' in navigator.webkitTemporaryStorage) {
    // Return a promise-based wrapper that will follow the expected interface.
    return new Promise(function(resolve, reject) {
      navigator.webkitTemporaryStorage.queryUsageAndQuota(
        function(usage, quota) {resolve({usage: usage, quota: quota})},
        reject
      );
    });
  }

  // If we can't estimate the values, return a Promise that resolves with NaN.
  return Promise.resolve({usage: NaN, quota: NaN});
}