Hướng dẫn cho người mới bắt đầu về Thông báo đẩy web bằng cách sử dụng Nhân viên dịch vụ (ok)

https://blog.atulr.com/web-notifications/

Ví dụ ok

Hướng dẫn cho người mới bắt đầu về Thông báo đẩy web bằng cách sử dụng Nhân viên dịch vụ

Ngày 25 tháng 9 năm 2018

Thông báo đẩy rất phổ biến trong các nền tảng ứng dụng di động gốc như Android và iOS. Các cách hiệu quả nhất để thu hút người dùng vào ứng dụng của bạn. Trong bài đăng này, chúng tôi sẽ xem xét cách triển khai thông báo đẩy cho web.

meme_push_one_doesnt

Thông báo có thể có hai loại:

  1. Thông báo cục bộ: Điều này được tạo bởi chính ứng dụng của bạn.

  2. Thông báo đẩy: Điều này được tạo bởi một máy chủ thông qua một sự kiện đẩy.

Nếu bạn ở đây chỉ để thông báo đẩy, bạn có thể chuyển sang Bước 3: Thông báo đẩy một cách an toàn

Tổng quat

Các thành phần cần thiết cho Thông báo đẩy:

  1. API thông báo: API này được sử dụng để hiển thị lời nhắc thông báo cho người dùng giống như trong trường hợp thiết bị di động.

  2. API đẩy: API này được sử dụng để nhận thông báo đẩy từ máy chủ.

đẩy_ tổng quan

Các bước liên quan đến việc thực hiện thông báo đẩy cục bộ:

  1. Yêu cầu sự cho phép của người dùng bằng cách sử dụng phương thức requestPermission API thông báo .

  2. Nếu được phép:

    • Xuất sắc! Bây giờ chúng tôi sử dụng nhân viên dịch vụ của mình để đăng ký Dịch vụ đẩy bằng API đẩy.

    • Nhân viên phục vụ của chúng tôi hiện đang lắng nghe các sự kiện đẩy.

    • Khi có sự kiện đẩy, nhân viên dịch vụ đánh thức và sử dụng thông tin từ tin nhắn đẩy để hiển thị thông báo bằng API thông báo.

  3. Nếu sự cho phép bị từ chối: Không có gì nhiều bạn có thể làm ở đây. Vì vậy, hãy chắc chắn rằng bạn xử lý trường hợp này trong mã là tốt.

Toàn bộ mã cho blog này có sẵn tại https://github.com/a7ul/web-push-demo

Bước 0: Nồi hơi

Cho phép tạo một ứng dụng web cơ bản (không có khung).

 mkdir frontend
 cd frontend
 git init
 touch index.html index.css index.js

Bây giờ chúng ta có một cấu trúc dự án cơ bản. Hãy thêm một số mã cơ bản.

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Push Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" type="text/css" media="screen" href="index.css" />
    <script src="index.js"></script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

index.js

console.log('js is working')

index.css

body {
  background-color: beige;
}

Để chạy dự án này cục bộ, hãy sử dụng một máy chủ phát triển đơn giản. Tôi sẽ sử dụng http-servermô-đun npm.

npm install -g http-server

Trong thư mục frontend chạy

http-server

Bây giờ hãy mở trên trình duyệt, bạn sẽ nhận được: http://127.0.0.1:8080/nồi hơi

Ủng hộ

Để làm cho thông báo đẩy hoạt động, chúng tôi cần API đẩy trình duyệt và nhân viên dịch vụ . Hầu như tất cả các trình duyệt hỗ trợ Công nhân Dịch vụ. Hỗ trợ trình duyệt cho Push API cũng tốt . Ngoại trừ Safari (Đã triển khai tùy chỉnh Push API) , tất cả các trình duyệt chính đều hỗ trợ nó.Hỗ trợ API đẩy

Hãy kiểm tra hỗ trợ trong mã của chúng tôi. Thay đổi nội dung của index.js thành: index.js

const check = () => {
  if (!('serviceWorker' in navigator)) {
    throw new Error('No Service Worker support!')
  }
  if (!('PushManager' in window)) {
    throw new Error('No Push API Support!')
  }
}

const main = () => {
  check()
}

main()

Nếu đầu ra trong bảng điều khiển trình duyệt không hiển thị bất kỳ lỗi nào tại thời điểm này, bạn tốt để đi.

Bước 1: Đăng ký một nhân viên dịch vụ và được phép thông báo

Để đăng ký một nhân viên dịch vụ chạy trong nền: Đầu tiên chúng tôi thêm một tệp js mới service.js : Điều này sẽ chứa tất cả mã nhân viên dịch vụ của chúng tôi sẽ chạy trên một luồng riêng biệt.

dịch vụ

console.log('Hello from service worker')

Bây giờ sửa đổi index.js frontend thành:

const check = () => {
    ....
    ....
}

// I added a function that can be used to register a service worker.
const registerServiceWorker = async () => {
    const swRegistration = await navigator.serviceWorker.register('service.js'); //notice the file name
    return swRegistration;
}


const main = async () => { //notice I changed main to async function so that I can use await for registerServiceWorker
    check();
    const swRegistration = await registerServiceWorker();
}

main();

Hãy chạy và xem:nhân viên đăng ký dịch vụ

Vì nhân viên dịch vụ chỉ được đăng ký một lần, Nếu bạn làm mới ứng dụng bằng nút làm mới trình duyệt, bạn sẽ không gặp Hello from service workerlại.

Bạn có thể gọi register () mỗi khi tải trang mà không cần quan tâm; trình duyệt sẽ tìm hiểu xem nhân viên dịch vụ đã đăng ký hay chưa và xử lý nó cho phù hợp.

Vì nhân viên phục vụ có định hướng sự kiện, nếu chúng tôi cần làm điều gì đó hữu ích với nhân viên phục vụ, chúng tôi sẽ cần lắng nghe các sự kiện bên trong nó.

Một sự tinh tế với phương thức register () là vị trí của tệp công nhân dịch vụ. Bạn sẽ nhận thấy trong trường hợp này rằng tệp nhân viên dịch vụ nằm ở thư mục gốc của tên miền. Điều này có nghĩa là phạm vi của nhân viên dịch vụ sẽ là toàn bộ nguồn gốc. Nói cách khác, nhân viên dịch vụ này sẽ nhận các sự kiện tìm nạp cho mọi thứ trên miền này. Nếu chúng tôi đăng ký tệp nhân viên dịch vụ tại /example/sw.js, thì nhân viên dịch vụ sẽ chỉ thấy các sự kiện tìm nạp cho các trang có URL bắt đầu bằng / example / (ví dụ / example / page1 /, / example / page2 /).

Mẹo gỡ lỗi cho nhân viên phục vụ

  • Nếu bạn đang sử dụng các công cụ phát triển của Chrome: Bạn có thể đi tới Tab Ứng dụng> Công nhân dịch vụ . Tại đây bạn có thể hủy đăng ký nhân viên dịch vụ và làm mới ứng dụng một lần nữa. Đối với mục đích gỡ lỗi, tôi sẽ đề nghị bạn kích hoạt cập nhật trên hộp kiểm tải lại ở trên cùng để tránh hủy đăng ký thủ công mỗi khi bạn thay đổi tệp nhân viên dịch vụ. Hướng dẫn chi tiết hơn tại đây .

  • Nếu bạn đang sử dụng Firefox: Trên thanh Menu, hãy chuyển đến Công cụ> Nhà phát triển web> Nhân viên dịch vụ hoặc nhập vào thanh URL: about: gỡ lỗi # worker . Điều này sẽ cho bạn thấy danh sách của tất cả các nhân viên dịch vụ.

Giấy phép thông báo

Để hiển thị thông báo web cho người dùng, ứng dụng web cần phải được sự cho phép của người dùng. Sửa đổi index.js frontend thành:

....
....

const registerServiceWorker = async () => {
   ....
   ....
}

const requestNotificationPermission = async () => {
    const permission = await window.Notification.requestPermission();
    // value of permission can be 'granted', 'default', 'denied'
    // granted: user has accepted the request
    // default: user has dismissed the notification permission popup by clicking on x
    // denied: user has denied the request.
    if(permission !== 'granted'){
        throw new Error('Permission not granted for Notification');
    }
}

const main = async () => {
    check();
    const swRegistration = await registerServiceWorker();
    const permission =  await requestNotificationPermission();
}

main();

Hãy làm mới và xem những gì sẽ xảy ra.nhắc nhở thông báo

Lưu ý: Chúng tôi đang xin phép thông báo trong chức năng chính vì đây là ứng dụng demo. Nhưng lý tưởng nhất, chúng ta không nên làm điều đó ở đây vì nó chiếm UX xấu. Thêm chi tiết về nơi bạn nên gọi nó trong một ứng dụng sản xuất tại đây .

Một cách tiếp cận khác ở đây sẽ là thêm một nút yêu cầu người dùng đăng ký thông báo và sau đó khi nhấp vào nút đó, chúng tôi sẽ hiển thị lời nhắc thông báo.

Bạn cũng có thể nhận được giá trị của sự cho phép thông qua

console.log(Notification.permission)
// This will output: granted, default or denied

Điều này có thể được sử dụng để ẩn nút nếu người dùng đã trả lời lời nhắc thông báo (nếu giá trị không phải'mặc định' ).

Bước 2: Thông báo địa phương

Bây giờ chúng tôi có sự cho phép từ người dùng. Chúng ta có thể tiếp tục và thử cửa sổ bật lên thông báo. Để làm điều đó sửa đổi index.js lối vào

....
....

const requestNotificationPermission = async () => {
   ....
   ....
}

const showLocalNotification = (title, body, swRegistration) => {
    const options = {
        body,
        // here you can add more properties like icon, image, vibrate, etc.
    };
    swRegistration.showNotification(title, options);

}

const main = async () => {
    check();
    const swRegistration = await registerServiceWorker();
    const permission =  await requestNotificationPermission();
    showLocalNotification('This is title', 'this is the message', swRegistration);
}

main();

Các giá trị có thể của Tùy chọn thông báo:

const options = {
  "//": "Visual Options",
  "body": "<String>",
  "icon": "<URL String>",
  "image": "<URL String>",
  "badge": "<URL String>",
  "vibrate": "<Array of Integers>",
  "sound": "<URL String>",
  "dir": "<String of 'auto' | 'ltr' | 'rtl'>",

  "//": "Behavioural Options",
  "tag": "<String>",
  "data": "<Anything>",
  "requireInteraction": "<boolean>",
  "renotify": "<Boolean>",
  "silent": "<Boolean>",

  "//": "Both Visual & Behavioural Options",
  "actions": "<Array of Strings>",

  "//": "Information Option. No visual affect.",
  "timestamp": "<Long>"
}

Chi tiết của mỗi và mọi tài sản được liệt kê ở đây

Chủ đề chính và chủ đề Công nhân dịch vụ

Chủ đề chính : Chủ đề JS chính chạy khi chúng tôi đang duyệt một trang web bằng javascript. Chủ đề Công nhân Dịch vụ : Đây là một chủ đề javascript độc lập chạy trên nền và có thể chạy ngay cả khi trang đã bị đóng.

Nếu bạn nhận thấy chúng tôi chưa sử dụng nhân viên dịch vụ của chúng tôi cho đến bây giờ cho bất kỳ mục đích nào khác ngoài việc đăng ký. Đối với Thông báo đẩy, luồng chính của JS chỉ nên được sử dụng cho tất cả các nội dung liên quan đến giao diện người dùng như thay đổi các thành phần DOM hoặc hỏi người dùng, các quyền để hiển thị thông báo. Lưu ý ở đây là tôi đang hiển thị thông báo từ luồng chính (index.js). Lý tưởng nhất là chúng tôi sẽ không làm điều này trong chủ đề chính. Hãy nhớ rằng thông báo chỉ hữu ích cho việc thu hút người dùng quay lại ứng dụng của bạn (tức là khi trang không mở). Nếu bạn hiển thị thông báo khi người dùng ở trên trang của bạn, thì người dùng thực sự bị phân tâm.

Chủ đề công nhân (nhân viên phục vụ) nên được sử dụng để chờ tin nhắn / sự kiện đẩy và hiển thị các thông báo. Điều này là do không cần thiết phải mở trang thực sự trên trình duyệt tại thời điểm sự kiện đẩy đến (có nghĩa là luồng js chính không tồn tại trong trường hợp này).

Do đó, trong các giai đoạn thực tế, chúng tôi sẽ gọi showNotification từ tệp service.js . Chúng tôi sẽ làm điều đó khi chúng tôi thực hiện các thông báo từ xa. Ở đây tôi chỉ muốn cho bạn thấy cuộc gọi showNotification trông như thế nào.

show_local_notification

Bước 3: Đẩy thông báo

