API del bridge JavaScript
Informazioni su dispositivo e app
Che telefono è, quale build, un identificatore stabile per installazione e le API standard di posizione e fotocamera.
getUserMedia standard web#
navigator.mediaDevices.getUserMedia — camera and microphone
Esempio
La web API standard per fotocamera e microfono funziona dentro l'app. L'app mostra la richiesta di autorizzazione di Android e passa il flusso alla pagina.
Restituisce: una Promise con un MediaStream, oppure un rifiuto (NotAllowedError quando l'utente ha rifiutato o l'opzione era disattivata durante la creazione, NotFoundError quando il dispositivo non esiste). Richiede: Fotocamera e/o Microfono nel Passaggio 4 (Accesso). Quando crei da uno ZIP o da una pagina HTML e il tuo codice chiama getUserMedia({ video: … }) o { audio: … } (oppure usa <input capture>, BarcodeDetector, SpeechRecognition), la build trova la chiamata e aggiunge per te l'autorizzazione corrispondente; { video: false } non aggiunge la fotocamera. Per una build da sito web (URL), attivale tu.
Mostra la fotocamera posteriore in un elemento video.
<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>Registra una nota vocale solo con il microfono.
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);
}Scatta una foto dal flusso della fotocamera e rilascia la fotocamera.
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);
}Note: Quando chiedi fotocamera e microfono insieme, l'app chiede all'utente entrambe le autorizzazioni in una volta. Se l'opzione era disattivata durante la creazione, la richiesta viene rifiutata senza alcuna richiesta all'utente: crea di nuovo l'app con l'opzione attiva. Ferma sempre le tracce quando hai finito, altrimenti la spia della fotocamera/microfono resta accesa. I video DRM (Widevine) sono consentiti senza alcuna opzione. Fotocamera e microfono sono sensibili: dichiarali nel modulo Sicurezza dei dati di Play.
IdleDetector standard web#
new IdleDetector() — Idle Detection
Esempio
La Idle Detection API standard: sai quando l'utente smette di usare il telefono (nessun input per un po') o blocca lo schermo, e quando torna, per uno stato "assente", per mettere in pausa una sessione dal vivo o per bloccare una cassaforte.
Restituisce: IdleDetector.requestPermission() → 'granted' o 'denied' (richiede un tocco); detector.start({threshold, signal}) → Promise (rifiuta con NotAllowedError senza l'autorizzazione, con TypeError sotto 60000 ms); poi userState ('active'/'idle'), screenState ('locked'/'unlocked') e un evento change. Richiede: niente da attivare e nessuna autorizzazione 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();
});Riprendi il monitoraggio al prossimo avvio senza un nuovo tocco, una volta che l'utente ha già detto sì:
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();Lascia che l'utente cambi il ritardo del blocco automatico: ferma il rilevatore in esecuzione, avviane uno nuovo e segnala gli errori reali:
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;
}
}Note: nell'app, "input" è l'input dell'utente verso QUESTA app mentre è in primo piano; mentre un'altra app è in primo piano con lo schermo acceso, l'utente conta come attivo (un'app non può vedere l'input delle altre app: Chrome su Android risponde allo stesso modo); quando lo schermo è spento o bloccato, il tempo di inattività conta dall'ultimo tocco. L'autorizzazione viene ricordata e navigator.permissions.query({name:'idle-detection'}) la restituisce. Trasporti interni: __idleState, __idlePermission; le variazioni di blocco/sblocco arrivano come appmint:idle. Le app create con l'AI usano watchIdle().
TextDetector standard web#
new TextDetector().detect(image) — text recognition (OCR)
Esempio
Legge il testo in un'immagine direttamente sul telefono (il totale di uno scontrino, un numero di serie, un biglietto da visita) con il riconoscitore ML Kit di Google. La forma è il TextDetector della Shape Detection API, quella che Chrome distribuisce dietro un flag.
Restituisce: una Promise con un array, una voce per riga: { rawValue, boundingBox, cornerPoints } nei pixel dell'immagine. Rifiuta con NotSupportedError senza i servizi Google Play e con OperationError quando il riconoscimento fallisce. Richiede: niente da attivare e nessuna autorizzazione (per leggere dalla fotocamera, scatta la foto con <input type="file" accept="image/*" capture> o con un flusso della fotocamera).
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 + ')';
}
});Leggi un numero di serie dall'anteprima in diretta della fotocamera (un <video> in riproduzione viene passato così com'è e ne viene letto il fotogramma corrente):
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.');
});Evidenzia ogni riga riconosciuta sopra la foto, usando boundingBox (pixel dell'immagine):
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;
}Nascondi il pulsante di scansione dove il riconoscimento del testo non c'è (nessun servizio Google Play) ed estrai i dati di contatto da un biglietto da visita:
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],
});
});Note: Solo alfabeti latini (inglese, spagnolo, francese, tedesco, indonesiano, vietnamita, turco e altri); cinese, devanagari, giapponese, coreano e arabo non vengono riconosciuti. Il modello proviene dai servizi Google Play: la build lo richiede all'installazione quando il tuo codice lo usa, altrimenti la prima chiamata su un telefono nuovo attende qualche secondo mentre viene scaricato. Le immagini di un altro sito devono consentire CORS (un canvas contaminato rifiuta con SecurityError). Trasporti interni: __ocrAvailable, __ocrRecognize (risposta come appmint:ocr). Le app create con l'AI usano recognizeText().
__idlePermission bridge#
window.WebToApk.__idlePermission(request: Boolean): String
IdleDetector.requestPermission() ([request] true, chiamata con un gesto dell'utente) registra la concessione; false la legge soltanto. "granted" | "prompt".
Esempio
Trasporto interno dietro IdleDetector.requestPermission() e lo stato di autorizzazione 'idle-detection'. Le pagine usano l'API standard; non chiamarlo mai direttamente: i metodi __ possono cambiare senza preavviso.
Restituisce: 'granted' o 'prompt'. Richiede: un gesto dell'utente per la richiesta.
Usa l'API pubblica:
button.onclick = async () => {
const state = await IdleDetector.requestPermission();
console.log(state); // 'granted'
};Rileva la funzionalitĂ e tratta un rifiuto come "non consentito":
async function askIdlePermission() {
if (!('IdleDetector' in window)) return false;
try {
return (await IdleDetector.requestPermission()) === 'granted';
} catch (err) {
return false;
}
}Richiedila da un tocco, poi avvia il rilevamento:
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 });
});Note: Implementato nella shell dell'app (IdleAndRelatedApps.kt).
__idleState bridge#
window.WebToApk.__idleState(): String
IdleDetector: `{idleMs, locked}` dall'input dell'app stessa e dal keyguard.
Esempio
Trasporto interno dietro IdleDetector: da quanto tempo l'utente non dà input e se lo schermo è bloccato. Le pagine usano l'API standard; non chiamarlo mai direttamente: i metodi __ possono cambiare senza preavviso.
Restituisce: JSON {idleMs, locked}. Le variazioni di schermo e app vengono inviate come evento appmint:idle con la stessa forma. Richiede: niente.
Usa l'API pubblica:
const idle = new IdleDetector();
idle.onchange = () => console.log(idle.userState, idle.screenState);
await idle.start({ threshold: 60000 });Smetti di seguire con un AbortController quando la schermata si chiude:
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();Blocca l'app quando lo schermo si blocca:
if ('IdleDetector' in window && (await IdleDetector.requestPermission()) === 'granted') {
const d = new IdleDetector();
d.onchange = () => { if (d.screenState === 'locked') showPinScreen(); };
await d.start({ threshold: 60000 });
}Note: Implementato nella shell dell'app (IdleAndRelatedApps.kt).
__permissionState bridge#
window.WebToApk.__permissionState(name: String): String
Stato della Permissions API per un nome di autorizzazione W3C, dalle concessioni reali di Android: "granted" | "denied" | "prompt", oppure "" quando la shell non risponde a quel nome (la richiesta della pagina passa allora all'implementazione propria della WebView).
Esempio
Trasporto interno dietro navigator.permissions.query({ name }). Le pagine usano l'API standard; non chiamare mai questo direttamente: i metodi __ possono cambiare senza preavviso.
Restituisce: 'granted', 'denied', 'prompt', oppure '' per un nome a cui l'app non risponde (la richiesta passa allora alla WebView). Richiede: niente da attivare.
Usa l'API pubblica:
const status = await navigator.permissions.query({ name: 'camera' });
showCameraHint(status.state); // 'granted' | 'denied' | 'prompt'
status.addEventListener('change', () => showCameraHint(status.state));Controlla piĂą autorizzazioni insieme:
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]));Chiedi solo quando lo stato non è denied, e di' cosa fare quando lo è:
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));
}Note: Risponde per camera, microphone, geolocation, notifications, screen-wake-lock, clipboard-read, clipboard-write. Una funzionalità che la build ha lasciato disattivata (o la cui autorizzazione non è nel manifest) è denied. Gli stati in tempo reale vengono riletti su appmint:permissions-changed (emesso dopo ogni finestra di autorizzazione) e quando la pagina torna visibile.
getAppName bridge#
window.WebToApk.getAppName(): String
Il nome dell'app scelto dal creatore nella procedura guidata: quello che mostra il launcher.
Esempio
Fornisce il nome dell'app che hai scelto nella procedura guidata di creazione: il nome sotto l'icona nel launcher.
Restituisce: una stringa subito. Richiede: niente.
Usa il vero nome dell'app in un titolo e in un testo di condivisione.
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 + '!' });
});Inserisci il nome dell'app nel titolo della pagina e in un'intestazione:
if (window.WebToApk && WebToApk.getAppName) {
const name = WebToApk.getAppName();
document.title = name;
document.querySelector('h1').textContent = name;
}Crea un'email di assistenza che riporta il nome dell'app e la sua versione:
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();Note: Questo è il nome inserito nella procedura guidata, non il <title> della pagina. Non cambia durante l'esecuzione. Per la versione usa getAppVersion().
getAppVersion bridge#
window.WebToApk.getAppVersion(): String
Il versionName del pacchetto installato, ad es. "1.4.2": la stringa che il creatore imposta durante la creazione, non il versionCode. Ripiega su "1.0" se il pacchetto non può essere letto, così una pagina può stamparlo senza controllare il valore nullo.
Esempio
Fornisce il nome della versione dell'app installata, ad esempio "1.4.2".
Restituisce: una stringa subito. Richiede: niente.
Mostra la versione in una schermata Informazioni.
var version = (window.WebToApk && window.WebToApk.getAppVersion)
? WebToApk.getAppVersion()
: 'web';
document.getElementById('about').textContent = 'Version ' + version;Chiedi un aggiornamento quando la versione installata è vecchia:
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;
}Aggiungi a una segnalazione di bug la versione e il codice versione da getDeviceInfo():
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();Note: Questo è il NOME della versione che imposti durante la creazione, non il codice versione. Se il pacchetto non può essere letto restituisce "1.0", quindi non è mai vuoto. Il codice versione e le date di installazione sono in getDeviceInfo() sotto app.
getDeviceInfo bridge#
window.WebToApk.getDeviceInfo(): String
WebToApk.getDeviceInfo() -> stringa JSON, NESSUNA autorizzazione richiesta WebToApk.getPhoneIdentity() -> operatore/SIM, richiede l'autorizzazione Telefono WebToApk.getInstallId() -> UUID stabile per installazione
Descritto dal suo gruppo, Informazioni su dispositivo / app, invece che da solo.
Esempio
Fornisce in una sola chiamata informazioni sul telefono, su Android, sullo schermo, sulla tua app e sullo stato attuale (batteria, memoria, rete).
Restituisce: una stringa JSON subito: usa JSON.parse. Chiavi di primo livello: android, hardware, screen, app, runtime, installId. Richiede: niente, nessuna autorizzazione.
Mostra le informazioni sul telefono.
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;
}Tutte le chiavi:
{
"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-…"
}Avvisa su un telefono con poco spazio o poca memoria prima di un download grande.
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;
}Note: socManufacturer / socModel esistono solo su Android 12+. networkType è wifi, cellular, ethernet, other, none o unknown. installer è com.android.vending per le installazioni da Play e vuoto per quelle manuali. I valori vengono letti nel momento in cui chiami: richiama per aggiornare batteria o rete. Non ci sono IMEI o numero di serie: Android li blocca per tutte le app; usa getInstallId().
getInstallId bridge#
window.WebToApk.getInstallId(): String
WebToApk.getDeviceInfo() -> stringa JSON, NESSUNA autorizzazione richiesta WebToApk.getPhoneIdentity() -> operatore/SIM, richiede l'autorizzazione Telefono WebToApk.getInstallId() -> UUID stabile per installazione
Descritto dal suo gruppo, Informazioni su dispositivo / app, invece che da solo.
Esempio
Fornisce un ID casuale che resta lo stesso per questa installazione della tua app.
Restituisce: una stringa (un UUID come "3f2b…-…") subito. Richiede: niente.
Usalo come chiave del dispositivo per il tuo server o per un controllo "una prova gratuita per dispositivo".
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() })
});Invia l'id al tuo server con un header e tienilo fuori dall'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();
}Allegalo ai report di arresto anomalo così si possono individuare i problemi di una singola installazione.
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');
}Note: L'ID viene creato al primo utilizzo e conservato nell'archivio dell'app. Resta lo stesso dopo gli aggiornamenti dell'app. Cambia quando l'utente disinstalla l'app o ne cancella i dati. Non è un ID hardware: Android non fornisce alle app IMEI o numero di serie. Lo stesso valore è presente anche in getDeviceInfo() come installId.
getPhoneIdentity bridge#
window.WebToApk.getPhoneIdentity(): String
WebToApk.getDeviceInfo() -> stringa JSON, NESSUNA autorizzazione richiesta WebToApk.getPhoneIdentity() -> operatore/SIM, richiede l'autorizzazione Telefono WebToApk.getInstallId() -> UUID stabile per installazione
Descritto dal suo gruppo, Informazioni su dispositivo / app, invece che da solo.
Esempio
Fornisce i dati dell'operatore mobile e della SIM: nome dell'operatore, paese della SIM, codice operatore, tipo di telefono e stato della SIM.
Restituisce: una stringa JSON subito: usa JSON.parse: { carrierName, simCountryIso, networkCountryIso, simOperator, phoneType, simState, hardwareIdAvailable: false }, oppure { error }. Richiede: attiva Telefono nel Passaggio 4 (Accesso) quando crei l'app; altrimenti restituisce { "error": "not_enabled" }.
Mostra l'operatore e scegli il prefisso del paese.
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"
}Scegli un prefisso telefonico dal paese della SIM per un modulo di registrazione.
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();Distingui un tablet solo Wi-Fi da un telefono con 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
}
}Note: phoneType è gsm, cdma, sip o none (tablet solo Wi-Fi). simOperator è il codice MCC+MNC (es. "40445"). Non ci sono IMEI, MEID o numero di serie: hardwareIdAvailable è sempre false, perché Android li nasconde a tutte le app dalla versione 10; usa getInstallId() per un id stabile. L'opzione Telefono aggiunge un'autorizzazione sensibile alla tua scheda su Play; dichiara a cosa serve nel modulo Sicurezza dei dati.
isTv bridge#
window.WebToApk.isTv(): Boolean
Vero quando l'app è in esecuzione su una televisione (Android TV, Google TV, Fire TV).
Esempio
Dice alla pagina se l'app è in esecuzione su una televisione (Android TV, Google TV, Fire TV), così da mostrare un layout adatto al telecomando.
Restituisce: true o false (in modo sincrono). Richiede: niente. Con Supporto Android TV attivo nella procedura guidata, l'app ottiene anche un banner TV nel launcher della TV e, su una TV, la pagina riceve un chiaro anello di focus; link, pulsanti, campi ed elementi collegati con onclick o con un ruolo di pulsante diventano raggiungibili con le frecce del telecomando; OK fa clic sull'elemento in focus e Indietro funziona come su un telefono.
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); }Scegli una schermata iniziale diversa per la TV e tieni il layout per telefono come predefinito.
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; });Note: Prova con l'emulatore Android TV e il suo D-pad. La pubblicazione per TV su Google Play è un passaggio separato: in Play Console, abilita l'app per il fattore di forma Android TV (Release → Impostazioni avanzate → Fattori di forma) e aggiungi uno screenshot per TV.