Giao tiếp hai chiều với nhân viên dịch vụ

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

Trong một số trường hợp, ứng dụng web có thể cần thiết lập kênh giao tiếp hai chiều giữa trang và worker dịch vụ.

Ví dụ: trong một PWA podcast, bạn có thể tạo một tính năng cho phép người dùng tải các tập xuống để sử dụng khi không có mạng và cho phép worker dịch vụ thường xuyên thông báo cho trang về tiến trình để luồng chính có thể cập nhật giao diện người dùng.

Trong hướng dẫn này, chúng ta sẽ khám phá các cách triển khai giao tiếp hai chiều giữa ngữ cảnh Cửa sổworker dịch vụ, bằng cách khám phá các API khác nhau, thư viện Workbox cũng như một số trường hợp nâng cao.

Sơ đồ thể hiện một trình chạy dịch vụ và trang trao đổi thông báo.

Sử dụng Workbox

workbox-window là một tập hợp các mô-đun của thư viện Workbox dùng để chạy trong ngữ cảnh cửa sổ. Lớp Workbox cung cấp một phương thức messageSW() để gửi thông báo đến worker dịch vụ đã đăng ký của thực thể và chờ phản hồi.

Mã trang sau đây tạo một thực thể Workbox mới và gửi thông báo đến worker dịch vụ để lấy phiên bản của worker đó:

const wb = new Workbox('/sw.js');
wb.register();

const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);

Trình chạy dịch vụ triển khai trình nghe thông báo ở đầu bên kia và phản hồi trình chạy dịch vụ đã đăng ký:

const SW_VERSION = '1.0.0';

self.addEventListener('message', (event) => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});

Về cơ bản, thư viện này sử dụng một API trình duyệt mà chúng ta sẽ xem xét trong phần tiếp theo: Message Channel (Kênh thông báo), nhưng tóm tắt nhiều chi tiết triển khai, giúp dễ sử dụng hơn, đồng thời tận dụng khả năng hỗ trợ trình duyệt rộng rãi mà API này có.

Sơ đồ cho thấy hoạt động giao tiếp hai chiều giữa trang và worker dịch vụ, sử dụng Cửa sổ Workbox.

Sử dụng API trình duyệt

Nếu thư viện Workbox không đủ đáp ứng nhu cầu của bạn, bạn có thể sử dụng một số API cấp thấp hơn để triển khai giao tiếp "hai chiều" giữa các trang và worker dịch vụ. Các phương thức này có một số điểm tương đồng và khác biệt:

Điểm tương đồng:

  • Trong mọi trường hợp, quá trình giao tiếp bắt đầu ở một đầu qua giao diện postMessage() và được nhận ở đầu kia bằng cách triển khai trình xử lý message.
  • Trong thực tế, tất cả các API hiện có đều cho phép chúng ta triển khai cùng một trường hợp sử dụng, nhưng một số API có thể đơn giản hoá quá trình phát triển trong một số trường hợp.

Điểm khác biệt:

  • Các phương thức này có nhiều cách để xác định phía bên kia của quá trình giao tiếp: một số phương thức sử dụng tham chiếu rõ ràng đến ngữ cảnh khác, trong khi một số phương thức khác có thể giao tiếp ngầm thông qua đối tượng proxy được tạo bản sao ở mỗi bên.
  • Khả năng hỗ trợ trình duyệt sẽ khác nhau giữa các trình duyệt.
Sơ đồ cho thấy hoạt động giao tiếp hai chiều giữa trang và worker dịch vụ, cũng như các API trình duyệt hiện có.

Broadcast Channel API

Hỗ trợ trình duyệt

  • Chrome: 54.
  • Edge: 79.
  • Firefox: 38.
  • Safari: 15.4.

Nguồn

Broadcast Channel API cho phép giao tiếp cơ bản giữa các ngữ cảnh duyệt web thông qua các đối tượng BroadcastChannel.

Để triển khai, trước tiên, mỗi ngữ cảnh phải tạo bản sao của đối tượng BroadcastChannel có cùng mã nhận dạng và gửi cũng như nhận thông báo từ đối tượng đó:

const broadcast = new BroadcastChannel('channel-123');

Đối tượng BroadcastChannel hiển thị giao diện postMessage() để gửi thông báo đến bất kỳ ngữ cảnh nghe nào:

//send message
broadcast.postMessage({ type: 'MSG_ID', });

Mọi ngữ cảnh trình duyệt đều có thể nghe thông báo thông qua phương thức onmessage của đối tượng BroadcastChannel:

//listen to messages
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process message...
  }
};

