الاشتراك في الإشعارات الفورية

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

وتدير JavaScript API هذه العملية بشكل مباشر. يشرح هذا الدليل العملية بأكملها، بما في ذلك رصد الميزة وطلب الإذن وإدارة عملية الاشتراك.

رصد الميزات

أولاً، تحقَّق مما إذا كان المتصفّح يتيح ميزة الإشعارات الفورية. يمكنك التحقّق من توفّر ميزة الإشعارات الفورية من خلال إجراءَين:

  • تحقَّق من السمة serviceWorker في العنصر navigator.
  • تحقَّق من السمة PushManager في العنصر window.
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

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

تسجيل مشغّل خدمات

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

عند تسجيل مشغّل خدمات، عليك إخبار المتصفّح بموقع ملف مشغّل الخدمات. الملف هو ملف JavaScript، ولكن المتصفّح يمنحه إمكانية الوصول إلى واجهات برمجة التطبيقات الخاصة بـ Service Worker، بما في ذلك ميزة الإشعارات الفورية. على وجه التحديد، يشغّل المتصفّح الملف في بيئة مشغّل الخدمات.

لتسجيل مشغّل خدمات، استدعِ navigator.serviceWorker.register() ومرِّر المسار إلى ملفك. على سبيل المثال:

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

تُعلم هذه الدالة المتصفّح بموقع ملف مشغّل الخدمات. في هذه الحالة، يكون ملف مشغّل الخدمات في /service-worker.js. بعد استدعاء register()، ينفّذ المتصفّح الخطوات التالية:

  1. نزِّل ملف مشغّل الخدمات.

  2. شغِّل JavaScript.

  3. إذا تم تشغيل الملف بشكل صحيح بدون أخطاء، يتم حلّ الوعد الذي يعرضه register(). في حال حدوث أخطاء، يتم رفض الوعد.

ملاحظة: إذا تم رفض register()، تحقَّق من JavaScript بحثًا عن أخطاء إملائية أو أخطاء أخرى في "أدوات مطوّري البرامج في Chrome".

عندما يتم حلّ register()، يتم عرض ServiceWorkerRegistration. يمكنك استخدام هذا التسجيل للوصول إلى واجهة برمجة التطبيقات PushManager.

توافُق المتصفّح مع واجهة برمجة التطبيقات PushManager API

Browser Support

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Source

طلب الإذن

بعد تسجيل مشغّل الخدمات والحصول على الإذن، احصل على إذن من المستخدم لإرسال رسائل فورية.

واجهة برمجة التطبيقات للحصول على الإذن بسيطة. ومع ذلك، تم تغيير واجهة برمجة التطبيقات مؤخرًا من استخدام دالة ردّ اتصال إلى عرض كائن Promise. بما أنّه لا يمكنك تحديد إصدار واجهة برمجة التطبيقات الذي ينفّذه المتصفّح، عليك تنفيذ كلا الإصدارَين والتعامل معهما.

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

في الرمز السابق، يؤدي استدعاء Notification.requestPermission() إلى عرض طلب للمستخدم:

يظهر طلب الإذن على متصفّح Chrome لأجهزة الكمبيوتر المكتبي والأجهزة الجوّالة.

بعد أن يتفاعل المستخدم مع طلب الإذن من خلال اختيار سماح أو حظر أو إغلاقه، ستتلقّى النتيجة كسلسلة: 'granted' أو 'default' أو 'denied'.

في الرمز النموذجي، يتم حلّ الوعد الذي يعرضه askPermission() في حال منح الإذن، وإلا سيظهر خطأ ويتم رفض الوعد.

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

يمنح معظم المستخدمين الإذن إذا فهموا سبب طلب التطبيق له.

ويتناول هذا المستند كيفية طلب بعض المواقع الإلكترونية الرائجة الإذن لاحقًا في هذا المستند.

اشتراك مستخدم باستخدام PushManager

بعد تسجيل مشغّل الخدمات والحصول على الإذن، يمكنك الاشتراك لمستخدم من خلال طلب registration.pushManager.subscribe().

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

عند استدعاء طريقة subscribe()، يتم تمرير كائن options يتكوّن من مَعلمات مطلوبة واختيارية.

يصف هذا القسم الخيارات التي يمكنك تمريرها.

