Membangun server notifikasi push

Dalam codelab ini, Anda akan mem-build 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.

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

  1. Klik Remix to Edit untuk menjadikan project dapat diedit.
  2. Untuk melihat pratinjau situs, tekan Lihat Aplikasi. Kemudian tekan Layar Penuh layar penuh.

Aplikasi live 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 tersemat di halaman ini. Muat ulang tab baru dengan aplikasi yang sudah aktif untuk melihat perubahan.

Memahami aplikasi awal dan kodenya

Mulai 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 di konsol developer Chrome).

    • Mendaftarkan pekerja layanan akan mendaftarkan pekerja layanan untuk cakupan URL project Glitch Anda. Batalkan pendaftaran pekerja layanan akan menghapus pekerja layanan. Jika langganan push dilampirkan ke akun tersebut, langganan push juga akan dinonaktifkan.

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

    • Jika Anda memiliki langganan push yang aktif, Beri tahu langganan saat ini meminta server untuk mengirim notifikasi ke endpoint-nya.

    • 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. Langganan dapat saja hilang pada saat server mengirim 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 Alat -> 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. Fungsi ini melakukan deteksi fitur, mendaftarkan dan membatalkan pendaftaran pekerja layanan, serta mengontrol langganan pengguna untuk notifikasi push. Layanan ini juga mengirimkan informasi tentang langganan baru dan yang telah 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 gunakan untuk melakukan sebagian besar pekerjaan selama codelab ini.

    Kode awal 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 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 berlangganan notifikasi, pengguna harus memercayai identitas aplikasi dan servernya. Pengguna juga harus yakin bahwa, saat mereka menerima notifikasi, notifikasi tersebut berasal dari aplikasi yang sama dengan yang menyiapkan langganan. Mereka juga harus yakin bahwa tidak ada orang lain yang dapat membaca konten 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. Muat variabel lingkungan di server dan tambahkan 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 -> Logs 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 tidak terlihat saat output lainnya muncul.

  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, komentari lagi dua 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 publik ke dalam kode klien juga.

    Di public/index.js, masukkan nilai yang sama untuk VAPID_PUBLIC_KEY 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

Di 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 khawatir.

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 bagi server untuk menghindari pengiriman notifikasi kepada pengguna setelah notifikasi tersebut 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 dengan mudah menambahkan penanganan error.

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 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 yang di-string dalam isi.

  4. Server mengambil subscription yang di-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 untuk server guna mengirim notifikasi.

Penerapan

Permintaan untuk langganan baru akan masuk 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 mengetahui kapan langganan menjadi tidak aktif–misalnya, langganan dapat dihapus saat browser menonaktifkan pekerja layanan.

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

Dengan cara ini, server menghindari pengiriman banyak 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 akan dikirim ke 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);
 
});