TLD

  • Chúng tôi đăng ký nhân viên dịch vụ của mình để đẩy các sự kiện từ Dịch vụ đẩy của trình duyệt.

  • Chúng tôi sẽ gửi tin nhắn đến dịch vụ đẩy từ phụ trợ của chúng tôi.

  • Sự kiện đẩy từ dịch vụ đẩy có chứa thông báo từ phụ trợ của chúng tôi sẽ đến trình duyệt.

  • Chúng tôi sử dụng tin nhắn từ dịch vụ đẩy để hiển thị thông báo.

dịch vụ đẩy tổng quan

Vậy dịch vụ đẩy là gì?

Dịch vụ đẩy nhận được yêu cầu mạng, xác thực nó và gửi thông báo đẩy đến trình duyệt thích hợp. Nếu trình duyệt ngoại tuyến, tin nhắn sẽ được xếp hàng cho đến khi trình duyệt trực tuyến.

Mỗi trình duyệt có thể sử dụng bất kỳ dịch vụ đẩy nào họ muốn, đó là điều mà các nhà phát triển không kiểm soát được. Đây không phải là vấn đề vì mọi dịch vụ đẩy đều yêu cầu cùng một lệnh gọi API. Có nghĩa là bạn không cần phải quan tâm dịch vụ đẩy là ai. Bạn chỉ cần đảm bảo rằng lệnh gọi API của bạn hợp lệ.

Để biết thêm: Đọc lên ở đây

Trước khi chúng tôi tiến hành

Cho phép làm sạch mã của chúng tôi một chút theo khuyến nghị ở trên. Hãy chắc chắn rằng các tập tin của chúng tôi trông như thế này ngay bây giờ:

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Push Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" type="text/css" media="screen" href="index.css" />
    <script src="index.js"></script>
  </head>

  <body>
    <h1>Hello World</h1>
    <button id="permission-btn" onclick="main()">Ask Permission</button>
  </body>
</html>

index.js

const check = () => {
  if (!('serviceWorker' in navigator)) {
    throw new Error('No Service Worker support!')
  }
  if (!('PushManager' in window)) {
    throw new Error('No Push API Support!')
  }
}

const registerServiceWorker = async () => {
  const swRegistration = await navigator.serviceWorker.register('service.js')
  return swRegistration
}

const requestNotificationPermission = async () => {
  const permission = await window.Notification.requestPermission()
  // value of permission can be 'granted', 'default', 'denied'
  // granted: user has accepted the request
  // default: user has dismissed the notification permission popup by clicking on x
  // denied: user has denied the request.
  if (permission !== 'granted') {
    throw new Error('Permission not granted for Notification')
  }
}

const main = async () => {
  check()
  const swRegistration = await registerServiceWorker()
  const permission = await requestNotificationPermission()
}
// main(); we will not call main in the beginning.

dịch vụ

self.addEventListener('activate', async () => {
  // This will be called only once when the service worker is activated.
  console.log('service worker activate')
})

Hãy chạy cái này: Bây giờ bạn sẽ thấy một nút trên trang có nội dung 'Hỏi quyền'. Nhấp vào nó sẽ gọi chức năng chính của chúng tôi.

Nghe thông báo từ xa

Bây giờ chúng tôi sẽ chỉ tập trung vào tệp nhân viên dịch vụ kể từ bây giờ trừ khi có quy định khác. Để nghe / đăng ký tin nhắn đẩy từ xa, chúng tôi cần ứng dụng web trình duyệt để đăng ký dịch vụ đẩy.

dịch vụ

self.addEventListener('activate', async () => {
  // This will be called only once when the service worker is activated.
  try {
    const options = {}
    const subscription = await self.registration.pushManager.subscribe(options)
    console.log(JSON.stringify(subscription))
  } catch (err) {
    console.log('Error', err)
  }
})

Bây giờ, hủy đăng ký nhân viên dịch vụ và làm mới ứng dụng web. Nhấp vào nút Yêu cầu cho phép.

Trong bảng điều khiển Firefox, bạn sẽ thấy:

{
  "endpoint":"https://updates.push.services.mozilla.com:443/wpush/v1/<some_id>",
  "keys":{
    "auth":"<some key>",
    "p256dh":"<some key>"
  }
}

Giá trị đăng ký sẽ là null nếu ứng dụng web chưa đăng ký dịch vụ đẩy.

Trong bảng điều khiển Chrome, bạn sẽ thấy một lỗi:

