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à trình chạy dịch vụ.

Ví dụ: trong một PWA podcast, người dùng 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 để nghe khi không có mạng và cho phép trình chạy dịch vụ thường xuyên thông báo cho trang về tiến trình, 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ẽ tìm hiểu các cách triển khai hoạt động giao tiếp hai chiều giữa bối cảnh Window và trình chạy 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.

Biểu đồ cho thấy 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 được thiết kế để chạy trong ngữ cảnh cửa sổ. Lớp Workbox cung cấp phương thức messageSW() để gửi thông báo đến trình chạy 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 trình chạy dịch vụ để lấy phiên bản của trình chạy dịch vụ:

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 tin nhắn ở đầ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);
  }
});

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: 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ó.

Biểu đồ cho thấy hoạt động giao tiếp hai chiều giữa trang và trình chạy dịch vụ, sử dụng Workbox Window.

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, thì có một số API cấp thấp hơn để triển khai hoạt động giao tiếp "hai chiều" giữa các trang và trình chạy dịch vụ. Chúng 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, hoạt động giao tiếp bắt đầu ở một đầu thông qua giao diện postMessage() và được nhận ở đầu kia bằng cách triển khai trình xử lý message.
  • Trên thực tế, tất 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á việc 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 khác nhau để xác định phía bên kia của hoạt động giao tiếp: một số phương thức sử dụng thông tin tham chiếu rõ ràng đến bối 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 một đối tượng proxy được tạo thực thể ở mỗi bên.
  • Mức độ hỗ trợ của trình duyệt sẽ khác nhau tuỳ theo từng trình duyệt.
Biểu đồ cho thấy hoạt động giao tiếp hai chiều giữa trang và trình chạy dịch vụ, cũng như các API trình duyệt hiện có.

Broadcast Channel API

Browser Support

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

Source

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

Để triển khai, trước tiên, mỗi ngữ cảnh phải tạo thực thể cho một đối tượng BroadcastChannel có cùng mã nhận dạng, đồng thời gửi và 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', });

Bất kỳ bối cảnh trình duyệt nào cũng 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ư bạn thấy, không có tham chiếu rõ ràng đến một ngữ cảnh cụ thể, vì vậy, bạn không cần phải lấy tham chiếu trước đến trình chạy 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à trình chạy 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 này được Chrome, Firefox và Edge hỗ trợ, nhưng các trình duyệt khác như Safari vẫn chưa hỗ trợ.

API ứng dụng

Browser Support

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

Source

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

Vì trang này do một trình chạy dịch vụ duy nhất kiểm soát, nên trang này sẽ nghe và gửi thông báo đến trình chạy dịch vụ đang hoạt động trực tiếp 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ự, trình chạy 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, trình chạy dịch vụ sẽ lấy một mảng đối tượng WindowClient bằng cách thực thi các phương thức như Clients.matchAll() và Clients.get(). Sau đó, ứng dụng có thể postMessage() bất kỳ thao tá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 trình chạy dịch vụ đang giao tiếp với một loạt ứ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 trình chạy dịch vụ theo cách tương đối đơn giản. API này được tất cả các trình duyệt chính hỗ trợ, nhưng có thể không phải tất cả các phương thức của API này đều có sẵn. Vì vậy, hãy nhớ kiểm tra khả năng hỗ trợ của trình duyệt trước khi triển khai API này trên trang web của bạn.

Kênh tin nhắn

Browser Support

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

Source

Kênh thông báo yêu cầu xác định và truyền một 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 thực thể cho đối tượng MessageChannel và sử dụng đối tượng đó để gửi một cổng đến trình chạy dịch vụ đã đăng ký. Trang này cũng triển khai trình nghe onmessage để nhận thông báo từ bối 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 truyền cổng đến một trình chạy dịch vụ để thiết lập hoạt động giao tiếp hai chiều.

Trình chạy dịch vụ nhận cổng, lưu tệp đối chiếu vào cổng đó và sử dụng cổng đó để gửi thông báo đến phía 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ả các trình duyệt chính hỗ trợ.

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

Trong hướng dẫn này, chúng ta đã tìm hiểu 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 cần 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ẽ tìm hiểu hai API để xử lý các trường hợp cụ thể: mất kết nối và tải xuống trong thời gian dài.

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

Browser Support

  • Chrome: 49.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

Ứ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á trong nền) cho phép bạn trì hoãn các hành động cần thử lại khi người dùng có kết nối ổn định. Điều này giúp đảm bảo rằng bất cứ nội dung nào người dùng muốn gửi đều được gửi đi.

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

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

Sau đó, trình chạy 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 trạng thái 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 hàm thực hiện được, 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. Quá trình đồ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, trình chạy 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 đã khám phá trước đó.

Google Tìm kiếm sử dụng tính năng Đồng bộ hoá trong 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 sau khi người dùng kết nối mạng. Sau khi thực hiện thao tác, họ sẽ thông báo kết quả cho người dùng thông qua thông báo đẩy trên web:

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

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

Browser Support

  • Chrome: 74.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

Đố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 cần lưu vào bộ nhớ đệm, các lựa chọn được 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ẽ tắt trình chạy dịch vụ, nếu không, quyền riêng tư và pin của người dùng sẽ gặp rủi ro.

Background Fetch API (API tìm nạp trong nền) cho phép bạn chuyển một tác vụ cần nhiều thời gian sang trình chạy dịch vụ, chẳng hạn như tải phim, podcast hoặc cá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 truyền cổng đến một trình chạy dịch vụ để thiết lập hoạt động 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ó trình chạy 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 ta đã tìm hiểu trường hợp giao tiếp chung nhất giữa trang và trình chạy dịch vụ (giao tiếp hai chiều).

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