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

Một trong những điểm bất cập khi xử lý công nghệ đẩy trên web là việc kích hoạt thông báo đẩy sẽ cực kỳ hiệu quả "lười biếng". Để kích hoạt thông báo đẩy, ứng dụng cần gửi yêu cầu POST cho thao tác đẩy theo sau thông báo đẩy trên web giao thức. Để sử dụng chế độ đẩy trên tất cả 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 yêu cầu đặt tiêu đề có giá trị chứng minh ứ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 phải được mã hoá và các tiêu đề cụ thể cần được thêm để trình duyệt có thể giải mã thư một cách chính xác.

Vấn đề chính của việc kích hoạt thông báo đẩy là nếu bạn gặp phải một vấn đề thì rất khó để chẩn đoán vấn đề. Quá trình này sẽ được cải thiện theo thời gian và phạm vi hỗ trợ trình duyệt rộng hơn nhưng không dễ dàng. Cho lý do này, tôi thực sự khuyên 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 của bạn.

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

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

Chúng tôi sẽ đi qua 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 gói thuê bao đó.
  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ẽ thay đổi tuỳ thuộc vào ngôn ngữ phía máy chủ và lựa chọn cơ sở dữ liệu của bạn, nhưng có thể bạn nên xem ví dụ về cách có thể 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ó một 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 lộ trình này, chúng tôi xác thực gói thuê bao chỉ để đảm bảo rằng yêu cầu vẫn ổn và chưa đầy 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 tôi cần lưu gói thuê bao và trả về một gói thuê bao thích hợp Phản hồi JSON:

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 đăng ký. cơ sở dữ liệu dựa trên tệp đơn giản, nhưng bạn có thể dùng bất kỳ cơ sở dữ liệu nào tuỳ ý. Chúng tôi chỉ sử dụng làm bạn không cần thiết lập gì cả. Đối với phiên bản phát hành công khai, bạn nên dùng thiết bị đáng tin cậy hơn. (Tôi có xu hướng tiếp tục 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);
    });
  });
}

Đang gửi thông báo đẩy

Để gửi thông báo đẩy, rốt cuộc chúng ta cần một số sự kiện để kích hoạt quá trình gửi tin nhắn cho 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. Nhưng 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 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ục "thích của quản trị viên" cho phép bạn kích hoạt một lượt đẩy. Vì đây chỉ là một bản minh hoạ nên trang công khai.

Tôi sẽ tìm hiểu từng bước để làm cho bản minh hoạ hoạt động. Đây sẽ là em bé để mọi người có thể làm theo, kể cả những người mới sử dụng Nút.

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 Tuỳ chọn subscribe(). Chúng tôi sẽ cần khoá riêng tư này ở hệ thống.

Trong bản minh hoạ, các giá trị này được thêm vào ứng dụng Nút của chúng ta theo cách tương tự (tôi biết mã nhàm chán, nhưng chỉ muốn để bạn biết rằng chẳ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ủ Nút:

npm install web-push --save

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

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ề khoá máy chủ ứng dụng của chúng tôi. (Hãy nhớ rằng chúng còn được gọi là khoá VAPID vì đó là tên của khoá 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 "mailto:" . Chuỗi này phải là một URL hoặc một mailto địa chỉ email. Thông tin này sẽ được gửi đến dịch vụ đẩy trên web như một phần của để kích hoạt một lượt đẩy. Lý do là vì nếu dịch vụ đẩy web cần liên hệ với người gửi, họ có một số thông tin cho phép họ làm vậy.

Như vậ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 quản trị giả để kích hoạt thông báo đẩy.

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

Nhấp vào "Kích hoạt thông báo đẩy" sẽ gửi yêu cầu POST tới /api/trigger-push-msg/, là tín hiệu để phần phụ trợ của chúng tôi gửi thông báo đẩy, vì vậy, chúng ta 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à thì 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 đẩy web để gửi thông báo đến được 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 tin nhắn đã được gửi thành công, lời hứa sẽ giải quyết và có chúng tôi không cần phải làm gì. Nếu lời hứa bị từ chối, bạn cần kiểm tra vì mã này sẽ cho bạn biết liệu PushSubscription có đang có hợp lệ hay không.

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

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

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

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

Sau khi lặp lại các gói thuê bao, chúng ta cần trả về 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 tôi đã tìm hiểu 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 đến hệ thống phụ trợ của chúng tôi để có thể lưu chúng vào cơ sở dữ liệu.
  2. Tạo một API để kích hoạt việc gửi thông báo đẩy (trong trường hợp này là API được gọi từ bảng điều khiển quản trị giả mạo).
  3. Truy xuất tất cả gói thuê bao qua phần phụ trợ và gửi tin nhắn đến từng gói thuê bao bằng một trong các tính năng web-push thư viện.

Bất kể phần phụ trợ của bạn là gì (Nút, PHP, Python, ...), các bước triển khai chế độ đẩy vẫn sẽ diễn ra sao cho giống nhau.

Tiếp theo, chính xác thì các thư viện đẩy web này đang làm gì cho chúng ta?

Điểm đến tiếp theo

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