רישום משתמש

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

ה-JavaScript API שעוזר לבצע את הפעולה הזו הוא פשוט למדי, אז נעבור על תהליך הלוגיקה.

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

  1. מחפשים את האפשרות serviceWorker ב-navigator.
  2. מחפשים את 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;
}

למרות שהתמיכה בדפדפנים מתפתחת במהירות, גם ב-Service Worker וגם ב-push Messages, תמיד כדאי להשתמש בתכונות לזיהוי שניהם יחד ולשיפור הדרגתי.

רישום Service Worker

בעזרת התכונה הזו אנחנו יודעים שנתמכים גם ב-Service Workers וגם ב-Push. השלב הבא הוא 'רישום' של ה-service worker.

כשאנחנו רושמים קובץ שירות (service worker), אנחנו מודיעים לדפדפן איפה נמצא קובץ ה-Service Worker. הקובץ עדיין הוא JavaScript בלבד, אבל הדפדפן "יעניק לו גישה" לממשקי ה-API של Service Worker, כולל Push. ליתר דיוק, הדפדפן מפעיל את הקובץ בסביבת service worker.

כדי לרשום קובץ שירות (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():

  1. מורידים את קובץ ה-service worker.

  2. מריצים את ה-JavaScript.

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

אם register() נדחה, צריך לבדוק שוב אם יש ב-JavaScript שגיאות הקלדה או שגיאות בכלי הפיתוח ל-Chrome.

כשהבעיה ב-register() נפתרת, היא מחזירה ServiceWorkerRegistration. אנחנו נשתמש ברישום הזה כדי לגשת ל-PushManager API.

תאימות דפדפנים של PushManager API

תמיכה בדפדפן

  • Chrome: 42.
  • Edge:‏ 17.
  • Firefox: 44.
  • Safari: 16.

מקור

נשלחה בקשת הרשאה

רשמנו את ה-service worker שלנו ועכשיו אנחנו מוכנים להירשם את המשתמש. השלב הבא הוא לקבל מהמשתמש הרשאה לשלוח לו הודעות דחיפה.

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

בקשה להרשאה ב-Chrome למחשב ולנייד.

אחרי שהמשתמש ילחץ על 'אישור', 'חסימה' או פשוט יסגור את הבקשה, התוצאה תתקבל כמחרוזת: '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;
    });
}

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

נבחן את כל האפשרויות שאפשר להעביר.

אפשרויות userVisibleOnly

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

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

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

בשלב הזה חובה להעביר ערך של true. אם לא תכללו את המפתח userVisibleOnly או תעבירו את false, תופיע השגיאה הבאה:

נכון לעכשיו, ב-Chrome יש תמיכה ב-Push API רק עבור מינויים שיגרמו להצגת הודעות למשתמשים. כדי לציין זאת, צריך להפעיל את הפונקציה pushManager.subscribe({userVisibleOnly: true}) במקום זאת. אפשר לקרוא פרטים נוספים בכתובת https://goo.gl/yqv4Q4.

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

האפשרות applicationServerKey

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

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

האפשרות applicationServerKey שמועברת לקריאה subscribe() היא המפתח הציבורי של האפליקציה. הדפדפן מעביר את המידע הזה לשירות ה-push כשמצרפים את המשתמש, כך ששירות ה-push יכול לקשר את המפתח הציבורי של האפליקציה ל-PushSubscription של המשתמש.

התרשים הבא מדגים את השלבים האלה.

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

אם נרשם משתמש במחשב, האם הוא רשום גם בטלפון?

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

לאן ממשיכים

Codelabs