השלב הראשון הוא לקבל מהמשתמש הרשאה לשלוח לו הודעות דחיפה, ואז נוכל לקבל PushSubscription
.
ה-JavaScript API שעוזר לבצע את הפעולה הזו הוא פשוט למדי, אז נעבור על תהליך הלוגיקה.
זיהוי תכונות
קודם כול צריך לבדוק אם הדפדפן הנוכחי תומך באמת בהודעות דחיפה. אפשר לבדוק אם יש תמיכה ב-push באמצעות שתי בדיקות פשוטות.
- בודקים אם יש serviceWorker ב-navigator.
- מחפשים את PushManager בחלון.
if (!('serviceWorker' in navigator)) {
// Service Worker isn't supported on this browser, disable or hide UI.
return;
}
if (!('PushManager' in window)) {
// Push isn't supported on this browser, disable or hide UI.
return;
}
התמיכה בדפדפנים בשירותי worker ובהודעות push הולכת וגדלה במהירות, אבל תמיד כדאי לזהות את התכונות בשתיהן ולשפר אותן בהדרגה.
רישום של קובץ שירות (service worker)
בעזרת זיהוי התכונות אנחנו יודעים שיש תמיכה גם ב-Service Workers וגם ב-Push. השלב הבא הוא 'רישום' של ה-service worker.
כשאנחנו רושמים שירות עבודה, אנחנו אומרים לדפדפן איפה נמצא קובץ השירות העובד. הקובץ עדיין הוא רק JavaScript, אבל הדפדפן 'ייתן לו גישה' לממשקי ה-API של שירות העבודה, כולל Push. ליתר דיוק, הדפדפן מפעיל את הקובץ בסביבת service worker.
כדי לרשום קובץ שירות, קוראים ל-navigator.serviceWorker.register()
ומעבירים את הנתיב לקובץ. למשל:
function registerServiceWorker() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
console.log('Service worker successfully registered.');
return registration;
})
.catch(function (err) {
console.error('Unable to register service worker.', err);
});
}
הפונקציה הזו מעדכנת את הדפדפן שיש לנו קובץ service worker ואיפה הוא נמצא. במקרה הזה, קובץ ה-Service Worker נמצא בכתובת /service-worker.js
. מאחורי הקלעים, הדפדפן יבצע את השלבים הבאים אחרי הקריאה ל-register()
:
מורידים את קובץ ה-service worker.
מריצים את ה-JavaScript.
אם הכל פועל כמו שצריך ואין שגיאות, ההבטחה שמוחזרת על ידי
register()
תבוטל. אם יהיו שגיאות מסוג כלשהו, ההבטחה תידחה.
אם
register()
נדחה, צריך לבדוק את ה-JavaScript כדי לאתר שגיאות הקלדה או שגיאות אחרות באמצעות כלי הפיתוח ל-Chrome.
כשהבעיה ב-register()
נפתרת, היא מחזירה ServiceWorkerRegistration
. אנחנו נשתמש ברישום הזה כדי לגשת ל-PushManager API.
תאימות דפדפנים של PushManager API
שליחת בקשה לקבלת הרשאה
רשמנו את ה-service worker שלנו ועכשיו אנחנו מוכנים להירשם את המשתמש. השלב הבא הוא לקבל מהמשתמש הרשאה לשלוח לו הודעות דחיפה.
ה-API לקבלת הרשאה הוא פשוט יחסית, אבל החיסרון הוא שלאחרונה ה-API השתנה מקבלת קריאה חוזרת להחזרת Promise. הבעיה היא שאנחנו לא יכולים לדעת איזו גרסה של ה-API מוטמעת בדפדפן הנוכחי, ולכן צריך להטמיע את שתי הגרסאות ולטפל בשתיהן.
function askPermission() {
return new Promise(function (resolve, reject) {
const permissionResult = Notification.requestPermission(function (result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
}).then(function (permissionResult) {
if (permissionResult !== 'granted') {
throw new Error("We weren't granted permission.");
}
});
}
בקוד שלמעלה, קטע הקוד החשוב הוא הקריאה ל-Notification.requestPermission()
. השיטה הזו תציג למשתמש הודעה:
אחרי שהמשתמש ילחץ על 'אישור', 'חסימה' או פשוט ייסגור את הבקשה, התוצאה תתקבל כמחרוזת: 'granted'
, 'default'
או 'denied'
.
בקוד לדוגמה שלמעלה, ההתחייבות שמוחזרת על ידי askPermission()
מתקבלת אם ההרשאה ניתנת, אחרת אנחנו זורקים שגיאה שגורמת לדחייה של ההתחייבות.
תרחיש קיצוני אחד שצריך לטפל בו הוא אם המשתמש לוחץ על הלחצן 'חסימה'. במקרה כזה, אפליקציית האינטרנט לא תוכל לבקש מהמשתמש הרשאה שוב. הם יצטרכו לבטל את החסימה של האפליקציה באופן ידני על ידי שינוי מצב ההרשאה שלה, שמוסתר בחלונית ההגדרות. חשוב לחשוב היטב איך ומתי מבקשים מהמשתמש הרשאה, כי אם הוא לוחץ על 'חסימה', קשה מאוד לבטל את ההחלטה הזו.
החדשות הטובות הן שרוב המשתמשים מוכנים לתת הרשאה, כל עוד הם יודעים למה מבקשים את ההרשאה.
בהמשך נראה איך אתרים פופולריים מסוימים מבקשים הרשאה.
הרשמה של משתמש באמצעות PushManager
אחרי שנרשם את ה-service worker ונקבל הרשאה, נוכל להירשם משתמש על ידי קריאה ל-registration.pushManager.subscribe()
.
function subscribeUserToPush() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
),
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function (pushSubscription) {
console.log(
'Received PushSubscription: ',
JSON.stringify(pushSubscription),
);
return pushSubscription;
});
}
כשקוראים לשיטה subscribe()
, מעבירים אובייקט options שמכיל פרמטרים חובה וגם פרמטרים אופציונליים.
נבחן את כל האפשרויות שאפשר להעביר.
אפשרויות userVisibleOnly
כשהתכונת Push נוספה לדפדפנים בפעם הראשונה, לא היה ברור אם המפתחים צריכים להיות מסוגלים לשלוח הודעת Push בלי להציג התראה. התכונה הזו נקראת בדרך כלל 'דחיפה שקטה', כי המשתמש לא יודע שמשהו קרה ברקע.
החשש היה שמפתחים יוכלו לעשות דברים לא טובים, כמו לעקוב אחרי המיקום של משתמש באופן שוטף בלי שהמשתמש ידע על כך.
כדי למנוע את התרחיש הזה ולתת לכותבי המפרט זמן לחשוב איך הכי טוב לתמוך בתכונה הזו, הוספנו את האפשרות userVisibleOnly
. העברת הערך true
היא הסכם סמלי עם הדפדפן, לפיו אפליקציית האינטרנט תציג התראה בכל פעם שתתקבל התראה ב-push (כלומר, לא תהיה התראה שקטה).
בשלב הזה חובה להעביר ערך של true
. אם לא תכללו את המפתח userVisibleOnly
או תעבירו את false
, תופיע השגיאה הבאה:
נכון לעכשיו, ב-Chrome יש תמיכה ב-Push API רק עבור מינויים שיגרמו להצגת הודעות למשתמשים. כדי לציין זאת, צריך להפעיל את הפונקציה pushManager.subscribe({userVisibleOnly: true})
במקום זאת. פרטים נוספים זמינים בכתובת https://goo.gl/yqv4Q4.
נראה כרגע שאי אפשר יהיה להטמיע ב-Chrome שליחת התראות שקופצות ללא הודעה מראש. במקום זאת, מחברי המפרט בודקים את הרעיון של Budget API, שיאפשר לאפליקציות אינטרנט לשלוח מספר מסוים של הודעות push שקשות על סמך השימוש באפליקציית האינטרנט.
האפשרות applicationServerKey
הזכרנו בקצרה את 'מפתחות של שרת אפליקציה' בקטע הקודם. שירותי דחיפה משתמשים ב'מפתחות שרת של אפליקציות' כדי לזהות את האפליקציה שהרשימה משתמש, וכדי לוודא שאותה אפליקציה שולחת הודעות למשתמש הזה.
מפתחות של שרת אפליקציה הם זוג מפתחות ציבורי ופרטי ייחודיים לאפליקציה. המפתח הפרטי צריך להישאר סוד באפליקציה, ואת המפתח הציבורי אפשר לשתף באופן חופשי.
האפשרות applicationServerKey
שמועברת לקריאה subscribe()
היא המפתח הציבורי של האפליקציה. הדפדפן מעביר את הפרטים האלה לשירות ה-push כשמצרפים את המשתמש, כך ששירות ה-push יכול לקשר את המפתח הציבורי של האפליקציה ל-PushSubscription
של המשתמש.
התרשים הבא מדגים את השלבים האלה.
- אפליקציית האינטרנט נטענת בדפדפן, ומפעילים את
subscribe()
ומעבירים את המפתח הציבורי של שרת האפליקציה. - לאחר מכן הדפדפן שולח בקשת רשת לשירות דחיפה שיוצר נקודת קצה, משייך את נקודת הקצה הזו למפתח הציבורי של האפליקציה ומחזיר את נקודת הקצה לדפדפן.
- הדפדפן יוסיף את נקודת הקצה הזו ל-
PushSubscription
, שמוחזר דרך ההבטחהsubscribe()
.
כשתרצו לשלוח בהמשך הודעת דחיפה, תצטרכו ליצור כותרת Authorization שתכלול מידע חתום באמצעות המפתח הפרטי של שרת האפליקציות. כששירות ה-push מקבל בקשה לשליחת הודעת push, הוא יכול לאמת את הכותרת החתומה Authorization על ידי חיפוש המפתח הציבורי שמקושר לנקודת הקצה שמקבלת את הבקשה. אם החתימה תקינה, שירות ה-push יודע שהיא חייבת להגיע משרת האפליקציה עם המפתח הפרטי התואם. בעיקרון, זהו אמצעי אבטחה שמונעת מאנשים אחרים לשלוח הודעות למשתמשים באפליקציה.
מבחינה טכנית, הערך applicationServerKey
הוא אופציונלי. עם זאת, ההטמעה הקלה ביותר ב-Chrome דורשת זאת, ויכול להיות שגם בדפדפנים אחרים יהיה צורך בכך בעתיד. ב-Firefox, הפעולה הזו אופציונלית.
המפרט שמגדיר מה צריך להיות מפתח שרת האפליקציה הוא מפרט VAPID. בכל פעם שקוראים משהו שמתייחס ל'מפתחות שרת אפליקציה' או ל'מפתחות VAPID', חשוב לזכור שמדובר באותו הדבר.
איך יוצרים מפתחות של שרת אפליקציות
אפשר ליצור קבוצה ציבורית ופרטית של מפתחות של שרת אפליקציות בכתובת web-push-codelab.glitch.me, או להשתמש בשורת הפקודה של web-push כדי ליצור מפתחות באופן הבא:
$ npm install -g web-push
$ web-push generate-vapid-keys
צריך ליצור את המפתחות האלה לאפליקציה רק פעם אחת, אבל חשוב לוודא שהמפתח הפרטי יישאר פרטי. (כן, זה מה שאמרתי.)
הרשאות והפונקציה subscribe()
יש תופעת לוואי אחת של קריאה ל-subscribe()
. אם לאפליקציית האינטרנט אין הרשאות להצגת התראות בזמן הקריאה ל-subscribe()
, הדפדפן יבקש את ההרשאות בשבילכם. האפשרות הזו שימושית אם ממשק המשתמש שלכם פועל בתהליך הזה, אבל אם אתם רוצים יותר שליטה (ואנחנו מניחים שרוב המפתחים ירצו), כדאי להשתמש ב-API של Notification.requestPermission()
שבו השתמשנו מקודם.
מהו PushSubscription?
אנחנו קוראים ל-subscribe()
, מעבירים כמה אפשרויות ובתמורה מקבלים הבטחה שמתקבלת ל-PushSubscription
, וכתוצאה מכך מתקבל קוד כזה:
function subscribeUserToPush() {
return navigator.serviceWorker
.register('/service-worker.js')
.then(function (registration) {
const subscribeOptions = {
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
),
};
return registration.pushManager.subscribe(subscribeOptions);
})
.then(function (pushSubscription) {
console.log(
'Received PushSubscription: ',
JSON.stringify(pushSubscription),
);
return pushSubscription;
});
}
האובייקט PushSubscription
מכיל את כל המידע הנדרש לשליחת הודעות push למשתמש הזה. אם תדפיסו את התוכן באמצעות JSON.stringify()
, תוצג ההודעה הבאה:
{
"endpoint": "https://some.pushservice.com/something-unique",
"keys": {
"p256dh":
"BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
"auth":"FPssNDTKnInHVndSTdbKFw=="
}
}
endpoint
היא כתובת ה-URL של שירותי ה-push. כדי להפעיל הודעת דחיפה, שולחים בקשת POST לכתובת ה-URL הזו.
האובייקט keys
מכיל את הערכים שמשמשים להצפנת נתוני ההודעות שנשלחות עם הודעת ה-push (נדון בכך בהמשך הקטע).
מינוי מחדש באופן קבוע כדי למנוע תפוגה
כשנרשמים לקבלת התראות, בדרך כלל מקבלים PushSubscription.expirationTime
של null
. באופן תיאורטי, המשמעות היא שתוקף המינוי לא יפוג אף פעם (בניגוד למקרה שבו מקבלים DOMHighResTimeStamp
, שמציין את הנקודה המדויקת בזמן שבה יפוג תוקף המינוי). עם זאת, בפועל, דפדפנים רבים עדיין מאפשרים לסמכויות הרשמה לפוג, למשל אם לא התקבלו התראות דחיפה במשך זמן רב, או אם הדפדפן מזהה שהמשתמש לא משתמש באפליקציה שיש לה הרשאה לשלוח התראות דחיפה. דפוס אחד שיכול למנוע זאת הוא לרשום מחדש את המשתמש בכל פעם שהוא מקבל התראה, כפי שמתואר בקטע הקוד הבא. לשם כך, צריך לשלוח התראות בתדירות גבוהה מספיק כדי שהתוקף של המינוי לא יפוג באופן אוטומטי בדפדפן. חשוב לשקול היטב את היתרונות והחסרונות של הצורך בהתראות לגיטימיות מול הספאם הלא מכוון למשתמש רק כדי שהמינוי לא יפוג. בסופו של דבר, אל תנסו להילחם בדפדפן במאמצים שלו להגן על המשתמש מפני מינויים לקבלת התראות שנשכחו מזמן.
/* In the Service Worker. */
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
// Display notification or handle data
// Example: show a notification
const title = 'New Notification';
const body = 'You have new updates!';
const icon = '/images/icon.png';
const tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
// Attempt to resubscribe after receiving a notification
event.waitUntil(resubscribeToPush());
});
function resubscribeToPush() {
return self.registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.then(function() {
return self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
});
})
.then(function(subscription) {
console.log('Resubscribed to push notifications:', subscription);
// Optionally, send new subscription details to your server
})
.catch(function(error) {
console.error('Failed to resubscribe:', error);
});
}
שליחת מינוי לשרת
אחרי שיוצרים מינוי לקבלת התראות, צריך לשלוח אותו לשרת. אתם יכולים לעשות זאת בדרכים שונות, אבל טיפ קטן הוא להשתמש ב-JSON.stringify()
כדי לקבל את כל הנתונים הנדרשים מאובייקט המינוי. לחלופין, אפשר לקבל את אותה תוצאה באופן ידני באופן הבא:
const subscriptionObject = {
endpoint: pushSubscription.endpoint,
keys: {
p256dh: pushSubscription.getKeys('p256dh'),
auth: pushSubscription.getKeys('auth'),
},
};
// The above is the same output as:
const subscriptionObjectToo = JSON.stringify(pushSubscription);
שולחים את המינוי בדף האינטרנט באופן הבא:
function sendSubscriptionToBackEnd(subscription) {
return fetch('/api/save-subscription/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription),
})
.then(function (response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function (responseData) {
if (!(responseData.data && responseData.data.success)) {
throw new Error('Bad response from server.');
}
});
}
שרת הצומת מקבל את הבקשה הזו ושומר את הנתונים במסד נתונים לשימוש מאוחר יותר.
app.post('/api/save-subscription/', function (req, res) {
if (!isValidSaveRequest(req, res)) {
return;
}
return saveSubscriptionToDatabase(req.body)
.then(function (subscriptionId) {
res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify({data: {success: true}}));
})
.catch(function (err) {
res.status(500);
res.setHeader('Content-Type', 'application/json');
res.send(
JSON.stringify({
error: {
id: 'unable-to-save-subscription',
message:
'The subscription was received but we were unable to save it to our database.',
},
}),
);
});
});
כשפרטי PushSubscription
נמצאים בשרת שלנו, אנחנו יכולים לשלוח הודעה למשתמש מתי שאנחנו רוצים.
מינוי מחדש באופן קבוע כדי למנוע תפוגה
כשנרשמים לקבלת התראות, בדרך כלל מקבלים PushSubscription.expirationTime
של null
. באופן תיאורטי, המשמעות היא שתוקף המינוי לא יפוג אף פעם (בניגוד למקרה שבו מופיעה הודעת DOMHighResTimeStamp
, שמציינת את הנקודה המדויקת בזמן שבה יפוג תוקף המינוי). עם זאת, בפועל, דפדפנים רבים עדיין מאפשרים לתפוגת התוקף של מינויים, למשל אם לא התקבלו התראות דחיפה במשך זמן רב, או אם הדפדפן מזהה שהמשתמש לא משתמש באפליקציה שיש לה הרשאה לשלוח התראות דחיפה. דפוס אחד שיכול למנוע זאת הוא להירשם מחדש את המשתמש בכל פעם שהוא מקבל התראה, כפי שמתואר בקטע הקוד הבא. לשם כך, צריך לשלוח התראות בתדירות גבוהה מספיק כדי שהתוקף של המינוי לא יפוג באופן אוטומטי בדפדפן. חשוב לשקול היטב את היתרונות והחסרונות של הצורך בהתראות לגיטימיות מול הספאם למשתמש רק כדי שהמינוי לא יפוג. בסופו של דבר, אל תנסו להילחם בדפדפן במאמצים שלו להגן על המשתמש מפני מינויים להתראות שנשכחו מזמן.
/* In the Service Worker. */
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
// Display notification or handle data
// Example: show a notification
const title = 'New Notification';
const body = 'You have new updates!';
const icon = '/images/icon.png';
const tag = 'simple-push-demo-notification-tag';
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
);
// Attempt to resubscribe after receiving a notification
event.waitUntil(resubscribeToPush());
});
function resubscribeToPush() {
return self.registration.pushManager.getSubscription()
.then(function(subscription) {
if (subscription) {
return subscription.unsubscribe();
}
})
.then(function() {
return self.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
});
})
.then(function(subscription) {
console.log('Resubscribed to push notifications:', subscription);
// Optionally, send new subscription details to your server
})
.catch(function(error) {
console.error('Failed to resubscribe:', error);
});
}
שאלות נפוצות
ריכזנו כאן כמה שאלות נפוצות שאנשים שאלו בשלב הזה:
האם אפשר לשנות את שירות ה-push שבו משתמש הדפדפן?
לא. שירות ה-push נבחר על ידי הדפדפן, וכפי שראינו בקריאה subscribe()
, הדפדפן ישלח בקשות רשת לשירות ה-push כדי לאחזר את הפרטים שמרכיבים את PushSubscription.
כל דפדפן משתמש בשירות Push שונה, האם אין להם ממשקי API שונים?
כל שירותי ה-push יקבלו את אותו ממשק API.
ממשק ה-API הנפוץ הזה נקרא פרוטוקול Web Push, והוא מתאר את בקשת הרשת שהאפליקציה צריכה לשלוח כדי להפעיל הודעת דחיפה.
אם נרשם משתמש במחשב, האם הוא רשום גם בטלפון?
לצערנו, לא. המשתמשים צריכים להירשם לקבלת התראות בכל דפדפן שבו הם רוצים לקבל הודעות. חשוב גם לציין שהמשתמש יצטרך להעניק הרשאה בכל מכשיר.
לאן ממשיכים
- סקירה כללית בנושא התראות מאפליקציית אינטרנט
- איך פועלת התכונה 'עדכונים'
- הוספת משתמש כמנוי
- חוויית המשתמש של ההרשאות
- שליחת הודעות באמצעות ספריות Web Push
- Web Push Protocol
- טיפול באירועי Push
- הצגת התראה
- התנהגות ההתראות
- דפוסי התראות נפוצים
- שאלות נפוצות בנושא התראות
- בעיות נפוצות ודיווח על באגים