Buat APK Gratis

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

navigator.geolocation standar web#

navigator.geolocation

Contoh

API lokasi web standar berfungsi di dalam aplikasi. Aplikasi menampilkan permintaan lokasi bawaan Android dan meneruskan jawabannya ke halaman.

Mengembalikan: callback W3C normal: sebuah GeolocationPosition (coords.latitude, coords.longitude, coords.accuracy, …) atau sebuah GeolocationPositionError (code 1 = izin ditolak, 2 = tidak tersedia, 3 = batas waktu). Membutuhkan: GPS (presisi) dan/atau Lokasi (perkiraan) di Langkah 4 (Akses). Bila Anda membuat build dari ZIP atau halaman HTML dan kode Anda memanggil navigator.geolocation, getCurrentPosition atau watchPosition, build menemukan panggilan itu dan menambahkan lokasi untuk Anda. Untuk build situs web (URL), centang sendiri.

Dapatkan posisi satu kali.

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

Ikuti pengguna saat mereka bergerak (pelacak lari, peta pengiriman). Berhenti memantau saat layar ditutup.

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

Periksa izin dulu, lalu minta posisi hanya bila bisa dijawab:

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

Catatan: Dengan GPS aktif, aplikasi meminta lokasi presisi dan perkiraan bersamaan; di Android 12+ pengguna dapat memilih "Perkiraan", yang tetap berfungsi (akurasi lebih rendah). Dengan hanya Lokasi aktif, hanya lokasi perkiraan yang diminta. Jika keduanya nonaktif, setiap panggilan gagal dengan kode 1 dan tanpa permintaan. Setelah pengguna mengizinkan sekali, panggilan berikutnya dijawab tanpa permintaan. Sakelar lokasi ponsel sendiri juga harus aktif. Lokasi adalah data pribadi: deklarasikan di formulir Keamanan data Play Anda; lokasi latar belakang tidak disediakan.

navigator.getInstalledRelatedApps standar web#

navigator.getInstalledRelatedApps()

Contoh

API aplikasi terkait standar: memberi tahu halaman mana dari aplikasi Android miliknya sendiri (tercantum di manifes aplikasi webnya) yang terpasang - untuk menyembunyikan banner "Pasang aplikasi kami yang lain", atau menautkan langsung ke aplikasi itu.

Mengembalikan: Promise berisi array entri related_applications manifes yang terpasang: { platform: 'play', id, url?, version? }. Membutuhkan: sebuah <link rel="manifest"> yang manifesnya mencantumkan aplikasi dengan "platform": "play" dan "id": "<package name>", disertakan dalam aplikasi agar build dapat mendeklarasikannya (Android hanya mengizinkan aplikasi melihat paket yang disebutkan namanya).

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

Buka aplikasi pendamping bila terpasang (url entri berasal dari manifes Anda), jika tidak arahkan pengguna ke halaman tokonya:

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

Tampilkan aplikasi Anda mana yang sudah ada di ponsel pada layar "Lainnya dari kami":

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

Catatan: build membaca manifes di bundel Anda dan mendeklarasikan setiap paket yang tercantum; aplikasi yang tidak tercantum di sana tidak dapat dilihat dan tidak pernah dilaporkan. Aplikasi mode situs web menyimpan manifesnya di situs, jadi tidak ada yang dideklarasikan dan daftarnya kosong. Transport internal: __relatedAppsInstalled.

navigator.permissions standar web#

navigator.permissions.query({ name })

Contoh

Permissions API standar, dijawab dari status izin Android sebenarnya di ponsel - sehingga halaman dapat menampilkan "Izinkan kamera" hanya bila masih bisa diminta, dan "Aktifkan di Pengaturan" bila tidak bisa.

Mengembalikan: Promise berisi PermissionStatus {name, state, onchange}; state adalah 'granted', 'denied' atau 'prompt', dan change dipicu tepat setelah dialog izin ditutup atau saat pengguna kembali dari Pengaturan. Membutuhkan: tidak ada yang perlu diaktifkan.

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

Minta hanya bila halaman masih bisa meminta, dan arahkan pengguna ke Pengaturan bila tidak:

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

Bereaksi saat pengguna mengubah izin di Pengaturan, dan gabungkan dengan permintaan nyata:

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

Catatan: denied juga mencakup kemampuan yang tidak ada saat aplikasi dibuat (sakelarnya nonaktif di build, sehingga Android tidak pernah bisa memberikannya) dan penolakan "Jangan tanya lagi". screen-wake-lock, clipboard-read dan clipboard-write adalah 'granted' (aplikasi menyediakannya tanpa permintaan). Nama lain (midi, accelerometer, …) dijawab oleh WebView seperti sebelumnya. Event di baliknya: appmint:permissions-changed.

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.

__relatedAppsInstalled jembatan#

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

navigator.getInstalledRelatedApps(): mana dari [idsJson] (nama paket dari manifes halaman) yang terpasang dan terlihat oleh aplikasi ini.

Contoh

Transport internal di balik navigator.getInstalledRelatedApps(). Halaman memakai API standar; jangan pernah memanggil ini langsung - metode __ dapat berubah tanpa pemberitahuan.

Mengembalikan: array JSON berisi nama paket yang diberikan yang terpasang dan terlihat oleh aplikasi ini. Membutuhkan: paket yang dideklarasikan saat build (dari manifes aplikasi web yang disertakan).

Gunakan API publik:

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

Tampilkan tombol "Dapatkan aplikasi pendamping" hanya bila belum terpasang:

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

Paket berasal dari manifes aplikasi web yang ditautkan halaman:

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

Catatan: Diimplementasikan di shell aplikasi (IdleAndRelatedApps.kt).

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.

Dibuat dari runtime aplikasi dan file contohnya pada setiap build dokumentasi. Baca sebagai Markdown · Semua keluarga.

Diperiksa terhadap jembatan yang dirilis pada 2026-10-22.