JavaScript ब्रिज API
बायोमेट्रिक्स और कीस्टोर
फ़िंगरप्रिंट और फ़ेस अनलॉक, और हार्डवेयर-समर्थित कुंजियाँ जिनसे पेज एन्क्रिप्ट कर सकता है लेकिन उन्हें कभी पढ़ नहीं सकता।
AppMintKeystore.decrypt हेल्पर#
window.AppMintKeystore.decrypt(alias, base64)
उदाहरण
AppMintKeystore.encrypt से बना डेटा उसी कुंजी से डिक्रिप्ट करता है।
लौटाता है: एक ऑब्जेक्ट, तुरंत (Promise नहीं): {ok:true, base64} (सादा डेटा) या {ok:false, error, detail}। ज़रूरत: बिल्ड करते समय Secure Keys (encryption) चालू हो।
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;
}WebToApkFS.writeSealed से लिखी फ़ाइल को WebToApkFS.readSealed(path, alias) से वापस पढ़ें - नतीजे का रूप वही है।
सील किया हुआ JSON ऑब्जेक्ट वापस पढ़ें, कुंजी को ज़रूरत हो तो पहले अनलॉक करके:
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;
}सील की गई डायरी एंट्री की सूची खोलें, और जो फ़ेल हों उन्हें “locked” के रूप में दिखाते रहें:
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'
};
});
}ध्यान दें: deleteKey के बाद डेटा हमेशा के लिए चला जाता है; यही इसका मकसद है। बदला हुआ पेलोड GCM जाँच में फ़ेल होता है। दूसरी त्रुटियाँ: bad base64, disabled, foreign_origin, unsupported.
AppMintKeystore.deleteKey हेल्पर#
window.AppMintKeystore.deleteKey(alias)
उदाहरण
एक सिक्योर कुंजी को हमेशा के लिए नष्ट कर देता है। उससे एन्क्रिप्ट की गई हर चीज़ फिर कभी किसी से भी नहीं पढ़ी जा सकती।
लौटाता है: तुरंत true (मिटा दी गई) या false (ऐसी कोई कुंजी नहीं, या फ़ीचर बंद है)। ज़रूरत: बिल्ड करते समय Secure Keys (encryption) चालू हो।
फ़ाइल को ओवरराइट करने से वह फ़्लैश मेमोरी से नहीं मिटती। उसे एन्क्रिप्ट करें, फिर कुंजी नष्ट कर दें - यही असली डिलीट है:
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.');
}सेटिंग्स में “सब कुछ मिटाएँ”: इस ऐप की बनाई हर कुंजी नष्ट करें, फिर पेज का स्टोरेज साफ़ करें:
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();
}ऐप के गलत PIN बहुत बार डाले जाने पर वॉल्ट को खुद नष्ट कर दें:
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.');
}ध्यान दें: इसे वापस नहीं लाया जा सकता और कोई रिकवरी नहीं है। अगर isHardwareBacked(alias) का नतीजा false था, तो कुंजी सॉफ़्टवेयर में थी - यूज़र से हमेशा के लिए डिलीट का वादा करने से पहले यह बता दें।
AppMintKeystore.encrypt हेल्पर#
window.AppMintKeystore.encrypt(alias, base64)
उदाहरण
base64 डेटा को एक सुरक्षित कुंजी से एन्क्रिप्ट करता है। नतीजे को कहीं भी रखें - कुंजी के बिना यह बेकार है।
लौटाता है: एक ऑब्जेक्ट, तुरंत (Promise नहीं): {ok:true, base64} (IV + ciphertext + tag) या {ok:false, error, detail}। ज़रूरी: बिल्ड करते समय Secure Keys (encryption) चालू, और इससे बनी एक कुंजी 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 को authenticateBiometricEx पर बनाया जा सकता है (वह उदाहरण देखें)।
किसी भी साइज़ की एन्क्रिप्टेड फ़ाइल लिखने के लिए WebToApkFS.writeSealed(path, base64, alias) इस्तेमाल करें - यह इसी कुंजी से टुकड़े-टुकड़े एन्क्रिप्ट करता है।
एक छोटे JSON ऑब्जेक्ट (नोट, टोकन, सेटिंग्स) को सील करें और 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);कुंजी पहली बार ज़रूरत पड़ने पर बनाएँ, फिर एन्क्रिप्ट करें:
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}
}ध्यान दें: हर कॉल में अधिकतम 4 MB (chunk too large)। त्रुटियाँ: no-such-key, auth-required, key-invalidated (स्क्रीन लॉक बदल गया - कुंजी जा चुकी है), bad base64, disabled, foreign_origin, unsupported.
AppMintKeystore.generateKey हेल्पर#
window.AppMintKeystore.generateKey(alias, options)
उदाहरण
Android Keystore में एक AES-256-GCM कुंजी बनाता है (या बदल देता है)। कुंजी सुरक्षित हार्डवेयर से बाहर कभी नहीं जाती; आपका पेज सिर्फ़ उसका नाम इस्तेमाल करता है।
लौटाता है: एक ऑब्जेक्ट, तुरंत (Promise नहीं): {ok:true, alias, hardwareBacked} या {ok:false, error, detail}। ज़रूरत: बिल्ड करते समय सिक्योर कीज़ (एन्क्रिप्शन) चालू हो (देखें 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);
}एक सादी कुंजी, अनलॉक की ज़रूरत नहीं:
var r = window.AppMintKeystore.generateKey('notes'); // options default to {}अलग सिक्योर चिप (StrongBox) की माँग करें। यह कमज़ोर कुंजी देने के बजाय फ़ेल हो जाता है:
var r = window.AppMintKeystore.generateKey('wallet', { strongBox: true });
if (!r.ok && r.error === 'strongbox-unavailable') {
alert('This phone has no secure chip.');
}ध्यान दें: एलियस: अक्षर, अंक, _ - ., ज़्यादा से ज़्यादा 64 वर्ण (वरना error:"bad alias")। मौजूदा एलियस इस्तेमाल करने पर कुंजी बदल जाती है, और पुरानी कुंजी से सील किया डेटा खो जाता है। दूसरी त्रुटियाँ: disabled, bad options json, foreign_origin, unsupported (कोई ब्रिज नहीं)।
AppMintKeystore.hasKey हेल्पर#
window.AppMintKeystore.hasKey(alias)
उदाहरण
बताता है कि इस एलियस वाली सिक्योर कुंजी मौजूद है या नहीं।
लौटाता है: तुरंत true / false। ज़रूरत: बिल्ड करते समय Secure Keys (encryption) चालू हो।
function startVault() {
if (!window.AppMintKeystore || !window.AppMintKeystore.isAvailable()) return;
if (window.AppMintKeystore.hasKey('vault')) {
showUnlockScreen();
} else {
showCreateVaultScreen();
}
}
window.addEventListener('load', startVault);पहले सेव से पहले पक्का करें कि कुंजी मौजूद है, उसे सिर्फ़ एक बार बनाते हुए:
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();उस सील किए डेटा को साफ़ करें जिसकी कुंजी जा चुकी है (मिटा दी गई, या ऐप डेटा उसके बिना रीस्टोर हुआ):
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);ध्यान दें: Secure Keys बंद होने पर false जवाब देता है, इसलिए अगर “अभी कोई कुंजी नहीं” और “यह फ़ीचर इस ऐप में नहीं” में फ़र्क करना हो तो पहले isAvailable() जाँचें।
AppMintKeystore.isAvailable हेल्पर#
window.AppMintKeystore.isAvailable()
उदाहरण
बताता है कि इस ऐप में हार्डवेयर-समर्थित सिक्योर कुंजियाँ इस्तेमाल हो सकती हैं या नहीं।
लौटाता है: true / false, तुरंत (Promise नहीं)। ज़रूरी: बिल्ड करते समय Secure Keys (encryption) चालू। window.AppMintKeystore ऑब्जेक्ट (आपकी पहली स्क्रिप्ट चलने से पहले) तब इंस्टॉल हो जाता है जब Secure Keys या Native Folder Access (SAF) चालू हो; isAvailable() Secure Keys के लिए जवाब देता है।
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();
});जब यह फ़ीचर इस ऐप में न हो, तब सिक्योर वॉल्ट के सभी कंट्रोल छिपा दें:
window.addEventListener('load', function () {
var ok = !!window.AppMintKeystore && window.AppMintKeystore.isAvailable();
document.querySelectorAll('.needs-secure-keys').forEach(function (el) {
el.hidden = !ok;
});
});कौन सी स्क्रीन दिखानी है यह तय करते समय "फ़ीचर बंद" और "अभी कोई कुंजी नहीं" में फ़र्क करें:
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();
});ध्यान दें: हेल्पर पेज लोड होने के बाद जुड़ता है, इसलिए इसकी जाँच ऐसी स्क्रिप्ट में न करें जो पेज के पार्स होते समय चलती है - क्लिक हैंडलर में या load के बाद जाँचें। ब्राउज़र में window.AppMintKeystore नहीं होता; ऊपर की जाँच बिना एरर के false लौटाती है।
AppMintKeystore.isHardwareBacked हेल्पर#
window.AppMintKeystore.isHardwareBacked(alias)
उदाहरण
बताता है कि कुंजी फ़ोन के सिक्योर हार्डवेयर (TEE या StrongBox) में है या सॉफ़्टवेयर में।
लौटाता है: true / false, तुरंत। कुंजी न होने पर false। ज़रूरत: बिल्ड करते समय सिक्योर कीज़ (एन्क्रिप्शन) चालू हो।
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)';
}कोई कीमती राज़ (वॉलेट सीड, रिकवरी कोड) तभी रखें जब उसकी कुंजी सिक्योर हार्डवेयर में हो:
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.');
}सपोर्ट रिपोर्ट के लिए सूची बनाएँ कि हर कुंजी कहाँ रहती है:
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()));नोट: generateKey कुंजी बनाने के तुरंत बाद hardwareBacked भी लौटाता है।
AppMintKeystore.listKeys हेल्पर#
window.AppMintKeystore.listKeys()
उदाहरण
आपके पेजों की बनाई हर सुरक्षित कुंजी के एलियास की सूची देता है।
लौटाता है: स्ट्रिंग्स का ऐरे, क्रम से, तुरंत, जैसे ['notes', 'vault']। कोई न हो या फ़ीचर बंद हो तो []। ज़रूरत: बिल्ड करते समय Secure Keys (encryption) चालू हो।
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);
});
}"मेरा सारा डेटा मिटाएँ" पर हर कुंजी नष्ट करें, जिससे उनसे सील किया गया सब कुछ पढ़ने लायक नहीं रहता:
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;
}साइन इन किए हर यूज़र के लिए एक कुंजी रखें और साइन आउट कर चुके यूज़र की कुंजियाँ हटा दें:
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);
});
}नोट: सिर्फ़ ब्रिज से बनाई गई कुंजियाँ ही सूची में आती हैं; ऐप की अपनी आंतरिक कुंजियाँ कभी नहीं दिखाई जातीं।
authenticateBiometric ब्रिज#
window.WebToApk.authenticateBiometric(callbackId: String, title: String, subtitle: String)
WebToApk.isBiometricAvailable() -> "available" | "no_hardware" | "not_enrolled" | "unavailable" WebToApk.authenticateBiometric(id, title, subtitle) नतीजा उसी __webToApkAuth कॉलबैक टेबल में आता है जिसे 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');
इसे अकेले नहीं, इसके समूह Fingerprint / biometric bridge के तहत बताया गया है।
उदाहरण
शीर्षक और उपशीर्षक के साथ सिस्टम का फ़िंगरप्रिंट प्रॉम्प्ट दिखाता है। authenticateBiometricEx का सरल रूप (PIN विकल्प, कस्टम Cancel लेबल या "strong" बायोमेट्रिक्स के लिए वही इस्तेमाल करें)।
लौटाता है: कुछ नहीं। जवाब बाद में आता है: शेल window.__webToApkAuth[callbackId](json) को '{"ok":true}' या '{"ok":false,"error":"<code>"}' के साथ कॉल करता है, फिर वह एंट्री हटा देता है। ज़रूरी: बिल्ड करते समय Fingerprint पर टिक (इसके बिना: error:"permission_missing").
पहले कॉलबैक रजिस्टर करें, फिर कॉल करें:
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);
});जोखिम भरे काम की पुष्टि पहले फ़िंगरप्रिंट से करें (खाली शीर्षक ऐप का नाम दिखाता है):
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);
});ऐप एक मिनट से ज़्यादा बैकग्राउंड में रहकर लौटे तो उसे फिर से लॉक करें:
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
});ध्यान दें: खाली टाइटल पर ऐप का नाम इस्तेमाल होता है। ग़लत उँगली से प्रॉम्प्ट खत्म नहीं होता - कॉलबैक सिर्फ़ सफलता, रद्द करने या असली त्रुटि पर चलता है। त्रुटि कोड वही हैं जो authenticateBiometricEx के हैं। यह साबित करता है कि फ़ोन का मालिक मौजूद है; यह किसी सर्वर को नहीं साबित करता कि वे कौन हैं।
authenticateBiometricEx ब्रिज#
window.WebToApk.authenticateBiometricEx(callbackId: String, optionsJson: String)
पूरा रूप। [optionsJson] ये लेता है: title, subtitle, description, negativeButtonText, allowDeviceCredential (PIN/पैटर्न/पासवर्ड का विकल्प), strong (BIOMETRIC_STRONG ज़रूरी करें - भुगतान जैसी हर चीज़ के लिए इस्तेमाल करें)। त्रुटियाँ अलग-अलग कोड होती हैं, एक बूलियन नहीं। किसी भी प्रॉम्प्ट से पहले: no_hardware | none_enrolled | unavailable (स्टेटस जाँच नहीं चल सकी - isBiometricAvailable देखें)। Android 9+ के सिस्टम प्रॉम्प्ट से: user_cancel | no_hardware | hw_unavailable | none_enrolled | lockout | lockout_permanent | no_device_credential | timeout | no_space | unable_to_process | vendor_error, और जो कोड Android बाद में जोड़े, उसके लिए उसका अपना संदेश टेक्स्ट (या `error_<code>`)। बिल्ड में Fingerprint अनुमति न होने पर permission_missing। Android 7-8 (API 24-27, सिर्फ़ फ़िंगरप्रिंट) पर त्रुटि फ़ोन का अपना संदेश टेक्स्ट होती है, जैसा निर्माता ने लिखा - कोई तय कोड नहीं।
उदाहरण
सिस्टम का फ़िंगरप्रिंट / चेहरा प्रॉम्प्ट सभी विकल्पों के साथ दिखाता है: दूसरे तरीके के रूप में PIN या पैटर्न, अपनी पसंद का रद्द करें लेबल, और सिर्फ़ "strong" बायोमेट्रिक्स।
लौटाता है: कुछ नहीं। जवाब बाद में आता है: शेल window.__webToApkAuth[callbackId](json) को '{"ok":true}' या '{"ok":false,"error":"<code>"}' के साथ कॉल करता है, फिर वह एंट्री हटा देता है। ज़रूरी: बिल्ड करते समय Fingerprint पर टिक (इसके बिना: error:"permission_missing").
एक छोटा Promise रैपर जिसे आप दोबारा इस्तेमाल कर सकते हैं:
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));
});
}ऐप को अनलॉक करें - फ़िंगरप्रिंट, चेहरा, या फ़ोन का PIN/पैटर्न:
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.');
}भुगतान की पुष्टि करें - सिर्फ़ strong बायोमेट्रिक्स, आपके अपने रद्द करें लेबल के साथ:
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);
}requireAuth से बनी Keystore कुंजी को अनलॉक करें, फिर दोबारा कोशिश करें:
async function openVault() {
var r = await verifyUser({ title: 'Open vault', allowDeviceCredential: true });
if (r.ok) readSecret(); // AppMintKeystore.decrypt works for authValiditySeconds now
}ध्यान दें: त्रुटि कोड: user_cancel, none_enrolled, no_hardware, hw_unavailable, lockout (रुकें और दोबारा कोशिश करें), lockout_permanent (यूज़र को पहले PIN से फ़ोन अनलॉक करना होगा), no_device_credential, timeout, permission_missing, unavailable, और कभी-कभार no_space, unable_to_process, vendor_error। Android 7-8 पर शेल अपना डायलॉग दिखाता है और त्रुटि सिस्टम का संदेश होती है। allowDeviceCredential और negativeButtonText साथ नहीं चलते: PIN विकल्प के साथ सिस्टम अपना बटन खुद बनाता है। गलत उँगली लगने से प्रॉम्प्ट खत्म नहीं होता।
decryptWithKey ब्रिज#
window.WebToApk.decryptWithKey(alias: String, base64Payload: String): String
[encryptWithKey] को उलटता है। कुंजी डिलीट होने के बाद `error:"no-such-key"` के साथ फ़ेल होता है - तब पेलोड जान-बूझकर वापस नहीं पाया जा सकता। @return JSON `{ok, base64, error}`।
उदाहरण
encryptWithKey से बनाए गए डेटा को उसी Keystore कुंजी से डिक्रिप्ट करता है। ज़्यादातर पेज AppMintKeystore.decrypt(alias, base64) इस्तेमाल करते हैं, जो पार्स किया हुआ ऑब्जेक्ट लौटाता है।
लौटाता है: एक JSON स्ट्रिंग, तुरंत: {"ok":true,"base64":"<plain data>"} या {"ok":false,"error":"...","detail":"..."}। ज़रूरत: बिल्ड करते समय Secure Keys (encryption) चालू हो।
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;
}requireAuth:true से बनी कुंजी के लिए: auth-required पर अनलॉक प्रॉम्प्ट दिखाएँ, फिर दोबारा डिक्रिप्ट करें:
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;
}एन्क्रिप्ट की गई फ़ोटो दिखाएँ: बाइट्स में डिक्रिप्ट करें, फिर blob URL से दिखाएँ:
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;
}ध्यान दें: कुंजी डिलीट होने (deleteKey) के बाद डेटा फिर कभी डिक्रिप्ट नहीं हो सकता - यह जान-बूझकर है। बदला या ख़राब हुआ पेलोड फ़ेल होता है (GCM टैग मेल नहीं खाता); तब error प्लेटफ़ॉर्म का संदेश होता है, ऊपर दिए कोड में से कोई नहीं। दूसरे कोड: bad base64, disabled, foreign_origin.
deleteKey ब्रिज#
window.WebToApk.deleteKey(alias: String): String
[alias] को हमेशा के लिए नष्ट करता है। उससे एन्क्रिप्ट की गई हर चीज़ डिक्रिप्ट होने लायक नहीं रहती - इस ऐप से भी नहीं, और डिवाइस रखने वाले किसी से भी नहीं।
उदाहरण
Keystore की एक कुंजी को हमेशा के लिए नष्ट करता है। उससे एन्क्रिप्ट की गई हर चीज़ फिर कभी नहीं पढ़ी जा सकती - न आपसे, न यूज़र से, न फ़ोन रखने वाले किसी और से। ज़्यादातर पेज AppMintKeystore.deleteKey(alias) इस्तेमाल करते हैं, जो असली boolean लौटाता है।
लौटाता है: स्ट्रिंग "true" (डिलीट हो गई) या "false" (ऐसी कोई कुंजी नहीं, Secure Keys बंद, या ऐप का अपना पेज नहीं), तुरंत। ज़रूरी: बिल्ड करते समय Secure Keys (encryption) चालू।
फ़ोन पर यही असली “सिक्योर डिलीट” है: फ़ाइल को ओवरराइट करने से वह फ़्लैश मेमोरी से नहीं मिटती, कुंजी नष्ट करने से मिटती है।
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.');
}कुंजी बदलें: डेटा को पुरानी कुंजी से खोलें, नई कुंजी से सील करें, फिर पुरानी कुंजी नष्ट कर दें:
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
}साइन आउट पर “इस फ़ोन पर मुझे भूल जाएँ”: अकाउंट की कुंजी नष्ट करें, फिर उसका कैश किया डेटा हटा दें:
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();
}ध्यान दें: यूज़र से यह वादा करने से पहले कि डिलीट वापस नहीं होगा, isKeyHardwareBacked जाँच लें - सिर्फ़ सॉफ़्टवेयर वाली कुंजी कमज़ोर होती है।
encryptWithKey ब्रिज#
window.WebToApk.encryptWithKey(alias: String, base64Plaintext: String): String
base64 [base64Plaintext] को [alias] से एन्क्रिप्ट करता है। हर कॉल अपना IV बनाती है, इसलिए बड़ी फ़ाइल टुकड़े-टुकड़े एन्क्रिप्ट होती है - टुकड़े एक-दूसरे से अलग होते हैं और किसी भी क्रम में डिक्रिप्ट हो सकते हैं। @return JSON `{ok, base64, error}` जहाँ `base64` है `IV || ciphertext || tag`।
उदाहरण
base64 डेटा को Keystore कुंजी से एन्क्रिप्ट करता है। ज़्यादातर पेज AppMintKeystore.encrypt(alias, base64) इस्तेमाल करते हैं, जो पार्स किया हुआ ऑब्जेक्ट लौटाता है।
लौटाता है: एक JSON स्ट्रिंग, तुरंत: {"ok":true,"base64":"<IV + ciphertext + tag>"} या {"ok":false,"error":"...","detail":"..."}। ज़रूरत: बिल्ड करते समय Secure Keys (encryption) चालू हो, और इससे बनी एक कुंजी generateKey.
इनपुट base64 होता है। टेक्स्ट के लिए पहले उसे UTF-8 base64 में बदलें:
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;
}स्टोर करने से पहले पूरे ऑब्जेक्ट (सेशन, कुछ सेटिंग्स) को JSON के रूप में सील करें:
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);यूज़र की चुनी हुई फ़ोटो एन्क्रिप्ट करें (बाइनरी डेटा, हर कॉल में ज़्यादा से ज़्यादा 4 MB):
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);
}
});ध्यान दें: हर कॉल नया IV इस्तेमाल करती है, इसलिए एक ही टेक्स्ट हर बार अलग नतीजा देता है। हर कॉल में ज़्यादा से ज़्यादा 4 MB (error:"chunk too large ..."); बड़ी फ़ाइलों के लिए टुकड़े-टुकड़े एन्क्रिप्ट करें, या WebToApkFS.writeSealed इस्तेमाल करें। त्रुटि कोड: no-such-key, auth-required, key-invalidated (यूज़र ने स्क्रीन लॉक बदल दिया - कुंजी हमेशा के लिए चली गई), bad base64, disabled, foreign_origin.
generateKey ब्रिज#
window.WebToApk.generateKey(alias: String, optionsJson: String): String
[alias] नाम की AES-256-GCM कुंजी बनाता है (या बदल देता है)। @param optionsJson `{"requireAuth":bool, "authValiditySeconds":int, "strongBox":bool}`। `requireAuth` कुंजी को डिवाइस लॉक से बाँधता है; `strongBox` उन डिवाइस पर सीधे फेल हो जाता है जिनमें सिक्योर एलिमेंट नहीं है, माँगी गई कुंजी से कमज़ोर कुंजी देने के बजाय। @return JSON `{ok, alias, hardwareBacked, error}`।
उदाहरण
Android Keystore में AES-256-GCM कुंजी बनाता है (या बदल देता है)। पेज को कुंजी का एक नाम मिलता है, कुंजी खुद कभी नहीं। ज़्यादातर पेज AppMintKeystore.generateKey(alias, options) इस्तेमाल करते हैं, जो JSON का काम आपके लिए कर देता है।
लौटाता है: एक JSON स्ट्रिंग, तुरंत: {"ok":true,"alias":"vault","hardwareBacked":true} या {"ok":false,"error":"...","detail":"..."}। ज़रूरत: बिल्ड करते समय Secure Keys (encryption) चालू हो।
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;
}बिना अनलॉक नियम वाली सादी कुंजी (विकल्प ये हो सकते हैं '{}'):
var r = JSON.parse(window.WebToApk.generateKey('notes', '{}'));कुंजी को सिर्फ़ एक बार, पहली बार ज़रूरत पड़ने पर बनाएँ - generateKey को दोबारा बुलाने से वह बदल जाएगी और पहले से सील किया सब कुछ अनाथ हो जाएगा:
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();वॉलेट कुंजी के लिए अलग सिक्योर चिप की माँग करें, और अगर फ़ोन में वह नहीं है तो यूज़र को साफ़ शब्दों में बताएँ:
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;
}ध्यान दें: एलियस में सिर्फ़ अक्षर, अंक, _, -, . हो सकते हैं और ज़्यादा से ज़्यादा 64 वर्ण, वरना error:"bad alias"। उसी एलियस से दोबारा कॉल करने पर कुंजी बदल जाती है, और पुरानी कुंजी से सील किया गया डेटा फिर पढ़ा नहीं जा सकता। त्रुटि कोड: disabled (Secure Keys चालू नहीं हैं), bad options json, strongbox-unavailable (आपने strongBox:true माँगा और फ़ोन में सिक्योर चिप नहीं है, या वह Android 9 से पुराना है), foreign_origin (ऐसे पेज से कॉल किया गया जो ऐप का अपना नहीं है)।
hasKey ब्रिज#
window.WebToApk.hasKey(alias: String): String
इस ऐप के कीस्टोर में [alias] नाम की कुंजी मौजूद हो तो True।
उदाहरण
बताता है कि इस एलियस वाली Keystore कुंजी मौजूद है या नहीं। ज़्यादातर पेज AppMintKeystore.hasKey(alias) इस्तेमाल करते हैं, जो असली बूलियन लौटाता है।
लौटाता है: STRING "true" या "false", तुरंत। ज़रूरत: बिल्ड करते समय सिक्योर कीज़ (एन्क्रिप्शन) चालू हो (इसके बिना जवाब हमेशा होता है "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();
}शुरुआती स्क्रीन चुनें: मौजूदा वॉल्ट अनलॉक करें, या नया बनाएँ - और वह सील किया हुआ डेटा साफ़ करें जिसकी कुंजी जा चुकी है:
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();
}
}साइन इन किए हुए हर अकाउंट के लिए एक कुंजी, जो उस अकाउंट के इस फ़ोन पर पहली बार साइन इन करने पर बनती है:
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);
}नोट: जवाब एक स्ट्रिंग है, इसलिए if (WebToApk.hasKey('vault')) हमेशा true होता है - === 'true' से तुलना करें। आपको सिर्फ़ वही कुंजियाँ दिखती हैं जो इस ऐप ने ब्रिज से बनाईं; शेल की अपनी कुंजियाँ छिपी रहती हैं।
isBiometricAvailable ब्रिज#
window.WebToApk.isBiometricAvailable(): String
"available" | "no_hardware" | "not_enrolled" | "unavailable"। Android 10+ में चेहरा, आइरिस और फ़िंगरप्रिंट गिने जाते हैं; Android 6-9 में सिर्फ़ फ़िंगरप्रिंट सेंसर। "unavailable" = सेंसर अस्थायी रूप से इस्तेमाल लायक नहीं है, या जाँच चल नहीं सकी (जैसे बिल्ड में Fingerprint अनुमति चालू नहीं थी)। [BiometricStatus] देखें।
उदाहरण
बताता है कि फ़ोन अभी बायोमेट्रिक अनलॉक (चेहरा, आइरिस या फ़िंगरप्रिंट) कर सकता है या नहीं। अनलॉक बटन दिखाने से पहले इसे जाँचें।
लौटाता है: एक स्ट्रिंग, तुरंत: "available", "no_hardware" (सेंसर नहीं है), "not_enrolled" (सेंसर है, पर कुछ रजिस्टर नहीं है या स्क्रीन लॉक सेट नहीं है) या "unavailable" (सेंसर कुछ समय के लिए इस्तेमाल नहीं हो सकता, या जाँच चल नहीं पाई - जैसे बिल्ड में फ़िंगरप्रिंट टिक नहीं किया गया था)। ज़रूरत: बिल्ड करते समय फ़िंगरप्रिंट टिक हो - इसके बिना Android जवाब देने से मना कर देता है और नतीजा होता है "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
}
}"फ़िंगरप्रिंट से अनलॉक करें" नाम की एक सेटिंग्स स्विच, जो काम न कर पाने पर कारण के साथ धूसर दिखती है:
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] || '');
}जब यूज़र फ़ोन सेटिंग्स से लौटे तो फिर से जाँचें, हो सकता है उन्होंने अभी फ़िंगरप्रिंट जोड़ा हो:
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.');
}
});ध्यान दें: Android 10 और उसके बाद की वर्ज़न में यह जाँच चेहरा, आइरिस और फ़िंगरप्रिंट गिनती है, इसलिए सिर्फ़ फ़ेस-अनलॉक वाला फ़ोन "available" जवाब देता है। Android 6-9 में यह सिर्फ़ फ़िंगरप्रिंट सेंसर देखती है, इसलिए वहाँ सिर्फ़ चेहरे वाला फ़ोन "no_hardware" जवाब देता है; authenticateBiometricEx में allowDeviceCredential:true के साथ यूज़र फ़ोन के PIN, पैटर्न या पासवर्ड से भी अनलॉक कर सकता है (Android 11+)।
isKeyHardwareBacked ब्रिज#
window.WebToApk.isKeyHardwareBacked(alias: String): String
सही (True) जब [alias] सॉफ़्टवेयर में नहीं, बल्कि TEE या सिक्योर एलिमेंट में रहती है। यूज़र से यह वादा करने से पहले जाँच लेना अच्छा है कि कुंजी नष्ट करना वापस नहीं हो सकता।
उदाहरण
बताता है कि कुंजी फ़ोन के सिक्योर हार्डवेयर (TEE या StrongBox चिप) में है या सॉफ़्टवेयर में। ज़्यादातर पेज AppMintKeystore.isHardwareBacked(alias) इस्तेमाल करते हैं, जो असली बूलियन लौटाता है।
लौटाता है: स्ट्रिंग "true" या "false", तुरंत। कुंजी न होने पर भी "false"। ज़रूरी: बिल्ड करते समय Secure Keys (encryption) चालू।
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.';
}ऐसे फ़ोन पर कीमती सीक्रेट रखने से मना करें जो कुंजी को सिर्फ़ सॉफ़्टवेयर में रख सकता है:
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;
}साइन इन के साथ अपने सर्वर को डिवाइस की सुरक्षा का एक छोटा सारांश भेजें, ताकि वह तय कर सके कि क्या अनुमति देनी है:
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() })
});नोट: अपने UI में "हार्डवेयर" तभी लिखें जब यह हो "true".
isKeystoreEnabled ब्रिज#
window.WebToApk.isKeystoreEnabled(): String
क्रिएटर ने इस ऐप के लिए कीस्टोर ब्रिज चालू किया है या नहीं।
उदाहरण
बताता है कि क्रिएटर ने इस ऐप के लिए Keystore ब्रिज चालू किया है या नहीं। ज़्यादातर पेज AppMintKeystore.isAvailable() इस्तेमाल करते हैं, जो असली बूलियन लौटाता है।
लौटाता है: STRING "true" या "false", तुरंत। ज़रूरत: इसे कॉल करने के लिए कुछ नहीं; यह "true" तभी जवाब देता है जब बिल्ड करते समय Secure Keys (encryption) चालू किया गया था।
function keystoreReady() {
return !!window.WebToApk &&
typeof window.WebToApk.isKeystoreEnabled === 'function' &&
window.WebToApk.isKeystoreEnabled() === 'true';
}
document.getElementById('vault-button').hidden = !keystoreReady();"Remember me" तभी दें जब टोकन एन्क्रिप्ट करके रखा जा सके:
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);
}टेस्ट करते समय “ब्राउज़र में खोला गया” और “ऐप Secure Keys के बिना बना है” में फ़र्क करें:
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());ध्यान दें: जब यह "false" हो, तब बाकी हर कुंजी मेथड "false", "[]" या {ok:false, error:"disabled"} लौटाता है। ऐसा बटन देने के बजाय फ़ीचर को छिपा दें जो काम ही नहीं कर सकता।
listKeys ब्रिज#
window.WebToApk.listKeys(): String
इस पेज द्वारा बनाई गई हर कुंजी के ऐलियास का JSON ऐरे।
उदाहरण
इस ऐप के पेजों द्वारा बनाई गई हर Keystore कुंजी के ऐलियास की सूची देता है। ज़्यादातर पेज AppMintKeystore.listKeys() इस्तेमाल करते हैं, जो पार्स किया हुआ ऐरे लौटाता है।
लौटाता है: नाम के हिसाब से क्रमबद्ध JSON ऐरे स्ट्रिंग, तुरंत: '["notes","vault"]'। ज़रूरी: बिल्ड करते समय Secure Keys (encryption) चालू (इसके बिना: "[]").
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';
}एक “Security keys” सेटिंग्स स्क्रीन जो हर कुंजी, वह कहाँ रहती है, और एक डिलीट बटन दिखाती है:
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);
});
}साइन आउट पर, हर अकाउंट की कुंजी (जिसका नाम user_... है) नष्ट कर दें ताकि फ़ोन पर कैश की हुई कोई चीज़ फिर न पढ़ी जा सके:
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();
});ध्यान दें: सिर्फ़ ब्रिज से बनी कुंजियाँ ही सूची में आती हैं; ऐप की अपनी अंदरूनी कुंजियाँ कभी नहीं दिखतीं। जब इसे ऐसे पेज से कॉल किया जाए जो ऐप का अपना नहीं है (ऐप में खुली कोई और साइट), तब भी यह "[]" जवाब देता है।