JavaScript köprü API'si
Çökme raporları ve cihaz bütünlüğü
Sayfanın günlüğe yazdığı hatalar, son yerel çökme ve telefonun root'lu veya değiştirilmiş görünüp görünmediği; sunucunuz için isteğe bağlı bir Play Integrity belirteciyle.
AppMint.integrity yardımcı#
AppMint.integrity.check() / AppMint.integrity.token(nonce, cloudProjectNumber?)
Örnek
getDeviceIntegrity ve requestIntegrityToken işlevlerinin Promise sürümleri; derleme Cihaz bütünlüğü denetimlerini (Entegrasyon adımı, 3. Adım) açtığında sayfanızın ilk betiğinden önce bulunur.
Döndürür: check() sezgi nesnesiyle ({rooted, emulator, debuggerAttached, signals, installer, installedFromPlay, signatureSha256, method: 'heuristic'}) tamamlanır; token(nonce) Play Integrity belirteci dizesiyle tamamlanır veya iletisi hata adı (not_enabled, nonce_too_short, network_error, …) olan ve code Play hata kodunu taşıyan bir Error ile reddedilir. Sezgiler ipucudur; yalnızca sunucunuzun belirteci çözmesi bir karardır.
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);
}
}Uygulama yeniden paketlenmiş bir kopya olduğunda uyarın:
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);
}Bir belirteç hatasını adıyla ele alın:
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 köprü#
window.WebToApk.getDeviceIntegrity(): String
Yerel root / emülatör / hata ayıklayıcı SEZGİLERİ (bkz. DeviceIntegrity) JSON olarak: `{rooted, emulator, debuggerAttached, signals:[…], installer, installedFromPlay, signatureSha256, method:"heuristic", sdk}`. Temiz bir sonuç "işaret bulunamadı" demektir, kanıt değil - güvenebileceğiniz bir karar için sunucunuza bir Play Integrity belirteci gönderin (requestIntegrityToken). Derleme Cihaz bütünlüğünü açmadıysa `{"error":"not_enabled"}`. Birkaç milisaniye bekletir (dosyaları okur): bir kez çağırın.
Örnek
JSON dizesi olarak yerel root, emülatör ve hata ayıklayıcı SEZGİLERİ. Temiz bir sonuç "işaret bulunamadı" demektir, kanıt değil: root'u gizleyen bir modül kullanan kararlı bir kullanıcı geçer. Uyarmak veya bir denetim eklemek için kullanın; güvenebileceğiniz bir karar için sunucunuza bir Play Integrity belirteci gönderin (requestIntegrityToken).
Döndürür: '{"rooted":false,"emulator":false,"debuggerAttached":false,"signals":[],"installer":"com.android.vending","installedFromPlay":true,"signatureSha256":"AB:CD:…","method":"heuristic","sdk":34}' veya '{"error":"not_enabled"}'. Sinyaller neyin bulunduğunu adlandırır: 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. Gerekenler: Entegrasyon adımında (3. Adım) Cihaz bütünlüğü denetimlerini açın. Birkaç milisaniye sürer (dosyaları okur): bir kez çağırın, döngüde değil.
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)');Kendi imzanızı denetleyin - uygulamanızın yeniden paketlenmiş bir kopyası başka bir anahtarla imzalanır:
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();Güvenebileceğiniz bir karar için bir sunucuya sorun, bir Play Integrity belirteciyle (yanıt bir olay olarak gelir).
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 köprü#
window.WebToApk.getLastCrash(): String
Bu kurulumun kaydettiği en yeni çökme (uygulamanın yakalanmayan bir hatayla kapanması veya sayfa oluşturucunun çökmesi), JSON olarak `{kind, message, stack, url, at, count, appVersion, user, fp}`, ya da hiç olmadıysa (veya Çökme raporları kapalıysa) "". Yüklemeden sonra da saklanır, böylece bir sayfa "üzgünüz, uygulama geçen sefer kapandı" diyebilir.
Örnek
Bu kurulumun kaydettiği en yeni çökme - uygulamanın yakalanmayan bir hatayla kapanması (kind: "native") veya sayfa oluşturucunun çökmesi (kind: "renderer") - JSON dizesi olarak, ya da hiç olmadıysa ''.
Döndürür: '{"kind","message","stack","url","at","count","appVersion","user","fp"}' veya ''. Derleme Çökme raporlarını açmadıysa ayrıca ''. Gerekenler: Entegrasyon adımında (3. Adım) Çökme raporları açık.
Bir çökmeden sonra bir kez özür dileyin:
(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.');
})();Saklanan çökmeyi kendi sunucunuza yükleyin, bir kez, gerçekleştiği uygulama sürümüyle.
(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)); });
})();Uygun bir mesaj vermek için türüne göre dallanın.
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.');
}Notlar: rapor yüklendikten sonra da kalır; bu yüzden mesajı bir kez göstermek için at değerini sakladığınızla karşılaştırın. Sayfa hataları ve logError çağrıları çökme değildir ve burada asla görünmez.
logError köprü#
window.WebToApk.logError(message: String, stack: String): Boolean
Çökme raporları: sayfanızın YAKALADIĞI bir hatayı (başarısız ödeme çağrısı, ayrıştırma hatası) kaydeder; böylece AppMint'in Çökme raporları ekranında çökmelerin yanında gruplanıp sayılarak görünür. [stack]: `err.stack` veya "". Derleme Çökme raporlarını açmadıysa, ikisi de boşsa veya aynı hata bu oturumda zaten çok kez günlüğe yazıldıysa false döndürür. Mesaja asla kişisel veri (e-posta, telefon, belirteç) koymayın.
Örnek
Sayfanızın YAKALADIĞI bir hatayı (başarısız ödeme çağrısı, JSON ayrıştırma hatası) kaydeder; böylece AppMint'in Çökme raporları ekranında (Uygulamayı Güncelle → uygulamanız → Çökme raporları) çökmelerle gruplanıp sayılarak görünür. Yakalanmayan hatalar ve işlenmeyen promise reddedilmeleri sizin için kaydedilir; bu, sizin ele aldıklarınız içindir.
Döndürür: kaydedildiğinde true; derleme Çökme raporlarını açmadıysa, iki argüman da boşsa veya aynı hata bu oturumda zaten çok kez günlüğe yazıldıysa false. Raporlar uygulama bir sonraki açıldığında yüklenir. Gerekenler: derleme sırasında Entegrasyon adımında (3. Adım) Çökme raporları seçeneğini açın. Mesaja asla kişisel veri (e-posta adresi, telefon numarası, belirteç) koymayın.
Ele alınmış bir hatayı günlüğe yazın:
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');
}
}Başarısız API çağrılarını adresin sorgu dizesi olmadan bildirin (belirteç taşıyabilir):
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;
}Yakalanan bir hatayı kullanıcı kimliğiyle günlüğe yazın; böylece bir kullanıcının sorunları sonradan bulunabilir.
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)');
}
}Notlar: mesajdaki sayılar farklı olsa bile aynı nedene sahip raporlar gruplanır (item 42 ve item 7). Sayfa adresi sorgu ve parça kısmı olmadan saklanır.
requestIntegrityToken köprü#
window.WebToApk.requestIntegrityToken(nonce: String, cloudProjectNumber: String): String
Google Play'den bir Play Integrity belirteci ister (klasik istek). [nonce]: URL-güvenli Base64, 16-500 karakter, bu tek istek için SİZİN sunucunuz tarafından üretilir. [cloudProjectNumber]: Google Cloud proje numaranız veya derlemede ayarlananı kullanmak için "" (Play'den yüklenen bir uygulama ikisini de boş bırakabilir). Hemen bir istek kimliği döndürür (derleme Cihaz bütünlüğünü açmadıysa ""); yanıt `appmint:integrity` / `window.onAppMintIntegrity({requestId, token})` veya `{requestId, error, code}` olarak gelir. Belirteci sunucunuza gönderin; sunucu onu Google ile çözer (playintegrity.googleapis.com … :decodeIntegrityToken) ve nonce değerini denetler.
Örnek
Google Play'den bir Play Integrity belirteci ister (klasik istek). Belirteç telefonda opaktır: SUNUCUNUZ onu Google'a gönderir, kararı okur (gerçek uygulama mı? gerçek cihaz mı? lisanslı mı?) ve nonce değerinin kendi verdiği olduğunu denetler. Yanıtı güvenilir yapan şey bu sunucu denetimidir.
Döndürür: hemen bir istek kimliği (derleme Cihaz bütünlüğünü açmadıysa ''). Yanıt appmint:integrity olayı olarak (ve window.onAppMintIntegrity) {requestId, token} veya {requestId, error, code} ile gelir - nonce_too_short, nonce_is_not_base64, network_error, play_store_not_found, cloud_project_number_is_invalid, too_many_requests gibi hatalar. Gerekenler: Entegrasyon adımında (3. Adım) Cihaz bütünlüğü denetimleri açık, uygulama için Play Integrity API etkin (Play Console → Uygulama bütünlüğü) ve - Google Play'den YÜKLENMEMİŞ bir uygulama için - sihirbazda veya ikinci argüman olarak Google Cloud proje numaranız ('' derlemedekini kullanır).
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());Sunucu adımı - bağlı Cloud projesinin bir hizmet hesabıyla {"integrity_token": token} ile POST https://playintegrity.googleapis.com/v1/<your.package>:decodeIntegrityToken çağrısı yapın, sonra denetleyin:
const p = decoded.tokenPayloadExternal;
const ok = p.requestDetails.nonce === issuedNonce &&
p.appIntegrity.appRecognitionVerdict === 'PLAY_RECOGNIZED' &&
p.deviceIntegrity.deviceRecognitionVerdict.includes('MEETS_DEVICE_INTEGRITY');Geri çağrı biçimi, olayları dinleyemeyen kod için:
window.onAppMintIntegrity = function (d) {
if (d.error) console.warn('integrity failed', d.error, d.code);
else sendToServer(d.token);
};
WebToApk.requestIntegrityToken(nonceFromServer, '123456789012');setCrashUserId köprü#
window.WebToApk.setCrashUserId(id: String): Boolean
Çökme raporları: oturum açmış kullanıcı için SİZİN kimliğiniz (harfler, rakamlar ve _ @ . : + -, en fazla 128), bundan sonraki raporlara eklenir; böylece etkilenen kullanıcıları sayabilir ve bir kullanıcının çökmelerini bulabilirsiniz. "" temizler. Asla e-posta adresi veya telefon numarası değil. Başka karakterler içeren bir kimlik için veya Çökme raporları kapalıysa false döndürür.
Örnek
Oturum açmış kullanıcı için SİZİN kimliğinizi bundan sonraki çökme raporlarına ekler; böylece Çökme raporları ekranı bir sorunun kaç kullanıcıyı etkilediğini sayabilir. Oturum açtıktan sonra çağırın; oturumu kapatırken '' ile çağırın.
Döndürür: kaydedildiğinde true; harf, rakam ve _ @ . : + - dışında karakterler içeren (veya 128'den uzun) bir kimlik için ya da derleme Çökme raporlarını açmadıysa false. Gerekenler: Entegrasyon adımında (3. Adım) Çökme raporları açık. Veritabanı kimliğinizi kullanın, asla e-posta adresi veya telefon numarası değil.
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
}Reddedilen bir kimlik veya Çökme raporsuz bir derleme false döndürür:
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 addressYeniden başlatmadan sonra kullanıcıya son çökmeyi bildirin:
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 + ').');
}
}Notlar: kimlik, siz değiştirene kadar telefonda kalır ve yalnızca çökme raporlarının içinde gönderilir.