Membangun server notifikasi push

Dalam codelab ini, Anda akan membangun server notifikasi push. Server akan mengelola daftar langganan push dan mengirimkan notifikasi kepada mereka.

Kode klien sudah lengkap. Dalam codelab ini, Anda akan mengerjakan fungsi sisi server.

Me-remix aplikasi contoh dan melihatnya di tab baru

Notifikasi diblokir secara otomatis dari aplikasi Glitch yang disematkan, sehingga Anda tidak akan dapat melihat pratinjau aplikasi di halaman ini. Sebagai gantinya, berikut ini hal yang harus dilakukan:

  1. Klik Remix to Edit agar project dapat diedit.
  2. Untuk melihat pratinjau situs, tekan Lihat Aplikasi. Lalu tekan Layar penuh layar penuh.

Aplikasi aktif akan terbuka di tab Chrome baru. Di Glitch yang disematkan, klik View Source untuk menampilkan kode lagi.

Saat Anda mengerjakan codelab ini, buat perubahan pada kode di Glitch yang disematkan di halaman ini. Muat ulang tab baru dengan aplikasi yang sudah aktif untuk melihat perubahan.

Memahami aplikasi awal dan kodenya

Mulailah dengan melihat UI klien aplikasi.

Di tab Chrome baru:

  1. Tekan `Control+Shift+J` (atau `Command+Option+J` di Mac) untuk membuka DevTools. Klik tab Konsol.

  2. Coba klik tombol di UI (periksa konsol dev Chrome untuk output).

    • Mendaftarkan pekerja layanan akan mendaftarkan pekerja layanan untuk cakupan URL project Glitch Anda. Membatalkan pendaftaran pekerja layanan akan menghapus pekerja layanan. Jika langganan push terpasang padanya, langganan push juga akan dinonaktifkan.

    • Berlangganan push akan membuat langganan push. Ini hanya tersedia jika pekerja layanan telah didaftarkan dan konstanta VAPID_PUBLIC_KEY ada dalam kode klien (selengkapnya tentang hal ini akan dibahas nanti), sehingga Anda belum dapat mengkliknya.

    • Saat Anda memiliki langganan push yang aktif, opsi Beri tahu langganan saat ini akan meminta agar server mengirimkan notifikasi ke endpoint-nya.

    • Beri tahu semua langganan memberi tahu server untuk mengirim notifikasi ke semua endpoint langganan dalam database-nya.

      Perlu diperhatikan bahwa beberapa endpoint ini mungkin tidak aktif. Selalu ada kemungkinan bahwa langganan akan hilang pada saat server mengirimkan notifikasi.

Mari kita lihat apa yang terjadi di sisi server. Untuk melihat pesan dari kode server, lihat log Node.js dalam antarmuka Glitch.

  • Di aplikasi Glitch, klik Tools -> Log.

    Anda mungkin akan melihat pesan seperti Listening on port 3000.

    Jika Anda mencoba mengklik Beri tahu langganan saat ini atau Beri tahu semua langganan di UI aplikasi yang sudah tayang, Anda juga akan melihat pesan berikut:

    TODO: Implement sendNotifications()
    Endpoints to send to:  []
    

Sekarang, mari kita lihat beberapa kode.

  • public/index.js berisi kode klien yang sudah selesai. Layanan ini melakukan deteksi fitur, mendaftarkan dan membatalkan pendaftaran pekerja layanan, serta mengontrol langganan pengguna ke notifikasi push. Layanan ini juga mengirimkan informasi tentang langganan baru dan yang dihapus ke server.

    Karena Anda hanya akan mengerjakan fungsi server, Anda tidak akan mengedit file ini (selain mengisi konstanta VAPID_PUBLIC_KEY).

  • public/service-worker.js adalah pekerja layanan sederhana yang merekam peristiwa push dan menampilkan notifikasi.

  • /views/index.html berisi UI aplikasi.

  • .env berisi variabel lingkungan yang dimuat Glitch ke server aplikasi saat Glitch dimulai. Anda akan mengisi .env dengan detail autentikasi untuk mengirim notifikasi.

  • server.js adalah file yang akan Anda gunakan untuk melakukan sebagian besar pekerjaan selama codelab ini.

    Kode awal akan membuat server web Express sederhana. Ada empat item TODO untuk Anda, yang ditandai dalam komentar kode dengan TODO:. Anda harus:

    Dalam codelab ini, Anda akan menggunakan item TODO ini satu per satu.

Membuat dan memuat detail VAPID

Item TODO pertama Anda adalah membuat detail VAPID, menambahkannya ke variabel lingkungan Node.js, serta memperbarui kode klien dan server dengan nilai baru.

Latar belakang

Saat pengguna berlangganan notifikasi, mereka harus memercayai identitas aplikasi dan servernya. Pengguna juga harus yakin bahwa, saat menerima notifikasi, berasal dari aplikasi yang sama yang menyiapkan langganan. Mereka juga harus percaya bahwa tidak ada orang lain yang dapat membaca isi notifikasi.

Protokol yang membuat notifikasi push aman dan bersifat pribadi disebut Voluntary Application Server Identification for Web Push (VAPID). VAPID menggunakan kriptografi kunci publik untuk memverifikasi identitas aplikasi, server, dan endpoint langganan, serta untuk mengenkripsi konten notifikasi.

Dalam aplikasi ini, Anda akan menggunakan paket npm web-push untuk membuat kunci VAPID, serta mengenkripsi dan mengirim notifikasi.

Penerapan

Pada langkah ini, buat sepasang kunci VAPID untuk aplikasi Anda dan tambahkan ke variabel lingkungan. Memuat variabel lingkungan di server dan menambahkan kunci publik sebagai konstanta dalam kode klien.

  1. Gunakan fungsi generateVAPIDKeys dari library web-push untuk membuat sepasang kunci VAPID.

    Di server.js, hapus komentar dari sekitar baris kode berikut:

    server.js

    // Generate VAPID keys (only do this once).
    /*
     * const vapidKeys = webpush.generateVAPIDKeys();
     * console.log(vapidKeys);
     */
    const vapidKeys = webpush.generateVAPIDKeys();
    console.log(vapidKeys);
    
  2. Setelah memulai ulang aplikasi Glitch, Glitch akan menampilkan kunci yang dibuat ke log Node.js dalam antarmuka Glitch (bukan ke konsol Chrome). Untuk melihat kunci VAPID, pilih Tools -> Log di antarmuka Glitch.

    Pastikan Anda menyalin kunci publik dan pribadi dari pasangan kunci yang sama.

    Glitch memulai ulang aplikasi setiap kali Anda mengedit kode, sehingga pasangan kunci pertama yang Anda buat mungkin akan ter-scroll keluar dari tampilan saat lebih banyak output menyusul.

  3. Di .env, salin dan tempel kunci VAPID. Sertakan kunci dalam tanda kutip ganda ("...").

    Untuk VAPID_SUBJECT, Anda dapat memasukkan "mailto:test@test.test".

    .env

    # process.env.SECRET
    VAPID_PUBLIC_KEY=
    VAPID_PRIVATE_KEY=
    VAPID_SUBJECT=
    VAPID_PUBLIC_KEY="BN3tWzHp3L3rBh03lGLlLlsq..."
    VAPID_PRIVATE_KEY="I_lM7JMIXRhOk6HN..."
    VAPID_SUBJECT="mailto:test@test.test"
    
  4. Di server.js, jadikan dua baris kode tersebut sebagai komentar lagi, karena Anda hanya perlu membuat kunci VAPID satu kali.

    server.js

    // Generate VAPID keys (only do this once).
    /*
    const vapidKeys = webpush.generateVAPIDKeys();
    console.log(vapidKeys);
    */
    const vapidKeys = webpush.generateVAPIDKeys();
    console.log(vapidKeys);
    
  5. Di server.js, muat detail VAPID dari variabel lingkungan.

    server.js

    const vapidDetails = {
      // TODO: Load VAPID details from environment variables.
      publicKey: process.env.VAPID_PUBLIC_KEY,
      privateKey: process.env.VAPID_PRIVATE_KEY,
      subject: process.env.VAPID_SUBJECT
    }
    
  6. Salin juga kunci public ke kode klien.

    Di public/index.js, masukkan nilai yang sama untuk VAPID_PUBLIC_KEY seperti yang Anda salin ke dalam file .env:

    public/index.js

    // Copy from .env
    const VAPID_PUBLIC_KEY = '';
    const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...';
    ````
    

