Komunikasi dua arah dengan pekerja layanan

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

Dalam beberapa kasus, aplikasi web mungkin perlu membuat saluran komunikasi dua arah antara halaman dan pekerja layanan.

Misalnya: di PWA podcast, seseorang dapat membuat fitur untuk memungkinkan pengguna mendownload episode untuk penggunaan offline dan mengizinkan service worker untuk terus memberi tahu halaman secara rutin tentang progres, sehingga thread utama dapat memperbarui UI.

Dalam panduan ini, kita akan mempelajari berbagai cara untuk menerapkan komunikasi dua arah antara konteks Window dan service worker, dengan mempelajari berbagai API, library Workbox, serta beberapa kasus lanjutan.

Diagram yang menunjukkan pertukaran pesan antara pekerja layanan dan halaman.

Menggunakan Workbox

workbox-window adalah sekumpulan modul library Workbox yang dimaksudkan untuk berjalan dalam konteks jendela. Class Workbox menyediakan metode messageSW() untuk mengirim pesan ke service worker terdaftar instance dan menunggu respons.

Kode halaman berikut membuat instance Workbox baru dan mengirim pesan ke service worker untuk mendapatkan versinya:

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

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

Service worker mengimplementasikan pemroses pesan di ujung lainnya, dan merespons service worker yang terdaftar:

const SW_VERSION = '1.0.0';

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

Di balik layar, library ini menggunakan browser API yang akan kita tinjau di bagian berikutnya: Message Channel, tetapi mengabstraksi banyak detail implementasi, sehingga lebih mudah digunakan, sekaligus memanfaatkan dukungan browser yang luas yang dimiliki API ini.

Diagram yang menunjukkan komunikasi dua arah antara halaman dan pekerja layanan, menggunakan Workbox Window.

Menggunakan API Browser

Jika library Workbox tidak cukup untuk kebutuhan Anda, ada beberapa API tingkat bawah yang tersedia untuk menerapkan komunikasi "dua arah" antara halaman dan pekerja layanan. Keduanya memiliki beberapa persamaan dan perbedaan:

Persamaan:

  • Dalam semua kasus, komunikasi dimulai di satu ujung melalui antarmuka postMessage() dan diterima di ujung lainnya dengan menerapkan handler message.
  • Pada praktiknya, semua API yang tersedia memungkinkan kita menerapkan kasus penggunaan yang sama, tetapi beberapa di antaranya dapat menyederhanakan pengembangan dalam beberapa skenario.

Perbedaan:

  • Keduanya memiliki cara yang berbeda untuk mengidentifikasi sisi lain komunikasi: beberapa di antaranya menggunakan referensi eksplisit ke konteks lain, sementara yang lain dapat berkomunikasi secara implisit melalui objek proxy yang di-instance di setiap sisi.
  • Dukungan browser bervariasi di antara keduanya.
Diagram yang menunjukkan komunikasi dua arah antara halaman dan pekerja layanan, serta API browser yang tersedia.

Broadcast Channel API

Browser Support

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

Source

Broadcast Channel API memungkinkan komunikasi dasar antara konteks penjelajahan melalui objek BroadcastChannel.

Untuk menerapkannya, pertama, setiap konteks harus membuat instance objek BroadcastChannel dengan ID yang sama dan mengirim serta menerima pesan darinya:

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

Objek BroadcastChannel mengekspos antarmuka postMessage() untuk mengirim pesan ke konteks yang mendengarkan:

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

Konteks browser apa pun dapat memproses pesan melalui metode onmessage objek BroadcastChannel:

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

Seperti yang terlihat, tidak ada referensi eksplisit ke konteks tertentu, sehingga tidak perlu mendapatkan referensi terlebih dahulu ke pekerja layanan atau klien tertentu.

Diagram yang menunjukkan komunikasi dua arah antara halaman dan pekerja layanan, menggunakan objek Broadcast Channel.

Kekurangannya adalah, pada saat penulisan artikel ini, API tersebut didukung oleh Chrome, Firefox, dan Edge, tetapi browser lain, seperti Safari, belum mendukungnya.

Client API

Browser Support

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

Source

Client API memungkinkan Anda mendapatkan referensi ke semua objek WindowClient yang merepresentasikan tab aktif yang dikontrol oleh pekerja layanan.

Karena dikontrol oleh satu pekerja layanan, halaman ini memproses dan mengirim pesan ke pekerja layanan aktif secara langsung melalui antarmuka 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
  }
};

Demikian pula, pekerja layanan memproses pesan dengan menerapkan pemroses onmessage:

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

Untuk berkomunikasi kembali dengan kliennya, pekerja layanan mendapatkan array objek WindowClient dengan menjalankan metode seperti Clients.matchAll() dan Clients.get(). Kemudian, postMessage() dapat postMessage() salah satunya:

//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'});
  }
});
Diagram yang menunjukkan pekerja layanan yang berkomunikasi dengan serangkaian klien.

Client API adalah opsi yang baik untuk berkomunikasi dengan mudah dengan semua tab aktif dari pekerja layanan dengan cara yang relatif mudah. API ini didukung oleh semua browser utama, tetapi tidak semua metodenya mungkin tersedia, jadi pastikan untuk memeriksa dukungan browser sebelum menerapkannya di situs Anda.

