Membuat Langganan Pengguna

Langkah pertama adalah mendapatkan izin dari pengguna untuk mengirim pesan push kepada mereka, lalu kita dapat mendapatkan PushSubscription.

JavaScript API untuk melakukan hal ini cukup mudah, jadi mari kita bahas alur logikanya.

Deteksi fitur

Pertama, kita perlu memeriksa apakah browser saat ini benar-benar mendukung pesan push. Kita dapat memeriksa apakah push didukung dengan dua pemeriksaan sederhana.

  1. Periksa serviceWorker di navigator.
  2. Periksa PushManager di window.
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

Meskipun dukungan browser berkembang dengan cepat untuk pekerja layanan dan pesan push, sebaiknya selalu deteksi fitur untuk keduanya dan tingkatkan secara bertahap.

Mendaftarkan pekerja layanan

Dengan deteksi fitur, kita tahu bahwa pekerja layanan dan Push didukung. Langkah berikutnya adalah "mendaftarkan" pekerja layanan.

Saat mendaftarkan service worker, kita memberi tahu browser tempat file service worker berada. File ini masih berupa JavaScript, tetapi browser akan "memberinya akses" ke API service worker, termasuk push. Lebih tepatnya, browser menjalankan file di lingkungan pekerja layanan.

Untuk mendaftarkan pekerja layanan, panggil navigator.serviceWorker.register(), dengan meneruskan jalur ke file kita. Contoh:

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

Fungsi ini memberi tahu browser bahwa kita memiliki file pekerja layanan dan lokasinya. Dalam hal ini, file pekerja layanan berada di /service-worker.js. Di balik layar, browser akan melakukan langkah-langkah berikut setelah memanggil register():

  1. Download file pekerja layanan.

  2. Jalankan JavaScript.

  3. Jika semuanya berjalan dengan benar dan tidak ada error, promise yang ditampilkan oleh register() akan di-resolve. Jika ada error apa pun, promise akan ditolak.

Jika register() menolak, periksa kembali JavaScript Anda untuk menemukan kesalahan ketik / error di Chrome DevTools.

Saat register() di-resolve, ServiceWorkerRegistration akan ditampilkan. Kita akan menggunakan pendaftaran ini untuk mengakses PushManager API.

Kompatibilitas browser PushManager API

Dukungan Browser

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Sumber

Meminta izin

Kita telah mendaftarkan pekerja layanan dan siap untuk membuat pengguna berlangganan. Langkah berikutnya adalah mendapatkan izin dari pengguna untuk mengirim pesan push kepada mereka.

API untuk mendapatkan izin relatif sederhana, tetapi kelemahannya adalah API baru-baru ini berubah dari mengambil callback menjadi menampilkan Promise. Masalahnya, kita tidak dapat mengetahui versi API yang diterapkan oleh browser saat ini, sehingga Anda harus menerapkan keduanya dan menangani keduanya.

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

Dalam kode di atas, cuplikan kode yang penting adalah panggilan ke Notification.requestPermission(). Metode ini akan menampilkan perintah kepada pengguna:

Permintaan izin di Chrome desktop dan seluler.

Setelah pengguna berinteraksi dengan dialog izin dengan menekan Izinkan, Blokir, atau hanya menutupnya, kita akan diberi hasilnya sebagai string: 'granted', 'default', atau 'denied'.

Dalam contoh kode di atas, promise yang ditampilkan oleh askPermission() akan di-resolve jika izin diberikan, jika tidak, kita akan menampilkan error yang membuat promise ditolak.

Salah satu kasus ekstrem yang perlu Anda tangani adalah jika pengguna mengklik tombol 'Blokir'. Jika hal ini terjadi, aplikasi web Anda tidak akan dapat meminta izin lagi kepada pengguna. Mereka harus "membatalkan pemblokiran" aplikasi Anda secara manual dengan mengubah status izinnya, yang tersembunyi di panel setelan. Pikirkan dengan cermat cara dan waktu Anda meminta izin kepada pengguna, karena jika mereka mengklik blokir, tidak mudah untuk membatalkan keputusan tersebut.

