Créer un APK gratuit

API du pont JavaScript

Rapports de plantage et intégrité de l'appareil

Les erreurs consignées par la page, le dernier plantage natif, et si le téléphone semble rooté ou modifié, avec un jeton Play Integrity facultatif pour votre serveur.

AppMint.integrity assistant#

AppMint.integrity.check() / AppMint.integrity.token(nonce, cloudProjectNumber?)

Exemple

Versions Promise de getDeviceIntegrity et requestIntegrityToken, présentes avant le premier script de votre page lorsque la compilation a activé les Vérifications de l'intégrité de l'appareil (étape Intégration, étape 3).

Renvoie : check() se résout avec l'objet des heuristiques ({rooted, emulator, debuggerAttached, signals, installer, installedFromPlay, signatureSha256, method: 'heuristic'}) ; token(nonce) se résout avec la chaîne du jeton Play Integrity, ou est rejeté avec une Error dont le message est le nom de l'erreur (not_enabled, nonce_too_short, network_error, …) et code le code d'erreur Play. Les heuristiques sont des indices ; seul le décodage du jeton par votre serveur est un verdict.

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);
  }
}

Avertissez lorsque l'application est une copie reconditionnée :

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);
}

Gérez un échec de jeton par son nom :

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 pont#

window.WebToApk.getDeviceIntegrity(): String

HEURISTIQUES locales de root / émulateur / débogueur (voir DeviceIntegrity) en JSON : `{rooted, emulator, debuggerAttached, signals:[…], installer, installedFromPlay, signatureSha256, method:"heuristic", sdk}`. Un résultat propre signifie « aucun signe trouvé », pas une preuve - pour un verdict fiable, envoyez un jeton Play Integrity à votre serveur (requestIntegrityToken). `{"error":"not_enabled"}` sauf si la compilation a activé l'intégrité de l'appareil. Bloque quelques millisecondes (il lit des fichiers) : appelez-le une seule fois.

Exemple

HEURISTIQUES locales de root, d'émulateur et de débogueur sous forme de chaîne JSON. Un résultat propre signifie « aucun signe trouvé », pas une preuve : un utilisateur déterminé avec un module de masquage du root passe. Utilisez-le pour avertir ou ajouter une vérification ; pour un verdict fiable, envoyez un jeton Play Integrity à votre serveur (requestIntegrityToken).

Renvoie : '{"rooted":false,"emulator":false,"debuggerAttached":false,"signals":[],"installer":"com.android.vending","installedFromPlay":true,"signatureSha256":"AB:CD:…","method":"heuristic","sdk":34}', ou '{"error":"not_enabled"}'. Les signaux indiquent ce qui a été trouvé : 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. Nécessite : activez les Vérifications de l'intégrité de l'appareil à l'étape Intégration (étape 3). Cela prend quelques millisecondes (il lit des fichiers) : appelez-le une seule fois, pas dans une boucle.

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)');

Vérifiez votre propre signature - une copie reconditionnée de votre application est signée avec une autre clé :

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();

Demandez à un serveur un verdict fiable avec un jeton Play Integrity (la réponse arrive sous forme d'événement).

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 pont#

window.WebToApk.getLastCrash(): String

Le plantage le plus récent enregistré par cette installation (l'application qui se ferme sur une erreur non interceptée, ou le moteur de rendu de la page qui plante), en JSON `{kind, message, stack, url, at, count, appVersion, user, fp}`, ou "" s'il n'y en a pas eu (ou si les rapports de plantage sont désactivés). Conservé après l'envoi, ce qui permet à une page de dire « désolé, l'application s'est fermée la dernière fois ».

Exemple

Le plantage le plus récent enregistré par cette installation - l'application qui se ferme sur une erreur non interceptée (kind: "native") ou le moteur de rendu de la page qui plante (kind: "renderer") - sous forme de chaîne JSON, ou '' s'il n'y en a pas eu.

Renvoie : '{"kind","message","stack","url","at","count","appVersion","user","fp"}' ou ''. Aussi '' lorsque la compilation n'a pas activé les rapports de plantage. Nécessite : Rapports de plantage activés à l'étape Intégration (étape 3).

Excusez-vous une seule fois après un plantage :

(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.');
})();

Envoyez le plantage enregistré à votre propre serveur une seule fois, avec la version de l'application sur laquelle il s'est produit.

(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)); });
})();

Branchez selon le type pour donner un message adapté.

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.');
}

Remarques : il reste après l'envoi du rapport ; comparez donc at avec ce que vous avez stocké pour n'afficher le message qu'une fois. Les erreurs de page et les appels logError ne sont pas des plantages et n'apparaissent jamais ici.

logError pont#

window.WebToApk.logError(message: String, stack: String): Boolean

Rapports de plantage : enregistre une erreur que votre page a INTERCEPTÉE (un appel de paiement échoué, une erreur d'analyse) afin qu'elle apparaisse dans l'écran Rapports de plantage d'AppMint à côté des plantages, regroupée et comptabilisée. [stack] : `err.stack` ou "". Renvoie false lorsque la compilation n'a pas activé les rapports de plantage, lorsque les deux sont vides, ou lorsque la même erreur a déjà été consignée de nombreuses fois pendant cette session. Ne mettez jamais de données personnelles (e-mail, téléphone, jetons) dans le message.

Exemple

Enregistre une erreur que votre page a INTERCEPTÉE (un appel de paiement échoué, une erreur d'analyse JSON) afin qu'elle apparaisse dans l'écran Rapports de plantage d'AppMint (Mettre à jour l'application → votre application → Rapports de plantage), regroupée avec les plantages et comptabilisée. Les erreurs non interceptées et les rejets de promesse non gérés sont enregistrés pour vous ; ceci sert pour celles que vous avez gérées.

Renvoie : true lorsque l'enregistrement a réussi ; false lorsque la compilation n'a pas activé les rapports de plantage, lorsque les deux arguments sont vides, ou lorsque la même erreur a déjà été consignée de nombreuses fois pendant cette session. Les rapports sont envoyés à la prochaine ouverture de l'application. Nécessite : activez les Rapports de plantage à l'étape Intégration (étape 3) lors de la compilation. Ne mettez jamais de données personnelles (adresse e-mail, numéro de téléphone, jetons) dans le message.

Consignez une erreur gérée :

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');
  }
}

Signalez les appels d'API échoués sans la chaîne de requête de l'adresse (elle peut contenir des jetons) :

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;
}

