פרוטוקול Web Push

ראינו איך ספרייה יכולה לשמש להפעלת הודעות דחיפה, אבל מה בדיוק הספריות האלה עושות?

הם שולחים בקשות ברשת, תוך שהם מבטיחים שבקשות כאלה הן בפורמט הנכון. המפרט שמגדיר את בקשת הרשת הזו הוא Web Push Protocol.

תרשים של שליחת הודעת Push מהשרת שלכם לשירות Push

בקטע הזה מוסבר איך השרת יכול להזדהות באמצעות מפתחות שרת אפליקציות, ואיך השליחה של המטען הייעודי (payload) המוצפן והנתונים המשויכים אליו.

זה לא הצד היפה של הדחיפה באינטרנט, ואין לי מומחיות בהצפנה, אבל בואו נבחן כל שלב איך הספריות האלה עובדות מאחורי הקלעים.

מפתחות של שרת אפליקציות

כשאנחנו נרשמים למינוי של משתמש, אנחנו מעבירים applicationServerKey. המפתח הזה מועבר לשירות ה-Push ומשמש לבדיקה אם האפליקציה שרשם את המשתמש היא גם האפליקציה שמפעילה הודעות Push.

כשאנחנו מפעילים הודעת Push, אנחנו שולחים קבוצה של כותרות שמאפשרות לשירות ה-Push לאמת את האפליקציה. (הוא מוגדר על ידי מפרט VAPID).

מה המשמעות של כל זה ומה בדיוק קורה? אלה השלבים הנדרשים לאימות שרת האפליקציות:

  1. שרת האפליקציות חותם על חלק מנתוני ה-JSON באמצעות מפתח האפליקציה הפרטי שלו.
  2. המידע החתום הזה נשלח לשירות Push ככותרת בבקשת POST.
  3. שירות ה-Push משתמש במפתח הציבורי המאוחסן שהוא קיבל מ-pushManager.subscribe() כדי לבדוק שהמידע שהתקבל חתום על ידי המפתח הפרטי שקשור למפתח הציבורי. חשוב לזכור: המפתח הציבורי הוא applicationServerKey שהועבר לשיחת ההרשמה.
  4. אם המידע החתום חוקי, שירות ה-Push שולח את ההודעה למשתמש.

לפניכם דוגמה לזרימת המידע הזו. (שימו לב למקרא בפינה הימנית התחתונה כדי לציין מפתחות ציבוריים ופרטיים).

איור של אופן השימוש במפתח השרת הפרטי של האפליקציה כששולחים הודעה

'המידע החתום' שנוסף לכותרת בבקשה הוא אסימון אינטרנט מסוג JSON.

אסימון אינטרנט מסוג JSON

אסימון אינטרנט מסוג JSON (או בקיצור JWT) מאפשר לשלוח הודעה לצד שלישי, כך שהנמען יוכל לאמת מי שלח אותה.

כשצדדים שלישיים מקבלים הודעה, הם צריכים לקבל את המפתח הציבורי של השולחים ולהשתמש בו כדי לאמת את החתימה של ה-JWT. אם החתימה חוקית, ה-JWT צריך להיות נחתם באמצעות המפתח הפרטי התואם, כך שהחתימה צריכה להגיע מהשולח המצופה.

יש מספר ספריות ב-https://jwt.io/ שיכולות לבצע את החתימה בשבילך, והייתי ממליץ לך לעשות זאת אם זה אפשרי. למען השלמות, נראה איך ליצור באופן ידני JWT חתום.

אסימוני JWT חתומים באינטרנט

JWT חתום הוא פשוט מחרוזת, אבל אפשר לחשוב עליו כשלוש מחרוזות שמחוברות בנקודות.

איור של המחרוזות באסימון אינטרנט מסוג JSON

המחרוזת הראשונה והשנייה (פרטי ה-JWT ונתוני ה-JWT) הן קטעי JSON בקידוד base64, כלומר הם ניתנים לקריאה באופן ציבורי.

המחרוזת הראשונה כוללת מידע על ה-JWT עצמו, שמציין באיזה אלגוריתם נעשה שימוש כדי ליצור את החתימה.

פרטי JWT לדחיפה של אינטרנט חייבים להכיל את המידע הבא:

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

המחרוזת השנייה היא נתוני ה-JWT. כך אפשר לקבל מידע על השולח של ה-JWT, למי מיועד הבקשה ולמשך כמה זמן הוא תקף.

ל-WebPush, הנתונים יהיו בפורמט הבא:

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

הערך של aud הוא ה "קהל", כלומר למי מיועד ה-JWT. בדחיפת אינטרנט, הקהל הוא שירות ה-Push, ולכן הגדרנו אותו למקור של שירות ה-Push.

הערך exp הוא תאריך התפוגה של ה-JWT, וכך מונעים מחטטנים להשתמש שוב ב-JWT אם הם מיירטים אותו. התפוגה היא חותמת זמן בשניות, ולא יכולה להיות יותר מ-24 שעות.

ב-Node.js, התפוגה מוגדרת באמצעות:

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

כדאי להשתמש ב-12 שעות במקום ב-24 שעות כדי למנוע בעיות בשעון בין האפליקציה השולחת לבין שירות ה-Push.

לבסוף, הערך 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 Protocol מצוין שמפתח שרת האפליקציות הציבורי חייב להישלח בכותרת Crypto-Key כמחרוזת בקידוד base64 בטוחה לכתובת URL שמכילה את הערך p256ecdsa=.

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

הצפנת המטען הייעודי (payload)

עכשיו נראה איך אפשר לשלוח מטען ייעודי (payload) באמצעות הודעת דחיפה, כדי שאפליקציית האינטרנט שלנו תקבל הודעת דחיפה, כדי שהיא תוכל לגשת לנתונים שהיא מקבלת.

שאלה נפוצה שמוצגת בקרב משתמשים שהשתמשו בשירותי דחיפה אחרים היא למה צריך להצפין את המטען הייעודי (payload) באינטרנט? באמצעות אפליקציות מקוריות, הודעות בדחיפה יכולות לשלוח נתונים כטקסט פשוט.

אחד היתרונות של דחיפה באינטרנט הוא שמכיוון שכל שירותי הדחיפה משתמשים באותו API (פרוטוקול דחיפת אינטרנט), למפתחים לא צריך לדעת מיהו שירות ה-push. אנחנו יכולים לשלוח בקשה בפורמט הנכון וצפוי שתישלח הודעת Push. החיסרון של זה הוא שמפתחים יכולים לשלוח הודעות לשירות דחיפה לא מהימן. שירות Push לא יכול לקרוא את הנתונים שנשלחים באמצעות הצפנת המטען הייעודי (payload). רק הדפדפן יכול לפענח את המידע. כך ניתן להגן על הנתונים של המשתמש.

ההצפנה של המטען הייעודי (payload) מוגדרת במפרט של הצפנת הודעות.

לפני שנבחן את השלבים הספציפיים להצפנת מטען ייעודי (payload) של הודעות בדחיפה, כדאי לדבר על כמה שיטות שישמשו בתהליך ההצפנה. (טיפ לכובע מסיבי ל'סולמות מאט' לגבי המאמר המצוין שלו על הצפנת Push).

ECDH ו-HKDF

גם ECDH וגם HKDF משמשים לתהליך ההצפנה, ומספקים יתרונות להצפנת מידע.

ECDH: חילופי מפתחות Eliptic Curve Diffie-Hellman

נניח שיש לך שני אנשים שרוצים לשתף מידע, עינת וירון. לאליס ולבוב יש מפתחות ציבוריים ופרטיים משלהם. עינת וירון חולקים את המפתחות הציבוריים שלהם זה עם זה.

המאפיין השימושי של מפתחות שנוצרו באמצעות ECDH הוא שאליס יכולה להשתמש במפתח הפרטי שלה ובמפתח הציבורי של יוסי כדי ליצור את הערך הסודי 'X'. גם בוב יכול לעשות את אותו הדבר על ידי שימוש במפתח הפרטי שלו ובמפתח הציבורי של עינת כדי ליצור באופן עצמאי את אותו ערך 'X'. כך 'X' הוא סוד משותף, ואליס וירון נאלצו רק לשתף את המפתח הציבורי שלהם. עכשיו בוב ואליס יכולים להשתמש בסימן X כדי להצפין ולפענח הודעות ביניהם.

למיטב ידיעתי, 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);
}

במאמר של Maat Scale תוכלו למצוא טיפ לגבי הקוד לדוגמה הזה.

המכסה הזו מכסה באופן רופף את ECDH ואת HKDF.

בעזרת ECDH אפשר לשתף מפתחות ציבוריים וליצור סוד משותף בצורה מאובטחת. ב-HKDF אפשר לקחת חומר לא מאובטח ולהפוך אותו למאובטח.

נשתמש בזה במהלך ההצפנה של המטען הייעודי (payload). עכשיו נראה מה אנחנו לוקחים כקלט ואיך הוא מוצפן.

קלט

כשאנחנו רוצים לשלוח הודעת דחיפה למשתמש עם מטען ייעודי (payload), אנחנו צריכים להזין שלושה קלט:

  1. המטען הייעודי (payload) עצמו.
  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 נחוצים כקלט, והתוצאה של תהליך ההצפנה תהיה המטען הייעודי (payload) המוצפן, ערך salt ומפתח ציבורי שמשמש רק להצפנת הנתונים.

מלח

ערך ה-salt צריך להיות 16 בייטים של נתונים אקראיים. ב-NodeJS, נבצע את הפעולות הבאות כדי ליצור salt:

const salt = crypto.randomBytes(16);

מפתחות ציבוריים / פרטיים

את המפתחות הציבוריים והפרטיים יש ליצור באמצעות עקומה אליפטית P-256, שאותה נבצע ב-Node באופן הבא:

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

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

אנחנו נתייחס למפתחות האלה בתור 'מפתחות מקומיים'. הם משמשים רק להצפנה, ואין להם שום דבר לעשות עם מפתחות של שרת אפליקציות.

באמצעות המטען הייעודי (payload), סוד האימות ומפתח המינוי הציבורי כערכי קלט, ועם salt וקבוצה חדשה של מפתחות מקומיים, אנחנו מוכנים לבצע הצפנה בפועל.

סוד משותף

השלב הראשון הוא ליצור סוד משותף באמצעות המפתח הציבורי של המינוי והמפתח הפרטי החדש שלנו (זוכרים את ההסבר על ECDH עם עינת וירון? בדיוק ככה).

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

בשלב הבא נשתמש בערך הזה כדי לחשב את המַפְתח Pseudo אקראי (PRK).

מפתח פסאודו אקראי

המפתח פסאודו אקראי (PRK) הוא השילוב של סוד האימות של מינוי ה-Push והסוד המשותף שנוצר עכשיו.

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 ואחריו הנתונים המוצפנים.

המפתח הפסאודו אקראי שלנו מפעיל את האימות, הסוד המשותף וקטע מידע של הקידוד דרך 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,
]);

מאגר ההקשר הסופי הוא תווית, מספר הבייטים במפתח הציבורי של המינוי, ואחריו המפתח עצמו, ולבסוף מספר הבייטים של המפתח הציבורי המקומי, ואחריו המפתח עצמו.

בעזרת ערך ההקשר הזה אפשר להשתמש בו ביצירה של צופן חד-פעמי (nonce) ומפתח להצפנת תוכן (CEK).

מפתח להצפנת תוכן וצופן חד-פעמי (nonce)

nonce הוא ערך שמונע התקפות חוזרות, כי צריך להשתמש בו רק פעם אחת.

מפתח הצפנת התוכן (CEK) הוא המפתח שבסופו של דבר ישמש להצפנת המטען הייעודי (payload) שלנו.

