بروتوكول Web Push

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

يجب أن تتضمّن معلومات JWT لإشعارات الويب الفورية ما يلي:

{
  "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. (لهذا السبب، احتاجت مكتبة الإشعارات الفورية على الويب إلى عنوان بريد إلكتروني).

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

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

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

// 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 كسلسلة آمنة لعنوان URL بترميز base64 مع إضافة p256ecdsa= في بدايتها.

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

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

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

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

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

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

قبل أن نلقي نظرة على الخطوات المحددة لتشفير حمولة رسائل الإشعارات الفورية، علينا تناول بعض الأساليب التي سيتم استخدامها أثناء عملية التشفير. (Massive hat tip to Mat Scales for his excellent article on push encryption.)

بروتوكول ديفي-هيلمان القائم على تشفير المنحنى الإهليلجي (ECDH) ودالة اشتقاق المفتاح المستندة إلى رمز مصادقة الرسالة المجزأ (HKDF)

يتم استخدام كل من بروتوكول ECDH ودالة HKDF خلال عملية التشفير، ويوفّران مزايا لغرض تشفير المعلومات.

‫ECDH: تبادل مفاتيح ديفي-هيلمان القائم على تشفير المنحنى الإهليلجي

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

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

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

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

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

في Node، سننفّذ ما يلي:

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

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

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

يحتوي موقع Wikipedia على وصف موجز لدالة HKDF:

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

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

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

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

// 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);
}

نشكر مات سكيل على مقالته التي تتضمّن هذا الرمز النموذجي.

يشمل ذلك بشكل عام 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، وستكون نتيجة عملية التشفير هي الحمولة المشفّرة وقيمة عشوائية ومفتاح عام يُستخدَم فقط لتشفير البيانات.

Salt

يجب أن تكون القيمة العشوائية التي يتم استخدامها عبارة عن 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، متبوعة بالبيانات المشفرة.

"المفتاح العشوائي الزائف" (Pseudo Random Key) هو ببساطة تشغيل المصادقة والمفتاح السري المشترك وجزء من معلومات الترميز من خلال 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) هو المفتاح الذي سيتم استخدامه في النهاية لتشفير الحمولة.

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

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);

يمنحنا ذلك رقم nonce ومفتاح تشفير المحتوى.

تنفيذ التشفير

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

ننشئ شفرة 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.

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

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

يجب أن يكون الملح المكوّن من 16 بايت بترميز base64 URL آمنًا وأن تتم إضافته إلى عنوان التشفير، على النحو التالي:

Encryption: salt=[URL Safe Base64 Encoded Salt]

رأس Crypto-Key

رأينا أنّ العنوان 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 أو مفاتيح خادم التطبيقات (أي كيفية تحديد التطبيق من خلال خدمة الإشعارات الفورية) وتناولنا العناوين المستخدَمة لإرسال حمولة مشفّرة.

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

عنوان TTL

مطلوب

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

TTL: [Time to live in seconds]

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

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

الموضوع

اختياريّ

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

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

حاجة ماسة

اختياريّ

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

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

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

كل شيء معًا

إذا كانت لديك أسئلة أخرى حول كيفية عمل كل هذا، يمكنك دائمًا الاطّلاع على كيفية تشغيل المكتبات لرسائل الإشعارات الفورية على مؤسسة web-push-libs.

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

إذًا، ماذا نفعل بالردّ على طلب POST هذا؟

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

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

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

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

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

Code labs