في هذا الدرس التطبيقي حول الترميز، ستُنشئ خادم إشعارات فورية. سيدير الخادم قائمة بالاشتراكات في خدمة الإشعارات الفورية ويرسل إشعارات إليها.
اكتمل رمز العميل، وستعمل في هذا الدرس التطبيقي على وظائف جهة الخادم.
إعادة مزج نموذج التطبيق وعرضه في علامة تبويب جديدة
يتم تلقائيًا حظر الإشعارات من تطبيق Glitch المضمّن، لذا لن تتمكّن من معاينة التطبيق على هذه الصفحة. بدلاً من ذلك، إليك الخطوات التي يجب اتّباعها:
- انقر على Remix to Edit (إنشاء ريمكس لتعديله) ليصبح المشروع قابلاً للتعديل.
- لمعاينة الموقع الإلكتروني، اضغط على عرض التطبيق، ثم اضغط على ملء الشاشة .
يتم فتح التطبيق المباشر في علامة تبويب جديدة في Chrome. في Glitch المضمّنة، انقر على عرض المصدر لعرض الرمز مرة أخرى.
أثناء العمل على هذا الدليل التعليمي حول الرموز البرمجية، عليك إجراء تغييرات على الرمز البرمجي في Glitch المضمّنة في هذه الصفحة. يُرجى إعادة تحميل علامة التبويب الجديدة التي تتضمّن تطبيقك المباشر للاطّلاع على التغييرات.
التعرّف على التطبيق الأوّلي ورمزه
ابدأ بالاطّلاع على واجهة مستخدم العميل للتطبيق.
في علامة التبويب الجديدة في Chrome:
اضغط على "Control+Shift+J" (أو "Command+Option+J" على نظام التشغيل Mac) لفتح "أدوات مطوري البرامج". انقر على علامة التبويب وحدة التحكم.
جرِّب النقر على الأزرار في واجهة المستخدم (تحقَّق من وحدة تحكّم مطوّري برامج 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 لتطبيقك وأضِفهما إلى متغيّرات البيئة. حمِّل متغيّرات البيئة في الخادم وأضِف المفتاح العام كقيمة ثابتة في رمز العميل.
استخدِم الدالة
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);بعد أن يُعيد Glitch تشغيل تطبيقك، يُخرج المفاتيح التي تم إنشاؤها إلى سجلّ Node.js ضمن واجهة Glitch (وليس إلى وحدة تحكّم Chrome). للاطّلاع على مفاتيح VAPID، اختَر الأدوات -> السجلات في واجهة Glitch.
احرص على نسخ المفتاحَين العام والخاص من زوج المفاتيح نفسه.
يعيد Glitch تشغيل تطبيقك في كل مرة تعدّل فيها الرمز، لذا قد يتم الانتقال إلى أول زوج من المفاتيح التي تنشئها خارج الشاشة مع ظهور المزيد من النتائج.
في .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"في 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);في 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
}انسخ المفتاح العام والصقه في رمز العميل أيضًا.
في 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} `);
});
});
}
معالجة الاشتراكات الجديدة
الخلفية
في ما يلي ما يحدث عندما يشترك المستخدم في الإشعارات الفورية:
ينقر المستخدم على الاشتراك في الدفعة.
يستخدم العميل المتغير
VAPID_PUBLIC_KEY
الثابت (مفتاح VAPID العام للخادم) لإنشاء كائنsubscription
فريد خاص بالخادم. يبدو كائنsubscription
على النحو التالي:{
"endpoint": "https://fcm.googleapis.com/fcm/send/cpqAgzGzkzQ:APA9...",
"expirationTime": null,
"keys":
{
"p256dh": "BNYDjQL9d5PSoeBurHy2e4d4GY0sGJXBN...",
"auth": "0IyyvUGNJ9RxJc83poo3bA"
}
}يُرسِل العميل طلب
POST
إلى عنوان URL/add-subscription
، بما في ذلك الاشتراك بتنسيق JSON في النصّ.يستردّ الخادم السلسلة
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);
});