Consignez une erreur interceptée avec un identifiant utilisateur, afin de retrouver plus tard les problèmes d'un utilisateur.

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)');
  }
}

Remarques : les rapports ayant la même cause sont regroupés même lorsque les nombres dans le message diffèrent (item 42 et item 7). L'adresse de la page est stockée sans sa chaîne de requête ni son fragment.

requestIntegrityToken pont#

window.WebToApk.requestIntegrityToken(nonce: String, cloudProjectNumber: String): String

Demande à Google Play un jeton Play Integrity (requête classique). [nonce] : Base64 compatible URL, de 16 à 500 caractères, généré par VOTRE serveur pour cette unique requête. [cloudProjectNumber] : le numéro de votre projet Google Cloud, ou "" pour utiliser celui défini dans la compilation (une application installée depuis Play peut laisser les deux vides). Renvoie immédiatement un identifiant de requête ("" lorsque la compilation n'a pas activé l'intégrité de l'appareil) ; la réponse arrive sous la forme `appmint:integrity` / `window.onAppMintIntegrity({requestId, token})` ou `{requestId, error, code}`. Envoyez le jeton à votre serveur, qui le décode avec Google (playintegrity.googleapis.com … :decodeIntegrityToken) et vérifie le nonce.

Exemple

Demande à Google Play un jeton Play Integrity (requête classique). Le jeton est opaque sur le téléphone : votre SERVEUR l'envoie à Google, lit le verdict (application authentique ? appareil authentique ? sous licence ?) et vérifie que le nonce est celui qu'il a émis. C'est cette vérification côté serveur qui rend la réponse fiable.

Renvoie : un identifiant de requête immédiatement ('' lorsque la compilation n'a pas activé l'intégrité de l'appareil). La réponse arrive sous la forme de l'événement appmint:integrity (et window.onAppMintIntegrity) avec {requestId, token} ou {requestId, error, code} - des erreurs telles que nonce_too_short, nonce_is_not_base64, network_error, play_store_not_found, cloud_project_number_is_invalid, too_many_requests. Nécessite : Vérifications de l'intégrité de l'appareil activées à l'étape Intégration (étape 3), l'API Play Integrity activée pour l'application (Play Console → Intégrité de l'application) et - pour une application NON installée depuis Google Play - le numéro de votre projet Google Cloud, dans l'assistant ou en second argument ('' utilise celui de la compilation).

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());

L'étape serveur - appelez POST https://playintegrity.googleapis.com/v1/<your.package>:decodeIntegrityToken avec {"integrity_token": token} en utilisant un compte de service du projet Cloud associé, puis vérifiez :

const p = decoded.tokenPayloadExternal;
const ok = p.requestDetails.nonce === issuedNonce &&
  p.appIntegrity.appRecognitionVerdict === 'PLAY_RECOGNIZED' &&
  p.deviceIntegrity.deviceRecognitionVerdict.includes('MEETS_DEVICE_INTEGRITY');

La forme avec rappel, pour le code qui ne peut pas écouter les événements :

window.onAppMintIntegrity = function (d) {
  if (d.error) console.warn('integrity failed', d.error, d.code);
  else sendToServer(d.token);
};
WebToApk.requestIntegrityToken(nonceFromServer, '123456789012');

setCrashUserId pont#

window.WebToApk.setCrashUserId(id: String): Boolean

Rapports de plantage : VOTRE identifiant de l'utilisateur connecté (lettres, chiffres et _ @ . : + -, jusqu'à 128), associé aux rapports à partir de maintenant afin que vous puissiez compter les utilisateurs touchés et retrouver les plantages d'un utilisateur. "" l'efface. Jamais une adresse e-mail ni un numéro de téléphone. Renvoie false pour un identifiant contenant d'autres caractères, ou lorsque les rapports de plantage sont désactivés.

Exemple

Associe VOTRE identifiant de l'utilisateur connecté aux rapports de plantage à partir de maintenant, afin que l'écran Rapports de plantage puisse compter combien d'utilisateurs un problème touche. Appelez-le après la connexion ; appelez-le avec '' à la déconnexion.

Renvoie : true lorsque l'enregistrement a réussi ; false pour un identifiant contenant des caractères autres que des lettres, des chiffres et _ @ . : + - (ou de plus de 128), ou lorsque la compilation n'a pas activé les rapports de plantage. Nécessite : Rapports de plantage activés à l'étape Intégration (étape 3). Utilisez l'identifiant de votre base de données, jamais une adresse e-mail ni un numéro de téléphone.

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
}

Un identifiant refusé, ou une compilation sans rapports de plantage, renvoie 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

Informez l'utilisateur du dernier plantage après un redémarrage :

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 + ').');
  }
}

Remarques : l'identifiant reste sur le téléphone jusqu'à ce que vous le changiez et n'est envoyé que dans les rapports de plantage.

Généré à partir de l'environnement d'exécution de l'application et de ses fichiers d'exemple à chaque compilation de la documentation. Lire en Markdown · Toutes les familles.

Vérifié par rapport au pont publié le 22/10/2026.