إنشاء خادم للإشعارات الفورية

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

اكتمل رمز العميل، وستعمل في هذا الدرس التطبيقي على وظائف جهة الخادم.

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

  1. انقر على Remix to Edit (إنشاء ريمكس لتعديله) ليصبح المشروع قابلاً للتعديل.
  2. لمعاينة الموقع الإلكتروني، اضغط على عرض التطبيق، ثم اضغط على ملء الشاشة ملء الشاشة.

يتم فتح التطبيق المباشر في علامة تبويب جديدة في Chrome. في Glitch المضمّنة، انقر على عرض المصدر لعرض الرمز مرة أخرى.

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

التعرّف على التطبيق الأوّلي ورمزه

ابدأ بالاطّلاع على واجهة مستخدم العميل للتطبيق.

في علامة التبويب الجديدة في Chrome:

  1. اضغط على "Control+Shift+J" (أو "Command+Option+J" على نظام التشغيل Mac) لفتح "أدوات مطوري البرامج". انقر على علامة التبويب وحدة التحكم.

  2. جرِّب النقر على الأزرار في واجهة المستخدم (تحقَّق من وحدة تحكّم مطوّري برامج Chrome للاطّلاع على الإخراج).

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

    • يؤدي النقر على الاشتراك في الإشعارات الفورية إلى إنشاء اشتراك في الإشعارات الفورية. لا يتوفّر هذا الإجراء إلا عند تسجيل عامل خدمة ووجود ثابت VAPID_PUBLIC_KEY في رمز العميل (مزيد من المعلومات عن هذا الموضوع لاحقًا)، لذا لا يمكنك النقر عليه الآن.

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

    • يؤدي اختيار إشعار جميع الاشتراكات إلى إرسال إشعار إلى الخادم إلى جميع نقاط نهاية الاشتراك في قاعدة بياناته.

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

لنطّلِع على ما يحدث من جهة الخادم. وللاطّلاع على الرسائل الواردة من رمز الخادم، اطّلِع على سجلّ Node.js في واجهة Glitch.

  • في تطبيق Glitch، انقر على الأدوات -> السجلّات.

    ستظهر لك على الأرجح رسالة مثل Listening on port 3000.

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

    TODO: Implement sendNotifications()
    Endpoints to send to:  []

الآن لنلقِ نظرة على بعض التعليمات البرمجية.

  • يحتوي public/index.js على رمز العميل المكتمل. ويعمل هذا الإجراء على رصد الميزات وتسجيل مشغّل الخدمات وإلغاء تسجيله والتحكّم في اشتراك المستخدم في الإشعارات الفورية. ويُرسِل التطبيق أيضًا معلومات عن الاشتراكات الجديدة والمحذوفة إلى الخادم.

    بما أنّك ستتعامل فقط مع وظيفة الخادم، لن تعدِّل هذا الملف (باستثناء تعبئة الثابت VAPID_PUBLIC_KEY).

  • public/service-worker.js هو عامل خدمة بسيط يسجّل أحداث الإشعارات الفورية ويعرض الإشعارات.

  • يحتوي /views/index.html على واجهة مستخدم التطبيق.

  • يحتوي .env على متغيّرات البيئة التي تحمّلها Glitch في خادم تطبيقك عند تشغيله. ستُعبئ .env بتفاصيل المصادقة لإرسال الإشعارات.

  • server.js هو الملف الذي ستُجري فيه معظم عملك خلال هذا البرنامج التعليمي.

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

    في هذا الدليل التعليمي حول الرموز البرمجية، ستتولّى تنفيذ كل عنصر من عناصر "المهام التي يجب إكمالها" واحدًا تلو الآخر.

إنشاء تفاصيل VAPID وتحميلها

عنصر TODO الأول هو إنشاء تفاصيل VAPID وإضافتها إلى متغيرات بيئة Node.js وتعديل رمز العميل والخادم بالقيم الجديدة.

الخلفية

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

يُعرف البروتوكول الذي يجعل الإشعارات الفورية آمنة وخاصة باسم Voluntary Application Server Identification for Web Push ‏ (VAPID). يستخدم بروتوكول VAPID التشفير باستخدام مفتاح عام للتحقّق من هوية التطبيقات والخوادم ونقاط نهاية الاشتراكات، ولتشفير محتوى الإشعارات.

في هذا التطبيق، ستستخدم مجموعة web-push npm لإنشاء مفاتيح VAPID وتشفير الإشعارات وإرسالها.

