Crea APK gratis

API del bridge JavaScript

File: una cartella concessa dall'utente

Una cartella scelta una volta, leggibile e scrivibile in seguito, con un'autorizzazione che sopravvive ai riavvii.

WebToApkFS.clearAccess helper#

WebToApkFS.clearAccess()

Esempio

Dimentica la cartella concessa e restituisce l'autorizzazione ad Android.

Restituisce: niente (sincrono). Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app.

Offri sempre un modo per scollegare accanto al nome della cartella:

document.getElementById('disconnect').addEventListener('click', function () {
  if (!window.WebToApkFS) return;
  window.WebToApkFS.clearAccess();
  console.log('still connected?', window.WebToApkFS.hasAccess());   // false
  document.getElementById('folder-name').textContent = 'No folder chosen';
  document.getElementById('choose-folder').hidden = false;
});

Un pulsante che collega o scollega, a seconda dello stato attuale:

var btn = document.getElementById('folder-toggle');
function paint() {
  btn.textContent = window.WebToApkFS.hasAccess() ? 'Disconnect folder' : 'Connect a folder';
}

btn.addEventListener('click', async function () {
  if (!window.WebToApkFS) return;
  if (window.WebToApkFS.hasAccess()) {
    if (!confirm('Stop saving to this folder? Your files stay where they are.')) return;
    window.WebToApkFS.clearAccess();
  } else {
    await window.WebToApkFS.requestAccess();
  }
  paint();
});
if (window.WebToApkFS) paint();

Cambio di account: dimentica la cartella insieme al resto dello stato di accesso:

function switchAccount() {
  sessionStorage.clear();
  if (window.WebToApkFS) window.WebToApkFS.clearAccess();   // the next account picks its own folder
  showLoginScreen();
}

Note: I file restano dove sono; l'app semplicemente non può più raggiungerli. Per usare di nuovo una cartella, chiama WebToApkFS.requestAccess() da un tocco.

WebToApkFS.delete helper#

WebToApkFS.delete(relativePath, options)

Esempio

Elimina un file o una cartella all'interno della cartella concessa.

Restituisce: un booleano, in modo sincrono: true quando è stato eliminato. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app, e una cartella concessa.

Elimina un file, dopo aver chiesto conferma:

if (window.WebToApkFS && confirm('Delete notes/old.txt?')) {
  var ok = window.WebToApkFS.delete('notes/old.txt');
  if (!ok) alert('Could not delete the file.');
}

Elimina una cartella e tutto ciò che contiene: passa { recursive: true }:

if (window.WebToApkFS && confirm('Delete the whole 2025 folder?')) {
  var gone = window.WebToApkFS.delete('notes/2025', { recursive: true });
  console.log(gone ? 'deleted' : 'not deleted');
}

Pulisci le esportazioni piĂą vecchie di 30 giorni, file per file:

function pruneOldExports() {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return 0;
  var cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
  var removed = 0;
  window.WebToApkFS.list('exports').forEach(function (e) {
    if (e.isDirectory || e.lastModified >= cutoff) return;
    if (window.WebToApkFS.delete('exports/' + e.name)) removed++;
  });
  return removed;
}

if (confirm('Delete exports older than 30 days?')) {
  document.getElementById('status').textContent = pruneOldExports() + ' old exports deleted';
}

Note: Senza { recursive: true } una cartella viene eliminata solo se è vuota. La cartella concessa stessa ('') non può mai essere eliminata. L'eliminazione è immediata e non c'è cestino, quindi chiedi sempre conferma all'utente.

WebToApkFS.getAccessUri helper#

WebToApkFS.getAccessUri()

Esempio

Fornisce l'indirizzo della cartella concessa, così puoi mostrare all'utente dove vanno i file.

Restituisce: una stringa, in modo sincrono: un URI ad albero come content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FNotes, oppure '' quando non è concessa alcuna cartella. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app.

Mostra un nome di cartella leggibile accanto a un pulsante Disconnetti:

function connectedFolderName() {
  if (!window.WebToApkFS) return '';
  var uri = window.WebToApkFS.getAccessUri();
  if (!uri) return '';
  var tail = uri.split('/tree/')[1] || '';
  try { tail = decodeURIComponent(tail); } catch (e) {}
  return tail.split(':').pop() || tail;        // "Documents/Notes"
}

var name = connectedFolderName();
document.getElementById('folder-name').textContent = name ? 'Folder: ' + name : 'No folder chosen';

Accorgiti che l'utente ha scelto una cartella diversa dall'ultima visita e ricostruisci ciò che avevi messo in cache sulla vecchia:

function checkFolderChanged() {
  if (!window.WebToApkFS) return;
  var uri = window.WebToApkFS.getAccessUri();
  var last = '';
  try { last = localStorage.getItem('folderUri') || ''; } catch (e) {}
  if (uri && uri !== last) {
    rebuildFileIndex();                         // your app: re-read the new folder
    try { localStorage.setItem('folderUri', uri); } catch (e) {}
  }
}

checkFolderChanged();

Un pulsante Disconnetti che dimentica la cartella e conferma che non c'è più:

document.getElementById('disconnect').addEventListener('click', function () {
  if (!window.WebToApkFS || !window.WebToApkFS.getAccessUri()) return;
  if (!confirm('Stop saving to this folder? Your files stay where they are.')) return;
  window.WebToApkFS.clearAccess();
  var still = window.WebToApkFS.getAccessUri();
  document.getElementById('folder-name').textContent = still ? 'Could not disconnect' : 'No folder chosen';
});

Note: I metodi per i file non accettano questo URI: accettano percorsi relativi alla cartella concessa ('notes/today.txt'). Il formato dell'URI appartiene al provider di file da cui l'utente ha scelto, quindi usalo solo per la visualizzazione.

WebToApkFS.hasAccess helper#

WebToApkFS.hasAccess()

Esempio

Ti dice se l'utente ha giĂ dato all'app una cartella.

Restituisce: un booleano, in modo sincrono. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app.

Mostra "Scegli cartella" o l'elenco dei file, a seconda della risposta:

function renderFolderScreen() {
  if (!window.WebToApkFS) {
    document.getElementById('folder-panel').hidden = true;   // not in the app, or not enabled
    return;
  }
  var connected = window.WebToApkFS.hasAccess();
  document.getElementById('choose-folder').hidden = connected;
  document.getElementById('file-list').hidden = !connected;
  if (connected) {
    var entries = window.WebToApkFS.list('');
    document.getElementById('file-list').textContent =
      entries.map(function (e) { return e.name; }).join('\n');
  }
}

Salva automaticamente solo mentre una cartella è collegata, e dillo quando non lo è:

var saveTimer = null;
document.getElementById('editor').addEventListener('input', function () {
  clearTimeout(saveTimer);
  saveTimer = setTimeout(function () {
    var status = document.getElementById('status');
    if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) {
      status.textContent = 'Choose a folder to turn on autosave';
      return;
    }
    var ok = window.WebToApkFS.writeText('notes/draft.txt', document.getElementById('editor').value);
    status.textContent = ok ? 'Saved' : 'Could not save';
  }, 800);
});

All'avvio, riapri l'ultima nota quando una cartella è ricordata, altrimenti mostra la schermata di benvenuto:

window.addEventListener('load', function () {
  var fs = window.WebToApkFS;
  if (!fs || !fs.hasAccess()) {
    showWelcome();                                 // your app: explains "Choose folder"
    return;
  }
  var last = 'notes/today.txt';
  try { last = localStorage.getItem('lastNote') || last; } catch (e) {}
  document.getElementById('editor').value = fs.readText(last);
});

Note: Dice solo che l'app ricorda una cartella. Se l'utente ha eliminato o spostato quella cartella, list('') restituisce []: offri di nuovo "Scegli cartella". window.WebToApkFS compare quando la pagina finisce di caricarsi; durante il caricamento usa il grezzo window.WebToApk.hasFolderAccess() === 'true'.

WebToApkFS.isAvailable helper#

WebToApkFS.isAvailable()

Esempio

Ti dice se questa app può usare una cartella concessa dall'utente.

Restituisce: un booleano, in modo sincrono: true quando la build ha l'accesso alle cartelle attivo. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app: senza di esso (e senza Chiavi sicure) window.WebToApkFS non viene aggiunto alla pagina; con le sole Chiavi sicure esiste e risponde false.

window.WebToApkFS viene aggiunto prima che giri il primo script della tua pagina; sulle WebView precedenti al 2021 circa arriva solo quando la pagina finisce di caricarsi. Per coprire anche quelle, aspettalo brevemente prima di decidere:

function whenFolderHelperReady(done) {
  var tries = 0;
  (function check() {
    if (window.WebToApkFS) return done(window.WebToApkFS);
    if (++tries > 30) return done(null);       // in a browser, or the build did not enable it
    setTimeout(check, 100);
  })();
}

whenFolderHelperReady(function (fs) {
  var usable = !!(fs && fs.isAvailable());
  document.getElementById('folder-panel').hidden = !usable;
});

All'interno di un tocco su un pulsante la pagina ha giĂ finito di caricarsi da un pezzo, quindi basta un controllo diretto:

if (window.WebToApkFS && window.WebToApkFS.isAvailable()) {
  // show the folder features
}

Trasforma i due controlli in un unico stato che governa l'intera schermata della cartella:

function folderState() {
  var fs = window.WebToApkFS;
  if (!fs || !fs.isAvailable()) return 'unsupported';   // this build has no folder access
  return fs.hasAccess() ? 'ready' : 'needs-folder';
}

var state = folderState();
document.getElementById('folder-panel').hidden = state === 'unsupported';
document.getElementById('choose-folder').hidden = state !== 'needs-folder';
document.getElementById('export').disabled = state !== 'ready';

Note: Questo non dice se una cartella è già stata scelta: usa WebToApkFS.hasAccess() per quello. Prima che la pagina sia caricata, il metodo grezzo window.WebToApk.isFolderAccessEnabled() (stringa "true"/"false") risponde alla stessa domanda.

WebToApkFS.list helper#

WebToApkFS.list(relativePath)

Esempio

Elenca i file e le sottocartelle all'interno di una cartella della cartella concessa.

Restituisce: un array, in modo sincrono: [{ name, uri, isDirectory, length, lastModified }] (length in byte, lastModified in ms dal 1970). Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app, e una cartella concessa.

Un semplice browser di file ('' è la cartella concessa stessa):

function renderFolder(path) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return;
  var ul = document.getElementById('files');
  ul.innerHTML = '';
  window.WebToApkFS.list(path)
    .sort(function (a, b) { return (b.isDirectory - a.isDirectory) || a.name.localeCompare(b.name); })
    .forEach(function (e) {
      var li = document.createElement('li');
      li.textContent = (e.isDirectory ? '[folder] ' : '') + e.name +
        (e.isDirectory ? '' : ' (' + e.length + ' bytes, ' + new Date(e.lastModified).toLocaleDateString() + ')');
      if (e.isDirectory) li.onclick = function () { renderFolder(path ? path + '/' + e.name : e.name); };
      ul.appendChild(li);
    });
}

renderFolder('');

Percorri ogni sottocartella per raccogliere tutte le note Markdown (percorsi che puoi passare a readText):

function findNotes(path, out) {
  out = out || [];
  window.WebToApkFS.list(path).forEach(function (e) {
    var full = path ? path + '/' + e.name : e.name;
    if (e.isDirectory) findNotes(full, out);
    else if (/\.md$/i.test(e.name)) out.push(full);
  });
  return out;
}

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  var notes = findNotes('');
  document.getElementById('count').textContent = notes.length + ' notes';
}

Trova il backup piĂą recente e quanto spazio occupano i backup:

function backupSummary() {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return null;
  var files = window.WebToApkFS.list('backups').filter(function (e) { return !e.isDirectory; });
  if (!files.length) return null;
  var newest = files.reduce(function (a, b) { return b.lastModified > a.lastModified ? b : a; });
  var bytes = files.reduce(function (sum, e) { return sum + e.length; }, 0);
  return { newest: 'backups/' + newest.name, count: files.length, kb: Math.round(bytes / 1024) };
}

var s = backupSummary();
document.getElementById('backup-info').textContent = s
  ? s.count + ' backups, ' + s.kb + ' KB. Latest: ' + s.newest
  : 'No backups yet';

Note: Cartella vuota, cartella mancante, un percorso che è un file, un percorso con .. e nessuna cartella concessa restituiscono tutti []. L'elenco non è ordinato. Ogni uri funziona anche con WebToApkFS.stat() e WebToApkFS.readBytes().

WebToApkFS.mkdir helper#

WebToApkFS.mkdir(relativePath)

Esempio

Crea una cartella all'interno della cartella concessa, incluse eventuali cartelle superiori mancanti.

Restituisce: un booleano, in modo sincrono: true quando la cartella esiste dopo la chiamata. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app, e una cartella concessa.

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  var month = new Date().toISOString().slice(0, 7);          // "2026-06"
  if (window.WebToApkFS.mkdir('journal/' + month)) {
    window.WebToApkFS.writeText('journal/' + month + '/readme.txt', 'One file per day.');
  } else {
    alert('Could not create the folder.');
  }
}

Prepara la struttura delle cartelle di un progetto al primo utilizzo e segnala ogni cartella che non è stato possibile creare:

function createProjectFolders(project) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return false;
  var failed = ['images', 'exports', 'drafts'].filter(function (sub) {
    return !window.WebToApkFS.mkdir('projects/' + project + '/' + sub);
  });
  if (failed.length) alert('Could not create: ' + failed.join(', '));
  return failed.length === 0;
}

createProjectFolders('holiday-2026');

Un pulsante "Nuova cartella" che avvisa quando il nome è già usato (perché mkdir risponde true per una cartella esistente):

document.getElementById('new-folder').addEventListener('click', function () {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return;
  var name = (prompt('Folder name') || '').trim();
  if (!name || /[\\/]/.test(name) || name === '.' || name === '..') return;
  var taken = window.WebToApkFS.list('').some(function (e) { return e.name === name; });
  if (taken) { alert('"' + name + '" already exists.'); return; }
  if (window.WebToApkFS.mkdir(name)) renderFolder('');   // your app: refresh the list
  else alert('Could not create "' + name + '".');
});

Note: Restituisce true se la cartella esiste già , quindi chiamarlo prima di ogni salvataggio va bene. Restituisce false quando c'è di mezzo un FILE con quel nome, il percorso contiene .. oppure non è stata concessa alcuna cartella. WebToApkFS.writeText() crea già le cartelle superiori mancanti, quindi mkdir serve solo per le cartelle vuote.

WebToApkFS.readText helper#

WebToApkFS.readText(relativePath)

Esempio

Legge un file di testo dalla cartella concessa come UTF-8.

Restituisce: una stringa, in modo sincrono: il testo del file, oppure ''. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app, e una cartella concessa.

'' significa "file vuoto" OPPURE "file inesistente". Usa WebToApkFS.stat() quando devi sapere quale dei due:

function loadSettings() {
  if (!window.WebToApkFS) return null;
  var info = window.WebToApkFS.stat('data/settings.json');
  if (!info.ok) return null;                                // not saved yet
  try {
    return JSON.parse(window.WebToApkFS.readText('data/settings.json'));
  } catch (e) {
    alert('settings.json is damaged: ' + e.message);
    return null;
  }
}

Apri una nota in un campo di testo:

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  document.getElementById('editor').value = window.WebToApkFS.readText('notes/today.txt');
}

Importa un CSV che l'utente ha messo nella cartella:

function importCsv(path) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return [];
  var text = window.WebToApkFS.readText(path);
  if (!text) return [];                                     // missing or empty
  var lines = text.replace(/\r/g, '').split('\n').filter(Boolean);
  var header = lines.shift().split(',');
  return lines.map(function (line) {
    var cells = line.split(',');
    var row = {};
    header.forEach(function (h, i) { row[h.trim()] = (cells[i] || '').trim(); });
    return row;
  });
}

var contacts = importCsv('import/contacts.csv');
console.log(contacts.length + ' rows imported');

Note: L'intero file viene caricato in una volta: per file grandi o binari usa WebToApkFS.readBytes(). I byte che non sono UTF-8 valido tornano come carattere di sostituzione. Anche una cartella, un percorso con .. o nessuna cartella concessa restituiscono ''.

WebToApkFS.rename helper#

WebToApkFS.rename(relativePath, newName)

Esempio

Rinomina un file o una cartella all'interno della cartella concessa.

Restituisce: un booleano, in modo sincrono. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app, e una cartella concessa.

newName è solo il nuovo nome, non un percorso. La voce resta nella stessa cartella:

function renameNote(path, newName) {
  if (!window.WebToApkFS) return false;
  var ok = window.WebToApkFS.rename(path, newName);     // 'notes/a.txt', 'b.txt' -> notes/b.txt
  if (!ok) alert('Could not rename. A file called "' + newName + '" may already exist.');
  return ok;
}

renameNote('notes/today.txt', 'journal.txt');
renameNote('notes/2025', 'archive-2025');               // folders work too

Rinomina senza conflitti: prova "nome (2)", "nome (3)" … finchĂ© non trovi un nome libero:

function renameUnique(path, wanted) {
  if (!window.WebToApkFS) return '';
  var dot = wanted.lastIndexOf('.');
  var base = dot > 0 ? wanted.slice(0, dot) : wanted;
  var ext = dot > 0 ? wanted.slice(dot) : '';
  for (var n = 1; n <= 20; n++) {
    var name = n === 1 ? wanted : base + ' (' + n + ')' + ext;
    if (window.WebToApkFS.rename(path, name)) return name;
  }
  return '';
}

var finalName = renameUnique('photos/IMG_0042.jpg', 'Beach.jpg');

Salva in sicurezza: scrivi prima un file temporaneo, poi sostituiscilo (la rinomina rifiuta un nome giĂ usato, quindi il vecchio file viene eliminato subito prima):

function saveSafely(folder, name, text) {
  var fs = window.WebToApkFS;
  if (!fs || !fs.hasAccess()) return false;
  var tmp = folder + '/' + name + '.tmp';
  if (!fs.writeText(tmp, text)) return false;           // the original is untouched
  fs.delete(folder + '/' + name);                       // false when it did not exist yet
  return fs.rename(tmp, name);
}

saveSafely('notes', 'today.txt', document.getElementById('editor').value);

Note: Restituisce false quando un'altra voce ha già newName, quando newName è vuoto, ./.., o contiene / o \, quando il percorso non esiste o quando non è concessa alcuna cartella. Per spostare un file tra cartelle, leggilo, scrivilo nella nuova posizione, poi elimina il vecchio.

WebToApkFS.requestAccess helper#

WebToApkFS.requestAccess()

Esempio

Apre il selettore di cartelle di sistema così l'utente può dare all'app una cartella, mantenuta tra i riavvii.

Restituisce: una Promise che si risolve sempre (non rifiuta mai) con { success, folderUri, requestId }, piĂą error quando success è falso. success è un booleano; folderUri è l'indirizzo content://…/tree/… della cartella concessa, oppure ''. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app.

Chiedi da un pulsante che l'utente tocca e mostra quale cartella è collegata:

document.getElementById('choose-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) {
    alert('Folder access is not available in this app.');
    return;
  }
  var r = await window.WebToApkFS.requestAccess();
  if (!r.success) {
    document.getElementById('status').textContent = 'No folder chosen.';
    return;
  }
  var name = decodeURIComponent(r.folderUri.split('/tree/')[1] || '').split(':').pop();
  document.getElementById('status').textContent = 'Saving to: ' + name;
  window.WebToApkFS.writeText('hello.txt', 'Connected at ' + new Date().toISOString());
});

Chiedi solo quando non è ancora stata concessa alcuna cartella:

async function ensureFolder() {
  if (!window.WebToApkFS) return false;
  if (window.WebToApkFS.hasAccess()) return true;
  var r = await window.WebToApkFS.requestAccess();
  return r.success;
}

Un pulsante "Cambia cartella" che spiega tutti i modi in cui la richiesta può fallire:

var folderErrors = {
  cancelled: 'No folder chosen.',
  busy: 'The folder picker is already open.',
  not_persisted: 'Android would not keep access to that folder. Try another one.',
  disabled: 'Folder access is switched off in this app.',
  no_picker: 'This phone has no folder picker.',
  foreign_origin: 'This page is not allowed to use folders.'
};

document.getElementById('change-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) return;
  var r = await window.WebToApkFS.requestAccess();
  document.getElementById('status').textContent = r.success
    ? 'Folder changed.'
    : (folderErrors[r.error] || 'Could not open the folder picker.');
  if (r.success) renderFolder('');                      // your app: show the new folder
});

Note: Chiamalo da un tocco, mai al caricamento della pagina: il selettore copre l'app. success: false arriva con error: 'cancelled' (l'utente ha annullato), 'not_persisted' (Android non ha mantenuto la concessione), 'disabled', 'busy' (c'è già un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), 'no_picker' o 'foreign_origin'. Scegliere di nuovo sostituisce la cartella precedente.

WebToApkFS.writeText helper#

WebToApkFS.writeText(relativePath, content)

Esempio

Scrive un file di testo UTF-8 nella cartella concessa, creandolo (e le cartelle superiori mancanti) oppure sostituendolo.

Restituisce: un booleano, in modo sincrono: true quando il file è stato scritto. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app, e una cartella concessa.

Salva da un pulsante:

document.getElementById('save').addEventListener('click', async function () {
  if (!window.WebToApkFS) { alert('Saving to a folder is not available here.'); return; }
  if (!window.WebToApkFS.hasAccess()) {
    var r = await window.WebToApkFS.requestAccess();
    if (!r.success) return;                              // user cancelled
  }
  var ok = window.WebToApkFS.writeText('notes/today.txt', document.getElementById('editor').value);
  document.getElementById('status').textContent = ok ? 'Saved' : 'Could not save';
});

Salva un oggetto come JSON:

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  window.WebToApkFS.writeText('data/settings.json', JSON.stringify({ theme: 'dark', size: 16 }, null, 2));
}

Aggiungi una riga a un log giornaliero (non esiste l'append, quindi leggi, aggiungi e riscrivi):

function logLine(message) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return false;
  var path = 'logs/' + new Date().toISOString().slice(0, 10) + '.log';   // one file per day
  var old = window.WebToApkFS.readText(path);
  var line = new Date().toLocaleTimeString() + '  ' + message + '\n';
  return window.WebToApkFS.writeText(path, old + line);
}

logLine('Order #1042 marked as shipped');

Note: Un file esistente viene sostituito completamente: non c'è append. content viene convertito in stringa (null diventa ''). Restituisce false quando il percorso è una cartella esistente, contiene .. oppure non è concessa alcuna cartella. Il tipo di file deriva dall'estensione, quindi il nome su disco è esattamente quello che hai dato.

clearFolderAccess bridge#

window.WebToApk.clearFolderAccess()

Rilascia la concessione persistente della cartella. Rifiutato (nessuna azione) per una pagina esterna.

Esempio

Dimentica la cartella concessa e restituisce l'autorizzazione ad Android.

Restituisce: niente (sincrono). WebToApkFS.clearAccess() fa lo stesso. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso); con l'opzione disattivata non fa nulla.

Un pulsante "Disconnetti":

document.getElementById('disconnect').addEventListener('click', function () {
  if (window.WebToApkFS) {
    window.WebToApkFS.clearAccess();
  } else if (window.WebToApk && typeof window.WebToApk.clearFolderAccess === 'function') {
    window.WebToApk.clearFolderAccess();
  }
  document.getElementById('folder-name').textContent = 'No folder chosen';
});

All'uscita dall'account, dimentica anche la cartella, così la persona successiva su questo telefono sceglie la propria:

function signOut() {
  localStorage.clear();
  if (window.WebToApk && typeof window.WebToApk.clearFolderAccess === 'function') {
    window.WebToApk.clearFolderAccess();
  }
  location.href = 'login.html';
}

Dimentica una cartella che è stata spostata o eliminata nell'app File, poi offri di sceglierla di nuovo (l'app scrive myapp-folder.json quando una cartella viene collegata per la prima volta):

async function reconnectIfMissing() {
  var fs = window.WebToApkFS;
  if (!fs || !fs.hasAccess()) return;
  if (fs.stat('myapp-folder.json').ok) return;          // marker still there: the folder is fine
  if (typeof window.WebToApk.clearFolderAccess === 'function') {
    window.WebToApk.clearFolderAccess();                // drop the grant that points nowhere
  }
  if (confirm('The folder you chose earlier is gone. Choose a folder again?')) {
    var r = await fs.requestAccess();
    if (r.success) fs.writeText('myapp-folder.json', '{}');
  }
}

Note: I file nella cartella NON vengono eliminati; l'app semplicemente non può più raggiungerli. Dopo questo, hasFolderAccess() risponde "false" e ogni metodo per i file fallisce finché l'utente non sceglie di nuovo una cartella.

deleteEntry bridge#

window.WebToApk.deleteEntry(relativePath: String, recursive: String): String

Accesso alle cartelle adatto a Play, tramite ACTION_OPEN_DOCUMENT_TREE con autorizzazioni URI persistenti. Uso in JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." oppure "" window.WebToApk.clearFolderAccess() // rilascia l'accesso persistente Callback da Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4° argomento, "" in caso di successo) indica perchĂ© una richiesta è fallita: "disabled", "foreign_origin", "busy" (c'è giĂ un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), "cancelled", "not_persisted", "no_picker".

Descritto dal suo gruppo, Bridge Accesso nativo alle cartelle (SAF), invece che da solo.

Esempio

Elimina un file o una cartella all'interno della cartella concessa.

Restituisce: la STRINGA "true" o "false", in modo sincrono. WebToApkFS.delete(path, { recursive }) restituisce un vero booleano. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso) e una cartella concessa.

Con l'helper, dopo aver chiesto all'utente:

function removeEntry(path, isFolder) {
  if (!window.WebToApkFS) return false;
  if (!confirm('Delete ' + path + '? This cannot be undone.')) return false;
  return window.WebToApkFS.delete(path, { recursive: isFolder });
}

removeEntry('notes/old.txt', false);
removeEntry('notes/2025', true);      // the folder and everything in it

La chiamata grezza: il secondo argomento è la STRINGA 'true' oppure 'false':

if (window.WebToApk && typeof window.WebToApk.deleteEntry === 'function') {
  var gone = window.WebToApk.deleteEntry('cache', 'true') === 'true';
}

Tieni solo i cinque backup piĂą recenti ed elimina gli altri:

function pruneBackups(keep) {
  var fs = window.WebToApkFS;
  if (!fs) return 0;
  var files = fs.list('backups').filter(function (e) { return !e.isDirectory; });
  files.sort(function (a, b) { return b.lastModified - a.lastModified; });   // newest first
  var removed = 0;
  files.slice(keep).forEach(function (e) {
    if (fs.delete('backups/' + e.name)) removed++;
  });
  return removed;
}

console.log(pruneBackups(5) + ' old backups deleted');

Note: Una cartella non vuota viene eliminata solo con 'true' ricorsivo. La cartella concessa stessa ('') non può mai essere eliminata. L'eliminazione è reale e immediata: non c'è cestino.

getFolderAccessUri bridge#

window.WebToApk.getFolderAccessUri(): String

L'URI ad albero `content://` della cartella concessa, oppure "" (nessuna, disattivata o pagina esterna).

Esempio

Fornisce l'indirizzo della cartella concessa dall'utente, così puoi mostrare quale cartella è collegata.

Restituisce: una stringa, in modo sincrono: un URI ad albero come content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FNotes, oppure "" quando non è concessa alcuna cartella o l'opzione è disattivata. WebToApkFS.getAccessUri() restituisce la stessa stringa. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso).

Trasforma l'URI in un nome che l'utente riconosce:

function folderLabel() {
  var uri = '';
  if (window.WebToApkFS) {
    uri = window.WebToApkFS.getAccessUri();
  } else if (window.WebToApk && typeof window.WebToApk.getFolderAccessUri === 'function') {
    uri = window.WebToApk.getFolderAccessUri();
  }
  if (!uri) return '';
  var tail = uri.split('/tree/')[1] || '';
  try { tail = decodeURIComponent(tail); } catch (e) {}
  return tail.split(':').pop() || tail;          // "primary:Documents/Notes" -> "Documents/Notes"
}

var label = folderLabel();
document.getElementById('folder-name').textContent = label ? 'Saving to: ' + label : 'No folder chosen';

Ricarica l'elenco dei file solo quando l'utente ha davvero scelto una cartella diversa:

document.getElementById('change-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) return;
  var before = window.WebToApkFS.getAccessUri();
  var r = await window.WebToApkFS.requestAccess();
  if (!r.success) return;                        // the old folder is still connected
  if (r.folderUri !== before) {
    renderFiles(window.WebToApkFS.list(''));     // a new place: show what is in it
  }
});

Indica se la cartella è sul telefono, su una scheda SD o in un'altra app di archiviazione:

function folderLocation() {
  var uri = window.WebToApkFS ? window.WebToApkFS.getAccessUri() : '';
  if (!uri) return 'none';
  var id = uri.split('/tree/')[1] || '';
  try { id = decodeURIComponent(id); } catch (e) {}
  if (uri.indexOf('com.android.externalstorage.documents') === -1) return 'other';   // e.g. a cloud drive app
  return id.indexOf('primary:') === 0 ? 'phone' : 'sd-card';                         // "1A2B-3C4D:Music" is a card
}

document.getElementById('folder-where').textContent =
  { none: '', phone: 'Phone storage', 'sd-card': 'SD card', other: 'Another storage app' }[folderLocation()];

Note: Non passare mai questo URI ai metodi per i file: tutti accettano percorsi relativi a questa cartella. Mostra il nome accanto a un pulsante "Disconnetti" così l'utente sa dove vanno i file.

hasFolderAccess bridge#

window.WebToApk.hasFolderAccess(): String

Accesso alle cartelle adatto a Play, tramite ACTION_OPEN_DOCUMENT_TREE con autorizzazioni URI persistenti. Uso in JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." oppure "" window.WebToApk.clearFolderAccess() // rilascia l'accesso persistente Callback da Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4° argomento, "" in caso di successo) indica perchĂ© una richiesta è fallita: "disabled", "foreign_origin", "busy" (c'è giĂ un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), "cancelled", "not_persisted", "no_picker".

Descritto dal suo gruppo, Bridge Accesso nativo alle cartelle (SAF), invece che da solo.

Esempio

Ti dice se l'utente ha giĂ dato all'app una cartella.

Restituisce: la STRINGA "true" o "false" (non un booleano), in modo sincrono. WebToApkFS.hasAccess() dà un vero booleano. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso); senza di esso la risposta è sempre "false".

Il metodo grezzo esiste fin dalla primissima riga della pagina, quindi è il controllo giusto mentre la pagina si sta ancora caricando (WebToApkFS viene aggiunto solo quando il caricamento termina):

function folderConnected() {
  if (window.WebToApkFS) return window.WebToApkFS.hasAccess();
  return !!(window.WebToApk && typeof window.WebToApk.hasFolderAccess === 'function' &&
    window.WebToApk.hasFolderAccess() === 'true');
}

document.getElementById('choose-folder').hidden = folderConnected();

Controlla prima di un salvataggio e apri il selettore solo quando non è ancora collegata alcuna cartella:

async function saveBackup(json) {
  if (!window.WebToApkFS) return false;
  if (!window.WebToApkFS.hasAccess()) {
    var r = await window.WebToApkFS.requestAccess();   // asked once; the grant is remembered
    if (!r.success) return false;
  }
  return window.WebToApkFS.writeText('backup.json', json);
}

document.getElementById('backup').addEventListener('click', function () {
  saveBackup(JSON.stringify(appState));
});

Un timer di salvataggio automatico che scrive solo quando una cartella è collegata e non apre mai il selettore da solo:

setInterval(function () {
  if (!window.WebToApk || typeof window.WebToApk.hasFolderAccess !== 'function') return;
  if (window.WebToApk.hasFolderAccess() !== 'true') return;   // not connected: the Save button asks
  window.WebToApk.writeTextFile('drafts/autosave.txt', document.getElementById('editor').value);
}, 30000);

Note: Confronta con === 'true'; la stringa "false" è vera (truthy). Dice solo che l'app ricorda una cartella. Se quella cartella è stata eliminata o spostata nell'app File, listFolderEntries('') risponde []: chiedi all'utente di sceglierla di nuovo.

isFolderAccessEnabled bridge#

window.WebToApk.isFolderAccessEnabled(): String

Accesso alle cartelle adatto a Play, tramite ACTION_OPEN_DOCUMENT_TREE con autorizzazioni URI persistenti. Uso in JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." oppure "" window.WebToApk.clearFolderAccess() // rilascia l'accesso persistente Callback da Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4° argomento, "" in caso di successo) indica perchĂ© una richiesta è fallita: "disabled", "foreign_origin", "busy" (c'è giĂ un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), "cancelled", "not_persisted", "no_picker".

Descritto dal suo gruppo, Bridge Accesso nativo alle cartelle (SAF), invece che da solo.

Esempio

Ti dice se questa app è stata creata con l'accesso alle cartelle attivato.

Restituisce: la STRINGA "true" o "false", in modo sincrono. Richiede: niente per chiamarlo; risponde "true" solo quando Accesso nativo alle cartelle (SAF) è stato selezionato nel Passaggio 4 (Accesso).

Usalo per decidere se mostrare la funzione cartella. WebToApkFS.isAvailable() risponde alla stessa domanda come booleano una volta caricata la pagina:

function folderFeatureShipped() {
  if (window.WebToApkFS) return window.WebToApkFS.isAvailable();
  return !!(window.WebToApk && typeof window.WebToApk.isFolderAccessEnabled === 'function' &&
    window.WebToApk.isFolderAccessEnabled() === 'true');
}

if (!folderFeatureShipped()) {
  document.getElementById('folder-panel').hidden = true;   // in a browser, or the switch was off
}

Gestisci una riga delle impostazioni nei suoi tre stati (non in questa build, non collegata, collegata):

function folderSettingsState() {
  var fs = window.WebToApkFS;
  if (!fs || !fs.isAvailable()) return 'unavailable';
  return fs.hasAccess() ? 'connected' : 'not-connected';
}

var row = document.getElementById('folder-row');
switch (folderSettingsState()) {
  case 'unavailable':   row.hidden = true; break;
  case 'not-connected': row.querySelector('button').textContent = 'Choose a folder'; break;
  case 'connected':     row.querySelector('button').textContent = 'Disconnect'; break;
}

Contrassegna la pagina prima che venga disegnata, così il CSS può mostrare i controlli solo-cartella senza sfarfallio:

// In a <script> inside <head>: the raw method is already there.
if (window.WebToApk && typeof window.WebToApk.isFolderAccessEnabled === 'function' &&
    window.WebToApk.isFolderAccessEnabled() === 'true') {
  document.documentElement.classList.add('has-folders');   // .has-folders .folder-only { display: block }
}

Note: Questo non dice se una cartella è già stata scelta: usa hasFolderAccess() per quello. In un browser window.WebToApk non esiste, quindi il controllo qui sopra risponde false senza generare errori.

listFolderEntries bridge#

window.WebToApk.listFolderEntries(relativePath: String): String

Accesso alle cartelle adatto a Play, tramite ACTION_OPEN_DOCUMENT_TREE con autorizzazioni URI persistenti. Uso in JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." oppure "" window.WebToApk.clearFolderAccess() // rilascia l'accesso persistente Callback da Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4° argomento, "" in caso di successo) indica perchĂ© una richiesta è fallita: "disabled", "foreign_origin", "busy" (c'è giĂ un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), "cancelled", "not_persisted", "no_picker".

Descritto dal suo gruppo, Bridge Accesso nativo alle cartelle (SAF), invece che da solo.

Esempio

Elenca i file e le sottocartelle all'interno di una cartella della cartella concessa.

Restituisce: una STRINGA JSON, in modo sincrono: un array di { name, uri, isDirectory, length, lastModified } (length in byte, lastModified in ms dal 1970). WebToApkFS.list(path) la analizza per te. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso) e una cartella concessa.

Con l'helper ('' è la cartella concessa stessa):

function showFolder(path) {
  if (!window.WebToApkFS) return;
  var entries = window.WebToApkFS.list(path);          // already an array
  entries.forEach(function (e) {
    console.log(e.isDirectory ? '[dir] ' : '      ', e.name, e.length + ' bytes');
  });
}
showFolder('');          // the root
showFolder('notes');     // a sub-folder

La chiamata grezza, che restituisce testo da analizzare:

if (window.WebToApk && typeof window.WebToApk.listFolderEntries === 'function') {
  var entries = JSON.parse(window.WebToApk.listFolderEntries('notes'));
  console.log(entries.length + ' entries');
}

Trova il file di backup più recente e mostra quando è stato creato:

function latestBackup() {
  if (!window.WebToApkFS) return null;
  var backups = window.WebToApkFS.list('backups').filter(function (e) {
    return !e.isDirectory && /\.json$/i.test(e.name);
  });
  backups.sort(function (a, b) { return b.lastModified - a.lastModified; });   // newest first
  return backups[0] || null;
}

var last = latestBackup();
document.getElementById('last-backup').textContent =
  last ? 'Last backup: ' + new Date(last.lastModified).toLocaleString() : 'No backups yet';

Note: Una cartella vuota, una cartella mancante, un percorso che è un file, un percorso con .., nessuna cartella concessa e una pagina di un sito web esterno rispondono tutti []: chiama prima hasFolderAccess() se devi distinguere il caso "nessuna cartella". I percorsi sono relativi, usano / e non salgono mai sopra la cartella concessa.

mkdir bridge#

window.WebToApk.mkdir(relativePath: String): String

