Hướng dẫn API đẩy

API đẩy cho phép ứng dụng web nhận thông báo do máy chủ đẩy, ngay cả khi ứng dụng web hiện không mở trong trình duyệt hoặc không chạy trên thiết bị.

API đẩy cho phép ứng dụng web nhận thông báo do máy chủ đẩy, ngay cả khi ứng dụng web hiện không mở trong trình duyệt hoặc không chạy trên thiết bị

Sử dụng API Đẩy, bạn có thể gửi tin nhắn cho người dùng của mình, đẩy họ từ máy chủ đến máy khách, ngay cả khi người dùng không duyệt trang web.

Điều này cho phép bạn gửi thông báo và cập nhật nội dung, mang lại cho bạn khả năng có được nhiều khán giả gắn bó hơn.

Điều này là rất lớn vì một trong những trụ cột còn thiếu của web di động, so với các ứng dụng gốc, là khả năng nhận thông báo cùng với hỗ trợ ngoại tuyến.

Nó có được hỗ trợ tốt không?

Push API là một bổ sung gần đây cho các API của trình duyệt và nó hiện được hỗ trợ bởi Chrome (Máy tính để bàn và Di động), Firefox và Opera kể từ năm 2016, Edge kể từ phiên bản 17 (đầu năm 2018). Xem thêm về trạng thái hiện tại của hỗ trợ trình duyệt tạihttps://caniuse.com/#feat=push-api

IE không hỗ trợ nó vàSafari có cách triển khai riêng.

Vì Chrome và Firefox hỗ trợ nó, khoảng 60% người dùng duyệt trên máy tính để bàn có quyền truy cập vào nó, vì vậy nó kháan toànđể sử dụng.

Làm thế nào nó hoạt động

Tổng quat

Khi người dùng truy cập ứng dụng web của bạn, bạn có thể kích hoạt một bảng yêu cầu quyền gửi các bản cập nhật. ANhân viên phục vụđược cài đặt và hoạt động trong nền sẽ lắng ngheSự kiện đẩy.

Đẩy và Thông báo là một khái niệm và API riêng biệt, đôi khi bị trộn lẫn vìthông báo đẩythuật ngữ được sử dụng trong iOS. Về cơ bản, API thông báo được gọi khi nhận được sự kiện đẩy bằng API đẩy.

Của bạnngười phục vụgửi thông báo cho khách hàng và Nhân viên dịch vụ, nếu được cho phép, sẽ nhận đượcsự kiện đẩy. Nhân viên Dịch vụ phản ứng với sự kiện này bằng cáchkích hoạt một thông báo.

Nhận được sự cho phép của người dùng

Bước đầu tiên khi làm việc với API Push là người dùng có quyền nhận dữ liệu từ bạn.

Nhiều trang web triển khai bảng điều khiển này không tốt, hiển thị bảng điều khiển này khi tải trang đầu tiên. Người dùng vẫn chưa tin rằng nội dung của bạn là tốt và họ sẽ từ chối cấp phép. Làm điều đó một cách khôn ngoan.

Có 6 bước:

  1. Kiểm tra xem Nhân viên dịch vụ có được hỗ trợ không
  2. Kiểm tra xem API Push có được hỗ trợ không
  3. Đăng ký Nhân viên Dịch vụ
  4. Yêu cầu quyền từ người dùng
  5. Đăng ký người dùng và nhận đối tượng PushSubscription
  6. Gửi đối tượng PushSubscription đến máy chủ của bạn

Kiểm tra xem Nhân viên dịch vụ có được hỗ trợ không

if (!('serviceWorker' in navigator)) {
  // Service Workers are not supported. Return
  return
}

Kiểm tra xem API Push có được hỗ trợ không

if (!('PushManager' in window)) {
  // The Push API is not supported. Return
  return
}

Đăng ký Nhân viên Dịch vụ

Mã này đăng ký Service Worker nằm trongworker.jstệp được đặt trong tên miền gốc:

window.addEventListener('load', () => {
  navigator.serviceWorker.register('/worker.js')
  .then((registration) => {
    console.log('Service Worker registration completed with scope: ',
      registration.scope)
  }, (err) => {
    console.log('Service Worker registration failed', err)
  })
})

Để biết thêm về cách làm việc chi tiết của Nhân viên dịch vụ, hãy xemHướng dẫn Nhân viên Dịch vụ.

