API de File System Access: Simplificación del acceso a archivos locales

La API de File System Access permite que las aplicaciones web lean o guarden cambios directamente en archivos y carpetas del dispositivo del usuario.

¿Qué es la API de File System Access?

La API de File System Access (antes conocida como API de Native File System y antes de que se llamara API de Writeable Files) permite a los desarrolladores compilar apps web potentes que interactúan con archivos en el dispositivo local del usuario, como IDE, editores de foto y video, editores de texto y muchos más. Después de que un usuario otorga acceso a una app web, esta API le permite leer o guardar cambios directamente en archivos y carpetas del dispositivo del usuario. Además de leer y escribir archivos, la API de File System Access permite abrir un directorio y enumerar su contenido.

Si ya trabajaste con la lectura y escritura de archivos, mucho de lo que voy a compartir te resultará familiar. Te recomiendo que lo leas de todos modos, porque no todos los sistemas son iguales.

En la actualidad, la API de File System Access es compatible con la mayoría de los navegadores Chromium en Windows, macOS, ChromeOS y Linux. Una excepción notable es el de Brave, que actualmente solo está disponible detrás de una marca. A partir de Chromium 109, Android admite la parte de la API del sistema de archivos privados de origen. Por el momento, no hay planes para los métodos de selección, pero puedes hacer un seguimiento del posible progreso si destacas crbug.com/1011535.

Cómo usar la API de File System Access

Para mostrar la potencia y utilidad de la API de File System Access, escribí un solo editor de texto de archivos. Te permite abrir un archivo de texto, editarlo, guardar los cambios en el disco o iniciar un archivo nuevo y guardar los cambios en el disco. No es nada sofisticado, pero proporciona suficiente para ayudarte a comprender los conceptos.

Navegadores compatibles

Navegadores compatibles

  • 86
  • 86
  • x
  • x

Origen

Probar

Observa cómo funciona la API de File System Access en la demostración del editor de texto.

Lee un archivo desde el sistema de archivos local

El primer caso de uso que quiero abordar es pedirle al usuario que elija un archivo y, luego, lo abra y lo lea desde el disco.

Pídele al usuario que elija un archivo para leer

El punto de entrada a la API de File System Access es window.showOpenFilePicker(). Cuando se lo llama, muestra un cuadro de diálogo de selector de archivos y le pide al usuario que seleccione un archivo. Después de seleccionar un archivo, la API muestra un arreglo de controladores de archivos. Un parámetro opcional options te permite influir en el comportamiento del selector de archivos, por ejemplo, permitiendo que el usuario seleccione varios archivos, directorios o diferentes tipos de archivos. Sin ninguna opción especificada, el selector de archivos permite que el usuario seleccione un solo archivo. Es perfecto para un editor de texto.

Al igual que muchas otras APIs potentes, la llamada a showOpenFilePicker() debe hacerse en un contexto seguro y debe llamarse desde un gesto del usuario.

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  // Destructure the one-element array.
  [fileHandle] = await window.showOpenFilePicker();
  // Do something with the file handle.
});

Una vez que el usuario selecciona un archivo, showOpenFilePicker() muestra un array de controladores, en este caso, un array de un elemento con un FileSystemFileHandle que contiene las propiedades y los métodos necesarios para interactuar con el archivo.

Es útil mantener una referencia al controlador del archivo para poder usarlo más adelante. Lo necesitarás para guardar los cambios en el archivo o para realizar cualquier otra operación relacionada con él.

Cómo leer un archivo desde el sistema de archivos

Ahora que tienes un handle para un archivo, puedes obtener las propiedades del archivo o acceder a él. Por ahora, simplemente leeré su contenido. Si llamas a handle.getFile(), se muestra un objeto File, que contiene un BLOB. Para obtener los datos del BLOB, llama a uno de sus métodos (slice(), stream(), text() o arrayBuffer()).

const file = await fileHandle.getFile();
const contents = await file.text();

El objeto File que muestra FileSystemFileHandle.getFile() solo se puede leer siempre y cuando el archivo subyacente en el disco no haya cambiado. Si se modifica el archivo en el disco, el objeto File se vuelve ilegible y deberás volver a llamar a getFile() para obtener un objeto File nuevo que lea los datos modificados.

Organizando los resultados

Cuando los usuarios hacen clic en el botón Abrir, el navegador muestra un selector de archivos. Una vez que selecciona un archivo, la app lee el contenido y lo coloca en un <textarea>.

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const contents = await file.text();
  textArea.value = contents;
});

