Web push kitaplıklarıyla mesaj gönderme

Matt Gaunt

Web push ile çalışırken karşılaşılan sorunlardan biri, push mesajı tetiklemenin son derece "zor" olmasıdır. Bir uygulamanın push mesajı tetiklemesi için web push protokolüne uygun olarak bir push hizmetine POST isteği göndermesi gerekir. Push'i tüm tarayıcılarda kullanmak için VAPID'i (diğer adıyla uygulama sunucusu anahtarları) kullanmanız gerekir. Bu da temel olarak, uygulamanızın bir kullanıcıya mesaj gönderebildiğini kanıtlayan bir değer içeren bir üstbilgi ayarlamanızı gerektirir. Verileri push mesajıyla göndermek için verilerin şifrelenmesi ve tarayıcının mesajın şifresini doğru şekilde çözebilmesi için belirli üstbilgilerin eklenmesi gerekir.

Push'i tetiklemenin en büyük sorunu, bir sorunla karşılaşırsanız sorunu teşhis etmenin zor olmasıdır. Bu durum zaman içinde ve daha geniş tarayıcı desteğiyle iyileşiyor olsa da kolay bir iş değildir. Bu nedenle, push mesajınızın şifrelenmesini, biçimlendirilmesini ve tetiklenmesini yönetmek için bir kitaplık kullanmanızı önemle tavsiye ederiz.

Kitaplıkların ne yaptığı hakkında daha fazla bilgi edinmek istiyorsanız bunu bir sonraki bölümde ele alacağız. Şimdilik, abonelikleri yönetmeye ve push isteklerini göndermek için mevcut bir web push kitaplığını kullanmaya bakacağız.

Bu bölümde web-push Node kitaplığını kullanacağız. Diğer diller arasında farklılıklar olsa da bu farklılıklar çok fazla olmaz. JavaScript olduğu ve okuyucular için en erişilebilir seçenek olması gerektiği için Node'u inceliyoruz.

Aşağıdaki adımları uygulayacağız:

  1. Arka uçımıza bir abonelik gönderip kaydedin.
  2. Kayıtlı abonelikleri alma ve push mesajı tetikleme.

Abonelikleri kaydetme

Bir veritabanından PushSubscription öğelerini kaydetme ve sorgulama, sunucu tarafı dilinize ve veritabanı seçiminize bağlı olarak değişiklik gösterir. Ancak bunun nasıl yapılacağına dair bir örnek görmek yararlı olabilir.

Demo web sayfasında, PushSubscription basit bir POST isteği göndererek arka uçımıza gönderilir:

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

Demo'daki Express sunucusunda, /api/save-subscription/ uç noktası için eşleşen bir istek dinleyicisi vardır:

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

Bu yönde, isteğin iyi olduğundan ve gereksiz veri içermediğinden emin olmak için aboneliği doğrularız:

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;
};

Abonelik geçerliyse kaydetmemiz ve uygun bir JSON yanıtı döndürmemiz gerekir:

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