Accesso alle cartelle adatto a Play, tramite ACTION_OPEN_DOCUMENT_TREE con autorizzazioni URI persistenti. Uso in JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." oppure "" window.WebToApk.clearFolderAccess() // rilascia l'accesso persistente Callback da Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4° argomento, "" in caso di successo) indica perchĂ© una richiesta è fallita: "disabled", "foreign_origin", "busy" (c'è giĂ un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), "cancelled", "not_persisted", "no_picker".

Descritto dal suo gruppo, Bridge Accesso nativo alle cartelle (SAF), invece che da solo.

Esempio

Crea una cartella all'interno della cartella concessa, incluse eventuali cartelle superiori mancanti.

Restituisce: la STRINGA "true" o "false", in modo sincrono. WebToApkFS.mkdir(path) restituisce un vero booleano. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso) e una cartella concessa.

Con l'helper:

if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
  var ok = window.WebToApkFS.mkdir('photos/2026/june');   // creates photos, 2026 and june
  if (!ok) console.log('Could not create the folder');
}

La chiamata grezza:

if (window.WebToApk && typeof window.WebToApk.mkdir === 'function') {
  var made = window.WebToApk.mkdir('backups') === 'true';
}

Crea una cartella con la data per un'esportazione e scrivi al suo interno ogni elemento:

function exportAll(items) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return false;
  var dir = 'exports/' + new Date().toISOString().slice(0, 10);   // e.g. exports/2026-09-25
  if (!window.WebToApkFS.mkdir(dir)) {
    alert('Could not create ' + dir + ' (is a file with that name in the way?)');
    return false;
  }
  items.forEach(function (item) {
    window.WebToApkFS.writeText(dir + '/' + item.id + '.json', JSON.stringify(item));
  });
  return true;
}

Note: Risponde "true" quando la cartella esiste già , quindi si può chiamare in sicurezza prima di ogni salvataggio. Risponde "false" quando c'è di mezzo un FILE con quel nome, il percorso contiene .. oppure non è stata concessa alcuna cartella.

readTextFile bridge#

window.WebToApk.readTextFile(relativePath: String): String

Accesso alle cartelle adatto a Play, tramite ACTION_OPEN_DOCUMENT_TREE con autorizzazioni URI persistenti. Uso in JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." oppure "" window.WebToApk.clearFolderAccess() // rilascia l'accesso persistente Callback da Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4° argomento, "" in caso di successo) indica perchĂ© una richiesta è fallita: "disabled", "foreign_origin", "busy" (c'è giĂ un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), "cancelled", "not_persisted", "no_picker".

Descritto dal suo gruppo, Bridge Accesso nativo alle cartelle (SAF), invece che da solo.

Esempio

Legge un file di testo dalla cartella concessa come UTF-8.

Restituisce: una stringa, in modo sincrono: il testo del file, oppure "". WebToApkFS.readText(path) restituisce lo stesso. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso) e una cartella concessa.

"" significa "file vuoto" OPPURE "file inesistente", quindi controlla con WebToApkFS.stat() quando la differenza conta:

function loadNote(path) {
  if (!window.WebToApkFS) return null;
  var info = window.WebToApkFS.stat(path);    // { ok: true, name, size, ... } or { ok: false, error: 'not-found' }
  if (!info.ok) return null;                  // the file does not exist
  return window.WebToApkFS.readText(path);    // '' here really is an empty file
}

var text = loadNote('notes/today.txt');
document.getElementById('editor').value = text === null ? '' : text;

La chiamata grezza:

if (window.WebToApk && typeof window.WebToApk.readTextFile === 'function') {
  var raw = window.WebToApk.readTextFile('notes/today.txt');
  console.log(raw.length + ' characters');
}

Importa ogni nota .txt e .md da una cartella:

function importNotes(folder) {
  if (!window.WebToApkFS) return [];
  return window.WebToApkFS.list(folder)
    .filter(function (e) { return !e.isDirectory && /\.(txt|md)$/i.test(e.name); })
    .map(function (e) {
      return {
        title: e.name.replace(/\.(txt|md)$/i, ''),
        body: window.WebToApkFS.readText(folder + '/' + e.name)
      };
    });
}

renderNotes(importNotes('notes'));

Note: L'intero file viene letto in memoria: va bene per note e JSON, non per file grandi; per quelli usa WebToApkFS.readBytes. I byte che non sono UTF-8 valido tornano come carattere di sostituzione. Una cartella, un percorso con .., nessuna cartella concessa o una pagina di un sito web esterno rispondono tutti "".

renameEntry bridge#

window.WebToApk.renameEntry(relativePath: String, newName: String): String

Accesso alle cartelle adatto a Play, tramite ACTION_OPEN_DOCUMENT_TREE con autorizzazioni URI persistenti. Uso in JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." oppure "" window.WebToApk.clearFolderAccess() // rilascia l'accesso persistente Callback da Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4° argomento, "" in caso di successo) indica perchĂ© una richiesta è fallita: "disabled", "foreign_origin", "busy" (c'è giĂ un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), "cancelled", "not_persisted", "no_picker".

Descritto dal suo gruppo, Bridge Accesso nativo alle cartelle (SAF), invece che da solo.

Esempio

Rinomina un file o una cartella all'interno della cartella concessa.

Restituisce: la STRINGA "true" o "false", in modo sincrono. WebToApkFS.rename(path, newName) restituisce un vero booleano. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso) e una cartella concessa.

newName è un semplice nome, non un percorso: la voce resta nella stessa cartella:

if (window.WebToApkFS) {
  var ok = window.WebToApkFS.rename('notes/today.txt', 'journal.txt');   // -> notes/journal.txt
  if (!ok) alert('Rename failed. Is there already a file called journal.txt?');
}

La chiamata grezza:

if (window.WebToApk && typeof window.WebToApk.renameEntry === 'function') {
  var renamed = window.WebToApk.renameEntry('old-folder', 'new-folder') === 'true';
}

Tieni il backup precedente rinominandolo prima di scrivere quello nuovo:

function rotateBackup(json) {
  var fs = window.WebToApkFS;
  if (!fs || !fs.hasAccess()) return false;
  if (fs.stat('backup.json').ok) {
    var stamp = new Date().toISOString().replace(/[:.]/g, '-');   // no characters a file name cannot hold
    fs.rename('backup.json', 'backup-' + stamp + '.json');        // stays in the same folder
  }
  return fs.writeText('backup.json', json);
}

