Créer un APK gratuit

API du pont JavaScript

Informations sur l'appareil et l'application

Quel est ce téléphone, quelle compilation, un identifiant stable par installation, et les API standard de localisation et de caméra.

getUserMedia standard web#

navigator.mediaDevices.getUserMedia — camera and microphone

Exemple

L'API web standard de la caméra et du microphone fonctionne dans l'application. L'application affiche l'invite d'autorisation propre à Android et transmet le flux à la page.

Retour : une Promise d'un MediaStream, ou un rejet (NotAllowedError quand l'utilisateur a refusé ou que l'option était désactivée à la compilation, NotFoundError quand aucun appareil de ce type n'existe). Nécessite : Caméra et/ou Micro à l'étape 4 (Accès). Quand vous compilez depuis un ZIP ou une page HTML et que votre code appelle getUserMedia({ video: … }) ou { audio: … } (ou utilise <input capture>, BarcodeDetector, SpeechRecognition), la compilation repère l'appel et ajoute l'autorisation correspondante pour vous ; { video: false } n'ajoute pas la Caméra. Pour une compilation de site web (URL), cochez-les vous-même.

Afficher la caméra arrière dans un élément vidéo.

<video id="preview" autoplay playsinline muted></video>
<button id="startCam">Start camera</button>
<script>
document.getElementById('startCam').addEventListener('click', async function () {
  if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
    showMessage('Camera is not available here.');
    return;
  }
  try {
    var stream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: 'environment' },   // 'user' = front camera
      audio: false
    });
    document.getElementById('preview').srcObject = stream;
  } catch (err) {
    if (err.name === 'NotAllowedError') showMessage('Camera permission was refused.');
    else showMessage('Camera error: ' + err.name);
  }
});
</script>

Enregistrer une note vocale avec le seul microphone.

async function recordVoice(seconds) {
  var stream;
  try {
    stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
  } catch (err) {
    showMessage('Microphone permission was refused.');
    return;
  }
  var chunks = [];
  var rec = new MediaRecorder(stream);
  rec.ondataavailable = function (e) { chunks.push(e.data); };
  rec.onstop = function () {
    stream.getTracks().forEach(function (t) { t.stop(); });   // turn the mic off
    var blob = new Blob(chunks, { type: rec.mimeType });
    document.getElementById('player').src = URL.createObjectURL(blob);
  };
  rec.start();
  setTimeout(function () { rec.stop(); }, seconds * 1000);
}

Prendre une photo depuis le flux de la caméra et libérer la caméra.

async function snapshot() {
  if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) return null;
  let stream;
  try {
    stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } });
  } catch (err) {
    showMessage(err.name === 'NotFoundError' ? 'No camera on this device.' : 'Camera permission was refused.');
    return null;
  }
  const video = document.createElement('video');
  video.srcObject = stream;
  await video.play();
  const canvas = document.createElement('canvas');
  canvas.width = video.videoWidth; canvas.height = video.videoHeight;
  canvas.getContext('2d').drawImage(video, 0, 0);
  stream.getTracks().forEach(function (t) { t.stop(); });
  return canvas.toDataURL('image/jpeg', 0.9);
}

Remarques : Quand vous demandez la caméra et le micro ensemble, l'application demande les deux à l'utilisateur en une fois. Si l'option était désactivée à la compilation, la demande est refusée sans aucune invite - compilez de nouveau avec l'option activée. Arrêtez toujours les pistes quand vous avez terminé, sinon le voyant de la caméra/du micro reste allumé. La vidéo DRM (Widevine) est autorisée sans aucune option. La caméra et le microphone sont sensibles : déclarez-les dans votre formulaire Sécurité des données Play.

IdleDetector standard web#

new IdleDetector() — Idle Detection

Exemple

L'API standard Idle Detection : savoir quand l'utilisateur cesse d'utiliser le téléphone (aucune saisie depuis un moment) ou verrouille l'écran, et quand il revient - pour un statut « absent », la mise en pause d'une session en direct ou le verrouillage d'un coffre.

