Ir al contenido
HexaTransfer
Volver al blog
Analisis tecnicos

APIs de almacenamiento del navegador para aplicaciones de transferencia de archivos

Usa IndexedDB, File System Access API y Cache API para apps de transferencia de archivos. Límites de almacenamiento, rendimiento y compatibilidad con navegadores.

El almacenamiento del navegador para una app de transferencia de archivos se divide limpiamente entre cuatro APIs: IndexedDB para metadatos estructurados de sesiones y fragmentos (transaccional, asíncrona, tipada), la File System Access API para leer y escribir archivos de varios gigabytes directamente en el disco del usuario, la Cache API para respuestas HTTP y activos del shell de la aplicación, y la Storage Manager API para gestión de cuotas e indicaciones de persistencia. El Origin Private File System (OPFS) se sitúa junto a estas para I/O de alto rendimiento en sandbox. Elegir la correcta importa porque los límites varían de 1 GB en iOS Safari a "el 60% del disco libre" en Chrome de escritorio, y la elección incorrecta lleva eventualmente a QuotaExceededError en producción.

Mapear cada API al trabajo correcto

Usa IndexedDB para cualquier cosa que se parezca a una fila: sesiones de subida, índices de fragmentos completados, metadatos de compartición, tokens de revocación. Es asíncrona, transaccional, indexable y sobrevive a través de sesiones.

Usa la File System Access API cuando necesites entregar bytes al disco sin cargar todo el archivo en RAM: ideal para guardar descargas descifradas de más de 500 MB. Chrome, Edge y Opera la soportan; Firefox y Safari solo implementan un subconjunto de solo lectura mediante showOpenFilePicker.

Usa la Cache API para objetos HTTP Response: tu bundle de JS, CSS, iconos y posiblemente respuestas de API en caché. Está optimizada para la intercepción de fetch en Service Workers.

Usa OPFS (una rama Origin-Private especial de la File System Access API) cuando quieras almacenamiento rápido en sandbox no visible para el usuario, por ejemplo un buffer de escritura durante un pase de cifrado de varios gigabytes. Alcanza un rendimiento de disco un orden de magnitud superior a IndexedDB para blobs binarios.

IndexedDB sin las asperezas

La IndexedDB en bruto tiene una API basada en eventos notoriamente incómoda. Usa el paquete idb de Jake Archibald (1,5 KB comprimido) o Dexie.js (20 KB, API de consulta más rica):

import { openDB } from 'idb';
const db = await openDB('transfers', 2, {
  upgrade(db, oldVersion) {
    if (oldVersion < 1) {
      const sessions = db.createObjectStore('sessions', { keyPath: 'id' });
      sessions.createIndex('by_expiry', 'expiresAt');
    }
    if (oldVersion < 2) {
      db.createObjectStore('chunks', { keyPath: ['sessionId', 'index'] });
    }
  }
});
await db.put('sessions', { id: 'abc', fileName: 'informe.pdf', expiresAt: Date.now() + 86400000 });

Las migraciones de versión se ejecutan en el callback upgrade. Siempre protege las migraciones con oldVersion para que los usuarios que salten de v1 a v3 reciban ambos pasos.

IndexedDB maneja la mayoría de formas de datos, incluyendo Blobs y referencias a File, mediante clon estructurado. Eso significa que puedes almacenar un handle de File en un registro de sesión y releer los bytes del archivo original después de recargar la pestaña, perfecto para subidas reanudables.

File System Access API para descargas grandes

La API te permite entregar un stream escribible al diálogo de guardado del navegador:

const handle = await window.showSaveFilePicker({
  suggestedName: 'archivo-descifrado.zip',
  types: [{ description: 'Zip', accept: { 'application/zip': ['.zip'] } }]
});
const writable = await handle.createWritable();
await decryptionStream.pipeTo(writable);

Los bytes fluyen directamente al disco, sin pasar nunca por el heap de JS. Esta es la única forma práctica de guardar un archivo descifrado de 10 GB en el navegador.

Para Firefox y Safari, recurre a StreamSaver.js, que usa un Service Worker para sintetizar una respuesta en streaming que activa la interfaz de descarga. Misma ergonomía, un poco más de partes móviles.

Los handles de archivo persistentes también permiten a una app reabrir archivos entre sesiones. Una vez que el usuario concede permiso mediante showOpenFilePicker, puedes persistir el FileSystemFileHandle en IndexedDB y luego llamar a handle.requestPermission() para recuperar el acceso sin volver a solicitar permiso para cada archivo.

OPFS para espacio de trabajo temporal

El Origin Private File System es un almacenamiento en sandbox por origen que se comporta como un sistema de archivos pero no es visible para el usuario:

const root = await navigator.storage.getDirectory();
const fh = await root.getFileHandle('scratch.bin', { create: true });
const access = await fh.createSyncAccessHandle(); // solo en workers
access.write(buffer, { at: offset });
access.flush();
access.close();

