اشتراک کاربر

اولین قدم دریافت مجوز از کاربر برای ارسال پیام‌های فشاری است و سپس می‌توانیم یک PushSubscription دریافت کنیم.

API جاوا اسکریپت برای انجام این کار کاملاً مستقیم است، بنابراین اجازه دهید از طریق جریان منطقی قدم برداریم.

تشخیص ویژگی

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

  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;
}

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

ثبت نام کارگر خدماتی

با شناسایی ویژگی می دانیم که هم سرویس کارگران و هم Push پشتیبانی می شوند. قدم بعدی «ثبت نام» کارگر خدماتی است.

وقتی یک سرویس‌کار را ثبت می‌کنیم، به مرورگر می‌گوییم که فایل سرویس‌کار ما کجاست. فایل هنوز فقط جاوا اسکریپت است، اما مرورگر به 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() مراحل زیر را انجام می دهد:

  1. فایل کارگر سرویس را دانلود کنید.

  2. جاوا اسکریپت را اجرا کنید.

  3. اگر همه چیز به درستی اجرا شود و هیچ خطایی وجود نداشته باشد، وعده بازگشتی توسط register() برطرف می شود. در صورت وجود خطا از هر نوع، قول رد می شود.

اگر register() رد کرد، جاوا اسکریپت خود را از نظر غلط املایی/خطا در ابزار توسعه کروم دوباره بررسی کنید.

هنگامی که register() حل شد، یک ServiceWorkerRegistration را برمی گرداند. ما از این ثبت برای دسترسی به PushManager API استفاده خواهیم کرد.

سازگاری مرورگر PushManager API

پشتیبانی مرورگر

  • کروم: 42.
  • لبه: 17.
  • فایرفاکس: 44.
  • سافاری: 16.

منبع

درخواست اجازه

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

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() است. این روش یک اعلان را به کاربر نمایش می دهد:

درخواست مجوز در کروم دسکتاپ و موبایل.

هنگامی که کاربر با فشار دادن Allow، Block یا بستن آن با فرمان مجوز تعامل برقرار کرد، نتیجه به عنوان یک رشته به ما داده می‌شود: 'granted' ، 'default' یا 'denied' .

در کد نمونه بالا، وعده ای که توسط askPermission() برگردانده می شود، در صورت اعطای مجوز حل می شود، در غیر این صورت با خطای رد وعده مواجه می شویم.

یکی از موارد لبه ای که باید به آن رسیدگی کنید این است که کاربر روی دکمه "Block" کلیک کند. اگر این اتفاق بیفتد، برنامه وب شما نمی‌تواند دوباره از کاربر اجازه بخواهد. آنها باید به صورت دستی برنامه شما را با تغییر وضعیت مجوز، که در پانل تنظیمات مدفون است، «بدون انسداد» کنند. به دقت در مورد چگونگی و زمان درخواست مجوز از کاربر فکر کنید، زیرا اگر آنها بر روی block کلیک کنند، این راه آسانی برای برگرداندن آن تصمیم نیست.

خبر خوب این است که اکثر کاربران تا زمانی که بدانند چرا مجوز درخواست شده است، از دادن مجوز خوشحال هستند.

در ادامه نحوه درخواست مجوز برخی از سایت‌های محبوب را بررسی خواهیم کرد.

مشترک شدن کاربر با PushManager

پس از ثبت نام و دریافت مجوز، می‌توانیم یک کاربر را با فراخوانی 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() ، یک شی option را ارسال می کنیم که از پارامترهای مورد نیاز و اختیاری تشکیل شده است.

بیایید به همه گزینه هایی که می توانیم از آنها عبور کنیم نگاه کنیم.

گزینه های userVisibleOnly

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

نگرانی این بود که توسعه دهندگان می توانند کارهای ناخوشایند مانند ردیابی موقعیت مکانی کاربر را به طور مداوم بدون اینکه کاربر بداند انجام دهند.

برای جلوگیری از این سناریو و دادن زمان به نویسندگان مشخصات برای بررسی بهترین روش برای پشتیبانی از این ویژگی، گزینه userVisibleOnly اضافه شد و عبور از مقدار true یک توافق نمادین با مرورگر است که برنامه وب هر بار یک اعلان را نشان می دهد. دریافت می شود (یعنی بدون فشار بی صدا).

در حال حاضر باید از مقدار true عبور کنید. اگر کلید userVisibleOnly را وارد نکنید یا به صورت false عبور نکنید، خطای زیر را دریافت خواهید کرد:

Chrome در حال حاضر فقط از Push API برای اشتراک‌هایی پشتیبانی می‌کند که منجر به پیام‌های قابل مشاهده توسط کاربر می‌شوند. به جای آن می توانید با فراخوانی pushManager.subscribe({userVisibleOnly: true}) این موضوع را نشان دهید. برای جزئیات بیشتر به https://goo.gl/yqv4Q4 مراجعه کنید.

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

