Vai al contenuto
HexaTransfer
Torna al blog
Approfondimenti tecnici

API di archiviazione browser per applicazioni di trasferimento file

Usa IndexedDB, File System Access API e Cache API per app di trasferimento file. Limiti di archiviazione, prestazioni e compatibilità browser.

Il Garante per la protezione dei dati personali segnala regolarmente che i dati non protetti nei browser costituiscono un rischio per la privacy — e la scelta dell'API di archiviazione giusta determina sia la sicurezza che l'affidabilità di un'app di trasferimento file. L'archiviazione del browser per un'app di trasferimento si divide chiaramente tra quattro API: IndexedDB per i metadati strutturati di sessione e blocco (transazionale, asincrono, tipizzato), l'API File System Access per la lettura e scrittura di file multi-gigabyte direttamente sul disco dell'utente, l'API Cache per le risposte HTTP e gli asset della shell dell'app, e l'API Storage Manager per la gestione delle quote e i suggerimenti di persistenza. L'Origin Private File System (OPFS) si affianca a queste per I/O sandbox ad alte prestazioni. Scegliere quello giusto è importante perché i limiti variano da 1 GB su iOS Safari a "60% dello spazio libero su disco" su Chrome desktop, e la scelta sbagliata alla fine porta a QuotaExceededError in produzione.

Mappare ogni API al lavoro giusto

Usa IndexedDB per tutto ciò che assomiglia a una riga: sessioni di upload, indici di blocchi completati, metadati delle condivisioni, token di revoca. È asincrono, transazionale, indicizzabile, e sopravvive tra le sessioni.

Usa l'API File System Access quando hai bisogno di consegnare byte al disco senza caricare l'intero file in RAM — ideale per salvare download decifrati superiori a 500 MB. Chrome, Edge e Opera la supportano; Firefox e Safari implementano solo un sottoinsieme di sola lettura tramite showOpenFilePicker.

Usa l'API Cache per gli oggetti HTTP Response — il bundle JS, CSS, icone, e forse risposte API memorizzate nella cache. È ottimizzata per l'intercettazione di fetch nei Service Worker.

Usa OPFS (un ramo Origin-Private speciale dell'API File System Access) quando vuoi archiviazione sandbox veloce e non visibile all'utente, ad esempio un buffer di scrittura durante un passaggio di crittografia multi-gigabyte. Raggiunge throughput su disco un ordine di grandezza superiore rispetto a IndexedDB per i blob binari.

IndexedDB senza le asperità

L'API IndexedDB grezza ha un'API basata su eventi notoriamente scomoda. Usa il pacchetto idb di Jake Archibald (1,5 KB gzippato) o Dexie.js (20 KB, API di query più ricca):

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: 'report.pdf', expiresAt: Date.now() + 86400000 });

Le migrazioni delle versioni vengono eseguite nel callback upgrade. Proteggi sempre le migrazioni con oldVersion affinché gli utenti che saltano da v1 a v3 ricevano entrambi i passaggi.

IndexedDB gestisce la maggior parte delle forme di dati, compresi Blob e riferimenti File, tramite structured clone. Ciò significa che puoi memorizzare un File handle in un record di sessione e rileggere i byte del file originale dopo un ricaricamento della scheda — perfetto per gli upload ripristinabili.

API File System Access per i download di grandi dimensioni

L'API consente di consegnare un flusso scrivibile alla finestra di salvataggio del browser:

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

I byte fluiscono direttamente su disco, senza mai atterrare nell'heap JS. Questo è l'unico modo pratico per salvare un file decifrato da 10 GB nel browser.

Per Firefox e Safari, usa il fallback StreamSaver.js, che usa un Service Worker per sintetizzare una risposta in streaming che attiva l'interfaccia di download. Stessa ergonomia, un po' più di complessità.

Gli handle di file persistenti consentono anche a un'app di riaprire i file tra le sessioni. Una volta che l'utente concede il permesso tramite showOpenFilePicker, puoi persistere il FileSystemFileHandle in IndexedDB e in seguito chiamare handle.requestPermission() per riacquisire l'accesso senza richiedere nuovamente ogni volta.

OPFS per lo spazio di scratch

L'Origin Private File System è uno storage sandbox per origine che si comporta come un filesystem ma non è visibile all'utente:

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

createSyncAccessHandle è disponibile solo all'interno dei Web Worker (compresi i Service Worker). È sincrono ed estremamente veloce — i benchmark mostrano prestazioni 3-10 volte superiori a IndexedDB per le scritture sequenziali. Usalo per bufferizzare alcune centinaia di megabyte di output di crittografia prima di caricarli, o per memorizzare nella cache una copia di lavoro decifrata senza inquinare la cartella Download dell'utente.