Saluran Pesan

Browser Support

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

Source

Message Channel memerlukan penentuan dan penerusan port dari satu konteks ke konteks lain untuk membuat saluran komunikasi dua arah.

Untuk menginisialisasi saluran, halaman membuat instance objek MessageChannel dan menggunakannya untuk mengirim port ke pekerja layanan terdaftar. Halaman juga menerapkan pemroses onmessage untuk menerima pesan dari konteks lain:

const messageChannel = new MessageChannel();

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

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
Diagram yang menunjukkan halaman meneruskan port ke pekerja layanan, untuk membangun komunikasi dua arah.

Service worker menerima port, menyimpan referensinya, dan menggunakannya untuk mengirim pesan ke sisi lain:

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 saat ini didukung oleh semua browser utama.

API Lanjutan: Sinkronisasi Latar Belakang dan Pengambilan Latar Belakang

Dalam panduan ini, kita telah mempelajari cara menerapkan teknik komunikasi dua arah, untuk kasus yang relatif sederhana, seperti meneruskan pesan string yang menjelaskan operasi yang akan dilakukan, atau daftar URL yang akan di-cache dari satu konteks ke konteks lainnya. Di bagian ini, kita akan mempelajari dua API untuk menangani skenario tertentu: kurangnya konektivitas dan download yang lama.

Sinkronisasi Latar Belakang

Browser Support

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

Source

Aplikasi chat mungkin ingin memastikan bahwa pesan tidak pernah hilang karena konektivitas yang buruk. Background Sync API memungkinkan Anda menunda tindakan untuk dicoba lagi saat pengguna memiliki konektivitas yang stabil. Hal ini berguna untuk memastikan bahwa apa pun yang ingin dikirim pengguna, benar-benar dikirim.

Alih-alih antarmuka postMessage(), halaman mendaftarkan sync:

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

Kemudian, pekerja layanan memproses peristiwa sync untuk memproses pesan:

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

Fungsi doSomeStuff() harus menampilkan promise yang menunjukkan keberhasilan/kegagalan apa pun yang dicoba dilakukan. Jika berhasil, sinkronisasi selesai. Jika gagal, sinkronisasi lain akan dijadwalkan untuk dicoba lagi. Sinkronisasi ulang juga menunggu konektivitas, dan menggunakan backoff eksponensial.

Setelah operasi dilakukan, pekerja layanan kemudian dapat berkomunikasi kembali dengan halaman untuk memperbarui UI, dengan menggunakan salah satu API komunikasi yang dibahas sebelumnya.

Penelusuran Google menggunakan Sinkronisasi Latar Belakang untuk mempertahankan kueri yang gagal karena konektivitas yang buruk, dan mencoba lagi kueri tersebut nanti saat pengguna online. Setelah operasi dilakukan, mereka akan mengomunikasikan hasilnya kepada pengguna melalui notifikasi push web:

Diagram yang menunjukkan halaman meneruskan port ke pekerja layanan, untuk membangun komunikasi dua arah.

Pengambilan Latar Belakang

Browser Support

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

Source

Untuk pekerjaan yang relatif singkat seperti mengirim pesan, atau daftar URL yang akan di-cache, opsi yang telah dieksplorasi sejauh ini adalah pilihan yang baik. Jika tugas terlalu lama, browser akan menghentikan service worker, jika tidak, hal ini akan membahayakan privasi dan baterai pengguna.

Background Fetch API memungkinkan Anda memindahkan tugas yang panjang ke pekerja layanan, seperti mendownload film, podcast, atau level game.

Untuk berkomunikasi dengan pekerja layanan dari halaman, gunakan backgroundFetch.fetch, bukan 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,
    },
  );
});

Objek BackgroundFetchRegistration memungkinkan halaman memproses peristiwa progress untuk mengikuti progres download:

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}%`);
});
Diagram yang menunjukkan halaman meneruskan port ke pekerja layanan, untuk membangun komunikasi dua arah.
UI diupdate untuk menunjukkan progres download (kiri). Berkat pekerja layanan, operasi dapat terus berjalan saat semua tab telah ditutup (kanan).

Langkah berikutnya

Dalam panduan ini, kita telah mempelajari kasus komunikasi yang paling umum antara halaman dan pekerja layanan (komunikasi dua arah).

Sering kali, satu konteks mungkin hanya memerlukan satu konteks lain untuk berkomunikasi, tanpa menerima respons. Lihat panduan berikut untuk mendapatkan panduan tentang cara menerapkan teknik satu arah di halaman Anda dari dan ke pekerja layanan, beserta kasus penggunaan dan contoh produksi:

  • Panduan caching imperatif: Memanggil pekerja layanan dari halaman untuk melakukan caching resource terlebih dahulu (misalnya, dalam skenario pengambilan data terlebih dahulu).
  • Menyiarkan update: Memanggil halaman dari pekerja layanan untuk memberi tahu tentang update penting (misalnya, versi baru aplikasi web tersedia).