Retour : IdleDetector.requestPermission() → 'granted' ou 'denied' (nécessite un appui) ; detector.start({threshold, signal}) → Promise (rejette NotAllowedError sans l'autorisation, TypeError sous 60000 ms) ; puis userState ('active'/'idle'), screenState ('locked'/'unlocked') et un événement change. Nécessite : aucune option à activer et aucune autorisation Android.

document.getElementById('away').addEventListener('click', async () => {
  if (!('IdleDetector' in window)) return;
  if ((await IdleDetector.requestPermission()) !== 'granted') return;
  const ac = new AbortController();
  const idle = new IdleDetector();
  idle.addEventListener('change', () => {
    setStatus(idle.userState === 'idle' || idle.screenState === 'locked' ? 'Away' : 'Online');
  });
  await idle.start({ threshold: 5 * 60000, signal: ac.signal });
  stopButton.onclick = () => ac.abort();
});

Reprendre le suivi au prochain lancement sans nouvel appui, une fois que l'utilisateur a déjà accepté :

async function watchAwayIfAllowed() {
  if (!('IdleDetector' in window)) return;
  const perm = await navigator.permissions.query({ name: 'idle-detection' });
  if (perm.state !== 'granted') return;          // 'prompt' until requestPermission() ran from a tap
  const idle = new IdleDetector();
  idle.onchange = () => {
    if (idle.screenState === 'locked') pauseLiveSession();
    else if (idle.userState === 'active') resumeLiveSession();
  };
  await idle.start({ threshold: 60000 });         // 1 minute is the smallest threshold
}
watchAwayIfAllowed();

Laisser l'utilisateur modifier le délai de verrouillage automatique : arrêter le détecteur en cours, en démarrer un nouveau et signaler les vraies erreurs :

let vaultWatch = null;
async function setAutoLock(minutes) {
  if (vaultWatch) vaultWatch.abort();             // aborting stops the old detector
  vaultWatch = new AbortController();
  const idle = new IdleDetector();
  idle.addEventListener('change', () => {
    if (idle.userState === 'idle' || idle.screenState === 'locked') lockVault();
  });
  try {
    await idle.start({ threshold: minutes * 60000, signal: vaultWatch.signal });
  } catch (err) {
    if (err.name === 'NotAllowedError') showMessage('Tap "Enable auto-lock" first.');
    else if (err instanceof TypeError) showMessage('Pick at least 1 minute.');
    else throw err;
  }
}

Remarques : dans l'application, « input » désigne la saisie de l'utilisateur dans CETTE application tant qu'elle est au premier plan ; quand une autre application est au premier plan avec l'écran allumé, l'utilisateur est considéré comme actif (une application ne voit pas la saisie des autres - Chrome sur Android répond de la même façon) ; une fois l'écran éteint ou verrouillé, le temps d'inactivité court depuis le dernier toucher. L'autorisation est mémorisée, et navigator.permissions.query({name:'idle-detection'}) y répond. Transports internes : __idleState, __idlePermission ; les changements de verrouillage/déverrouillage arrivent sous la forme appmint:idle. Les applications créées par IA utilisent watchIdle().

navigator.geolocation standard web#

navigator.geolocation

Exemple

L'API web standard de localisation fonctionne dans l'application. L'application affiche l'invite de localisation propre à Android et transmet la réponse à la page.

Retour : les callbacks W3C habituels : un GeolocationPosition (coords.latitude, coords.longitude, coords.accuracy, …) ou un GeolocationPositionError (code 1 = autorisation refusée, 2 = indisponible, 3 = délai dépassé). Nécessite : GPS (précis) et/ou Localisation (approximative) à l'étape 4 (Accès). Quand vous compilez depuis un ZIP ou une page HTML et que votre code appelle navigator.geolocation, getCurrentPosition ou watchPosition, la compilation repère cet appel et ajoute la localisation pour vous. Pour une compilation de site web (URL), cochez-la vous-même.

Obtenir la position une seule fois.

function whereAmI() {
  if (!('geolocation' in navigator)) { showMessage('Location is not available here.'); return; }

  navigator.geolocation.getCurrentPosition(
    function (pos) {
      showMessage('You are at ' + pos.coords.latitude.toFixed(5) + ', ' +
                  pos.coords.longitude.toFixed(5) + ' (±' + Math.round(pos.coords.accuracy) + ' m)');
    },
    function (err) {
      if (err.code === 1) showMessage('Location permission was refused.');
      else if (err.code === 3) showMessage('Location timed out. Try again outside.');
      else showMessage('Location is unavailable. Is location turned on in phone settings?');
    },
    { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 }
  );
}

Suivre l'utilisateur pendant ses déplacements (suivi de course, carte de livraison). Arrêtez le suivi à la fermeture de l'écran.

var watchId = null;

function startTracking() {
  if (!('geolocation' in navigator)) return;
  watchId = navigator.geolocation.watchPosition(
    function (pos) { addPointToMap(pos.coords.latitude, pos.coords.longitude); },
    function (err) { showMessage('Tracking stopped: ' + err.message); },
    { enableHighAccuracy: true }
  );
}

function stopTracking() {
  if (watchId !== null) { navigator.geolocation.clearWatch(watchId); watchId = null; }
}

Vérifier d'abord l'autorisation, puis demander une position seulement quand on peut y répondre :

async function locateIfAllowed() {
  if (!('geolocation' in navigator)) return;
  if (navigator.permissions) {
    const s = await navigator.permissions.query({ name: 'geolocation' });
    if (s.state === 'denied') { showMessage('Location is off for this app. Turn it on in Settings.'); return; }
  }
  navigator.geolocation.getCurrentPosition(
    (pos) => centerMap(pos.coords.latitude, pos.coords.longitude),
    (err) => showMessage('Location error ' + err.code + ': ' + err.message),
    { timeout: 10000 }
  );
}

Remarques : Avec GPS activé, l'application demande ensemble la localisation précise et approximative ; sur Android 12+, l'utilisateur peut choisir « Approximative », ce qui fonctionne quand même (précision moindre). Avec seulement Localisation activée, seule la localisation approximative est demandée. Si aucune des deux n'est activée, chaque appel échoue avec le code 1 et sans invite. Une fois l'autorisation accordée, les appels suivants reçoivent une réponse sans invite. Le réglage de localisation du téléphone doit aussi être activé. La localisation est une donnée personnelle : déclarez-la dans votre formulaire Sécurité des données Play ; la localisation en arrière-plan n'est pas fournie.

navigator.getInstalledRelatedApps standard web#

navigator.getInstalledRelatedApps()

Exemple

L'API standard des applications associées : indique à la page lesquelles de ses propres applications Android (listées dans son manifeste d'application web) sont installées - pour masquer une bannière « Installez notre autre application », ou y créer un lien direct.

