API del bridge JavaScript
Biometria e keystore
Sblocco con impronta e volto, e chiavi protette dall'hardware con cui la pagina può cifrare ma che non può mai leggere.
AppMintKeystore.decrypt helper#
window.AppMintKeystore.decrypt(alias, base64)
Esempio
Decifra i dati prodotti da AppMintKeystore.encrypt con la stessa chiave.
Restituisce: un oggetto, subito (non una Promise): {ok:true, base64} (i dati in chiaro) oppure {ok:false, error, detail}. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app.
function fromBase64(b64) { return decodeURIComponent(escape(atob(b64))); }
function readSecret() {
var sealed = localStorage.getItem('secret');
if (!sealed || !window.AppMintKeystore) return null;
var r = window.AppMintKeystore.decrypt('vault', sealed);
if (r.ok) return fromBase64(r.base64);
switch (r.error) {
case 'auth-required': unlockThen(readSecretAndShow); break; // unlock, then try again
case 'no-such-key':
case 'key-invalidated': alert('This secret can no longer be opened.'); break;
default: alert('Could not decrypt: ' + r.error);
}
return null;
}Per un file scritto con WebToApkFS.writeSealed, rileggilo con WebToApkFS.readSealed(path, alias): stessa forma del risultato.
Rileggi un oggetto JSON sigillato, sbloccando prima quando la chiave lo richiede:
function unlock() {
return new Promise(function (resolve) {
var id = 'unlock_' + Date.now();
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) { resolve(JSON.parse(json)); };
window.WebToApk.authenticateBiometricEx(id, JSON.stringify({
title: 'Unlock your settings', strong: true, allowDeviceCredential: true
}));
});
}
async function loadSealedSettings() {
var sealed = localStorage.getItem('settings');
if (!sealed || !window.AppMintKeystore || !window.AppMintKeystore.isAvailable()) return null;
var r = window.AppMintKeystore.decrypt('vault', sealed);
if (!r.ok && r.error === 'auth-required' && (await unlock()).ok) {
r = window.AppMintKeystore.decrypt('vault', sealed);
}
return r.ok ? JSON.parse(decodeURIComponent(escape(atob(r.base64)))) : null;
}Apri un elenco di voci di diario sigillate, mantenendo visibili come "bloccate" quelle che non si aprono:
function loadDiary() {
if (!window.AppMintKeystore || !window.AppMintKeystore.isAvailable()) return [];
var entries = JSON.parse(localStorage.getItem('diary') || '[]'); // [{date, sealed}]
return entries.map(function (e) {
var r = window.AppMintKeystore.decrypt('diary', e.sealed);
return {
date: e.date,
text: r.ok ? decodeURIComponent(escape(atob(r.base64))) : null,
error: r.ok ? null : r.error // e.g. 'auth-required', 'no-such-key'
};
});
}Note: Dopo deleteKey i dati sono persi per sempre; Ăš proprio questo lo scopo. Un contenuto modificato non supera il controllo GCM. Altri errori: bad base64, disabled, foreign_origin, unsupported.
AppMintKeystore.deleteKey helper#
window.AppMintKeystore.deleteKey(alias)
Esempio
Distrugge definitivamente una chiave sicura. Tutto ciĂČ che Ăš stato cifrato con essa non potrĂ mai piĂč essere letto, da nessuno.
Restituisce: true (eliminata) oppure false (nessuna chiave di quel nome, o funzione disattivata), subito. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app.
Sovrascrivere un file non lo cancella dalla memoria flash. Cifralo, poi distruggi la chiave: quella è una vera cancellazione:
function deleteNoteForReal() {
if (!window.AppMintKeystore || !window.WebToApkFS) return;
if (!confirm('Delete this note forever?')) return;
var gone = window.AppMintKeystore.deleteKey('vault'); // secret.bin is unreadable from now on
window.WebToApkFS.delete('secret.bin'); // tidy up the file too
alert(gone ? 'Deleted.' : 'Nothing to delete.');
}"Cancella tutto" nelle impostazioni: distruggi ogni chiave creata da questa app, poi svuota l'archiviazione della pagina:
function eraseEverything() {
var K = window.AppMintKeystore;
if (!K || !K.isAvailable()) return;
if (!confirm('Erase all protected data on this phone? This cannot be undone.')) return;
var aliases = K.listKeys();
aliases.forEach(function (alias) { K.deleteKey(alias); });
localStorage.clear();
alert('Erased ' + aliases.length + ' key(s).');
location.reload();
}Autodistruggi la cassaforte dopo troppi PIN dell'app sbagliati:
var MAX_TRIES = 5;
function onWrongPin() {
var tries = Number(localStorage.getItem('pinTries') || 0) + 1;
localStorage.setItem('pinTries', String(tries));
if (tries < MAX_TRIES) return alert((MAX_TRIES - tries) + ' tries left.');
if (window.AppMintKeystore) window.AppMintKeystore.deleteKey('vault'); // vault unreadable from now on
localStorage.removeItem('vaultData');
localStorage.removeItem('pinTries');
alert('Too many wrong PINs. The vault was erased.');
}Note: non c'è annullamento né recupero. Se isHardwareBacked(alias) era false, la chiave stava nel software: diglielo prima di promettere all'utente un'eliminazione irreversibile.
AppMintKeystore.encrypt helper#
window.AppMintKeystore.encrypt(alias, base64)
Esempio
Cifra dati base64 con una chiave sicura. Conserva il risultato dove vuoi: senza la chiave è inutilizzabile.
Restituisce: un oggetto, subito (non una Promise): {ok:true, base64} (IV + testo cifrato + tag) oppure {ok:false, error, detail}. Richiede: Chiavi sicure (cifratura) attivate durante la creazione e una chiave creata da AppMintKeystore.generateKey.
function toBase64(text) { return btoa(unescape(encodeURIComponent(text))); }
function saveSecret(text) {
if (!window.AppMintKeystore || !window.AppMintKeystore.isAvailable()) return false;
var r = window.AppMintKeystore.encrypt('vault', toBase64(text));
if (r.ok) {
localStorage.setItem('secret', r.base64);
return true;
}
if (r.error === 'auth-required') {
// the key needs a fresh unlock: ask for the fingerprint, then call saveSecret again
unlockThen(function () { saveSecret(text); });
} else {
alert('Could not encrypt: ' + r.error);
}
return false;
}unlockThen puĂČ essere creato su authenticateBiometricEx (vedi quell'esempio).
Per scrivere un FILE cifrato di qualsiasi dimensione, usa WebToApkFS.writeSealed(path, base64, alias): cifra a blocchi con questa stessa chiave.
Sigilla un piccolo oggetto JSON (una nota, un token, le impostazioni) e conservalo in localStorage:
function sealJson(alias, obj) {
if (!window.AppMintKeystore || !window.AppMintKeystore.isAvailable()) return null;
var b64 = btoa(unescape(encodeURIComponent(JSON.stringify(obj))));
var r = window.AppMintKeystore.encrypt(alias, b64);
if (!r.ok) { console.warn('encrypt failed:', r.error, r.detail); return null; }
return r.base64;
}
var sealed = sealJson('notes', { title: 'Shopping', items: ['milk', 'eggs'] });
if (sealed) localStorage.setItem('note-1', sealed);Crea la chiave la prima volta che serve, poi cifra:
function encryptWithKeyReady(alias, b64) {
var ks = window.AppMintKeystore;
if (!ks || !ks.isAvailable()) return { ok: false, error: 'unsupported' };
var r = ks.encrypt(alias, b64);
if (!r.ok && r.error === 'no-such-key') {
var g = ks.generateKey(alias); // plain key, no unlock needed
if (!g.ok) return g;
r = ks.encrypt(alias, b64);
}
return r; // {ok:true, base64} or {ok:false, error, detail}
}Note: massimo 4 MB per chiamata (chunk too large). Errori: no-such-key, auth-required, key-invalidated (il blocco schermo è cambiato: la chiave non esiste più), bad base64, disabled, foreign_origin, unsupported.
AppMintKeystore.generateKey helper#
window.AppMintKeystore.generateKey(alias, options)
Esempio
Crea (o sostituisce) una chiave AES-256-GCM nel Keystore di Android. La chiave non lascia mai l'hardware sicuro; la tua pagina usa solo il suo nome.
Restituisce: un oggetto, subito (non una Promise): {ok:true, alias, hardwareBacked} oppure {ok:false, error, detail}. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app (vedi AppMintKeystore.isAvailable).
function createVault() {
if (!window.AppMintKeystore || !window.AppMintKeystore.isAvailable()) {
alert('Secure keys are not available in this app.');
return;
}
var r = window.AppMintKeystore.generateKey('vault', {
requireAuth: true, // usable only after a fingerprint / PIN unlock...
authValiditySeconds: 30 // ...for 30 seconds after it
});
if (!r.ok) { alert('Could not create the key: ' + r.error); return; }
console.log('Hardware key:', r.hardwareBacked);
}Una chiave semplice, senza bisogno di sblocco:
var r = window.AppMintKeystore.generateKey('notes'); // options default to {}Richiedi il chip sicuro separato (StrongBox). Se non c'Ăš, la chiamata fallisce invece di darti una chiave piĂč debole:
var r = window.AppMintKeystore.generateKey('wallet', { strongBox: true });
if (!r.ok && r.error === 'strongbox-unavailable') {
alert('This phone has no secure chip.');
}Note: Alias: lettere, cifre, _ - ., fino a 64 caratteri (altrimenti error:"bad alias"). Usare un alias esistente sostituisce la chiave, e i dati sigillati con la vecchia chiave vanno persi. Altri errori: disabled, bad options json, foreign_origin, unsupported (nessun bridge).
AppMintKeystore.hasKey helper#
window.AppMintKeystore.hasKey(alias)
Esempio
Ti dice se esiste una chiave sicura con questo alias.
Restituisce: true / false, subito. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app.
function startVault() {
if (!window.AppMintKeystore || !window.AppMintKeystore.isAvailable()) return;
if (window.AppMintKeystore.hasKey('vault')) {
showUnlockScreen();
} else {
showCreateVaultScreen();
}
}
window.addEventListener('load', startVault);Assicurati che una chiave esista prima del primo salvataggio, creandola una sola volta:
function ensureKey(alias) {
var ks = window.AppMintKeystore;
if (!ks || !ks.isAvailable()) return false;
if (ks.hasKey(alias)) return true;
var r = ks.generateKey(alias); // generateKey REPLACES an existing key, so only call it when hasKey says no
if (!r.ok) console.warn('Could not create key:', r.error);
return r.ok;
}
if (ensureKey('notes')) enableSaveButton();Ripulisci i dati sigillati la cui chiave non esiste più (eliminata, o i dati dell'app sono stati ripristinati senza di essa):
function checkVault() {
var ks = window.AppMintKeystore;
if (!ks || !ks.isAvailable()) return;
var sealed = localStorage.getItem('secret');
if (sealed && !ks.hasKey('vault')) {
localStorage.removeItem('secret'); // without the key it can never be opened
showMessage('Your saved secret could not be recovered and was removed.');
}
}
window.addEventListener('load', checkVault);Note: Risponde false quando le Chiavi sicure sono disattivate, quindi controlla prima isAvailable() se devi distinguere "ancora nessuna chiave" da "funzione non presente in questa app".
AppMintKeystore.isAvailable helper#
window.AppMintKeystore.isAvailable()
Esempio
Ti dice se in questa app Ăš possibile usare chiavi sicure supportate dall'hardware.
Restituisce: true / false, subito (non una Promise). Richiede: Chiavi sicure (cifratura) attivate durante la creazione. L'oggetto window.AppMintKeystore viene installato (prima dell'esecuzione del tuo primo script) ogni volta che Chiavi sicure o Accesso nativo alle cartelle (SAF) Ăš attivo; isAvailable() risponde per le Chiavi sicure.
function secureKeysReady() {
return !!window.AppMintKeystore && window.AppMintKeystore.isAvailable();
}
// Check when the user acts (the helper is added when the page finishes loading):
document.getElementById('vault-button').addEventListener('click', function () {
if (!secureKeysReady()) {
alert('The secure vault is not available in this app.');
return;
}
openVault();
});Nascondi ogni controllo del vault sicuro quando la funzione non Ăš presente in questa app:
window.addEventListener('load', function () {
var ok = !!window.AppMintKeystore && window.AppMintKeystore.isAvailable();
document.querySelectorAll('.needs-secure-keys').forEach(function (el) {
el.hidden = !ok;
});
});Distingui "funzione disattivata" da "ancora nessuna chiave" quando decidi quale schermata mostrare:
function vaultState() {
var ks = window.AppMintKeystore;
if (!ks || !ks.isAvailable()) return 'off'; // a browser, or Secure Keys not on in this build
return ks.hasKey('vault') ? 'ready' : 'new';
}
document.getElementById('vault-button').addEventListener('click', function () {
var state = vaultState();
if (state === 'off') alert('The secure vault is not available in this app.');
else if (state === 'new') showCreateVaultScreen();
else showUnlockScreen();
});Note: L'helper viene aggiunto dopo il caricamento della pagina, quindi non verificarne la presenza in uno script eseguito mentre la pagina Ăš ancora in analisi: verificalo in un gestore di clic o dopo load. In un browser non esiste window.AppMintKeystore; il controllo qui sopra restituisce false senza generare errori.
AppMintKeystore.isHardwareBacked helper#
window.AppMintKeystore.isHardwareBacked(alias)
Esempio
Ti dice se una chiave si trova nell'hardware sicuro del telefono (TEE o StrongBox) invece che nel software.
Restituisce: true / false, subito. false quando la chiave non esiste. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app.
function showKeyStrength() {
if (!window.AppMintKeystore || !window.AppMintKeystore.hasKey('vault')) return;
var hw = window.AppMintKeystore.isHardwareBacked('vault');
document.getElementById('strength').textContent = hw
? 'Protected by secure hardware'
: 'Protected by software (this phone has no secure hardware for keys)';
}Conserva un segreto di grande valore (un seed di portafoglio, un codice di recupero) solo quando la sua chiave Ăš nell'hardware sicuro:
function walletKeyIsHardware() {
var ks = window.AppMintKeystore;
if (!ks || !ks.isAvailable()) return false;
if (!ks.hasKey('wallet')) {
var r = ks.generateKey('wallet', { requireAuth: true });
if (!r.ok) return false;
}
return ks.isHardwareBacked('wallet');
}
if (!walletKeyIsHardware()) {
alert('This phone cannot keep the wallet key in secure hardware.');
}Elenca dove si trova ogni chiave, per una segnalazione al supporto:
function keyReport() {
var ks = window.AppMintKeystore;
if (!ks || !ks.isAvailable()) return { secureKeys: false };
return {
secureKeys: true,
keys: ks.listKeys().map(function (alias) {
return { alias: alias, hardware: ks.isHardwareBacked(alias) };
})
};
}
console.log(JSON.stringify(keyReport()));Note: generateKey restituisce anche hardwareBacked subito dopo aver creato la chiave.
AppMintKeystore.listKeys helper#
window.AppMintKeystore.listKeys()
Esempio
Elenca gli alias di ogni chiave sicura creata dalle tue pagine.
Restituisce: un array di stringhe, ordinato, subito, ad es. ['notes', 'vault']. [] quando non ce ne sono o la funzione è disattivata. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app.
function renderKeyList() {
var list = document.getElementById('key-list');
list.innerHTML = '';
if (!window.AppMintKeystore) return;
window.AppMintKeystore.listKeys().forEach(function (alias) {
var li = document.createElement('li');
li.textContent = alias + (window.AppMintKeystore.isHardwareBacked(alias) ? ' (hardware)' : '');
list.appendChild(li);
});
}Distruggi ogni chiave con "Cancella tutti i miei dati", rendendo illeggibile tutto ciò che era sigillato con esse:
function eraseAllKeys() {
var ks = window.AppMintKeystore;
if (!ks || !ks.isAvailable()) return 0;
var removed = 0;
ks.listKeys().forEach(function (alias) {
if (ks.deleteKey(alias)) removed++;
});
localStorage.clear(); // the sealed copies can no longer be opened anyway
return removed;
}Tieni una chiave per ogni utente connesso e rimuovi le chiavi degli utenti che si sono disconnessi:
function pruneUserKeys(currentUserId) {
var ks = window.AppMintKeystore;
if (!ks || !ks.isAvailable()) return;
var keep = 'user-' + currentUserId; // aliases: letters, digits, _ - . only
ks.listKeys().forEach(function (alias) {
if (alias.indexOf('user-') === 0 && alias !== keep) ks.deleteKey(alias);
});
}Note: vengono elencate solo le chiavi create tramite il bridge; le chiavi interne dell'app non vengono mai mostrate.
authenticateBiometric bridge#
window.WebToApk.authenticateBiometric(callbackId: String, title: String, subtitle: String)
WebToApk.isBiometricAvailable() -> "available" | "no_hardware" | "not_enrolled" | "unavailable" WebToApk.authenticateBiometric(id, title, subtitle) Il risultato viene consegnato alla stessa tabella di callback __webToApkAuth usata dal bridge di Google Sign-In: window.__webToApkAuth = window.__webToApkAuth || {}; window.__webToApkAuth['unlock'] = function (json) { var r = JSON.parse(json); // {ok:true} | {ok:false,error:"..."} if (r.ok) showApp(); }; WebToApk.authenticateBiometric('unlock', 'Unlock', 'Use your fingerprint');
Descritto dal suo gruppo, Bridge impronta digitale / biometria, e non da solo.
Esempio
Mostra la richiesta di impronta del sistema con titolo e sottotitolo. La forma semplice di authenticateBiometricEx (usa quello per l'opzione PIN, per un'etichetta Annulla personalizzata o per la biometria "forte").
Restituisce: niente. La risposta arriva dopo: la shell chiama window.__webToApkAuth[callbackId](json) con '{"ok":true}' oppure '{"ok":false,"error":"<code>"}', poi rimuove quella voce. Richiede: Impronta digitale selezionata quando crei l'app (senza: error:"permission_missing").
Registra PRIMA la callback, poi chiama:
function unlockWithFingerprint() {
return new Promise(function (resolve) {
if (!window.WebToApk || typeof window.WebToApk.authenticateBiometric !== 'function') {
resolve({ ok: false, error: 'not_in_app' });
return;
}
var id = 'bio_' + Date.now();
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) { resolve(JSON.parse(json)); };
window.WebToApk.authenticateBiometric(id, 'Unlock', 'Touch the fingerprint sensor');
});
}
document.getElementById('unlock').addEventListener('click', async function () {
var r = await unlockWithFingerprint();
if (r.ok) showMyApp();
else if (r.error === 'user_cancel') { /* the user closed it — do nothing */ }
else if (r.error === 'none_enrolled') alert('Please add a fingerprint in phone settings.');
else if (r.error === 'lockout') alert('Too many tries. Wait a little and try again.');
else alert('Could not unlock: ' + r.error);
});Conferma un'azione rischiosa prima con l'impronta (un titolo vuoto mostra il nome dell'app):
function confirmItsYou(subtitle) {
return new Promise(function (resolve) {
if (!window.WebToApk || typeof window.WebToApk.authenticateBiometric !== 'function') {
resolve({ ok: false, error: 'not_in_app' });
return;
}
var id = 'confirm_' + Date.now();
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) { resolve(JSON.parse(json)); };
window.WebToApk.authenticateBiometric(id, '', subtitle);
});
}
document.getElementById('delete-account').addEventListener('click', async function () {
var r = await confirmItsYou('Confirm to delete your account');
if (r.ok) deleteMyAccount();
else if (r.error !== 'user_cancel') alert('Not confirmed: ' + r.error);
});Blocca di nuovo l'app quando torna dopo più di un minuto in background:
var hiddenAt = 0;
document.addEventListener('visibilitychange', async function () {
if (document.visibilityState === 'hidden') {
hiddenAt = Date.now();
return;
}
if (!hiddenAt || Date.now() - hiddenAt < 60000) return;
hiddenAt = 0;
hideMyApp();
var r = await unlockWithFingerprint(); // from the first example
if (r.ok) showMyApp();
else showLockedScreen(); // with its own "Unlock" button
});Note: Un titolo vuoto usa il nome dell'app. Un dito sbagliato NON chiude la richiesta: la callback viene chiamata solo in caso di successo, annullamento o errore vero. I codici di errore sono gli stessi di authenticateBiometricEx. Questo dimostra che il proprietario del telefono Ăš presente; non dimostra a un server chi sia.
authenticateBiometricEx bridge#
window.WebToApk.authenticateBiometricEx(callbackId: String, optionsJson: String)
La forma completa. [optionsJson] accetta: title, subtitle, description, negativeButtonText, allowDeviceCredential (alternativa con PIN/sequenza/password), strong (richiede BIOMETRIC_STRONG: da usare per tutto ciĂČ che riguarda pagamenti). Gli errori sono codici distinti, non un unico booleano. Prima di qualsiasi richiesta: no_hardware | none_enrolled | unavailable (il controllo dello stato non ha potuto essere eseguito - vedi isBiometricAvailable). Dalla richiesta di sistema di Android 9+: user_cancel | no_hardware | hw_unavailable | none_enrolled | lockout | lockout_permanent | no_device_credential | timeout | no_space | unable_to_process | vendor_error, e per un codice che Android aggiungerĂ in futuro, il suo testo del messaggio (o `error_<code>`). permission_missing quando la build non ha l'autorizzazione Fingerprint. Su Android 7-8 (API 24-27, solo impronta digitale) un errore Ăš il testo del messaggio del telefono cosĂŹ come l'ha scritto il produttore, non un codice fisso.
Esempio
Mostra la richiesta di impronta / volto del sistema con tutte le opzioni: PIN o sequenza come seconda via di accesso, un'etichetta Annulla personalizzata e solo biometria "forte".
Restituisce: niente. La risposta arriva dopo: la shell chiama window.__webToApkAuth[callbackId](json) con '{"ok":true}' oppure '{"ok":false,"error":"<code>"}', poi rimuove quella voce. Richiede: Impronta digitale selezionata quando crei l'app (senza: error:"permission_missing").
Un piccolo wrapper basato su Promise che puoi riutilizzare:
function verifyUser(options) {
return new Promise(function (resolve) {
if (!window.WebToApk || typeof window.WebToApk.authenticateBiometricEx !== 'function') {
resolve({ ok: false, error: 'not_in_app' });
return;
}
var id = 'bio_' + Date.now() + '_' + Math.random().toString(36).slice(2);
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) { resolve(JSON.parse(json)); };
window.WebToApk.authenticateBiometricEx(id, JSON.stringify(options));
});
}Sblocca l'app: impronta, volto, oppure PIN/sequenza del telefono:
async function unlockApp() {
var r = await verifyUser({
title: 'Unlock',
subtitle: 'Use your fingerprint or PIN',
allowDeviceCredential: true // Android 11+: the system adds "Use PIN"
});
if (r.ok) showMyApp();
else if (r.error === 'lockout') alert('Too many tries. Wait a little and try again.');
}Conferma un pagamento: solo biometria forte, con la tua etichetta Annulla:
async function confirmPayment() {
var r = await verifyUser({
title: 'Confirm payment',
description: 'Pay 12.00 to Coffee House',
negativeButtonText: 'Not now',
strong: true // BIOMETRIC_STRONG (Android 11+)
});
if (r.ok) sendPayment();
else if (r.error !== 'user_cancel') alert('Could not confirm: ' + r.error);
}Sblocca una chiave del Keystore creata con requireAuth, poi riprova:
async function openVault() {
var r = await verifyUser({ title: 'Open vault', allowDeviceCredential: true });
if (r.ok) readSecret(); // AppMintKeystore.decrypt works for authValiditySeconds now
}Note: codici di errore: user_cancel, none_enrolled, no_hardware, hw_unavailable, lockout (attendi e riprova), lockout_permanent (l'utente deve prima sbloccare il telefono con il PIN), no_device_credential, timeout, permission_missing, unavailable, e raramente no_space, unable_to_process, vendor_error. Su Android 7-8 la shell mostra una propria finestra e un errore è il testo del messaggio di sistema. allowDeviceCredential e negativeButtonText non vanno insieme: con l'opzione PIN il sistema disegna un proprio pulsante. Un dito sbagliato non termina la richiesta.
decryptWithKey bridge#
window.WebToApk.decryptWithKey(alias: String, base64Payload: String): String
Inverte [encryptWithKey]. Fallisce con `error:"no-such-key"` una volta eliminata la chiave: il contenuto Ăš allora irrecuperabile per progetto. @return JSON `{ok, base64, error}`.
Esempio
Decifra i dati prodotti da encryptWithKey con la stessa chiave del Keystore. La maggior parte delle pagine usa AppMintKeystore.decrypt(alias, base64), che restituisce l'oggetto già interpretato.
Restituisce: una stringa JSON, subito: {"ok":true,"base64":"<plain data>"} oppure {"ok":false,"error":"...","detail":"..."}. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app.
function fromBase64(b64) { return decodeURIComponent(escape(atob(b64))); }
function openNote() {
if (!window.WebToApk || typeof window.WebToApk.decryptWithKey !== 'function') return null;
var sealed = localStorage.getItem('note');
if (!sealed) return null;
var r = JSON.parse(window.WebToApk.decryptWithKey('vault', sealed));
if (r.ok) return fromBase64(r.base64);
if (r.error === 'auth-required') {
askUserToUnlockThenRetry(); // unlock with authenticateBiometricEx, then call again
} else if (r.error === 'no-such-key' || r.error === 'key-invalidated') {
alert('This note can no longer be opened.'); // the key was deleted or the screen lock changed
} else {
alert('Could not open the note: ' + r.error);
}
return null;
}Per una chiave creata con requireAuth:true: su auth-required, mostra la richiesta di sblocco, poi decifra di nuovo:
function unlock() {
return new Promise(function (resolve) {
var id = 'unlock_' + Date.now();
window.__webToApkAuth = window.__webToApkAuth || {};
window.__webToApkAuth[id] = function (json) { resolve(JSON.parse(json)); };
window.WebToApk.authenticateBiometricEx(id, JSON.stringify({
title: 'Unlock your notes', strong: true, allowDeviceCredential: true
}));
});
}
async function openNoteWithUnlock() {
if (!window.WebToApk || typeof window.WebToApk.decryptWithKey !== 'function') return null;
var sealed = localStorage.getItem('note');
if (!sealed) return null;
var r = JSON.parse(window.WebToApk.decryptWithKey('vault', sealed));
if (!r.ok && r.error === 'auth-required') {
var auth = await unlock();
if (!auth.ok) return null; // cancelled or failed
r = JSON.parse(window.WebToApk.decryptWithKey('vault', sealed));
}
return r.ok ? decodeURIComponent(escape(atob(r.base64))) : null;
}Mostra una foto cifrata: decifra in byte, poi visualizzala tramite un URL blob:
function showSealedPhoto(sealedB64, img) {
if (!window.WebToApk || typeof window.WebToApk.decryptWithKey !== 'function') return;
var r = JSON.parse(window.WebToApk.decryptWithKey('vault', sealedB64));
if (!r.ok) {
alert('Could not open the photo: ' + r.error);
return;
}
var bin = atob(r.base64);
var bytes = new Uint8Array(bin.length);
for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
var url = URL.createObjectURL(new Blob([bytes], { type: 'image/jpeg' }));
img.onload = function () { URL.revokeObjectURL(url); };
img.src = url;
}Note: Una volta eliminata la chiave (deleteKey) i dati non potranno mai piĂč essere decifrati, per progetto. Un contenuto modificato o danneggiato fallisce (il tag GCM non corrisponde); l'error Ăš allora il messaggio della piattaforma, non uno dei codici sopra. Altri codici: bad base64, disabled, foreign_origin.
deleteKey bridge#
window.WebToApk.deleteKey(alias: String): String
Distrugge [alias] definitivamente. Tutto ciĂČ che Ăš stato cifrato con esso diventa impossibile da decifrare, anche da parte di questa app e di chiunque possieda il dispositivo.
Esempio
Distrugge per sempre una chiave del Keystore. Tutto ciò che è stato cifrato con essa non potrà più essere letto, né da te, né dall'utente, né da chiunque abbia il telefono. La maggior parte delle pagine usa AppMintKeystore.deleteKey(alias), che restituisce un vero booleano.
Restituisce: la STRINGA "true" (eliminata) o "false" (chiave inesistente, Chiavi sicure disattivate o pagina non dell'app), subito. Richiede: Chiavi sicure (cifratura) attivate durante la creazione.
Questa Ăš l'unica vera "cancellazione sicura" su un telefono: sovrascrivere un file non lo cancella sulla memoria flash, distruggere la chiave sĂŹ.
function destroyVault() {
if (!window.WebToApk || typeof window.WebToApk.deleteKey !== 'function') return;
if (!confirm('Delete the vault? This cannot be undone.')) return;
var gone = window.WebToApk.deleteKey('vault') === 'true';
localStorage.removeItem('note'); // the sealed data is now useless anyway
alert(gone ? 'Vault deleted.' : 'There was no vault to delete.');
}Ruota una chiave: apri i dati con la vecchia chiave, sigillali con una nuova, poi distruggi la vecchia chiave:
function rotateVault() {
var W = window.WebToApk;
if (!W || typeof W.deleteKey !== 'function') return false;
var sealed = localStorage.getItem('note');
if (!sealed) return false;
var clear = JSON.parse(W.decryptWithKey('vault', sealed));
if (!clear.ok) return false;
var made = JSON.parse(W.generateKey('vault2', '{}'));
if (!made.ok) return false;
var fresh = JSON.parse(W.encryptWithKey('vault2', clear.base64));
if (!fresh.ok) return false;
localStorage.setItem('note', fresh.base64);
localStorage.setItem('noteKey', 'vault2');
return W.deleteKey('vault') === 'true'; // the old copies are dead from here on
}"Dimenticami su questo telefono" alla disconnessione: distruggi la chiave dell'account, poi elimina i suoi dati in cache:
function signOutAndForget(userId) {
var alias = 'user_' + userId;
if (window.WebToApk && typeof window.WebToApk.deleteKey === 'function') {
var gone = window.WebToApk.deleteKey(alias) === 'true';
console.log(gone ? 'Account key destroyed' : 'No key for this account');
}
localStorage.removeItem('messages_' + userId); // sealed with that key, unreadable now
signOut();
}Note: Controlla isKeyHardwareBacked prima di promettere all'utente che l'eliminazione Ăš irreversibile: una chiave solo software Ăš piĂč debole.
encryptWithKey bridge#
window.WebToApk.encryptWithKey(alias: String, base64Plaintext: String): String
Cifra il base64 [base64Plaintext] con [alias]. Ogni chiamata genera un proprio IV, quindi un file grande viene cifrato a blocchi: i blocchi sono indipendenti e possono essere decifrati in qualsiasi ordine. @return JSON `{ok, base64, error}` dove `base64` Ăš `IV || ciphertext || tag`.
Esempio
Cifra dati base64 con una chiave del Keystore. La maggior parte delle pagine usa AppMintKeystore.encrypt(alias, base64), che restituisce l'oggetto già interpretato.
Restituisce: una stringa JSON, subito: {"ok":true,"base64":"<IV + ciphertext + tag>"} oppure {"ok":false,"error":"...","detail":"..."}. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app, e una chiave creata con generateKey.
L'input Ăš in base64. Per un testo, convertilo prima in base64 UTF-8:
function toBase64(text) { return btoa(unescape(encodeURIComponent(text))); }
function sealNote(text) {
if (!window.WebToApk || typeof window.WebToApk.encryptWithKey !== 'function') return null;
var r = JSON.parse(window.WebToApk.encryptWithKey('vault', toBase64(text)));
if (r.ok) {
localStorage.setItem('note', r.base64); // safe to store anywhere: useless without the key
return r.base64;
}
if (r.error === 'auth-required') {
// key was made with requireAuth — unlock first (authenticateBiometricEx), then call again
askUserToUnlockThenRetry();
} else {
alert('Could not encrypt: ' + r.error);
}
return null;
}Sigilla un intero oggetto (una sessione, delle impostazioni) come JSON prima di conservarlo:
function sealJson(alias, obj) {
if (!window.WebToApk || typeof window.WebToApk.encryptWithKey !== 'function') return null;
var b64 = btoa(unescape(encodeURIComponent(JSON.stringify(obj))));
var r = JSON.parse(window.WebToApk.encryptWithKey(alias, b64));
if (!r.ok) console.warn('encrypt failed: ' + r.error);
return r.ok ? r.base64 : null;
}
var sealed = sealJson('vault', { token: accessToken, savedAt: Date.now() });
if (sealed) localStorage.setItem('session', sealed);Cifra una foto scelta dall'utente (dati binari, fino a 4 MB per chiamata):
document.getElementById('id-photo').addEventListener('change', async function (e) {
var file = e.target.files[0];
if (!file || !window.WebToApk || typeof window.WebToApk.encryptWithKey !== 'function') return;
var bytes = new Uint8Array(await file.arrayBuffer());
var bin = '';
for (var i = 0; i < bytes.length; i += 0x8000) {
bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
}
var r = JSON.parse(window.WebToApk.encryptWithKey('vault', btoa(bin)));
if (r.ok) {
saveSealedPhoto(file.name, r.base64); // your own storage
} else if (r.error.indexOf('chunk too large') === 0) {
alert('That photo is too big. Pick one under 4 MB.');
} else {
alert('Could not encrypt the photo: ' + r.error);
}
});Note: Ogni chiamata usa un IV nuovo, quindi lo stesso testo dĂ un risultato diverso ogni volta. Al massimo 4 MB per chiamata (error:"chunk too large ..."); per file piĂč grandi cifra a blocchi, oppure usa WebToApkFS.writeSealed. Codici di errore: no-such-key, auth-required, key-invalidated (l'utente ha cambiato il blocco schermo: la chiave Ăš persa per sempre), bad base64, disabled, foreign_origin.
generateKey bridge#
window.WebToApk.generateKey(alias: String, optionsJson: String): String
Crea (o sostituisce) una chiave AES-256-GCM chiamata [alias]. @param optionsJson `{"requireAuth":bool, "authValiditySeconds":int, "strongBox":bool}`. `requireAuth` lega la chiave al blocco del dispositivo; `strongBox` fallisce del tutto sui dispositivi senza secure element invece di restituire una chiave più debole di quella richiesta. @return JSON `{ok, alias, hardwareBacked, error}`.
Esempio
Crea (o sostituisce) una chiave AES-256-GCM nel Keystore di Android. La pagina riceve un nome per la chiave, mai la chiave stessa. La maggior parte delle pagine usa AppMintKeystore.generateKey(alias, options), che si occupa del lavoro con il JSON per te.
Restituisce: una stringa JSON, subito: {"ok":true,"alias":"vault","hardwareBacked":true} oppure {"ok":false,"error":"...","detail":"..."}. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app.
function createVaultKey() {
if (!window.WebToApk || typeof window.WebToApk.generateKey !== 'function') {
alert('Secure keys work only in the app.');
return false;
}
var options = {
requireAuth: true, // key works only after the user unlocks (fingerprint / PIN)
authValiditySeconds: 30, // ...and for 30 seconds after that unlock
strongBox: false // true = demand the separate secure chip (fails if missing)
};
var r = JSON.parse(window.WebToApk.generateKey('vault', JSON.stringify(options)));
if (!r.ok) {
alert('Could not create the key: ' + r.error);
return false;
}
if (!r.hardwareBacked) console.log('Key is kept in software on this phone');
return true;
}Una chiave semplice senza regole di sblocco (le opzioni possono essere '{}'):
var r = JSON.parse(window.WebToApk.generateKey('notes', '{}'));Crea la chiave una sola volta, la prima volta che serve: chiamare di nuovo generateKey la sostituirebbe e renderebbe orfano tutto ciò che è già sigillato:
function ensureKey(alias) {
if (!window.WebToApk || typeof window.WebToApk.generateKey !== 'function') return false;
if (window.WebToApk.hasKey(alias) === 'true') return true; // keep the existing key
var r = JSON.parse(window.WebToApk.generateKey(alias, '{}'));
if (!r.ok && r.error === 'disabled') {
console.warn('Secure Keys was not turned on for this build');
}
return r.ok;
}
if (ensureKey('notes')) enableEncryptedNotes();Richiedi il chip sicuro separato per una chiave di portafoglio e dillo chiaramente all'utente quando il telefono non ne ha uno:
function createWalletKey() {
if (!window.WebToApk || typeof window.WebToApk.generateKey !== 'function') return false;
var r = JSON.parse(window.WebToApk.generateKey('wallet', JSON.stringify({
requireAuth: true,
authValiditySeconds: 10,
strongBox: true
})));
if (r.ok) return true;
if (r.error === 'strongbox-unavailable') {
alert('The wallet needs a phone with a dedicated security chip.');
} else if (r.error === 'bad alias') {
alert('Key names may use letters, digits, _ - . only.');
} else {
alert('Could not create the wallet key: ' + r.error);
}
return false;
}Note: l'alias può usare solo lettere, cifre, _, -, . e al massimo 64 caratteri, altrimenti error:"bad alias". Richiamarlo con lo stesso alias SOSTITUISCE la chiave, e i dati sigillati con la vecchia chiave non potranno più essere letti. Codici di errore: disabled (Chiavi sicure non attivate), bad options json, strongbox-unavailable (hai chiesto strongBox:true e il telefono non ha un chip sicuro, o è più vecchio di Android 9), foreign_origin (chiamato da una pagina che non è quella dell'app).
hasKey bridge#
window.WebToApk.hasKey(alias: String): String
Vero quando una chiave chiamata [alias] esiste nel keystore di questa app.
Esempio
Ti dice se esiste una chiave Keystore con questo alias. La maggior parte delle pagine usa AppMintKeystore.hasKey(alias), che restituisce un vero booleano.
Restituisce: la STRINGA "true" oppure "false", subito. Richiede: Chiavi sicure (cifratura) attivate quando crei l'app (senza, la risposta è sempre "false").
function vaultExists() {
if (!window.WebToApk || typeof window.WebToApk.hasKey !== 'function') return false;
return window.WebToApk.hasKey('vault') === 'true'; // compare the string
}
if (!vaultExists()) {
showCreateVaultScreen();
}Scegli la schermata iniziale: sblocca una cassaforte esistente oppure creane una, e ripulisci i dati sigillati la cui chiave non esiste più:
function chooseStartScreen() {
if (!window.WebToApk || typeof window.WebToApk.hasKey !== 'function') return showMyApp();
var hasVault = window.WebToApk.hasKey('vault') === 'true';
var hasData = localStorage.getItem('note') !== null;
if (hasVault) {
showUnlockScreen();
} else if (hasData) {
localStorage.removeItem('note'); // sealed with a key that no longer exists
showCreateVaultScreen('Your old vault could not be opened, so it was cleared.');
} else {
showCreateVaultScreen();
}
}Una chiave per ogni account con accesso effettuato, creata la prima volta che quell'account accede su questo telefono:
function keyAliasFor(userId) {
// aliases allow letters, digits, _ - . and at most 64 characters
return 'user_' + String(userId).replace(/[^A-Za-z0-9_.-]/g, '_').slice(0, 59);
}
function onSignedIn(user) {
if (!window.WebToApk || typeof window.WebToApk.hasKey !== 'function') return openAccount(user, null);
var alias = keyAliasFor(user.id);
if (window.WebToApk.hasKey(alias) !== 'true') {
var r = JSON.parse(window.WebToApk.generateKey(alias, '{}'));
if (!r.ok) return alert('Could not set up secure storage: ' + r.error);
}
openAccount(user, alias);
}Note: la risposta è una stringa, quindi if (WebToApk.hasKey('vault')) è sempre vero: confronta con === 'true'. Vedi solo le chiavi create da questa app tramite il bridge; le chiavi della shell sono nascoste.
isBiometricAvailable bridge#
window.WebToApk.isBiometricAvailable(): String
"available" | "no_hardware" | "not_enrolled" | "unavailable". Android 10+ considera volto, iride e impronta; Android 6-9 solo il sensore di impronte. "unavailable" = il sensore è temporaneamente inutilizzabile, o il controllo non è potuto avvenire (ad es. la build non aveva attivato l'autorizzazione Impronta digitale). Vedi [BiometricStatus].
Esempio
Ti dice se il telefono può eseguire uno sblocco biometrico (volto, iride o impronta) in questo momento. Controllalo prima di mostrare un pulsante di sblocco.
Restituisce: una stringa, subito: "available", "no_hardware" (nessun sensore), "not_enrolled" (sensore presente, ma nulla registrato, o nessun blocco schermo impostato) oppure "unavailable" (il sensore è temporaneamente inutilizzabile, o il controllo non è potuto avvenire, ad esempio perché la build non aveva selezionato Impronta digitale). Richiede: Impronta digitale selezionata quando crei l'app: senza, Android rifiuta di rispondere e il risultato è "unavailable".
function setupFingerprintButton() {
var btn = document.getElementById('fingerprint-button');
if (!window.WebToApk || typeof window.WebToApk.isBiometricAvailable !== 'function') {
btn.hidden = true; // browser: no fingerprint bridge
return;
}
var state = window.WebToApk.isBiometricAvailable();
if (state === 'available') {
btn.hidden = false;
} else if (state === 'not_enrolled') {
btn.hidden = true;
showHint('Add a fingerprint in your phone settings to unlock with it.');
} else {
btn.hidden = true; // no_hardware / unavailable
}
}Un interruttore nelle impostazioni "Sblocca con l'impronta" che è disattivato con il motivo quando non può funzionare:
var BIO_REASONS = {
no_hardware: 'This phone has no fingerprint or face sensor.',
not_enrolled: 'Set up a fingerprint or face unlock in your phone settings first.',
unavailable: 'The sensor cannot be used right now.'
};
function renderBioSwitch() {
var sw = document.getElementById('bio-switch');
var note = document.getElementById('bio-note');
var inApp = window.WebToApk && typeof window.WebToApk.isBiometricAvailable === 'function';
var state = inApp ? window.WebToApk.isBiometricAvailable() : 'not_in_app';
sw.disabled = state !== 'available';
if (sw.disabled) sw.checked = false;
note.textContent = state === 'not_in_app' ? 'Available in the app.' : (BIO_REASONS[state] || '');
}Controlla di nuovo quando l'utente torna dalle impostazioni del telefono, nel caso abbia appena aggiunto un'impronta digitale:
document.addEventListener('visibilitychange', function () {
if (document.visibilityState !== 'visible') return;
if (!window.WebToApk || typeof window.WebToApk.isBiometricAvailable !== 'function') return;
if (window.WebToApk.isBiometricAvailable() === 'available') {
renderBioSwitch();
showHint('Fingerprint unlock is ready. Turn it on in Settings.');
}
});Note: su Android 10 e successivi il controllo considera volto, iride e impronta, quindi un telefono con solo sblocco facciale risponde "available". Su Android 6-9 guarda solo il sensore di impronte, quindi lì un telefono con solo il volto risponde "no_hardware"; con allowDeviceCredential:true in authenticateBiometricEx l'utente può comunque sbloccare con il PIN, la sequenza o la password del telefono (Android 11+).
isKeyHardwareBacked bridge#
window.WebToApk.isKeyHardwareBacked(alias: String): String
Vero quando [alias] si trova nel TEE o in un secure element invece che nel software. Vale la pena controllarlo prima di promettere a un utente che distruggere una chiave è irreversibile.
Esempio
Ti dice se una chiave si trova nell'hardware sicuro del telefono (TEE o chip StrongBox) invece che nel software. La maggior parte delle pagine usa AppMintKeystore.isHardwareBacked(alias), che restituisce un vero booleano.
Restituisce: la STRINGA "true" o "false", subito. "false" anche quando la chiave non esiste. Richiede: Chiavi sicure (cifratura) attivate durante la creazione.
function describeVault() {
if (!window.WebToApk || typeof window.WebToApk.isKeyHardwareBacked !== 'function') return;
var hw = window.WebToApk.isKeyHardwareBacked('vault') === 'true';
document.getElementById('vault-info').textContent = hw
? 'Your key is locked in this phone\'s secure hardware.'
: 'Your key is protected by software on this phone.';
}Rifiuta di conservare un segreto di grande valore su un telefono che può tenere la chiave solo nel software:
function setUpWalletKey() {
var W = window.WebToApk;
if (!W || typeof W.isKeyHardwareBacked !== 'function') return false;
var r = JSON.parse(W.generateKey('wallet', JSON.stringify({ requireAuth: true })));
if (!r.ok) return false;
if (W.isKeyHardwareBacked('wallet') !== 'true') {
W.deleteKey('wallet');
alert('This phone cannot keep the wallet key in secure hardware, so the wallet is off here.');
return false;
}
return true;
}Invia al tuo server un breve riepilogo sulla sicurezza del dispositivo insieme all'accesso, cosĂŹ che possa decidere cosa consentire:
function deviceSecurity() {
var W = window.WebToApk;
var inApp = !!W && typeof W.isKeyHardwareBacked === 'function';
return {
secureKeys: inApp && W.isKeystoreEnabled() === 'true',
hardwareKey: inApp && W.isKeyHardwareBacked('vault') === 'true'
};
}
fetch('/api/sign-in', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email, password: password, device: deviceSecurity() })
});Note: scrivi "hardware" nella tua interfaccia solo quando questo è "true".
isKeystoreEnabled bridge#
window.WebToApk.isKeystoreEnabled(): String
Indica se il creatore ha attivato il bridge keystore per questa app.
Esempio
Ti dice se il creatore ha attivato il bridge Keystore per questa app. La maggior parte delle pagine usa AppMintKeystore.isAvailable(), che restituisce un vero booleano.
Restituisce: la STRINGA "true" oppure "false", subito. Richiede: nulla per chiamarlo; risponde "true" solo se Chiavi sicure (cifratura) era attivato quando hai creato l'app.
function keystoreReady() {
return !!window.WebToApk &&
typeof window.WebToApk.isKeystoreEnabled === 'function' &&
window.WebToApk.isKeystoreEnabled() === 'true';
}
document.getElementById('vault-button').hidden = !keystoreReady();Offri "Ricordami" solo quando il token può essere conservato cifrato:
var canSeal = !!window.WebToApk &&
typeof window.WebToApk.isKeystoreEnabled === 'function' &&
window.WebToApk.isKeystoreEnabled() === 'true';
var remember = document.getElementById('remember-me');
remember.parentElement.hidden = !canSeal;
function afterSignIn(token) {
if (!canSeal || !remember.checked) return; // nothing stored in plain text
if (window.WebToApk.hasKey('session') !== 'true') window.WebToApk.generateKey('session', '{}');
var r = JSON.parse(window.WebToApk.encryptWithKey('session', btoa(token)));
if (r.ok) localStorage.setItem('token', r.base64);
}Distingui tra "aperta in un browser" e "app creata senza Chiavi sicure" mentre fai i test:
function keystoreStatus() {
if (!window.WebToApk) return 'browser: no app bridge';
if (typeof window.WebToApk.isKeystoreEnabled !== 'function') return 'app without the keystore bridge';
return window.WebToApk.isKeystoreEnabled() === 'true'
? 'Secure Keys on'
: 'Secure Keys off in this build';
}
console.log('Keystore:', keystoreStatus());Note: quando è "false", ogni altro metodo delle chiavi risponde "false", "[]" o {ok:false, error:"disabled"}. Nascondi la funzione invece di offrire un pulsante che non può funzionare.
listKeys bridge#
window.WebToApk.listKeys(): String
Array JSON di ogni alias di chiave che questa pagina ha creato.
Esempio
Elenca gli alias di ogni chiave del Keystore creata dalle pagine di questa app. La maggior parte delle pagine usa AppMintKeystore.listKeys(), che restituisce l'array già interpretato.
Restituisce: una stringa con un array JSON, ordinata per nome, subito: '["notes","vault"]'. Richiede: Chiavi sicure (cifratura) attivate durante la creazione (senza: "[]").
function showKeys() {
if (!window.WebToApk || typeof window.WebToApk.listKeys !== 'function') return;
var aliases = JSON.parse(window.WebToApk.listKeys());
document.getElementById('keys').textContent =
aliases.length ? aliases.join(', ') : 'No keys yet';
}Una schermata di impostazioni "Chiavi di sicurezza" che elenca ogni chiave, dove si trova e un pulsante Elimina:
function renderKeyManager() {
if (!window.WebToApk || typeof window.WebToApk.listKeys !== 'function') return;
var list = document.getElementById('key-list');
list.innerHTML = '';
JSON.parse(window.WebToApk.listKeys()).forEach(function (alias) {
var hw = window.WebToApk.isKeyHardwareBacked(alias) === 'true';
var li = document.createElement('li');
li.textContent = alias + (hw ? ' (secure hardware) ' : ' (software) ');
var del = document.createElement('button');
del.textContent = 'Delete';
del.onclick = function () {
if (!confirm('Delete "' + alias + '"? Data sealed with it is lost for good.')) return;
window.WebToApk.deleteKey(alias);
renderKeyManager();
};
li.appendChild(del);
list.appendChild(li);
});
}Alla disconnessione, distruggi ogni chiave per account (chiamata user_...) cosĂŹ che nulla di memorizzato sul telefono possa piĂč essere letto:
function forgetAllAccounts() {
if (!window.WebToApk || typeof window.WebToApk.listKeys !== 'function') return 0;
var removed = 0;
JSON.parse(window.WebToApk.listKeys()).forEach(function (alias) {
if (alias.indexOf('user_') === 0 && window.WebToApk.deleteKey(alias) === 'true') removed++;
});
return removed;
}
document.getElementById('sign-out').addEventListener('click', function () {
var n = forgetAllAccounts();
console.log('Destroyed ' + n + ' account key(s)');
signOut();
});Note: Vengono elencate solo le chiavi create tramite il bridge; le chiavi interne dell'app non vengono mai mostrate. Risponde anche "[]" quando viene chiamata da una pagina che non Ăš quella dell'app (un altro sito caricato nell'app).