گزینه applicationServerKey

در قسمت قبل به طور خلاصه به «کلیدهای سرور برنامه» اشاره کردیم. "کلیدهای سرور برنامه" توسط یک سرویس فشار برای شناسایی برنامه مشترک یک کاربر و اطمینان از اینکه همان برنامه در حال ارسال پیام به کاربر است استفاده می شود.

کلیدهای سرور برنامه یک جفت کلید عمومی و خصوصی هستند که مختص برنامه شما هستند. کلید خصوصی باید برای برنامه شما مخفی بماند و کلید عمومی را می توان آزادانه به اشتراک گذاشت.

گزینه applicationServerKey ارسال شده به فراخوانی subscribe() کلید عمومی برنامه است. مرورگر هنگام اشتراک کاربر، این مورد را به یک سرویس push ارسال می کند، به این معنی که سرویس فشار می تواند کلید عمومی برنامه شما را به PushSubscription کاربر گره بزند.

نمودار زیر این مراحل را نشان می دهد.

  1. برنامه وب شما در یک مرورگر بارگذاری می شود و شما با subscribe() ، کلید سرور برنامه عمومی خود را ارسال می کنید.
  2. سپس مرورگر یک درخواست شبکه به یک سرویس فشار می دهد که یک نقطه پایانی ایجاد می کند، این نقطه پایانی را با کلید عمومی برنامه ها مرتبط می کند و نقطه پایانی را به مرورگر باز می گرداند.
  3. مرورگر این نقطه پایانی را به PushSubscription اضافه می کند که از طریق وعده subscribe() برگردانده می شود.

تصویر کلید سرور برنامه عمومی در روش subscribe استفاده می شود.

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

نحوه استفاده از کلید سرور برنامه خصوصی هنگام ارسال پیام

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

مشخصاتی که تعیین می کند کلید سرور برنامه باید چه باشد ، مشخصات VAPID است. هر زمان که چیزی با اشاره به "کلیدهای سرور برنامه" یا "کلیدهای VAPID" می خوانید، فقط به یاد داشته باشید که آنها یکسان هستند.

نحوه ایجاد کلیدهای سرور برنامه

می‌توانید با مراجعه به web-push-codelab.glitch.me یک مجموعه عمومی و خصوصی از کلیدهای سرور برنامه ایجاد کنید یا می‌توانید با انجام کارهای زیر از خط فرمان web-push برای تولید کلیدها استفاده کنید:

    $ npm install -g web-push
    $ web-push generate-vapid-keys

شما فقط باید یک بار این کلیدها را برای برنامه خود ایجاد کنید، فقط مطمئن شوید که کلید خصوصی را خصوصی نگه دارید. (آره همین الان گفتم.)

مجوزها و اشتراک ()

فراخوانی 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 شامل تمام اطلاعات مورد نیاز برای ارسال پیام های فشار به آن کاربر است. اگر محتویات را با استفاده از JSON.stringify() چاپ کنید، موارد زیر را مشاهده خواهید کرد:

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

endpoint URL خدمات فشار است. برای راه اندازی یک پیام فشار، یک درخواست POST به این URL ارسال کنید.

شی keys حاوی مقادیری است که برای رمزگذاری داده پیام ارسال شده با یک پیام فشاری استفاده می شود (که بعداً در این بخش به آن خواهیم پرداخت).

اشتراک مجدد منظم برای جلوگیری از انقضا

هنگام اشتراک در اعلان‌های فشار، اغلب یک 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 توسط مرورگر انتخاب می‌شود و همانطور که با فراخوانی subscribe() دیدیم، مرورگر درخواست‌های شبکه را به سرویس push ارسال می‌کند تا جزئیاتی را که PushSubscription را تشکیل می‌دهند، بازیابی کند.

هر مرورگر از Push Service متفاوتی استفاده می کند، آیا API های متفاوتی ندارند؟

همه سرویس‌های فشار همان API را انتظار دارند.

این API متداول Web Push Protocol نامیده می شود و درخواست شبکه ای را که برنامه شما برای راه اندازی یک پیام فشار باید انجام دهد را توصیف می کند.

اگر کاربر را روی دسکتاپ مشترک کنم، آیا در تلفن خود نیز مشترک می شود؟

متاسفانه نه. یک کاربر باید برای فشار در هر مرورگری که مایل به دریافت پیام در آن است ثبت نام کند. همچنین شایان ذکر است که این امر مستلزم اعطای مجوز کاربر برای هر دستگاه است.

بعد کجا بریم

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