Membangun server notifikasi push

Dalam codelab ini, Anda akan mem-build server notifikasi push. Server akan mengelola daftar langganan push dan mengirimkan notifikasi kepadanya.

Kode klien sudah selesai–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 yang harus dilakukan:

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

Aplikasi live akan terbuka di tab Chrome baru. Di Glitch tersemat, 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 live Anda 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 output konsol developer Chrome).

    • Register service worker 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 terdaftar dan konstanta VAPID_PUBLIC_KEY ada dalam kode klien (selengkapnya tentang hal ini akan dibahas nanti), jadi Anda belum dapat mengkliknya.

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

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

      Perlu diperhatikan bahwa beberapa endpoint ini mungkin tidak aktif. Ada kemungkinan bahwa langganan akan hilang saat server mengirimkan notifikasi ke langganan tersebut.

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 -> Logs.

    Anda mungkin akan melihat pesan seperti Listening on port 3000.

    Jika mencoba mengklik Beri tahu langganan saat ini atau Beri tahu semua langganan di UI aplikasi yang aktif, 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. Objek 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 menangkap peristiwa push dan menampilkan notifikasi.

  • /views/index.html berisi UI aplikasi.

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

  • server.js adalah file yang akan Anda lakukan sebagian besar pekerjaan Anda selama codelab ini.

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

    Dalam codelab ini, Anda akan mengerjakan 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, dan 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 mereka menerima notifikasi, notifikasi berasal dari aplikasi yang sama yang menyiapkan langganan. Mereka juga perlu 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 mengenkripsi konten notifikasi.

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

Penerapan

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

  1. Gunakan fungsi generateVAPIDKeys dari library web-push untuk membuat pasangan 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 Anda, Glitch akan meng-output kunci yang dihasilkan ke log Node.js dalam antarmuka Glitch (bukan ke konsol Chrome). Untuk melihat kunci VAPID, pilih Tools -> Logs di antarmuka Glitch.

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

    Glitch akan memulai ulang aplikasi setiap kali Anda mengedit kode, sehingga pasangan kunci pertama yang dihasilkan mungkin akan ter-scroll keluar dari tampilan seiring bertambahnya output yang ditampilkan.

  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, komentarkan lagi kedua baris kode tersebut, 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 dan tempel kunci public ke kode klien juga.

    Di public/index.js, masukkan nilai yang sama untuk VAPID_PUBLIC_KEY yang Anda salin ke 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 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 bagi server untuk menghindari pengiriman notifikasi kepada pengguna setelah notifikasi 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);
  });
}

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

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 terjadi saat pengguna berlangganan notifikasi push:

  1. Pengguna mengklik Berlangganan push.

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

       {
         "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
         "expirationTime": null,
         "keys":
         {
           "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
           "auth": "0IyyvUGNJ9RxJc83poo3bA"
         }
       }
    
  3. Klien mengirimkan 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 {i>database<i} 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 mengetahui kapan langganan menjadi tidak aktif–misalnya, langganan dapat dihapus total saat browser menonaktifkan 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. Tentu saja ini tidak terlalu penting dengan aplikasi pengujian sederhana, tetapi menjadi penting pada skala yang lebih besar.

Penerapan

Permintaan untuk membatalkan langganan berasal dari 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 titik akhir langganan yang dibatalkan dari isi permintaan.
  • Mengakses {i>database<i} 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);
  });