Web push kitaplıklarıyla mesaj gönderme

Matt Gaunt

Web Push ile çalışırken yaşanan sorunlardan biri, push mesajlarını tetiklemenin son derece "rahatsız edici" olmasıdır. Push mesajını tetiklemek için bir uygulamanın web push protokolünü izleyerek bir push hizmetine POST isteğinde bulunması gerekir. Push özelliğini tüm tarayıcılarda kullanabilmek için VAPID (uygulama sunucusu anahtarları olarak da bilinir) kullanmanız gerekir. Bu işlem, temelde uygulamanızın kullanıcıya mesaj gönderebileceğini kanıtlayan bir değerle bir başlık ayarlanmasını gerektirir. Push mesajıyla veri göndermek için verilerin şifrelenmesi ve tarayıcının mesajın şifresini doğru şekilde çözebilmesi için belirli başlıkların eklenmesi gerekir.

Gönderme işleminin tetiklenmesiyle ilgili temel sorun, bir sorunla karşılaştığınızda sorunu teşhis etmenin zor olmasıdır. Bu durum, zamanla ve daha geniş kapsamlı tarayıcı desteğiyle daha iyi hale gelmektedir, ancak kolay değildir. Bu nedenle, push mesajınızın şifrelenmesi, biçimlendirilmesi ve tetiklenmesi için kitaplık kullanmanızı önemle tavsiye ederiz.

Kitaplıkların neler yaptığını gerçekten öğrenmek istiyorsanız bir sonraki bölümde bunu ele alacağız. Şimdilik abonelikleri yönetmeye ve push isteklerinde bulunmak için mevcut bir web push kitaplığını kullanmaya bakacağız.

Bu bölümde web-push Düğümü kitaplığını kullanacağız. Diğer dillerde farklılıklar olacaktır ancak çok fazla benzerlik göstermeyecektir. Düğüm, JavaScript olduğu ve okuyucular için en erişilebilir olması gerektiğinden Düğüm'e bakıyoruz.

Bunun için aşağıdaki adımları uygulayacağız:

  1. Bir aboneliği arka ucumuza gönderin ve kaydedin.
  2. Kaydedilen abonelikleri alın ve bir push mesajı tetikleyin.

Abonelikler kaydediliyor

PushSubscription öğelerini bir veritabanından kaydedip sorgulamak, sunucu tarafı dilinize ve veritabanı seçiminize bağlı olarak değişiklik gösterir ancak nasıl yapılabileceğine dair bir örnek görmek faydalı olabilir.

Demo web sayfasında, PushSubscription basit bir POST isteği yapılarak arka ucumuza 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.');
      }
    });
}

Demomuzdaki Express sunucusunda, /api/save-subscription/ uç noktası için eşleşen bir istek işleyici vardır:

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

Bu aşamada yalnızca isteğin uygun olduğundan ve gereksiz öğe bulunmadığından 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 aboneliği kaydedip 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 demo, abonelikleri depolamak için nedb kullanır. Bu demo, dosya tabanlı basit bir veritabanıdır ancak istediğiniz herhangi bir veritabanını kullanabilirsiniz. Bunu sadece kurulum gerektiği için kullanıyoruz. Üretim için daha güvenilir bir yöntem tercih ederdiniz. (Genellikle iyi bir eski MySQL kullanırım.)

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

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

Push mesaj gönderme

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

Demomuzda, push gönderebilmenizi sağlayan bir "yönetici beğenisi" sayfası bulunmaktadır. Bu sadece bir demo olduğundan herkese açık bir sayfa.

Demonun çalışmasını sağlamak için gereken her adımı tek tek anlatacağım. Bunlar, düğümde yeni olan herkes dahil herkesin takip edebileceği küçük adımlar olacaktır.

Bir kullanıcıya abone olma konusunu ele alırken, subscribe() seçeneklerine applicationServerKey eklemeyi ele almıştık. Arka uçta bu özel anahtara ihtiyacımız olacak.

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

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

Şimdi Düğüm sunucumuz için web-push modülünü yüklememiz gerekiyor:

npm install web-push --save

Ardından, Düğüm komut dosyamızda web-push modülü şu şekilde gerekir:

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

Artık web-push modülünü kullanmaya başlayabiliriz. Öncelikle web-push modülüne uygulama sunucusu anahtarlarımız hakkında bilgi vermemiz gerekir. (Spesifikasyonun adı olduğundan VAPID anahtarları olarak da bilindiklerini unutmayın.)

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

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

Aynı zamanda bir "mailto:" dizesi de eklediğimizi unutmayın. Bu dizenin bir URL veya mailto e-posta adresi olması gerekir. Bu bilgi, aslında bir aktarma işlemini tetikleme isteğinin bir parçası olarak web push hizmetine gönderilir. Bunun nedeni, bir web push hizmetinin gönderenle iletişim kurması gerektiğinde, bu kişilere bunu yapabilecek bazı bilgilerin sunulmasıdır.

Bununla birlikte web-push modülü kullanıma hazır hale gelir, sonraki adım bir push mesajı tetiklemektir.

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

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

"Push Mesajını Tetikle" düğmesi tıklandığında /api/trigger-push-msg/ adresine POST isteği gönderilir. Bu istek, arka ucumuzun push mesajları göndermesi için kullanılan sinyaldir. Dolayısıyla, bu uç nokta için rotayı ekspres olarak oluştururuz:

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

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

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

Böylece triggerPushMsg() işlevi, web-push kitaplığını kullanarak sağlanan aboneliğe bir mesaj gönderebilir.

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() araması bir söz verir. Mesaj başarıyla gönderildiyse söz verilen çözüm gerçekleşecektir ve yapmamız gereken bir şey yoktur. Vaat reddedilirse, PushSubscription belgesinin hâlâ geçerli olup olmadığı konusunda sizi bilgilendireceğinden hatayı incelemeniz gerekir.

Push hizmetinden alınan hata türünü belirlemek için durum koduna bakmak en iyisidir. Hata mesajları, push hizmetlerine göre değişiklik gösterir ve bazıları diğerlerinden daha faydalıdır.

Bu örnekte, "Bulunamadı" ve "Gitti" için HTTP durum kodları olan 404 ve 410 durum kodlarını kontrol eder. Bunlardan birini almamız, aboneliğin süresinin dolduğu veya artık geçerli olmadığı anlamına gelir. Böyle durumlarda abonelikleri veritabanımızdan kaldırmamız gerekir.

Başka bir hata olması durumunda, yalnızca throw err işlemi uygulanır. Bu işlem, triggerPushMsg() tarafından döndürülen sözü reddeder.

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

Abonelikler arasında geçiş yaptıktan sonra 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}'`
    }
}));
});

Başlıca uygulama adımlarının üzerinden geçtik:

  1. Web sayfamızdan arka uç hizmetimize abonelik göndererek abonelikleri veritabanına kaydetmek için bir API oluşturun.
  2. Push mesajlarının gönderilmesini tetikleyecek bir API (bu örnekte, sahte yönetici panelinden çağrılan bir API) oluşturun.
  3. Arka ucumuzdan tüm abonelikleri alın ve web-push kitaplıklarından biriyle her aboneliğe bir mesaj gönderin.

Arka ucunuz (Node, PHP, Python, ...) ne olursa olsun, push uygulama adımları aynı olacaktır.

Şimdi de bu web-push kitaplıkları bize tam olarak ne yapıyor?

Sonraki adımlar

Code Labs