Halaman toko Budi kini memiliki beberapa fitur yang masing-masing meminta izin: salin kode promo (clipboard), cari cabang terdekat (geolocation), dan pengingat stok (notifications). Menampilkan ketiga dialog izin sekaligus saat halaman dibuka adalah resep penolakan massal — pengunjung yang belum paham konteks akan menolak semuanya, dan penolakan yang tersimpan membuat fitur tidak pernah berfungsi kembali. Cara yang lebih baik: tanyakan izin saat fiturnya benar-benar dipakai, dan ketahui lebih dulu apa status izin yang sudah tersimpan.

Permissions API menyediakan pembacaan itu lewat navigator.permissions.query() — satu antarmuka untuk melihat status izin semua API berizin: granted, denied, atau prompt. Status ini tidak diberikan lewat Permissions API sendiri; ia hanya melaporkan, lalu pemberian izin tetap terjadi pada API masing-masing seperti yang sudah kita lihat pada readText() dan getCurrentPosition().

Membaca Status dengan permissions.query #

Aturannya: navigator.permissions.query({ name: namaIzin }) mengembalikan promise yang terisi object PermissionStatus — dengan properti state berisi status saat ini dan listener untuk perubahannya. Berkas berikut merangkai pemeriksaan izin dengan fitur salin promo:

js cek-izin.js
// cek-izin.js — memeriksa status izin sebelum memakai API browser
// Permissions API hanya ada di browser;
// berkas ini lolos node --check dan dijalankan pada halaman toko.

// --- 1. membaca status izin sebuah API ---
async function periksaIzin(nama) {
  try {
    const status = await navigator.permissions.query({ name: nama });
    console.log(`${nama}: ${status.state}`);
    return status;
  } catch (error) {
    console.log(`${nama}: query tidak didukung (${error.name})`);
    return null;
  }
}

// --- 2. pola degrade gracefully untuk fitur salin ---
async function siapkanFiturSalin() {
  if (!navigator.clipboard) {
    console.log("Tanpa clipboard: tampilkan kode manual DISKON20.");
    return;
  }

  const status = await periksaIzin("clipboard-write");
  if (status === null) return;

  if (status.state === "granted") {
    console.log("Tombol salin siap: izin sudah diberikan.");
  } else if (status.state === "denied") {
    console.log("Izin ditolak: tampilkan kode manual DISKON20.");
  } else {
    console.log("Izin prompt: tombol salin tetap aktif, dialog muncul saat ditekan.");
  }

  // perubahan status (misalnya pengguna mengubah pengaturan situs)
  status.addEventListener("change", () => {
    console.log(`Status clipboard-write berubah: ${status.state}`);
  });
}

siapkanFiturSalin();

Permissions API tidak tersedia di Node maupun jsdom, sehingga berkas ini diverifikasi sintaksnya dengan node --check cek-izin.js; keluaran berikut dicatat sebagai keluaran console browser sesuai perilaku standar. Pada kunjungan pertama — izin belum pernah diminta — console mencatat:

Keluaran console browser:

text
clipboard-write: prompt
Izin prompt: tombol salin tetap aktif, dialog muncul saat ditekan.

Setelah pengunjung memberi izin dan halaman dibuka lagi, status tersimpan dan deretannya berubah menjadi:

Keluaran console browser:

text
clipboard-write: granted
Tombol salin siap: izin sudah diberikan.

Mari kita telaah baris-baris pentingnya:

  1. Baris 6–15 (function periksaIzin): query() dipanggil dengan object { name: nama }. Promise-nya selesai dengan PermissionStatus; kegagalan ditangkap try-catch karena nama izin yang tidak dikenal melempar TypeError — halaman tidak boleh berhenti hanya karena satu query gagal.
  2. Baris 9 (status.state): string berisi granted, denied, atau prompt. Nilai inilah seluruh jawaban yang kita butuhkan untuk menentukan jalur UI.
  3. Baris 19–22 (pemeriksaan keberadaan API): sebelum urusan izin, fitur diperiksa keberadaannya. Browser tanpa navigator.clipboard tidak akan pernah bisa menyalin — jalur manual menjadi satu-satunya pilihan, tanpa memanggil query sama sekali.
  4. Baris 27–33 (tiga cabang status): granted berarti tombol siap dipakai tanpa dialog; denied berarti jangan meminta lagi — tampilkan jalur manual; prompt berarti tombol tetap aktif, dan dialog izin baru muncul ketika pengunjung benar-benar menekannya.
  5. Baris 36–38 (addEventListener("change", ...)): status izin bukan nilai statis. Pengunjung dapat membuka pengaturan situs lalu membalik izinnya; saat itu terjadi, listener ini dijalankan dan UI bisa disesuaikan tanpa memuat ulang halaman.

Arti Tiga Status #

Setiap status menuntut sikap yang berbeda dari halaman kita:

StatusArtiSikap halaman
grantedizin sudah diberikanfitur aktif penuh; pemanggilan API tidak memunculkan dialog
deniedizin ditolak dan tersimpanjangan meminta lagi; sediakan jalur alternatif manual
promptbelum pernah dimintafitur tetap aktif; dialog muncul saat API dipanggil lewat aksi pengguna

Status denied paling perlu dihormati: browser menyimpan penolakan itu, dan dialog izin tidak akan ditampilkan lagi oleh pemanggilan biasa. Halaman yang terus mencoba memakai API yang ditolak hanya mengumpulkan galat di console. Satu-satunya jalan kembali adalah pengunjung membuka pengaturan situs sendiri — karena itu listener change berharga: begitu pengunjung membalik statusnya, halaman bisa langsung menyesuaikan diri.

Nama izin yang umum diperiksa mengikuti API-nya: "geolocation", "clipboard-read", "clipboard-write", "notifications", "camera", dan "microphone". Tidak semua nama didukung semua browser — beberapa izin bahkan tidak punya status yang bisa dibaca — dan itulah alasan periksaIzin selalu mengembalikan null pada kegagalan, bukan melempar galat ke pemanggil.

Pola Degrade Gracefully #

Rangkaian di cek-izin.js mengikuti pola yang berlaku untuk semua API berizin — dan sebagian besar API browser pada umumnya. Pola ini disebut degrade gracefully: fitur tetap berfungsi sebagaimana kemampuan lingkungannya, dan bagian yang tidak tersedia digantikan alternatif, bukan menggagalkan seluruh halaman. Empat langkahnya:

  1. Periksa keberadaan API (if (!navigator.clipboard)): lingkungan tidak mendukung sama sekali → jalur manual.
  2. Periksa status izin (permissions.query): lingkungan mendukung, status denied → jalur manual; granted → langsung pakai.
  3. Minta lewat aksi pengguna: status prompt → fitur tetap ditayangkan; dialog izin muncul tepat saat pengunjung menekan tombolnya.
  4. Dengarkan perubahan (change): status bisa berbalik kapan saja; sesuaikan UI tanpa memuat ulang.

Urutan langkah itu juga menjelaskan letak Permissions API di section ini: ia bekerja di atas semua API berizin yang sudah kita pelajari — clipboard, geolocation, dan notifications. Dengan satu function periksaIzin, halaman toko Budi kini tahu kapan boleh langsung beraksi, kapan harus menunggu dialog, dan kapan sebaiknya menyerahkan pilihan sepenuhnya kepada pengunjung.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

cek-izin.js

Unduh berkas
javascript
// cek-izin.js — memeriksa status izin sebelum memakai API browser
// Permissions API hanya ada di browser;
// berkas ini lolos node --check dan dijalankan pada halaman toko.

// --- 1. membaca status izin sebuah API ---
async function periksaIzin(nama) {
  try {
    const status = await navigator.permissions.query({ name: nama });
    console.log(`${nama}: ${status.state}`);
    return status;
  } catch (error) {
    console.log(`${nama}: query tidak didukung (${error.name})`);
    return null;
  }
}

// --- 2. pola degrade gracefully untuk fitur salin ---
async function siapkanFiturSalin() {
  if (!navigator.clipboard) {
    console.log("Tanpa clipboard: tampilkan kode manual DISKON20.");
    return;
  }

  const status = await periksaIzin("clipboard-write");
  if (status === null) return;

  if (status.state === "granted") {
    console.log("Tombol salin siap: izin sudah diberikan.");
  } else if (status.state === "denied") {
    console.log("Izin ditolak: tampilkan kode manual DISKON20.");
  } else {
    console.log("Izin prompt: tombol salin tetap aktif, dialog muncul saat ditekan.");
  }

  // perubahan status (misalnya pengguna mengubah pengaturan situs)
  status.addEventListener("change", () => {
    console.log(`Status clipboard-write berubah: ${status.state}`);
  });
}

siapkanFiturSalin();