Retour : Promise d'un tableau des entrées related_applications du manifeste qui sont installées : { platform: 'play', id, url?, version? }. Nécessite : un <link rel="manifest"> dont le manifeste liste les applications avec "platform": "play" et "id": "<package name>", inclus dans l'application pour que la compilation puisse les déclarer (Android ne laisse une application voir que les packages qu'elle nomme).

// manifest.webmanifest: { "related_applications": [{ "platform": "play", "id": "com.example.scanner" }] }
const apps = await navigator.getInstalledRelatedApps();
const hasScanner = apps.some((a) => a.id === 'com.example.scanner');
document.getElementById('get-scanner').hidden = hasScanner;

Ouvrir l'application compagnon quand elle est installée (le url de l'entrée provient de votre manifeste), sinon envoyer l'utilisateur vers sa page de boutique :

// manifest: { "platform": "play", "id": "com.example.scanner", "url": "https://example.com/scan" }
document.getElementById('open-scanner').addEventListener('click', async () => {
  const apps = typeof navigator.getInstalledRelatedApps === 'function'
    ? await navigator.getInstalledRelatedApps() : [];
  const scanner = apps.find((a) => a.id === 'com.example.scanner');
  location.href = scanner && scanner.url
    ? scanner.url
    : 'https://play.google.com/store/apps/details?id=com.example.scanner';
});

Lister lesquelles de vos applications sont déjà sur le téléphone, sur un écran « Plus de nos applications » :

async function renderMoreApps(catalog) {         // [{ id, name }] matching your manifest entries
  if (typeof navigator.getInstalledRelatedApps !== 'function') return; // e.g. inside an iframe
  const installed = new Set((await navigator.getInstalledRelatedApps()).map((a) => a.id));
  document.getElementById('more-apps').innerHTML = catalog.map((app) =>
    `<li>${app.name} — ${installed.has(app.id) ? 'Installed' : '<a href="https://play.google.com/store/apps/details?id=' + app.id + '">Get it</a>'}</li>`
  ).join('');
}

Remarques : la compilation lit le manifeste de votre bundle et déclare chaque package listé ; une application qui n'y figure pas est invisible et n'est jamais signalée. Les applications en mode site web gardent leur manifeste sur le site, donc rien n'est déclaré et la liste est vide. Transport interne : __relatedAppsInstalled.

navigator.permissions standard web#

navigator.permissions.query({ name })

Exemple

L'API Permissions standard, alimentée par l'état réel des autorisations Android du téléphone - une page peut ainsi afficher « Autoriser la caméra » seulement quand on peut encore la demander, et « Activez-la dans les Paramètres » quand ce n'est plus possible.

Retour : une Promise d'un PermissionStatus {name, state, onchange} ; state vaut 'granted', 'denied' ou 'prompt', et change se déclenche juste après la fermeture d'une boîte de dialogue d'autorisation ou quand l'utilisateur revient des Paramètres. Nécessite : aucune option à activer.

async function cameraButton() {
  const btn = document.getElementById('scan');
  const status = await navigator.permissions.query({ name: 'camera' });
  function render() {
    if (status.state === 'denied') {
      btn.textContent = 'Camera is off — enable it in Settings';
      btn.disabled = true;
    } else {
      btn.textContent = status.state === 'granted' ? 'Scan' : 'Allow camera and scan';
      btn.disabled = false;
    }
  }
  render();
  status.onchange = render;   // e.g. after the Android dialog, or back from Settings
}
cameraButton();

// The other names the app answers the same way:
for (const name of ['microphone', 'geolocation', 'notifications']) {
  navigator.permissions.query({ name }).then((s) => console.log(name, s.state));
}

Ne demander que lorsque la page peut encore demander, et sinon envoyer l'utilisateur dans les Paramètres :

async function ensureMic() {
  if (!(navigator.permissions && navigator.permissions.query)) return true;   // a browser without it
  const s = await navigator.permissions.query({ name: 'microphone' });
  if (s.state === 'denied') {
    showMessage('Microphone is off for this app. Turn it on in Settings.');
    return false;
  }
  return true;   // 'granted', or 'prompt' (Android asks on first use)
}

document.getElementById('record').onclick = async () => {
  if (await ensureMic()) startRecording();
};

Réagir quand l'utilisateur modifie une autorisation dans les Paramètres, et la combiner avec une vraie demande :

async function watchLocationSwitch() {
  const status = await navigator.permissions.query({ name: 'geolocation' });
  status.addEventListener('change', () => {
    if (status.state === 'granted') {
      navigator.geolocation.getCurrentPosition((p) => showNearby(p.coords.latitude, p.coords.longitude));
    } else {
      hideNearby();
    }
  });
}
watchLocationSwitch();

Remarques : denied couvre aussi une capacité avec laquelle l'application a été compilée sans (son option était désactivée à la compilation, Android ne peut donc jamais l'accorder) et un refus « Ne plus demander ». screen-wake-lock, clipboard-read et clipboard-write valent 'granted' (l'application les fournit sans invite). Les autres noms (midi, accelerometer, …) reçoivent la réponse du WebView comme avant. Événement derrière : appmint:permissions-changed.

TextDetector standard web#

new TextDetector().detect(image) — text recognition (OCR)

Exemple

Lit le texte d'une image sur le téléphone lui-même - le total d'un ticket, un numéro de série, une carte de visite - avec le module de reconnaissance ML Kit de Google. Le format est celui de TextDetector de la Shape Detection API, que Chrome propose derrière un indicateur.

Retour : Promise d'un tableau, une entrée par ligne : { rawValue, boundingBox, cornerPoints } en pixels de l'image. Rejette NotSupportedError sans Google Play services et OperationError quand la reconnaissance échoue. Nécessite : aucune option à activer et aucune autorisation (pour lire depuis la caméra, prenez la photo avec <input type="file" accept="image/*" capture> ou un flux de caméra).

document.getElementById('photo').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (!file || !('TextDetector' in window)) return;
  status.textContent = 'Reading…';
  try {
    const lines = await new TextDetector().detect(file);   // a Blob, <img>, <canvas>, <video> or ImageBitmap
    const text = lines.map((l) => l.rawValue).join('\n');
    const total = /total\s*[:$€£]?\s*([\d.,]+)/i.exec(text);
    status.textContent = total ? 'Total: ' + total[1] : text;
  } catch (err) {
    status.textContent = 'Could not read the text (' + err.name + ')';
  }
});

Lire un numéro de série depuis l'aperçu en direct de la caméra (un <video> en lecture est passé tel quel, son image courante est lue) :

const video = document.querySelector('video');
async function startPreview() {
  video.srcObject = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
  await video.play();
}

document.getElementById('read-serial').addEventListener('click', async () => {
  if (!('TextDetector' in window)) return;
  const lines = await new TextDetector().detect(video);
  const hit = lines.map((l) => l.rawValue).find((t) => /S\/?N[:\s]*[A-Z0-9-]{6,}/i.test(t));
  serialField.value = hit ? hit.replace(/^.*S\/?N[:\s]*/i, '') : '';
  if (!hit) showMessage('No serial number in view — move closer and try again.');
});

Entourer chaque ligne reconnue sur la photo, avec boundingBox (pixels de l'image) :

async function outlineText(img, canvas) {
  await img.decode();                             // detect() rejects InvalidStateError before it has loaded
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  const g = canvas.getContext('2d');
  g.drawImage(img, 0, 0);
  const lines = await new TextDetector().detect(img);
  g.strokeStyle = '#ff3b30';
  g.lineWidth = 3;
  for (const { boundingBox: b, rawValue } of lines) {
    g.strokeRect(b.x, b.y, b.width, b.height);
    console.log(rawValue);
  }
  return lines.length;
}

Masquer le bouton de scan là où la reconnaissance de texte est absente (pas de Google Play services), et extraire les coordonnées d'une carte de visite :

const scanBtn = document.getElementById('scan-card');
scanBtn.hidden = !('TextDetector' in window);
document.getElementById('card-photo').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (!file) return;
  const text = (await new TextDetector().detect(file)).map((l) => l.rawValue).join('\n');
  fillContact({
    email: (text.match(/[\w.+-]+@[\w-]+\.[\w.]+/) || [''])[0],
    phone: (text.match(/\+?\d[\d\s().-]{7,}\d/) || [''])[0],
  });
});

Remarques : Écritures latines uniquement (anglais, espagnol, français, allemand, indonésien, vietnamien, turc et autres) ; le chinois, le devanagari, le japonais, le coréen et l'arabe ne sont pas reconnus. Le modèle provient de Google Play services : la compilation le demande à l'installation quand votre code l'utilise, sinon le premier appel sur un téléphone neuf attend quelques secondes pendant le téléchargement. Les images d'un autre site doivent autoriser CORS (un canvas contaminé rejette SecurityError). Transports internes : __ocrAvailable, __ocrRecognize (répondu sous la forme appmint:ocr). Les applications créées par IA utilisent recognizeText().

__idlePermission pont#

window.WebToApk.__idlePermission(request: Boolean): String

IdleDetector.requestPermission() ([request] true, appelé lors d'un geste de l'utilisateur) enregistre l'autorisation ; false ne fait que la lire. "granted" | "prompt".

Exemple

Transport interne derrière IdleDetector.requestPermission() et l'état d'autorisation 'idle-detection'. Les pages utilisent l'API standard ; n'appelez jamais ceci directement - les méthodes __ peuvent changer sans préavis.

Retour : 'granted' ou 'prompt'. Nécessite : un geste de l'utilisateur pour la demande.

Utilisez l'API publique :

button.onclick = async () => {
  const state = await IdleDetector.requestPermission();
  console.log(state); // 'granted'
};

Détecter la fonctionnalité, et traiter un refus comme « non autorisé » :