Note: Fallisce quando un'altra voce ha già newName, quando newName è vuoto o contiene / o \, quando il percorso non esiste o quando non è concessa alcuna cartella. Per spostare un file in un'altra cartella, scrivilo lì ed elimina il vecchio.

requestFolderAccess bridge#

window.WebToApk.requestFolderAccess(requestId: String)

Apre il selettore di cartelle di sistema e rende persistente l'accesso in lettura/scrittura alla cartella scelta. La risposta arriva su `WebToApkOnFolderAccessResult`; può essere aperto un solo selettore alla volta, quindi una richiesta fatta mentre uno è aperto riceve subito la risposta `error:"busy"` e quello aperto mantiene la propria risposta.

Esempio

Apre il selettore di cartelle di sistema così l'utente può dare all'app una cartella. Le pagine di solito usano WebToApkFS.requestAccess(), che racchiude questa chiamata in una Promise.

Restituisce: niente. La risposta torna più tardi tramite una callback interna di cui WebToApkFS è proprietario; WebToApkFS.requestAccess() si risolve con { success, folderUri, requestId }, più error quando success è falso. Richiede: seleziona Accesso nativo alle cartelle (SAF) nel Passaggio 4 (Accesso) quando crei l'app.

Chiedi da un tocco su un pulsante, con l'helper:

document.getElementById('choose-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) {
    alert('Folder access is not available in this app.');
    return;
  }
  var r = await window.WebToApkFS.requestAccess();
  if (r.success) {
    console.log('Folder granted:', r.folderUri);   // content://.../tree/...
  } else {
    console.log('No folder chosen');                 // user cancelled, or Android refused the grant
  }
});

Chiedi solo la prima volta, poi prepara la sottocartella propria dell'app:

async function ensureFolder() {
  if (!window.WebToApkFS || !window.WebToApkFS.isAvailable()) return false;
  if (window.WebToApkFS.hasAccess()) return true;          // granted on an earlier run
  var r = await window.WebToApkFS.requestAccess();
  if (!r.success) return false;
  window.WebToApkFS.mkdir('exports');                       // true even if it already exists
  return true;
}

document.getElementById('export').addEventListener('click', async function () {
  if (!(await ensureFolder())) return;
  window.WebToApkFS.writeText('exports/list.csv', buildCsv());
});

Di' all'utente esattamente perché non è stata collegata alcuna cartella, usando i veri codici error:

var folderMessages = {
  cancelled: 'No folder was chosen.',
  not_persisted: 'Android would not keep access to that folder. Please choose another one.',
  busy: 'The folder picker is already open.',
  disabled: 'This app was built without folder access.',
  foreign_origin: 'A folder can only be connected from the app\'s own pages.',
  no_picker: 'This phone has no folder picker.'
};

document.getElementById('choose-folder').addEventListener('click', async function () {
  if (!window.WebToApkFS) return;
  var r = await window.WebToApkFS.requestAccess();
  document.getElementById('status').textContent = r.success
    ? 'Folder connected.'
    : (folderMessages[r.error] || 'Could not connect a folder.');
});

Note: Non chiamare tu il metodo grezzo: la sua unica risposta è la callback che l'helper ascolta. success è false quando l'utente annulla (error:'cancelled'), quando Android non mantiene la concessione ('not_persisted'), e subito quando la build non ha attivato l'opzione ('disabled') o la pagina sullo schermo non appartiene all'app ('foreign_origin'). La concessione viene mantenuta tra i riavvii dell'app; scegliere di nuovo sostituisce la cartella. Può essere aperto un solo selettore alla volta: una richiesta fatta mentre uno è aperto riceve subito la risposta error:'busy', e quello aperto mantiene la propria risposta.

writeTextFile bridge#

window.WebToApk.writeTextFile(relativePath: String, content: String): String

Accesso alle cartelle adatto a Play, tramite ACTION_OPEN_DOCUMENT_TREE con autorizzazioni URI persistenti. Uso in JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." oppure "" window.WebToApk.clearFolderAccess() // rilascia l'accesso persistente Callback da Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4° argomento, "" in caso di successo) indica perchĂ© una richiesta è fallita: "disabled", "foreign_origin", "busy" (c'è giĂ un selettore aperto per una richiesta precedente: quella riceve comunque la propria risposta), "cancelled", "not_persisted", "no_picker".

Descritto dal suo gruppo, Bridge Accesso nativo alle cartelle (SAF), invece che da solo.

Esempio

Scrive un file di testo (UTF-8) nella cartella concessa, creandolo o sostituendone il contenuto.

Restituisce: la STRINGA "true" o "false", in modo sincrono. WebToApkFS.writeText(path, text) restituisce un vero booleano. Richiede: Accesso nativo alle cartelle (SAF) selezionato nel Passaggio 4 (Accesso) e una cartella concessa.

Con l'helper:

function saveNote(path, text) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) {
    alert('Choose a folder first.');
    return false;
  }
  var ok = window.WebToApkFS.writeText(path, text);   // missing parent folders are created
  if (!ok) alert('Could not save ' + path);
  return ok;
}

saveNote('notes/2026/today.txt', document.getElementById('editor').value);

La chiamata grezza, la cui risposta è testo:

if (window.WebToApk && typeof window.WebToApk.writeTextFile === 'function') {
  var saved = window.WebToApk.writeTextFile('data/settings.json', JSON.stringify({ theme: 'dark' })) === 'true';
}

Aggiungi una riga a un log giornaliero: scrivere sostituisce il file, quindi leggilo prima:

function appendLog(line) {
  if (!window.WebToApkFS || !window.WebToApkFS.hasAccess()) return false;
  var now = new Date().toISOString();
  var path = 'logs/' + now.slice(0, 10) + '.log';          // one file per day
  var old = window.WebToApkFS.readText(path);              // '' the first time
  return window.WebToApkFS.writeText(path, old + now + ' ' + line + '\n');
}

appendLog('Order #1042 marked as shipped');

Note: Un file esistente viene sostituito completamente. Fallisce quando il percorso è una cartella esistente, contiene .. oppure non è stata concessa alcuna cartella. Il tipo di file deriva dall'estensione (.json, .txt, .md…), quindi il nome che dai è il nome sul disco.

Generato dal runtime dell'app e dai suoi file di esempio a ogni build della documentazione. Leggilo in Markdown · Tutte le famiglie.

Verificato rispetto al bridge distribuito il 22/10/2026.