createSyncAccessHandle solo está disponible dentro de Web Workers (incluidos Service Workers). Es síncrono y extremadamente rápido: los benchmarks muestran 3-10x IndexedDB para escrituras secuenciales. Úsalo para almacenar algunos cientos de megabytes de salida de cifrado antes de subirlos, o para cachear una copia de trabajo descifrada sin contaminar la carpeta de Descargas del usuario.

Safari 17 lanzó OPFS con handles de acceso síncrono; Firefox 111 siguió. Los tres navegadores principales ahora lo soportan, lo que lo hace viable para código en producción.

Cuotas de almacenamiento y cómo sobrevivirlas

Todas las APIs comparten el mismo grupo de cuota de origen. Techos aproximados:

  • Chrome de escritorio: 60% del disco libre
  • Firefox de escritorio: 50% del disco libre, limitado a 2 GB por origen de forma predeterminada
  • Safari de escritorio: aviso de 1 GB, crece hasta ~20% del disco con aprobación del usuario
  • iOS Safari: 1 GB por origen, desalojo agresivo cada 7 días si no se usa
  • Chrome Android: 10% del disco libre, desalojo bajo presión

Comprueba la cuota en tiempo de ejecución:

const { quota, usage } = await navigator.storage.estimate();
console.log(`Usando ${(usage/1e9).toFixed(2)} GB de ${(quota/1e9).toFixed(2)} GB`);

Solicita persistencia para almacenes críticos:

const persisted = await navigator.storage.persist();

Devuelve true si el navegador otorgó almacenamiento persistente, lo que significa que no lo desalojará bajo presión. Chrome lo concede automáticamente a sitios con los que el usuario ha interactuado significativamente; Firefox pregunta.

Cache API para shell de app y offline

La Cache API almacena pares Request + Response y es la elección correcta dentro de los Service Workers:

const cache = await caches.open('shell-v7');
await cache.addAll([
  '/', '/app.js', '/app.css', '/icons/192.png'
]);

Recuperar en la intercepción:

self.addEventListener('fetch', (e) => {
  e.respondWith(caches.match(e.request).then(r => r ?? fetch(e.request)));
});

No pongas bytes de archivos cifrados en la Cache API. Un objeto de respuesta de 2 GB supera la cuota de iOS Safari de un solo golpe y no puede recuperarse por rangos después. Los bytes pertenecen a OPFS o directamente al disco mediante la File System Access API.

Manejar el desalojo y la pérdida de datos con elegancia

El almacenamiento no persistido se desaloja: debes planificarlo. iOS Safari desaloja después de 7 días sin uso, independientemente de la cuota. Chrome desaloja solo cuando el disco está realmente bajo presión. Firefox desaloja los orígenes menos recientemente usados una vez que el grupo de cuotas se llena.

Dos patrones defensivos:

  • Escribe cualquier estado que no puedas recrear (IDs de sesión de subida, offsets de fragmentos parciales) en un formato amigable con la recarga para que una carga de página nueva pueda obtener de nuevo del servidor y continuar.
  • Para estado de larga duración, solicita navigator.storage.persist() y muestra una interfaz de usuario cuando el navegador pregunte.

Mantén el servidor como fuente de verdad para cualquier cosa que no puedas permitirte perder. Trata el almacenamiento del navegador como una caché rápida que podría desaparecer de la noche a la mañana.

Minas terrestres de compatibilidad con navegadores

Tres trampas aparecen una y otra vez:

  1. indexedDB.databases() no está soportado en Firefox (los usuarios que optaron por "eliminar cookies al cerrar" pierden todo el contenido de IndexedDB sin que se disparen eventos).
  2. FileSystemFileHandle.queryPermission() se comporta de forma diferente tras las recargas: a veces devuelve 'prompt' incluso cuando se había concedido. Siempre llama a requestPermission() de forma defensiva.
  3. El modo privado/incógnito da a las tres APIs una cuota separada, más pequeña y solo de sesión. El código que funciona en navegación normal puede llegar a QuotaExceededError inmediatamente en ventanas privadas.

HexaTransfer usa IndexedDB para el estado de sesión, OPFS para el almacenamiento temporal de ciphertext durante el cifrado en streaming y la File System Access API para descargas descifradas de 10 GB en navegadores compatibles. Pruébalo en https://hexatransfer.com — gratuito, sin cuenta, hasta 10 GB.

Elegir una pila para tu aplicación

Para la mayoría de apps de transferencia, la combinación correcta es: wrapper idb sobre IndexedDB para metadatos, handles de acceso síncronos OPFS para espacio de trabajo de cifrado/descifrado, Cache API para el shell de la app dentro de un Service Worker, File System Access API para descargas finales con fallback de StreamSaver, y una llamada a navigator.storage.persist() durante el proceso de incorporación. Eso cubre todos los navegadores actuales, se mantiene dentro de las cuotas en móvil y se recupera con elegancia cuando algo se desaloja.

Envía archivos grandes de forma segura con cifrado de extremo a extremo

Transfiere archivos de hasta 10 GB gratis con cifrado de extremo a extremo. Sin necesidad de cuenta. Tus archivos se cifran en tu navegador antes de subirlos: nadie más puede leerlos.

Enviar un archivo