Đăng ký người dùng

Bước đầu tiên là yêu cầu người dùng cho phép gửi cho họ thông báo đẩy, sau đó chúng ta có thể nhận được PushSubscription.

API JavaScript để thực hiện việc này được tiến hành một cách hợp lý, vì vậy, hãy xem qua quy trình logic.

Trước tiên, chúng ta cần kiểm tra xem trình duyệt hiện tại có thực sự hỗ trợ tính năng nhắn tin đẩy hay không. Chúng ta có thể kiểm tra xem tính năng đẩy có được hỗ trợ hay không bằng hai bước kiểm tra đơn giản.

  1. Kiểm tra serviceWorker trên navigator.
  2. Kiểm tra PushManager trên cửa sổ.
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;
}

Mặc dù khả năng hỗ trợ trình duyệt đang tăng nhanh cho cả trình chạy dịch vụ và thông báo đẩy, nhưng bạn nên luôn tính năng phát hiện cho cả hai và nâng cao dần.

Đăng ký trình chạy dịch vụ

Với tính năng phát hiện tính năng, chúng ta biết rằng cả worker dịch vụ và tính năng đẩy đều được hỗ trợ. Bước tiếp theo là "đăng ký" worker dịch vụ.

Khi đăng ký một worker dịch vụ, chúng ta sẽ cho trình duyệt biết vị trí của tệp worker dịch vụ. Tệp này vẫn chỉ là JavaScript, nhưng trình duyệt sẽ "cấp quyền truy cập" vào các API worker dịch vụ, bao gồm cả tính năng đẩy. Chính xác hơn, trình duyệt chạy tệp trong môi trường worker của dịch vụ.

Để đăng ký một worker dịch vụ, hãy gọi navigator.serviceWorker.register(), truyền vào đường dẫn đến tệp của chúng ta. Chẳng hạn như:

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

Hàm này cho trình duyệt biết rằng chúng ta có một tệp trình chạy dịch vụ và vị trí của tệp đó. Trong trường hợp này, tệp trình chạy dịch vụ nằm ở /service-worker.js. Ở chế độ nền, trình duyệt sẽ thực hiện các bước sau sau khi gọi register():

  1. Tải tệp worker dịch vụ xuống.

  2. Chạy JavaScript.

  3. Nếu mọi thứ chạy đúng cách và không có lỗi, lời hứa do register() trả về sẽ được giải quyết. Nếu có bất kỳ lỗi nào, lời hứa sẽ bị từ chối.

Nếu register() từ chối, hãy kiểm tra kỹ JavaScript để tìm lỗi chính tả / lỗi trong Công cụ của Chrome cho nhà phát triển.

Khi phân giải, register() sẽ trả về một ServiceWorkerRegistration. Chúng ta sẽ sử dụng thông tin đăng ký này để truy cập vào PushManager API.

Khả năng tương thích của trình duyệt với PushManager API

Hỗ trợ trình duyệt

  • Chrome: 42.
  • Cạnh: 17.
  • Firefox: 44.
  • Safari: 16.

Nguồn

Đang yêu cầu cấp quyền

Chúng ta đã đăng ký worker dịch vụ và sẵn sàng đăng ký người dùng. Bước tiếp theo là yêu cầu người dùng cấp quyền gửi thông báo đẩy.

API để nhận quyền tương đối đơn giản, điểm bất lợi là API gần đây đã thay đổi từ việc thực hiện lệnh gọi lại thành trả về một Lời hứa. Vấn đề là chúng tôi không thể cho biết phiên bản API được triển khai bởi trình duyệt hiện tại, vì vậy bạn phải triển khai cả hai và xử lý cả hai.

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

Trong mã trên, đoạn mã quan trọng là lệnh gọi đến Notification.requestPermission(). Phương thức này sẽ hiển thị lời nhắc cho người dùng:

Lời nhắc cấp quyền trên Chrome dành cho máy tính và thiết bị di động.

Sau khi người dùng tương tác với lời nhắc cấp quyền bằng cách nhấn vào Cho phép, Chặn hoặc chỉ đóng lời nhắc, chúng ta sẽ nhận được kết quả dưới dạng một chuỗi: 'granted', 'default' hoặc 'denied'.

Trong mã mẫu ở trên, lời hứa do askPermission() trả về sẽ phân giải nếu quyền được cấp, nếu không, chúng ta sẽ gửi một lỗi khiến lời hứa bị từ chối.

