Đă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ể có thể bắt tay vào PushSubscription.

API JavaScript để thực hiện việc này khá đơn giản, vì vậy hãy bước thông qua luồng 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ợ thông báo đẩy hay không. Chúng tôi có thể kiểm tra xem được hỗ trợ qua 2 bước kiểm tra đơn giản.

  1. Kiểm tra serviceWorker trên navigation (trình điều hướng).
  2. Kiểm tra PushManager trên window.
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ả Service worker và thông báo đẩy, bạn nên sử dụng tính năng phát hiện cho cả nâng cao dần.

Đăng ký một 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ả Service worker và Đẩy đều được hỗ trợ. Bước tiếp theo là "đăng ký" trình chạy dịch vụ của chúng tôi.

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

Để đăng ký trình chạy dịch vụ, hãy gọi navigator.serviceWorker.register(), truyền đường dẫn đến tệp của chúng tôi. 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ó tệp trình chạy dịch vụ và vị trí của tệp này. Trong trong trường hợp này, tệp trình chạy dịch vụ nằm tại /service-worker.js. Video hậu trường về trình duyệt sẽ thực hiện các bước sau đây sau khi gọi register():

  1. Tải tệp trình chạy dịch vụ xuống.

  2. Chạy JavaScript.

  3. Nếu mọi thứ chạy chính xác và không có lỗi, lời hứa được register() trả về sẽ được giải quyết. Nếu có lỗi dưới bất kỳ hình thức nào, lời hứa sẽ 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 xong, register() sẽ trả về ServiceWorkerRegistration. Chúng tôi sẽ sử dụng để truy cập vào PushManager API.

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

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 tôi đã đăng ký service worker và sẵn sàng đăng ký người dùng, bước tiếp theo là tải quyền từ người dùng để gửi cho họ thông báo đẩy.

API để yêu cầu cấp quyền tương đối đơn giản, nhược điểm là API gần đây đã thay đổi từ việc thực hiện lệnh gọi lại sang trả về một lời hứa (Promise). Chiến lược phát hành đĩa đơn vấn đề với vấn đề này là chúng tôi không thể cho biết phiên bản API nào được triển khai bởi trình duyệt, nên 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 đoạn 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 Cho phép, Chặn hoặc chỉ cần đóng lời nhắc, chúng ta sẽ được cung cấp kết quả dưới dạng 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 chấp nhận, nếu không thì chúng ta sẽ gây ra lỗi khi từ chối lời hứa.

Một trường hợp đặc biệt mà bạn cần xử lý là khi người dùng nhấp vào nút "Chặn" . Nếu trường hợp 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ần nữa. Họ sẽ phải "bỏ chặn" theo cách thủ công ứng dụng của bạn bằng cách thay đổi trạng thái cấp quyền (sẽ bị ẩn) 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 cho phép, bởi vì nếu họ nhấp vào chặn, đó không phải là cách dễ dàng để đảo ngược quyết định đó.

Tin vui là hầu hết người dùng sẽ sẵn lòng cấp quyền, miễn là họ biết lý do 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 này.

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

Sau khi chúng tôi đăng ký trình chạy dịch vụ và được phép, chúng tôi có thể đăng ký người dùng bằng cách đang 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, đối tượng này bao gồm cả hai cá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ể đưa vào.

Các tuỳ chọn userVisibleOnly

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

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

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

Hiện tại, bạn phải truyền vào giá trị true. Nếu bạn không đưa vào 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 điệp mà người dùng nhìn thấy. Bạn có thể cho biết điều này bằng cách gọi Hãy pushManager.subscribe({userVisibleOnly: true}). Xem https://goo.gl/yqv4Q4 để biết thêm chi tiết.

Hiện tại, có vẻ như cơ chế đẩy im lặng hoàn toàn sẽ không bao giờ được triển khai trong Chrome. Thay vào đó, đang tìm hiểu khái niệm API ngân sách, API này sẽ cho phép các ứng dụng web số lượng thông báo đẩy không có âm thanh dựa trên mức sử dụng ứng dụng web.

Tuỳ chọn applicationServerKey

Chúng tôi đã đề cập ngắn gọn đến "khoá máy chủ ứng dụng" trong phần trước. "Ứng dụng khoá máy chủ" đượ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 phải giữ bí mật khoá riêng tư cho ứng dụng của mình và có thể chia sẻ khoá công khai này thoải mái.

Tuỳ chọn applicationServerKey được truyền vào lệnh gọi subscribe() là tuỳ chọn công khai của ứng dụng . Trình duyệt chuyển thông tin này đến một dịch vụ đẩy khi đăng ký người dùng, nghĩa là yêu cầu đẩ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 dữ liệu công khai của bạn khoá máy chủ ứng dụng.
  2. Sau đó, trình duyệt 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ề qua phương thức Cam kết subscribe().

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

Sau này, nếu muốn gửi một thông báo đẩy, bạn cần tạo tiêu đề Uỷ quyền tệp này sẽ chứa thông tin đã ký bằng khoá riêng tư trên máy chủ ứng dụng của bạn. Khi dịch vụ đẩy nhận được yêu cầu gửi thông báo đẩy, dịch vụ nà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 được yêu cầu. Nếu chữ ký là hợp lệ thì dịch vụ đẩy biết rằng dịch vụ này 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 chặn người khác gửi thư cho người dùng ứng dụng.

Cách sử dụng khoá máy chủ ứng dụng riêng tư khi gửi một
tin nhắn

Về mặt kỹ thuật, applicationServerKey là không bắt buộc. Tuy nhiên, bước dễ nhất Việc triển khai trên Chrome đòi hỏi nó, và các trình duyệt khác có thể yêu cầu nó 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 nội dung của khoá máy chủ ứng dụng thông số 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", chỉ cần nhớ rằng chúng giống nhau.

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

Bạn có thể tạo bộ 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 bạn có thể sử dụng dòng lệnh đẩy web để 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ữ lại khoá riêng tư. (Đúng rồi, 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 đối với hiển thị thông báo tại thời điểm gọi subscribe(), 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 biết thêm kiểm soát (và tôi nghĩ hầu hết các nhà phát triển sẽ) sử dụng API Notification.requestPermission() mà chúng tôi đã 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ẽ giải quyết 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 một lệnh đẩy nhắn tin cho người dùng đó. Nếu in nội dung bằng JSON.stringify(), bạn sẽ thấy sau:

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

endpoint là URL dịch vụ đẩy. Để kích hoạt thông báo đẩy, hãy thực hiện yêu cầu POST vào 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 sau trong phần này).

Thường xuyên đăng ký lại để 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 null. Về mặt 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 trường hợp bạn nhận được DOMHighResTimeStamp (thông báo này cho bạn biết chính xác thời điểm 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 để cho 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 khoảng thời gian dài hơn hoặc nếu trình duyệt phát hiện người dùng hiện không sử dụng một ứng dụng có quyền truy cập thông báo đẩy. Một cách để 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ư minh hoạ 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. Đồng thời, bạn nên cân nhắc thật cẩn thận các ưu và nhược điểm của việc cần thông báo chính đáng với việc vô tình gửi spam 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. Điều này tuỳ thuộc vào bạn cách bạn bạn có thể làm điều đó, 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ừ của bạn. Ngoài ra, bạn có thể ghép nối 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 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 cho người dùng thông báo bất cứ khi nào chúng tôi muốn.

Thường xuyên đăng ký lại để 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 null. Về mặt 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 trường hợp bạn nhận được DOMHighResTimeStamp (thông báo này cho bạn biết chính xác thời điểm gói thuê bao hết hạn). Tuy nhiên, trên thực tế, các trình duyệt vẫn thường để cho 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 cách để 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ư minh hoạ 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. Đồng thời, bạn nên cân nhắc thật cẩn thận các ưu điểm và nhược điểm của việc cần thông báo chính đáng để gửi spam cho người dùng để 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);
    });
}

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

Một số câu hỏi phổ biến mà mọi người đã đặt ra vào thời điểm này:

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

Không. Dịch vụ đẩy được trình duyệt chọn và như chúng ta đã thấy với subscribe(), trình duyệt sẽ gửi 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 thông thường này được gọi là Giao thức đẩy web và mô tả yêu cầu mạng ứ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ý 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ý thông báo đẩy trên mỗi trình duyệt họ muốn nhận tin nhắn trên. Một điểm khác đáng lưu ý là việc này sẽ đòi hỏi việc người dùng cấp quyền trên từng thiết bị.

Điểm đến tiếp theo

Phòng thí nghiệm lập trình