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, người ta 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 để xem khi không có mạng và cho phép service worker 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ẽ khám phá các cách triển khai hoạt động giao tiếp hai chiều giữa ngữ cảnh Windowservice worker 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ơ đồ minh hoạ 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 một thông báo đến worker 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);

Worker dịch vụ triển khai một trình nghe thông báo ở đầu kia và phản hồi worker 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);
  }
});

Dưới phần nâng cao, 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: MessageChannel, nhưng trừu tượng hoá nhiều chi tiết triển khai, giúp bạn dễ dàng sử dụng hơn, đồng thời tận dụng khả năng hỗ trợ trình duyệt rộng mà API này có.

Sơ đồ minh hoạ quá trình giao tiếp hai chiều giữa trang và worker 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 để triển khai giao tiếp "hai chiều" giữa các trang và worker 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ả các API hiện có đề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:

  • Chúng có nhiều cách để xác định phía bên kia của quá trình giao tiếp: một số cách sử dụng một tham chiếu rõ ràng đến ngữ cảnh khác, trong khi những cách khác có thể giao tiếp một cách ngầm định thông qua một đối tượng proxy được khởi tạo ở mỗi bên.
  • Mỗi trình duyệt sẽ có mức độ hỗ trợ khác nhau.
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 có sẵn.

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 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 một đố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ị một giao diện postMessage() để gửi thông báo đến mọi ngữ cảnh đang nghe:

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

Mọi ngữ cảnh 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ư bạn thấy, không có thông tin tham chiếu rõ ràng đến một bối cảnh cụ thể, vì vậy, bạn không cần phải lấy thông tin tham chiếu trước cho trình chạy dịch vụ hoặc bất kỳ ứng dụng nào cụ thể.

Sơ đồ minh hoạ hoạt động giao tiếp hai chiều giữa trang và worker dịch vụ, bằng cách 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, chẳng hạn như Safari, chưa hỗ trợ.

Client API

Browser Support

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

Source

Client API cho phép bạn lấy một 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 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ẽ lắng 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ự, worker dịch vụ sẽ lắng nghe 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 đó, 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ụ đang giao tiếp với một mảng các ứ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 từ một worker 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 đề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

Message Channel 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 tạo kênh, trang sẽ tạo một đối tượng MessageChannel và 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 một trình nghe onmessage trên trang để 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 một worker dịch vụ để thiết lập giao tiếp hai chiều.

Service worker nhận cổng, lưu một tham chiếu đến cổng đó và 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á ở chế độ nền và Tìm nạp ở chế độ nền

Trong hướng dẫn này, chúng ta đã khám phá các cách triển khai kỹ thuật giao tiếp hai chiều, đối với 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 dạng chuỗi mô tả thao tác cần thực hiện hoặc một danh sách URL để lưu vào bộ nhớ đệm từ bối cảnh này sang bối 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à 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

Một ứ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 cho phép bạn trì hoãn các thao tác cần thử lại khi người dùng có kết nối ổn định. Điều này hữu ích để đả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.

Thay vì giao diện postMessage(), trang này đă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 thành công/thất bại của bất kỳ thao tác nào mà hàm đang cố gắng thực hiện. Nếu hàm thực hiện thành công, 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 thử lại đồng bộ hoá 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, service worker 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 đó.

Tìm kiếm của Google sử dụng tính năng Đồng bộ hoá trong nền để duy trì các cụm từ tìm kiếm 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 chuyển cổng đến một worker dịch vụ để thiết lập 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 những phần 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 lựa 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ẽ huỷ service worker, 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 cho phép bạn chuyển một tác vụ dài sang một worker dịch vụ, chẳng hạn như tải phim, podcast hoặc các cấp độ của một 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 cổng đến một 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 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 đã khám phá trường hợp giao tiếp chung nhất giữa trang và các worker dịch vụ (giao tiếp hai chiều).

Nhiều khi, một người có thể chỉ cần một ngữ cảnh để giao tiếp với người khác 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 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ề quy trình sản xuất:

  • Hướng dẫn về bộ nhớ đệm bắt buộc: Gọi một trình chạy dịch vụ từ trang để lưu trước các tài nguyên vào bộ nhớ đệm (ví dụ: trong các trường hợp tìm nạp trước).
  • Thông báo cập nhật: Gọi trang từ worker dịch vụ để thông báo về các nội dung cập nhật quan trọng (ví dụ: có phiên bản mới của ứng dụng web).