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

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

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

إعادة مزج نموذج التطبيق وعرضه في علامة تبويب جديدة

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

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

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

أثناء العمل على هذا الدرس التطبيقي حول الترميز، عليك إجراء تغييرات على الرمز في تأثير 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: عليها في تعليقات الرمز. عليك إجراء ما يلي:

    في هذا الدرس التطبيقي حول الترميز، ستعمل على عناصر قائمة المهام هذه واحدًا تلو الآخر.

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

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

الخلفية

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

يُسمى البروتوكول الذي يجعل الإشعارات الفورية آمنًا وخاصةً بـ "التعريف الطوعي لخادم التطبيقات" (VAPID). يستخدم VAPID تقنية تشفير المفتاح العام للتحقق من هوية التطبيقات والخوادم ونقاط نهاية الاشتراك، ولتشفير محتوى الإشعارات.

في هذا التطبيق، عليك استخدام حزمة 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...';
    ````
    

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

الخلفية

في هذا التطبيق، ستستخدم حزمة 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 للمشاركة. سيظهر لك معالج مسار التردّد في 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);
  });