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 liên lạc hai chiều giữa trang và trình chạy dịch vụ.

Ví dụ: trong PWA podcast, người dùng có thể xây dựng một tính năng cho phép người dùng tải các tập xuống để tiêu thụ ngoại tuyến và cho phép trình chạy dịch vụ cập nhật trang thường xuyên 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 Cửa sổ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.

Sơ đồ cho thấy một trình chạy dịch vụ và trang trao đổi tin nhắn.

Sử dụng Workbox

workbox-window là một tập hợp các mô-đun của thư viện Workbox dự định 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 sẽ 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 thực thể đó:

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 ở phía 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ề sau, thư viện sẽ sử dụng 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 thông tin triển khai nhằm giúp bạn dễ sử dụng hơn, đồng thời tận dụng sự hỗ trợ trên toàn bộ trình duyệt mà API này có.

Sơ đồ cho thấy hoạt động giao tiếp hai chiều giữa trang và trình chạy dịch vụ bằng Cửa sổ hộp công việc.

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

Nếu thư viện Workbox không đủ cho nhu cầu của bạn, bạn có thể dùng 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 bên kia bằng cách triển khai một trình xử lý message.
  • Trên thực tế, tất cả API có sẵn đều cho phép chúng ta triển khai các trường hợp sử dụng tương tự, 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 lớp này có nhiều cách để xác định phía còn lại của hoạt động giao tiếp: một số trong số đó sử dụng thông tin tham chiếu rõ ràng đến ngữ cảnh khác, trong khi một số 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.
  • Khả năng hỗ trợ trình duyệt là khác nhau giữa các phiên bản.
Sơ đồ 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ó.

API kênh truyền phát

Hỗ trợ trình duyệt

  • 54
  • 79
  • 38
  • 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 qua thông qua các đối tượng BroadcastChannel.

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

Mọi ngữ cảnh của trình duyệt đều có thể theo dõi 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, bạn không cần lấy thông tin tham chiếu đến trình chạy dịch vụ hoặc bất kỳ ứng dụng cụ thể nào trước tiên.

Sơ đồ cho thấy hoạt động giao tiếp hai chiều giữa trang và trình chạy dịch vụ bằng cách sử dụng đối tượng Kênh truyền phát.

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

API ứng dụng

Hỗ trợ trình duyệt

  • 40
  • 17
  • 44
  • 11,1

Nguồn

API ứng dụng cho phép bạn 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à trình chạy dịch vụ đang kiểm soát.

Vì trang do một trình chạy dịch vụ kiểm soát, nên trình chạy dịch vụ này sẽ nghe và gửi thông báo trực tiếp đế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ự, trình chạy dịch vụ theo dõi thông báo bằng cách triển khai một 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 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 đó, ứng dụng có thể postMessage() bất kỳ nội dung 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 nhân viên dịch vụ đang giao tiếp với một mảng ứng dụng.

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

Kênh tin nhắn

Hỗ trợ trình duyệt

  • 2
  • 12
  • 41
  • 5

Nguồn

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 này để gửi cổng đến trình chạy dịch vụ đã đăng ký. Trang này cũng triển khai trình nghe onmessage trên đó để 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 một cổng đến một nhân viên 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 cổng, lưu tham chiếu đến cổng đó và sử dụng cổng đó để gửi thông báo cho 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ả 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 điệp chuỗi mô tả thao tác sẽ thực hiện hoặc danh sách các URL để lưu nội dung từ ngữ cảnh này sang ngữ cảnh khác. Trong phần này, chúng ta sẽ khám phá 2 API để xử lý các trường hợp cụ thể: thiếu kết nối và quá trình tải xuống dài.

Đồng bộ hoá dưới nền

Hỗ trợ trình duyệt

  • 49
  • 79
  • x
  • x

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á 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 rất hữu ích trong việc đảm bảo rằng bất kỳ nội dung nào người dùng muốn gửi đều thực sự được gửi.

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

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

Sau đó, dịch vụ này 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 mức độ thành công/không thành công của bất kỳ việc gì nó đang cố gắng thực hiện. Nếu hàm này đáp ứng thì quá trình đồng bộ hoá sẽ hoàn tất. Nếu không thành công, một quá trình đồng bộ hoá khác sẽ được lên lịch để thử lại. Thử đồng bộ lại cũng sẽ 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 trở 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á 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 này sau khi người dùng có kết nối mạng. Sau khi thực hiện thao tác, chúng 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 chuyển một cổng đến một nhân viên dịch vụ để thiết lập hoạt động giao tiếp hai chiều.

Tìm nạp trong nền

Hỗ trợ trình duyệt

  • Tăng 74
  • 79
  • x
  • x

Nguồn

Đối với các nhiệm vụ tương đối ngắn như gửi thư hoặc danh sách URL cần lưu vào bộ nhớ đệm, các tuỳ chọn đã khám phá cho đến thời điểm này 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ẽ khiến trình chạy dịch vụ bị gián đoạn, nếu không thì sẽ gây rủi ro cho quyền riêng tư và pin của người dùng.

API Tìm nạp trong nền cho phép bạn giảm tải một tác vụ dài cho một trình chạy dịch vụ, 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 theo dõi 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 một cổng đến một nhân viên 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ẻ đã được đó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à nhân viên dịch vụ (giao tiếp hai chiều).

Trong nhiều trường hợp, một trong hai trường hợp có thể chỉ cần một ngữ cảnh để giao tiếp với nhau mà không nhận được 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ề thực tế: