بروتوكول Web Push

لقد اطّلعنا على كيفية استخدام مكتبة لتشغيل رسائل فورية، ولكن ما هو دور هذه المكتبات بالضبط؟

حسنًا، إنّها تقدّم طلبات للشبكة مع التأكّد من أنّ هذه الطلبات بالتنسيق الصحيح. المواصفة التي تحدّد طلب الشبكة هذا هي Web Push Protocol.

مخطّط بياني لإرسال رسالة فورية من خادمك إلى
خدمة فورية

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

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

مفاتيح خادم التطبيقات

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

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

ماذا يعني كل هذا في الواقع وما الذي يحدث بالضبط؟ في ما يلي الخطوات التي يتم اتّخاذها لمحاولة مصادقة خادم التطبيقات:

  1. يوقّع خادم التطبيقات بعض معلومات JSON باستخدام مفتاح التطبيق الخاص.
  2. ويتم إرسال هذه المعلومات الموقَّعة إلى خدمة الإشعارات الفورية كعنوان في طلب POST.
  3. تستخدم خدمة الإرسال الفوري المفتاح العام المخزّن الذي تلقّته من pushManager.subscribe() للتحقّق من توقيع العميل باستخدام المفتاح الخاص المرتبط بالمفتاح العام على المعلومات المستلَمة. تذكَّر: المفتاح العام هو applicationServerKey الذي تم تمريره إلى طلب الاشتراك.
  4. إذا كانت المعلومات الموقَّعة صالحة، تُرسِل خدمة الدفع المدفوعة الرسائل المدفوعة إلى المستخدم.

في ما يلي مثال على تدفّق المعلومات هذا. (يُرجى ملاحظة التوضيح في أسفل يمين الصفحة للإشارة إلى المفتاحَين العام والخاص).

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

إنّ "المعلومات الموقَّعة" التي تمت إضافتها إلى عنوان في الطلب هي رمز JSON المميّز للويب.

رمز JSON المميّز للويب

رمز JSON المميّز للويب (أو JWT اختصارًا) هو طريقة ل إرسال رسالة إلى جهة خارجية كي يتمكّن المستلِم من التحقّق من هوية مُرسِلها.

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

هناك مجموعة من المكتبات على https://jwt.io/ التي يمكنها إجراء التوقيع نيابةً عنك، وأنصحك بتنفيذ ذلك متى أمكن. لإكمال هذه المقالة، لنلقِ نظرة على كيفية إنشاء رمز JWT موقَّع يدويًا.

إشعارات الويب والرموز الموقَّعة من تنسيق JWT

رمز JWT موقَّع هو مجرد سلسلة، إلا أنّه يمكن اعتباره ثلاث سلاسل مرتبطة بنقاط.

صورة توضيحية للسلاسل في رمز JSON المميّز للويب

السلسلتان الأولى والثانية (معلومات JWT وبيانات JWT) هما جزءان من ملف JSON تم تشفيرهما باستخدام ترميز base64، ما يعني أنّه يمكن قراءتهما بشكل علني.

السلسلة الأولى هي معلومات عن JWT نفسه، وتشير إلى الخوارزمية التي تم استخدامها لإنشاء التوقيع.

يجب أن تحتوي معلومات JWT لرسائل Web Push على المعلومات التالية:

{
 
"typ": "JWT",
 
"alg": "ES256"
}

السلسلة الثانية هي بيانات JWT. يوفّر هذا الحقل معلومات عن مُرسِل رمز JWT والجهة المقصودة منه ومدة صلاحيته.

بالنسبة إلى الإشعارات الفورية على الويب، سيكون تنسيق البيانات على النحو التالي:

{
 
"aud": "https://some-push-service.org",
 
"exp": "1469618703",
 
"sub": "mailto:example@web-push-book.org"
}

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

القيمة exp هي تاريخ انتهاء صلاحية رمز JWT، ويمنع ذلك المتلصصين من إعادة استخدام رمز JWT إذا اعترضوه. صلاحية الرمز هي طابع زمني بالثواني، ويجب ألا تزيد عن 24 ساعة.

في Node.js، يتم ضبط تاريخ انتهاء الصلاحية باستخدام:

Math.floor(Date.now() / 1000) + 12 * 60 * 60;

تكون المدة 12 ساعة بدلاً من 24 ساعة لتجنُّب أي مشاكل متعلّقة باختلافات الساعات بين تطبيق الإرسال وخدمة الإشعارات الفورية.

أخيرًا، يجب أن تكون قيمة sub عنوان URL أو عنوان بريد إلكتروني mailto. ويعود السبب في ذلك إلى أنّه إذا أرادت خدمة الإرسال المباشر التواصل مع المُرسِل، يمكنها العثور على معلومات الاتصال من JWT. (لهذا السبب، كانت مكتبة Web-Push بحاجة إلى عنوان بريد إلكتروني).

تمامًا مثل معلومات JWT، يتم ترميز بيانات JWT كسلسلة base64 آمنة لعنوان URL.

السلسلة الثالثة، وهي التوقيع، هي نتيجة أخذ أول سلسلتَين (معلومات JWT وبيانات JWT) ودمجهما معًا باستخدام حرف نقطة، والذي سندعوه "الرمز المميّز غير الموقَّع"، ثم توقيعه.

تتطلّب عملية التوقيع تشفير "الرمز المميّز غير الموقَّع" باستخدام ES256. وفقًا لمواصفات JWT، ES256 هي اختصار لـ "ECDSA باستخدام منحنى P-256 و خوارزمية التجزئة SHA-256". باستخدام التشفير على الويب، يمكنك إنشاء التوقيع على النحو التالي:

// Utility function for UTF-8 encoding a string to an ArrayBuffer.
const utf8Encoder = new TextEncoder('utf-8');

// The unsigned token is the concatenation of the URL-safe base64 encoded
// header and body.
const unsignedToken = .....;

// Sign the |unsignedToken| using ES256 (SHA-256 over ECDSA).
const key = {
  kty
: 'EC',
  crv
: 'P-256',
  x
: window.uint8ArrayToBase64Url(
    applicationServerKeys
.publicKey.subarray(1, 33)),
  y
: window.uint8ArrayToBase64Url(
    applicationServerKeys
.publicKey.subarray(33, 65)),
  d
: window.uint8ArrayToBase64Url(applicationServerKeys.privateKey),
};

// Sign the |unsignedToken| with the server's private key to generate
// the signature.
return crypto.subtle.importKey('jwk', key, {
  name
: 'ECDSA', namedCurve: 'P-256',
}, true, ['sign'])
.then((key) => {
 
return crypto.subtle.sign({
    name
: 'ECDSA',
    hash
: {
      name
: 'SHA-256',
   
},
 
}, key, utf8Encoder.encode(unsignedToken));
})
.then((signature) => {
  console
.log('Signature: ', signature);
});

يمكن لخدمة الإرسال الفوري التحقّق من صحة ملف JWT باستخدام مفتاح خادم التطبيق العام لفك تشفير التوقيع والتأكّد من أنّ السلسلة التي تم فك تشفيرها هي نفسها "الرمز المميّز غير الموقَّع" (أي أول سلسلتَين في ملف JWT).

يتم إرسال ملف JWT الموقَّع (أي جميع السلاسل الثلاث التي تم ربطها بنقاط) إلى خدمة الدفع على الويب كعنوان Authorization مع إضافة WebPush في البداية، على النحو التالي:

Authorization: 'WebPush [JWT Info].[JWT Data].[Signature]';

ينصّ "بروتوكول Web Push" أيضًا على أنّه يجب إرسال مفتاح خادم التطبيقات العام في عنوان Crypto-Key كسلسلة بترميز base64 آمنة لعنوان URL مع prepended p256ecdsa=.

Crypto-Key: p256ecdsa=[URL Safe Base64 Public Application Server Key]

تشفير الحمولة

بعد ذلك، لنلقِ نظرة على كيفية إرسال حمولة مع رسالة دفع حتى يتمكّن تطبيق الويب من الوصول إلى البيانات التي يتلقّاها عند تلقّي رسالة دفع.

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

من مزايا إشعارات الويب الفورية أنّه بما أنّ جميع خدمات الإشعارات الفورية تستخدم واجهة برمجة التطبيقات نفسها (بروتوكول إشعارات الويب الفورية)، لا يحتاج المطوّرون إلى الاهتمام بجهة تقديم خدمة الإشعارات الفورية. يمكننا تقديم طلب بالتنسيق الصحيح والتوقع أن يتم إرسال رسالة إشعار فوري. الجانب السلبي لهذا الإجراء هو أنّه يمكن للمطوّرين إرسال الرسائل إلى خدمة دفع غير جديرة بالثقة. من خلال تشفير الحمولة، لا يمكن لخدمة الدفع قراءة البيانات المُرسَلة. ولا يمكن لأي جهة أخرى فك تشفير المعلومات سوى المتصفّح. ويؤدي ذلك إلى حماية data المستخدم.

يتم تحديد تشفير الحمولة في مواصفات تشفير الرسائل.

قبل الاطّلاع على الخطوات المحدّدة لتشفير الحمولة في الرسائل الفورية، علينا تغطية بعض الأساليب التي سيتم استخدامها أثناء عملية التشفير. (أودّ أن أشكر Mat Scales على مقالته الرائعة حول التشفير العميق.)

دالة ECDH ودالة HKDF

يتم استخدام كل من ECDH وHKDF طوال عملية التشفير ويقدّمان مزايا بهدف تشفير المعلومات.

‫ECDH: تبادل مفاتيح Diffie-Hellman للمنحنى الإهليلجي

لنفترض أنّ لديك شخصَين يريدان مشاركة المعلومات، هما "نور" و"علي". يملك كل من "نبيلة" و"يوسف" مفتاحَيه العام والخاص. تشارك نبيلة ويوسف مفتاحَيهما العامَّين مع بعضهما.

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

حسب معلوماتي، تحدِّد دالة ECDH خصائص المنحنيات التي تسمح بهذه "الميزة" لإنشاء سر مشترَك "X".

إليك شرحًا عامًا عن مفتاح التشفير المتماثل (ECDH). إذا أردت معرفة المزيد من المعلومات، ننصحك بمشاهدة هذا الفيديو.

من حيث الرمز البرمجي، تأتي معظم اللغات أو المنصات مع مكتبات لتسهيل إنشاء هذه المفاتيح.

في العقدة، سننفّذ ما يلي:

const keyCurve = crypto.createECDH('prime256v1');
keyCurve
.generateKeys();

const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();

دالة HKDF: دالة اشتقاق المفاتيح المستندة إلى معيار HMAC

تقدّم ويكيبيديا وصفًا موجزًا HKDF:

دالة HKDF هي دالة اشتقاق مفاتيح تستند إلى HMAC، وهي تحوّل أي مادة مفاتيح ضعيفة إلى مادة مفاتيح قوية من الناحية التشفيرية. ويمكن استخدامه، مثلاً، لتحويل الأسرار المشتركة التي تم تبادلها باستخدام خوارزمية Diffie Hellman إلى مادة مفاتيح مناسبة للاستخدام في التشفير أو التحقّق من السلامة أو المصادقة.

في الأساس، سيأخذ HKDF مدخلات غير آمنة بشكل خاص ويجعلها أكثر أمانًا.

تتطلّب المواصفات التي تحدّد هذا التشفير استخدام SHA-256 كخوارزمية تجزئة ، ويجب ألا تزيد طول مفاتيح HKDF الناتجة في ميزة "الدفع على الويب" عن 256 بت (32 بايت).

في العقدة، يمكن تنفيذ ذلك على النحو التالي:

// Simplified HKDF, returning keys up to 32 bytes long
function hkdf(salt, ikm, info, length) {
 
// Extract
 
const keyHmac = crypto.createHmac('sha256', salt);
  keyHmac
.update(ikm);
 
const key = keyHmac.digest();

 
// Expand
 
const infoHmac = crypto.createHmac('sha256', key);
  infoHmac
.update(info);

 
// A one byte long buffer containing only 0x01
 
const ONE_BUFFER = new Buffer(1).fill(1);
  infoHmac
.update(ONE_BUFFER);

 
return infoHmac.digest().slice(0, length);
}

نشكر Mat Scale على مقالته التي تضمّ هذا المثال على الرمز البرمجي.

يتناول هذا القسم بشكل فضفاض ECDH وHKDF.

تشكّل خوارزمية ECDH طريقة آمنة لمشاركة المفاتيح العامة وإنشاء سر مشترَك. ‫HKDF هي طريقة لأخذ مواد غير آمنة وجعلها آمنة.

سيتم استخدام هذا أثناء تشفير الحمولة. بعد ذلك، لنلقِ نظرة على ما نأخذه كข้อมูล مدخلة وكيفية تشفيرها.

مدخلات

عندما نريد إرسال رسالة فورية إلى مستخدم تتضمّن حمولة، هناك ثلاثة مدخلات نحتاج إليها:

  1. الحمولة نفسها
  2. سر auth من PushSubscription
  3. مفتاح p256dh من PushSubscription

لقد لاحظنا أنّ قيم auth وp256dh يتم استرجاعها من PushSubscription، ولكن للتذكير، سنحتاج إلى القيم التالية في حال توفّر اشتراك:

subscription.toJSON().keys.auth;
subscription
.toJSON().keys.p256dh;

subscription
.getKey('auth');
subscription
.getKey('p256dh');

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

مفتاح p256dh هو مفتاح عام، ويُشار إليه أحيانًا باسم المفتاح العام للعميل. في ما يلي، سنشير إلى p256dh على أنّه المفتاح العام للاشتراك. ينشئ المتصفّح المفتاح العام للاشتراك. سيحافظ المتصفّح على سرية المفتاح الخاص وسيستخدمه لفك تشفير ملف التحميل.

يجب استخدام القيم الثلاث auth وp256dh وpayload كمدخلات، وستكون نتيجة عملية التشفير هي الحمولة المشفَّرة وقيمة الملح ومفتاح عام يُستخدَم فقط لتشفير البيانات.

الملح

يجب أن تكون القيمة العشوائية التي يتم استخدامها عبارة عن 16 بايت من البيانات العشوائية. في NodeJS، يمكننا إجراء ما يلي لإنشاء الملح:

const salt = crypto.randomBytes(16);

المفاتيح العامة / الخاصة

يجب إنشاء المفتاحَين العام والخاص باستخدام منحنى إهليجي من النوع P-256، ويمكنك إجراء ذلك في Node على النحو التالي:

const localKeysCurve = crypto.createECDH('prime256v1');
localKeysCurve
.generateKeys();

const localPublicKey = localKeysCurve.getPublicKey();
const localPrivateKey = localKeysCurve.getPrivateKey();

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

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

المفتاح السري المشترَك

الخطوة الأولى هي إنشاء مفتاح سري مشترَك باستخدام المفتاح العام للاشتراك والمفتاح الخاص الجديد (هل تتذكر شرح ECDH مع "منى" و"بدر"؟ تمامًا مثل ذلك).

const sharedSecret = localKeysCurve.computeSecret(
  subscription
.keys.p256dh,
 
'base64',
);

ويُستخدَم ذلك في الخطوة التالية لاحتساب المفتاح شبه العشوائي (PRK).

مفتاح عشوائي زائف

المفتاح شبه العشوائي (PRK) هو تركيبة من مفتاح مصادقة اشتراك الإشعارات الفورية والمفتاح السري المشترَك الذي أنشأناه للتو.

const authEncBuff = new Buffer('Content-Encoding: auth\0', 'utf8');
const prk = hkdf(subscription.keys.auth, sharedSecret, authEncBuff, 32);

قد تتساءل عن الغرض من سلسلة Content-Encoding: auth\0. باختصار، ليس له غرض واضح، على الرغم من أنّ المتصفّحات يمكنها فك تشفير رسالة واردة والبحث عن ترميز المحتوى المتوقّع. تُضيف القيمة \0 بايتًا بقيمة 0 إلى نهاية المخزن المؤقت. وهذا هو الإجراء الذي تتوقعه المتصفّحات التي تُفكّ تشفير الرسالة، والتي ستتوقع عددًا كبيرًا من البايتات لترميز المحتوى، متبوعًا بايت بقيمة 0، ثم بال data المشفَّرة.

إنّ مفتاحنا شبه العشوائي هو ببساطة تشغيل المصادقة والسرية المشتركة وجزء من معلومات التشفير من خلال HKDF (أي جعله أقوى من الناحية التشفيرية).

السياق

"السياق" هو مجموعة من البايتات التي يتم استخدامها لاحتساب قيمتَين لاحقًا في ملف التشفير المتصفّح. وهو في الأساس صفيف من البايتات يحتوي على المفتاح العام للاشتراك والملف العميق المفتاح العام.

const keyLabel = new Buffer('P-256\0', 'utf8');

// Convert subscription public key into a buffer.
const subscriptionPubKey = new Buffer(subscription.keys.p256dh, 'base64');

const subscriptionPubKeyLength = new Uint8Array(2);
subscriptionPubKeyLength
[0] = 0;
subscriptionPubKeyLength
[1] = subscriptionPubKey.length;

const localPublicKeyLength = new Uint8Array(2);
subscriptionPubKeyLength
[0] = 0;
subscriptionPubKeyLength
[1] = localPublicKey.length;

const contextBuffer = Buffer.concat([
  keyLabel
,
  subscriptionPubKeyLength
.buffer,
  subscriptionPubKey
,
  localPublicKeyLength
.buffer,
  localPublicKey
,
]);

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

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

مفتاح تشفير المحتوى والمفتاح العشوائي

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

مفتاح تشفير المحتوى (CEK) هو المفتاح الذي سيتم استخدامه في نهاية المطاف لتشفير الحمولة.

نحتاج أولاً إلى إنشاء وحدات البايت للبيانات الخاصة بـ nonce وCEK، وهي ببساطة سلسلة ترميز للمحتوى متبوعة بوحدة تخزين السياق التي احتسبناها للتو:

const nonceEncBuffer = new Buffer('Content-Encoding: nonce\0', 'utf8');
const nonceInfo = Buffer.concat([nonceEncBuffer, contextBuffer]);

const cekEncBuffer = new Buffer('Content-Encoding: aesgcm\0');
const cekInfo = Buffer.concat([cekEncBuffer, contextBuffer]);

يتم تشغيل هذه المعلومات من خلال HKDF مع دمج الملح وPRK مع nonceInfo وcekInfo:

// The nonce should be 12 bytes long
const nonce = hkdf(salt, prk, nonceInfo, 12);

// The CEK should be 16 bytes long
const contentEncryptionKey = hkdf(salt, prk, cekInfo, 16);

يمنحنا ذلك مفتاح التشفير العشوائي ومفتاح تشفير المحتوى.

تنفيذ عملية التشفير

الآن بعد أن حصلنا على مفتاح تشفير المحتوى، يمكننا تشفير الحمولة.

ننشئ مفتاح تشفير AES128 باستخدام مفتاح تشفير المحتوى باعتباره المفتاح ويكون المفتاح العشوائي هو متجه الإعداد.

في Node، يتم ذلك على النحو التالي:

const cipher = crypto.createCipheriv(
 
'id-aes128-GCM',
  contentEncryptionKey
,
  nonce
,
);

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

يجب إضافة بايتين من الحشو للإشارة إلى طول أي حشو إضافي.

على سبيل المثال، إذا لم تُضِف أيّ بيانات تمديد، سيكون لديك بايتان بالقيمة 0، أي أنّه لا تتوفّر بيانات تمديد، وبعد هاتين البايتَين، ستقرأ الحمولة. إذا أضفت 5 بايت من الحشو، ستكون قيمة أول بايتين 5، لذلك سيقرأ المستهلك بعد ذلك خمسة بايت إضافية ثم يبدأ بقراءة الحمولة.

const padding = new Buffer(2 + paddingLength);
// The buffer must be only zeros, except the length
padding
.fill(0);
padding
.writeUInt16BE(paddingLength, 0);

بعد ذلك، نُجري عملية التشفير على الحشو والحمولة.

const result = cipher.update(Buffer.concat(padding, payload));
cipher
.final();

// Append the auth tag to the result -
// https://nodejs.org/api/crypto.html#crypto_cipher_getauthtag
const encryptedPayload = Buffer.concat([result, cipher.getAuthTag()]);

لدينا الآن الحمولة المشفّرة. رائع

كل ما تبقى هو تحديد كيفية إرسال الحمولة إلى خدمة الإرسال الفوري.

رؤوس الحمولة المشفَّرة ونصها

لإرسال هذه الحمولة المشفّرة إلى خدمة الإرسال الفوري، علينا تحديد بعض العناوين المختلفة في طلب POST.

عنوان التشفير

يجب أن يحتوي العنوان Encryption (التشفير) على الملح المستخدَم في تشفير الحمولة.

يجب أن يكون الملح الذي يبلغ طوله 16 بايت مشفَّرًا بترميز base64 لعنوان URL آمنًا ومُضافًا إلى عنوان Encryption (التشفير)، على النحو التالي:

Encryption: salt=[URL Safe Base64 Encoded Salt]

عنوان مفتاح التشفير

تبيّن لنا أنّه يتم استخدام العنوان Crypto-Key ضمن القسم "مفاتيح خادم التطبيقات" لاحتواء مفتاح خادم التطبيقات العام.

يُستخدَم هذا الرأس أيضًا لمشاركة المفتاح العام المحلي المستخدَم لتشفير حمولة البيانات.

يظهر العنوان الناتج على النحو التالي:

Crypto-Key: dh=[URL Safe Base64 Encoded Local Public Key String]; p256ecdsa=[URL Safe Base64 Encoded Public Application Server Key]

رؤوس نوع المحتوى وطوله وتشفيره

يشير رأس Content-Length إلى عدد وحدات البايت في الحمولة المشفّرة. إنّ عنوانَي Content-Type وContent-Encoding هما قيم ثابتة. يظهر ذلك أدناه.

Content-Length: [Number of Bytes in Encrypted Payload]
Content-Type: 'application/octet-stream'
Content-Encoding: 'aesgcm'

بعد ضبط هذه العناوين، علينا إرسال الحمولة المشفّرة كنص الطلب. لاحظ أنّه تم ضبط Content-Type على application/octet-stream. ويرجع ذلك إلى أنّه يجب إرسال الحمولة المشفَّرة كبثّ بايتات.

في NodeJS، يمكننا إجراء ذلك على النحو التالي:

const pushRequest = https.request(httpsOptions, function(pushResponse) {
pushRequest
.write(encryptedPayload);
pushRequest
.end();

هل هناك المزيد من العناوين؟

لقد غطّينا العناوين المستخدَمة لمفاتيح JWT / Application Server (أي كيفية تحديد التطبيق باستخدام خدمة الإرسال الفوري) وغطّينا العناوين المستخدَمة لإرسال حمولة مشفَّرة.

هناك رؤوس إضافية تستخدمها خدمات الدفع لتغيير سلوك الرسائل المُرسَلة. يكون بعض هذه العناوين مطلوبًا، في حين يكون البعض الآخر اختياريًا.

عنوان مدة البقاء (TTL)

مطلوب

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

TTL: [Time to live in seconds]

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

من الناحية الفنية، يمكن لخدمة الإشعارات الفورية تقليل TTL رسالة الإشعار الفوري إذا أرادت ذلك. يمكنك معرفة ما إذا حدث ذلك من خلال فحص العنوان TTL في الاستجابة الواردة من خدمة الإرسال الفوري.

الموضوع

اختياريّ

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

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

حاجة ماسة

اختياريّ

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

يتم تحديد قيمة العنوان كما هو موضّح أدناه. القيمة التلقائية هي normal.

Urgency: [very-low | low | normal | high]

كل العناصر معًا

إذا كانت لديك أسئلة أخرى حول آلية عمل هذه الميزة، يمكنك في أي وقت الاطّلاع على كيفية بدء المكتبات لرسائل الدفع على web-push-libs org.

بعد الحصول على حمولة مشفَّرة والعناوين أعلاه، ما عليك سوى إرسال طلب POST إلى endpoint في PushSubscription.

ما الذي سنفعله بالردّ على طلب POST هذا؟

استجابة من خدمة الإشعارات الفورية

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

رمز الحالة الوصف
201 تمّ إنشاء المحتوى. تم استلام طلب إرسال رسالة فورية وقبوله.
429 عدد الطلبات كبير جدًا. وهذا يعني أنّ خادم التطبيقات قد بلغ الحدّ الأقصى لمعدل الإرسال في خدمة الإشعارات الفورية. يجب أن تتضمّن خدمة الإرسال الفوري عنوان "Retry-After" لتحديد المدة التي يجب الانتظار خلالها قبل تقديم طلب آخر.
400 الطلب غير صالح. يعني ذلك بشكل عام أنّ أحد عناوينك غير صالح أو أنّ تنسيقه غير صحيح.
404 غير موجودة يشير ذلك إلى أنّ الاشتراك انتهت صلاحيته ولا يمكن استخدامه. في هذه الحالة، عليك حذف PushSubscription والانتظار إلى أن يعيد العميل اشتراك المستخدم.
410 تمّت إزالة المحتوى. لم يعُد الاشتراك صالحًا ويجب إزالته من خادم التطبيقات. يمكن تكرار ذلك من خلال استدعاء `unsubscribe()` على `PushSubscription`.
413 حجم الحمولة كبير جدًا. الحد الأدنى لحجم الحمولة التي يجب أن تتوافق معها خدمة الإرسال الفوري هو 4096 بايت (أو 4 كيلوبايت).

يمكنك أيضًا الاطّلاع على معيار Web Push (RFC8030) للحصول على مزيد من المعلومات حول رموز حالة HTTP.

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

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