Error DOMException: Registration failed - missing applicationServerKey, and gcm_sender_id not found in manifest

Điều này là do, có hai tham số tùy chọn applicationServerKey (còn được gọi là khóa công khai VAPID ) và userVisibleOnly là các tùy chọn trong firefox nhưng là các tham số bắt buộc trong chrome.

userVisibleOnly : Bạn cần gửi userVisibleOnly là true luôn trong chrome. Tham số này hạn chế nhà phát triển sử dụng tin nhắn đẩy cho thông báo. Đó là nhà phát triển sẽ không thể sử dụng tin nhắn đẩy để gửi dữ liệu đến máy chủ một cách im lặng mà không hiển thị thông báo. Hiện tại nó phải được đặt thành đúng nếu không bạn sẽ nhận được lỗi từ chối cấp phép. Về bản chất, tin nhắn đẩy im lặng không được hỗ trợ tại thời điểm này do những lo ngại về quyền riêng tư và bảo mật.

VAPID : Nhận dạng máy chủ ứng dụng tự nguyện cho Web Push là thông số cho phép máy chủ phụ trợ (máy chủ ứng dụng của bạn) tự nhận dạng với Dịch vụ đẩy (dịch vụ dành riêng cho trình duyệt). Đây là một biện pháp bảo mật nhằm ngăn chặn bất kỳ ai khác gửi tin nhắn đến người dùng của ứng dụng.

Làm thế nào để các khóa VAPID cho phép bảo mật?

Nói ngắn gọn:

  1. Bạn tạo một bộ khóa riêng và khóa chung (khóa VAPID) tại máy chủ ứng dụng (ví dụ: máy chủ NodeJS phụ trợ của bạn).

  2. Khóa chung sẽ được gửi đến dịch vụ đẩy khi ứng dụng lối vào cố gắng đăng ký dịch vụ đẩy. Bây giờ dịch vụ đẩy đã biết khóa chung từ máy chủ ứng dụng của bạn (phụ trợ). Phương thức đăng ký sẽ trả về một điểm cuối duy nhất cho ứng dụng web frontend của bạn. Bạn sẽ lưu trữ điểm cuối độc đáo này tại máy chủ ứng dụng phụ trợ.

  3. Từ máy chủ ứng dụng, bạn sẽ đạt yêu cầu API đến điểm cuối dịch vụ đẩy duy nhất mà bạn vừa lưu. Trong yêu cầu API này, bạn sẽ ký một số thông tin trong tiêu đề Ủy quyền bằng khóa riêng. Điều này cho phép dịch vụ đẩy xác minh rằng yêu cầu đến từ máy chủ ứng dụng chính xác.

  4. Sau khi xác thực tiêu đề, dịch vụ đẩy sẽ gửi tin nhắn đến ứng dụng web frontend.

PS: Để có được giá trị của thuê bao đẩy, bạn cũng có thể gọi (trong tệp nhân viên dịch vụ của bạn)

const subscription = await self.registration.pushManager.getSubscription()

Tạo khóa VAPID

Để tạo một bộ khóa VAPID riêng tư và công khai:

  1. npm install -g web-push
  2. web-push generate-vapid-keys

Điều này sẽ in ra một cái gì đó như thế này:

=======================================

Public Key:
BJ5IxJBWdeqFDJTvrZ4wNRu7UY2XigDXjgiUBYEYVXDudxhEs0ReOJRBcBHsPYgZ5dyV8VjyqzbQKS8V7bUAglk

Private Key:
ERIZmc5T5uWGeRxedxu92k3HnpVwy_RCnQfgek1x2Y4

=======================================

KHÔNG SỬ DỤNG CÁC KHÓA TRÊN, TẠO RA RIÊNG CỦA BẠN.

Bạn chỉ cần tạo cái này một lần. Giữ khóa riêng của bạn an toàn. Khóa công khai có thể được lưu trữ trên ứng dụng web frontend.

Bây giờ, hãy quay lại service.js

