JS `File System Access API` (浏览器):读写本地文件系统与沙箱限制

All right, gather ’round, code wranglers! Let’s talk about the File System Access API – the browser’s attempt to let you poke around (safely-ish) on the user’s hard drive. Think of it as giving your web app a tiny, heavily supervised sandbox to play in, rather than letting it loose with a bulldozer.

Why Bother? (The Allure of Local Files)

For ages, web apps have been stuck in a world of limited file access. You could upload, you could download (with a download prompt, of course), but actually working with files directly was a pain. Imagine a text editor that couldn’t save directly, or an image editor that made you download after every tweak. The File System Access API aims to fix that.

The Basics: Picking Your Poison (File Handles)

The API revolves around "handles." Think of them as keys – you get a handle to a file or directory, and then you can do things with it. There are a few ways to snag these handles:

  • showOpenFilePicker(): This pops up a familiar "Open File" dialog, letting the user choose one or more files. The browser retains permissions for the selected file(s) across sessions.
  • showSaveFilePicker(): Similar to the above, but for saving. You get a handle to a file (potentially a new one) that you can then write to. Also, the browser retains permissions for the selected file across sessions.
  • showDirectoryPicker(): This one’s for grabbing a handle to an entire directory. Be warned: users are often wary of giving web apps access to directories.

Let’s see some code:

async function openTextFile() {
  try {
    // Show the file picker, but only allow text files
    const [fileHandle] = await window.showOpenFilePicker({
      types: [{
        description: 'Text files',
        accept: {
          'text/plain': ['.txt'],
        }
      }],
      multiple: false,
    });

    // Get the actual file from the handle
    const file = await fileHandle.getFile();
    const contents = await file.text();

    console.log("File contents:", contents);
    // Do something with the file contents (e.g., display in a text area)
  } catch (err) {
    // Handle errors (user cancelled, permissions denied, etc.)
    console.error("Error opening file:", err);
  }
}

async function saveTextFile(content) {
  try {
    const fileHandle = await window.showSaveFilePicker({
      types: [{
        description: 'Text files',
        accept: {
          'text/plain': ['.txt'],
        },
      }],
    });

    // Create a writable stream
    const writable = await fileHandle.createWritable();

    // Write the content
    await writable.write(content);

    // Close the file and write the contents to disk.
    await writable.close();

    console.log("File saved!");
  } catch (err) {
    console.error("Error saving file:", err);
  }
}

Explanation:

  • async/await: This makes the code easier to read. These picker functions are asynchronous, meaning they take time (the user has to pick a file, after all). await pauses execution until the promise resolves.
  • window.showOpenFilePicker()/window.showSaveFilePicker(): These are the magic functions that trigger the native file picker dialog.
  • types: This allows you to filter which files the user can select. It’s polite to be specific!
  • fileHandle.getFile(): Gets the actual File object from the handle. This is a standard JavaScript File object, which you might already be familiar with (e.g., from <input type="file">).
  • file.text(): Reads the file’s contents as text.
  • fileHandle.createWritable(): This is how you create a stream to write to the file. Think of it as opening a pipe to the file.
  • writable.write(content): Writes your data to the stream.
  • writable.close(): Crucially important! This closes the stream and flushes the data to disk. Don’t forget this, or your changes might not be saved.
  • Error Handling: Always wrap your file operations in try...catch blocks. Users can cancel the dialog, deny permissions, or the file might be corrupted. Be prepared for things to go wrong.

Directory Access: Tread Carefully

Getting access to a directory is a bigger deal than getting access to a single file. Users are understandably wary of giving web apps access to their file systems.

async function openDirectory() {
  try {
    const directoryHandle = await window.showDirectoryPicker();

    // Now you can iterate over the directory's contents
    for await (const entry of directoryHandle.values()) {
      console.log(entry.name, entry.kind); // entry.kind is "file" or "directory"
      //You can read file contents from here too
    }
  } catch (err) {
    console.error("Error opening directory:", err);
  }
}

Key Points about Directory Access:

  • User Consent is Paramount: Be very clear about why you need access to a directory. Explain the benefits to the user. Don’t just ask for access to their entire home directory!
  • Iteration: You can iterate through the directory’s entries using directoryHandle.values(). Each entry will be either a FileSystemFileHandle (for files) or a FileSystemDirectoryHandle (for subdirectories).
  • Recursive Traversal: If you want to explore subdirectories, you’ll need to write a recursive function. Be careful to avoid infinite loops!
  • Permissions: The user can revoke directory access at any time. Be prepared to handle this gracefully.

