如何在浏览器JavaScript中实现类似vscode.dev的用户授权式文件读写功能
Hey there! The functionality you're aiming for is totally achievable using the File System Access API—this is exactly what tools like vscode.dev rely on to enable cross-platform file operations with user consent. Let’s walk through how to fill in your placeholder functions and get this working properly.
Key Background
Unlike some other APIs, the File System Access API doesn’t use a separate "permission request" dialog. Instead, consent is granted implicitly when the user selects a file or directory via the API’s picker methods. We’ll need to store the selected directory handle to perform all subsequent file operations.
Updated Working Code
Here’s your modified HTML framework with fully implemented functions using the File System Access API:
<!DOCTYPE html> <html> <head> <title>File permission web</title> <script> // Store the selected directory handle for reuse across functions let directoryHandle; // Request access to a directory (this is your permission flow) const reqPerm = async () => { try { // Prompt user to select a directory, requesting read/write access upfront directoryHandle = await window.showDirectoryPicker({ mode: 'readwrite' }); alert('Permission granted! You can now manage files in the selected directory.'); } catch (err) { // Handle user cancellation or errors console.error('Permission denied or error:', err); alert('Permission denied or an error occurred.'); } }; // Create a new file in the selected directory const createFile = async (fileName) => { if (!directoryHandle) { alert('Please grant directory access first!'); return; } try { // Create or overwrite the file if it exists const fileHandle = await directoryHandle.getFileHandle(fileName, { create: true }); // Open a writable stream and write empty initial content const writable = await fileHandle.createWritable(); await writable.write(''); await writable.close(); alert(`Successfully created ${fileName}!`); } catch (err) { console.error('Error creating file:', err); alert('Failed to create the file.'); } }; // Delete a file from the selected directory const deleteFile = async (fileName) => { if (!directoryHandle) { alert('Please grant directory access first!'); return; } try { // Remove the file from the directory await directoryHandle.removeEntry(fileName); alert(`Successfully deleted ${fileName}!`); } catch (err) { console.error('Error deleting file:', err); alert('Failed to delete the file (it may not exist).'); } }; // Edit content of an existing file const editFile = async (fileName, content) => { if (!directoryHandle) { alert('Please grant directory access first!'); return; } try { const fileHandle = await directoryHandle.getFileHandle(fileName); const writable = await fileHandle.createWritable(); // Overwrite existing content with the new value await writable.write(content); await writable.close(); alert(`Successfully updated ${fileName}!`); } catch (err) { console.error('Error editing file:', err); alert('Failed to edit the file (it may not exist).'); } }; // View content of an existing file const viewFile = async (fileName) => { if (!directoryHandle) { alert('Please grant directory access first!'); return; } try { const fileHandle = await directoryHandle.getFileHandle(fileName); const file = await fileHandle.getFile(); const content = await file.text(); alert(`Content of ${fileName}:\n\n${content}`); } catch (err) { console.error('Error viewing file:', err); alert('Failed to view the file (it may not exist).'); } }; </script> </head> <body> <center> <h1>File permission</h1> </br> <button onclick="reqPerm()">Get permission (Select Directory)</button> </br> <button onclick="createFile('hello.txt')">Create hello.txt</button> </br> <button onclick="deleteFile('hello.txt')">Delete hello.txt</button> </br> <button onclick="editFile('hello.txt', 'hellomg')">Edit hello.txt</button> </br> <button onclick="viewFile('hello.txt')">View hello.txt</button> </center> </body> </html>
Key Details to Know
- Relative Paths: All file operations work relative to the directory the user selects—you don’t need full system paths anymore (like
/home/runner/renne/hello.txt), just the file name. - Persistence Across Sessions: To retain access when the user reloads the page, you can save the directory handle to IndexedDB. Add this snippet to your script:
// Save handle to IndexedDB const saveHandle = async () => { if (!directoryHandle) return; await navigator.storage.persist(); const db = await indexedDB.open('FileAccessDB', 1); db.onupgradeneeded = () => db.createObjectStore('handles'); const tx = db.transaction('handles', 'readwrite'); tx.objectStore('handles').put(directoryHandle, 'selectedDir'); await tx.complete(); }; // Load handle on page load window.addEventListener('load', async () => { const db = await indexedDB.open('FileAccessDB', 1); const tx = db.transaction('handles', 'readonly'); const storedHandle = await tx.objectStore('handles').get('selectedDir'); if (storedHandle) { // Verify permission is still granted const permission = await storedHandle.requestPermission({ mode: 'readwrite' }); if (permission === 'granted') { directoryHandle = storedHandle; alert('Restored access to your previously selected directory!'); } } }); - Compatibility: This API works in all Chromium-based browsers (Chrome, Edge, Brave) across Windows, macOS, Linux, and Chromebooks—matching vscode.dev’s support.
Critical Restrictions
- User Gesture Required: Calls to
showDirectoryPicker()must be triggered by a user action (like clicking a button). Browsers block automatic requests for security. - Sandboxed Access: You can only interact with files/directories the user explicitly selects—no arbitrary system path access without consent.
内容的提问来源于stack exchange,提问作者iRennegade