// urlB64ToUint8Array is a magic function that will encode the base64 public key
// to Array buffer which is needed by the subscription option
const urlB64ToUint8Array = base64String => {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
  const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
  const rawData = atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

self.addEventListener('activate', async () => {
  // This will be called only once when the service worker is activated.
  try {
    const applicationServerKey = urlB64ToUint8Array(
      'BJ5IxJBWdeqFDJTvrZ4wNRu7UY2XigDXjgiUBYEYVXDudxhEs0ReOJRBcBHsPYgZ5dyV8VjyqzbQKS8V7bUAglk'
    )
    const options = { applicationServerKey, userVisibleOnly: true }
    const subscription = await self.registration.pushManager.subscribe(options)
    console.log(JSON.stringify(subscription))
  } catch (err) {
    console.log('Error', err)
  }
})

Bây giờ làm mới và hủy đăng ký / đăng ký lại nhân viên dịch vụ. Nhấp vào trên Hỏi Hỏi Quyền. Bạn sẽ thấy trong bảng điều khiển của bạn.

{
  "endpoint" : "https://fcm.googleapis.com/fcm/send/<some id>",
  "expirationTime" : null,
  "keys": {
    "p256dh" : "<some key>",
    "auth" : "<some key>"
  }
}

Điều này khá giống với cái chúng tôi nhận được từ thuê bao firefox. Lưu ý rằng fcm . googleapis . com (firebase) là dịch vụ đẩy được sử dụng bởi google chrome trong khi firefox sử dụng các bản cập nhật . đẩy . dịch vụ . mozilla . com . Các Đẩy Web Nghị định thư chuẩn hoá Push API. Điều này cho phép mỗi trình duyệt sử dụng dịch vụ đẩy mà nó thấy phù hợp miễn là nó phù hợp với cùng một API như được đề cập trong thông số kỹ thuật.

Bây giờ khi chúng tôi nhận được giá trị đăng ký, chúng tôi cần lưu nó trong máy chủ phụ trợ (máy chủ ứng dụng). Để làm như vậy, chúng ta nên tạo một điểm cuối API trên máy chủ ứng dụng của chúng tôi sẽ đăng ký và lưu trữ nó trong cơ sở dữ liệu / bộ đệm. Bây giờ hãy xem xét rằng API cần nhấn là POST http: // localhost: 3000 / save-đăng ký với phần thân yêu cầu có chứa toàn bộ đối tượng đăng ký. Chúng tôi sẽ sớm tạo API này trên máy chủ phụ trợ.

Vì vậy, hãy thay đổi tệp service.js để thêm chức năng để lưu thuê bao.

// urlB64ToUint8Array is a magic function that will encode the base64 public key
// to Array buffer which is needed by the subscription option
const urlB64ToUint8Array = base64String => {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
  const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
  const rawData = atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

// saveSubscription saves the subscription to the backend
const saveSubscription = async subscription => {
  const SERVER_URL = 'http://localhost:4000/save-subscription'
  const response = await fetch(SERVER_URL, {
    method: 'post',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
  return response.json()
}

self.addEventListener('activate', async () => {
  // This will be called only once when the service worker is activated.
  try {
    const applicationServerKey = urlB64ToUint8Array(
      'BJ5IxJBWdeqFDJTvrZ4wNRu7UY2XigDXjgiUBYEYVXDudxhEs0ReOJRBcBHsPYgZ5dyV8VjyqzbQKS8V7bUAglk'
    )
    const options = { applicationServerKey, userVisibleOnly: true }
    const subscription = await self.registration.pushManager.subscribe(options)
    const response = await saveSubscription(subscription)
    console.log(response)
  } catch (err) {
    console.log('Error', err)
  }
})

Bước tiếp theo là lưu đăng ký tại phụ trợ và sau đó sử dụng đăng ký để gửi tin nhắn đẩy đến ứng dụng lối vào thông qua dịch vụ đẩy. Nhưng trước đó, chúng tôi cũng sẽ cần lắng nghe sự kiện đẩy từ phần phụ trợ trên ứng dụng frontend của chúng tôi.

Nghe sự kiện Push

Trong service.js của bạn thêm

self.addEventListener('push', function(event) {
  if (event.data) {
    console.log('Push event!! ', event.data.text())
  } else {
    console.log('Push event but no data')
  }
})

Thời gian cho phụ trợ (NodeJS)

Hãy tạo một dự án js express cơ bản.

  1.   mkdir backend
      cd backend
      npm init
      npm install --save express
  2. Bây giờ, tạo một backend index.js tập tin

    const express = require('express')
    const cors = require('cors')
    const bodyParser = require('body-parser')
    
    const app = express()
    app.use(cors())
    app.use(bodyParser.json())
    
    const port = 4000
    
    app.get('/', (req, res) => res.send('Hello World!'))
    app.listen(port, () => console.log(`Example app listening on port ${port}!`))
  3. Chạy máy chủ nút như hiển thị bên dưới và mở ra localhost:4000trên trình duyệt

   node index.js

Bạn sẽ thấy một cái gì đó như thế này: backend_boilerplate_ yet

Lưu đăng ký trên phụ trợ Hãy tạo điểm cuối đăng ký lưu của chúng tôi trên máy chủ cấp tốc. Tất cả những gì chúng ta cần làm là lấy lại đăng ký từ frontend và lưu trữ nó ở đâu đó một cách an toàn trong phần phụ trợ. Chúng tôi sẽ cần đăng ký này sau để gửi tin nhắn đẩy đến trình duyệt của người dùng.

Thực hiện các thay đổi đối với tệp index.js phụ trợ của bạn :

const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')

const app = express()
app.use(cors())
app.use(bodyParser.json())

const port = 4000

app.get('/', (req, res) => res.send('Hello World!'))

const dummyDb = { subscription: null } //dummy in memory store

const saveToDatabase = async subscription => {
  // Since this is a demo app, I am going to save this in a dummy in memory store. Do not do this in your apps.
  // Here you should be writing your db logic to save it.
  dummyDb.subscription = subscription
}

// The new /save-subscription endpoint
app.post('/save-subscription', async (req, res) => {
  const subscription = req.body
  await saveToDatabase(subscription) //Method to save the subscription to Database
  res.json({ message: 'success' })
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

Bây giờ chúng tôi đã lưu đăng ký tại phần phụ trợ, bước tiếp theo là cuối cùng gửi tin nhắn đẩy đến frontend.

Tạo thông báo từ xa

Để tạo thông báo đẩy, chúng tôi sẽ sử dụng tính năng đẩy thư viện npm giúp chúng tôi mã hóa tin nhắn, đặt tham số chính xác, hỗ trợ kế thừa cho trình duyệt, v.v. Nếu bạn cần tìm ra cách thực hiện điều này bằng các ngôn ngữ khác ngoài Javascript (NodeJS ), bạn có thể xem mã nguồn web-đẩy.

Trong dự án phụ trợ của bạn, hãy cài đặt mô-đun npm web-đẩy cục bộ.

npm install --save web-push

Trong phần phụ trợ index.js thêm vào như sau:

...
...
...
const webpush = require('web-push') //requiring the web-push module
...
...
...
const app = express();
...
...
...

const vapidKeys = {
  publicKey:
    'BJ5IxJBWdeqFDJTvrZ4wNRu7UY2XigDXjgiUBYEYVXDudxhEs0ReOJRBcBHsPYgZ5dyV8VjyqzbQKS8V7bUAglk',
  privateKey: 'ERIZmc5T5uWGeRxedxu92k3HnpVwy_RCnQfgek1x2Y4',
}

//setting our previously generated VAPID keys
webpush.setVapidDetails(
  'mailto:myuserid@email.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey
)

//function to send the notification to the subscribed device
const sendNotification = (subscription, dataToSend='') => {
  webpush.sendNotification(subscription, dataToSend)
}

Giải thích webpush.setVapidDetails có ba đối số.

  • Đối số đầu tiên cần phải là URL hoặc địa chỉ email đến email.

  • Đối số thứ hai là khóa công khai VAPID mà chúng ta đã tạo trước đó.

  • Đối số thứ ba là khóa riêng VAPID.

Để kiểm tra điều này, hãy thêm một tuyến / gửi thông báo khác đến máy chủ tốc hành NodeJS của chúng tôi. Tệp index.js phụ trợ hoàn chỉnh sẽ trông giống như thế này.

const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')
const webpush = require('web-push')

const app = express()
app.use(cors())
app.use(bodyParser.json())

const port = 4000

app.get('/', (req, res) => res.send('Hello World!'))

const dummyDb = { subscription: null } //dummy in memory store

const saveToDatabase = async subscription => {
  // Since this is a demo app, I am going to save this in a dummy in memory store. Do not do this in your apps.
  // Here you should be writing your db logic to save it.
  dummyDb.subscription = subscription
}

// The new /save-subscription endpoint
app.post('/save-subscription', async (req, res) => {
  const subscription = req.body
  await saveToDatabase(subscription) //Method to save the subscription to Database
  res.json({ message: 'success' })
})

const vapidKeys = {
  publicKey:
    'BJ5IxJBWdeqFDJTvrZ4wNRu7UY2XigDXjgiUBYEYVXDudxhEs0ReOJRBcBHsPYgZ5dyV8VjyqzbQKS8V7bUAglk',
  privateKey: 'ERIZmc5T5uWGeRxedxu92k3HnpVwy_RCnQfgek1x2Y4',
}

//setting our previously generated VAPID keys
webpush.setVapidDetails(
  'mailto:myuserid@email.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey
)

//function to send the notification to the subscribed device
const sendNotification = (subscription, dataToSend) => {
  webpush.sendNotification(subscription, dataToSend)
}

//route to test send notification
app.get('/send-notification', (req, res) => {
  const subscription = dummyDb.subscription //get subscription from your databse here.
  const message = 'Hello World'
  sendNotification(subscription, message)
  res.json({ message: 'message sent' })
})

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

Hãy thử nó !!

demo_run

Cho phép mở http://127.0.0.1:8080 trong trình duyệt

Hãy chắc chắn rằng bạn bắt đầu từ đá phiến sạch. Hủy đăng ký nhân viên dịch vụ, xóa bộ nhớ cache, v.v ... Bây giờ hãy nhấn xin phép và bạn sẽ thấy thông báo bảng điều khiển thành công. Điều đó có nghĩa là đăng ký đẩy của chúng tôi đã được lưu trong phụ trợ.

Bây giờ hãy mở tuyến phụ trợ http: // localhost: 4000 / gửi thông báo trong một tab khác trong trình duyệt của bạn (vì đó là API GET). Bạn sẽ thấy thông báo đẩy thế giới hello trên bảng điều khiển của ứng dụng frontend.

demo_run_browsftimeconsole_chromdemo_run_browsftimeconsole_firefox

Thêm lần chạm cuối cùng: Hiển thị thông báo đẩy dưới dạng thông báo

Mở frontend service.js của bạn và thêm vào như sau:

...
...
...
self.addEventListener("push", function(event) {
  if (event.data) {
    console.log("Push event!! ", event.data.text());
    showLocalNotification("Yolo", event.data.text(), self.registration);
  } else {
    console.log("Push event but no data");
  }
});

const showLocalNotification = (title, body, swRegistration) => {
  const options = {
    body
    // here you can add more properties like icon, image, vibrate, etc.
  };
  swRegistration.showNotification(title, options);
};

Tệp service.js cuối cùng trông như thế này:

// urlB64ToUint8Array is a magic function that will encode the base64 public key
// to Array buffer which is needed by the subscription option
const urlB64ToUint8Array = base64String => {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
  const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
  const rawData = atob(base64)
  const outputArray = new Uint8Array(rawData.length)
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

const saveSubscription = async subscription => {
  const SERVER_URL = 'http://localhost:4000/save-subscription'
  const response = await fetch(SERVER_URL, {
    method: 'post',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
  return response.json()
}

self.addEventListener('activate', async () => {
  // This will be called only once when the service worker is installed for first time.
  try {
    const applicationServerKey = urlB64ToUint8Array(
      'BJ5IxJBWdeqFDJTvrZ4wNRu7UY2XigDXjgiUBYEYVXDudxhEs0ReOJRBcBHsPYgZ5dyV8VjyqzbQKS8V7bUAglk'
    )
    const options = { applicationServerKey, userVisibleOnly: true }
    const subscription = await self.registration.pushManager.subscribe(options)
    const response = await saveSubscription(subscription)
    console.log(response)
  } catch (err) {
    console.log('Error', err)
  }
})

self.addEventListener('push', function(event) {
  if (event.data) {
    console.log('Push event!! ', event.data.text())
    showLocalNotification('Yolo', event.data.text(), self.registration)
  } else {
    console.log('Push event but no data')
  }
})

const showLocalNotification = (title, body, swRegistration) => {
  const options = {
    body,
    // here you can add more properties like icon, image, vibrate, etc.
  }
  swRegistration.showNotification(title, options)
}

Bây giờ hãy chạy nó lần cuối. Một lần nữa, hủy đăng ký mọi thứ, dọn sạch bộ đệm và mở lại tab trình duyệt. Làm thủ tục như trước.

chrome_demo_notif firefox_demo_notif

Last updated