Mengimplementasikan fungsi untuk mengirim notifikasi

Latar belakang

Dalam aplikasi ini, Anda akan menggunakan paket npm web-push untuk mengirim notifikasi.

Paket ini otomatis mengenkripsi notifikasi saat webpush.sendNotification() dipanggil, sehingga Anda tidak perlu mengkhawatirkannya.

web-push menerima beberapa opsi untuk notifikasi–misalnya, Anda dapat melampirkan header ke pesan, dan menentukan encoding konten.

Dalam codelab ini, Anda hanya akan menggunakan dua opsi, yang ditentukan dengan baris kode berikut:

let options = {
  TTL: 10000; // Time-to-live. Notifications expire after this.
  vapidDetails: vapidDetails; // VAPID keys from .env
};

Opsi TTL (time-to-live) menetapkan waktu tunggu habis masa berlaku pada notifikasi. Ini adalah cara server untuk menghindari pengiriman notifikasi kepada pengguna setelah tidak lagi relevan.

Opsi vapidDetails berisi kunci VAPID yang Anda muat dari variabel lingkungan.

Penerapan

Di server.js, ubah fungsi sendNotifications sebagai berikut:

server.js

function sendNotifications(database, endpoints) {
  // TODO: Implement functionality to send notifications.
  console.log('TODO: Implement sendNotifications()');
  console.log('Endpoints to send to: ', endpoints);
  let notification = JSON.stringify(createNotification());
  let options = {
    TTL: 10000, // Time-to-live. Notifications expire after this.
    vapidDetails: vapidDetails // VAPID keys from .env
  };
  endpoints.map(endpoint => {
    let subscription = database[endpoint];
    webpush.sendNotification(subscription, notification, options);
  });
}

Karena webpush.sendNotification() menampilkan promise, Anda dapat menambahkan penanganan error dengan mudah.

Di server.js, ubah fungsi sendNotifications lagi:

server.js

function sendNotifications(database, endpoints) {
  let notification = JSON.stringify(createNotification());
  let options = {
    TTL: 10000; // Time-to-live. Notifications expire after this.
    vapidDetails: vapidDetails; // VAPID keys from .env
  };
  endpoints.map(endpoint => {
    let subscription = database[endpoint];
    webpush.sendNotification(subscription, notification, options);
    let id = endpoint.substr((endpoint.length - 8), endpoint.length);
    webpush.sendNotification(subscription, notification, options)
    .then(result => {
      console.log(`Endpoint ID: ${id}`);
      console.log(`Result: ${result.statusCode} `);
    })
    .catch(error => {
      console.log(`Endpoint ID: ${id}`);
      console.log(`Error: ${error.body} `);
    });
  });
}

Menangani langganan baru

Latar belakang

