Search code examples
progressive-web-appsservice-workernative-file-system-api-js

Store the path to uploaded file on client-side or the file outside the browser for offline


Is there a way to store the path to file which user wants to upload, but doesn't have an internet connection (it's a PWA) and reupload it when a connection is back? Or maybe not store the path, but save the file outside browser storage, somewhere on the user's machine (even if it will require some acceptance from the user to allow the browser to read/write files), but I'm not sure if it's even allowed to do.

Currently, I'm storing the whole file as a base64 in IndexedDB, but it's crashing/slowing down the browser when it comes to reading big files (around 100MB). Also, I don't want to overload browser storage.


Solution

  • There's a couple of things to consider.

    Storing the data you need to upload in IndexedDB and then reading that in later will be the most widely supported approach. As you say, though, it means taking up extra browser storage. One thing that might help is to skip the step of encoding the file in Base64 first, as in all modern browsers, IndexedDB will gladly store bytes directly for you as a Blob.

    A more modern approach, but one that's not currently supported by non-Chromium browsers, would be to use the File System Access API. As described in this article, once you get the user's permission, you can save a handle to a file in IndexedDB, and then read the file later on (assuming the underlying file hasn't changed in the interim). This has the advantage of not duplicating the file's contents in IndexedDB, saving on storage space. Here's a code snippet, borrowed from the article:

    import { get, set } from 'https://unpkg.com/[email protected]/dist/esm/index.js';
    
    const pre = document.querySelector('pre');
    const button = document.querySelector('button');
    
    button.addEventListener('click', async () => {
      try {
        // Try retrieving the file handle.
        const fileHandleOrUndefined = await get('file');    
        if (fileHandleOrUndefined) {      
          pre.textContent =
              `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
          return;
        }
        // This always returns an array, but we just need the first entry.
        const [fileHandle] = await window.showOpenFilePicker();
        // Store the file handle.
        await set('file', fileHandle);    
        pre.textContent =
            `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
      } catch (error) {
        alert(error.name, error.message);
      }
    });
    

    Regardless of how you store the file, it would be helpful to use the Background Sync API when available (again, currently limited to Chromium browsers) to handle automating the upload once the network is available again.