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