خيارات userVisibleOnly

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

وكان مصدر القلق هو أنّ المطوّرين يمكنهم تتبُّع الموقع الجغرافي للمستخدم باستمرار بدون علمه.

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

يجب تمرير قيمة true. إذا لم تُدرِج مفتاح userVisibleOnly أو لم تمرِّر false، سيظهر لك الخطأ التالي:

Chrome currently only supports the Push API for subscriptions that will result
in user-visible messages. You can indicate this by calling
`pushManager.subscribe({userVisibleOnly: true})` instead. See
[https://goo.gl/yqv4Q4](https://goo.gl/yqv4Q4) for more details.

يتيح Chrome استخدام Push API فقط للاشتراكات التي تؤدي إلى ظهور رسائل للمستخدمين. يمكنك الإشارة إلى ذلك من خلال الاتصال بالرقم pushManager.subscribe({userVisibleOnly: true}). لمزيد من المعلومات، يُرجى الانتقال إلى https://goo.gl/yqv4Q4.

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

خيار applicationServerKey

سبق أن ذكر هذا المستند مفاتيح خادم التطبيقات. تستخدم خدمة الإشعارات الفورية مفاتيح خادم التطبيق لتحديد التطبيق الذي يشترك فيه المستخدم ولضمان أن يرسل التطبيق نفسه رسائل إلى ذلك المستخدم.

مفاتيح خادم التطبيق هي مفتاحا تشفير عام وخاص فريدان لتطبيقك. يجب الحفاظ على سرية المفتاح الخاص لتطبيقك، ومشاركة المفتاح العام بحرية.

الخيار applicationServerKey الذي تم تمريره إلى طلب subscribe() هو المفتاح العام لتطبيقك. ينقل المتصفّح هذا المفتاح إلى خدمة الإشعارات الفورية عند اشتراك المستخدم، ما يتيح لخدمة الإشعارات الفورية ربط المفتاح العام لتطبيقك بـ PushSubscription الخاص بالمستخدم.

يوضّح المخطّط البياني التالي هذه الخطوات.

  1. حمِّل تطبيق الويب في متصفّح واستدعِ subscribe()، مع تمرير مفتاح خادم التطبيق العام.
  2. بعد ذلك، يرسل المتصفّح طلب شبكة إلى خدمة إشعارات فورية، والتي تنشئ نقطة نهاية وتربطها بالمفتاح العام لتطبيقك، ثم تعرض نقطة النهاية للمتصفّح.
  3. يضيف المتصفّح نقطة النهاية هذه إلى PushSubscription، والتي يعرضها الوعد subscribe().

مخطّط بياني يوضّح كيفية استخدام مفتاح خادم التطبيق العام في طريقة `subscribe()‎`.

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

مخطّط بياني يوضّح كيفية استخدام مفتاح خادم التطبيق الخاص عند إرسال رسالة

من الناحية الفنية، يُعدّ applicationServerKey اختياريًا. ومع ذلك، يتطلّب أبسط تنفيذ على Chrome ذلك، وقد تتطلّبه المتصفّحات الأخرى في المستقبل. وهو اختياري في Firefox.

تحدّد مواصفات VAPID مفتاح خادم التطبيق. عندما ترى إشارات إلى مفاتيح خادم التطبيق أو مفاتيح VAPID، تذكَّر أنّها المفاتيح نفسها.

إنشاء مفاتيح خادم التطبيق

يمكنك إنشاء مجموعة عامة وخاصة من مفاتيح خادم التطبيقات باستخدام سطر أوامر إشعارات الويب لإنشاء المفاتيح على النحو التالي:

    $ npm install -g web-push
    $ web-push generate-vapid-keys

أنشِئ هذه المفاتيح مرة واحدة فقط لتطبيقك، واحرص على الحفاظ على خصوصية المفتاح الخاص.

الأذونات وsubscribe()‎

يؤدي الاتصال بـ "subscribe()" إلى تأثير جانبي واحد. إذا لم يكن تطبيق الويب لديه إذن لعرض الإشعارات عند استدعاء subscribe()، سيطلب المتصفّح الأذونات نيابةً عنك. يكون ذلك مفيدًا إذا كانت واجهة المستخدم تعمل مع هذا المسار، ولكن إذا أردت المزيد من التحكّم (وهو ما يفعله معظم المطوّرين)، استخدِم واجهة برمجة التطبيقات Notification.requestPermission() التي تناولها هذا المستند سابقًا.

نظرة عامة على PushSubscription

يمكنك استدعاء subscribe() وتمرير الخيارات وتلقّي وعد يتم حله إلى PushSubscription. على سبيل المثال:

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

يحتوي العنصر PushSubscription على جميع المعلومات المطلوبة لإرسال رسائل فورية إلى ذلك المستخدم. إذا طبعت المحتوى باستخدام JSON.stringify()، فستظهر لك النتيجة التالية:

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

‫endpoint هو عنوان URL لخدمة الإشعارات الفورية. لتشغيل رسالة فورية، أرسِل طلب POST إلى عنوان URL هذا.

يحتوي الكائن keys على القيم المستخدَمة لتشفير بيانات الرسائل المرسَلة مع رسالة فورية. (يتناول هذا المستند تشفير الرسائل لاحقًا).

إرسال اشتراك إلى الخادم

بعد الحصول على اشتراك في الإشعارات الفورية، أرسِله إلى خادمك. يمكنك تحديد كيفية إرسالها، ولكن ننصحك باستخدام JSON.stringify() لاستخراج جميع البيانات اللازمة من عنصر الاشتراك. بدلاً من ذلك، يمكنك تجميع النتيجة نفسها يدويًا، على سبيل المثال:

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

لإرسال الاشتراك من صفحة الويب، استخدِم ما يلي:

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

يتلقّى خادم Node.js هذا الطلب ويحفظ البيانات في قاعدة بيانات لاستخدامها لاحقًا.

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

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

باستخدام تفاصيل PushSubscription على الخادم، يمكنك إرسال رسالة إلى المستخدم في أي وقت.

أعِد الاشتراك بانتظام لتجنُّب انتهاء صلاحية الاشتراك

عند الاشتراك في الإشعارات الفورية، غالبًا ما تتلقّى PushSubscription.expirationTime من null. من الناحية النظرية، يعني ذلك أنّ صلاحية الاشتراك لا تنتهي أبدًا. (في المقابل، يشير الرمز DOMHighResTimeStamp إلى وقت انتهاء الصلاحية بالضبط). ومع ذلك، من الناحية العملية، تسمح المتصفّحات عادةً بانتهاء صلاحية الاشتراكات. على سبيل المثال، يمكن أن يحدث ذلك في حال عدم تلقّي أي إشعارات فورية لفترة طويلة، أو إذا رصد المتصفّح أنّ المستخدم لا يستخدم تطبيقًا لديه إذن بإرسال الإشعارات الفورية. ومن الأنماط التي تمنع حدوث ذلك إعادة اشتراك المستخدم عند تلقّي كل إشعار، كما هو موضّح في المقتطف التالي. يتطلّب ذلك إرسال الإشعارات بشكل متكرّر بما يكفي لمنع المتصفّح من إنهاء الاشتراك تلقائيًا. عليك الموازنة بعناية بين مزايا وعيوب احتياجات الإشعارات المشروعة مقابل إرسال رسائل غير مرغوب فيها إلى المستخدم بشكل لا إرادي فقط لمنع انتهاء صلاحية الاشتراك. في النهاية، يجب عدم محاولة التحايل على جهود المتصفّح لحماية المستخدم من الاشتراكات في الإشعارات التي نسيها منذ فترة طويلة.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

الأسئلة الشائعة

في ما يلي بعض الأسئلة الشائعة:

هل يمكن تغيير خدمة الإشعارات الفورية التي يستخدمها المتصفّح؟

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

هل تستخدم خدمات الإشعارات الفورية المختلفة واجهات برمجة تطبيقات مختلفة؟

تتوقّع جميع خدمات الإشعارات الفورية واجهة برمجة التطبيقات نفسها.

تصف واجهة برمجة التطبيقات الشائعة هذه، والتي تُعرف باسم بروتوكول Web Push، طلب الشبكة الذي يرسله تطبيقك لتشغيل رسالة فورية.

إذا اشتركت لمستخدم على الكمبيوتر المكتبي، هل يتم اشتراكه على هاتفه أيضًا؟

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

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

Codelabs