File System Access API를 사용하면 웹 앱이 사용자의 기기에 있는 파일과 폴더를 직접 읽거나 변경사항을 저장할 수 있습니다.
게시일: 2024년 8월 19일
파일 시스템 액세스 API를 사용하면 개발자가 IDE, 사진 및 동영상 편집기, 텍스트 편집기 등 사용자 로컬 기기의 파일과 상호작용하는 강력한 웹 앱을 빌드할 수 있습니다. 사용자가 웹 앱 액세스 권한을 부여하면 이 API를 통해 사용자의 기기에 있는 파일과 폴더를 직접 읽거나 변경사항을 저장할 수 있습니다. 파일을 읽고 쓰는 것 외에도 File System Access API는 디렉터리를 열고 콘텐츠를 열거하는 기능을 제공합니다.
이전에 파일을 읽고 쓰는 작업을 해 본 적이 있다면 지금부터 설명할 내용이 대부분 익숙할 것입니다. 하지만 모든 시스템이 동일하지는 않으므로 읽어보시기 바랍니다.
파일 시스템 액세스 API는 Windows, macOS, ChromeOS, Linux, Android에서 대부분의 Chromium 브라우저에서 지원됩니다. Brave는 현재 플래그 뒤에서만 사용할 수 있습니다.
File System Access API 사용
File System Access API의 강력함과 유용성을 보여주기 위해 단일 파일 텍스트 편집기를 작성했습니다. 텍스트 파일을 열고, 수정하고, 변경사항을 디스크에 다시 저장하거나, 새 파일을 시작하고 변경사항을 디스크에 저장할 수 있습니다. 화려하지는 않지만 개념을 이해하는 데 충분한 정보를 제공합니다.
브라우저 지원
기능 감지
File System Access API가 지원되는지 알아보려면 관심 있는 선택기 메서드가 있는지 확인하세요.
if ('showOpenFilePicker' in self) {
// The `showOpenFilePicker()` method of the File System Access API is supported.
}
직접 해 보기
텍스트 편집기 데모에서 File System Access API의 실제 동작을 확인하세요.
로컬 파일 시스템에서 파일 읽기
첫 번째 사용 사례는 사용자에게 파일을 선택하라고 요청한 다음 디스크에서 해당 파일을 열고 읽는 것입니다.
사용자에게 읽을 파일을 선택하도록 요청
File System Access API의 진입점은 window.showOpenFilePicker()입니다. 호출되면 파일 선택기 대화상자를 표시하고 사용자에게 파일을 선택하라는 메시지를 표시합니다. 사용자가 파일을 선택하면 API는 파일 핸들 배열을 반환합니다. 선택사항인 options 매개변수를 사용하면 사용자가 여러 파일이나 디렉터리 또는 다양한 파일 형식을 선택할 수 있도록 허용하는 등 파일 선택기의 동작에 영향을 줄 수 있습니다.
옵션을 지정하지 않으면 파일 선택기를 통해 사용자가 단일 파일을 선택할 수 있습니다. 텍스트 편집기에 적합합니다.
다른 강력한 API와 마찬가지로 showOpenFilePicker() 호출은 보안 컨텍스트에서 이루어져야 하며 사용자 동작 내에서 호출해야 합니다.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
// Destructure the one-element array.
[fileHandle] = await window.showOpenFilePicker();
// Do something with the file handle.
});
사용자가 파일을 선택하면 showOpenFilePicker()는 핸들 배열을 반환합니다. 이 경우 파일과 상호작용하는 데 필요한 속성과 메서드가 포함된 FileSystemFileHandle이 하나 있는 단일 요소 배열입니다.
나중에 사용할 수 있도록 파일 핸들을 참조하는 것이 좋습니다. 파일에 변경사항을 저장하거나 다른 파일 작업을 실행하는 데 필요합니다.
파일 시스템에서 파일 읽기
이제 파일 핸들이 있으므로 파일의 속성을 가져오거나 파일 자체에 액세스할 수 있습니다.
일단은 콘텐츠를 읽어 드리겠습니다. handle.getFile()를 호출하면 blob이 포함된 File 객체가 반환됩니다. 블롭에서 데이터를 가져오려면 해당 메서드(slice(), stream(), text() 또는 arrayBuffer()) 중 하나를 호출합니다.
const file = await fileHandle.getFile();
const contents = await file.text();
FileSystemFileHandle.getFile()에서 반환된 File 객체는 디스크의 기본 파일이 변경되지 않는 한 읽기 전용입니다. 디스크의 파일이 수정되면 File 객체를 읽을 수 없게 되므로 변경된 데이터를 읽기 위해 새 File 객체를 가져오려면 getFile()를 다시 호출해야 합니다.
요약 정리
사용자가 열기 버튼을 클릭하면 브라우저에 파일 선택기가 표시됩니다. 파일을 선택하면 앱이 콘텐츠를 읽고 <textarea>에 넣습니다.
let fileHandle;
butOpenFile.addEventListener('click', async () => {
[fileHandle] = await window.showOpenFilePicker();
const file = await fileHandle.getFile();
const contents = await file.text();
textArea.value = contents;
});
파일을 로컬 파일 시스템에 씁니다.
텍스트 편집기에서 파일을 저장하는 방법은 저장과 다른 이름으로 저장 두 가지가 있습니다. Save는 이전에 가져온 파일 핸들을 사용하여 변경사항을 원본 파일에 다시 씁니다. 하지만 다른 이름으로 저장은 새 파일을 생성하므로 새 파일 핸들이 필요합니다.
새 파일 만들기
파일을 저장하려면 showSaveFilePicker()를 호출합니다. 그러면 파일 선택기가 '저장' 모드로 표시되어 사용자가 저장에 사용할 새 파일을 선택할 수 있습니다. 텍스트 편집기의 경우 .txt 확장 프로그램을 자동으로 추가하고 싶어서 몇 가지 추가 매개변수를 제공했습니다.
async function getNewFileHandle() {
const options = {
types: [
{
description: 'Text Files',
accept: {
'text/plain': ['.txt'],
},
},
],
};
const handle = await window.showSaveFilePicker(options);
return handle;
}
디스크에 변경사항 저장
GitHub의 텍스트 편집기 데모에서 파일에 변경사항을 저장하는 모든 코드를 확인할 수 있습니다. 핵심 파일 시스템 상호작용은 fs-helpers.js에 있습니다. 가장 간단한 경우 프로세스는 다음 코드와 같습니다.
각 단계를 살펴보고 설명해 드리겠습니다.
// fileHandle is an instance of FileSystemFileHandle..
async function writeFile(fileHandle, contents) {
// Create a FileSystemWritableFileStream to write to.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the file and write the contents to disk.
await writable.close();
}
디스크에 데이터를 쓰는 작업은 WritableStream의 서브클래스인 FileSystemWritableFileStream 객체를 사용합니다. 파일 핸들 객체에서 createWritable()를 호출하여 스트림을 만듭니다. createWritable()가 호출되면 브라우저는 먼저 사용자가 파일에 쓰기 권한을 부여했는지 확인합니다. 쓰기 권한이 부여되지 않은 경우 브라우저에서 사용자에게 권한을 요청합니다. 권한이 부여되지 않으면 createWritable()에서 DOMException를 발생시키고 앱은 파일에 쓸 수 없습니다. 텍스트 편집기에서 DOMException 객체는 saveFile() 메서드에서 처리됩니다.
write() 메서드는 문자열을 사용하며 이는 텍스트 편집기에 필요합니다. 하지만 BufferSource 또는 Blob을 사용할 수도 있습니다. 예를 들어 스트림을 직접 파이핑할 수 있습니다.
async function writeURLToFile(fileHandle, url) {
// Create a FileSystemWritableFileStream to write to.
const writable = await fileHandle.createWritable();
// Make an HTTP request for the contents.
const response = await fetch(url);
// Stream the response into the file.
await response.body.pipeTo(writable);
// pipeTo() closes the destination pipe by default, no need to close it.
}
스트림 내에서 seek() 또는 truncate()를 사용하여 특정 위치에서 파일을 업데이트하거나 파일 크기를 조정할 수도 있습니다.
추천 파일 이름 및 시작 디렉터리 지정
대부분의 경우 앱에서 기본 파일 이름이나 위치를 제안하도록 할 수 있습니다. 예를 들어 텍스트 편집기에서 Untitled 대신 Untitled Text.txt의 기본 파일 이름을 제안할 수 있습니다. showSaveFilePicker 옵션의 일부로 suggestedName 속성을 전달하여 이를 달성할 수 있습니다.
const fileHandle = await self.showSaveFilePicker({
suggestedName: 'Untitled Text.txt',
types: [{
description: 'Text documents',
accept: {
'text/plain': ['.txt'],
},
}],
});
기본 시작 디렉터리도 마찬가지입니다. 텍스트 편집기를 빌드하는 경우 기본 documents 폴더에서 파일 저장 또는 파일 열기 대화상자를 시작할 수 있지만 이미지 편집기의 경우 기본 pictures 폴더에서 시작할 수 있습니다. 다음과 같이 startIn 속성을 showSaveFilePicker, showDirectoryPicker() 또는 showOpenFilePicker 메서드에 전달하여 기본 시작 디렉터리를 제안할 수 있습니다.
const fileHandle = await self.showOpenFilePicker({
startIn: 'pictures'
});
잘 알려진 시스템 디렉터리 목록은 다음과 같습니다.
desktop: 사용자의 데스크톱 디렉터리(있는 경우)documents: 사용자가 만든 문서가 일반적으로 저장되는 디렉터리입니다.downloads: 다운로드한 파일이 일반적으로 저장되는 디렉터리입니다.music: 오디오 파일이 일반적으로 저장되는 디렉터리입니다.pictures: 사진 및 기타 정지 이미지가 일반적으로 저장되는 디렉터리입니다.videos: 동영상이나 영화가 일반적으로 저장되는 디렉터리입니다.
잘 알려진 시스템 디렉터리 외에도 기존 파일 또는 디렉터리 핸들을 startIn의 값으로 전달할 수 있습니다. 그러면 대화상자가 동일한 디렉터리에서 열립니다.
// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
startIn: directoryHandle
});
다양한 파일 선택기의 목적 지정
애플리케이션에 목적별로 다른 선택기가 있는 경우도 있습니다. 예를 들어 서식 있는 텍스트 편집기를 사용하면 사용자가 텍스트 파일을 열 수 있을 뿐만 아니라 이미지를 가져올 수도 있습니다. 기본적으로 각 파일 선택기는 마지막으로 기억된 위치에서 열립니다. 각 선택기 유형의 id 값을 저장하여 이 문제를 해결할 수 있습니다. id가 지정되면 파일 선택기 구현은 해당 id의 별도 마지막 사용 디렉터리를 기억합니다.
const fileHandle1 = await self.showSaveFilePicker({
id: 'openText',
});
const fileHandle2 = await self.showSaveFilePicker({
id: 'importImage',
});
IndexedDB에 파일 핸들 또는 디렉터리 핸들 저장
파일 핸들과 디렉터리 핸들은 직렬화 가능하므로 파일 또는 디렉터리 핸들을 IndexedDB에 저장하거나 postMessage()를 호출하여 동일한 최상위 출처 간에 전송할 수 있습니다.
파일 또는 디렉터리 핸들을 IndexedDB에 저장하면 상태를 저장하거나 사용자가 작업 중인 파일 또는 디렉터리를 기억할 수 있습니다. 이를 통해 최근에 열거나 수정한 파일 목록을 유지하고, 앱이 열릴 때 마지막 파일을 다시 열도록 제안하고, 이전 작업 디렉터리를 복원하는 등의 작업을 할 수 있습니다. 텍스트 편집기에서 사용자가 최근에 연 파일 5개의 목록을 저장하여 해당 파일에 다시 액세스할 수 있도록 합니다.
다음 코드 예는 파일 핸들과 디렉터리 핸들을 저장하고 검색하는 방법을 보여줍니다. Glitch에서 실제로 확인할 수 있습니다. (간결성을 위해 idb-keyval 라이브러리를 사용합니다.)
import { get, set } from 'https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js';
const pre1 = document.querySelector('pre.file');
const pre2 = document.querySelector('pre.directory');
const button1 = document.querySelector('button.file');
const button2 = document.querySelector('button.directory');
// File handle
button1.addEventListener('click', async () => {
try {
const fileHandleOrUndefined = await get('file');
if (fileHandleOrUndefined) {
pre1.textContent = `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
return;
}
const [fileHandle] = await window.showOpenFilePicker();
await set('file', fileHandle);
pre1.textContent = `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
} catch (error) {
alert(error.name, error.message);
}
});
// Directory handle
button2.addEventListener('click', async () => {
try {
const directoryHandleOrUndefined = await get('directory');
if (directoryHandleOrUndefined) {
pre2.textContent = `Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;
return;
}
const directoryHandle = await window.showDirectoryPicker();
await set('directory', directoryHandle);
pre2.textContent = `Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;
} catch (error) {
alert(error.name, error.message);
}
});
저장된 파일 또는 디렉터리 핸들 및 권한
권한은 세션 간에 항상 유지되지 않으므로 queryPermission()를 사용하여 사용자가 파일 또는 디렉터리에 권한을 부여했는지 확인해야 합니다. 아직 요청하지 않은 경우 requestPermission()를 호출하여 요청합니다. 파일 및 디렉터리 핸들에도 동일하게 적용됩니다. 각각 fileOrDirectoryHandle.requestPermission(descriptor) 또는 fileOrDirectoryHandle.queryPermission(descriptor)를 실행해야 합니다.
텍스트 편집기에서 사용자가 이미 권한을 부여했는지 확인하고 필요한 경우 요청하는 verifyPermission() 메서드를 만들었습니다.
async function verifyPermission(fileHandle, readWrite) {
const options = {};
if (readWrite) {
options.mode = 'readwrite';
}
// Check if permission was already granted. If so, return true.
if ((await fileHandle.queryPermission(options)) === 'granted') {
return true;
}
// Request permission. If the user grants permission, return true.
if ((await fileHandle.requestPermission(options)) === 'granted') {
return true;
}
// The user didn't grant permission, so return false.
return false;
}
읽기 요청과 함께 쓰기 권한을 요청하여 권한 메시지의 수를 줄였습니다. 사용자는 파일을 열 때 하나의 메시지를 보고 읽기 및 쓰기 권한을 모두 부여합니다.
디렉터리를 열고 콘텐츠를 열거합니다.
디렉터리의 모든 파일을 열거하려면 showDirectoryPicker()를 호출합니다. 사용자가 선택기에서 디렉터리를 선택하면 FileSystemDirectoryHandle가 반환되어 디렉터리의 파일을 열거하고 액세스할 수 있습니다. 기본적으로 디렉터리의 파일에 대한 읽기 액세스 권한이 있지만 쓰기 액세스 권한이 필요한 경우 { mode: 'readwrite' }를 메서드에 전달하면 됩니다.
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}
});
개별 파일 크기를 가져오는 등 getFile()를 사용하여 각 파일에 액세스해야 하는 경우 각 결과에서 순차적으로 await를 사용하지 말고 Promise.all()를 사용하여 모든 파일을 병렬로 처리하세요.
butDir.addEventListener('click', async () => {
const dirHandle = await window.showDirectoryPicker();
const promises = [];
for await (const entry of dirHandle.values()) {
if (entry.kind !== 'file') {
continue;
}
promises.push(entry.getFile().then((file) => `${file.name} (${file.size})`));
}
console.log(await Promise.all(promises));
});
디렉터리에서 파일 및 폴더 만들기 또는 액세스
디렉터리에서 getFileHandle() 또는 getDirectoryHandle() 메서드를 사용하여 각각 파일과 폴더를 만들거나 액세스할 수 있습니다. create 키와 true 또는 false 불리언 값이 있는 선택적 options 객체를 전달하여 파일이나 폴더가 없는 경우 새로 만들어야 하는지 확인할 수 있습니다.
// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });
디렉터리에서 항목의 경로 확인
디렉터리에서 파일이나 폴더로 작업할 때 문제가 되는 항목의 경로를 확인하는 것이 유용할 수 있습니다. 이 작업은 적절한 이름의 resolve() 메서드를 사용하여 실행할 수 있습니다. 해결을 위해 항목은 디렉터리의 직접 또는 간접 하위 항목일 수 있습니다.
// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]
디렉터리에서 파일 및 폴더 삭제
디렉터리에 대한 액세스 권한을 획득한 경우 removeEntry() 메서드를 사용하여 포함된 파일과 폴더를 삭제할 수 있습니다. 폴더의 경우 삭제가 선택적으로 재귀적일 수 있으며 모든 하위 폴더와 그 안에 포함된 파일을 포함할 수 있습니다.
// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });
파일 또는 폴더를 직접 삭제하는 경우
파일 또는 디렉터리 핸들에 액세스할 수 있는 경우 FileSystemFileHandle 또는 FileSystemDirectoryHandle에서 remove()를 호출하여 삭제합니다.
// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();
파일 및 폴더 이름 바꾸기 및 이동
FileSystemHandle 인터페이스에서 move()를 호출하여 파일과 폴더의 이름을 변경하거나 새 위치로 이동할 수 있습니다. FileSystemHandle에는 하위 인터페이스 FileSystemFileHandle 및 FileSystemDirectoryHandle가 있습니다. move() 메서드는 매개변수를 하나 또는 두 개 사용합니다. 첫 번째는 새 이름이 포함된 문자열이거나 대상 폴더의 FileSystemDirectoryHandle일 수 있습니다. 후자의 경우 선택사항인 두 번째 매개변수는 새 이름이 포함된 문자열이므로 이동과 이름 변경이 한 단계로 이루어질 수 있습니다.
// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');
드래그 앤 드롭 통합
HTML 드래그 앤 드롭 인터페이스를 사용하면 웹 애플리케이션이 웹페이지에서 드래그 앤 드롭된 파일을 수락할 수 있습니다. 드래그 앤 드롭 작업 중에 드래그된 파일 및 디렉터리 항목은 각각 파일 항목 및 디렉터리 항목과 연결됩니다. DataTransferItem.getAsFileSystemHandle() 메서드는 드래그된 항목이 파일인 경우 FileSystemFileHandle 객체가 포함된 프로미스를 반환하고, 드래그된 항목이 디렉터리인 경우 FileSystemDirectoryHandle 객체가 포함된 프로미스를 반환합니다. 다음 목록은 이 기능을 보여줍니다. 드래그 앤 드롭 인터페이스의 DataTransferItem.kind은 파일과 디렉터리 모두에 "file"인 반면, 파일 시스템 액세스 API의 FileSystemHandle.kind은 파일의 경우 "file"이고 디렉터리의 경우 "directory"입니다.
elem.addEventListener('dragover', (e) => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener('drop', async (e) => {
e.preventDefault();
const fileHandlesPromises = [...e.dataTransfer.items]
.filter((item) => item.kind === 'file')
.map((item) => item.getAsFileSystemHandle());
for await (const handle of fileHandlesPromises) {
if (handle.kind === 'directory') {
console.log(`Directory: ${handle.name}`);
} else {
console.log(`File: ${handle.name}`);
}
}
});
원본 비공개 파일 시스템 액세스
오리진 비공개 파일 시스템은 이름에서 알 수 있듯이 페이지의 오리진에 비공개인 스토리지 엔드포인트입니다. 브라우저는 일반적으로 이 출처 비공개 파일 시스템의 콘텐츠를 어딘가에 디스크에 유지하여 이를 구현하지만 콘텐츠가 사용자에게 액세스 가능하도록 의도된 것은 아닙니다. 마찬가지로 이름이 원본 비공개 파일 시스템의 하위 요소 이름과 일치하는 파일이나 디렉터리가 존재할 것이라고 예상하지 않습니다. 브라우저에서는 파일이 있는 것처럼 보일 수 있지만 내부적으로는 출처 비공개 파일 시스템이므로 브라우저가 이러한 '파일'을 데이터베이스나 다른 데이터 구조에 저장할 수 있습니다. 기본적으로 이 API를 사용하는 경우 생성된 파일이 하드 디스크의 어딘가에 일대일로 매칭될 것으로 예상하지 마세요. 루트 FileSystemDirectoryHandle에 액세스하면 원래 비공개 파일 시스템에서 평소와 같이 작동할 수 있습니다.
const root = await navigator.storage.getDirectory();
// Create a new file handle.
const fileHandle = await root.getFileHandle('Untitled.txt', { create: true });
// Create a new directory handle.
const dirHandle = await root.getDirectoryHandle('New Folder', { create: true });
// Recursively remove a directory.
await root.removeEntry('Old Stuff', { recursive: true });
출처 비공개 파일 시스템에서 성능에 최적화된 파일에 액세스
출처 비공개 파일 시스템은 파일의 콘텐츠에 대한 독점적인 인플레이스 쓰기 액세스를 제공하는 등 성능에 매우 최적화된 특수한 종류의 파일에 대한 선택적 액세스를 제공합니다. Chromium 102 이상에는 파일 액세스를 간소화하기 위한 출처 비공개 파일 시스템의 추가 메서드인 createSyncAccessHandle() (동기 읽기 및 쓰기 작업용)가 있습니다.
FileSystemFileHandle에 노출되지만 Web Workers에서만 노출됩니다.
// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts.
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });
폴리필
File System Access API 메서드를 완전히 폴리필할 수는 없습니다.
showOpenFilePicker()메서드는<input type="file">요소로 근사화할 수 있습니다.showSaveFilePicker()메서드는<a download="file_name">요소로 시뮬레이션할 수 있지만, 프로그래매틱 다운로드를 트리거하고 기존 파일을 덮어쓰는 것은 허용하지 않습니다.showDirectoryPicker()메서드는 비표준<input type="file" webkitdirectory>요소를 사용하여 어느 정도 에뮬레이션할 수 있습니다.
Google에서는 가능한 경우 파일 시스템 액세스 API를 사용하고 그 외의 모든 경우에는 차선책으로 대체되는 browser-fs-access라는 라이브러리를 개발했습니다.
보안 및 권한
Chrome팀은 사용자 제어 및 투명성, 사용자 인체 공학 등 강력한 웹 플랫폼 기능에 대한 액세스 제어에 정의된 핵심 원칙을 사용하여 파일 시스템 액세스 API를 설계하고 구현했습니다.
파일 열기 또는 새 파일 저장
파일을 열 때 사용자는 파일 선택기를 사용하여 파일이나 디렉터리를 읽을 수 있는 권한을 제공합니다.
열기 파일 선택기는 보안 컨텍스트에서 제공되는 경우 사용자 동작을 사용해서만 표시할 수 있습니다. 사용자가 마음을 바꾸면 파일 선택기에서 선택을 취소할 수 있으며 사이트에서 아무것도 액세스할 수 없습니다. 이는 <input type="file"> 요소의 동작과 동일합니다.
마찬가지로 웹 앱이 새 파일을 저장하려고 하면 브라우저에 파일 저장 선택기가 표시되어 사용자가 새 파일의 이름과 위치를 지정할 수 있습니다. 기존 파일을 덮어쓰는 것이 아니라 기기에 새 파일을 저장하므로 파일 선택기는 앱에 파일 쓰기 권한을 부여합니다.
제한된 폴더
사용자와 사용자의 데이터를 보호하기 위해 브라우저에서 사용자가 특정 폴더(예: Windows, macOS 라이브러리 폴더와 같은 핵심 운영체제 폴더)에 저장하는 기능을 제한할 수 있습니다. 이 경우 브라우저에 메시지가 표시되고 사용자에게 다른 폴더를 선택하라는 메시지가 표시됩니다.
기존 파일 또는 디렉터리 수정
웹 앱은 사용자로부터 명시적인 권한을 얻지 않고는 디스크의 파일을 수정할 수 없습니다.
권한 메시지
이전에 읽기 액세스 권한을 부여한 파일에 변경사항을 저장하려는 경우 브라우저에 권한 메시지가 표시되어 사이트가 디스크에 변경사항을 쓸 수 있는 권한을 요청합니다. 권한 요청은 사용자 동작(예: 저장 버튼 클릭)에 의해서만 트리거될 수 있습니다.
또는 여러 파일을 수정하는 웹 앱(예: IDE)이 열 때 변경사항을 저장할 권한을 요청할 수도 있습니다.
사용자가 취소를 선택하고 쓰기 액세스 권한을 부여하지 않으면 웹 앱이 로컬 파일에 변경사항을 저장할 수 없습니다. 사용자가 데이터를 저장할 수 있는 대체 방법을 제공해야 합니다(예: 파일을 '다운로드'하거나 데이터를 클라우드에 저장하는 방법 제공).
투명성
사용자가 웹 앱에 로컬 파일을 저장할 권한을 부여하면 브라우저에 주소 표시줄에 아이콘이 표시됩니다. 아이콘을 클릭하면 사용자가 액세스 권한을 부여한 파일 목록이 표시된 팝오버가 열립니다. 사용자는 원하는 경우 언제든지 액세스 권한을 취소할 수 있습니다.
권한 지속성
웹 앱은 출처의 모든 탭이 닫힐 때까지 프롬프트 없이 파일에 대한 변경사항을 계속 저장할 수 있습니다. 탭이 닫히면 사이트의 모든 액세스 권한이 손실됩니다. 다음에 사용자가 웹 앱을 사용하면 파일 액세스 권한을 다시 묻는 메시지가 표시됩니다.
의견
File System Access API 사용 경험에 관한 의견을 들려주세요.
API 설계에 대해 알려주세요.
API가 예상대로 작동하지 않는 부분이 있나요? 아이디어를 구현하는 데 필요한 메서드나 속성이 누락되어 있나요? 보안 모델에 관해 궁금한 점이나 의견이 있으신가요?
- WICG 파일 시스템 액세스 GitHub 저장소에 사양 문제를 제출하거나 기존 문제에 의견을 추가하세요.
구현에 문제가 있나요?
Chrome 구현에서 버그를 발견하셨나요? 아니면 구현이 사양과 다른가요?
- https://new.crbug.com에서 버그를 신고합니다. 가능한 한 많은 세부정보와 재현 안내를 포함하고 구성요소를
Blink>Storage>FileSystem로 설정하세요.
API를 사용할 계획이신가요?
사이트에서 File System Access API를 사용하려고 하시나요? 공개적인 지원은 Google이 기능을 우선순위로 지정하는 데 도움이 되며 다른 브라우저 공급업체에 이러한 기능을 지원하는 것이 얼마나 중요한지 보여줍니다.
- WICG Discourse 스레드에서 사용할 계획을 공유하세요.
#FileSystemAccess해시태그를 사용하여 @ChromiumDev에 트윗을 보내 어디에서 어떻게 사용하고 있는지 알려주세요.
유용한 링크
- 공개 설명서
- 파일 시스템 액세스 사양 및 파일 사양
- 버그 추적
- ChromeStatus.com 항목
- TypeScript 정의
- File System Access API - Chromium 보안 모델
- Blink 구성요소:
Blink>Storage>FileSystem
감사의 말씀
File System Access API 사양은 Marijn Kruisselbrink가 작성했습니다.