API jembatan JavaScript
Biometrik dan keystore
Buka kunci dengan sidik jari dan wajah, serta kunci berbasis perangkat keras yang bisa dipakai halaman untuk mengenkripsi tetapi tidak pernah bisa dibaca.
AppMintKeystore.decrypt helper#
window.AppMintKeystore.decrypt(alias, base64)
Contoh
Mendekripsi data yang dibuat oleh AppMintKeystore.encrypt dengan kunci yang sama.
Mengembalikan: sebuah objek, seketika (bukan Promise): {ok:true, base64} (data biasa) atau {ok:false, error, detail}. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi.
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;
}Untuk file yang ditulis dengan WebToApkFS.writeSealed, bacalah kembali dengan WebToApkFS.readSealed(path, alias) - bentuk hasilnya sama.
Baca kembali objek JSON tersegel, buka kunci dulu bila kunci memerlukannya:
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;
}Buka daftar entri buku harian yang tersegel, dengan entri yang gagal tetap terlihat sebagai "terkunci":
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'
};
});
}Catatan: Setelah deleteKey data hilang selamanya; memang itu tujuannya. Payload yang diubah gagal pada pemeriksaan GCM. Kesalahan lain: bad base64, disabled, foreign_origin, unsupported.
AppMintKeystore.deleteKey helper#
window.AppMintKeystore.deleteKey(alias)
Contoh
Menghancurkan kunci aman secara permanen. Semua yang dienkripsi dengannya tidak akan pernah bisa dibaca lagi - oleh siapa pun.
Mengembalikan: true (dihapus) atau false (tidak ada kunci itu, atau fitur mati), seketika. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi.
Menimpa file tidak menghapusnya di memori flash. Enkripsi, lalu hancurkan kuncinya - itulah penghapusan sungguhan:
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.');
}"Hapus semuanya" di pengaturan: hancurkan setiap kunci yang dibuat aplikasi ini, lalu bersihkan penyimpanan halaman:
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();
}Hancurkan brankas sendiri setelah terlalu banyak PIN aplikasi yang salah:
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.');
}Catatan: Tidak ada undo dan tidak ada pemulihan. Jika isHardwareBacked(alias) adalah false, kunci berada di perangkat lunak - katakan itu sebelum menjanjikan penghapusan yang tidak bisa dibatalkan kepada pengguna.
AppMintKeystore.encrypt helper#
window.AppMintKeystore.encrypt(alias, base64)
Contoh
Mengenkripsi data base64 dengan kunci aman. Simpan hasilnya di mana saja - tidak berguna tanpa kuncinya.
Hasil: sebuah objek, seketika (bukan Promise): {ok:true, base64} (IV + ciphertext + tag) atau {ok:false, error, detail}. Membutuhkan: Kunci Aman (enkripsi) diaktifkan saat build, dan sebuah kunci dari 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 dapat dibuat di authenticateBiometricEx (lihat contoh tersebut).
Untuk menulis FILE terenkripsi dengan ukuran berapa pun, gunakan WebToApkFS.writeSealed(path, base64, alias) - fungsi ini mengenkripsi bagian demi bagian dengan kunci yang sama.
Segel objek JSON kecil (catatan, token, pengaturan) dan simpan di 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);Buat kunci saat pertama kali dibutuhkan, lalu enkripsi:
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}
}Catatan: Maks 4 MB per panggilan (chunk too large). Kesalahan: no-such-key, auth-required, key-invalidated (kunci layar berubah - kuncinya hilang), bad base64, disabled, foreign_origin, unsupported.
AppMintKeystore.generateKey helper#
window.AppMintKeystore.generateKey(alias, options)
Contoh
Membuat (atau mengganti) kunci AES-256-GCM di Android Keystore. Kunci tidak pernah meninggalkan perangkat keras aman; halaman Anda hanya memakai namanya.
Mengembalikan: sebuah objek, seketika (bukan Promise): {ok:true, alias, hardwareBacked} atau {ok:false, error, detail}. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi (lihat 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);
}Kunci biasa, tanpa perlu buka kunci:
var r = window.AppMintKeystore.generateKey('notes'); // options default to {}Minta chip aman terpisah (StrongBox). Jika tidak ada, perintah gagal dan tidak memberi Anda kunci yang lebih lemah:
var r = window.AppMintKeystore.generateKey('wallet', { strongBox: true });
if (!r.ok && r.error === 'strongbox-unavailable') {
alert('This phone has no secure chip.');
}Catatan: Alias: huruf, angka, _ - ., hingga 64 karakter (jika tidak, error:"bad alias"). Memakai alias yang sudah ada akan mengganti kuncinya, dan data yang disegel dengan kunci lama hilang. Kesalahan lain: disabled, bad options json, foreign_origin, unsupported (tanpa bridge).
AppMintKeystore.hasKey helper#
window.AppMintKeystore.hasKey(alias)
Contoh
Memberi tahu Anda apakah kunci aman dengan alias ini ada.
Mengembalikan: true / false, seketika. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi.
function startVault() {
if (!window.AppMintKeystore || !window.AppMintKeystore.isAvailable()) return;
if (window.AppMintKeystore.hasKey('vault')) {
showUnlockScreen();
} else {
showCreateVaultScreen();
}
}
window.addEventListener('load', startVault);Pastikan kunci ada sebelum penyimpanan pertama, dengan membuatnya hanya sekali:
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();Bersihkan data tersegel yang kuncinya sudah hilang (dihapus, atau data aplikasi dipulihkan tanpa kunci itu):
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);Catatan: Menjawab false saat Kunci Aman nonaktif, jadi periksa isAvailable() lebih dulu bila Anda perlu membedakan "belum ada kunci" dari "fitur tidak ada di aplikasi ini".
AppMintKeystore.isAvailable helper#
window.AppMintKeystore.isAvailable()
Contoh
Memberi tahu Anda apakah kunci aman berbasis perangkat keras dapat digunakan di aplikasi ini.
Hasil: true / false, seketika (bukan Promise). Membutuhkan: Kunci Aman (enkripsi) diaktifkan saat build. Objek window.AppMintKeystore dipasang (sebelum skrip pertama Anda berjalan) setiap kali Kunci Aman atau Akses Folder Native (SAF) aktif; isAvailable() menjawab untuk Kunci Aman.
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();
});Sembunyikan setiap kontrol brankas aman saat fitur tidak ada di aplikasi ini:
window.addEventListener('load', function () {
var ok = !!window.AppMintKeystore && window.AppMintKeystore.isAvailable();
document.querySelectorAll('.needs-secure-keys').forEach(function (el) {
el.hidden = !ok;
});
});Bedakan "fitur mati" dari "belum ada kunci" saat memutuskan layar mana yang ditampilkan:
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();
});Catatan: Helper ditambahkan setelah halaman selesai dimuat, jadi jangan mengujinya di skrip yang berjalan saat halaman masih diurai - ujilah di handler klik atau setelah load. Di browser tidak ada window.AppMintKeystore; pemeriksaan di atas mengembalikan false tanpa melempar kesalahan.
AppMintKeystore.isHardwareBacked helper#
window.AppMintKeystore.isHardwareBacked(alias)
Contoh
Memberi tahu Anda apakah kunci berada di perangkat keras aman ponsel (TEE atau StrongBox) dan bukan di perangkat lunak.
Mengembalikan: true / false, seketika. false saat kunci tidak ada. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi.
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)';
}Simpan rahasia bernilai tinggi (seed dompet, kode pemulihan) hanya jika kuncinya berada di perangkat keras yang aman:
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.');
}Tampilkan tempat setiap kunci berada, untuk laporan dukungan:
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()));Catatan: generateKey juga mengembalikan hardwareBacked tepat setelah membuat kunci.
AppMintKeystore.listKeys helper#
window.AppMintKeystore.listKeys()
Contoh
Mencantumkan alias setiap kunci aman yang dibuat halaman Anda.
Mengembalikan: sebuah array string, terurut, seketika, mis. ['notes', 'vault']. [] saat tidak ada atau fitur mati. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi.
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);
});
}Hancurkan setiap kunci pada "Hapus semua data saya", yang membuat semua yang tersegel dengannya tidak terbaca:
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;
}Simpan satu kunci per pengguna yang masuk dan hapus kunci pengguna yang sudah keluar:
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);
});
}Catatan: Hanya kunci yang dibuat melalui jembatan yang dicantumkan; kunci internal aplikasi tidak pernah ditampilkan.
authenticateBiometric jembatan#
window.WebToApk.authenticateBiometric(callbackId: String, title: String, subtitle: String)
WebToApk.isBiometricAvailable() -> "available" | "no_hardware" | "not_enrolled" | "unavailable" WebToApk.authenticateBiometric(id, title, subtitle) Hasilnya dikirim ke tabel callback __webToApkAuth yang sama dengan yang dipakai bridge 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');
Dijelaskan oleh kelompoknya, Jembatan sidik jari / biometrik, bukan sendiri-sendiri.
Contoh
Menampilkan prompt sidik jari sistem dengan judul dan subjudul. Bentuk sederhana dari authenticateBiometricEx (gunakan yang itu untuk opsi PIN, label Batal kustom, atau biometrik "kuat").
Mengembalikan: tidak ada. Jawabannya tiba kemudian: shell memanggil window.__webToApkAuth[callbackId](json) dengan '{"ok":true}' atau '{"ok":false,"error":"<code>"}', lalu menghapus entri itu. Memerlukan: Sidik Jari dicentang saat Anda membuat aplikasi (tanpanya: error:"permission_missing").
Daftarkan callback DULU, lalu panggil:
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);
});Konfirmasi tindakan berisiko dengan sidik jari lebih dulu (judul kosong menampilkan nama aplikasi):
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);
});Kunci aplikasi lagi saat kembali setelah lebih dari semenit di latar belakang:
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
});Catatan: Judul kosong memakai nama aplikasi. Jari yang salah TIDAK mengakhiri prompt - callback hanya terpanggil saat berhasil, dibatalkan, atau ada kesalahan sungguhan. Kode kesalahannya sama dengan authenticateBiometricEx. Ini membuktikan pemilik ponsel hadir; tidak membuktikan siapa mereka kepada server.
authenticateBiometricEx jembatan#
window.WebToApk.authenticateBiometricEx(callbackId: String, optionsJson: String)
Bentuk lengkapnya. [optionsJson] menerima: title, subtitle, description, negativeButtonText, allowDeviceCredential (cadangan PIN/pola/kata sandi), strong (wajib BIOMETRIC_STRONG - gunakan untuk hal yang berkaitan dengan pembayaran). Kesalahan berupa kode yang berbeda-beda, bukan satu boolean. Sebelum prompt apa pun: no_hardware | none_enrolled | unavailable (pemeriksaan status tidak bisa dijalankan - lihat isBiometricAvailable). Dari prompt sistem Android 9+: user_cancel | no_hardware | hw_unavailable | none_enrolled | lockout | lockout_permanent | no_device_credential | timeout | no_space | unable_to_process | vendor_error, dan untuk kode yang ditambahkan Android kemudian, teks pesannya sendiri (atau `error_<code>`). permission_missing bila build tidak memiliki izin Fingerprint. Di Android 7-8 (API 24-27, hanya sidik jari) kesalahan berupa teks pesan ponsel itu sendiri sebagaimana ditulis OEM - bukan kode tetap.
Contoh
Menampilkan prompt sidik jari / wajah sistem dengan opsi lengkap: PIN atau pola sebagai jalan masuk kedua, label Batal kustom, dan hanya biometrik "kuat".
Mengembalikan: tidak ada. Jawabannya tiba kemudian: shell memanggil window.__webToApkAuth[callbackId](json) dengan '{"ok":true}' atau '{"ok":false,"error":"<code>"}', lalu menghapus entri itu. Memerlukan: Sidik Jari dicentang saat Anda membuat aplikasi (tanpanya: error:"permission_missing").
Wrapper Promise kecil yang bisa Anda pakai ulang:
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));
});
}Buka kunci aplikasi - sidik jari, wajah, atau PIN/pola ponsel:
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.');
}Konfirmasi pembayaran - hanya biometrik kuat, dengan label Batal Anda sendiri:
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);
}Buka kunci Keystore yang dibuat dengan requireAuth, lalu coba lagi:
async function openVault() {
var r = await verifyUser({ title: 'Open vault', allowDeviceCredential: true });
if (r.ok) readSecret(); // AppMintKeystore.decrypt works for authValiditySeconds now
}Catatan: Kode kesalahan: user_cancel, none_enrolled, no_hardware, hw_unavailable, lockout (tunggu dan coba lagi), lockout_permanent (pengguna harus membuka kunci ponsel dengan PIN lebih dulu), no_device_credential, timeout, permission_missing, unavailable, dan jarang no_space, unable_to_process, vendor_error. Pada Android 7-8 shell menampilkan dialognya sendiri dan kesalahan adalah teks pesan dari sistem. allowDeviceCredential dan negativeButtonText tidak bisa digabung: dengan opsi PIN sistem menggambar tombolnya sendiri. Jari yang salah tidak mengakhiri prompt.
decryptWithKey jembatan#
window.WebToApk.decryptWithKey(alias: String, base64Payload: String): String
Membalik [encryptWithKey]. Gagal dengan `error:"no-such-key"` setelah kunci dihapus - payload lalu tidak dapat dipulihkan, memang dirancang demikian. @return JSON `{ok, base64, error}`.
Contoh
Mendekripsi data buatan encryptWithKey dengan kunci Keystore yang sama. Kebanyakan halaman memakai AppMintKeystore.decrypt(alias, base64), yang mengembalikan objek yang sudah diurai.
Mengembalikan: sebuah string JSON, seketika: {"ok":true,"base64":"<plain data>"} atau {"ok":false,"error":"...","detail":"..."}. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi.
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;
}Untuk kunci yang dibuat dengan requireAuth:true: pada auth-required, tampilkan prompt buka kunci, lalu dekripsi lagi:
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;
}Tampilkan foto terenkripsi: dekripsi menjadi byte, lalu tampilkan melalui 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;
}Catatan: Setelah kunci dihapus (deleteKey), data tidak akan pernah bisa didekripsi lagi - memang dirancang demikian. Payload yang diubah atau rusak gagal (tag GCM tidak cocok); error lalu berupa pesan dari platform, bukan salah satu kode di atas. Kode lain: bad base64, disabled, foreign_origin.
deleteKey jembatan#
window.WebToApk.deleteKey(alias: String): String
Menghancurkan [alias] secara permanen. Semua yang dienkripsi dengannya tidak dapat didekripsi lagi - termasuk oleh aplikasi ini, dan oleh siapa pun yang memegang perangkat.
Contoh
Menghancurkan kunci Keystore selamanya. Semua yang dienkripsi dengannya tidak akan pernah bisa dibaca lagi - oleh Anda, pengguna, atau siapa pun yang memegang ponsel. Kebanyakan halaman memakai AppMintKeystore.deleteKey(alias), yang mengembalikan boolean sungguhan.
Hasil: STRING "true" (dihapus) atau "false" (kunci tidak ada, Kunci Aman nonaktif, atau bukan halaman milik aplikasi), seketika. Membutuhkan: Kunci Aman (enkripsi) diaktifkan saat build.
Ini satu-satunya "penghapusan aman" yang nyata di ponsel: menimpa file tidak menghapusnya di memori flash, sedangkan menghancurkan kunci menghapusnya.
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.');
}Rotasi kunci: buka data dengan kunci lama, segel dengan kunci baru, lalu hancurkan kunci lama:
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
}"Lupakan saya di ponsel ini" saat keluar akun: hancurkan kunci akun, lalu hapus data yang tersimpan:
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();
}Catatan: Periksa isKeyHardwareBacked sebelum Anda menjanjikan kepada pengguna bahwa penghapusan tidak dapat dibatalkan - kunci yang hanya berbasis perangkat lunak lebih lemah.
encryptWithKey jembatan#
window.WebToApk.encryptWithKey(alias: String, base64Plaintext: String): String
Mengenkripsi base64 [base64Plaintext] dengan [alias]. Setiap panggilan membuat IV sendiri, sehingga file besar dienkripsi bagian demi bagian - setiap bagian independen dan dapat didekripsi dalam urutan apa pun. @return JSON `{ok, base64, error}` dengan `base64` berupa `IV || ciphertext || tag`.
Contoh
Mengenkripsi data base64 dengan kunci Keystore. Kebanyakan halaman memakai AppMintKeystore.encrypt(alias, base64), yang mengembalikan objek yang sudah diurai.
Mengembalikan: sebuah string JSON, seketika: {"ok":true,"base64":"<IV + ciphertext + tag>"} atau {"ok":false,"error":"...","detail":"..."}. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi, dan kunci yang dibuat dengan generateKey.
Inputnya base64. Untuk teks, ubah dulu menjadi 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;
}Segel seluruh objek (sesi, beberapa pengaturan) sebagai JSON sebelum Anda menyimpannya:
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);Enkripsi foto yang dipilih pengguna (data biner, hingga 4 MB per panggilan):
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);
}
});Catatan: Setiap panggilan memakai IV baru, jadi teks yang sama menghasilkan hasil berbeda setiap kali. Maksimal 4 MB per panggilan (error:"chunk too large ..."); untuk file lebih besar, enkripsi bagian demi bagian, atau gunakan WebToApkFS.writeSealed. Kode kesalahan: no-such-key, auth-required, key-invalidated (pengguna mengubah kunci layar - kuncinya hilang selamanya), bad base64, disabled, foreign_origin.
generateKey jembatan#
window.WebToApk.generateKey(alias: String, optionsJson: String): String
Membuat (atau mengganti) kunci AES-256-GCM bernama [alias]. @param optionsJson `{"requireAuth":bool, "authValiditySeconds":int, "strongBox":bool}`. `requireAuth` mengikat kunci ke kunci layar perangkat; `strongBox` langsung gagal di perangkat tanpa secure element alih-alih memberi kunci yang lebih lemah dari yang diminta. @return JSON `{ok, alias, hardwareBacked, error}`.
Contoh
Membuat (atau mengganti) kunci AES-256-GCM di Android Keystore. Halaman mendapat nama untuk kunci, bukan kuncinya. Kebanyakan halaman memakai AppMintKeystore.generateKey(alias, options), yang menangani pekerjaan JSON untuk Anda.
Mengembalikan: sebuah string JSON, seketika: {"ok":true,"alias":"vault","hardwareBacked":true} atau {"ok":false,"error":"...","detail":"..."}. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi.
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;
}Kunci biasa tanpa aturan buka kunci (opsi dapat berupa '{}'):
var r = JSON.parse(window.WebToApk.generateKey('notes', '{}'));Buat kunci sekali saja, saat pertama kali dibutuhkan - memanggil generateKey lagi akan menggantinya dan membuat semua yang sudah tersegel menjadi yatim:
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();Minta chip aman terpisah untuk kunci dompet, dan beri tahu pengguna dengan jelas bila ponselnya tidak memilikinya:
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;
}Catatan: Alias hanya boleh memakai huruf, angka, _, -, . dan paling banyak 64 karakter, jika tidak error:"bad alias". Memanggilnya lagi dengan alias yang sama MENGGANTI kunci, dan data yang disegel dengan kunci lama tidak bisa dibaca lagi. Kode kesalahan: disabled (Kunci Aman tidak diaktifkan), bad options json, strongbox-unavailable (Anda meminta strongBox:true dan ponsel tidak memiliki chip aman, atau lebih lama dari Android 9), foreign_origin (dipanggil dari halaman yang bukan milik aplikasi itu sendiri).
hasKey jembatan#
window.WebToApk.hasKey(alias: String): String
Benar jika ada kunci bernama [alias] di keystore aplikasi ini.
Contoh
Memberi tahu Anda apakah kunci Keystore dengan alias ini ada. Sebagian besar halaman memakai AppMintKeystore.hasKey(alias), yang mengembalikan boolean sungguhan.
Mengembalikan: STRING "true" atau "false", seketika. Memerlukan: Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi (tanpanya jawabannya selalu "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();
}Pilih layar awal: buka brankas yang ada, atau siapkan satu - dan bersihkan data tersegel yang kuncinya sudah hilang:
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();
}
}Satu kunci per akun yang masuk, dibuat saat akun itu pertama kali masuk di ponsel ini:
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);
}Catatan: Jawabannya berupa string, jadi if (WebToApk.hasKey('vault')) selalu benar - bandingkan dengan === 'true'. Anda hanya melihat kunci yang dibuat aplikasi ini melalui jembatan; kunci milik shell sendiri disembunyikan.
isBiometricAvailable jembatan#
window.WebToApk.isBiometricAvailable(): String
"available" | "no_hardware" | "not_enrolled" | "unavailable". Android 10+ menghitung wajah, iris, dan sidik jari; Android 6-9 hanya sensor sidik jari. "unavailable" = sensor sementara tidak dapat digunakan, atau pemeriksaan tidak bisa dijalankan (mis. build tidak mengaktifkan izin Sidik Jari). Lihat [BiometricStatus].
Contoh
Memberi tahu apakah ponsel bisa melakukan buka kunci biometrik (wajah, iris, atau sidik jari) saat ini. Periksa sebelum menampilkan tombol buka kunci.
Mengembalikan: sebuah string, seketika: "available", "no_hardware" (tanpa sensor), "not_enrolled" (ada sensor, tetapi belum ada yang terdaftar, atau kunci layar belum diatur) atau "unavailable" (sensor sementara tidak dapat digunakan, atau pemeriksaan tidak bisa dijalankan - misalnya build tidak mencentang Sidik Jari). Memerlukan: Sidik Jari dicentang saat Anda membuat aplikasi - tanpanya Android menolak menjawab dan hasilnya adalah "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
}
}Sakelar pengaturan "Buka kunci dengan sidik jari" yang diredupkan beserta alasannya saat tidak bisa berfungsi:
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] || '');
}Periksa lagi saat pengguna kembali dari pengaturan ponsel, kalau-kalau mereka baru menambahkan sidik jari:
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.');
}
});Catatan: Pada Android 10 ke atas pemeriksaan menghitung wajah, iris, dan sidik jari, jadi ponsel dengan buka kunci wajah saja menjawab "available". Pada Android 6-9 pemeriksaan hanya melihat sensor sidik jari, jadi ponsel dengan wajah saja di sana menjawab "no_hardware"; dengan allowDeviceCredential:true di authenticateBiometricEx pengguna tetap bisa membuka kunci dengan PIN, pola, atau kata sandi ponsel (Android 11+).
isKeyHardwareBacked jembatan#
window.WebToApk.isKeyHardwareBacked(alias: String): String
Benar jika [alias] berada di TEE atau secure element, bukan di perangkat lunak. Layak diperiksa sebelum menjanjikan kepada pengguna bahwa menghancurkan kunci tidak bisa dibatalkan.
Contoh
Memberi tahu Anda apakah kunci berada di dalam perangkat keras aman ponsel (chip TEE atau StrongBox) dan bukan di perangkat lunak. Sebagian besar halaman memakai AppMintKeystore.isHardwareBacked(alias), yang mengembalikan boolean sungguhan.
Hasil: STRING "true" atau "false", seketika. "false" juga bila kunci tidak ada. Membutuhkan: Kunci Aman (enkripsi) diaktifkan saat build.
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.';
}Tolak menyimpan rahasia bernilai tinggi di ponsel yang hanya bisa menyimpan kunci dalam perangkat lunak:
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;
}Kirim ringkasan keamanan perangkat yang singkat ke server Anda saat masuk, agar server bisa memutuskan apa yang diizinkan:
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() })
});Catatan: Katakan "hardware" di UI Anda hanya saat ini "true".
isKeystoreEnabled jembatan#
window.WebToApk.isKeystoreEnabled(): String
Apakah pembuat mengaktifkan bridge keystore untuk aplikasi ini.
Contoh
Memberi tahu apakah pembuat aplikasi mengaktifkan jembatan Keystore untuk aplikasi ini. Kebanyakan halaman memakai AppMintKeystore.isAvailable(), yang mengembalikan boolean sungguhan.
Mengembalikan: STRING "true" atau "false", seketika. Memerlukan: tidak ada untuk memanggilnya; ia menjawab "true" hanya jika Kunci Aman (enkripsi) diaktifkan saat Anda membuat aplikasi.
function keystoreReady() {
return !!window.WebToApk &&
typeof window.WebToApk.isKeystoreEnabled === 'function' &&
window.WebToApk.isKeystoreEnabled() === 'true';
}
document.getElementById('vault-button').hidden = !keystoreReady();Tawarkan "Ingat saya" hanya jika token bisa disimpan terenkripsi:
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);
}Bedakan "dibuka di browser" dan "aplikasi dibuat tanpa Kunci Aman" saat Anda menguji:
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());Catatan: Saat ini "false", setiap metode kunci lain menjawab "false", "[]" atau {ok:false, error:"disabled"}. Sembunyikan fitur alih-alih menawarkan tombol yang tidak bisa berfungsi.
listKeys jembatan#
window.WebToApk.listKeys(): String
Array JSON berisi setiap alias kunci yang dibuat halaman ini.
Contoh
Mencantumkan alias setiap kunci Keystore yang dibuat halaman aplikasi ini. Kebanyakan halaman memakai AppMintKeystore.listKeys(), yang mengembalikan array yang sudah diurai.
Hasil: string array JSON, diurutkan menurut nama, seketika: '["notes","vault"]'. Membutuhkan: Kunci Aman (enkripsi) diaktifkan saat build (tanpa itu: "[]").
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';
}Layar pengaturan "Kunci keamanan" yang menampilkan setiap kunci, tempatnya berada, dan tombol Hapus:
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);
});
}Saat keluar akun, hancurkan setiap kunci per akun (bernama user_...) agar tidak ada data tersimpan di ponsel yang bisa dibaca lagi:
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();
});Catatan: Hanya kunci yang dibuat melalui bridge yang didaftar; kunci internal aplikasi tidak pernah ditampilkan. Juga menjawab "[]" bila dipanggil dari halaman yang bukan milik aplikasi (situs lain yang dimuat di dalam aplikasi).