פרוטוקול Web Push

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

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

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

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

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

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

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

הצפנת עומס העבודה

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

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

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

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

קלט

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

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

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

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

סוד משותף

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

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

ביצוע ההצפנה

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

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

ב-Node, זה נעשה כך:

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

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

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

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

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

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

כותרות וגוף של מטען נתונים מוצפן

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

כותרת הצפנה

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

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

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

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

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

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

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

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

לאן ממשיכים

Code labs