Gửi thông báo bằng thư viện đẩy của web

Một trong những điểm khó khăn khi làm việc với tính năng đẩy web là việc kích hoạt thông báo đẩy cực kỳ "phiền phức". Để kích hoạt thông báo đẩy, ứng dụng cần thực hiện yêu cầu POST đến một dịch vụ đẩy theo giao thức đẩy web. Để sử dụng tính năng đẩy trên mọi trình duyệt, bạn cần sử dụng VAPID (còn gọi là khoá máy chủ ứng dụng), về cơ bản, bạn phải đặt một tiêu đề có giá trị chứng minh rằng ứng dụng của bạn có thể nhắn tin cho người dùng. Để gửi dữ liệu bằng thông báo đẩy, dữ liệu cần được mã hoá và cần thêm các tiêu đề cụ thể để trình duyệt có thể giải mã chính xác thông báo.

Vấn đề chính khi kích hoạt thông báo đẩy là nếu bạn gặp sự cố, bạn sẽ khó chẩn đoán vấn đề. Điều này đang được cải thiện theo thời gian và hỗ trợ nhiều trình duyệt hơn, nhưng không hề dễ dàng. Vì lý do này, bạn nên sử dụng thư viện để xử lý việc mã hoá, định dạng và kích hoạt thông báo đẩy.

Nếu bạn thực sự muốn tìm hiểu về những việc các thư viện đang làm, chúng tôi sẽ đề cập đến vấn đề này trong phần tiếp theo. Hiện tại, chúng ta sẽ xem xét cách quản lý các gói thuê bao và sử dụng một thư viện đẩy web hiện có để tạo các yêu cầu đẩy.

Trong phần này, chúng ta sẽ sử dụng thư viện Nút web-push. Các ngôn ngữ khác sẽ có sự khác biệt, nhưng sẽ không quá khác biệt. Chúng ta sẽ xem xét Node vì đây là JavaScript và sẽ là ngôn ngữ dễ tiếp cận nhất đối với độc giả.

Chúng ta sẽ thực hiện các bước sau:

  1. Gửi gói thuê bao đến phần phụ trợ của chúng tôi và lưu lại.
  2. Truy xuất các gói thuê bao đã lưu và kích hoạt thông báo đẩy.

Đang lưu gói thuê bao

Việc lưu và truy vấn PushSubscription từ cơ sở dữ liệu sẽ khác nhau tuỳ thuộc vào ngôn ngữ phía máy chủ và lựa chọn cơ sở dữ liệu, nhưng bạn nên xem ví dụ về cách thực hiện.

Trong trang web minh hoạ, PushSubscription được gửi đến phần phụ trợ bằng cách thực hiện một yêu cầu POST đơn giản:

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ủ Express trong bản minh hoạ của chúng tôi có trình nghe yêu cầu phù hợp cho điểm cuối /api/save-subscription/:

