ارسال پیام با کتابخانه های فشار وب

مت گانت

یکی از نکات دردناک هنگام کار با فشار وب این است که راه‌اندازی یک پیام فشار بسیار "بیهوده" است. برای راه‌اندازی یک پیام فشار، یک برنامه باید درخواست POST را به دنبال پروتکل فشار وب به یک سرویس فشار ارسال کند. برای استفاده از فشار در همه مرورگرها، باید از VAPID (با نام مستعار کلیدهای سرور برنامه) استفاده کنید که اساساً مستلزم تنظیم یک هدر با مقداری است که ثابت کند برنامه شما می تواند به کاربر پیام دهد. برای ارسال داده با یک پیام فشاری، داده ها باید رمزگذاری شوند و هدرهای خاصی باید اضافه شوند تا مرورگر بتواند پیام را به درستی رمزگشایی کند.

مشکل اصلی با تحریک فشار این است که اگر به مشکلی برخورد کنید، تشخیص آن مشکل است. این با گذشت زمان و پشتیبانی گسترده تر مرورگر در حال بهبود است، اما آسان نیست. به همین دلیل، من قویاً توصیه می‌کنم از یک کتابخانه برای مدیریت رمزگذاری، قالب‌بندی و راه‌اندازی پیام فشار خود استفاده کنید.

اگر واقعاً می‌خواهید درباره کارهایی که کتابخانه‌ها انجام می‌دهند بدانید، در بخش بعدی به آن خواهیم پرداخت. در حال حاضر، ما قصد داریم به مدیریت اشتراک ها و استفاده از یک کتابخانه فشار وب موجود برای ایجاد درخواست های فشار نگاه کنیم.

در این بخش از کتابخانه web-push Node استفاده خواهیم کرد. زبان‌های دیگر تفاوت‌هایی با هم خواهند داشت، اما خیلی متفاوت نیستند. ما به Node نگاه می کنیم زیرا جاوا اسکریپت است و باید در دسترس ترین برای خوانندگان باشد.

مراحل زیر را طی خواهیم کرد:

  1. یک اشتراک به باطن ما ارسال کنید و آن را ذخیره کنید.
  2. اشتراک های ذخیره شده را بازیابی کنید و یک پیام فشار راه اندازی کنید.

ذخیره اشتراک ها

ذخیره و پرس و جو 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

سپس، در اسکریپت Node ما به ماژول 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:" را اضافه کردیم. این رشته باید یک URL یا یک آدرس ایمیل به ایمیل باشد. این بخش از اطلاعات در واقع به عنوان بخشی از درخواست برای راه اندازی فشار، به سرویس فشار وب ارسال می شود. دلیل انجام این کار این است که اگر یک سرویس فشار وب نیاز به تماس با فرستنده داشته باشد، اطلاعاتی در اختیار دارد که آنها را قادر می سازد.

با این کار، ماژول web-push آماده استفاده است، مرحله بعدی راه اندازی یک پیام فشار است.

نسخه ی نمایشی از پنل مدیریت تظاهر برای راه اندازی پیام های فشار استفاده می کند.

اسکرین شات از صفحه مدیریت

با کلیک کردن روی دکمه "پیام فشار محرک" یک درخواست POST به /api/trigger-push-msg/ ایجاد می‌شود، که سیگنالی است برای backend ما برای ارسال پیام‌های فشار، بنابراین مسیر را به صورت اکسپرس برای این نقطه پایانی ایجاد می‌کنیم:

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 هنوز معتبر است یا خیر.

برای تعیین نوع خطا از سرویس فشار بهتر است به کد وضعیت نگاه کنید. پیام های خطا بین سرویس های فشار متفاوت است و برخی از آنها مفیدتر از سایرین هستند.

در این مثال، کدهای وضعیت 404 و 410 را بررسی می‌کند که کدهای وضعیت HTTP برای «Not Found» و «Gone» هستند. اگر یکی از این موارد را دریافت کنیم، به این معنی است که اشتراک منقضی شده است یا دیگر معتبر نیست. در این سناریوها، ما باید اشتراک ها را از پایگاه داده خود حذف کنیم.

در صورت بروز خطای دیگری، ما فقط 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 ایجاد کنید تا ارسال پیام‌های فشاری را آغاز کند (در این مورد، یک API از پنل مدیریت ادعایی فراخوانی می‌شود).
  3. همه اشتراک‌ها را از باطن ما بازیابی کنید و برای هر اشتراک با یکی از کتابخانه‌های تحت فشار پیامی ارسال کنید.

صرف نظر از باطن شما (Node، PHP، Python، ...)، مراحل اجرای push یکسان خواهد بود.

در مرحله بعد، این کتابخانه های تحت فشار دقیقاً چه کاری برای ما انجام می دهند؟

بعد کجا بریم

آزمایشگاه های کد