Escribe el archivo en el sistema de archivos local

En el editor de texto, hay dos formas de guardar un archivo: Guardar y Guardar como. Save simplemente vuelve a escribir los cambios en el archivo original con el controlador de archivo recuperado anteriormente. Sin embargo, Guardar como crea un archivo nuevo y, por lo tanto, requiere un controlador de archivo nuevo.

Crea un archivo nuevo

Para guardar un archivo, llama a showSaveFilePicker(), que muestra el selector de archivos en modo "guardar", lo que le permite al usuario elegir un archivo nuevo que quiera usar para guardar. En el caso del editor de texto, también quería agregar automáticamente una extensión .txt, por lo que proporcioné algunos parámetros adicionales.

async function getNewFileHandle() {
  const options = {
    types: [
      {
        description: 'Text Files',
        accept: {
          'text/plain': ['.txt'],
        },
      },
    ],
  };
  const handle = await window.showSaveFilePicker(options);
  return handle;
}

Guardar cambios en el disco

Puedes encontrar todo el código para guardar cambios en un archivo en mi demostración del editor de texto en GitHub. Las interacciones principales del sistema de archivos se encuentran en fs-helpers.js. En su forma más simple, el proceso luce como el código que se muestra a continuación. Veamos cada paso y lo explicaré.

// 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();
}

Cuando se escriben datos en el disco, se usa un objeto FileSystemWritableFileStream, una subclase de WritableStream. Llama a createWritable() en el objeto del controlador de archivos para crear la transmisión. Cuando se llama a createWritable(), el navegador primero comprueba si el usuario otorgó permiso de escritura al archivo. Si no se otorgó permiso para escribir, el navegador lo solicitará al usuario. Si no se otorga el permiso, createWritable() arroja una DOMException, y la app no podrá escribir en el archivo. En el editor de texto, los objetos DOMException se controlan en el método saveFile().

El método write() toma una cadena, que es lo que se necesita para un editor de texto. Sin embargo, también puede tomar una BufferSource o un BLOB. Por ejemplo, puedes canalizar una transmisión directamente a ella:

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.
}

También puedes seek() o truncate() dentro de la transmisión para actualizar el archivo en una posición específica o cambiar su tamaño.

Cómo especificar un nombre de archivo y un directorio de inicio sugeridos

En muchos casos, es posible que quieras que la app sugiera un nombre de archivo o una ubicación predeterminados. Por ejemplo, un editor de texto podría sugerir un nombre de archivo predeterminado Untitled Text.txt en lugar de Untitled. Para ello, pasa una propiedad suggestedName como parte de las opciones de showSaveFilePicker.

const fileHandle = await self.showSaveFilePicker({
  suggestedName: 'Untitled Text.txt',
  types: [{
    description: 'Text documents',
    accept: {
      'text/plain': ['.txt'],
    },
  }],
});

Lo mismo ocurre con el directorio de inicio predeterminado. Si estás compilando un editor de texto, es posible que desees iniciar el diálogo para guardar o abrir archivos en la carpeta documents predeterminada, mientras que, para un editor de imágenes, puede comenzar en la carpeta pictures predeterminada. Para sugerir un directorio de inicio predeterminado, pasa una propiedad startIn a los métodos showSaveFilePicker, showDirectoryPicker() o showOpenFilePicker.

const fileHandle = await self.showOpenFilePicker({
  startIn: 'pictures'
});

Esta es la lista de directorios del sistema conocidos:

  • desktop: Es el directorio de escritorio del usuario, si es que existe.
  • documents: Es el directorio en el que se suelen almacenar los documentos creados por el usuario.
  • downloads: Es el directorio en el que se suelen almacenar los archivos descargados.
  • music: Es el directorio en el que se suelen almacenar los archivos de audio.
  • pictures: Directorio en el que se suelen almacenar las fotos y otras imágenes estáticas.
  • videos: Es el directorio en el que se suelen almacenar los videos y las películas.

Además de los directorios del sistema conocidos, también puedes pasar un controlador de directorio o archivo existente como un valor para startIn. El diálogo se abrirá en el mismo directorio.

// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
  startIn: directoryHandle
});

Cómo especificar el propósito de diferentes selectores de archivos

A veces, las aplicaciones tienen diferentes selectores para diferentes propósitos. Por ejemplo, un editor de texto enriquecido puede permitir al usuario abrir archivos de texto, pero también importar imágenes. De forma predeterminada, cada selector de archivos se abrirá en la última ubicación que recuerdes. Puedes evitar esto almacenando valores id para cada tipo de selector. Si se especifica un id, la implementación del selector de archivos recordará un directorio independiente que se usó por última vez para ese id.

const fileHandle1 = await self.showSaveFilePicker({
  id: 'openText',
});

const fileHandle2 = await self.showSaveFilePicker({
  id: 'importImage',
});

Cómo almacenar controladores de archivos o de directorio en IndexedDB

Los controladores de archivos y directorios se pueden serializar, lo que significa que puedes guardar un controlador de archivo o directorio en IndexedDB, o llamar a postMessage() para enviarlos entre el mismo origen de nivel superior.

Guardar los controladores de archivos o directorios en IndexedDB significa que puedes almacenar el estado o recordar en qué archivos o directorios estaba trabajando un usuario. Esto permite mantener una lista de archivos abiertos o editados recientemente, ofrecer volver a abrir el último archivo cuando se abre la app, restablecer el directorio de trabajo anterior y mucho más. En el editor de texto, almaceno una lista de los cinco archivos más recientes que el usuario ha abierto, lo que facilita el acceso a esos archivos nuevamente.

En el siguiente ejemplo de código, se muestra cómo almacenar y recuperar un controlador de archivo y un controlador de directorio. Puedes ver esto en acción en Glitch. (Uso la biblioteca idb-keyval para abreviar).

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);
  }
});

Permisos y controladores de directorios o archivos almacenados

Dado que los permisos no persisten entre sesiones, debes verificar si el usuario otorgó permiso al archivo o directorio mediante queryPermission(). De lo contrario, llama a requestPermission() para (volver a) solicitarlo. Esto funciona de la misma manera para los controladores de archivos y directorios. Debes ejecutar fileOrDirectoryHandle.requestPermission(descriptor) o fileOrDirectoryHandle.queryPermission(descriptor), respectivamente.

En el editor de texto, creé un método verifyPermission() que verifica si el usuario ya otorgó permiso y, si es necesario, realiza la solicitud.

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;
}

Cuando solicité un permiso de escritura con la solicitud de lectura, reduje la cantidad de solicitudes de permiso; el usuario ve un mensaje cuando abre el archivo y otorga permiso para leer y escribir en él.

Cómo abrir un directorio y enumerar su contenido

Para enumerar todos los archivos de un directorio, llama a showDirectoryPicker(). El usuario elige un directorio en un selector, después de lo cual se muestra un FileSystemDirectoryHandle, lo que te permite enumerar los archivos del directorio y acceder a ellos. De forma predeterminada, tendrás acceso de lectura a los archivos del directorio, pero si necesitas acceso de escritura, puedes pasar { mode: 'readwrite' } al método.

const butDir = document.getElementById('butDirectory');
butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  for await (const entry of dirHandle.values()) {
    console.log(entry.kind, entry.name);
  }
});

Además, si necesitas acceder a cada archivo a través de getFile() para obtener, por ejemplo, los tamaños de archivos individuales, no uses await en cada resultado de forma secuencial, sino que procesa todos los archivos en paralelo (por ejemplo, mediante Promise.all()).

const butDir = document.getElementById('butDirectory');
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));
});

Creación de archivos y carpetas en un directorio o acceso a ellos

Desde un directorio, puedes crear archivos y carpetas, o acceder a ellos, con getFileHandle() o, respectivamente, el método getDirectoryHandle(). Cuando pasas un objeto options opcional con una clave de create y un valor booleano de true o false, puedes determinar si se debe crear un archivo o una carpeta nuevos en caso de que no existan.

// 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 });

Resuelve la ruta de acceso de un elemento en un directorio

Cuando trabajas con archivos o carpetas en un directorio, puede ser útil resolver la ruta de acceso del elemento en cuestión. Esto se puede hacer con el método correctamente denominado resolve(). Para resolverlo, el elemento puede ser un elemento secundario directo o indirecto del directorio.

// 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"]

Borrar archivos y carpetas de un directorio

Si obtuviste acceso a un directorio, puedes borrar los archivos y las carpetas contenidos con el método removeEntry(). En el caso de las carpetas, la eliminación puede ser recursiva de forma opcional y, además, incluir todas las subcarpetas y los archivos contenidos en ellas.

// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });

Cómo borrar un archivo o una carpeta directamente

Si tienes acceso a un controlador de directorio o archivo, llama a remove() en un FileSystemFileHandle o FileSystemDirectoryHandle para quitarlo.

// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();

Cambia el nombre de archivos y carpetas y muévelos

Puedes cambiar el nombre de los archivos y las carpetas o moverlos a una nueva ubicación llamando a move() en la interfaz FileSystemHandle. FileSystemHandle tiene las interfaces secundarias FileSystemFileHandle y FileSystemDirectoryHandle. El método move() toma uno o dos parámetros. El primero puede ser una string con el nombre nuevo o un FileSystemDirectoryHandle para la carpeta de destino. En el último caso, el segundo parámetro opcional es una string con el nuevo nombre, por lo que el movimiento y el cambio de nombre pueden ocurrir en un solo paso.

// 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');

Integración de arrastrar y soltar

Las interfaces HTML de arrastrar y soltar permiten que las aplicaciones web acepten archivos arrastrados y soltados en una página web. Durante una operación de arrastrar y soltar, los elementos del directorio y del archivo arrastrados se asocian con entradas de archivo y de directorio, respectivamente. El método DataTransferItem.getAsFileSystemHandle() muestra una promesa con un objeto FileSystemFileHandle si el elemento arrastrado es un archivo, y una promesa con un objeto FileSystemDirectoryHandle si el elemento arrastrado es un directorio. En la siguiente lista, se muestra esto en acción. Ten en cuenta que el elemento DataTransferItem.kind de la interfaz de arrastrar y soltar es "file" para los archivos y directorios, mientras que FileSystemHandle.kind de la API de acceso al sistema de archivos es "file" para los archivos y "directory" para los directorios.

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}`);
    }
  }
});

Cómo acceder al sistema de archivos privados de origen

El sistema de archivos privados de origen es un extremo de almacenamiento que, como sugiere el nombre, es privado para el origen de la página. Si bien los navegadores suelen implementar esto mediante la conservación del contenido de este sistema de archivos privados de origen en un disco, no se pretende que el contenido sea de fácil acceso para el usuario. Del mismo modo, no se espera que existan archivos o directorios con nombres que coincidan con los de los elementos secundarios del sistema de archivos privados de origen. Si bien el navegador puede hacer parecer que hay archivos, internamente (ya que este es un sistema de archivos privados de origen), el navegador puede almacenar estos "archivos" en una base de datos o en cualquier otra estructura de datos. En esencia, si usas esta API, no esperes encontrar los archivos creados que coincidan uno a uno en alguna parte del disco duro. Puedes operar como de costumbre en el sistema de archivos privados de origen una vez que tengas acceso a la FileSystemDirectoryHandle raíz.

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 });

Navegadores compatibles

  • 86
  • 86
  • 111
  • 15.2

Origen

Acceso a archivos optimizados para el rendimiento desde el sistema de archivos privados de origen

El sistema de archivos privados de origen proporciona acceso opcional a un tipo especial de archivo que está altamente optimizado para el rendimiento, por ejemplo, cuando ofrece acceso de escritura local y exclusivo al contenido de un archivo. En Chromium 102 y versiones posteriores, existe un método adicional en el sistema de archivos privados de origen para simplificar el acceso a los archivos: createSyncAccessHandle() (para operaciones de lectura y escritura síncronas). Se expone en FileSystemFileHandle, pero exclusivamente en los 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 });

Polirelleno

No es posible completar polyfills por completo de los métodos de la API de File System Access.

  • Se puede aproximar el método showOpenFilePicker() con un elemento <input type="file">.
  • El método showSaveFilePicker() se puede simular con un elemento <a download="file_name">, aunque esto activa una descarga programática y no permite reemplazar archivos existentes.
  • El método showDirectoryPicker() se puede emular de alguna manera con el elemento <input type="file" webkitdirectory> no estándar.

Desarrollamos una biblioteca llamada browser-fs-access que usa la API de File System Access siempre que sea posible y que recurre a las siguientes mejores opciones en todos los demás casos.

Seguridad y permisos

El equipo de Chrome diseñó e implementó la API de acceso al sistema de archivos según los principios fundamentales definidos en el artículo Cómo controlar el acceso a funciones potentes de la plataforma web, incluidos el control y la transparencia del usuario, y la ergonomía.

Abrir un archivo o guardar uno nuevo

Selector de archivos para abrir un archivo y leerlo
Un selector de archivos que se usa para abrir un archivo existente y leerlo

Cuando abre un archivo, el usuario otorga permiso para leer un archivo o directorio a través del selector de archivos. El selector de archivos abierto solo se puede mostrar mediante un gesto del usuario cuando se entrega desde un contexto seguro. Si los usuarios cambian de opinión, pueden cancelar la selección en el selector de archivos y el sitio no tendrá acceso a nada. Este comportamiento es el mismo que el del elemento <input type="file">.

Selector de archivos para guardar un archivo en el disco.
Un selector de archivos que se usa para guardar un archivo en el disco.

Del mismo modo, cuando una app web quiere guardar un archivo nuevo, el navegador muestra el selector de archivos para guardar, lo que permite al usuario especificar el nombre y la ubicación del archivo nuevo. Dado que guarda un archivo nuevo en el dispositivo (en lugar de reemplazar uno existente), el selector de archivos le otorga a la app permiso para escribir en él.

Carpetas restringidas

Para ayudar a proteger a los usuarios y sus datos, el navegador puede limitar la capacidad del usuario de guardar en ciertas carpetas, por ejemplo, las carpetas principales del sistema operativo, como Windows, las carpetas de la biblioteca de macOS, etc. Cuando esto sucede, el navegador muestra un mensaje y le pide al usuario que elija una carpeta diferente.

Modifica un archivo o directorio existente

Una aplicación web no puede modificar un archivo en un disco sin obtener el permiso explícito del usuario.

Mensaje de permiso

Si una persona quiere guardar los cambios en un archivo al que le otorgó acceso de lectura anteriormente, el navegador mostrará un mensaje de permiso y solicitará permiso para que el sitio escriba cambios en el disco. La solicitud de permiso solo se puede activar mediante un gesto del usuario, por ejemplo, con un clic en el botón Guardar.

Mensaje de permiso que se muestra antes de guardar un archivo.
Es un mensaje que se muestra a los usuarios antes de que se otorgue permiso de escritura al navegador en un archivo existente.

Como alternativa, una app web que edita varios archivos, como un IDE, también puede solicitar permiso para guardar cambios en el momento de abrirse.

Si el usuario selecciona Cancelar y no otorga acceso de escritura, la aplicación web no podrá guardar los cambios en el archivo local. Debe ofrecer un método alternativo para que el usuario guarde sus datos, por ejemplo, proporcionando una forma de “descargar” el archivo, guardar datos en la nube, etcétera.

Transparencia

Ícono de cuadro multifunción
Ícono de cuadro multifunción que indica que el usuario otorgó permiso al sitio web para guardar el elemento en un archivo local.

Una vez que un usuario le otorga permiso a una app web para guardar un archivo local, el navegador muestra un ícono en la barra de URL. Si haces clic en el ícono, se abrirá una ventana emergente que muestra la lista de archivos a los que el usuario otorgó acceso. El usuario puede revocar fácilmente ese acceso si lo desea.

Persistencia de permisos

La app web puede seguir guardando cambios en el archivo sin solicitarlo hasta que se cierren todas las pestañas de su origen. Una vez que se cierra una pestaña, el sitio pierde todo el acceso. La próxima vez que el usuario utilice la aplicación web, se le volverá a solicitar acceso a los archivos.

Comentarios

Nos gustaría conocer tu experiencia con la API de File System Access.

Cuéntanos sobre el diseño de API

¿Hay algo acerca de la API que no funciona como esperabas? ¿O faltan métodos o propiedades que necesitas para implementar tu idea? ¿Tienes alguna pregunta o comentario sobre el modelo de seguridad?

¿Tienes problemas con la implementación?

¿Encontraste un error en la implementación de Chrome? ¿La implementación es diferente de la especificación?

  • Informa el error en https://new.crbug.com. Asegúrate de incluir todos los detalles que puedas, instrucciones simples para la reproducción y configura los componentes como Blink>Storage>FileSystem. Glitch funciona muy bien para compartir repros rápidos y fáciles.

¿Piensas usar la API?

¿Piensas usar la API de File System Access en tu sitio? Tu apoyo público nos ayuda a priorizar funciones y les muestra a otros proveedores de navegadores lo fundamental que es admitirlas.

Vínculos útiles

Agradecimientos

Marijn Kruisselbrink escribió la especificación de la API de File System Access.