Mengirim pesan dengan library web push

Salah satu titik masalah saat menggunakan push web adalah memicu pesan push sangat "rumit". Untuk memicu pesan push, aplikasi harus membuat permintaan POST ke layanan push 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 dengan memicu push adalah jika Anda mengalami masalah, akan sulit untuk mendiagnosis masalah tersebut. Hal ini akan semakin baik seiring waktu dan dukungan browser yang lebih luas, tetapi hal ini tidaklah mudah. Oleh karena itu, sebaiknya gunakan library untuk menangani enkripsi, pemformatan, dan pemicuan 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 push web. Bahasa lain akan memiliki perbedaan, tetapi tidak akan terlalu berbeda. Kita melihat Node karena merupakan JavaScript dan seharusnya paling mudah diakses oleh pembaca.

Kita akan membahas 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 sebaiknya lihat 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) {

Di rute ini, kita memvalidasi langganan hanya untuk memastikan permintaannya baik dan tidak penuh dengan sampah:

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, yaitu database berbasis file sederhana, tetapi Anda dapat menggunakan database apa pun sesuai pilihan Anda. Kita hanya menggunakannya karena tidak memerlukan penyiapan apa pun. Untuk produksi, sebaiknya gunakan sesuatu yang lebih andal. (Saya cenderung tetap menggunakan 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 mengirim pesan push, pada akhirnya kita memerlukan beberapa peristiwa untuk memicu proses pengiriman pesan kepada pengguna. Pendekatan yang umum 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 "seperti admin" yang memungkinkan Anda memicu push. Karena ini hanyalah demo, halaman ini adalah halaman publik.

Saya akan membahas setiap langkah yang terlibat dalam menjalankan demo. Ini akan menjadi langkah-langkah dasar sehingga semua orang dapat mengikutinya, termasuk siapa saja yang baru menggunakan Node.

Saat membahas cara membuat pengguna berlangganan, kita telah membahas cara menambahkan applicationServerKey ke opsi subscribe(). Di backend, kita akan memerlukan kunci pribadi ini.

Dalam demo, nilai ini ditambahkan ke aplikasi Node kita seperti ini (kode yang membosankan, saya tahu, tetapi hanya ingin memberi tahu Anda 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, kita memerlukan modul web-push seperti ini:

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 kita. (Ingat bahwa kunci ini juga dikenal sebagai kunci VAPID karena itulah nama spesifikasinya.)

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

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

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

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

Demo ini 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 untuk mengirim pesan push, sehingga kita membuat rute di express 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 kita lakukan. Jika promise ditolak, Anda perlu memeriksa error karena akan memberi tahu Anda apakah PushSubscription masih valid atau tidak.

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

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

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

Kita akan membahas beberapa kode status lainnya di bagian berikutnya saat kita melihat protokol push web secara lebih mendetail.

Setelah melakukan loop pada 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 backend agar dapat menyimpannya ke database.
  2. Buat API untuk memicu pengiriman pesan push (dalam hal ini, API yang dipanggil dari panel admin yang disimulasikan).
  3. Ambil semua langganan dari backend kami dan kirim pesan ke setiap langganan dengan salah satu library push web.

Apa pun backend Anda (Node, PHP, Python, …), langkah-langkah untuk menerapkan push akan sama.

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

Langkah berikutnya

Codelab