التنفيذ

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

  1. استخدِم الدالة generateVAPIDKeys من مكتبة web-push لإنشاء مفتاحَي VAPID.

    في server.js، أزِل التعليقات من سطور الرمز التالية:

    server.js

    // Generate VAPID keys (only do this once).
    /*
     * const vapidKeys = webpush.generateVAPIDKeys();
     * console.log(vapidKeys);
     */

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  2. بعد أن يُعيد Glitch تشغيل تطبيقك، يُخرج المفاتيح التي تم إنشاؤها إلى سجلّ Node.js ضمن واجهة Glitch (وليس إلى وحدة تحكّم Chrome). للاطّلاع على مفاتيح VAPID، اختَر الأدوات -> السجلات في واجهة Glitch.

    احرص على نسخ المفتاحَين العام والخاص من زوج المفاتيح نفسه.

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

  3. في ‎.env، انسخ مفاتيح VAPID والصقها. يمكنك وضع المفاتيح بين علامتَي اقتباس ("...").

    بالنسبة إلى VAPID_SUBJECT، يمكنك إدخال "mailto:test@test.test".

    .env

    # process.env.SECRET
    VAPID_PUBLIC_KEY
    =
    VAPID_PRIVATE_KEY
    =
    VAPID_SUBJECT
    =
    VAPID_PUBLIC_KEY
    ="BN3tWzHp3L3rBh03lGLlLlsq..."
    VAPID_PRIVATE_KEY
    ="I_lM7JMIXRhOk6HN..."
    VAPID_SUBJECT
    ="mailto:test@test.test"
  4. في server.js، أزِل التعليقات من سطرَي الرمز البرمجي مرة أخرى، لأنّك تحتاج إلى إنشاء مفاتيح VAPID مرة واحدة فقط.

    server.js

    // Generate VAPID keys (only do this once).
    /*
    const vapidKeys = webpush.generateVAPIDKeys();
    console.log(vapidKeys);
    */

    const vapidKeys = webpush.generateVAPIDKeys();
    console
    .log(vapidKeys);
  5. في server.js، حمِّل تفاصيل VAPID من متغيّرات البيئة.

    server.js

    const vapidDetails = {
     
    // TODO: Load VAPID details from environment variables.
      publicKey
    : process.env.VAPID_PUBLIC_KEY,
      privateKey
    : process.env.VAPID_PRIVATE_KEY,
      subject
    : process.env.VAPID_SUBJECT
    }
  6. انسخ المفتاح العام والصقه في رمز العميل أيضًا.

    في public/index.js، أدخِل القيمة نفسها لـ VAPID_PUBLIC_KEY التي نسختها إلى ملف ‎ .env:

    public/index.js

    // Copy from .env
    const VAPID_PUBLIC_KEY = '';
    const VAPID_PUBLIC_KEY = 'BN3tWzHp3L3rBh03lGLlLlsq...';
    ````

تنفيذ وظيفة إرسال الإشعارات

الخلفية

في هذا التطبيق، ستستخدم حزمة web-push npm لإرسال الإشعارات.

تشفّر هذه الحزمة الإشعارات تلقائيًا عند استدعاء webpush.sendNotification()، لذا لا داعي للقلق بشأن ذلك.

يقبل web-push خيارات متعددة للإشعارات - على سبيل المثال، يمكنك إرفاق عناوين بالرسالة وتحديد ترميز المحتوى.

في هذا الدليل التعليمي حول الرموز البرمجية، ستستخدم خيارَين فقط، تم تحديدهما باستخدام سطور الرمز البرمجي التالية:

let options = {
  TTL
: 10000; // Time-to-live. Notifications expire after this.
  vapidDetails
: vapidDetails; // VAPID keys from .env
};

يحدِّد الخيار TTL (مدة الصلاحية) مهلة انتهاء صلاحية للإشعار. وهذه هي الطريقة التي يتّبعها الخادم لتجنُّب إرسال إشعار إلى المستخدم بعد أن يصبح غير ذي صلة.

يحتوي الخيار vapidDetails على مفاتيح VAPID التي حمّلتها من متغيّرات البيئة.

التنفيذ

في server.js، عدِّل الدالة sendNotifications على النحو التالي:

server.js

function sendNotifications(database, endpoints) {
 
// TODO: Implement functionality to send notifications.
  console
.log('TODO: Implement sendNotifications()');
  console
.log('Endpoints to send to: ', endpoints);
  let notification
= JSON.stringify(createNotification());
  let options
= {
    TTL
: 10000, // Time-to-live. Notifications expire after this.
    vapidDetails
: vapidDetails // VAPID keys from .env
 
};
  endpoints
.map(endpoint => {
    let subscription
= database[endpoint];
    webpush
.sendNotification(subscription, notification, options);
 
});
}

بما أنّ webpush.sendNotification() يعرض وعدًا، يمكنك بسهولة إضافة معالجة للأخطاء.

في server.js، عدِّل الدالة sendNotifications مرة أخرى:

server.js

function sendNotifications(database, endpoints) {
  let notification
= JSON.stringify(createNotification());
  let options
= {
    TTL
: 10000; // Time-to-live. Notifications expire after this.
    vapidDetails
: vapidDetails; // VAPID keys from .env
 
};
  endpoints
.map(endpoint => {
    let subscription
= database[endpoint];
    webpush
.sendNotification(subscription, notification, options);
    let id
= endpoint.substr((endpoint.length - 8), endpoint.length);
    webpush
.sendNotification(subscription, notification, options)
   
.then(result => {
      console
.log(`Endpoint ID: ${id}`);
      console
.log(`Result: ${result.statusCode} `);
   
})
   
.catch(error => {
      console
.log(`Endpoint ID: ${id}`);
      console
.log(`Error: ${error.body} `);
   
});
 
});
}

معالجة الاشتراكات الجديدة

الخلفية

في ما يلي ما يحدث عندما يشترك المستخدم في الإشعارات الفورية:

  1. ينقر المستخدم على الاشتراك في الدفعة.

  2. يستخدم العميل المتغير VAPID_PUBLIC_KEY الثابت (مفتاح VAPID العام للخادم) لإنشاء كائن subscription فريد خاص بالخادم. يبدو كائن subscription على النحو التالي:

       {
         
    "endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
         
    "expirationTime": null,
         
    "keys":
         
    {
           
    "p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
           
    "auth": "0IyyvUGNJ9RxJc83poo3bA"
         
    }
       
    }
  3. يُرسِل العميل طلب POST إلى عنوان URL /add-subscription، بما في ذلك الاشتراك بتنسيق JSON في النصّ.

  4. يستردّ الخادم السلسلة subscription من نص طلب POST، ويحلّلها إلى تنسيق JSON، ويضيفها إلى قاعدة بيانات الاشتراكات.

    تخزِّن قاعدة البيانات الاشتراكات باستخدام نقاط النهاية الخاصة بها كمفتاح:

    {
     
"https://fcm...1234": {
        endpoint
: "https://fcm...1234",
        expirationTime
: ...,
        keys
: { ... }
     
},
     
"https://fcm...abcd": {
        endpoint
: "https://fcm...abcd",
        expirationTime
: ...,
        keys
: { ... }
     
},
     
"https://fcm...zxcv": {
        endpoint
: "https://fcm...zxcv",
        expirationTime
: ...,
        keys
: { ... }
     
},
   
}

أصبح الاشتراك الجديد متاحًا الآن للخادم لإرسال الإشعارات.

التنفيذ

تصل طلبات الاشتراكات الجديدة إلى المسار /add-subscription، وهو عنوان URL لطلب POST. سيظهر لك معالِج مسار نموذجي في server.js:

server.js

app.post('/add-subscription', (request, response) => {
 
// TODO: implement handler for /add-subscription
  console
.log('TODO: Implement handler for /add-subscription');
  console
.log('Request body: ', request.body);
  response
.sendStatus(200);
});

في عملية التنفيذ، يجب أن يستوفي معالِج الأحداث ما يلي:

  • يمكنك استرداد الاشتراك الجديد من نص الطلب.
  • الوصول إلى قاعدة بيانات الاشتراكات النشطة
  • أضِف الاشتراك الجديد إلى قائمة الاشتراكات النشطة.

لمعالجة الاشتراكات الجديدة:

  • في server.js، عدِّل معالِج المسار لـ /add-subscription على النحو التالي:

    server.js

    app.post('/add-subscription', (request, response) => {
     
// TODO: implement handler for /add-subscription
      console
.log('TODO: Implement handler for /add-subscription');
      console
.log('Request body: ', request.body);
      let subscriptions
= Object.assign({}, request.session.subscriptions);
      subscriptions
[request.body.endpoint] = request.body;
      request
.session.subscriptions = subscriptions;
      response
.sendStatus(200);
   
});

معالجة عمليات إلغاء الاشتراك

الخلفية

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

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

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

التنفيذ

تصل طلبات إلغاء الاشتراكات إلى عنوان URL لصفحة POST /remove-subscription.

يبدو معالِج مسار الرمز المُعدّل في server.js على النحو التالي:

server.js

app.post('/remove-subscription', (request, response) => {
 
// TODO: implement handler for /remove-subscription
  console
.log('TODO: Implement handler for /remove-subscription');
  console
.log('Request body: ', request.body);
  response
.sendStatus(200);
});

في عملية التنفيذ، يجب أن يستوفي هذا المعالج ما يلي:

  • استرداد نقطة نهاية الاشتراك المُلغى من نص الطلب
  • الوصول إلى قاعدة بيانات الاشتراكات النشطة
  • أزِل الاشتراك المُلغى من قائمة الاشتراكات النشطة.

يحتوي نص طلب POST المقدَّم من العميل على نقطة النهاية التي يجب إزالتها:

{
 
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9..."
}

لمعالجة عمليات إلغاء الاشتراك:

  • في server.js، عدِّل معالِج المسار لـ /remove-subscription على النحو التالي:

    server.js

  app.post('/remove-subscription', (request, response) => {
   
// TODO: implement handler for /remove-subscription
    console
.log('TODO: Implement handler for /remove-subscription');
    console
.log('Request body: ', request.body);
    let subscriptions
= Object.assign({}, request.session.subscriptions);
   
delete subscriptions[request.body.endpoint];
    request
.session.subscriptions = subscriptions;
    response
.sendStatus(200);
 
});