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

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

API JavaScript để thực hiện việc này khá đơn giản, vì vậy, hãy cùng tìm hiểu quy trình logic.

Phát hiện tính năng

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ù tính năng hỗ trợ trình duyệt đang phát triển nhanh chóng cho cả worker dịch vụ và tính năng nhắn tin đẩy, nhưng bạn luôn nên phát hiện tính năng cho cả hai và tăng cường dần.

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

Khi 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 API PushManager

Hỗ trợ trình duyệt

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Nguồn

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 đề ở đây là chúng ta không thể biết trình duyệt hiện tại triển khai phiên bản API nào, 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 truyền vào một đối tượng options (tuỳ chọn) bao gồm cả tham số bắt buộc và không bắt buộc.

Hãy xem tất cả các tuỳ chọn mà chúng ta có thể truyền 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 thê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 mà người dùng nhìn thấy. 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 thông tin.

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à một cặp khoá công khai và khoá 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 truyề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 vào khoá máy chủ ứng dụng công khai.
  2. Sau đó, trình duyệt sẽ tạo một yêu cầu mạng đến một dịch vụ đẩy sẽ tạo một đ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 làm việc 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 một 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 vào web-push-codelab.glitch.me hoặc bạn có thể sử dụng dòng lệnh web-push để tạo khoá bằng cách làm 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à subscribe()

Việc gọi subscribe() có một tác dụng phụ. 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 sẽ hữu ích nếu giao diện người dùng của bạn hoạt động với quy trình này, nhưng nếu bạn muốn có thêm quyền kiểm soát (và tôi nghĩ hầu hết các nhà phát triển đều muốn), hãy sử dụng API Notification.requestPermission() mà chúng ta đã sử dụng trước đó.

PushSubscription là gì?

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

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 cần thiết để gửi thông báo đẩy đến 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 tin nhắn được gửi bằng thông báo đẩy (chúng ta sẽ thảo luận về thông báo đẩy ở phần sau của 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 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);
    });
}

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 cũng được, 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);

Bạn có thể gửi gói thuê bao trong 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 nhận yêu cầu này và lưu dữ liệu vào cơ sở dữ liệu để sử dụng sau này.

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 ta có thể gửi cho người dùng một thông báo bất cứ khi nào chúng ta 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ế, 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 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 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. Dịch vụ đẩy do trình duyệt chọn và như chúng ta đã thấy với lệnh gọi subscribe(), trình duyệt sẽ đưa ra 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 sử dụng một Dịch vụ đẩy khác nhau, phải không? Chúng có API 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à Giao thức đẩy web và mô tả yêu cầu mạng mà ứng dụng của bạn cần thực hiện để kích hoạt thông báo đẩy.

Nếu tôi đăng ký cho người dùng trên máy tính, thì họ có được đăng ký trên điện thoại 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