Như đã thấy, không có tham chiếu rõ ràng đến một ngữ cảnh cụ thể, vì vậy, trước tiên, bạn không cần phải lấy tham chiếu đến worker dịch vụ hoặc bất kỳ ứng dụng cụ thể nào.

Sơ đồ cho thấy hoạt động giao tiếp hai chiều giữa trang và worker dịch vụ, sử dụng đối tượng Broadcast Channel.

Nhược điểm là tại thời điểm viết bài này, API có sự hỗ trợ của Chrome, Firefox và Edge, nhưng các trình duyệt khác như Safari chưa hỗ trợ API này.

API ứng dụng

Hỗ trợ trình duyệt

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Nguồn

API ứng dụng cho phép bạn lấy tham chiếu đến tất cả các đối tượng WindowClient đại diện cho các thẻ đang hoạt động mà worker dịch vụ đang kiểm soát.

Vì trang này do một trình chạy dịch vụ kiểm soát, nên trang này sẽ trực tiếp nghe và gửi thông báo đến trình chạy dịch vụ đang hoạt động thông qua giao diện serviceWorker:

//send message
navigator.serviceWorker.controller.postMessage({
  type: 'MSG_ID',
});

//listen to messages
navigator.serviceWorker.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process response
  }
};

Tương tự, worker dịch vụ sẽ theo dõi thông báo bằng cách triển khai trình nghe onmessage:

//listen to messages
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //Process message
  }
});

Để giao tiếp lại với bất kỳ ứng dụng nào, worker dịch vụ sẽ lấy một mảng các đối tượng WindowClient bằng cách thực thi các phương thức như Clients.matchAll()Clients.get(). Sau đó, nó có thể postMessage() bất kỳ mục nào trong số đó:

//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
  if (clients && clients.length) {
    //Respond to last focused tab
    clients[0].postMessage({type: 'MSG_ID'});
  }
});
Sơ đồ cho thấy một worker dịch vụ giao tiếp với một mảng ứng dụng.

Client API là một lựa chọn phù hợp để dễ dàng giao tiếp với tất cả các thẻ đang hoạt động từ một worker dịch vụ theo cách tương đối đơn giản. API này được tất cả trình duyệt lớn hỗ trợ, nhưng có thể không phải tất cả phương thức đều có sẵn. Vì vậy, hãy nhớ kiểm tra khả năng hỗ trợ trình duyệt trước khi triển khai API này trong trang web của bạn.

Kênh tin nhắn

Hỗ trợ trình duyệt

  • Chrome: 2.
  • Edge: 12.
  • Firefox: 41.
  • Safari: 5.

Nguồn

Kênh thông báo yêu cầu xác định và truyền cổng từ ngữ cảnh này sang ngữ cảnh khác để thiết lập kênh giao tiếp hai chiều.

Để khởi chạy kênh, trang sẽ tạo bản sao của đối tượng MessageChannel và sử dụng đối tượng đó để gửi cổng đến worker dịch vụ đã đăng ký. Trang này cũng triển khai trình nghe onmessage trên trang này để nhận thông báo từ ngữ cảnh khác:

const messageChannel = new MessageChannel();

//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
  messageChannel.port2,
]);

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
Sơ đồ cho thấy một trang chuyển cổng đến worker dịch vụ để thiết lập giao tiếp hai chiều.

Worker dịch vụ nhận cổng, lưu tệp tham chiếu đến cổng đó và sử dụng cổng đó để gửi thông báo đến bên kia:

let communicationPort;

//Save reference to port
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

//Send messages
communicationPort.postMessage({type: 'MSG_ID'});

MessageChannel hiện được tất cả trình duyệt lớn hỗ trợ.

API nâng cao: Đồng bộ hoá ở chế độ nền và Tìm nạp ở chế độ nền

Trong hướng dẫn này, chúng tôi đã khám phá các cách triển khai kỹ thuật giao tiếp hai chiều cho các trường hợp tương đối đơn giản, chẳng hạn như truyền một thông báo chuỗi mô tả thao tác cần thực hiện hoặc danh sách URL để lưu vào bộ nhớ đệm từ ngữ cảnh này sang ngữ cảnh khác. Trong phần này, chúng ta sẽ khám phá hai API để xử lý các trường hợp cụ thể: thiếu kết nối và tải xuống trong thời gian dài.

Đồng bộ hoá ở chế độ nền

