Mengirim pesan dengan library web push

Salah satu titik masalah saat bekerja dengan web push adalah bahwa memicu pesan push dengan sangat "tidak jelas". Untuk memicu pesan push, aplikasi perlu membuat permintaan POST ke layanan push dengan mengikuti protokol push web. Untuk menggunakan push di semua browser, Anda harus menggunakan VAPID (alias kunci server aplikasi) yang pada dasarnya memerlukan penetapan header dengan nilai yang membuktikan bahwa aplikasi Anda dapat mengirim pesan kepada pengguna. Untuk mengirim data dengan pesan push, data harus dienkripsi dan header tertentu harus ditambahkan agar browser dapat mendekripsi pesan dengan benar.

Masalah utama saat memicu push adalah jika Anda mencapai masalah, akan sulit untuk mendiagnosis masalah tersebut. Hal ini meningkat seiring waktu dan dukungan browser yang lebih luas, tetapi hal ini jauh dari mudah. Karena alasan ini, sebaiknya gunakan library untuk menangani enkripsi, pemformatan, dan pemicu pesan push Anda.

Jika Anda benar-benar ingin mempelajari apa yang dilakukan library, kita akan membahasnya di bagian berikutnya. Untuk saat ini, kita akan melihat cara mengelola langganan dan menggunakan library push web yang ada untuk membuat permintaan push.

Di bagian ini, kita akan menggunakan library Node web-push. Bahasa lain mungkin memiliki perbedaan, tapi tidak akan terlalu berbeda. Kita mempelajari Node karena itu adalah JavaScript dan seharusnya menjadi yang paling mudah diakses oleh pembaca.

Kita akan melakukan langkah-langkah berikut:

  1. Kirim langganan ke backend kami dan simpan.
  2. Mengambil langganan tersimpan dan memicu pesan push.

Menyimpan langganan

Menyimpan dan membuat kueri PushSubscription dari database akan bervariasi bergantung pada bahasa sisi server dan pilihan database Anda, tetapi mungkin ada baiknya untuk melihat contoh cara melakukannya.

Di halaman web demo, PushSubscription dikirim ke backend dengan membuat permintaan POST sederhana:

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 Express dalam demo kami memiliki pemroses permintaan yang cocok untuk endpoint /api/save-subscription/:

app.post('/api/save-subscription/', function (req, res) {

Dalam rute ini, kami memvalidasi langganan hanya untuk memastikan permintaan tersebut baik-baik saja dan tidak penuh sampah memori:

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    res.status(400);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'no-endpoint',
          message: 'Subscription must have an endpoint.',
        },
      }),
    );
    return false;
  }
  return true;
};

Jika langganan valid, kita perlu menyimpannya dan menampilkan respons JSON yang sesuai:

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.',
        },
      }),
    );
  });

Demo ini menggunakan nedb untuk menyimpan langganan. Ini adalah database berbasis file yang sederhana, tetapi Anda dapat menggunakan database apa pun pilihan Anda. Kita hanya menggunakan ini karena tidak perlu pengaturan sama sekali. Untuk produksi, sebaiknya gunakan sesuatu yang lebih dapat diandalkan. (Saya cenderung menggunakan My MySQL lama yang bagus.)

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

Mengirim pesan push

Dalam hal pengiriman pesan push, kita pada akhirnya memerlukan beberapa peristiwa untuk memicu proses pengiriman pesan kepada pengguna. Pendekatan umumnya adalah membuat halaman admin yang memungkinkan Anda mengonfigurasi dan memicu pesan push. Namun, Anda dapat membuat program untuk dijalankan secara lokal atau pendekatan lain yang memungkinkan akses ke daftar PushSubscription dan menjalankan kode untuk memicu pesan push.

Demo kami memiliki halaman "suka admin" yang memungkinkan Anda memicu push. Karena ini hanyalah demo, maka ini adalah halaman publik.

Saya akan menjelaskan setiap langkah yang diperlukan agar demo ini berfungsi. Ini akan menjadi langkah awal sehingga semua orang dapat mengikutinya, termasuk siapa saja yang baru menggunakan Node.

Saat membahas berlangganan pengguna, kita telah membahas penambahan applicationServerKey ke opsi subscribe(). Di bagian belakang kita akan membutuhkan kunci pribadi ini.

Dalam demo, nilai-nilai ini ditambahkan ke aplikasi Node kami seperti itu (kode membosankan yang saya tahu, tetapi hanya ingin Anda tahu bahwa tidak ada keajaiban):

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

Selanjutnya, kita perlu menginstal modul web-push untuk server Node:

npm install web-push --save

Kemudian, dalam skrip Node, kami memerlukan modul web-push seperti berikut:

const webpush = require('web-push');

Sekarang kita dapat mulai menggunakan modul web-push. Pertama, kita perlu memberi tahu modul web-push tentang kunci server aplikasi. (Ingat, kunci ini juga dikenal sebagai kunci VAPID karena merupakan nama spesifikasi.)

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

Perhatikan bahwa kami juga menyertakan string "mailto:". String ini harus berupa URL atau alamat email mailto. Informasi ini sebenarnya akan dikirim ke layanan web push sebagai bagian dari permintaan untuk memicu push. Alasan hal ini dilakukan adalah agar jika layanan web push perlu menghubungi pengirim, layanan tersebut memiliki beberapa informasi yang memungkinkan mereka melakukannya.

Setelah proses ini, modul web-push siap digunakan, langkah berikutnya adalah memicu pesan push.

Demo menggunakan panel admin palsu untuk memicu pesan push.

Screenshot halaman admin.

Mengklik tombol "Trigger Push Message" akan membuat permintaan POST ke /api/trigger-push-msg/, yang merupakan sinyal bagi backend kita untuk mengirim pesan push. Jadi, kita membuat rute secara ekspres untuk endpoint ini:

app.post('/api/trigger-push-msg/', function (req, res) {

Saat permintaan ini diterima, kita mengambil langganan dari database dan untuk setiap langganan, kita memicu pesan push.

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

Fungsi triggerPushMsg() kemudian dapat menggunakan library web-push untuk mengirim pesan ke langganan yang disediakan.

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

Panggilan ke webpush.sendNotification() akan menampilkan promise. Jika pesan berhasil dikirim, promise akan diselesaikan dan tidak ada yang perlu kami lakukan. Jika promise ditolak, Anda perlu memeriksa error tersebut karena akan memberi tahu Anda apakah PushSubscription masih valid atau tidak.

Untuk menentukan jenis kesalahan dari layanan push, sebaiknya lihat kode status. Pesan error bervariasi antara layanan push dan beberapa lebih membantu daripada yang lain.

Dalam contoh ini, pemeriksaan kode status 404 dan 410, yang merupakan kode status HTTP untuk 'Not Found' dan 'Gone'. Jika kami menerima salah satunya, berarti langganan telah habis masa berlakunya atau tidak lagi valid. Dalam skenario ini, kita perlu menghapus langganan dari database.

Jika terjadi error lainnya, kita hanya perlu throw err, yang akan membuat promise yang ditampilkan oleh triggerPushMsg() ditolak.

Kami akan membahas beberapa kode status lain di bagian berikutnya saat membahas protokol web push secara lebih mendetail.

Setelah melakukan loop langganan, kita perlu menampilkan respons JSON.

.then(() => {
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-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

Kita telah membahas langkah-langkah penerapan utama:

  1. Buat API untuk mengirim langganan dari halaman web ke back-end sehingga dapat menyimpannya ke database.
  2. Buat API untuk memicu pengiriman pesan push (dalam hal ini, API yang dipanggil dari panel admin palsu).
  3. Ambil semua langganan dari backend kami dan kirim pesan ke setiap langganan dengan salah satu library web-push.

Terlepas dari backend Anda (Node, PHP, Python, ...), langkah-langkah untuk menerapkan push akan sama.

Selanjutnya, apa sebenarnya yang dilakukan library web-push ini bagi kita?

Langkah berikutnya

Lab kode