Berikut yang akan terjadi jika pengguna berlangganan notifikasi push:

  1. Pengguna mengklik Berlangganan push.

  2. Klien menggunakan konstanta VAPID_PUBLIC_KEY (kunci VAPID publik server) untuk membuat objek subscription yang unik dan khusus server. Objek subscription akan terlihat seperti ini:

       {
         "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
         "expirationTime": null,
         "keys":
         {
           "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
           "auth": "0IyyvUGNJ9RxJc83poo3bA"
         }
       }
    
  3. Klien mengirim permintaan POST ke URL /add-subscription, termasuk langganan sebagai JSON string dalam isi.

  4. Server mengambil subscription string dari isi permintaan POST, mengurainya kembali ke JSON, dan menambahkannya ke database langganan.

    Database menyimpan langganan menggunakan endpoint-nya sendiri sebagai kunci:

    {
      "https://fcm...1234": {
        endpoint: "https://fcm...1234",
        expirationTime: ...,
        keys: { ... }
      },
      "https://fcm...abcd": {
        endpoint: "https://fcm...abcd",
        expirationTime: ...,
        keys: { ... }
      },
      "https://fcm...zxcv": {
        endpoint: "https://fcm...zxcv",
        expirationTime: ...,
        keys: { ... }
      },
    }

Sekarang, langganan baru tersedia bagi server untuk mengirimkan notifikasi.

Penerapan

Permintaan langganan baru datang ke rute /add-subscription, yang merupakan URL POST. Anda akan melihat pengendali rute stub di server.js:

server.js

app.post('/add-subscription', (request, response) => {
  // TODO: implement handler for /add-subscription
  console.log('TODO: Implement handler for /add-subscription');
  console.log('Request body: ', request.body);
  response.sendStatus(200);
});

Dalam implementasi Anda, pengendali ini harus:

  • Ambil langganan baru dari isi permintaan.
  • Mengakses database langganan aktif.
  • Tambahkan langganan baru ke daftar langganan aktif.

Untuk menangani langganan baru:

  • Di server.js, ubah pengendali rute untuk /add-subscription sebagai berikut:

    server.js

    app.post('/add-subscription', (request, response) => {
      // TODO: implement handler for /add-subscription
      console.log('TODO: Implement handler for /add-subscription');
      console.log('Request body: ', request.body);
      let subscriptions = Object.assign({}, request.session.subscriptions);
      subscriptions[request.body.endpoint] = request.body;
      request.session.subscriptions = subscriptions;
      response.sendStatus(200);
    });

Menangani pembatalan langganan

Latar belakang

Server tidak akan selalu tahu kapan langganan menjadi tidak aktif, misalnya, langganan dapat dihapus total ketika browser mematikan pekerja layanan.

Namun, server dapat mencari tahu tentang langganan yang dibatalkan melalui UI aplikasi. Pada langkah ini, Anda akan mengimplementasikan fungsi untuk menghapus langganan dari database.

Dengan cara ini, server menghindari pengiriman sekumpulan notifikasi ke endpoint yang tidak ada. Jelas ini tidak terlalu penting pada aplikasi pengujian sederhana, tetapi ini menjadi penting pada skala yang lebih besar.

Penerapan

Permintaan untuk membatalkan langganan muncul di URL POST /remove-subscription.

Pengendali rute stub di server.js terlihat seperti ini:

server.js

app.post('/remove-subscription', (request, response) => {
  // TODO: implement handler for /remove-subscription
  console.log('TODO: Implement handler for /remove-subscription');
  console.log('Request body: ', request.body);
  response.sendStatus(200);
});

Dalam implementasi Anda, pengendali ini harus:

  • Ambil endpoint langganan yang dibatalkan dari isi permintaan.
  • Mengakses database langganan aktif.
  • Hapus langganan yang dibatalkan dari daftar langganan aktif.

Isi permintaan POST dari klien berisi endpoint yang perlu Anda hapus:

{
  "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}

Untuk menangani pembatalan langganan:

  • Di server.js, ubah pengendali rute untuk /remove-subscription sebagai berikut:

    server.js

  app.post('/remove-subscription', (request, response) => {
    // TODO: implement handler for /remove-subscription
    console.log('TODO: Implement handler for /remove-subscription');
    console.log('Request body: ', request.body);
    let subscriptions = Object.assign({}, request.session.subscriptions);
    delete subscriptions[request.body.endpoint];
    request.session.subscriptions = subscriptions;
    response.sendStatus(200);
  });