Kabar baiknya adalah sebagian besar pengguna dengan senang hati memberikan izin selama mereka tahu alasan izin diminta.

Kita akan melihat cara beberapa situs populer meminta izin nanti.

Membuat pengguna berlangganan dengan PushManager

Setelah mendaftarkan pekerja layanan dan mendapatkan izin, kita dapat membuat pengguna berlangganan dengan memanggil registration.pushManager.subscribe().

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

Saat memanggil metode subscribe(), kita meneruskan objek options, yang terdiri dari parameter wajib dan opsional.

Mari kita lihat semua opsi yang dapat kita teruskan.

opsi userVisibleOnly

Saat push pertama kali ditambahkan ke browser, ada ketidakpastian tentang apakah developer harus dapat mengirim pesan push dan tidak menampilkan notifikasi. Hal ini biasanya disebut sebagai push senyap, karena pengguna tidak mengetahui bahwa sesuatu telah terjadi di latar belakang.

Kekhawatirannya adalah developer dapat melakukan hal-hal buruk seperti melacak lokasi pengguna secara berkelanjutan tanpa sepengetahuan pengguna.

Untuk menghindari skenario ini dan memberi penulis spesifikasi waktu untuk mempertimbangkan cara terbaik untuk mendukung fitur ini, opsi userVisibleOnly ditambahkan dan meneruskan nilai true adalah perjanjian simbolis dengan browser bahwa aplikasi web akan menampilkan notifikasi setiap kali push diterima (yaitu tidak ada push senyap).

Saat ini, Anda harus meneruskan nilai true. Jika tidak menyertakan kunci userVisibleOnly atau meneruskan false, Anda akan mendapatkan error berikut:

Chrome saat ini hanya mendukung Push API untuk langganan yang akan menghasilkan pesan yang terlihat oleh pengguna. Anda dapat menunjukkannya dengan memanggil pushManager.subscribe({userVisibleOnly: true}). Lihat https://goo.gl/yqv4Q4 untuk mengetahui detail selengkapnya.

Saat ini sepertinya push senyap menyeluruh tidak akan pernah diterapkan di Chrome. Sebagai gantinya, penulis spesifikasi sedang mempelajari konsep API anggaran yang akan memungkinkan aplikasi web mengirim sejumlah pesan push senyap berdasarkan penggunaan aplikasi web.

Opsi applicationServerKey

Kita telah menyebutkan "kunci server aplikasi" secara singkat di bagian sebelumnya. "Kunci server aplikasi" digunakan oleh layanan push untuk mengidentifikasi aplikasi yang berlangganan pengguna dan memastikan bahwa aplikasi yang sama mengirim pesan kepada pengguna tersebut.

Kunci server aplikasi adalah pasangan kunci publik dan pribadi yang unik untuk aplikasi Anda. Kunci pribadi harus dirahasiakan untuk aplikasi Anda dan kunci publik dapat dibagikan secara bebas.

Opsi applicationServerKey yang diteruskan ke panggilan subscribe() adalah kunci publik aplikasi. Browser meneruskannya ke layanan push saat pengguna berlangganan, yang berarti layanan push dapat mengaitkan kunci publik aplikasi Anda ke PushSubscription pengguna.

Diagram di bawah ini menggambarkan langkah-langkah ini.

  1. Aplikasi web Anda dimuat di browser dan Anda memanggil subscribe(), yang meneruskan kunci server aplikasi publik Anda.
  2. Browser kemudian membuat permintaan jaringan ke layanan push yang akan membuat endpoint, mengaitkan endpoint ini dengan kunci publik aplikasi, dan menampilkan endpoint ke browser.
  3. Browser akan menambahkan endpoint ini ke PushSubscription, yang ditampilkan melalui promise subscribe().

Ilustrasi kunci server aplikasi publik digunakan dalam metode
subscribe.

