JavaScript ब्रिज API
क्रैश रिपोर्ट और डिवाइस इंटीग्रिटी
पेज द्वारा दर्ज त्रुटियाँ, आख़िरी नेटिव क्रैश, और फ़ोन रूटेड या बदला हुआ लगता है या नहीं, साथ में आपके सर्वर के लिए वैकल्पिक Play Integrity टोकन।
AppMint.integrity हेल्पर#
AppMint.integrity.check() / AppMint.integrity.token(nonce, cloudProjectNumber?)
उदाहरण
getDeviceIntegrity और requestIntegrityToken के Promise वर्ज़न, जो आपके पेज की पहली स्क्रिप्ट से पहले मौजूद होते हैं, जब बिल्ड में Device integrity checks (Integrate चरण, चरण 3) चालू हो।
लौटाता है: check() हेयुरिस्टिक्स ऑब्जेक्ट ({rooted, emulator, debuggerAttached, signals, installer, installedFromPlay, signatureSha256, method: 'heuristic'}) के साथ resolve होता है; token(nonce) Play Integrity टोकन स्ट्रिंग के साथ resolve होता है, या ऐसी Error के साथ reject होता है जिसका संदेश त्रुटि का नाम है (not_enabled, nonce_too_short, network_error, …) और code Play का त्रुटि कोड है। हेयुरिस्टिक्स सिर्फ़ संकेत हैं; फ़ैसला सिर्फ़ आपके सर्वर द्वारा टोकन डिकोड करने से मिलता है।
async function guard() {
if (!(window.AppMint && AppMint.integrity)) return; // browser, or option off
const local = await AppMint.integrity.check();
if (local.rooted) console.warn('rooted signals:', local.signals);
const { nonce } = await fetch('/api/integrity/nonce').then(r => r.json());
try {
const token = await AppMint.integrity.token(nonce); // project number from the build
await fetch('/api/integrity/verify', { method: 'POST', body: token });
} catch (e) {
console.warn('Play Integrity unavailable:', e.message, e.code);
}
}ऐप दोबारा पैक की हुई कॉपी हो तो चेतावनी दें:
if (window.AppMint && AppMint.integrity) {
const d = await AppMint.integrity.check();
if (!d.installedFromPlay) console.log('installed by', d.installer || 'side-loaded');
if (d.signatureSha256 && d.signatureSha256 !== MY_CERT_SHA256) reportTamper(d.signatureSha256);
}टोकन की विफलता को नाम से संभालें:
async function getToken(nonce) {
if (!(window.AppMint && AppMint.integrity)) return null;
try {
return await AppMint.integrity.token(nonce);
} catch (e) {
if (e.message === 'not_enabled') console.warn('Device integrity checks are off in this build');
else if (e.message === 'nonce_too_short') console.warn('the server nonce must be longer');
else console.warn('Play Integrity error', e.message, e.code);
return null;
}
}getDeviceIntegrity ब्रिज#
window.WebToApk.getDeviceIntegrity(): String
लोकल root / एमुलेटर / डीबगर के अनुमान (HEURISTICS) (देखें DeviceIntegrity) JSON के रूप में: `{rooted, emulator, debuggerAttached, signals:[…], installer, installedFromPlay, signatureSha256, method:"heuristic", sdk}`। साफ़ नतीजे का मतलब है "कोई संकेत नहीं मिला", सबूत नहीं - भरोसेमंद फ़ैसले के लिए अपने सर्वर को Play Integrity टोकन भेजें (requestIntegrityToken)। बिल्ड में Device integrity चालू न हो तो `{"error":"not_enabled"}`। यह कुछ मिलीसेकंड रुकता है (फ़ाइलें पढ़ता है): इसे एक ही बार कॉल करें।
उदाहरण
लोकल root, एमुलेटर और डीबगर के अनुमान (HEURISTICS), JSON स्ट्रिंग के रूप में। साफ़ नतीजे का मतलब है "कोई संकेत नहीं मिला", सबूत नहीं: root छिपाने वाला मॉड्यूल इस्तेमाल करने वाला जानकार यूज़र जाँच पास कर लेता है। इसे चेतावनी देने या कोई जाँच जोड़ने के लिए इस्तेमाल करें; भरोसेमंद फ़ैसले के लिए अपने सर्वर को Play Integrity टोकन भेजें (requestIntegrityToken).
लौटाता है: '{"rooted":false,"emulator":false,"debuggerAttached":false,"signals":[],"installer":"com.android.vending","installedFromPlay":true,"signatureSha256":"AB:CD:…","method":"heuristic","sdk":34}', या '{"error":"not_enabled"}'। सिग्नल बताते हैं कि क्या मिला: test-keys, su-binary:/system/xbin/su, root-path:/data/adb/magisk, root-app:com.topjohnwu.magisk, system-writable, ro.debuggable=1, ro.secure=0, selinux-permissive, bootloader-unlocked, emulator:*, debugger-attached। ज़रूरत: Integrate चरण (चरण 3) में Device integrity checks चालू करें। इसमें कुछ मिलीसेकंड लगते हैं (फ़ाइलें पढ़ता है): इसे एक बार कॉल करें, लूप में नहीं।
function deviceCheck() {
if (!(window.WebToApk && window.WebToApk.getDeviceIntegrity)) return null;
const r = JSON.parse(WebToApk.getDeviceIntegrity());
if (r.error) return null; // 'not_enabled'
return r;
}
const d = deviceCheck();
if (d && d.rooted) {
showNotice('This phone looks rooted (' + d.signals.join(', ') + '). Some features are turned off.');
}
if (d && !d.installedFromPlay) console.log('installed by', d.installer || 'unknown (side-loaded)');अपना सिग्नेचर जाँचें - आपके ऐप की दोबारा पैक की हुई कॉपी किसी दूसरी key से साइन की होती है:
const MY_CERT = 'AB:CD:EF:…'; // Play Console → App integrity → App signing key SHA-256
const d = deviceCheck();
if (d && d.signatureSha256 && d.signatureSha256 !== MY_CERT) reportTamper();भरोसेमंद फ़ैसले के लिए किसी सर्वर से पूछें, Play Integrity टोकन के साथ (जवाब एक इवेंट के रूप में आता है)।
window.addEventListener('appmint:integrity', function (e) {
if (e.detail.error) { showNotice('Check failed: ' + e.detail.error); return; }
fetch('https://api.example.com/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: e.detail.token })
});
});
if (window.WebToApk && WebToApk.requestIntegrityToken) {
const nonce = crypto.randomUUID().replace(/-/g, '') + '0000'; // your server issues and checks this
WebToApk.requestIntegrityToken(nonce, '123456789012'); // your Google Cloud project number
}getLastCrash ब्रिज#
window.WebToApk.getLastCrash(): String
इस इंस्टॉल का दर्ज किया सबसे नया क्रैश (ऐप का किसी अनकॉट त्रुटि पर बंद होना, या पेज रेंडरर का क्रैश होना), JSON `{kind, message, stack, url, at, count, appVersion, user, fp}` के रूप में, या "" अगर कोई क्रैश नहीं हुआ (या Crash reports बंद हैं)। अपलोड के बाद भी रखा जाता है, ताकि पेज कह सके "माफ़ करें, पिछली बार ऐप बंद हो गया था"।
उदाहरण
इस इंस्टॉल का दर्ज किया सबसे नया क्रैश - ऐप का किसी अनकॉट त्रुटि पर बंद होना (kind: "native") या पेज रेंडरर का क्रैश होना (kind: "renderer") - JSON स्ट्रिंग के रूप में, या कोई क्रैश न हुआ हो तो ''।
लौटाता है: '{"kind","message","stack","url","at","count","appVersion","user","fp"}' या ''। बिल्ड में Crash reports चालू न हों तो '' भी। ज़रूरत: Integrate चरण (चरण 3) में Crash reports चालू हों।
क्रैश के बाद एक बार माफ़ी माँगें:
(function () {
if (!(window.WebToApk && window.WebToApk.getLastCrash)) return;
const raw = WebToApk.getLastCrash();
if (!raw) return;
const crash = JSON.parse(raw);
if (localStorage.getItem('seenCrash') === String(crash.at)) return; // shown already
localStorage.setItem('seenCrash', String(crash.at));
showBanner('Sorry, the app closed unexpectedly last time. We have been told about it.');
})();सेव किया हुआ क्रैश अपने सर्वर पर अपलोड करें, एक बार, उस ऐप वर्ज़न के साथ जिस पर वह हुआ।
(function () {
if (!(window.WebToApk && WebToApk.getLastCrash)) return;
const raw = WebToApk.getLastCrash();
if (!raw) return; // no crash, or Crash reports are off
const c = JSON.parse(raw);
if (localStorage.getItem('sentCrash') === String(c.at)) return;
fetch('https://api.example.com/crashes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ kind: c.kind, message: c.message, version: c.appVersion, count: c.count })
}).then(function () { localStorage.setItem('sentCrash', String(c.at)); });
})();किस्म के हिसाब से शाखा बनाएँ ताकि सही संदेश दिखे।
const raw = window.WebToApk && WebToApk.getLastCrash && WebToApk.getLastCrash();
if (raw) {
const c = JSON.parse(raw);
showBanner(c.kind === 'renderer'
? 'The page ran out of memory last time. Try closing other apps.'
: 'The app closed unexpectedly last time.');
}ध्यान दें: रिपोर्ट अपलोड होने के बाद भी यह बना रहता है, इसलिए संदेश एक ही बार दिखाने के लिए at की तुलना अपने सेव किए मान से करें। पेज की त्रुटियाँ और logError कॉल क्रैश नहीं हैं और यहाँ कभी नहीं दिखतीं।
logError ब्रिज#
window.WebToApk.logError(message: String, stack: String): Boolean
Crash reports: आपके पेज द्वारा पकड़ी गई (CAUGHT) त्रुटि (जैसे फ़ेल हुई पेमेंट कॉल, पार्स त्रुटि) दर्ज करता है, ताकि वह AppMint की Crash reports स्क्रीन में क्रैश के बगल में, ग्रुप और गिनती के साथ दिखे। [stack]: `err.stack` या ""। बिल्ड में Crash reports चालू न हों, दोनों खाली हों, या यही त्रुटि इस सेशन में कई बार दर्ज हो चुकी हो तो false लौटाता है। संदेश में कभी निजी डेटा (ईमेल, फ़ोन, टोकन) न डालें।
उदाहरण
आपके पेज द्वारा पकड़ी गई (CAUGHT) त्रुटि (जैसे फ़ेल हुई पेमेंट कॉल, JSON पार्स त्रुटि) दर्ज करता है, ताकि वह AppMint की Crash reports स्क्रीन (Update the App → आपका ऐप → Crash reports) में क्रैश के साथ ग्रुप होकर गिनी जाए। अनकॉट त्रुटियाँ और अनहैंडल्ड प्रॉमिस रिजेक्शन आपके लिए अपने आप दर्ज होते हैं; यह उनके लिए है जिन्हें आपने खुद संभाला।
लौटाता है: दर्ज होने पर true; बिल्ड में Crash reports चालू न हों, दोनों आर्गुमेंट खाली हों, या यही त्रुटि इस सेशन में कई बार दर्ज हो चुकी हो तो false। रिपोर्ट ऐप के अगली बार खुलने पर अपलोड होती हैं। ज़रूरत: बिल्ड करते समय Integrate चरण (चरण 3) में Crash reports चालू करें। संदेश में कभी निजी डेटा (ईमेल पता, फ़ोन नंबर, टोकन) न डालें।
संभाली गई त्रुटि दर्ज करें:
function logError(err, context) {
if (!(window.WebToApk && window.WebToApk.logError)) return false; // browser, or an older build
var msg = (context ? context + ': ' : '') + (err && err.message ? err.message : String(err));
return WebToApk.logError(msg, (err && err.stack) || '');
}
async function checkout(cart) {
try {
await payments.charge(cart.total);
} catch (err) {
logError(err, 'checkout'); // shows up as "Logged by your page"
showToast('Payment failed, please try again');
}
}फ़ेल हुई API कॉल रिपोर्ट करें, पते की query string के बिना (उसमें टोकन हो सकते हैं):
async function api(path) {
const res = await fetch('/api' + path);
if (!res.ok && window.WebToApk && WebToApk.logError) {
WebToApk.logError('API ' + res.status + ' ' + path.split('?')[0], '');
}
return res;
}यूज़र id के साथ पकड़ी गई त्रुटि दर्ज करें, ताकि किसी एक यूज़र की समस्याएँ बाद में ढूँढी जा सकें।
if (window.WebToApk && WebToApk.setCrashUserId) WebToApk.setCrashUserId('user_' + currentUser.id);
try {
JSON.parse(savedState);
} catch (err) {
if (window.WebToApk && WebToApk.logError) {
const logged = WebToApk.logError('state parse: ' + err.message, err.stack || '');
if (!logged) console.warn('not recorded (Crash reports off, or repeated too often)');
}
}ध्यान दें: एक ही कारण वाली रिपोर्टें तब भी ग्रुप होती हैं जब संदेश की संख्याएँ अलग हों (item 42 और item 7)। पेज का पता उसकी query और fragment के बिना सेव होता है।
requestIntegrityToken ब्रिज#
window.WebToApk.requestIntegrityToken(nonce: String, cloudProjectNumber: String): String
Google Play से Play Integrity टोकन माँगता है (क्लासिक रिक्वेस्ट)। [nonce]: URL-safe Base64, 16-500 अक्षर, जो आपका सर्वर इसी एक रिक्वेस्ट के लिए बनाता है। [cloudProjectNumber]: आपका Google Cloud प्रोजेक्ट नंबर, या बिल्ड में सेट नंबर इस्तेमाल करने के लिए "" (Play से इंस्टॉल ऐप दोनों खाली छोड़ सकता है)। यह तुरंत एक रिक्वेस्ट id लौटाता है (बिल्ड में Device integrity चालू न हो तो ""); जवाब `appmint:integrity` / `window.onAppMintIntegrity({requestId, token})` या `{requestId, error, code}` के रूप में आता है। टोकन अपने सर्वर को भेजें, जो उसे Google से डिकोड करता है (playintegrity.googleapis.com … :decodeIntegrityToken) और nonce जाँचता है।
उदाहरण
Google Play से Play Integrity टोकन माँगता है (क्लासिक रिक्वेस्ट)। टोकन फ़ोन पर अपारदर्शी है: आपका SERVER उसे Google को भेजता है, फ़ैसला पढ़ता है (असली ऐप? असली डिवाइस? लाइसेंस वाला?) और जाँचता है कि nonce वही है जो उसने जारी किया था। यही सर्वर जाँच जवाब को भरोसेमंद बनाती है।
लौटाता है: तुरंत एक रिक्वेस्ट id (बिल्ड में Device integrity चालू न हो तो '')। जवाब appmint:integrity इवेंट (और window.onAppMintIntegrity) के रूप में {requestId, token} या {requestId, error, code} के साथ आता है - त्रुटियाँ जैसे nonce_too_short, nonce_is_not_base64, network_error, play_store_not_found, cloud_project_number_is_invalid, too_many_requests। ज़रूरत: Integrate चरण (चरण 3) में Device integrity checks चालू हों, ऐप के लिए Play Integrity API चालू हो (Play Console → App integrity), और - जो ऐप Google Play से इंस्टॉल नहीं है उसके लिए - आपका Google Cloud प्रोजेक्ट नंबर, विज़ार्ड में या दूसरे आर्गुमेंट के रूप में ('' बिल्ड वाला इस्तेमाल करता है)।
async function integrityToken() {
// 1. Your server makes a one-time nonce: URL-safe Base64, 16-500 characters.
const { nonce } = await fetch('/api/integrity/nonce').then(r => r.json());
return new Promise((resolve, reject) => {
const id = WebToApk.requestIntegrityToken(nonce, '');
if (!id) return reject(new Error('not_enabled'));
window.addEventListener('appmint:integrity', function on(e) {
if (e.detail.requestId !== id) return;
window.removeEventListener('appmint:integrity', on);
e.detail.token ? resolve(e.detail.token) : reject(new Error(e.detail.error));
});
});
}
// 2. Your server decodes it with Google and decides.
const token = await integrityToken();
const verdict = await fetch('/api/integrity/verify', { method: 'POST', body: token }).then(r => r.json());सर्वर वाला चरण - लिंक किए गए Cloud प्रोजेक्ट के सर्विस अकाउंट से {"integrity_token": token} के साथ POST https://playintegrity.googleapis.com/v1/<your.package>:decodeIntegrityToken कॉल करें, फिर जाँचें:
const p = decoded.tokenPayloadExternal;
const ok = p.requestDetails.nonce === issuedNonce &&
p.appIntegrity.appRecognitionVerdict === 'PLAY_RECOGNIZED' &&
p.deviceIntegrity.deviceRecognitionVerdict.includes('MEETS_DEVICE_INTEGRITY');कॉलबैक वाला तरीका, उस कोड के लिए जो इवेंट नहीं सुन सकता:
window.onAppMintIntegrity = function (d) {
if (d.error) console.warn('integrity failed', d.error, d.code);
else sendToServer(d.token);
};
WebToApk.requestIntegrityToken(nonceFromServer, '123456789012');setCrashUserId ब्रिज#
window.WebToApk.setCrashUserId(id: String): Boolean
क्रैश रिपोर्ट: साइन-इन किए हुए उपयोगकर्ता के लिए आपका अपना id (अक्षर, अंक और _ @ . : + -, 128 तक), जो अब से रिपोर्ट के साथ जुड़ जाएगा ताकि आप प्रभावित उपयोगकर्ताओं की गिनती कर सकें और किसी एक उपयोगकर्ता के क्रैश ढूँढ सकें। "" इसे हटा देता है। यह कभी ईमेल पता या फ़ोन नंबर न हो। दूसरे अक्षरों वाले id पर, या क्रैश रिपोर्ट बंद होने पर false लौटाता है।
उदाहरण
अब से क्रैश रिपोर्ट में साइन-इन यूज़र की आपकी अपनी id जोड़ देता है, ताकि Crash reports स्क्रीन गिन सके कि किसी समस्या से कितने यूज़र प्रभावित हैं। साइन इन के बाद कॉल करें; साइन-आउट पर इसे '' के साथ कॉल करें।
लौटाता है: सेव होने पर true; ऐसी id के लिए false जिसमें अक्षरों, अंकों और _ @ . : + - के अलावा कुछ और हो (या 128 से लंबी हो), या जब बिल्ड में Crash reports चालू न हों। ज़रूरत: Integrate चरण (चरण 3) में Crash reports चालू हों। अपनी डेटाबेस id इस्तेमाल करें, कभी ईमेल पता या फ़ोन नंबर नहीं।
function crashUser(id) {
if (!(window.WebToApk && window.WebToApk.setCrashUserId)) return false;
return WebToApk.setCrashUserId(id);
}
async function signIn(email, password) {
const user = await myBackend.signIn(email, password);
crashUser(user.id); // e.g. 'u_42'
}
function signOut() {
crashUser(''); // later reports carry no user
}नकारी गई id, या Crash reports के बिना बना बिल्ड, false लौटाता है:
function setCrashUser(id) {
if (!(window.WebToApk && WebToApk.setCrashUserId)) return;
if (!WebToApk.setCrashUserId(id)) {
console.warn('Crash user id not saved: bad characters, over 128 long, or Crash reports are off');
}
}
setCrashUser('u_42'); // your database id, never an e-mail addressरीस्टार्ट के बाद यूज़र को आख़िरी क्रैश के बारे में बताएँ:
if (window.WebToApk && WebToApk.getLastCrash) {
const raw = WebToApk.getLastCrash(); // '' when there has been none
if (raw) {
const c = JSON.parse(raw);
showMessage('Sorry, the app closed last time (' + c.kind + ').');
}
}ध्यान दें: id फ़ोन पर तब तक रहती है जब तक आप उसे बदलें, और सिर्फ़ क्रैश रिपोर्ट के अंदर भेजी जाती है।