File System Access API מאפשר לאפליקציות אינטרנט לקרוא או לשמור שינויים ישירות בקבצים ובתיקיות במכשיר של המשתמש.
תאריך פרסום: 19 באוגוסט 2024
ה-API לגישה למערכת הקבצים מאפשר למפתחים ליצור אפליקציות אינטרנט מתקדמות שפועלות עם קבצים במכשיר המקומי של המשתמש, כמו סביבות פיתוח משולבות (IDE), עורכי תמונות וסרטונים, עורכי טקסט ועוד. אחרי שהמשתמש מעניק לאפליקציית אינטרנט גישה, ה-API הזה מאפשר לאפליקציה לקרוא או לשמור שינויים ישירות בקבצים ובתיקיות במכשיר של המשתמש. בנוסף לקריאה ולכתיבה של קבצים, File System Access API מאפשר לפתוח ספריה ולמנות את התוכן שלה.
אם עבדתם בעבר עם קבצים לקריאה וכתיבה, רוב המידע שאשתף יהיה מוכר לכם. אני ממליץ לך לקרוא את המאמר בכל זאת, כי לא כל המערכות דומות.
ה-API לגישה למערכת הקבצים נתמך ברוב הדפדפנים מבוססי Chromium ב-Windows, ב-macOS, ב-ChromeOS, ב-Linux וב-Android. יוצא דופן בולט הוא Brave, שבו התכונה זמינה כרגע רק מאחורי flag.
שימוש ב-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 שמכיל את המאפיינים והשיטות שנדרשים לאינטראקציה עם הקובץ.
כדאי לשמור הפניה ל-file handle כדי שאפשר יהיה להשתמש בו בהמשך. תצטרכו להשתמש בו כדי לשמור שינויים בקובץ או כדי לבצע פעולות אחרות בקובץ.
קריאת קובץ ממערכת הקבצים
עכשיו, כשיש לכם נקודת אחיזה לקובץ, אתם יכולים לקבל את המאפיינים של הקובץ או לגשת לקובץ עצמו.
בינתיים, אקריא את התוכן שלו. הקריאה ל-handle.getFile() מחזירה אובייקט File שמכיל blob. כדי לקבל את הנתונים מה-blob, קוראים לאחת מהשיטות שלו (slice(), stream(), text() או arrayBuffer()).
const file = await fileHandle.getFile();
const contents = await file.text();
אפשר לקרוא את אובייקט File שמוחזר על ידי FileSystemFileHandle.getFile() רק אם הקובץ הבסיסי בדיסק לא השתנה. אם הקובץ בדיסק משתנה, האובייקט File הופך ללא קריא, ותצטרכו לקרוא שוב לפונקציה getFile() כדי לקבל אובייקט File חדש ולקרוא את הנתונים ששונו.
סיכום של כל המידע
כשמשתמשים לוחצים על הלחצן פתיחה, בדפדפן מוצג בוחר קבצים. אחרי שהמשתמש בוחר קובץ, האפליקציה קוראת את התוכן שלו ומכניסה אותו ל<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 (שמירה) כותב את השינויים בחזרה לקובץ המקורי באמצעות נקודת האחיזה של הקובץ שאוחזרה קודם. אבל הפעולה Save As יוצרת קובץ חדש, ולכן נדרש מזהה קובץ חדש.
יצירת קובץ חדש
כדי לשמור קובץ, קוראים ל-showSaveFilePicker(), שפותח את בורר הקבצים במצב 'שמירה', ומאפשר למשתמש לבחור קובץ חדש שבו הוא רוצה להשתמש לשמירה. בנוסף, רציתי שאפליקציית Editor תוסיף באופן אוטומטי תוסף .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();
}
כתיבת נתונים לדיסק מתבצעת באמצעות אובייקט FileSystemWritableFileStream, שהוא מחלקת משנה של WritableStream. יוצרים את הסטרים על ידי קריאה ל-createWritable() באובייקט של ה-handle של הקובץ. כשקוראים ל-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 Text.txt במקום Untitled. כדי לעשות זאת, מעבירים נכס suggestedName כחלק מהאפשרויות של showSaveFilePicker.
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 מאפשרת לכם לאחסן מצב או לזכור אילו קבצים או ספריות משתמש עבד עליהם. כך אפשר לשמור רשימה של קבצים שנפתחו או נערכו לאחרונה, להציע לפתוח מחדש את הקובץ האחרון כשפותחים את האפליקציה, לשחזר את ספריית העבודה הקודמת ועוד. בעורך הטקסט, אני מאחסן רשימה של חמשת הקבצים האחרונים שהמשתמש פתח, כדי לאפשר לו לגשת לקבצים האלה שוב.
בדוגמה הבאה של קוד אפשר לראות איך מאחסנים ומאחזרים ידית של קובץ וידית של ספרייה. אפשר לראות את זה בפעולה ב-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));
});
יצירה או גישה לקבצים ולתיקיות בספרייה
אפשר ליצור קבצים ותיקיות או לגשת אליהם באמצעות ה-method getFileHandle() או getDirectoryHandle(), בהתאמה. אפשר להעביר אובייקט options אופציונלי עם מפתח create וערך בוליאני true או false, כדי לקבוע אם ליצור קובץ או תיקייה חדשים אם הם לא קיימים.
// 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 });
מחיקה ישירה של קובץ או תיקייה
אם יש לכם גישה לטיפול בקובץ או בספרייה, תוכלו להתקשר אל remove() ב-FileSystemFileHandle או ב-FileSystemDirectoryHandle כדי להסיר אותו.
// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();
שינוי השם והעברה של קבצים ותיקיות
אפשר לשנות את השם של קבצים ותיקיות או להעביר אותם למיקום חדש באמצעות קריאה ל-move() בממשק FileSystemHandle. ל-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" גם לקבצים וגם לספריות, ואילו הפונקציה FileSystemHandle.kind של File System Access API היא "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 הזה, לא כדאי לצפות למצוא את הקבצים שנוצרו בהתאמה אחד לאחד איפשהו בדיסק הקשיח. אחרי שתהיה לכם גישה ל-root 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 ואילך, יש method נוסף במערכת הקבצים הפרטית של המקור כדי לפשט את הגישה לקבצים: 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 });
פוליפיל
אי אפשר לבצע polyfill מלא לשיטות של File System Access API.
- אפשר להשתמש ברכיב
<input type="file">כדי להעריך את השיטהshowOpenFilePicker(). - אפשר לדמות את השיטה
showSaveFilePicker()באמצעות רכיב<a download="file_name">, אבל הפעולה הזו מפעילה הורדה תוכנתית ולא מאפשרת להחליף קבצים קיימים. - אפשר ליצור סוג של הדמיה לשיטת
showDirectoryPicker()באמצעות הרכיב הלא תקני<input type="file" webkitdirectory>.
פיתחנו ספריה בשם browser-fs-access שמשתמשת ב-File System Access API בכל המקרים האפשריים, ובמקרים אחרים משתמשת באפשרויות הבאות הכי טובות.
אבטחה והרשאות
צוות Chrome תכנן והטמיע את File System Access API (ממשק API לגישה למערכת הקבצים) בהתאם לעקרונות הליבה שמוגדרים במאמר שליטה בגישה לתכונות עוצמתיות של פלטפורמת האינטרנט, כולל שליטה ושקיפות של המשתמש וארגונומיה של המשתמש.
פתיחת קובץ או שמירת קובץ חדש
כשפותחים קובץ, המשתמש מעניק הרשאה לקרוא קובץ או ספרייה באמצעות הכלי לבחירת קבצים.
אפשר להציג את הכלי לבחירת קבצים רק באמצעות תנועת משתמש כשהוא מוגש מהקשר מאובטח. אם המשתמשים מתחרטים, הם יכולים לבטל את הבחירה בכלי לבחירת קבצים, והאתר לא מקבל גישה לשום דבר. ההתנהגות הזו זהה להתנהגות של הרכיב <input type="file">.
באופן דומה, כשרוצים לשמור קובץ חדש באפליקציית אינטרנט, הדפדפן מציג את הכלי לבחירת קובץ לשמירה, ומאפשר למשתמש לציין את השם והמיקום של הקובץ החדש. מכיוון שהם שומרים קובץ חדש במכשיר (ולא מחליפים קובץ קיים), הכלי לבחירת קבצים מעניק לאפליקציה הרשאה לכתוב בקובץ.
תיקיות מוגבלות
כדי להגן על המשתמשים והנתונים שלהם, הדפדפן עשוי להגביל את היכולת של המשתמש לשמור בתיקיות מסוימות, למשל תיקיות של מערכת ההפעלה כמו Windows או תיקיות הספרייה של macOS. במקרה כזה, הדפדפן מציג בקשה ומבקש מהמשתמש לבחור תיקייה אחרת.
שינוי של קובץ או ספרייה קיימים
אפליקציית אינטרנט לא יכולה לשנות קובץ בדיסק בלי לקבל הרשאה מפורשת מהמשתמש.
הנחיה לבקשת הרשאה
אם מישהו רוצה לשמור שינויים בקובץ שהוא קיבל בעבר הרשאת קריאה אליו, בדפדפן תוצג בקשת הרשאה, שבה האתר מבקש הרשאה לכתוב שינויים בדיסק. אפשר להפעיל את בקשת ההרשאה רק באמצעות תנועת משתמש, למשל, בלחיצה על לחצן שמירה.
לחלופין, אפליקציית אינטרנט לעריכת קבצים מרובים, כמו IDE, יכולה גם לבקש הרשאה לשמירת שינויים בזמן הפתיחה.
אם המשתמש בוחר באפשרות 'ביטול' ולא מעניק גישת כתיבה, אפליקציית האינטרנט לא יכולה לשמור שינויים בקובץ המקומי. צריך לספק למשתמש שיטה חלופית לשמירת הנתונים, למשל הורדה של הקובץ או שמירת הנתונים בענן.
שקיפות
אחרי שמשתמש נותן לאפליקציית אינטרנט הרשאה לשמור קובץ מקומי, מופיע סמל בסרגל הכתובות של הדפדפן. כשלוחצים על הסמל, נפתח חלון קופץ שבו מוצגת רשימת הקבצים שהמשתמש העניק גישה אליהם. המשתמש תמיד יכול לבטל את הגישה הזו אם הוא רוצה.
התמדה של הרשאות
אפליקציית האינטרנט יכולה להמשיך לשמור שינויים בקובץ בלי להציג בקשה עד שכל הכרטיסיות של המקור שלה ייסגרו. אחרי שסוגרים כרטיסייה, האתר מאבד את כל הגישה. בפעם הבאה שהמשתמש ישתמש באפליקציית האינטרנט, הוא יתבקש שוב לתת גישה לקבצים.
משוב
נשמח לשמוע על החוויות שלכם עם File System Access API.
מהו עיצוב ה-API?
האם יש משהו ב-API שלא פועל כמו שציפית? או שיש שיטות או מאפיינים חסרים שצריך להטמיע כדי לממש את הרעיון? יש לך שאלה או הערה לגבי מודל האבטחה?
- אפשר לפתוח בקשה בנושא מפרט במאגר GitHub של WICG File System Access, או להוסיף את המחשבות שלכם לבקשה קיימת.
בעיה בהטמעה?
מצאתם באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט?
- מדווחים על הבאג בכתובת https://new.crbug.com. חשוב לכלול כמה שיותר פרטים, הוראות לשחזור הבאג ולהגדיר את Components ל-
Blink>Storage>FileSystem.
מתכננים להשתמש ב-API?
מתכננים להשתמש ב-File System Access API באתר שלכם? התמיכה הציבורית שלכם עוזרת לנו לקבוע סדרי עדיפויות לגבי תכונות, ומראה לספקי דפדפנים אחרים עד כמה חשוב לתמוך בהן.
- אפשר לשתף את התוכניות שלכם לשימוש ב-WICG Discourse thread.
- אתם יכולים לשלוח ציוץ אל @ChromiumDev עם ההאשטאג
#FileSystemAccessולספר לנו איפה ואיך אתם משתמשים בו.
קישורים שימושיים
- הסבר לציבור
- מפרט הגישה למערכת הקבצים ומפרט הקובץ
- באג במעקב
- ערך ב-ChromeStatus.com
- הגדרות TypeScript
- File System Access API - Chromium Security Model
- רכיב Blink:
Blink>Storage>FileSystem
תודות
מפרט File System Access API נכתב על ידי Marijn Kruisselbrink.