async function askIdlePermission() {
  if (!('IdleDetector' in window)) return false;
  try {
    return (await IdleDetector.requestPermission()) === 'granted';
  } catch (err) {
    return false;
  }
}

Demandez-la depuis un appui, puis lancez la détection :

startBtn.addEventListener('click', async () => {
  if (!(await askIdlePermission())) { showMessage('Idle detection was not allowed.'); return; }
  const idle = new IdleDetector();
  idle.addEventListener('change', () => console.log(idle.userState));
  await idle.start({ threshold: 60000 });
});

Remarques : Implémenté dans le shell de l'application (IdleAndRelatedApps.kt).

__idleState pont#

window.WebToApk.__idleState(): String

IdleDetector : `{idleMs, locked}` d'après la saisie propre à l'application et le verrouillage de l'écran.

Exemple

Transport interne derrière IdleDetector : depuis combien de temps l'utilisateur n'a rien saisi, et si l'écran est verrouillé. Les pages utilisent l'API standard ; n'appelez jamais ceci directement - les méthodes __ peuvent changer sans préavis.

Retour : JSON {idleMs, locked}. Les changements d'écran et d'application sont envoyés sous la forme de l'événement appmint:idle avec le même format. Nécessite : rien.

Utilisez l'API publique :

const idle = new IdleDetector();
idle.onchange = () => console.log(idle.userState, idle.screenState);
await idle.start({ threshold: 60000 });

Arrêter le suivi avec un AbortController à la fermeture de l'écran :

const ctl = new AbortController();
const idle = new IdleDetector();
idle.addEventListener('change', () => {
  if (idle.userState === 'idle') showAwayBanner();
});
await idle.start({ threshold: 60000, signal: ctl.signal });
// later: ctl.abort();

Verrouiller l'application quand l'écran se verrouille :

if ('IdleDetector' in window && (await IdleDetector.requestPermission()) === 'granted') {
  const d = new IdleDetector();
  d.onchange = () => { if (d.screenState === 'locked') showPinScreen(); };
  await d.start({ threshold: 60000 });
}

Remarques : Implémenté dans le shell de l'application (IdleAndRelatedApps.kt).

__permissionState pont#

window.WebToApk.__permissionState(name: String): String