app.post('/api/save-subscription/', function (req, res) {

Trong tuyến này, chúng ta xác thực gói thuê bao để đảm bảo yêu cầu là hợp lệ và không chứa rác:

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    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;
};

Nếu gói thuê bao hợp lệ, chúng ta cần lưu gói thuê bao đó và trả về một phản hồi JSON thích hợp:

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

Bản minh hoạ này sử dụng nedb để lưu trữ các gói thuê bao, đây là một cơ sở dữ liệu dựa trên tệp đơn giản, nhưng bạn có thể sử dụng bất kỳ cơ sở dữ liệu nào tuỳ ý. Chúng tôi chỉ sử dụng phương thức này vì không yêu cầu thiết lập. Đối với bản phát hành chính thức, bạn nên sử dụng một phương thức đáng tin cậy hơn. (Tôi thường sử dụng MySQL cũ.)

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

Gửi thông báo đẩy

Khi nói đến việc gửi thông báo đẩy, cuối cùng chúng ta cần một số sự kiện để kích hoạt quy trình gửi thông báo đến người dùng. Một phương pháp phổ biến là tạo một trang quản trị cho phép bạn định cấu hình và kích hoạt thông báo đẩy. Tuy nhiên, bạn có thể tạo một chương trình để chạy cục bộ hoặc bất kỳ phương pháp nào khác cho phép truy cập vào danh sách PushSubscription và chạy mã để kích hoạt thông báo đẩy.

Bản minh hoạ của chúng tôi có một trang "giống như trang quản trị" cho phép bạn kích hoạt một thông báo đẩy. Vì đây chỉ là bản minh hoạ nên đây là trang công khai.

Tôi sẽ trình bày từng bước để chạy bản minh hoạ này. Đây sẽ là các bước cơ bản để mọi người có thể làm theo, kể cả những người mới sử dụng Node.

Khi thảo luận về việc đăng ký người dùng, chúng ta đã đề cập đến việc thêm applicationServerKey vào các tuỳ chọn subscribe(). Chúng ta sẽ cần khoá riêng tư này ở phần phụ trợ.

Trong bản minh hoạ, các giá trị này được thêm vào ứng dụng Node như sau (mã này rất nhàm chán, nhưng tôi chỉ muốn bạn biết rằng không có gì kỳ diệu cả):

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

Tiếp theo, chúng ta cần cài đặt mô-đun web-push cho máy chủ Node:

npm install web-push --save

Sau đó, trong tập lệnh Nút, chúng tôi yêu cầu mô-đun web-push như vậy:

const webpush = require('web-push');

Bây giờ, chúng ta có thể bắt đầu sử dụng mô-đun web-push. Trước tiên, chúng ta cần cho mô-đun web-push biết về các khoá máy chủ ứng dụng của chúng ta. (Hãy nhớ rằng các khoá này còn được gọi là khoá VAPID vì đó là tên của thông số kỹ thuật.)

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

Xin lưu ý rằng chúng tôi cũng bao gồm một chuỗi "mailto:". Chuỗi này cần phải là một URL hoặc địa chỉ email mailto. Thông tin này sẽ thực sự được gửi đến dịch vụ đẩy web trong yêu cầu kích hoạt một thông báo đẩy. Lý do thực hiện việc này là để nếu một dịch vụ đẩy web cần liên hệ với người gửi, thì họ sẽ có một số thông tin cho phép họ thực hiện việc này.

Với điều này, mô-đun web-push đã sẵn sàng để sử dụng, bước tiếp theo là kích hoạt thông báo đẩy.

Bản minh hoạ sử dụng bảng điều khiển giả mạo quản trị để kích hoạt thông báo đẩy.

Ảnh chụp màn hình trang quản trị.

Khi nhấp vào nút "Kích hoạt thông báo đẩy", một yêu cầu POST sẽ được gửi đến /api/trigger-push-msg/. Đây là tín hiệu để phần phụ trợ gửi thông báo đẩy. Vì vậy, chúng ta sẽ tạo tuyến trong express cho điểm cuối này:

app.post('/api/trigger-push-msg/', function (req, res) {

Khi nhận được yêu cầu này, chúng tôi sẽ lấy các gói thuê bao từ cơ sở dữ liệu và đối với mỗi gói thuê bao, chúng tôi sẽ kích hoạt một thông báo đẩy.

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

Sau đó, hàm triggerPushMsg() có thể sử dụng thư viện web-push để gửi thông báo đến gói thuê bao đã cung cấp.

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

Lệnh gọi đến webpush.sendNotification() sẽ trả về một lời hứa. Nếu thông báo được gửi thành công, lời hứa sẽ được giải quyết và chúng ta không cần làm gì cả. Nếu lời hứa bị từ chối, bạn cần kiểm tra lỗi vì lỗi này sẽ cho bạn biết liệu PushSubscription có còn hợp lệ hay không.

Để xác định loại lỗi từ dịch vụ đẩy, tốt nhất bạn nên xem mã trạng thái. Các dịch vụ đẩy có thông báo lỗi khác nhau và một số thông báo hữu ích hơn những thông báo khác.

Trong ví dụ này, hàm này sẽ kiểm tra mã trạng thái 404410. Đây là mã trạng thái HTTP cho trạng thái "Không tìm thấy" và "Không còn nữa". Nếu chúng tôi nhận được một trong những thông báo này, thì có nghĩa là gói thuê bao đã hết hạn hoặc không còn hợp lệ. Trong những trường hợp này, chúng ta cần xoá các gói thuê bao khỏi cơ sở dữ liệu.

Trong trường hợp xảy ra một số lỗi khác, chúng ta chỉ cần throw err. Thao tác này sẽ khiến lời hứa do triggerPushMsg() trả về bị từ chối.

Chúng ta sẽ đề cập đến một số mã trạng thái khác trong phần tiếp theo khi xem xét giao thức đẩy web chi tiết hơn.

Sau khi lặp lại các gói thuê bao, chúng ta cần trả về một phản hồi JSON.

.then(() => {
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-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

Chúng ta đã xem xét các bước triển khai chính:

  1. Tạo một API để gửi các gói thuê bao từ trang web của chúng ta đến phần phụ trợ để có thể lưu các gói thuê bao đó vào cơ sở dữ liệu.
  2. Tạo một API để kích hoạt tính năng gửi thông báo đẩy (trong trường hợp này là một API được gọi từ bảng điều khiển quản trị giả).
  3. Truy xuất tất cả gói thuê bao từ phần phụ trợ của chúng tôi và gửi thông báo đến từng gói thuê bao bằng một trong các thư viện đẩy web.

Bất kể phần phụ trợ của bạn là gì (Node, PHP, Python, ...), các bước triển khai phương thức đẩy sẽ như nhau.

Tiếp theo, những thư viện đẩy web này thực sự giúp ích gì cho chúng ta?

Bước tiếp theo

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