פרוטוקול Web Push

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

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

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

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

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

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

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

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

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

  1. שרת האפליקציה חותם על חלק מפרטי ה-JSON באמצעות מפתח האפליקציה הפרטי שלו.
  2. המידע החתום הזה נשלח לשירות ה-push ככותרת בבקשת POST.
  3. שירות הדחיפה משתמש במפתח הציבורי שנשמר וקיבל מ-pushManager.subscribe() כדי לבדוק שהמידע שהתקבל נחתם על ידי המפתח הפרטי שקשור למפתח הציבורי. חשוב לזכור: המפתח הציבורי הוא הערך של applicationServerKey שמוענק לקריאה subscribe.
  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, על מי הוא מיועד ועל משך התוקף שלו.

ל-Web Push, הנתונים יהיו בפורמט הזה:

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

הערך aud הוא ה'קהל', כלומר למי מיועד ה-JWT. בדחיפת ה-Web Push הוא שירות ה-Push, אז אנחנו מגדירים אותו למקור של שירות ה-Push.

הערך של exp הוא תאריך התפוגה של ה-JWT, והוא מונע מאנשים זדוניים להשתמש שוב ב-JWT אם הם יירטו אותו. חותמת הזמן של התוקף היא ב-‎seconds, והיא לא יכולה להיות ארוכה מ-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 החתום (כלומר כל שלוש המחרוזות שמחוברות בנקודות) נשלח לשירות ה-push באינטרנט ככותרת 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) עם הודעת דחיפה, כך שכשאפליקציית האינטרנט שלנו תקבל הודעה, היא תוכל לגשת לנתונים שהיא מקבלת.

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

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

ההצפנה של עומס העבודה מוגדרת במפרט של הצפנת ההודעות.

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

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

מלח

ערך ה-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();

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

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

סוד משותף

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

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

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

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

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

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

ב-Node עושים זאת כך:

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

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

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

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

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

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.

כותרת הצפנה

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

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

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

כותרת TTL

חובה

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

TTL: [Time to live in seconds]

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

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

נושא

אופציונלי

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

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

דחיפות

אופציונלי

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

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

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

הכול ביחד

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

אחרי שתקבלו את עומס העבודה המוצפן ואת הכותרות שלמעלה, פשוט צריך לשלוח בקשת POST אל endpoint ב-PushSubscription.

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

תגובה משירות הדחיפה

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

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

מידע נוסף על קודי הסטטוס של HTTP זמין גם בתקן Web Push‏ (RFC8030).

מה השלב הבא?

שיעורי Lab קוד