État dans l'API Permissions d'un nom d'autorisation W3C, d'après les vraies autorisations Android : "granted" | "denied" | "prompt", ou "" quand le shell ne répond pas à ce nom (la requête de la page passe alors à l'implémentation propre du WebView).

Exemple

Transport interne derrière navigator.permissions.query({ name }). Les pages utilisent l'API standard ; n'appelez jamais ceci directement - les méthodes __ peuvent changer sans préavis.

Retour : 'granted', 'denied', 'prompt', ou '' pour un nom auquel l'application ne répond pas (la requête passe alors au WebView lui-même). Nécessite : aucune option à activer.

Utilisez l'API publique :

const status = await navigator.permissions.query({ name: 'camera' });
showCameraHint(status.state);                       // 'granted' | 'denied' | 'prompt'
status.addEventListener('change', () => showCameraHint(status.state));

Vérifier plusieurs autorisations à la fois :

const names = ['camera', 'microphone', 'geolocation', 'notifications'];
const states = await Promise.all(names.map((name) =>
  navigator.permissions.query({ name }).then((s) => s.state, () => 'unknown')));
names.forEach((name, i) => console.log(name, states[i]));

Ne demander que si l'état n'est pas denied, et indiquer quoi faire quand il l'est :

async function ensureLocation() {
  const s = await navigator.permissions.query({ name: 'geolocation' });
  if (s.state === 'denied') { showMessage('Turn location on in the phone settings.'); return null; }
  return new Promise((ok, fail) => navigator.geolocation.getCurrentPosition(ok, fail));
}

Remarques : Répond à camera, microphone, geolocation, notifications, screen-wake-lock, clipboard-read, clipboard-write. Une capacité que la compilation a laissée désactivée (ou dont l'autorisation n'est pas dans le manifeste) vaut denied. Les états en direct sont relus sur appmint:permissions-changed (déclenché après chaque boîte de dialogue d'autorisation) et quand la page redevient visible.

__relatedAppsInstalled pont#

window.WebToApk.__relatedAppsInstalled(idsJson: String): String

navigator.getInstalledRelatedApps() : lesquels de [idsJson] (noms de package du manifeste de la page) sont installés et visibles par cette application.

Exemple

Transport interne derrière navigator.getInstalledRelatedApps(). Les pages utilisent l'API standard ; n'appelez jamais ceci directement - les méthodes __ peuvent changer sans préavis.

Retour : tableau JSON des noms de package donnés qui sont installés et visibles par cette application. Nécessite : les packages déclarés à la compilation (depuis le manifeste d'application web inclus).

Utilisez l'API publique :

const apps = await navigator.getInstalledRelatedApps();
console.log(apps.map((a) => a.id));

Afficher le bouton « Obtenir l'application compagnon » seulement quand elle n'est pas installée :

async function showCompanionLink() {
  if (!navigator.getInstalledRelatedApps) return;
  const apps = await navigator.getInstalledRelatedApps();
  document.getElementById('get-companion').hidden = apps.length > 0;
}

Les packages proviennent du manifeste d'application web lié par la page :

<link rel="manifest" href="/manifest.webmanifest">
<!-- manifest.webmanifest: "related_applications": [{ "platform": "play", "id": "com.example.companion" }] -->

Remarques : Implémenté dans le shell de l'application (IdleAndRelatedApps.kt).

getAppName pont#

window.WebToApk.getAppName(): String

Le nom d'application choisi par le créateur dans l'assistant - celui qu'affiche le lanceur.

Exemple

Donne le nom d'application que vous avez choisi dans l'assistant de compilation - le nom sous l'icône du lanceur.

Retour : une chaîne immédiatement. Nécessite : rien.

Utiliser le vrai nom de l'application dans un titre et un texte de partage.

var appName = (window.WebToApk && window.WebToApk.getAppName)
  ? WebToApk.getAppName()
  : document.title;                       // in a browser: the page title

document.getElementById('welcome').textContent = 'Welcome to ' + appName;
document.getElementById('shareBtn').addEventListener('click', function () {
  if (navigator.share) navigator.share({ text: 'Try ' + appName + '!' });
});

Mettre le nom de l'application dans le titre de la page et dans un titre de section :

if (window.WebToApk && WebToApk.getAppName) {
  const name = WebToApk.getAppName();
  document.title = name;
  document.querySelector('h1').textContent = name;
}

Construire un e-mail d'assistance qui mentionne l'application et sa version :

function supportMailto() {
  const b = window.WebToApk;
  const name = b && b.getAppName ? b.getAppName() : 'our app';
  const version = b && b.getAppVersion ? b.getAppVersion() : 'web';
  const subject = encodeURIComponent('Help with ' + name + ' ' + version);
  return 'mailto:[email protected]?subject=' + subject;
}
document.getElementById('contact').href = supportMailto();

Remarques : C'est le nom saisi dans l'assistant, pas le <title> de la page. Il ne change pas à l'exécution. Pour la version, utilisez getAppVersion().

getAppVersion pont#

window.WebToApk.getAppVersion(): String

Le versionName du package installé, p. ex. « 1.4.2 » - la chaîne définie par le créateur lors de la compilation, pas le versionCode. Retombe sur « 1.0 » si le package est illisible, donc une page peut l'afficher sans vérification de null.

Exemple

Donne le nom de version de l'application installée, par exemple "1.4.2".

Retour : une chaîne immédiatement. Nécessite : rien.

Afficher la version dans un écran À propos.

var version = (window.WebToApk && window.WebToApk.getAppVersion)
  ? WebToApk.getAppVersion()
  : 'web';

document.getElementById('about').textContent = 'Version ' + version;

Demander une mise à jour quand la version installée est ancienne :

function isOlder(a, b) {            // "1.4.2" < "1.10.0"
  const x = a.split('.').map(Number), y = b.split('.').map(Number);
  for (let i = 0; i < Math.max(x.length, y.length); i++) {
    if ((x[i] || 0) !== (y[i] || 0)) return (x[i] || 0) < (y[i] || 0);
  }
  return false;
}

if (window.WebToApk && WebToApk.getAppVersion && isOlder(WebToApk.getAppVersion(), '1.5.0')) {
  document.getElementById('update-banner').hidden = false;
}

Ajouter la version et le code de version de getDeviceInfo() à un rapport de bug :

function reportInfo() {
  const b = window.WebToApk;
  if (!(b && b.getAppVersion)) return 'web';
  let text = 'v' + b.getAppVersion();
  if (b.getDeviceInfo) {
    const info = JSON.parse(b.getDeviceInfo());
    text += ' (build ' + info.app.versionCode + ')';
  }
  return text;
}
document.getElementById('report').value = reportInfo();

Remarques : C'est le NOM de version que vous avez défini à la compilation, pas le code de version. Si le package est illisible, elle renvoie "1.0", donc jamais vide. Le code de version et les dates d'installation se trouvent dans getDeviceInfo() sous app.

getDeviceInfo pont#

window.WebToApk.getDeviceInfo(): String

WebToApk.getDeviceInfo() -> chaîne JSON, AUCUNE autorisation requise WebToApk.getPhoneIdentity() -> opérateur/SIM, nécessite l'autorisation Téléphone WebToApk.getInstallId() -> UUID stable par installation

Décrit par son groupe, Informations sur l'appareil / l'application, plutôt que séparément.

Exemple

Donne en un seul appel des informations sur le téléphone, Android, l'écran, votre application et l'état actuel (batterie, mémoire, réseau).

Retour : une chaîne JSON immédiatement - utilisez JSON.parse. Clés de premier niveau : android, hardware, screen, app, runtime, installId. Nécessite : rien - aucune autorisation.

Afficher les informations du téléphone.

function showPhoneInfo() {
  if (!(window.WebToApk && window.WebToApk.getDeviceInfo)) {
    document.getElementById('out').textContent = navigator.userAgent;   // browser: only the user agent
    return;
  }
  var info = JSON.parse(WebToApk.getDeviceInfo());

  document.getElementById('out').innerHTML =
    'Android: ' + info.android.release + ' (SDK ' + info.android.sdkInt + ')<br>' +
    'Version ID: ' + info.android.buildId + '<br>' +
    'Build number: ' + info.android.incremental + '<br>' +
    'Phone: ' + info.hardware.manufacturer + ' ' + info.hardware.model + '<br>' +
    'Screen: ' + info.screen.widthPx + 'x' + info.screen.heightPx + ' @' + info.screen.refreshRate + 'Hz<br>' +
    'Battery: ' + info.runtime.batteryLevel + '%' + (info.runtime.charging ? ' (charging)' : '') + '<br>' +
    'Network: ' + info.runtime.networkType + '<br>' +
    'App: ' + info.app.versionName + ' (' + info.app.versionCode + ')<br>' +
    'WebView: ' + info.app.webViewPackage;
}

Toutes les clés :

{
  "android":  { "release": "14", "sdkInt": 34, "codename": "REL", "buildId": "UP1A.231005.007", "incremental": "R.1a2b3c", "display": "…", "fingerprint": "…", "securityPatch": "2026-08-05" },
  "hardware": { "manufacturer": "OPPO", "brand": "OPPO", "model": "CPH2269", "device": "…", "product": "…", "board": "…", "hardware": "…", "abis": ["arm64-v8a"], "socManufacturer": "…", "socModel": "…", "isEmulator": false },
  "screen":   { "widthPx": 720, "heightPx": 1600, "density": 2, "densityDpi": 320, "refreshRate": 90, "nightMode": true },
  "app":      { "packageName": "com.example.app", "versionName": "1.0", "versionCode": 1, "targetSdk": 35, "firstInstallTime": 1737000000000, "lastUpdateTime": 1737000000000, "installer": "com.android.vending", "webViewPackage": "com.google.android.webview 131.0" },
  "runtime":  { "totalMemoryBytes": 4000000000, "availableMemoryBytes": 1500000000, "lowMemory": false, "storageFreeBytes": 20000000000, "storageTotalBytes": 64000000000, "batteryLevel": 80, "charging": false, "locale": "en-IN", "timezone": "Asia/Kolkata", "networkType": "wifi" },
  "installId": "3f2b8c1e-…"
}

Avertir sur un téléphone à stockage ou mémoire faible avant un gros téléchargement.

function canDownload(bytesNeeded) {
  if (!(window.WebToApk && WebToApk.getDeviceInfo)) return true;   // browser: let the download try
  const r = JSON.parse(WebToApk.getDeviceInfo()).runtime;
  if (r.storageFreeBytes < bytesNeeded * 2) { showMessage('Not enough free storage.'); return false; }
  if (r.lowMemory) showMessage('The phone is low on memory; this may be slow.');
  return true;
}

Remarques : socManufacturer / socModel n'existent que sur Android 12+. networkType vaut wifi, cellular, ethernet, other, none ou unknown. installer vaut com.android.vending pour les installations Play et est vide pour les installations manuelles. Les valeurs sont lues au moment de l'appel - rappelez pour actualiser la batterie ou le réseau. Il n'y a ni IMEI ni numéro de série : Android les bloque pour toutes les applications ; utilisez getInstallId().

getInstallId pont#

window.WebToApk.getInstallId(): String

WebToApk.getDeviceInfo() -> chaîne JSON, AUCUNE autorisation requise WebToApk.getPhoneIdentity() -> opérateur/SIM, nécessite l'autorisation Téléphone WebToApk.getInstallId() -> UUID stable par installation

Décrit par son groupe, Informations sur l'appareil / l'application, plutôt que séparément.

Exemple

Donne un identifiant aléatoire qui reste le même pour cette installation de votre application.

Retour : une chaîne (un UUID comme "3f2b…-…") immédiatement. Nécessite : rien.

L'utiliser comme clé d'appareil pour votre serveur ou pour une vérification « un seul essai gratuit par appareil ».

function deviceKey() {
  if (window.WebToApk && window.WebToApk.getInstallId) {
    return WebToApk.getInstallId();
  }
  // In a browser: keep your own random ID in localStorage (per browser, cleared with site data).
  var k = localStorage.getItem('installId');
  if (!k) {
    k = 'w-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
    localStorage.setItem('installId', k);
  }
  return k;
}

fetch('https://api.example.com/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ device: deviceKey() })
});

Envoyer l'identifiant à votre serveur dans un en-tête et le garder hors de l'URL.

async function apiGet(path) {
  const id = (window.WebToApk && WebToApk.getInstallId) ? WebToApk.getInstallId() : null;
  const res = await fetch('https://api.example.com' + path, {
    headers: id ? { 'X-Install-Id': id } : {}
  });
  return res.json();
}

Le joindre aux rapports de plantage pour retrouver les problèmes d'une installation précise.

if (window.WebToApk && WebToApk.setCrashUserId && WebToApk.getInstallId) {
  // returns false when Crash reports are off or the id has other characters
  const ok = WebToApk.setCrashUserId(WebToApk.getInstallId());
  if (!ok) console.log('crash user id not set');
}

Remarques : L'identifiant est créé à la première utilisation et conservé dans le stockage de l'application. Il reste le même après les mises à jour. Il change quand l'utilisateur désinstalle l'application ou efface ses données. Ce n'est pas un identifiant matériel - Android ne donne ni IMEI ni numéro de série aux applications. La même valeur se trouve aussi dans getDeviceInfo() sous installId.

getPhoneIdentity pont#

window.WebToApk.getPhoneIdentity(): String

WebToApk.getDeviceInfo() -> chaîne JSON, AUCUNE autorisation requise WebToApk.getPhoneIdentity() -> opérateur/SIM, nécessite l'autorisation Téléphone WebToApk.getInstallId() -> UUID stable par installation

Décrit par son groupe, Informations sur l'appareil / l'application, plutôt que séparément.

Exemple

Donne les informations sur l'opérateur mobile et la SIM : nom de l'opérateur, pays de la SIM, code opérateur, type de téléphone et état de la SIM.

Retour : une chaîne JSON immédiatement - utilisez JSON.parse : { carrierName, simCountryIso, networkCountryIso, simOperator, phoneType, simState, hardwareIdAvailable: false }, ou { error }. Nécessite : activez Téléphone à l'étape 4 (Accès) lors de la compilation ; sinon elle renvoie { "error": "not_enabled" }.

Afficher l'opérateur et choisir l'indicatif du pays.

function showCarrier() {
  if (!(window.WebToApk && window.WebToApk.getPhoneIdentity)) return;
  var p = JSON.parse(WebToApk.getPhoneIdentity());

  if (p.error === 'not_enabled') { showMessage('Turn on Phone when you build the app'); return; }
  if (p.error) { showMessage('Could not read SIM info: ' + p.error); return; }

  if (p.simState !== 'ready') {
    showMessage('No SIM ready (' + p.simState + ')');   // absent | pin_required | network_locked | unknown
    return;
  }
  document.getElementById('carrier').textContent = p.carrierName;                 // e.g. "Jio 4G"
  document.getElementById('country').value = p.simCountryIso.toUpperCase();        // e.g. "IN"
}

Choisir un indicatif téléphonique selon le pays de la SIM pour un formulaire d'inscription.

const DIAL = { IN: '+91', US: '+1', GB: '+44', DE: '+49' };
function defaultDialCode() {
  if (!(window.WebToApk && WebToApk.getPhoneIdentity)) return '+1';
  const p = JSON.parse(WebToApk.getPhoneIdentity());
  if (p.error || p.simState !== 'ready') return '+1';
  return DIAL[p.simCountryIso.toUpperCase()] || '+1';
}
document.getElementById('dial').value = defaultDialCode();

Distinguer une tablette Wi-Fi uniquement d'un téléphone avec phoneType.

if (window.WebToApk && WebToApk.getPhoneIdentity) {
  const p = JSON.parse(WebToApk.getPhoneIdentity());
  if (!p.error && p.phoneType === 'none') {
    document.getElementById('sms-login').hidden = true;   // no cellular radio: hide SMS sign-in
  }
}

Remarques : phoneType vaut gsm, cdma, sip ou none (tablettes Wi-Fi uniquement). simOperator est le code MCC+MNC (p. ex. "40445"). Il n'y a ni IMEI, ni MEID, ni numéro de série - hardwareIdAvailable vaut toujours false, car Android les masque à toutes les applications depuis Android 10 ; utilisez getInstallId() pour un identifiant stable. L'option Téléphone ajoute une autorisation sensible à votre fiche Play ; déclarez son usage dans le formulaire Sécurité des données.

isTv pont#

window.WebToApk.isTv(): Boolean

Vrai quand l'application s'exécute sur un téléviseur (Android TV, Google TV, Fire TV).

Exemple

Indique à la page si l'application s'exécute sur un téléviseur (Android TV, Google TV, Fire TV), afin qu'elle affiche une disposition adaptée à la télécommande.

Retour : true ou false (de façon synchrone). Nécessite : rien. Avec Prise en charge d'Android TV activée dans l'assistant, l'application reçoit aussi une bannière TV dans le lanceur TV, et sur un téléviseur la page affiche un contour de focus net ; les liens, boutons, champs et éléments reliés avec onclick ou un rôle de bouton deviennent accessibles avec les flèches de la télécommande ; OK clique sur l'élément ciblé et Retour fonctionne comme sur un téléphone.

var onTv = !!(window.WebToApk && typeof window.WebToApk.isTv === 'function' && window.WebToApk.isTv());
if (onTv) {
  document.documentElement.classList.add('tv');     /* bigger text, no hover-only menus */
  document.querySelector('#play').focus();           /* start the remote on the main action */
}
html.tv body { font-size: 22px; }
html.tv :focus-visible { transform: scale(1.05); }

Choisir un autre écran d'accueil pour la TV et garder la disposition téléphone par défaut.

function isTv() {
  return !!(window.WebToApk && typeof WebToApk.isTv === 'function' && WebToApk.isTv());
}
history.replaceState({}, '', isTv() ? '/tv-home' : '/home');
if (isTv()) document.querySelectorAll('.touch-only').forEach(function (el) { el.hidden = true; });

Remarques : Testez avec l'émulateur Android TV et sa croix directionnelle. Publier sur les TV dans Google Play est une étape distincte : dans Play Console, inscrivez l'application au format Android TV (Release → Advanced settings → Form factors) et ajoutez une capture d'écran TV.

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.