Jika nanti ingin mengirim pesan push, Anda harus membuat header Authorization yang akan berisi informasi yang ditandatangani dengan kunci pribadi server aplikasi Anda. Saat layanan push menerima permintaan untuk mengirim pesan push, layanan tersebut dapat memvalidasi header Authorization bertanda tangan ini dengan mencari kunci publik yang ditautkan ke endpoint yang menerima permintaan. Jika tanda tangan valid, layanan push akan mengetahui bahwa tanda tangan tersebut harus berasal dari server aplikasi dengan kunci pribadi yang cocok. Ini pada dasarnya adalah langkah keamanan yang mencegah orang lain mengirim pesan kepada pengguna aplikasi.

Cara kunci server aplikasi pribadi digunakan saat mengirim
pesan

Secara teknis, applicationServerKey bersifat opsional. Namun, penerapan termudah di Chrome memerlukannya, dan browser lain mungkin memerlukannya di masa mendatang. Kolom ini bersifat opsional di Firefox.

Spesifikasi yang menentukan apa yang harus menjadi kunci server aplikasi adalah spesifikasi VAPID. Setiap kali Anda membaca sesuatu yang merujuk pada "kunci server aplikasi" atau "kunci VAPID", ingatlah bahwa keduanya adalah hal yang sama.

Cara membuat kunci server aplikasi

Anda dapat membuat kumpulan kunci server aplikasi publik dan pribadi dengan membuka web-push-codelab.glitch.me atau menggunakan command line web-push untuk membuat kunci dengan melakukan hal berikut:

    $ npm install -g web-push
    $ web-push generate-vapid-keys

Anda hanya perlu membuat kunci ini satu kali untuk aplikasi Anda, cukup pastikan Anda menjaga kunci pribadi tetap bersifat pribadi. (Ya, saya baru saja mengatakannya.)

Izin dan subscribe()

Ada satu efek samping dari memanggil subscribe(). Jika aplikasi web Anda tidak memiliki izin untuk menampilkan notifikasi pada saat memanggil subscribe(), browser akan meminta izin untuk Anda. Hal ini berguna jika UI Anda berfungsi dengan alur ini, tetapi jika Anda menginginkan kontrol yang lebih besar (dan saya rasa sebagian besar developer menginginkannya), tetap gunakan Notification.requestPermission() API yang kita gunakan sebelumnya.

Apa yang dimaksud dengan PushSubscription?

Kita memanggil subscribe(), meneruskan beberapa opsi, dan sebagai hasilnya, kita mendapatkan promise yang me-resolve ke PushSubscription yang menghasilkan beberapa kode seperti ini:

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

Objek PushSubscription berisi semua informasi yang diperlukan untuk mengirim pesan push kepada pengguna tersebut. Jika mencetak konten menggunakan JSON.stringify(), Anda akan melihat hal berikut:

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

endpoint adalah URL layanan push. Untuk memicu pesan push, buat permintaan POST ke URL ini.

Objek keys berisi nilai yang digunakan untuk mengenkripsi data pesan yang dikirim dengan pesan push (yang akan kita bahas nanti di bagian ini).

Langganan ulang reguler untuk mencegah masa berlaku habis

Saat berlangganan notifikasi push, Anda sering menerima PushSubscription.expirationTime dari null. Secara teori, ini berarti langganan tidak pernah berakhir (berbeda dengan saat Anda menerima DOMHighResTimeStamp, yang memberi tahu Anda waktu persis saat langganan berakhir). Namun, dalam praktiknya, browser biasanya tetap mengizinkan langganan berakhir, misalnya, jika tidak ada notifikasi push yang diterima dalam waktu yang lebih lama, atau jika browser mendeteksi bahwa pengguna tidak menggunakan aplikasi yang memiliki izin notifikasi push. Salah satu pola untuk mencegah hal ini adalah dengan membuat pengguna berlangganan kembali setelah setiap notifikasi diterima, seperti yang ditunjukkan dalam cuplikan berikut. Hal ini mengharuskan Anda mengirim notifikasi cukup sering agar browser tidak otomatis menghentikan langganan, dan Anda harus mempertimbangkan dengan sangat cermat kelebihan dan kekurangan kebutuhan notifikasi yang sah dibandingkan dengan mengirim spam kepada pengguna secara tidak sengaja agar langganan tidak berakhir. Pada akhirnya, Anda tidak boleh mencoba melawan browser dalam upayanya melindungi pengguna dari langganan notifikasi yang sudah lama terlupakan.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Mengirim langganan ke Server Anda

Setelah memiliki langganan push, Anda dapat mengirimkannya ke server. Anda dapat melakukannya dengan cara apa saja, tetapi ada tips kecil untuk menggunakan JSON.stringify() guna mendapatkan semua data yang diperlukan dari objek langganan. Atau, Anda dapat menggabungkan hasil yang sama secara manual seperti ini:

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

Pengiriman langganan dilakukan di halaman web seperti ini:

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

Server node menerima permintaan ini dan menyimpan data ke database untuk digunakan nanti.

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

  return saveSubscriptionToDatabase(req.body)
    .then(function (subscriptionId) {
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify({data: {success: true}}));
    })
    .catch(function (err) {
      res.status(500);
      res.setHeader('Content-Type', 'application/json');
      res.send(
        JSON.stringify({
          error: {
            id: 'unable-to-save-subscription',
            message:
              'The subscription was received but we were unable to save it to our database.',
          },
        }),
      );
    });
});

Dengan detail PushSubscription di server, kita dapat mengirim pesan kepada pengguna setiap kali kita mau.

Langganan ulang reguler untuk mencegah masa berlaku habis

Saat berlangganan notifikasi push, Anda sering menerima PushSubscription.expirationTime dari null. Secara teori, ini berarti langganan tidak pernah berakhir (berbeda dengan saat Anda menerima DOMHighResTimeStamp, yang memberi tahu Anda waktu persis saat langganan berakhir). Namun, dalam praktiknya, browser biasanya tetap mengizinkan langganan berakhir, misalnya, jika tidak ada notifikasi push yang diterima dalam waktu lama, atau jika browser mendeteksi bahwa pengguna tidak menggunakan aplikasi yang memiliki izin notifikasi push. Salah satu pola untuk mencegah hal ini adalah dengan membuat pengguna berlangganan kembali setelah setiap notifikasi diterima, seperti yang ditunjukkan dalam cuplikan berikut. Hal ini mengharuskan Anda mengirim notifikasi cukup sering agar browser tidak otomatis menghentikan langganan, dan Anda harus mempertimbangkan dengan sangat cermat kelebihan dan kekurangan kebutuhan notifikasi yang sah dibandingkan dengan mengirim spam kepada pengguna hanya agar langganan tidak berakhir. Pada akhirnya, Anda tidak boleh mencoba melawan browser dalam upayanya melindungi pengguna dari langganan notifikasi yang sudah lama terlupakan.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

FAQ

Beberapa pertanyaan umum yang diajukan orang pada tahap ini:

Dapatkah saya mengubah layanan push yang digunakan browser?

Tidak. Layanan push dipilih oleh browser dan seperti yang kita lihat dengan panggilan subscribe(), browser akan membuat permintaan jaringan ke layanan push untuk mengambil detail yang membentuk PushSubscription.

Setiap browser menggunakan Layanan Push yang berbeda, bukankah mereka memiliki API yang berbeda?

Semua layanan push akan mengharapkan API yang sama.

API umum ini disebut Web Push Protocol dan menjelaskan permintaan jaringan yang harus dilakukan aplikasi Anda untuk memicu pesan push.

Jika saya membuat pengguna berlangganan di desktop, apakah dia juga akan berlangganan di ponselnya?

Sayangnya, tidak. Pengguna harus mendaftar untuk push di setiap browser yang ingin digunakan untuk menerima pesan. Perlu juga diperhatikan bahwa hal ini akan mengharuskan pengguna memberikan izin di setiap perangkat.

Langkah berikutnya

Codelab