פרוטוקול Web Push

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

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

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

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

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

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

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

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

הערך של 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 ללא סימני פסיק (safe URL).

המחרוזת השלישית, החתימה, היא התוצאה של הצירוף של שתי המחרוזות הראשונות (פרטי ה-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);
});

שירות דחיפה יכול לאמת 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]

הצפנת מטען הנתונים

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

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

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

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

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

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

סוד משותף

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

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

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

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

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

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

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

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

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

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

כותרת ההצפנה

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

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

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

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 הזו?

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

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

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

אפשר גם לקרוא את תקן ה-Web Push (RFC8030) כדי לקבל מידע נוסף על קודי המצב של HTTP.

מה השלב הבא?

Codelabs