רישום משתמש

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

ה-API של JavaScript כדי לעשות זאת הוא ישיר יחסית, אז בואו בתהליך הלוגיקה.

זיהוי תכונות

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

  1. מחפשים את האפשרות serviceWorker ב-navigator.
  2. מחפשים את PushManager ב-window.
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 וגם הודעות דחיפה, תמיד כדאי להציג לזיהוי לשפר בהדרגה.

רישום Service Worker

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

כשאנחנו רושמים קובץ שירות (service worker), אנחנו אומרים לדפדפן איפה נמצא קובץ ה-Service Worker. הקובץ הוא עדיין JavaScript בלבד, אבל הדפדפן יעניק לו גישה ל-Service Worker ממשקי API, כולל 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.
  • קצה: 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, שמורכב משניהם פרמטרים נדרשים ופרמטרים אופציונליים.

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

אפשרויות userViewOnly

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

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

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

כרגע חובה להעביר את הערך true. אם לא כוללים את מפתח או כרטיס של userVisibleOnly ב-false תתקבל השגיאה הבאה:

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

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

אפשרות applicationServerKey

הזכרנו בקצרה "מפתחות של שרת אפליקציות" בסעיף הקודם. "יישום מפתחות שרת" משמשים בשירות Push כדי לזהות את האפליקציה שרושם משתמש לוודא שאותה אפליקציה מעבירה הודעות למשתמש.

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

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

התרשים הבא ממחיש את השלבים האלה.

  1. אפליקציית האינטרנט שלך נטענת בדפדפן ומבצעת קריאה ל-subscribe(), ומעבירה את התוכן הציבורי שלך מפתח שרת האפליקציות.
  2. לאחר מכן הדפדפן שולח בקשת רשת לשירות Push שיייצר נקודת קצה (endpoint), לשייך את נקודת הקצה למפתח הציבורי של האפליקציות ולהחזיר את נקודת הקצה בדפדפן.
  3. הדפדפן יוסיף את נקודת הקצה הזו ל-PushSubscription, שמוחזר דרך הבטחה של subscribe().

איור של מפתח שרת האפליקציות הציבורי שמשמש ב-Subscribe
.

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

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

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

השדה endpoint הוא כתובת ה-URL של שירותי הדחיפה. כדי להפעיל הודעה בדחיפה, צריך לבקש 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);
    });
}

שליחת מינוי לשרת שלך

לאחר שיהיה לכם מינוי Push תוכלו לשלוח אותו לשרת שלכם. זה תלוי בך לעשות את זה, אבל טיפ קטנטן הוא להשתמש ב-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 Service אחר, נכון?

כל שירותי ה-Push יצפו באותו API.

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

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

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

מה השלב הבא?

שיעורי Lab קוד