Bu demoda, abonelikleri depolamak için nedb kullanılır. Basit bir dosya tabanlı veritabanı olan nedb yerine dilediğiniz veritabanını kullanabilirsiniz. Bu yöntemi yalnızca sıfır kurulum gerektirdiği için kullanıyoruz. Üretim için daha güvenilir bir yöntem kullanmanız gerekir. (Ben eski MySQL'i kullanmaya devam ediyorum.)

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

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

Push mesajları gönderme

Push mesajı gönderme söz konusu olduğunda, kullanıcılara mesaj gönderme sürecini tetikleyecek bir etkinliğe ihtiyacımız vardır. Yaygın bir yaklaşım, push mesajını yapılandırmanıza ve tetiklemenize olanak tanıyan bir yönetici sayfası oluşturmaktır. Ancak yerel olarak çalışacak bir program veya PushSubscription listesine erişip itme mesajını tetiklemek için kodu çalıştırmaya olanak tanıyan başka bir yaklaşım oluşturabilirsiniz.

Demomuzda, push bildirimleri tetiklemenize olanak tanıyan "yönetici benzeri" bir sayfa bulunur. Bu yalnızca bir demo olduğu için herkese açık bir sayfadır.

Demoyu çalıştırmaya yönelik her adımı tek tek uygulayacağım. Bu adımlar, Node'a yeni başlayanlar da dahil olmak üzere herkesin takip edebilmesi için basit olacak.

Kullanıcıların abone olmasıyla ilgili olarak, subscribe() seçeneklerine applicationServerKey ekleme konusunu ele aldık. Bu özel anahtara arka uçta ihtiyacımız olacak.

Demoda bu değerler Node uygulamamıza aşağıdaki gibi eklenir (Biliyorum, sıkıcı bir kod ama sihirli bir şey olmadığını bilmenizi isterim):

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

Ardından, düğüm sunucumuz için web-push modülünü yüklememiz gerekir:

npm install web-push --save

Ardından, Node komut dosyamızda web-push modülünü şu şekilde kullanırız:

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

Artık web-push modülünü kullanmaya başlayabiliriz. Öncelikle web-push modülüne uygulama sunucu anahtarlarımız hakkında bilgi vermemiz gerekir. (Bu anahtarlar, spesifikasyonun adı VAPID olduğu için VAPID anahtarları olarak da bilinir.)

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

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

"mailto:" dizesi de eklediğimizi unutmayın. Bu dizenin bir URL veya mailto e-posta adresi olması gerekir. Bu bilgi, aslında bir push tetikleme isteği kapsamında web push hizmetine gönderilir. Bunun nedeni, bir web push hizmetinin gönderenle iletişime geçmesi gerektiğinde bunu yapabilmesi için bazı bilgilere sahip olmasıdır.

Bu işlemle web-push modülü kullanıma hazır hale gelir. Sonraki adım, bir push mesajı tetiklemektir.

Demoda, push mesajlarını tetiklemek için sanal yönetici paneli kullanılır.

Yönetici sayfasının ekran görüntüsü.

"Push Mesajını Tetikle" düğmesini tıkladığınızda /api/trigger-push-msg/ adresine bir POST isteği gönderilir. Bu istek, arka uç sistemimizin push mesajı göndermesi için bir sinyaldir. Bu nedenle, bu uç nokta için express'te rotayı oluştururuz:

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

Bu istek alındığında, abonelikleri veritabanından alırız ve her biri için bir push mesajı tetikleriz.

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;
});

triggerPushMsg() işlevi daha sonra sağlanan aboneliğe mesaj göndermek için web push kitaplığını kullanabilir.

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;
    }
  });
};

webpush.sendNotification() çağrısı bir promise döndürür. Mesaj başarıyla gönderildiyse söz çözülür ve herhangi bir işlem yapmamız gerekmez. Söz reddedilirse PushSubscription'ün geçerli olup olmadığı konusunda sizi bilgilendireceğinden hatayı incelemeniz gerekir.

Bir push hizmetindeki hatanın türünü belirlemek için en iyi yöntem durum koduna bakmaktır. Hata mesajları, push hizmetleri arasında değişiklik gösterir ve bazı mesajlar diğerlerinden daha faydalıdır.

Bu örnekte, "Bulunamadı" ve "Yok" için HTTP durum kodları olan 404 ve 410 durum kodları kontrol edilir. Bu hatalardan birini alırsak aboneliğin süresi dolmuş veya artık geçerli değil demektir. Bu durumlarda, abonelikleri veritabanımızdan kaldırmamız gerekir.

Başka bir hata olması durumunda throw err yaparız. Bu durumda, triggerPushMsg() tarafından döndürülen söz reddedilir.

Web push protokolünü daha ayrıntılı bir şekilde incelediğimiz sonraki bölümde diğer durum kodlarından bazılarını ele alacağız.

Aboneliklerde döngü yaptıktan sonra bir JSON yanıtı döndürmemiz gerekir.

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

Uygulamanın ana adımlarını ele aldık:

  1. Web sayfamızdan arka uçımıza abonelikleri göndermek için bir API oluşturun. Böylece, abonelikler bir veritabanına kaydedilebilir.
  2. Push mesajlarının gönderilmesini tetikleyecek bir API oluşturun (bu durumda, sahte yönetici panelinden çağrılan bir API).
  3. Tüm abonelikleri arka uçtan alın ve web push kitaplıklarından biriyle her aboneliğe bir mesaj gönderin.

Arka uçunuzdan (Node, PHP, Python vb.) bağımsız olarak, push'i uygulama adımları aynı olacaktır.

Ardından, bu web push kitaplıkları bizim için tam olarak ne yapıyor?

Sonraki adımlar

Codelab uygulamaları