API jembatan JavaScript
Informasi perangkat dan aplikasi
Ponsel apa ini, build yang mana, pengenal stabil per instalasi, serta API lokasi dan kamera standar.
getUserMedia standar web#
navigator.mediaDevices.getUserMedia — camera and microphone
Contoh
API kamera dan mikrofon web standar berfungsi di dalam aplikasi. Aplikasi menampilkan permintaan izin bawaan Android dan memberikan aliran ke halaman.
Mengembalikan: Promise berisi MediaStream, atau penolakan (NotAllowedError bila pengguna menolak atau sakelar nonaktif saat build, NotFoundError bila tidak ada perangkat seperti itu). Membutuhkan: Kamera dan/atau Mikrofon di Langkah 4 (Akses). Bila Anda membuat build dari ZIP atau halaman HTML dan kode Anda memanggil getUserMedia({ video: … }) atau { audio: … } (atau memakai <input capture>, BarcodeDetector, SpeechRecognition), build menemukan panggilan itu dan menambahkan izin yang sesuai untuk Anda; { video: false } tidak menambahkan Kamera. Untuk build situs web (URL), centang sendiri.
Tampilkan kamera belakang di elemen 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>Rekam catatan suara hanya dengan mikrofon.
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);
}Ambil foto dari aliran kamera dan lepaskan kamera.
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);
}Catatan: Saat Anda meminta kamera dan mikrofon bersamaan, aplikasi meminta keduanya sekaligus kepada pengguna. Jika sakelar nonaktif saat Anda membuat build, permintaan ditolak tanpa permintaan izin sama sekali - buat build lagi dengan sakelar aktif. Selalu hentikan track bila selesai, atau lampu kamera/mikrofon tetap menyala. Video DRM (Widevine) diizinkan tanpa sakelar apa pun. Kamera dan mikrofon bersifat sensitif: deklarasikan di formulir Keamanan data Play Anda.
IdleDetector standar web#
new IdleDetector() — Idle Detection
Contoh
Idle Detection API standar: ketahui kapan pengguna berhenti memakai ponsel (tanpa input beberapa saat) atau mengunci layar, dan kapan mereka kembali - untuk status "pergi", menjeda sesi langsung, atau mengunci brankas.
Mengembalikan: IdleDetector.requestPermission() → 'granted' atau 'denied' (perlu ketukan); detector.start({threshold, signal}) → Promise (ditolak NotAllowedError tanpa izin, TypeError di bawah 60000 ms); lalu userState ('active'/'idle'), screenState ('locked'/'unlocked') dan event change. Membutuhkan: tidak ada yang perlu diaktifkan dan tanpa izin 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();
});Lanjutkan memantau pada peluncuran berikutnya tanpa ketukan baru, setelah pengguna sudah menyetujui:
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();Biarkan pengguna mengubah jeda kunci otomatis: hentikan detektor yang berjalan, mulai yang baru, dan laporkan kesalahan sebenarnya:
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;
}
}Catatan: di aplikasi, "input" adalah masukan pengguna ke aplikasi INI saat aplikasi di depan; saat aplikasi lain di depan dengan layar menyala, pengguna dianggap aktif (aplikasi tidak bisa melihat input aplikasi lain - Chrome di Android menjawab dengan cara yang sama); setelah layar mati atau terkunci, waktu diam dihitung dari sentuhan terakhir. Izin diingat, dan navigator.permissions.query({name:'idle-detection'}) menjawabnya. Transport internal: __idleState, __idlePermission; perubahan kunci/buka kunci tiba sebagai appmint:idle. Aplikasi buatan AI menggunakan watchIdle().
TextDetector standar web#
new TextDetector().detect(image) — text recognition (OCR)
Contoh
Membaca teks di dalam gambar langsung di ponsel - total struk, nomor seri, kartu nama - dengan pengenal ML Kit dari Google. Bentuknya adalah TextDetector dari Shape Detection API, yang dikirim Chrome di balik flag.
Mengembalikan: Promise berisi array, satu entri per baris: { rawValue, boundingBox, cornerPoints } dalam piksel gambar. Ditolak NotSupportedError tanpa Google Play services dan OperationError bila pengenalan gagal. Membutuhkan: tidak ada yang perlu diaktifkan dan tanpa izin (untuk membaca dari kamera, ambil foto dengan <input type="file" accept="image/*" capture> atau aliran kamera).
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 + ')';
}
});Baca nomor seri dari pratinjau kamera langsung (<video> yang sedang diputar dikirim apa adanya, frame saat ini dibaca):
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.');
});Garis tepi setiap baris yang dikenali di atas foto, menggunakan boundingBox (piksel gambar):
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;
}Sembunyikan tombol pindai di tempat pengenalan teks tidak ada (tanpa Google Play services), dan ambil detail kontak dari kartu nama:
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],
});
});Catatan: Hanya aksara Latin (Inggris, Spanyol, Prancis, Jerman, Indonesia, Vietnam, Turki, dan lainnya); Mandarin, Devanagari, Jepang, Korea, dan Arab tidak dikenali. Modelnya berasal dari Google Play services: build memintanya saat instalasi bila kode Anda menggunakannya, jika tidak panggilan pertama di ponsel baru menunggu beberapa detik selama pengunduhan. Gambar dari situs lain harus mengizinkan CORS (canvas yang tercemar menolak SecurityError). Transport internal: __ocrAvailable, __ocrRecognize (dijawab sebagai appmint:ocr). Aplikasi buatan AI menggunakan recognizeText().
__idlePermission jembatan#
window.WebToApk.__idlePermission(request: Boolean): String
IdleDetector.requestPermission() ([request] true, dipanggil lewat gestur pengguna) mencatat pemberian izin; false hanya membacanya. "granted" | "prompt".
Contoh
Transport internal di balik IdleDetector.requestPermission() dan status izin 'idle-detection'. Halaman memakai API standar; jangan pernah memanggil ini langsung - metode __ dapat berubah tanpa pemberitahuan.
Mengembalikan: 'granted' atau 'prompt'. Membutuhkan: gestur pengguna untuk permintaan.
Gunakan API publik:
button.onclick = async () => {
const state = await IdleDetector.requestPermission();
console.log(state); // 'granted'
};Deteksi fitur, dan perlakukan penolakan sebagai "tidak diizinkan":
async function askIdlePermission() {
if (!('IdleDetector' in window)) return false;
try {
return (await IdleDetector.requestPermission()) === 'granted';
} catch (err) {
return false;
}
}Minta dari sebuah ketukan, lalu mulai mendeteksi:
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 });
});Catatan: Diimplementasikan di shell aplikasi (IdleAndRelatedApps.kt).
__idleState jembatan#
window.WebToApk.__idleState(): String
IdleDetector: `{idleMs, locked}` dari input aplikasi sendiri dan keyguard.
Contoh
Transport internal di balik IdleDetector: berapa lama sejak pengguna terakhir memberi input, dan apakah layar terkunci. Halaman memakai API standar; jangan pernah memanggil ini langsung - metode __ dapat berubah tanpa pemberitahuan.
Mengembalikan: JSON {idleMs, locked}. Perubahan layar dan aplikasi dikirim sebagai event appmint:idle dengan bentuk yang sama. Membutuhkan: tidak ada.
Gunakan API publik:
const idle = new IdleDetector();
idle.onchange = () => console.log(idle.userState, idle.screenState);
await idle.start({ threshold: 60000 });Berhenti memantau dengan AbortController saat layar ditutup:
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();Kunci aplikasi saat layar terkunci:
if ('IdleDetector' in window && (await IdleDetector.requestPermission()) === 'granted') {
const d = new IdleDetector();
d.onchange = () => { if (d.screenState === 'locked') showPinScreen(); };
await d.start({ threshold: 60000 });
}Catatan: Diimplementasikan di shell aplikasi (IdleAndRelatedApps.kt).
__permissionState jembatan#
window.WebToApk.__permissionState(name: String): String
Status Permissions API dari nama izin W3C, dari pemberian izin Android yang sebenarnya: "granted" | "denied" | "prompt", atau "" bila shell tidak menjawab nama itu (kueri halaman lalu diteruskan ke implementasi WebView sendiri).
Contoh
Transport internal di balik navigator.permissions.query({ name }). Halaman memakai API standar; jangan pernah memanggil ini langsung - metode __ dapat berubah tanpa pemberitahuan.
Mengembalikan: 'granted', 'denied', 'prompt', atau '' untuk nama yang tidak dijawab aplikasi (kueri lalu diteruskan ke WebView sendiri). Membutuhkan: tidak ada yang perlu diaktifkan.
Gunakan API publik:
const status = await navigator.permissions.query({ name: 'camera' });
showCameraHint(status.state); // 'granted' | 'denied' | 'prompt'
status.addEventListener('change', () => showCameraHint(status.state));Periksa beberapa izin sekaligus:
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]));Minta hanya bila statusnya bukan denied, dan katakan apa yang harus dilakukan bila statusnya itu:
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));
}Catatan: Menjawab camera, microphone, geolocation, notifications, screen-wake-lock, clipboard-read, clipboard-write. Kemampuan yang dimatikan saat build (atau yang izinnya tidak ada di manifes) adalah denied. Status langsung dibaca ulang pada appmint:permissions-changed (dipicu setelah setiap dialog izin) dan saat halaman terlihat lagi.
getAppName jembatan#
window.WebToApk.getAppName(): String
Nama aplikasi yang dipilih pembuat di wizard - yang ditampilkan launcher.
Contoh
Memberikan nama aplikasi yang Anda pilih di wizard build - nama di bawah ikon launcher.
Mengembalikan: string langsung. Membutuhkan: tidak ada.
Gunakan nama aplikasi yang sebenarnya di judul dan teks berbagi.
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 + '!' });
});Taruh nama aplikasi di judul halaman dan judul bagian:
if (window.WebToApk && WebToApk.getAppName) {
const name = WebToApk.getAppName();
document.title = name;
document.querySelector('h1').textContent = name;
}Buat email dukungan yang menyebut nama aplikasi dan versinya:
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();Catatan: Ini adalah nama dari wizard, bukan <title> halaman. Tidak berubah saat dijalankan. Untuk versi, gunakan getAppVersion().
getAppVersion jembatan#
window.WebToApk.getAppVersion(): String
versionName dari paket terpasang, mis. "1.4.2" - string yang ditetapkan pembuat saat build, bukan versionCode. Kembali ke "1.0" bila paket tidak bisa dibaca, sehingga halaman dapat mencetaknya tanpa pemeriksaan null.
Contoh
Memberikan nama versi aplikasi yang terpasang, misalnya "1.4.2".
Mengembalikan: string langsung. Membutuhkan: tidak ada.
Tampilkan versi di layar Tentang.
var version = (window.WebToApk && window.WebToApk.getAppVersion)
? WebToApk.getAppVersion()
: 'web';
document.getElementById('about').textContent = 'Version ' + version;Minta pembaruan bila versi terpasang sudah lama:
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;
}Tambahkan versi dan kode versi dari getDeviceInfo() ke laporan 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();Catatan: Ini adalah NAMA versi yang Anda tetapkan saat build, bukan kode versi. Bila paket tidak bisa dibaca, mengembalikan "1.0", jadi tidak pernah kosong. Kode versi dan tanggal instalasi ada di getDeviceInfo() di bawah app.
getDeviceInfo jembatan#
window.WebToApk.getDeviceInfo(): String
WebToApk.getDeviceInfo() -> string JSON, TANPA izin WebToApk.getPhoneIdentity() -> operator/SIM, perlu izin Telepon WebToApk.getInstallId() -> UUID stabil per instalasi
Dijelaskan oleh grupnya, Informasi perangkat / aplikasi, bukan secara tersendiri.
Contoh
Memberikan fakta tentang ponsel, Android, layar, aplikasi Anda, dan keadaan saat ini (baterai, memori, jaringan) dalam satu panggilan.
Mengembalikan: string JSON langsung - gunakan JSON.parse. Kunci tingkat atas: android, hardware, screen, app, runtime, installId. Membutuhkan: tidak ada - tanpa izin.
Tampilkan informasi telepon.
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;
}Semua kuncinya:
{
"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-…"
}Beri peringatan di ponsel dengan penyimpanan atau memori rendah sebelum unduhan besar.
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;
}Catatan: socManufacturer / socModel hanya ada di Android 12+. networkType adalah wifi, cellular, ethernet, other, none atau unknown. installer adalah com.android.vending untuk instalasi Play dan kosong untuk side-load. Nilai dibaca saat Anda memanggil - panggil lagi untuk menyegarkan baterai atau jaringan. Tidak ada IMEI atau nomor seri: Android memblokirnya untuk semua aplikasi; gunakan getInstallId().
getInstallId jembatan#
window.WebToApk.getInstallId(): String
WebToApk.getDeviceInfo() -> string JSON, TANPA izin WebToApk.getPhoneIdentity() -> operator/SIM, perlu izin Telepon WebToApk.getInstallId() -> UUID stabil per instalasi
Dijelaskan oleh grupnya, Informasi perangkat / aplikasi, bukan secara tersendiri.
Contoh
Memberikan ID acak yang tetap sama untuk instalasi aplikasi Anda ini.
Mengembalikan: string (UUID seperti "3f2b…-…") langsung. Membutuhkan: tidak ada.
Gunakan sebagai kunci perangkat untuk server Anda atau untuk pemeriksaan "satu uji coba gratis per perangkat".
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() })
});Kirim id ke server Anda lewat header dan jauhkan dari 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();
}Lampirkan ke laporan crash agar masalah satu instalasi dapat ditemukan.
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');
}Catatan: ID dibuat saat pertama dipakai dan disimpan di penyimpanan aplikasi. ID tetap sama setelah pembaruan aplikasi. ID berubah bila pengguna menghapus aplikasi atau menghapus datanya. Ini bukan ID perangkat keras - Android tidak memberikan IMEI atau serial ke aplikasi. Nilai yang sama juga ada di getDeviceInfo() sebagai installId.
getPhoneIdentity jembatan#
window.WebToApk.getPhoneIdentity(): String
WebToApk.getDeviceInfo() -> string JSON, TANPA izin WebToApk.getPhoneIdentity() -> operator/SIM, perlu izin Telepon WebToApk.getInstallId() -> UUID stabil per instalasi
Dijelaskan oleh grupnya, Informasi perangkat / aplikasi, bukan secara tersendiri.
Contoh
Memberikan fakta operator seluler dan SIM: nama operator, negara SIM, kode operator, jenis telepon, dan status SIM.
Mengembalikan: string JSON langsung - gunakan JSON.parse: { carrierName, simCountryIso, networkCountryIso, simOperator, phoneType, simState, hardwareIdAvailable: false }, atau { error }. Membutuhkan: aktifkan Telepon di Langkah 4 (Akses) saat Anda membuat build; jika tidak, mengembalikan { "error": "not_enabled" }.
Tampilkan operator dan pilih kode negara.
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"
}Pilih kode panggilan dari negara SIM untuk formulir pendaftaran.
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();Bedakan tablet khusus Wi-Fi dari ponsel dengan 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
}
}Catatan: phoneType adalah gsm, cdma, sip atau none (tablet khusus Wi-Fi). simOperator adalah kode MCC+MNC (mis. "40445"). Tidak ada IMEI, MEID atau serial - hardwareIdAvailable selalu false, karena Android menyembunyikannya dari semua aplikasi sejak Android 10; gunakan getInstallId() untuk id yang stabil. Sakelar Telepon menambahkan izin sensitif ke listing Play Anda; deklarasikan untuk apa Anda memakainya di formulir Keamanan data.
isTv jembatan#
window.WebToApk.isTv(): Boolean
True bila aplikasi berjalan di televisi (Android TV, Google TV, Fire TV).
Contoh
Memberi tahu halaman apakah aplikasi berjalan di televisi (Android TV, Google TV, Fire TV), sehingga dapat menampilkan tata letak yang ramah remote.
Mengembalikan: true atau false (sinkron). Membutuhkan: tidak ada. Dengan dukungan Android TV aktif di wizard, aplikasi juga mendapat banner TV di launcher TV, dan di TV halaman mendapat cincin fokus yang jelas serta tautan, tombol, kolom, dan elemen yang dihubungkan dengan onclick atau peran tombol dapat dijangkau dengan panah remote; OK mengeklik elemen yang difokuskan dan Kembali berfungsi seperti di ponsel.
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); }Pilih layar beranda yang berbeda untuk TV dan pertahankan tata letak ponsel sebagai default.
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; });Catatan: Uji dengan emulator Android TV dan D-pad-nya. Menerbitkan ke TV di Google Play adalah langkah terpisah: di Play Console, ikutkan aplikasi ke form factor Android TV (Rilis → Pengaturan lanjutan → Form factor) dan tambahkan tangkapan layar TV.