Caché de archivos con Service Worker para transferencia offline
Usa Service Workers para habilitar capacidades de transferencia sin conexión: estrategias de caché, sincronización en segundo plano y patrones PWA.
Los Service Workers permiten que una aplicación de transferencia de archivos siga funcionando cuando cae la red: almacena en caché el shell HTML y el JS con la Cache API, encola las subidas fallidas con la Background Sync API, guarda fragmentos parciales en IndexedDB y reproduce todo cuando vuelve la conectividad. El worker se ejecuta en un hilo separado con su propio bucle de eventos, intercepta eventos fetch para su ámbito y persiste a través de cierres de pestañas. Para herramientas de subida, la receta correcta es una estrategia stale-while-revalidate para el shell de la aplicación, encola de fragmentos respaldada por IndexedDB para transferencias en curso, y un registro de Background Sync periódico que reintenta las subidas cada 15 minutos hasta que tienen éxito.
Qué hacen realmente los Service Workers para las apps de transferencia
La gran ventaja es que navigator.serviceWorker sobrevive a recargas de pestaña, períodos offline e incluso al reposo del teléfono. Cuando un usuario comienza una subida de 2 GB en un Wi-Fi inestable del tren, quieres que los fragmentos ya completados sigan completados, los que están en vuelo se reintenten al reconectar, y todo el estado sea recuperable si el navegador cierra la pestaña para reclamar memoria. Un Service Worker, que se ejecuta independientemente de cualquier pestaña específica, es la pieza que habilita todo eso.
La API te da tres bloques de construcción: Cache para almacenar respuestas por URL, IndexedDB para datos estructurados (colas de fragmentos, estado de sesión) y SyncManager para programar reintentos que se disparan cuando el dispositivo está en línea.
Registrar y versionar el worker
Registra una vez al cargar la aplicación y gestiona las actualizaciones explícitamente:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
.then((reg) => reg.addEventListener('updatefound', () => {
const sw = reg.installing;
sw.addEventListener('statechange', () => {
if (sw.state === 'installed' && navigator.serviceWorker.controller) {
// nueva versión lista, pide al usuario que recargue
}
});
}));
}
Versiona las claves de caché (transfer-v7) para que un nuevo despliegue invalide los activos antiguos sin dejar JavaScript obsoleto. El bug clásico: index.html se cachea para siempre, los usuarios nunca reciben la actualización, y te quedas depurando por Slack durante semanas. Ancla los cachés a hashes de compilación y límpialos en el evento activate.
Estrategias de caché para shell de app frente a datos de usuario
Diferentes recursos merecen diferentes estrategias:
- Shell de app (HTML, CSS, JS, iconos): cache-first con fallback de red. Cargas instantáneas, funciona offline.
- Metadatos de API (
/shares/:id): network-first con fallback de caché, TTL de 60 segundos. Actualizado cuando hay conexión, usable cuando no la hay. - Bytes de archivo: nunca almacenar en caché. Los archivos suelen ser de varios gigabytes y la Cache API tiene cuotas por origen (típicamente el 60% del disco libre).
- Fuentes de CDN: stale-while-revalidate. Rápido y con actualización automática.
En el manejador de fetch:
self.addEventListener('fetch', (e) => {
const url = new URL(e.request.url);
if (url.pathname.startsWith('/assets/')) {
e.respondWith(cacheFirst(e.request, 'shell-v7'));
} else if (url.pathname.startsWith('/api/shares/')) {
e.respondWith(networkFirst(e.request, 'api-v1', 60));
}
});
Nunca interceptes peticiones de subidas de archivos binarios: enrútalas comprobando e.request.method === 'PUT' y devuelve early. Redirigir PUT de gigabytes a través del worker es un desastre de memoria.
Encolar subidas fallidas con Background Sync
SyncManager es la clave para subidas resilientes. Cuando un PUT de fragmento falla, guárdalo en IndexedDB y registra una sincronización:
// en el código de la página
const reg = await navigator.serviceWorker.ready;
await reg.sync.register('flush-uploads');
// en sw.js
self.addEventListener('sync', (event) => {
if (event.tag === 'flush-uploads') {
event.waitUntil(flushPendingUploads());
}
});
El navegador dispara el evento sync una vez que vuelve la red, con retroceso exponencial hasta aproximadamente 24 horas. Chrome y Edge lo soportan; Safari lanzó un subconjunto en 17.5 bajo el indicador "Background Fetch" pero requiere permiso del usuario. Para Safari, recurre a reintentar en la próxima apertura de página visible mediante visibilitychange.
La Background Fetch API es una herramienta separada específicamente para operaciones de archivos grandes: muestra una notificación persistente en la interfaz del navegador para que los usuarios puedan rastrear el progreso incluso después de cerrar la pestaña. Vale la pena usarla para subidas de más de 500 MB.
Almacenar subidas parciales en IndexedDB
IndexedDB es tu espacio de trabajo duradero. Abre una pequeña base de datos una vez y guarda metadatos de sesión más offsets de fragmentos:
const db = await openDB('transfers', 1, {
upgrade(db) {
db.createObjectStore('sessions', { keyPath: 'id' });
db.createObjectStore('chunks', { keyPath: ['sessionId', 'index'] });
}
});
await db.put('sessions', {
id, fileName, fileSize, fileFingerprint, createdAt: Date.now(),
completedIndexes: [], partUrls
});
No almacenes bytes de fragmentos en bruto: provienen del handle del File, que IndexedDB puede persistir como una referencia de clon estructurado que permanece válida a través de recargas. Almacenar el handle evita duplicar 2 GB de bytes en la base de datos.
El almacenamiento del origen tiene límites: aproximadamente el 60% del disco libre en Chrome de escritorio, 1 GB por origen en iOS Safari antes de que entre en juego la presión de desalojo. Solicita navigator.storage.persist() para obtener el depósito "persistente" que los navegadores evitan desalojar automáticamente.
Gestionar transiciones de offline a online
Escucha los eventos online y offline, tanto en la página como en el service worker:
// página
window.addEventListener('online', () => {
ui.showBanner('De vuelta en línea — reanudando subidas');
navigator.serviceWorker.controller?.postMessage({ type: 'resume' });
});
window.addEventListener('offline', () => {
ui.showBanner('Sin conexión — subidas pausadas');
});
navigator.onLine es notoriamente poco fiable en portales cautivos corporativos: informa true cuando el dispositivo tiene conexión de red local pero no internet. Para detección fiable, haz un pequeño fetch('/ping', { cache: 'no-store' }) con un tiempo de espera de 3 segundos.
Convertirlo en una PWA real
Incluye un manifest.json con display: standalone, un conjunto de iconos y start_url: /. Añade enlaces apple-touch-icon para iOS. Declara el manejo de archivos para que el SO pueda asociar tu app con extensiones específicas:
{
"name": "Hex Transfer",
"file_handlers": [{
"action": "/share-target",
"accept": { "application/*": [".pdf", ".zip", ".docx"] }
}]
}
Combinado con Web Share Target, esto permite a los usuarios compartir archivos desde el menú de compartir de su SO directamente en tu aplicación. En Chrome Android y Chromium de escritorio, la PWA puede registrarse como manejador predeterminado para los tipos de archivo que declaras. Esto convierte una página de navegador en una aplicación que se comporta como una herramienta de subida nativa.
Probar el funcionamiento offline
Tres escenarios que probar manualmente, porque las pruebas offline automatizadas son inestables:
- Inicia una subida de 500 MB en Wi-Fi rápido, cambia a modo avión al 30%, espera 30 segundos, activa el Wi-Fi de nuevo. La subida debe reanudarse desde donde se detuvo sin acción del usuario.
- Inicia una subida, cierra la pestaña al 60%, espera 2 minutos, vuelve a abrirla. Ofrece reanudar la sesión.
- Inicia una subida en móvil, bloquea la pantalla 5 minutos. El Background Sync debe dispararse al desbloquear y finalizar la transferencia.
La casilla "Offline" de las DevTools de Chrome y Application > Service Workers > Update on reload son indispensables. Los perfiles de "Throttling" del panel Network te permiten simular Fast 3G y Slow 3G para ver cómo se comporta tu interfaz de error.
La aplicación web de HexaTransfer usa un Service Worker para el caché del shell de la app e IndexedDB para el estado de sesión en vuelo, de modo que las recargas y los breves períodos offline no pierden el progreso de subida. Pruébalo en https://hexatransfer.com — gratuito, sin cuenta, hasta 10 GB.
Problemas que conviene conocer
Los Service Workers tienen una pequeña pila de peculiaridades que atrapan a los recién llegados: solo funcionan en HTTPS (excepto localhost), las cuotas de caché varían enormemente entre navegadores, iOS Safari no activa de forma fiable los workers para Background Sync, DevTools puede cachear workers obsoletos de forma agresiva (siempre haz clic en "Bypass for network" mientras desarrollas), e importScripts se ejecuta de forma síncrona durante la instalación así que nunca cargues scripts de terceros lentos allí. Escribe una pequeña prueba de integración que compruebe que el worker se activa, reclama clientes y sirve la página offline: esa única prueba detecta el 80% de las regresiones que encontrarás en producción.
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