إرسال الرسائل باستخدام مكتبات الإشعارات الفورية على الويب

من المشاكل التي تواجهك عند استخدام إشعارات الدفع على الويب هي أنّ بدء إرسال رسالة دفع هو عملية "معقدة" للغاية. لتشغيل رسالة فورية، يجب أن يُرسل التطبيق طلب POST إلى خدمة إعلامات فورية باتّباع بروتوكول إعلامات الويب الفورية. لاستخدام ميزة الإرسال الفوري على جميع المتصفحات، عليك استخدام VAPID (المعروفة أيضًا باسم مفاتيح خادم التطبيقات)، ما يتطلّب بشكل أساسي ضبط عنوان يحتوي على قيمة تثبت أنّ تطبيقك يمكنه إرسال رسالة إلى المستخدم. لإرسال البيانات من خلال رسالة فورية، يجب أولاً تشفير البيانات وإضافة رؤوس معيّنة كي يتمكّن المتصفّح من فك تشفير الرسالة بشكل صحيح.

المشكلة الرئيسية في بدء عملية الدفع هي أنّه في حال مواجهة مشكلة، من الصعب تشخيص هذه المشكلة. يتحسن هذا الأمر بمرور الوقت ويصبح متوافقًا مع المزيد من المتصفّحات، ولكنّه ليس بالأمر السهل. لهذا السبب، أنصح بشدة باستخدام مكتبة للتعامل مع التشفير والتنسيق و تنشيط رسالة الإشعارات الفورية.

إذا أردت معرفة المزيد عن الإجراءات التي تتّخذها المكتبات، سنتناول ذلك في القسم التالي. في الوقت الحالي، سنلقي نظرة على إدارة الاشتراكات واستخدام مكتبة دفع حالية على الويب لتقديم طلبات الدفع.

في هذا القسم، سنستخدم مكتبة web-push Node . ستكون هناك اختلافات في اللغات الأخرى، ولكن لن تكون مختلفة جدًا. نحن ننظر إلى Node لأنّه JavaScript ومن المفترض أن يكون التنسيق الذي يسهل على القرّاء استخدامه.

سنوضّح لك الخطوات التالية:

  1. أرسِل اشتراكًا إلى الخلفية واحفظه.
  2. استرداد الاشتراكات المحفوظة وتشغيل رسالة فورية

حفظ الاشتراكات

يختلف حفظ PushSubscription وطلبات البحث عنها من قاعدة بيانات حسب اللغة على جانب الخادم واختيار قاعدة البيانات، ولكن قد يكون من المفيد الاطّلاع على مثال على كيفية إجراء ذلك.

في صفحة الويب التجريبية، يتم إرسال PushSubscription إلى الخلفية من خلال إجراء طلب POST بسيط:

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

يحتوي خادم Express في العرض التوضيحي على مستمع طلبات مطابق لنقطة نهاية /api/save-subscription/:

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

في هذا المسار، نتحقّق من الاشتراك للتأكد فقط من أن الطلب يعمل على ما يرام ومن أنّه ليس مليئًا بأي بيانات غير مرغوب فيها:

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

إذا كان الاشتراك صالحًا، علينا حفظه وعرض استجابة JSON مناسبة:

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

يستخدم هذا العرض الترويجي nedb لتخزين الاشتراكات، وهي قاعدة بيانات بسيطة تستند إلى الملفات، ولكن يمكنك استخدام أي قاعدة بيانات من اختيارك. نحن نستخدم هذا فقط لأنه لا يتطلب أي إعداد. لاستخدام الإصدار العلني، عليك استخدام طريقة أكثر موثوقية. (أميل إلى الالتزام باستخدام MySQL القديم الجيد.)

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

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

إرسال رسائل فورية

عندما يتعلق الأمر بإرسال رسالة فورية، نحتاج في النهاية إلى بعض الأحداث لبدء عملية إرسال رسالة إلى المستخدمين. من الطرق الشائعة إنشاء صفحة مشرف تتيح لك ضبط رسالة الإشعارات الفورية وتشغيلها. ولكن يمكنك إنشاء برنامج لتشغيله على الجهاز أو أي أسلوب آخر يتيح الوصول إلى قائمة PushSubscription وتشغيل الرمز لبدء PushSubscription.

يتضمن العرض التوضيحي صفحة "إبداء الإعجاب بالمشرف" التي تتيح لك إرسال دفعة. بما أنّها مجرد نسخة تجريبية، فهي صفحة علنية.

سأشرح لك كل خطوة من خطوات تشغيل العرض الترويجي. ستكون هذه الخطوات بسيطة بحيث يمكن للجميع اتّباعها، بما في ذلك أي مستخدم جديد في Node.

عندما ناقشنا اشتراك أحد المستخدمين، غطّينا إضافة applicationServerKey إلى خيارات subscribe(). سنحتاج إلى هذا المفتاح الخاص في الخلفية.

في العرض الترويجي، تتم إضافة هذه القيم إلى تطبيق Node على النحو التالي (أعلم أنّ الرمز مملّ، ولكن أريد فقط إعلامك بأنّه ليس هناك أي سحر):

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

بعد ذلك، نحتاج إلى تثبيت الوحدة web-push لخادم العُقدة:

npm install web-push --save

بعد ذلك، في نص Node، نطلب وحدة web-push على النحو التالي:

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

يمكننا الآن بدء استخدام وحدة web-push. أولاً، علينا إخبار وحدة web-push بمفاتيح خادم التطبيقات. (تذكر أنها تُعرف أيضًا باسم مفاتيح VAPID لأن هذا هو اسم المواصفات).

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

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

يُرجى العلم أنّنا أدرجنا أيضًا سلسلة "mailto:". يجب أن تكون هذه السلسلة عنوان URL أو عنوان بريد إلكتروني mailto. سيتم إرسال هذه المعلومة إلى خدمة الإشعارات الفورية على الويب كجزء من طلب بدء إرسال إشعار فوري. ويعود سبب إجراء ذلك إلى أنّه إذا كانت خدمة الإشعارات الفورية على الويب تحتاج إلى التواصل مع المُرسِل، ستتوفّر لديها بعض المعلومات التي ستمكّنها من ذلك.

بعد ذلك، تكون وحدة web-push جاهزة للاستخدام، والخطوة التالية هي تنشيط رسالة فورية.

يستخدم العرض الترويجي لوحة المشرف الوهمية لتشغيل رسائل الإشعارات الفورية.

لقطة شاشة لصفحة المشرف

سيؤدي النقر على الزر "تشغيل رسالة فورية" إلى إرسال طلب POST إلى /api/trigger-push-msg/، وهي إشارة لنظامنا الأساسي لإرسال الرسائل الفورية، لذلك ننشئ المسار في express لهذه النقطة النهائية:

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

عند تلقّي هذا الطلب، نجلب الاشتراكات من قاعدة البيانات، ونُرسل رسالة فورية لكل اشتراك منها.

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() بعد ذلك استخدام مكتبة web-push لإرسال رسالة إلى الاشتراك الذي تم تقديمه.

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() إلى عرض وعد. إذا تم إرسال الرسالة بنجاح، سيُحلّ المشكلة وليس هناك أي إجراءات يجب اتّخاذها. أما في حال رفض الوعد، يجب فحص الخطأ، لأنّه سيُعلمك بما إذا كانت "PushSubscription" لا تزال صالحة أم لا.

لتحديد نوع الخطأ من خدمة الإرسال الفوري، من الأفضل الاطّلاع على رمز الحالة. تختلف رسائل الخطأ بين خدمات الدفع، وبعضها يكون أكثر فائدة من غيرها.

في هذا المثال، تتحقق الأداة من رمزَي الحالة 404 و410، وهما رمزا حالة HTTP لـ "لم يتم العثور على الصفحة" و"تم إلغاء العثور عليه". إذا تلقّينا إحدى هذه المستندات، يعني ذلك أنّ الاشتراك قد انتهت صلاحيته أو لم يعُد صالحًا. في هذه الحالات، نحتاج إلى إزالة الاشتراكات من قاعدة بياناتنا.

في حال حدوث خطأ آخر، نعلّق throw err فقط، ما يجعل الوعد الذي تم إرجاعه من خلال "triggerPushMsg()" يرفضه.

سنتناول بعض رموز الحالة الأخرى في القسم التالي عندما نلقي نظرة على بروتوكول إعلامات الويب العميقة بمزيد من التفصيل.

بعد تصفُّح الاشتراكات، يجب عرض استجابة 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}'`
    }
}));
});

لقد اطّلعنا على الخطوات الرئيسية لتنفيذ الميزة:

  1. أنشئ واجهة برمجة تطبيقات لإرسال الاشتراكات من صفحة الويب إلى الخلفية كي تتمكّن من حفظها في قاعدة بيانات.
  2. أنشئ واجهة برمجة تطبيقات لبدء إرسال رسائل الإشعارات الفورية (في هذه الحالة، واجهة برمجة تطبيقات يتمّ استدعاؤها من لوحة المشرف المزعومة).
  3. استرداد جميع الاشتراكات من الخلفية وإرسال رسالة إلى كل اشتراك باستخدام إحدى مكتبات web-push

بغض النظر عن الواجهة الخلفية (Node أو PHP أو Python أو ...)، فإن خطوات تنفيذ Push ستكون هي نفسها.

بعد ذلك، ما هي الوظيفة التي تؤديها لنا مكتبات Web Push بالضبط؟

الخطوات التالية

الدروس التطبيقية حول الترميز