API da ponte JavaScript
Arquivos: uma pasta concedida pelo usuário
Um diretório escolhido uma vez, legível e gravável depois, com permissão que sobrevive a reinicializações.
WebToApkFS.clearAccess helper#
WebToApkFS.clearAccess()
Exemplo
Esquece a pasta concedida e devolve a permissão ao Android.
Retorna: nada (síncrono). Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar.
Sempre ofereça uma forma de desconectar ao lado do nome da pasta:
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;
});Um botão que conecta ou desconecta, conforme o estado atual:
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();Troque de conta: esqueça a pasta junto com o restante do estado de login:
function switchAccount() {
sessionStorage.clear();
if (window.WebToApkFS) window.WebToApkFS.clearAccess(); // the next account picks its own folder
showLoginScreen();
}Observações: Os arquivos permanecem onde estão; o app simplesmente não consegue mais acessá-los. Para usar uma pasta de novo, chame WebToApkFS.requestAccess() a partir de um toque.
WebToApkFS.delete helper#
WebToApkFS.delete(relativePath, options)
Exemplo
Exclui um arquivo ou pasta dentro da pasta concedida.
Retorna: um booleano, de forma síncrona: true quando foi excluído. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar e uma pasta concedida.
Exclua um arquivo, depois de perguntar:
if (window.WebToApkFS && confirm('Delete notes/old.txt?')) {
var ok = window.WebToApkFS.delete('notes/old.txt');
if (!ok) alert('Could not delete the file.');
}Exclua uma pasta e tudo dentro dela — passe { 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');
}Limpe exportações com mais de 30 dias, arquivo por arquivo:
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';
}Observações: Sem { recursive: true }, uma pasta só é excluída quando está vazia. A própria pasta concedida ('') nunca pode ser excluída. A exclusão é imediata e não há lixeira, então sempre confirme com o usuário antes.
WebToApkFS.getAccessUri helper#
WebToApkFS.getAccessUri()
Exemplo
Fornece o endereço da pasta concedida, para você mostrar ao usuário para onde os arquivos vão.
Retorna: uma string, de forma síncrona: uma URI de árvore como content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FNotes, ou '' quando nenhuma pasta foi concedida. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar.
Mostre um nome de pasta legível ao lado de um botão Desconectar:
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';Perceba que o usuário escolheu outra pasta desde a última visita e refaça o que você tinha em cache sobre a antiga:
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();Um botão Desconectar que esquece a pasta e confirma que ela se foi:
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';
});Observações: Os métodos de arquivo não recebem esta URI — recebem caminhos relativos à pasta concedida ('notes/today.txt'). O formato da URI pertence ao provedor de arquivos de onde o usuário escolheu, então use-a apenas para exibição.
WebToApkFS.hasAccess helper#
WebToApkFS.hasAccess()
Exemplo
Informa se o usuário já deu uma pasta ao app.
Retorna: um booleano, de forma síncrona. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar.
Mostre "Escolher pasta" ou a lista de arquivos, conforme a resposta:
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');
}
}Salve automaticamente só enquanto houver uma pasta conectada e avise quando não houver:
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);
});Ao iniciar, reabra a última nota quando houver uma pasta lembrada; caso contrário, mostre a tela de boas-vindas:
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);
});Observações: Só diz que o app lembra de uma pasta. Se o usuário apagou ou moveu essa pasta, list('') retorna [] — ofereça "Escolher pasta" de novo. window.WebToApkFS aparece quando a página termina de carregar; durante o carregamento use o bruto window.WebToApk.hasFolderAccess() === 'true'.
WebToApkFS.isAvailable helper#
WebToApkFS.isAvailable()
Exemplo
Informa se este app pode usar uma pasta concedida pelo usuário.
Retorna: um booleano, de forma síncrona: true quando a compilação tem o acesso a pastas ativado. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar — sem isso (e sem Chaves seguras) window.WebToApkFS não é adicionado à página; só com Chaves seguras ele existe e responde false.
window.WebToApkFS é adicionado antes de o primeiro script da página rodar; em WebViews anteriores a cerca de 2021, só chega quando a página termina de carregar. Para cobrir também esses casos, espere por ele brevemente antes de decidir:
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;
});Dentro do toque de um botão a página já terminou de carregar há tempo, então uma verificação direta basta:
if (window.WebToApkFS && window.WebToApkFS.isAvailable()) {
// show the folder features
}Transforme as duas verificações em um único estado que controla toda a tela de pastas:
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';Observações: Isto não diz se uma pasta já foi escolhida — use WebToApkFS.hasAccess() para isso. Antes de a página carregar, o window.WebToApk.isFolderAccessEnabled() bruto (string "true"/"false") responde à mesma pergunta.
WebToApkFS.list helper#
WebToApkFS.list(relativePath)
Exemplo
Lista os arquivos e subpastas dentro de uma pasta da pasta concedida.
Retorna: um array, de forma síncrona: [{ name, uri, isDirectory, length, lastModified }] (length em bytes, lastModified em ms desde 1970). Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar e uma pasta concedida.
Um navegador de arquivos simples ('' é a própria pasta concedida):
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('');Percorra todas as subpastas para reunir todas as notas Markdown (caminhos que você pode passar para 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';
}Encontre o backup mais recente e quanto espaço os backups ocupam:
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';Observações: Pasta vazia, pasta inexistente, um caminho que é um arquivo, um caminho com .. e nenhuma pasta concedida retornam todos []. A lista não é ordenada. Cada uri também funciona com WebToApkFS.stat() e WebToApkFS.readBytes().
WebToApkFS.mkdir helper#
WebToApkFS.mkdir(relativePath)
Exemplo
Cria uma pasta dentro da pasta concedida, incluindo as pastas pai que faltarem.
Retorna: um booleano, de forma síncrona: true quando a pasta existe depois. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar e uma pasta concedida.
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.');
}
}Monte a estrutura de pastas de um projeto no primeiro uso e informe qualquer pasta que não pôde ser criada:
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');Um botão "Nova pasta" que avisa quando o nome já está em uso (porque mkdir responde true para uma pasta existente):
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 + '".');
});Observações: Retorna true se a pasta já existe, então chamar antes de cada salvamento é seguro. Retorna false quando um ARQUIVO com esse nome está no caminho, o caminho contém .. ou nenhuma pasta foi concedida. WebToApkFS.writeText() já cria as pastas pai que faltam, então você só precisa de mkdir para pastas vazias.
WebToApkFS.readText helper#
WebToApkFS.readText(relativePath)
Exemplo
Lê um arquivo de texto da pasta concedida como UTF-8.
Retorna: uma string, de forma síncrona: o texto do arquivo, ou ''. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar e uma pasta concedida.
'' significa "arquivo vazio" OU "arquivo inexistente". Use WebToApkFS.stat() quando precisar saber qual:
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;
}
}Abra uma nota em uma caixa de texto:
if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
document.getElementById('editor').value = window.WebToApkFS.readText('notes/today.txt');
}Importe um CSV que o usuário colocou na pasta:
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');Observações: O arquivo inteiro é carregado de uma vez — para arquivos grandes ou binários, use WebToApkFS.readBytes(). Bytes que não são UTF-8 válido voltam como o caractere de substituição. Uma pasta, um caminho com .. ou nenhuma pasta concedida também retornam ''.
WebToApkFS.rename helper#
WebToApkFS.rename(relativePath, newName)
Exemplo
Renomeia um arquivo ou pasta dentro da pasta concedida.
Retorna: um booleano, de forma síncrona. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar e uma pasta concedida.
newName é só o novo nome, não um caminho. A entrada permanece na mesma pasta:
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 tooRenomeie sem conflito: tente "nome (2)", "nome (3)" … até achar um nome livre:
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');Salve com segurança: grave um arquivo temporário primeiro e depois troque (renomear recusa um nome já usado, então o arquivo antigo é excluído logo antes):
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);Observações: Retorna false quando outra entrada já tem newName, quando newName está vazio, ./.. ou contém / ou \, quando o caminho não existe ou quando nenhuma pasta foi concedida. Para mover um arquivo entre pastas, leia-o, grave-o no novo lugar e depois exclua o antigo.
WebToApkFS.requestAccess helper#
WebToApkFS.requestAccess()
Exemplo
Abre o seletor de pastas do sistema para o usuário dar uma pasta ao app, mantida entre reinicializações.
Retorna: uma Promise que sempre é resolvida (nunca rejeita) com { success, folderUri, requestId }, mais error quando success é false. success é um booleano; folderUri é o endereço content://…/tree/… concedido, ou ''. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar.
Peça a partir de um botão em que o usuário toca e mostre qual pasta está conectada:
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());
});Só peça quando ainda não houver pasta concedida:
async function ensureFolder() {
if (!window.WebToApkFS) return false;
if (window.WebToApkFS.hasAccess()) return true;
var r = await window.WebToApkFS.requestAccess();
return r.success;
}Um botão "Alterar pasta" que explica todas as formas como o pedido pode falhar:
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
});Observações: Chame a partir de um toque, nunca ao carregar a página — o seletor cobre o app. success: false vem com error: 'cancelled' (o usuário voltou atrás), 'not_persisted' (o Android não manteve a concessão), 'disabled', 'busy' (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), 'no_picker' ou 'foreign_origin'. Escolher de novo substitui a pasta anterior.
WebToApkFS.writeText helper#
WebToApkFS.writeText(relativePath, content)
Exemplo
Grava um arquivo de texto UTF-8 na pasta concedida, criando-o (e as pastas pai que faltarem) ou substituindo-o.
Retorna: um booleano, de forma síncrona: true quando o arquivo foi gravado. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar e uma pasta concedida.
Salve a partir de um botão:
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';
});Salve um objeto como JSON:
if (window.WebToApkFS && window.WebToApkFS.hasAccess()) {
window.WebToApkFS.writeText('data/settings.json', JSON.stringify({ theme: 'dark', size: 16 }, null, 2));
}Adicione uma linha a um registro diário (não há append, então leia, acrescente e grave de volta):
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');Observações: Um arquivo existente é substituído por completo — não há append. content é convertido em string (null vira ''). Retorna false quando o caminho é uma pasta existente, contém .. ou nenhuma pasta foi concedida. O tipo do arquivo vem da extensão, então o nome no disco é exatamente o nome que você deu.
clearFolderAccess ponte#
window.WebToApk.clearFolderAccess()
Libera a concessão persistida da pasta. Recusado (sem efeito) para uma página externa.
Exemplo
Esquece a pasta concedida e devolve a permissão ao Android.
Retorna: nada (síncrono). WebToApkFS.clearAccess() faz o mesmo. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso); com a chave desativada, não faz nada.
Um botão "Desconectar":
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';
});Ao sair da conta, esqueça a pasta também, para que a próxima pessoa neste telefone escolha a sua:
function signOut() {
localStorage.clear();
if (window.WebToApk && typeof window.WebToApk.clearFolderAccess === 'function') {
window.WebToApk.clearFolderAccess();
}
location.href = 'login.html';
}Esqueça uma pasta que foi movida ou apagada no app Arquivos e depois ofereça escolher de novo (o app grava myapp-folder.json quando uma pasta é conectada pela primeira vez):
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', '{}');
}
}Observações: Os arquivos da pasta NÃO são excluídos; o app só não consegue mais acessá-los. Depois disso, hasFolderAccess() responde "false" e todo método de arquivo falha até o usuário escolher uma pasta de novo.
deleteEntry ponte#
window.WebToApk.deleteEntry(relativePath: String, recursive: String): String
Acesso a pastas compatível com o Play usando ACTION_OPEN_DOCUMENT_TREE com permissões de URI persistentes. Uso em JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." ou "" window.WebToApk.clearFolderAccess() // libera o acesso persistido Callback do Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4º argumento, "" em caso de sucesso) diz por que um pedido falhou: "disabled", "foreign_origin", "busy" (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), "cancelled", "not_persisted", "no_picker".
Descrito pelo seu grupo, Bridge de acesso nativo a pastas (SAF), e não separadamente.
Exemplo
Exclui um arquivo ou pasta dentro da pasta concedida.
Retorna: a STRING "true" ou "false", de forma síncrona. WebToApkFS.delete(path, { recursive }) retorna um booleano de verdade. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso) e uma pasta concedida.
Com o helper, depois de perguntar ao usuário:
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 itA chamada bruta — o segundo argumento é a STRING 'true' ou 'false':
if (window.WebToApk && typeof window.WebToApk.deleteEntry === 'function') {
var gone = window.WebToApk.deleteEntry('cache', 'true') === 'true';
}Mantenha só os cinco backups mais recentes e exclua o resto:
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');Observações: Uma pasta que não está vazia só é excluída com 'true' recursivo. A própria pasta concedida ('') nunca pode ser excluída. A exclusão é real e imediata: não há lixeira.
getFolderAccessUri ponte#
window.WebToApk.getFolderAccessUri(): String
A URI de árvore `content://` da pasta concedida, ou "" (nenhuma, desativado ou página externa).
Exemplo
Fornece o endereço da pasta que o usuário concedeu, para você mostrar qual pasta está conectada.
Retorna: uma string, de forma síncrona: uma URI de árvore como content://com.android.externalstorage.documents/tree/primary%3ADocuments%2FNotes, ou "" quando nenhuma pasta foi concedida ou a chave está desativada. WebToApkFS.getAccessUri() retorna a mesma string. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso).
Transforme a URI em um nome que o usuário reconheça:
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';Recarregue a lista de arquivos só quando o usuário realmente escolheu outra pasta:
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
}
});Diga se a pasta está no telefone, em um cartão SD ou em outro app de armazenamento:
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()];Observações: Você nunca passa esta URI de volta aos métodos de arquivo — todos recebem caminhos relativos a esta pasta. Mostre o nome ao lado de um botão "Desconectar" para o usuário saber para onde os arquivos vão.
hasFolderAccess ponte#
window.WebToApk.hasFolderAccess(): String
Acesso a pastas compatível com o Play usando ACTION_OPEN_DOCUMENT_TREE com permissões de URI persistentes. Uso em JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." ou "" window.WebToApk.clearFolderAccess() // libera o acesso persistido Callback do Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4º argumento, "" em caso de sucesso) diz por que um pedido falhou: "disabled", "foreign_origin", "busy" (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), "cancelled", "not_persisted", "no_picker".
Descrito pelo seu grupo, Bridge de acesso nativo a pastas (SAF), e não separadamente.
Exemplo
Informa se o usuário já deu uma pasta ao app.
Retorna: a STRING "true" ou "false" (não um booleano), de forma síncrona. WebToApkFS.hasAccess() dá um booleano de verdade. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso); sem isso a resposta é sempre "false".
O método bruto existe desde a primeira linha da página, então é a verificação certa enquanto a página ainda carrega (WebToApkFS só é adicionado quando o carregamento 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();Verifique antes de salvar e só abra o seletor quando ainda não houver pasta conectada:
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));
});Um temporizador de salvamento automático que só grava quando há pasta conectada e nunca abre o seletor por conta própria:
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);Observações: Compare com === 'true'; a string "false" é verdadeira. Ela só diz que o app lembra de uma pasta. Se essa pasta foi apagada ou movida no app Arquivos, listFolderEntries('') responde [] — peça ao usuário para escolhê-la de novo.
isFolderAccessEnabled ponte#
window.WebToApk.isFolderAccessEnabled(): String
Acesso a pastas compatível com o Play usando ACTION_OPEN_DOCUMENT_TREE com permissões de URI persistentes. Uso em JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." ou "" window.WebToApk.clearFolderAccess() // libera o acesso persistido Callback do Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4º argumento, "" em caso de sucesso) diz por que um pedido falhou: "disabled", "foreign_origin", "busy" (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), "cancelled", "not_persisted", "no_picker".
Descrito pelo seu grupo, Bridge de acesso nativo a pastas (SAF), e não separadamente.
Exemplo
Informa se este app foi compilado com o acesso a pastas ativado.
Retorna: a STRING "true" ou "false", de forma síncrona. Requer: nada para chamar; só responde "true" quando Acesso nativo a pastas (SAF) foi marcado na Etapa 4 (Acesso).
Use para decidir se mostra o recurso de pastas. WebToApkFS.isAvailable() responde à mesma pergunta como booleano depois que a página carrega:
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
}Conduza uma linha de configurações por seus três estados (não está nesta compilação, não conectada, conectada):
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;
}Marque a página antes de ser desenhada, para o CSS poder mostrar controles de pasta sem um piscar:
// 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 }
}Observações: Isto não diz se uma pasta já foi escolhida — use hasFolderAccess() para isso. Em um navegador, window.WebToApk não existe, então a verificação acima responde false sem lançar erro.
listFolderEntries ponte#
window.WebToApk.listFolderEntries(relativePath: String): String
Acesso a pastas compatível com o Play usando ACTION_OPEN_DOCUMENT_TREE com permissões de URI persistentes. Uso em JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." ou "" window.WebToApk.clearFolderAccess() // libera o acesso persistido Callback do Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4º argumento, "" em caso de sucesso) diz por que um pedido falhou: "disabled", "foreign_origin", "busy" (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), "cancelled", "not_persisted", "no_picker".
Descrito pelo seu grupo, Bridge de acesso nativo a pastas (SAF), e não separadamente.
Exemplo
Lista os arquivos e subpastas dentro de uma pasta da pasta concedida.
Retorna: uma STRING JSON, de forma síncrona: um array de { name, uri, isDirectory, length, lastModified } (length em bytes, lastModified em ms desde 1970). WebToApkFS.list(path) a converte para você. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso) e uma pasta concedida.
Com o helper ('' é a própria pasta concedida):
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-folderA chamada bruta, que retorna texto que você precisa converter:
if (window.WebToApk && typeof window.WebToApk.listFolderEntries === 'function') {
var entries = JSON.parse(window.WebToApk.listFolderEntries('notes'));
console.log(entries.length + ' entries');
}Encontre o arquivo de backup mais recente e mostre quando foi feito:
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';Observações: Uma pasta vazia, uma pasta inexistente, um caminho que é um arquivo, um caminho com .., nenhuma pasta concedida e uma página de um site externo respondem todos [] — chame hasFolderAccess() antes se precisar distinguir "sem pasta". Os caminhos são relativos, usam / e nunca sobem acima da pasta concedida.
mkdir ponte#
window.WebToApk.mkdir(relativePath: String): String
Acesso a pastas compatível com o Play usando ACTION_OPEN_DOCUMENT_TREE com permissões de URI persistentes. Uso em JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." ou "" window.WebToApk.clearFolderAccess() // libera o acesso persistido Callback do Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4º argumento, "" em caso de sucesso) diz por que um pedido falhou: "disabled", "foreign_origin", "busy" (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), "cancelled", "not_persisted", "no_picker".
Descrito pelo seu grupo, Bridge de acesso nativo a pastas (SAF), e não separadamente.
Exemplo
Cria uma pasta dentro da pasta concedida, incluindo as pastas pai que faltarem.
Retorna: a STRING "true" ou "false", de forma síncrona. WebToApkFS.mkdir(path) retorna um booleano de verdade. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso) e uma pasta concedida.
Com o 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');
}A chamada bruta:
if (window.WebToApk && typeof window.WebToApk.mkdir === 'function') {
var made = window.WebToApk.mkdir('backups') === 'true';
}Crie uma pasta com data para uma exportação e grave cada item nela:
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;
}Observações: Responde "true" quando a pasta já existe, então é seguro chamar antes de cada salvamento. Responde "false" quando um ARQUIVO com esse nome está no caminho, o caminho contém .. ou nenhuma pasta foi concedida.
readTextFile ponte#
window.WebToApk.readTextFile(relativePath: String): String
Acesso a pastas compatível com o Play usando ACTION_OPEN_DOCUMENT_TREE com permissões de URI persistentes. Uso em JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." ou "" window.WebToApk.clearFolderAccess() // libera o acesso persistido Callback do Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4º argumento, "" em caso de sucesso) diz por que um pedido falhou: "disabled", "foreign_origin", "busy" (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), "cancelled", "not_persisted", "no_picker".
Descrito pelo seu grupo, Bridge de acesso nativo a pastas (SAF), e não separadamente.
Exemplo
Lê um arquivo de texto da pasta concedida como UTF-8.
Retorna: uma string, de forma síncrona: o texto do arquivo, ou "". WebToApkFS.readText(path) retorna o mesmo. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso) e uma pasta concedida.
"" significa "arquivo vazio" OU "arquivo inexistente", então verifique com WebToApkFS.stat() quando a diferença importar:
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;A chamada bruta:
if (window.WebToApk && typeof window.WebToApk.readTextFile === 'function') {
var raw = window.WebToApk.readTextFile('notes/today.txt');
console.log(raw.length + ' characters');
}Importe todas as notas .txt e .md de uma pasta:
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'));Observações: O arquivo inteiro é lido na memória — bom para notas e JSON, não para arquivos grandes; use WebToApkFS.readBytes para esses. Bytes que não são UTF-8 válido voltam como o caractere de substituição. Um caminho de pasta, um caminho com .., nenhuma pasta concedida ou uma página de um site externo respondem todos "".
renameEntry ponte#
window.WebToApk.renameEntry(relativePath: String, newName: String): String
Acesso a pastas compatível com o Play usando ACTION_OPEN_DOCUMENT_TREE com permissões de URI persistentes. Uso em JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." ou "" window.WebToApk.clearFolderAccess() // libera o acesso persistido Callback do Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4º argumento, "" em caso de sucesso) diz por que um pedido falhou: "disabled", "foreign_origin", "busy" (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), "cancelled", "not_persisted", "no_picker".
Descrito pelo seu grupo, Bridge de acesso nativo a pastas (SAF), e não separadamente.
Exemplo
Renomeia um arquivo ou pasta dentro da pasta concedida.
Retorna: a STRING "true" ou "false", de forma síncrona. WebToApkFS.rename(path, newName) retorna um booleano de verdade. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso) e uma pasta concedida.
newName é um nome simples, não um caminho — a entrada permanece na mesma pasta:
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?');
}A chamada bruta:
if (window.WebToApk && typeof window.WebToApk.renameEntry === 'function') {
var renamed = window.WebToApk.renameEntry('old-folder', 'new-folder') === 'true';
}Mantenha o backup anterior renomeando-o antes de gravar o novo:
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);
}Observações: Falha quando outra entrada já tem newName, quando newName está vazio ou contém / ou \, quando o caminho não existe ou quando nenhuma pasta foi concedida. Para mover um arquivo para outra pasta, grave-o lá e exclua o antigo.
requestFolderAccess ponte#
window.WebToApk.requestFolderAccess(requestId: String)
Abre o seletor de pastas do sistema e mantém o acesso de leitura/gravação à pasta escolhida. A resposta chega em `WebToApkOnFolderAccessResult`; só um seletor pode ficar aberto por vez, então um pedido feito enquanto um está aberto é respondido na hora com `error:"busy"` e o aberto mantém sua própria resposta.
Exemplo
Abre o seletor de pastas do sistema para o usuário dar uma pasta ao app. As páginas normalmente usam WebToApkFS.requestAccess(), que envolve esta chamada em uma Promise.
Retorna: nada. A resposta volta depois por um callback interno que WebToApkFS controla; WebToApkFS.requestAccess() é resolvida com { success, folderUri, requestId }, mais error quando success é false. Requer: marque Acesso nativo a pastas (SAF) na Etapa 4 (Acesso) ao compilar.
Peça a partir do toque em um botão, com o 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
}
});Peça só na primeira vez e depois monte a subpasta do próprio 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());
});Diga ao usuário exatamente por que nenhuma pasta foi conectada, usando os códigos reais de 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.');
});Observações: Não chame o método bruto você mesmo: sua única resposta é o callback que o helper escuta. success é false quando o usuário cancela (error:'cancelled'), quando o Android não mantém a concessão ('not_persisted') e na hora quando a compilação não ativou a chave ('disabled') ou a página na tela não é do próprio app ('foreign_origin'). A concessão é mantida entre reinicializações do app; escolher de novo substitui a pasta. Só um seletor pode ficar aberto por vez — um pedido feito enquanto um está aberto é respondido na hora com error:'busy', e o aberto mantém sua própria resposta.
writeTextFile ponte#
window.WebToApk.writeTextFile(relativePath: String, content: String): String
Acesso a pastas compatível com o Play usando ACTION_OPEN_DOCUMENT_TREE com permissões de URI persistentes. Uso em JavaScript: window.WebToApk.requestFolderAccess('myRequestId') window.WebToApk.hasFolderAccess() // "true" / "false" window.WebToApk.getFolderAccessUri() // "content://..." ou "" window.WebToApk.clearFolderAccess() // libera o acesso persistido Callback do Android → JS: window.WebToApkOnFolderAccessResult(success, folderUri, requestId, error) `error` (4º argumento, "" em caso de sucesso) diz por que um pedido falhou: "disabled", "foreign_origin", "busy" (já há um seletor aberto para um pedido anterior — esse ainda recebe sua própria resposta), "cancelled", "not_persisted", "no_picker".
Descrito pelo seu grupo, Bridge de acesso nativo a pastas (SAF), e não separadamente.
Exemplo
Grava um arquivo de texto (UTF-8) na pasta concedida, criando-o ou substituindo seu conteúdo.
Retorna: a STRING "true" ou "false", de forma síncrona. WebToApkFS.writeText(path, text) retorna um booleano de verdade. Requer: Acesso nativo a pastas (SAF) marcado na Etapa 4 (Acesso) e uma pasta concedida.
Com o 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);A chamada bruta, cuja resposta é texto:
if (window.WebToApk && typeof window.WebToApk.writeTextFile === 'function') {
var saved = window.WebToApk.writeTextFile('data/settings.json', JSON.stringify({ theme: 'dark' })) === 'true';
}Acrescente uma linha a um registro diário — gravar substitui o arquivo, então leia-o primeiro:
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');Observações: Um arquivo existente é substituído por completo. Falha quando o caminho é uma pasta existente, contém .. ou nenhuma pasta foi concedida. O tipo do arquivo vem da extensão (.json, .txt, .md…), então o nome que você dá é o nome no disco.