Một trường hợp đặc biệt mà bạn cần xử lý là nếu người dùng nhấp vào nút "Chặn". Nếu điều này xảy ra, ứng dụng web của bạn sẽ không thể yêu cầu người dùng cấp quyền lại. Người dùng sẽ phải "bỏ chặn" ứng dụng của bạn theo cách thủ công bằng cách thay đổi trạng thái quyền của ứng dụng, trạng thái này nằm trong bảng cài đặt. Hãy suy nghĩ kỹ về cách thức và thời điểm bạn yêu cầu người dùng cấp quyền, vì nếu họ nhấp vào nút chặn, thì bạn sẽ không dễ dàng đảo ngược quyết định đó.

Tin vui là hầu hết người dùng đều sẵn sàng cấp quyền miễn là họ biết lý do ứng dụng yêu cầu cấp quyền.

Chúng ta sẽ xem xét cách một số trang web phổ biến yêu cầu cấp quyền sau.

Đăng ký người dùng bằng PushManager

Sau khi đăng ký worker dịch vụ và có quyền, chúng ta có thể đăng ký người dùng bằng cách gọi 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;
    });
}

Khi gọi phương thức subscribe(), chúng ta sẽ truyền vào một đối tượng options, đối tượng này bao gồm cả tham số bắt buộc và tham số không bắt buộc.

Hãy xem tất cả các tuỳ chọn mà chúng ta có thể đưa vào.

tuỳ chọn userVisibleOnly

Khi tính năng đẩy được thêm vào trình duyệt lần đầu tiên, chúng tôi không chắc liệu nhà phát triển có thể gửi thông báo đẩy mà không hiển thị thông báo hay không. Phương thức này thường được gọi là đẩy âm thầm, do người dùng không biết rằng có điều gì đó đã xảy ra ở chế độ nền.

Mối lo ngại là nhà phát triển có thể làm những việc khó chịu như theo dõi vị trí của người dùng liên tục mà người dùng không biết.

Để tránh trường hợp này và để tác giả thông số kỹ thuật có thời gian cân nhắc cách tốt nhất để hỗ trợ tính năng này, tuỳ chọn userVisibleOnly đã được thêm vào và việc truyền vào giá trị true là một thoả thuận tượng trưng với trình duyệt rằng ứng dụng web sẽ hiển thị thông báo mỗi khi nhận được thông báo đẩy (tức là không có thông báo đẩy thầm).

Hiện tại, bạn phải truyền vào giá trị true. Nếu không bao gồm khoá userVisibleOnly hoặc truyền vào false, bạn sẽ gặp lỗi sau:

Chrome hiện chỉ hỗ trợ Push API cho các gói thuê bao sẽ dẫn đến thông báo hiển thị với người dùng. Bạn có thể chỉ báo điều này bằng cách gọi pushManager.subscribe({userVisibleOnly: true}). Hãy xem https://goo.gl/yqv4Q4 để biết thêm chi tiết.

Có vẻ như tính năng đẩy âm thầm toàn bộ sẽ không bao giờ được triển khai trong Chrome. Thay vào đó, các tác giả thông số kỹ thuật đang khám phá khái niệm về API ngân sách, cho phép các ứng dụng web gửi một số lượng thông báo đẩy thầm nhất định dựa trên mức sử dụng của ứng dụng web.

Tuỳ chọn applicationServerKey

Chúng tôi đã đề cập ngắn gọn về "khoá máy chủ ứng dụng" trong phần trước. "Khoá máy chủ ứng dụng" được dịch vụ đẩy sử dụng để xác định ứng dụng đăng ký người dùng và đảm bảo rằng chính ứng dụng đó đang nhắn tin cho người dùng đó.

Khoá máy chủ ứng dụng là cặp khoá công khai và riêng tư dành riêng cho ứng dụng của bạn. Bạn nên giữ bí mật khoá riêng tư cho ứng dụng và có thể tự do chia sẻ khoá công khai.

Tuỳ chọn applicationServerKey được chuyển vào lệnh gọi subscribe() là khoá công khai của ứng dụng. Trình duyệt sẽ chuyển thông tin này đến dịch vụ đẩy khi người dùng đăng ký, nghĩa là dịch vụ đẩy có thể liên kết khoá công khai của ứng dụng với PushSubscription của người dùng.

Sơ đồ dưới đây minh hoạ các bước này.

  1. Ứng dụng web của bạn được tải trong trình duyệt và bạn gọi subscribe(), truyền khoá máy chủ ứng dụng công khai của bạn.
  2. Sau đó, trình duyệt sẽ gửi yêu cầu mạng đến một dịch vụ đẩy. Dịch vụ này sẽ tạo điểm cuối, liên kết điểm cuối này với khoá công khai của ứng dụng và trả về điểm cuối cho trình duyệt.
  3. Trình duyệt sẽ thêm điểm cuối này vào PushSubscription, được trả về thông qua lời hứa subscribe().

