الاشتراك في قناة مستخدم

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

يمكن استخدام واجهة برمجة تطبيقات JavaScript لإجراء ذلك بشكل مباشر ومنطقي، لذا هيا ننتقل إلى الخطوات المنطقية.

رصد الميزات

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

  1. ابحث عن serviceWorker في أداة التنقّل.
  2. ابحث عن PushManager في نافذة.
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;
}

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

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

من خلال هذه الميزة، نعلم أنّ كلاً من مشغّلي الخدمات وخدمة Push متاحون. الخطوة التالية هي "تسجيل" عامل الخدمات لدينا.

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

لتسجيل مشغّل الخدمات، اتصل بـ 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 API.

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

التوافق مع المتصفح

  • 42
  • 17
  • 44
  • 16

المصدر

جارٍ طلب الإذن

لقد سجّلنا مشغّل الخدمات

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

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 المتوافق مع أجهزة الكمبيوتر المكتبي والأجهزة الجوّالة

بعد تفاعل المستخدم مع طلب الإذن بالضغط على "Allow" (السماح) أو "Block" (حظر) أو مجرد إغلاقه، ستظهر النتيجة على شكل سلسلة: '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، يتألف من معلَمات مطلوبة واختيارية معًا.

دعونا نلقي نظرة على كل الخيارات التي يمكننا تمريرها.

خيارات userمرئي Only

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

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

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

في الوقت الحالي، يجب ضبط القيمة true. إذا لم يتم تضمين مفتاح userVisibleOnly أو المرور في false، سيظهر الخطأ التالي:

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

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

خيار appServerKey

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

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

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

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

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

صورة توضيحية لمفتاح خادم التطبيقات العام في طريقة الاشتراك

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

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

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

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

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

يمكنك إنشاء مجموعة عامة وخاصة من مفاتيح خادم التطبيقات عن طريق الانتقال إلى web-push-codelab.glitch.me أو استخدام سطر الأوامر web-push لإنشاء المفاتيح عن طريق اتّباع الخطوات التالية:

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

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

الأذونات والاشتراك()

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

ما المقصود بـ Pushاشتراك؟

نحن نسمي 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.');
      }
    });
}

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

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 على خادمنا، يسرّنا إرسال رسالة إلى المستخدم وقتما يشاء.

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

بعض الأسئلة الشائعة التي طرحها الأشخاص في هذه المرحلة:

هل يمكنني تغيير الخدمة الفورية التي يستخدمها أحد المتصفحات؟

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

يستخدم كل متصفح خدمة Push Service مختلفة، أليست له واجهات برمجة تطبيقات مختلفة؟

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

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

في حال اشتركتُ مع مستخدم من جهاز كمبيوتر مكتبي، هل هو أيضًا مشترك من هاتفه؟

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

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

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