קודם כל, אנחנו צריכים ליצור את הבייטים של הנתונים לצופן חד-פעמי (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, ומשלבת את ה-salt וה-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) ושל תוכן.

ביצוע ההצפנה

עכשיו, כשיש לנו את המפתח להצפנת התוכן, אנחנו יכולים להצפין את המטען הייעודי (payload).

אנחנו יוצרים צופן AES128 תוך שימוש במפתח להצפנת תוכן בתור מפתח, והצופן החד-פעמי הוא וקטור אתחול.

ב-Node, הדבר נעשה כך:

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

לפני שאנחנו מצפינים את המטען הייעודי (payload), אנחנו צריכים להגדיר את מידת המרווח הפנימי שאנחנו רוצים להוסיף לחלק הקדמי של המטען הייעודי (payload). הסיבה לכך שאנחנו רוצים להוסיף מרווח פנימי היא שהיא מונעת את הסיכון שמצותתים יוכלו לקבוע 'סוגים' של הודעות על סמך גודל המטען הייעודי (payload).

צריך להוסיף שני בייטים של מרווח פנימי כדי לציין את האורך של כל מרווח פנימי נוסף.

לדוגמה, אם לא הוספתם מרווח פנימי, יהיו לכם שני בייטים עם הערך 0. כלומר לא קיים מרווח פנימי, אחרי שני הבייטים האלה תקראו את המטען הייעודי (payload). אם הוספתם מרווח פנימי של 5 בייטים, שני הבייטים הראשונים יקבלו את הערך 5, כך שהצרכן יקרא חמישה בייטים נוספים ואז יתחיל לקרוא את המטען הייעודי (payload).

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

לאחר מכן אנחנו מעבירים את המרווח הפנימי ואת המטען הייעודי (payload) דרך הצופן הזה.

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

יש לנו עכשיו את המטען המוצפן שלנו. יש!

כל מה שנשאר זה לקבוע איך המטען הייעודי (payload) הזה נשלח לשירות ה-Push.

כותרות וגוף מטען ייעודי (payload) מוצפנים

כדי לשלוח את המטען המוצפן הזה לשירות ה-Push, אנחנו צריכים להגדיר כמה כותרות שונות בבקשת ה-POST.

כותרת ההצפנה

הכותרת 'הצפנה' חייבת להכיל את ה-salt שמשמש להצפנת המטען הייעודי (payload).

נתוני ה-salt של 16 בייטים צריכים להיות בקידוד בטוח של כתובות URL מסוג base64 ולהוסיף אותם לכותרת ההצפנה, באופן הבא:

Encryption: salt=[URL Safe Base64 Encoded Salt]

כותרת של מפתח הצפנה

ראינו שהכותרת Crypto-Key נמצאת בשימוש בקטע 'מפתחות שרתי אפליקציות' כדי להכיל את המפתח הציבורי של שרת האפליקציות.

הכותרת הזו משמשת גם לשיתוף המפתח הציבורי המקומי שמשמש להצפנת המטען הייעודי (payload).

הכותרת שמתקבלת נראית כך:

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

סוג התוכן, אורך וכותרות קידוד

הכותרת Content-Length היא מספר הבייטים במטען הייעודי (payload) המוצפן. הכותרות Content-Type ו-'Content-Encoding' הן ערכים קבועים. השם מוצג בהמשך.

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

כשהכותרות האלה מוגדרות, אנחנו צריכים לשלוח את המטען הייעודי (payload) המוצפן בתור גוף הבקשה שלנו. שימו לב שהשדה Content-Type מוגדר לערך application/octet-stream. הסיבה לכך היא שהמטען הייעודי (payload) המוצפן חייב להישלח כזרם של בייטים.

ב-NodeJS נבצע את הפעולות הבאות:

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

כותרות נוספות?

כיסינו את הכותרות המשמשות למפתחות JWT ומפתחות של שרתי אפליקציות (כלומר איך לזהות את האפליקציה באמצעות שירות ה-Push), והסברנו את הכותרות שמשמשות לשליחת מטען מוצפן.

יש כותרות נוספות שמשמשות את השירותים כדי לשנות את ההתנהגות של ההודעות שנשלחו. חלק מהכותרות הן חובה, ואחרות הן אופציונליות.

כותרת TTL

חובה

TTL (או time to Live) הוא מספר שלם שמציין את מספר השניות שאתם רוצים שההודעה תוצג בשירות ה-Push לפני שהיא נמסרת. כשיפוג התוקף של TTL, ההודעה תוסר מהתור של שירות ה-push ולא תימסר.

TTL: [Time to live in seconds]

אם מגדירים TTL של אפס, שירות ה-Push ינסה להעביר את ההודעה באופן מיידי, אבל אם לא ניתן יהיה לגשת למכשיר, ההודעה תוסר מיד מהתור של שירות ה-Push.

מבחינה טכנית, שירות דחיפה יכול להפחית את ה-TTL של הודעת Push אם הוא רוצה. כדי לדעת אם זה קרה, אפשר לבדוק את הכותרת TTL בתגובה של שירות Push.

נושא

אופציונלי

נושאים הם מחרוזות שניתן להשתמש בהן כדי להחליף הודעות בהמתנה בהודעה חדשה, אם יש להן שמות נושאים תואמים.

זו אפשרות שימושית בתרחישים שבהם נשלחות כמה הודעות בזמן שהמכשיר לא מחובר לאינטרנט, ואתם רוצים שהמשתמש יראה רק את ההודעה האחרונה כשהמכשיר פועל.

דחיפות

אופציונלי

הדחיפות מציינת לשירות ה-push עד כמה הודעה חשובה למשתמש. שירות ה-Push יכול להשתמש באפשרות הזו כדי לשמר את חיי הסוללה של מכשיר המשתמש, בכך שהוא מתעורר רק כשיש הודעות חשובות כשהסוללה חלשה.

ערך הכותרת מוגדר כפי שמוצג בהמשך. ערך ברירת המחדל הוא normal.

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

הכול ביחד

אם יש לכם שאלות נוספות לגבי אופן הפעולה של כל זה, תמיד תוכלו לראות כיצד ספריות מפעילות הודעות Push בארגון web-push-libs.

ברגע שיש מטען ייעודי (payload) מוצפן, יחד עם הכותרות שלמעלה, צריך רק לשלוח בקשת POST ל-endpoint ב-PushSubscription.

אז מה אנחנו עושים עם התגובה לבקשת ה-POST הזו?

תשובה משירות Push

אחרי ששלחתם בקשה לשירות Push, תצטרכו לבדוק את קוד הסטטוס של התשובה כדי לדעת אם הבקשה הצליחה או לא.

קוד סטטוס תיאור
201 נוצר. הבקשה לשליחת הודעת דחיפה התקבלה ואושרה.
429 יותר מדי בקשות. משמעות הדבר היא ששרת האפליקציות הגיע למגבלת הקצב של יצירת הבקשות באמצעות שירות דחיפה. שירות ה-Push צריך לכלול את הכותרת 'Retry-After' כדי לציין את משך הזמן לפני שאפשר יהיה לבצע בקשה נוספת.
400 בקשה לא חוקית. בדרך כלל פירוש הדבר הוא שאחת מהכותרות לא תקינה או בפורמט שגוי.
404 לא נמצא זהו סימן לכך שהמינוי כבר לא בתוקף ולא ניתן להשתמש בו. במקרה כזה, צריך למחוק את 'PushSubscription' ולהמתין שהלקוח ירשום את המשתמש מחדש.
410 נעלם. המינוי כבר לא בתוקף וצריך להסיר אותו משרת האפליקציות. אפשר לשחזר את הערך הזה באמצעות קריאה ל- 'unsubscribe() ' ב-'PushSubscription'.
413 המטען הייעודי גדול מדי. המטען הייעודי (payload) המינימלי ששירות Push צריך לתמוך בו הוא 4,096 בייטים (או 4kb).

השלבים הבאים

שיעורי Lab