Hình minh hoạ khoá máy chủ ứng dụng công khai được dùng trong phương thức đăng ký.

Sau này, khi muốn gửi thông báo đẩy, bạn cần tạo tiêu đề Uỷ quyền. Tiêu đề này sẽ chứa thông tin được ký bằng khoá riêng tư của máy chủ ứng dụng. Khi nhận được yêu cầu gửi thông báo đẩy, dịch vụ đẩy có thể xác thực tiêu đề Uỷ quyền đã ký này bằng cách tra cứu khoá công khai được liên kết với điểm cuối nhận yêu cầu. Nếu chữ ký hợp lệ, dịch vụ đẩy sẽ biết rằng chữ ký đó phải đến từ máy chủ ứng dụng có khoá riêng tư trùng khớp. Về cơ bản, đây là một biện pháp bảo mật ngăn mọi người khác gửi thông báo đến người dùng của ứng dụng.

Cách sử dụng khoá máy chủ ứng dụng riêng tư khi gửi thông báo

Về mặt kỹ thuật, applicationServerKey là không bắt buộc. Tuy nhiên, cách triển khai dễ dàng nhất trên Chrome yêu cầu bạn phải có mã này và các trình duyệt khác có thể yêu cầu bạn phải có mã này trong tương lai. Bạn không bắt buộc phải sử dụng trình duyệt này trên Firefox.

Thông số kỹ thuật xác định loại khoá máy chủ ứng dụng là thông số kỹ thuật VAPID. Bất cứ khi nào bạn đọc nội dung đề cập đến "khoá máy chủ ứng dụng" hoặc "khoá VAPID", hãy nhớ rằng đó là cùng một loại khoá.

Cách tạo khoá máy chủ ứng dụng

Bạn có thể tạo tập hợp khoá máy chủ ứng dụng công khai và riêng tư bằng cách truy cập web-push-codelab.glitch.me hoặc sử dụng dòng lệnh web-push để tạo khoá bằng cách thực hiện như sau:

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

Bạn chỉ cần tạo các khoá này một lần cho ứng dụng của mình, chỉ cần đảm bảo bạn giữ khoá riêng tư ở chế độ riêng tư. (Vâng, tôi vừa nói vậy.)

Quyền và subscription()

Có một tác dụng phụ khi gọi subscribe(). Nếu ứng dụng web của bạn không có quyền hiển thị thông báo tại thời điểm gọi subscribe(), thì trình duyệt sẽ yêu cầu quyền cho bạn. Điều này rất hữu ích nếu giao diện người dùng của bạn hoạt động theo quy trình này, nhưng nếu bạn muốn có nhiều quyền kiểm soát hơn (và tôi cho rằng hầu hết các nhà phát triển sẽ làm như vậy), hãy sử dụng API Notification.requestPermission() mà chúng ta đã sử dụng trước đó.

Đăng ký đẩy là gì?

Chúng ta gọi subscribe(), truyền một số tuỳ chọn vào và đổi lại, chúng ta nhận được lời hứa sẽ phân giải thành PushSubscription dẫn đến một số mã như vậy:

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

Đối tượng PushSubscription chứa tất cả thông tin bắt buộc cần thiết để gửi thông báo đẩy cho người dùng đó. Nếu in nội dung bằng JSON.stringify(), bạn sẽ thấy nội dung sau:

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

endpoint là URL của dịch vụ đẩy. Để kích hoạt thông báo đẩy, hãy tạo một yêu cầu POST đến URL này.

Đối tượng keys chứa các giá trị dùng để mã hoá dữ liệu thông báo được gửi bằng thông báo đẩy (chúng ta sẽ thảo luận về nội dung này sau trong phần này).

Thường xuyên gia hạn gói thuê bao để tránh hết hạn

Khi đăng ký nhận thông báo đẩy, bạn thường nhận được PushSubscription.expirationTime của null. Về lý thuyết, điều này có nghĩa là gói thuê bao không bao giờ hết hạn (trái ngược với khi bạn nhận được DOMHighResTimeStamp, cho bạn biết thời điểm chính xác khi gói thuê bao hết hạn). Tuy nhiên, trong thực tế, trình duyệt thường vẫn cho phép gói thuê bao hết hạn, ví dụ: nếu không nhận được thông báo đẩy trong một khoảng thời gian dài hoặc nếu trình duyệt phát hiện người dùng không sử dụng ứng dụng có quyền gửi thông báo đẩy. Một mẫu để ngăn chặn điều này là đăng ký lại người dùng sau mỗi thông báo nhận được, như trong đoạn mã sau. Điều này đòi hỏi bạn phải gửi thông báo đủ thường xuyên để trình duyệt không tự động hết hạn gói thuê bao. Bạn nên cân nhắc thật kỹ những ưu và khuyết điểm của việc gửi thông báo hợp lệ so với việc vô tình gửi thông báo rác cho người dùng chỉ để gói thuê bao không hết hạn. Cuối cùng, bạn không nên tìm cách chống lại trình duyệt trong nỗ lực bảo vệ người dùng khỏi các đăng ký thông báo bị lãng quên từ lâu.

/* 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);
    });
}

Gửi gói thuê bao đến Máy chủ của bạn

Sau khi có gói thuê bao đẩy, bạn sẽ muốn gửi gói thuê bao đó đến máy chủ của mình. Bạn có thể làm theo cách nào tuỳ ý, nhưng một mẹo nhỏ là sử dụng JSON.stringify() để lấy tất cả dữ liệu cần thiết từ đối tượng thuê bao. Ngoài ra, bạn có thể kết hợp cùng một kết quả theo cách thủ công như sau:

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

Quá trình gửi gói thuê bao được thực hiện trên trang web như sau:

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

Máy chủ nút này nhận được yêu cầu này và lưu dữ liệu vào cơ sở dữ liệu để sử dụng sau.

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

Với thông tin chi tiết về PushSubscription trên máy chủ, chúng tôi có thể gửi thông báo cho người dùng bất cứ khi nào chúng tôi muốn.

Thường xuyên gia hạn gói thuê bao để tránh hết hạn

Khi đăng ký nhận thông báo đẩy, bạn thường nhận được PushSubscription.expirationTime của null. Về lý thuyết, điều này có nghĩa là gói thuê bao không bao giờ hết hạn (trái ngược với khi bạn nhận được DOMHighResTimeStamp, cho bạn biết thời điểm chính xác khi gói thuê bao hết hạn). Tuy nhiên, trong thực tế, các trình duyệt vẫn thường để gói thuê bao hết hạn, chẳng hạn như nếu người dùng không nhận được thông báo đẩy trong một thời gian dài hoặc nếu trình duyệt phát hiện thấy người dùng không sử dụng ứng dụng có quyền truy cập thông báo đẩy. Một mẫu để ngăn chặn điều này là đăng ký lại người dùng sau mỗi thông báo nhận được, như trong đoạn mã sau. Điều này đòi hỏi bạn phải gửi thông báo đủ thường xuyên để trình duyệt không tự động hết hạn gói thuê bao. Bạn nên cân nhắc thật kỹ những ưu và khuyết điểm của việc gửi thông báo hợp lệ so với việc gửi thông báo rác cho người dùng chỉ để gói thuê bao không hết hạn. Cuối cùng, bạn không nên cố gắng chống lại trình duyệt trong nỗ lực bảo vệ người dùng khỏi các gói thuê bao thông báo đã lâu bị lãng quên.

/* 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);
    });
}

Câu hỏi thường gặp

Một vài câu hỏi thường gặp mà mọi người đã hỏi tại thời điểm này:

Tôi có thể thay đổi dịch vụ đẩy mà trình duyệt sử dụng không?

Không. Trình duyệt chọn dịch vụ đẩy và như chúng ta đã thấy với lệnh gọi subscribe(), trình duyệt sẽ gửi các yêu cầu mạng đến dịch vụ đẩy để truy xuất thông tin chi tiết tạo nên PushSubscription.

Mỗi trình duyệt dùng một Dịch vụ đẩy riêng, các API đó có khác nhau không?

Tất cả dịch vụ đẩy đều sẽ sử dụng cùng một API.

API phổ biến này được gọi là Web Push Protocol (Giao thức đẩy web) và mô tả yêu cầu mạng mà ứng dụng cần thực hiện để kích hoạt một thông báo đẩy.

Nếu tôi đăng ký một người dùng trên máy tính, thì họ có đăng ký trên điện thoại của họ không?

Rất tiếc là không. Người dùng phải đăng ký nhận thông báo đẩy trên mỗi trình duyệt mà họ muốn nhận thông báo. Cũng cần lưu ý rằng người dùng sẽ phải cấp quyền trên từng thiết bị.

Bước tiếp theo

Lớp học lập trình