Safari 17 ha rilasciato OPFS con handle di accesso sincrono; Firefox 111 ha seguito. Tutti e tre i principali browser ora lo supportano, il che lo rende praticabile per il codice in produzione.

Quote di storage e come sopravvivere

Ogni API condivide lo stesso pool di quote per origine. Soffitti approssimativi:

  • Chrome desktop: 60% dello spazio libero su disco
  • Firefox desktop: 50% dello spazio libero, limitato a 2 GB per origine di default
  • Safari desktop: avviso a 1 GB, cresce fino a circa il 20% del disco con approvazione dell'utente
  • iOS Safari: 1 GB per origine, evizione aggressiva dopo 7 giorni di non utilizzo
  • Chrome Android: 10% dello spazio libero, evizione sotto pressione

Controlla la quota in fase di esecuzione:

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

Richiedi la persistenza per gli store critici:

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

Restituisce true se il browser ha concesso lo storage persistente, il che significa che non lo eliminerà sotto pressione. Chrome lo concede automaticamente ai siti con cui l'utente ha interagito in modo significativo; Firefox chiede conferma.

API Cache per la shell dell'app e l'offline

L'API Cache memorizza coppie Request + Response ed è la scelta giusta all'interno dei Service Worker:

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

Recupera all'intercettazione:

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

Non mettere i byte dei file cifrati nell'API Cache. Un oggetto risposta da 2 GB supera immediatamente la quota di iOS Safari e non può essere recuperato tramite range in seguito. I byte appartengono a OPFS o direttamente su disco tramite File System Access.

Gestire l'evizione e la perdita di dati con grazia

Lo storage non persistente viene eliminato — devi pianificarlo. iOS Safari elimina dopo 7 giorni di non utilizzo, indipendentemente dalla quota. Chrome elimina solo quando il disco è effettivamente sotto pressione. Firefox elimina le origini usate meno di recente quando il pool di quota si riempie.

Due pattern difensivi:

  • Scrivi qualsiasi stato che non puoi ricreare (ID di sessione di upload, offset dei blocchi parziali) in un formato reload-friendly affinché un nuovo caricamento della pagina possa recuperare dal server e continuare.
  • Per lo stato a lungo termine, richiedi navigator.storage.persist() e mostra un'interfaccia agli utenti da confermare quando il browser chiede.

Mantieni il server come fonte di verità per tutto ciò che non puoi permetterti di perdere. Tratta lo storage del browser come una cache veloce che potrebbe sparire durante la notte.

Mine di compatibilità del browser

Tre trappole appaiono continuamente:

  1. L'enumerazione indexedDB.databases() non è supportata in Firefox (gli utenti che hanno scelto "cancella i cookie alla chiusura" perdono tutto il contenuto IndexedDB senza che vengano attivati eventi).
  2. FileSystemFileHandle.queryPermission() si comporta diversamente dopo i ricaricamenti — a volte restituisce 'prompt' anche quando il permesso è stato concesso. Chiama sempre requestPermission() in modo difensivo.
  3. La modalità privata/incognito dà a tutte e tre le API una quota separata, più piccola e solo per la sessione. Il codice che funziona nella navigazione normale può incontrare immediatamente QuotaExceededError nelle finestre private.

HexaTransfer usa IndexedDB per lo stato della sessione, OPFS per il buffering del testo cifrato durante la crittografia in streaming, e l'API File System Access per i download decifrati da 10 GB sui browser supportati. Prova su https://hexatransfer.com — gratuito, senza account, massimo 10 GB.

Scegliere uno stack per la tua app

Per la maggior parte delle app di trasferimento, la combinazione giusta è: wrapper idb su IndexedDB per i metadati, handle di accesso sincrono OPFS per lo spazio di scratch di cifratura/decifratura, API Cache per la shell dell'app all'interno di un Service Worker, API File System Access per i download finali con fallback StreamSaver, e una chiamata a navigator.storage.persist() durante l'onboarding. Questo copre ogni browser in distribuzione oggi, rimane sotto le quote su mobile, e si recupera agevolmente quando qualcosa viene eliminato. Costruisci piccoli adattatori attorno a ogni API affinché il giorno in cui OPFS rilasci un nuovo metodo o Safari alzi la sua quota, cambi un file e distribuisci.

Invia file di grandi dimensioni in modo sicuro con crittografia end-to-end

Trasferisci file fino a 10 GB gratuitamente con crittografia end-to-end. Nessun account necessario. I tuoi file vengono crittografati nel browser prima del caricamento — nessun altro può leggerli.

Invia un file