Hỗ trợ trình duyệt

  • Chrome: 49.
  • Edge: 79.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Ứng dụng trò chuyện có thể muốn đảm bảo rằng tin nhắn không bao giờ bị mất do kết nối kém. Background Sync API (API Đồng bộ hoá ở chế độ nền) cho phép bạn trì hoãn các thao tác để thử lại khi người dùng có kết nối ổn định. Điều này rất hữu ích để đảm bảo rằng mọi nội dung mà người dùng muốn gửi đều được gửi.

Thay vì giao diện postMessage(), trang này sẽ đăng ký sync:

navigator.serviceWorker.ready.then(function (swRegistration) {
  return swRegistration.sync.register('myFirstSync');
});

Sau đó, worker dịch vụ sẽ theo dõi sự kiện sync để xử lý thông báo:

self.addEventListener('sync', function (event) {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(doSomeStuff());
  }
});

Hàm doSomeStuff() sẽ trả về một lời hứa cho biết kết quả thành công/không thành công của bất kỳ việc gì mà hàm đang cố gắng thực hiện. Nếu lời hứa được thực hiện, quá trình đồng bộ hoá sẽ hoàn tất. Nếu không thành công, một lần đồng bộ hoá khác sẽ được lên lịch để thử lại. Các lần đồng bộ hoá thử lại cũng chờ kết nối và sử dụng thời gian đợi luỹ thừa.

Sau khi thực hiện thao tác, worker dịch vụ có thể giao tiếp lại với trang để cập nhật giao diện người dùng bằng cách sử dụng bất kỳ API giao tiếp nào được khám phá trước đó.

Google Tìm kiếm sử dụng tính năng Đồng bộ hoá ở chế độ nền để duy trì các truy vấn không thành công do kết nối kém và thử lại các truy vấn đó sau khi người dùng có kết nối mạng. Sau khi thực hiện thao tác, các dịch vụ này sẽ thông báo kết quả cho người dùng thông qua thông báo đẩy web:

Sơ đồ cho thấy một trang chuyển cổng đến worker dịch vụ để thiết lập giao tiếp hai chiều.

Tìm nạp ở chế độ nền

Hỗ trợ trình duyệt

  • Chrome: 74.
  • Edge: 79.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Đối với các công việc tương đối ngắn như gửi tin nhắn hoặc danh sách URL để lưu vào bộ nhớ đệm, các tuỳ chọn đã khám phá cho đến nay là một lựa chọn phù hợp. Nếu tác vụ mất quá nhiều thời gian, trình duyệt sẽ chấm dứt worker dịch vụ, nếu không, tác vụ này sẽ gây rủi ro cho quyền riêng tư và pin của người dùng.

Background Fetch API (API Tìm nạp ở chế độ nền) cho phép bạn giảm tải một tác vụ dài cho một worker dịch vụ, chẳng hạn như tải phim, podcast hoặc cấp độ của trò chơi xuống.

Để giao tiếp với trình chạy dịch vụ từ trang, hãy sử dụng backgroundFetch.fetch thay vì postMessage():

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.fetch(
    'my-fetch',
    ['/ep-5.mp3', 'ep-5-artwork.jpg'],
    {
      title: 'Episode 5: Interesting things.',
      icons: [
        {
          sizes: '300x300',
          src: '/ep-5-icon.png',
          type: 'image/png',
        },
      ],
      downloadTotal: 60 * 1024 * 1024,
    },
  );
});

Đối tượng BackgroundFetchRegistration cho phép trang nghe sự kiện progress để theo dõi tiến trình tải xuống:

bgFetch.addEventListener('progress', () => {
  // If we didn't provide a total, we can't provide a %.
  if (!bgFetch.downloadTotal) return;

  const percent = Math.round(
    (bgFetch.downloaded / bgFetch.downloadTotal) * 100,
  );
  console.log(`Download progress: ${percent}%`);
});
Sơ đồ cho thấy một trang chuyển cổng đến worker dịch vụ để thiết lập giao tiếp hai chiều.
Giao diện người dùng được cập nhật để cho biết tiến trình tải xuống (bên trái). Nhờ các worker dịch vụ, thao tác này có thể tiếp tục chạy khi tất cả các thẻ đã đóng (bên phải).

Các bước tiếp theo

Trong hướng dẫn này, chúng tôi đã khám phá trường hợp giao tiếp chung nhất giữa trình chạy trang và trình chạy dịch vụ (giao tiếp hai chiều).

Nhiều khi, một thành phần có thể chỉ cần một ngữ cảnh để giao tiếp với thành phần khác mà không cần nhận phản hồi. Hãy xem các hướng dẫn sau đây để biết cách triển khai các kỹ thuật một chiều trong trang của bạn từ và đến worker dịch vụ, cùng với các trường hợp sử dụng và ví dụ về sản xuất: