פרוטוקול Web Push

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

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

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

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

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

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

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

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

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

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

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

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

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

‫JSON Web Token

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

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

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

התראות מאתרים ו-JWT חתומים

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

איור של המחרוזות ב-JSON Web Token.

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

בדומה למידע של ה-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 כמחרוזת בקידוד Base64 שמתאימה לכתובות URL, עם הקידומת p256ecdsa=.

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

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

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

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

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

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

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

ECDH ו-HKDF

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

‫ECDH: חילופי מפתחות בפרוטוקול Elliptic Curve Diffie-Hellman

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

המאפיין השימושי של מפתחות שנוצרו באמצעות 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

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

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

כניסות קלט

כשאנחנו רוצים לשלוח הודעת Push למשתמש עם מטען ייעודי (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

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

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

מפתח הצפנת תוכן ו-nonce

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

מפתח הצפנת התוכן (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).

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

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

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

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

כותרת ההצפנה

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

ה-salt בגודל 16 בייטים צריך להיות מקודד ב-Base64 URL safe ולהתווסף לכותרת ההצפנה, כך:

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

ב-NodeJS, עושים את זה כך:

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

עוד כותרות?

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

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

כותרת TTL

חובה

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

TTL: [Time to live in seconds]

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

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

נושא

אופציונלי

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

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

דחיפות

אופציונלי

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

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

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

הכול ביחד

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

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

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

תגובה משירות ההתראות

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

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

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

לאן ממשיכים מכאן

Code Labs