Yêu cầu quyền từ người dùng

Bây giờ Service worker đã được đăng ký, bạn có thể yêu cầu quyền.

API để thực hiện việc này đã thay đổi theo thời gian và nó đã chuyển từ việc chấp nhận một hàm gọi lại làm tham số để trả về mộtLời hứa, phá vỡ khả năng tương thích ngược và chuyển tiếp, và chúng ta cần làmcả haivì chúng tôi không biết phương pháp tiếp cận nào được trình duyệt của người dùng triển khai.

Mã như sau, đang gọiNotification.requestPermission().

const askPermission = () => {
  return new Promise((resolve, reject) => {
    const permissionResult = Notification.requestPermission((result) => {
      resolve(result)
    })
    if (permissionResult) {
      permissionResult.then(resolve, reject)
    }
  })
  .then((permissionResult) => {
    if (permissionResult !== 'granted') {
      throw new Error('Permission denied')
    }
  })
}

CácpermissionResultvalue là một chuỗi, có thể có giá trị là: -granted-default-denied

Mã này khiến trình duyệt hiển thị hộp thoại quyền:

The browser permission dialogue

Nếu người dùng nhấp vào Chặn, bạn sẽ không thể yêu cầu quyền của người dùng nữa, trừ khi họ truy cập và bỏ chặn trang web theo cách thủ công trong bảng cài đặt nâng cao trong trình duyệt (rất khó xảy ra).

Đăng ký người dùng và nhận đối tượng PushSubscription

Nếu người dùng đã cho phép chúng tôi, chúng tôi có thể đăng ký và bằng cách gọiregistration.pushManager.subscribe().

const APP_SERVER_KEY = 'XXX'

window.addEventListener(‘load’, () => { navigator.serviceWorker.register(’/worker.js’) .then((registration) => { askPermission().then(() => { const options = { userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(APP_SERVER_KEY) } return registration.pushManager.subscribe(options) }).then((pushSubscription) => { // we got the pushSubscription object } }, (err) => { console.log(‘Service Worker registration failed’, err) }) })

APP_SERVER_KEYlà một chuỗi - được gọi làKhóa máy chủ ứng dụnghoặc làKhóa VAPID- xác định khóa công khai của ứng dụng, một phần của cặp khóa công khai / riêng tư.

Nó sẽ được sử dụng như một phần của xác thực vì lý do bảo mật xảy ra để đảm bảo rằng bạn (và chỉ bạn chứ không phải ai khác) có thể gửi thông báo đẩy trở lại người dùng.

Gửi đối tượng PushSubscription đến máy chủ của bạn

Trong đoạn mã trước, chúng tôi cópushSubscriptionđối tượng, chứa tất cả những gì chúng ta cần để gửi thông điệp đẩy tới người dùng. Chúng tôi cần gửi thông tin này đến máy chủ của mình để có thể gửi thông báo sau này.

Trước tiên, chúng tôi tạo một biểu diễn JSON của đối tượng

const subscription = JSON.stringify(pushSubscription)

và chúng tôi có thể đăng nó lên máy chủ của chúng tôi bằng cách sử dụngAPI tìm nạp:

const sendToServer = (subscription) => {
  return fetch('/api/subscription', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(subscription)
  })
  .then((res) => {
    if (!res.ok) {
      throw new Error('An error occurred')
    }
    return res.json()
  })
  .then((resData) => {
    if (!(resData.data && resData.data.success)) {
      throw new Error('An error occurred')
    }
  })
}

sendToServer(subscription)

Phía máy chủ,/api/subscriptionendpoint nhận được yêu cầu POST và có thể lưu trữ thông tin đăng ký vào bộ nhớ của nó.

Cách hoạt động của phía Máy chủ

Cho đến nay, chúng tôi chỉ nói về phần phía máy khách: nhận được sự cho phép của người dùng để được thông báo trong tương lai.

Còn máy chủ thì sao? Nó phải làm gì, và nó nên tương tác với khách hàng như thế nào?

Các ví dụ phía máy chủ này sử dụng Express.js (http://expressjs.com/) dưới dạng khung HTTP cơ sở, nhưng bạn có thể viết trình xử lý API Đẩy phía máy chủ bằng bất kỳ ngôn ngữ hoặc khung công tác nào

Đăng ký gói đăng ký khách hàng mới

Khi khách hàng gửi đăng ký mới, hãy nhớ rằng chúng tôi đã sử dụng/api/subscriptionĐiểm cuối HTTP POST, gửi chi tiết đối tượng PushSubscription ở định dạng JSON, trong nội dung.

Chúng tôi khởi tạo Express.js:

const express = require('express')
const app = express()

Hàm tiện ích này đảm bảo yêu cầu hợp lệ, có phần thân và thuộc tính điểm cuối, nếu không, nó trả về lỗi cho máy khách:

const isValidSaveRequest = (req, res) => {
  if (!req.body || !req.body.endpoint) {
    res.status(400)
    res.setHeader('Content-Type', 'application/json')
    res.send(JSON.stringify({
      error: {
        id: 'no-endpoint',
        message: 'Subscription must have an endpoint'
      }
    }))
    return false
  }
  return true
}

Chức năng tiện ích tiếp theo lưu đăng ký vào cơ sở dữ liệu, trả về một lời hứa được giải quyết khi việc chèn hoàn tất (hoặc không thành công). CácinsertToDatabasehàm là một trình giữ chỗ, chúng tôi sẽ không đi sâu vào các chi tiết đó ở đây:

const saveSubscriptionToDatabase = (subscription) => {
  return new Promise((resolve, reject) => {
    insertToDatabase(subscription, (err, id) => {
      if (err) {
        reject(err)
        return
      }
  <span style="color:#a6e22e">resolve</span>(<span style="color:#a6e22e">id</span>)
})

}) }

Chúng tôi sử dụng các chức năng đó trong trình xử lý yêu cầu POST bên dưới. Chúng tôi kiểm tra xem yêu cầu có hợp lệ không, sau đó chúng tôi lưu yêu cầu và sau đó chúng tôi trả vềdata.success: truephản hồi lại máy khách hoặc lỗi:

app.post('/api/subscription', (req, res) => {
  if (!isValidSaveRequest(req, res)) {
    return
  }

saveSubscriptionToDatabase(req, res.body) .then((subscriptionId) => { res.setHeader(‘Content-Type’, ‘application/json’) res.send(JSON.stringify({ data: { success: true } })) }) .catch((err) => { res.status(500) res.setHeader(‘Content-Type’, ‘application/json’) res.send(JSON.stringify({ error: { id: ‘unable-to-save-subscription’, message: ‘Subscription received but failed to save it’ } })) }) })

app.listen(3000, () => { console.log(‘App listening on port 3000’) })

Gửi tin nhắn đẩy

Bây giờ máy chủ đã đăng ký ứng dụng khách trong danh sách của nó, chúng ta có thể gửi cho nó các thông điệp Đẩy. Hãy xem cách đó hoạt động như thế nào bằng cách tạo một đoạn mã ví dụ tìm nạp tất cả các đăng ký và gửi Thông báo đẩy cho tất cả chúng cùng một lúc.

Chúng tôi sử dụng một thư viện vìGiao thức đẩy webphức tạpvà lib cho phép chúng ta trừu tượng hóa rất nhiều mã cấp thấp để đảm bảo rằng chúng ta có thể làm việc an toàn và xử lý chính xác mọi trường hợp cạnh.

Ví dụ này sử dụngweb-push Node.jsthư viện (https://github.com/web-push-libs/web-push) để xử lý việc gửi tin nhắn Đẩy

Đầu tiên chúng tôi khởi tạoweb-pushlib và chúng tôi tạo một loạt các khóa riêng tư và công khai, đồng thời đặt chúng làm chi tiết VAPID:

const webpush = require('web-push')
const vapidKeys = webpush.generateVAPIDKeys()

const PUBLIC_KEY = ‘XXX’ const PRIVATE_KEY = ‘YYY’

const vapidKeys = { publicKey: PUBLIC_KEY, privateKey: PRIVATE_KEY }

webpush.setVapidDetails( mailto:[email protected], vapidKeys.publicKey, vapidKeys.privateKey )

Sau đó, chúng tôi thiết lập mộttriggerPush(), chịu trách nhiệm gửi sự kiện đẩy tới máy khách. Nó chỉ gọiwebpush.sendNotification()và bắt bất kỳ lỗi nào. Nếu lỗi trả về mã trạng thái HTTP là410, nghĩa làKhông còn, chúng tôi xóa người đăng ký đó khỏi cơ sở dữ liệu.

const triggerPush = (subscription, dataToSend) => {
  return webpush.sendNotification(subscription, dataToSend)
  .catch((err) => {
    if (err.statusCode === 410) {
      return deleteSubscriptionFromDatabase(subscription._id)
    } else {
      console.log('Subscription is no longer valid: ', err)
    }
  })
}

Chúng tôi không triển khai việc lấy các đăng ký từ cơ sở dữ liệu, nhưng chúng tôi để nó ở dạng sơ khai:

const getSubscriptionsFromDatabase = () => {
  //stub
}

Phần lớn của mã là lệnh gọi lại của yêu cầu ĐĂNG tới/api/pushđiểm cuối:

app.post('/api/push', (req, res) => {
  return getSubscriptionsFromDatabase()
  .then((subscriptions) => {
    let promiseChain = Promise.resolve()
    for (let i = 0; i < subscriptions.length; i++) {
      const subscription = subscriptions[i]
      promiseChain = promiseChain.then(() => {
        return triggerPush(subscription, dataToSend)
      })
    }
    return promiseChain
  })
  .then(() => {
    res.setHeader('Content-Type', 'application/json')
    res.send(JSON.stringify({ data: { success: true } }))
  })
  .catch((err) => {
    res.status(500)
    res.setHeader('Content-Type', 'application/json')
    res.send(JSON.stringify({
      error: {
        id: 'unable-to-send-messages',
        message: `Failed to send the push ${err.message}`
      }
    }))
  })
})

Những gì đoạn mã trên làm là: nó lấy tất cả các đăng ký từ cơ sở dữ liệu, sau đó nó lặp lại trên chúng và nó gọitriggerPush()chức năng chúng tôi đã giải thích trước đây.

Sau khi đăng ký xong, chúng tôi trả về phản hồi JSON thành công, trừ khi xảy ra lỗi và chúng tôi trả về lỗi 500.

Trong thế giới thực…

Không chắc rằng bạn sẽ thiết lập máy chủ Đẩy của riêng mình trừ khi bạn có một trường hợp sử dụng rất đặc biệt, hoặc bạn chỉ muốn tìm hiểu công nghệ hoặc bạn thích tự làm. Thay vào đó, bạn thường muốn sử dụng các nền tảng như OneSignal (https://onesignal.com) xử lý minh bạch các sự kiện Đẩy tới tất cả các loại nền tảng, bao gồm Safari và iOS, miễn phí.

Nhận sự kiện Đẩy

Khi một sự kiện Đẩy được gửi từ máy chủ, làm thế nào để máy khách nhận được nó?

Đó là một bình thườngJavaScriptngười nghe sự kiện, trênpushsự kiện chạy bên trong Service Worker:

self.addEventListener('push', (event) => {
  // data is available in event.data
})

event.datachứaPushMessageDatađối tượng hiển thị các phương thức để truy xuất dữ liệu đẩy được gửi bởi máy chủ, ở định dạng bạn muốn:

  • arrayBuffer (): như mộtArrayBuffervật
  • bãi(): như mộtBãivật
  • json (): phân tích cú pháp làJSON
  • bản văn(): văn bản thô

Bạn sẽ thường sử dụngevent.data.json().

Hiển thị thông báo

Ở đây chúng tôi giao nhau một chút vớiAPI thông báo, nhưng vì một lý do chính đáng, vì một trong những trường hợp sử dụng chính của API Đẩy là hiển thị thông báo.

Bên trong của chúng tôipushtrình xử lý sự kiện trong Service Worker, chúng tôi cần hiển thị thông báo cho người dùng và yêu cầu sự kiện đợi cho đến khi trình duyệt hiển thị nó trước khi chức năng có thể kết thúc. Chúng tôi kéo dài thời gian tồn tại của sự kiện cho đến khi trình duyệt hoàn tất việc hiển thị thông báo (cho đến khi lời hứa đã được giải quyết), nếu không, Service Worker có thể bị dừng giữa chừng khi bạn đang xử lý:

self.addEventListener('push', (event) => {
  const promiseChain = self.registration.showNotification('Hey!')
  event.waitUntil(promiseChain)
})

Thêm thông báo trongHướng dẫn API thông báo.

Tải xuống miễn phí của tôiSổ tay dành cho Người mới bắt đầu JavaScript


Các hướng dẫn khác về trình duyệt: