פרוטוקול Web Push

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

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

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

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

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

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

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

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

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

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

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

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

הקטע "פרטים חתומים" שנוסף לכותרת בבקשה הוא אסימון אינטרנט מסוג 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, היא מיועדת ולכמה זמן היא תקפה.

בשביל דחיפת דף אינטרנט, הנתונים יהיו בפורמט הבא:

{
  "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 מקודדים בתור בסיס בטוח לכתובת URL64 String.

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

תהליך החתימה מחייב הצפנה של 'האסימון הלא חתום' באמצעות ES256. לפי JWT המפרט, ES256 הוא קיצור של 'ECDSA באמצעות עקומת P-256' את אלגוריתם הגיבוב (hash) 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);
});

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

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

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

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

ECDH ו-HKDF

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

ECDH: חילופי מפתחות Elliptic 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 שמשנה כל מפתח חלש לחומר מפתח קריפטוגרפי חזק. אפשר להשתמש בו למשל, כדי להמיר דיפי הלמן החליפה סודות משותפים לחומר מפתח מתאים לשימוש בהצפנה, בבדיקת תקינות או באימות.

בעיקרון, 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 הוא דרך חומר לא מאובטח ולהפוך אותו למאובטח.

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

שלושת הערכים האלה, 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 Random Key (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). הסיבה שבגללה אנחנו רוצים להוסיף מרווח פנימי היא מונעת את הסיכון שמציתים יוכלו לזהות 'types' של הודעות בהתאם לגודל המטען הייעודי (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) המוצפן שלנו. יש!

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

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

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

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

כותרת TTL

חובה

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

TTL: [Time to live in seconds]

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

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

נושא

אופציונלי

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

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

דחיפות

אופציונלי

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

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

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

הכול ביחד

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

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

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

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

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

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

מה השלב הבא?

שיעורי Lab קוד