שליחת הודעות באמצעות ספריות דחיפה באינטרנט

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

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

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

בקטע הזה נשתמש ב-web-push Node לספרייה. יהיו הבדלים בשפות אחרות, אבל הם לא יהיו שונים מדי. אנחנו מסתכלים על Node כי הוא JavaScript והוא אמור להיות הזמינים ביותר לקוראים.

נפעל לפי השלבים הבאים:

  1. שליחת המינוי לקצה העורפי שלנו ושמירתו.
  2. אחזור מינויים שמורים והפעלה של הודעת Push.

מתבצעת שמירה של המינויים

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

בדף האינטרנט להדגמה, השדה PushSubscription נשלח לקצה העורפי שלנו באמצעות בקשת POST פשוטה:

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.');
      }
    });
}

בשרת Express בהדגמה שלנו יש אוזן בקשות תואמת עבור נקודת קצה /api/save-subscription/:

app.post('/api/save-subscription/', function (req, res) {

במסלול הזה אנחנו מאמתים את המינוי רק כדי לוודא שהבקשה תקינה ולא מלאה אשפה:

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    res.status(400);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'no-endpoint',
          message: 'Subscription must have an endpoint.',
        },
      }),
    );
    return false;
  }
  return true;
};

אם המינוי תקף, עלינו לשמור אותו ולהחזיר אותו תגובת JSON:

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.',
        },
      }),
    );
  });

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

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

שליחת הודעות בדחיפה

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

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

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

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

בהדגמה, הערכים האלה מתווספים לאפליקציית Node למשל (קוד משעמם לי, אבל אני רק רוצה חשוב לדעת שאין קסם):

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

בשלב הבא צריך להתקין את המודול web-push לשרת ה-Node שלנו:

npm install web-push --save

לאחר מכן, בסקריפט הצמתים אנחנו דורשים את המודול web-push למשל:

const webpush = require('web-push');

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

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

שימו לב שכללנו גם "mailto:" String. המחרוזת הזו צריכה להיות כתובת URL או mailto כתובת אימייל. קטע המידע הזה יישלח בפועל לשירות Web Push כחלק מ- הבקשה להפעלת דחיפה. הסיבה לכך היא שאם יש צורך בשירות דחיפת דפי אינטרנט כדי ליצור קשר עם השולח, יש לו מידע שיאפשר לו.

עכשיו המודול web-push מוכן לשימוש. השלב הבא הוא להפעיל הודעת דחיפה.

ההדגמה משתמשת בחלונית הניהול לכאורה כדי להפעיל הודעות דחיפה.

צילום מסך של דף הניהול.

לחיצה על 'Trigger Push Message' הלחצן ישלח בקשת POST אל /api/trigger-push-msg/, וזה האות שהקצה העורפי שלנו שולח הודעות דחיפה, אז אנחנו יוצרים את המסלול Express לנקודת הקצה הזו:

app.post('/api/trigger-push-msg/', function (req, res) {

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

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

לאחר מכן הפונקציה triggerPushMsg() יכולה להשתמש בספריית ה-web-push כדי לשלוח הודעה במסגרת המינוי.

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

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

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

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

במקרה של שגיאה אחרת, אנחנו רק throw err, ואז נוכל להחזיר את ההבטחה דחייה של triggerPushMsg().

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

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

.then(() => {
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-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

עברנו על שלבי ההטמעה העיקריים:

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

ללא קשר לקצה העורפי (Node, PHP, Python, ...), השלבים להטמעת Push יבוצעו יהיה זהה.

בשלב הבא, מה בדיוק ספריות ה-web-push האלה עושות בשבילנו?

מה השלב הבא?

שיעורי Lab קוד