The Sandbox: Keeping Things Safe(ish)

The File System Access API is designed with security in mind. Here’s a rundown of the sandbox limitations:

Feature Description
User Gestures Most file operations require a user gesture (e.g., a button click). This prevents malicious scripts from silently reading or writing files in the background.
Permissions The user must explicitly grant permission for each file or directory.
Same-Origin Policy The API is subject to the same-origin policy. You can only access files from the same origin as your web app (unless CORS is configured correctly, which is rare for local files).
No Arbitrary Paths You can’t just specify an arbitrary path like /etc/passwd. You must go through the file picker or directory picker.
Transient Permissions Permissions granted via showOpenFilePicker or showSaveFilePicker are persisted across sessions. This means the user doesn’t have to re-grant permission every time they visit your site. Directory access is not persisted in some browsers.
HTTPS Required The API generally requires a secure context (HTTPS). This helps prevent man-in-the-middle attacks.
File System Isolation Your web app doesn’t get direct access to the underlying file system. It works through the browser’s API, which provides a layer of abstraction and security.

Beyond the Basics: Advanced Techniques

  • Streaming Writes: For large files, you can use streams to write data in chunks. This prevents your app from freezing while writing the entire file to memory. We saw this above with createWritable().
  • FileSystemSyncAccessHandle: This allows synchronous file access from a Web Worker. This is useful for computationally intensive tasks that need to read and write files quickly. However, be careful not to block the worker thread. Requires OPFS (Origin Private File System)
  • The Origin Private File System (OPFS): The File System Access API has an origin-private file system that is accessible to websites. This is a sandboxed file system that is private to the origin of the website. This is useful for storing data that should not be accessible to other websites.

Example: Saving Data Incrementally with FileSystemWritableFileStream

async function saveLargeFile(dataChunks) {
  try {
    const fileHandle = await window.showSaveFilePicker();
    const writableStream = await fileHandle.createWritable();

    for (const chunk of dataChunks) {
      await writableStream.write(chunk); // Write each chunk as it becomes available
    }

    await writableStream.close();
    console.log("Large file saved successfully!");
  } catch (error) {
    console.error("Error saving large file:", error);
  }
}

When to Use (and Not Use) the File System Access API

Use Cases:

  • Local Text Editors/IDEs: Directly saving and opening files is a huge improvement.
  • Image/Video Editors: Avoid the upload/download cycle for every minor edit.
  • Document Viewers/Converters: Working with local documents without server-side processing.
  • Games: Saving game state locally.

Don’t Use (or Use With Caution):

  • Anything Security-Sensitive: Don’t store sensitive information (passwords, API keys) in local files. Local files can be accessed by other applications or even by the user directly. Use secure storage mechanisms (e.g., browser’s localStorage or server-side storage).
  • Background File Synchronization: The API is designed for user-initiated actions. Don’t try to create a background file sync service without explicit user consent and control.
  • Replacing Server-Side Storage: The API is not a replacement for a proper database or server-side file storage. It’s for local file access.

Browser Support

Browser support is still evolving. Check caniuse.com before relying on this API in production. Chrome-based browsers are generally the most supportive. Safari and Firefox are catching up, but support may be partial or require enabling experimental features.

The Future

The File System Access API is still relatively new, and it’s likely to evolve over time. Expect to see improvements in browser support, new features, and potentially tighter security restrictions.

Summary Table

Feature Description Security Considerations
showOpenFilePicker() Opens a file picker dialog to select files. Requires user gesture, user must grant permission.
showSaveFilePicker() Opens a file picker dialog to save files. Requires user gesture, user must grant permission.
showDirectoryPicker() Opens a directory picker dialog to select a directory. Requires user gesture, user must grant permission (often treated with more caution by users).
FileSystemFileHandle Represents a handle to a file. Permissions are associated with the handle.
FileSystemDirectoryHandle Represents a handle to a directory. Permissions are associated with the handle.
createWritable() Creates a writable stream to write to a file. Remember to close() the stream after writing.
getFile() Gets the File object from a FileSystemFileHandle. The File object is subject to the same-origin policy.
OPFS Origin Private File System. Sandboxed file system accessible only to the origin of the website. Sandboxed, but still be mindful of the data you store.

In Conclusion

The File System Access API is a powerful tool that can significantly enhance the capabilities of web applications. But with great power comes great responsibility. Understand the security implications, respect user privacy, and always handle errors gracefully. Now go forth and build awesome things… responsibly! And for goodness sake, comment your code! (You’ll thank yourself later.)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注