การสื่อสารแบบ 2 ทางกับ Service Worker

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

ในบางกรณี เว็บแอปอาจต้องสร้างช่องทางการสื่อสารแบบ 2 ทางระหว่างหน้าเว็บกับ Service Worker

ตัวอย่างเช่น ใน PWA ของพอดแคสต์ คุณสามารถสร้างฟีเจอร์ที่ให้ผู้ใช้ดาวน์โหลดตอนเพื่อ รับชมแบบออฟไลน์ และอนุญาตให้ Service Worker แจ้งความคืบหน้าให้หน้าเว็บทราบเป็นประจำ เพื่อให้เธรดหลักอัปเดต UI ได้

ในคู่มือนี้ เราจะมาดูวิธีต่างๆ ในการใช้การสื่อสารแบบ 2 ทางระหว่างบริบทของ Window กับ Service Worker โดยการสำรวจ API ต่างๆ, ไลบรารี Workbox รวมถึงกรณีขั้นสูงบางกรณี

แผนภาพแสดง Service Worker และหน้าเว็บที่แลกเปลี่ยนข้อความ

การใช้ Workbox

workbox-window คือชุดโมดูลของไลบรารี Workbox ที่มีไว้สำหรับเรียกใช้ในบริบทของหน้าต่าง คลาส Workbox มีเมธอด messageSW() สำหรับส่งข้อความไปยัง Service Worker ที่ลงทะเบียนของอินสแตนซ์และ รอการตอบกลับ

โค้ดหน้าเว็บต่อไปนี้จะสร้างอินสแตนซ์ Workbox ใหม่และส่งข้อความไปยัง Service Worker เพื่อรับเวอร์ชันของ Service Worker

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

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

Service Worker จะใช้เครื่องฟังสัญญาณข้อความที่ปลายอีกด้านหนึ่ง และตอบกลับ Service Worker ที่ลงทะเบียนไว้ ดังนี้

const SW_VERSION = '1.0.0';

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

ไลบรารีนี้ใช้ API ของเบราว์เซอร์ที่เราจะตรวจสอบในส่วนถัดไป นั่นคือ Message Channel แต่จะแยกรายละเอียดการติดตั้งใช้งานหลายอย่างออกไป ทำให้ใช้งานได้ง่ายขึ้น พร้อมทั้งใช้ประโยชน์จากการรองรับเบราว์เซอร์ที่หลากหลายที่ API นี้มี

แผนภาพที่แสดงการสื่อสารแบบ 2 ทางระหว่างหน้าเว็บและ Service Worker โดยใช้ Workbox Window

การใช้ Browser API

หากไลบรารี Workbox ไม่เพียงพอต่อความต้องการของคุณ ก็มี API ระดับล่างอีกหลายรายการที่พร้อมให้ใช้งานเพื่อ ใช้การสื่อสาร"สองทาง"ระหว่างหน้าเว็บกับ Service Worker โดยมีความคล้ายคลึง และแตกต่างกันดังนี้

ความคล้ายคลึง

  • ในทุกกรณี การสื่อสารจะเริ่มต้นที่ฝั่งหนึ่งผ่านอินเทอร์เฟซ postMessage() และรับที่อีกฝั่งโดยการติดตั้งใช้งานแฮนเดิล message
  • ในทางปฏิบัติ API ที่พร้อมใช้งานทั้งหมดช่วยให้เราสามารถใช้กรณีการใช้งานเดียวกันได้ แต่ API บางตัว อาจช่วยลดความซับซ้อนในการพัฒนาในบางสถานการณ์

ความแตกต่าง

  • ซึ่งแต่ละวิธีจะมีวิธีระบุอีกฝั่งของการสื่อสารที่แตกต่างกัน โดยบางวิธีจะใช้การอ้างอิงที่ชัดเจนไปยังบริบทอื่น ในขณะที่บางวิธีจะสื่อสารโดยอ้อมผ่านออบเจ็กต์พร็อกซีที่สร้างขึ้นในแต่ละฝั่ง
  • การรองรับเบราว์เซอร์จะแตกต่างกันไป
แผนภาพที่แสดงการสื่อสารแบบ 2 ทางระหว่างหน้าเว็บและ Service Worker รวมถึง API ของเบราว์เซอร์ที่พร้อมใช้งาน

Broadcast Channel API

Browser Support

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

Source

Broadcast Channel API ช่วยให้มีการสื่อสารพื้นฐานระหว่างบริบทการเรียกดูผ่านออบเจ็กต์ BroadcastChannel

หากต้องการใช้ ให้แต่ละบริบทสร้างออบเจ็กต์ BroadcastChannel ที่มีรหัสเดียวกันก่อน แล้วส่งและรับข้อความจากออบเจ็กต์นั้น

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

ออบเจ็กต์ BroadcastChannel จะแสดงอินเทอร์เฟซ postMessage() เพื่อส่งข้อความไปยังบริบทการฟัง ใดก็ได้

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

บริบทของเบราว์เซอร์ใดก็ได้สามารถรับฟังข้อความผ่านเมธอด onmessage ของออบเจ็กต์ BroadcastChannel ได้

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

ดังที่เห็น ไม่มีข้อมูลอ้างอิงที่ชัดเจนถึงบริบทใดบริบทหนึ่ง ดังนั้นจึงไม่จำเป็นต้องขอ ข้อมูลอ้างอิงก่อนไปยัง Service Worker หรือไคลเอ็นต์ใดๆ

แผนภาพที่แสดงการสื่อสารแบบ 2 ทางระหว่างหน้าเว็บและ Service Worker โดยใช้ออบเจ็กต์ Broadcast Channel

ข้อเสียคือ ณ เวลาที่เขียนบทความนี้ API ได้รับการรองรับจาก Chrome, Firefox และ Edge แต่เบราว์เซอร์อื่นๆ เช่น Safari ยังไม่รองรับ

Client API

Browser Support

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

Source

Client API ช่วยให้คุณได้รับ การอ้างอิงไปยังออบเจ็กต์ WindowClient ทั้งหมดที่แสดงแท็บที่ใช้งานอยู่ซึ่ง Service Worker ควบคุม

เนื่องจากหน้าเว็บควบคุมโดย Service Worker เดียว จึงรับฟังและส่งข้อความไปยัง Service Worker ที่ใช้งานอยู่โดยตรงผ่านอินเทอร์เฟซ 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
  }
};

ในทำนองเดียวกัน Service Worker จะรับฟังข้อความโดยการใช้โปรแกรมฟัง onmessage ดังนี้

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

หากต้องการสื่อสารกับลูกค้า บริการ Worker จะรับอาร์เรย์ของออบเจ็กต์ WindowClient โดยการเรียกใช้เมธอดต่างๆ เช่น Clients.matchAll() และ Clients.get() จากนั้นก็สามารถpostMessage()รายการใดก็ได้ต่อไปนี้

//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'});
  }
});
แผนภาพแสดง Service Worker ที่สื่อสารกับอาร์เรย์ของไคลเอ็นต์

Client API เป็นตัวเลือกที่ดีในการสื่อสารกับแท็บที่ใช้งานอยู่ทั้งหมดจาก Service Worker ได้อย่างง่ายดายในลักษณะที่ค่อนข้างตรงไปตรงมา API นี้รองรับเบราว์เซอร์หลักทั้งหมด แต่บางวิธีอาจไม่พร้อมใช้งาน ดังนั้นโปรดตรวจสอบการรองรับของเบราว์เซอร์ก่อน นำไปใช้ในเว็บไซต์

ช่องทางของข้อความ

Browser Support

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

Source

Message Channel ต้องมีการกำหนดและส่งพอร์ตจากบริบทหนึ่งไปยังอีกบริบทหนึ่งเพื่อสร้างช่องทางการสื่อสารแบบ 2 ทาง

หากต้องการเริ่มต้นช่องทาง หน้าเว็บจะสร้างออบเจ็กต์ MessageChannel และใช้เพื่อส่งพอร์ตไปยัง Service Worker ที่ลงทะเบียนไว้ หน้านี้ยังใช้ onmessage Listener ใน หน้าดังกล่าวเพื่อรับข้อความจากบริบทอื่นด้วย

const messageChannel = new MessageChannel();

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

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
แผนภาพแสดงหน้าเว็บที่ส่งพอร์ตไปยัง Service Worker เพื่อสร้างการสื่อสารแบบ 2 ทาง

Service Worker จะรับพอร์ต บันทึกการอ้างอิงไปยังพอร์ต และใช้พอร์ตเพื่อส่งข้อความไปยังอีกด้านหนึ่ง

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รองรับเบราว์เซอร์หลักทั้งหมด

API ขั้นสูง: การซิงค์ในเบื้องหลังและการดึงข้อมูลในเบื้องหลัง

ในคู่มือนี้ เราได้สำรวจวิธีใช้เทคนิคการสื่อสารสองทางสำหรับกรณีที่ค่อนข้างง่าย เช่น การส่งข้อความสตริงที่อธิบายการดำเนินการที่จะทำ หรือรายการ URL ที่จะแคชจากบริบทหนึ่งไปยังอีกบริบทหนึ่ง ในส่วนนี้ เราจะดู API 2 รายการเพื่อจัดการกับสถานการณ์ที่เฉพาะเจาะจง ได้แก่ การขาดการเชื่อมต่อและการดาวน์โหลดที่ใช้เวลานาน

การซิงค์ในเบื้องหลัง

Browser Support

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

Source

แอปแชทอาจต้องการตรวจสอบว่าข้อความจะไม่สูญหายเนื่องจากการเชื่อมต่อไม่ดี Background Sync API ช่วยให้คุณ เลื่อนการดำเนินการเพื่อลองอีกครั้งเมื่อผู้ใช้มีการเชื่อมต่อที่เสถียร ซึ่งมีประโยชน์ในการตรวจสอบว่าระบบได้ส่งสิ่งที่ผู้ใช้ต้องการส่งแล้วจริง

หน้าเว็บจะลงทะเบียน sync แทนอินเทอร์เฟซ postMessage() ดังนี้

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

จากนั้น Service Worker จะรอเหตุการณ์ sync เพื่อประมวลผลข้อความ

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

ฟังก์ชัน doSomeStuff() ควรแสดงผล Promise ที่ระบุความสำเร็จ/ความล้มเหลวของสิ่งที่ฟังก์ชัน พยายามทำ หากฟังก์ชันทำงานสำเร็จ การซิงค์จะเสร็จสมบูรณ์ หากไม่สำเร็จ ระบบจะกำหนดเวลาการซิงค์อีกครั้งเพื่อ ลองอีกครั้ง การซิงค์ที่ลองใหม่จะรอการเชื่อมต่อและใช้ Exponential Backoff ด้วย

เมื่อดำเนินการแล้ว Service Worker จะสื่อสารกลับไปยังหน้าเว็บเพื่อ อัปเดต UI ได้โดยใช้ API การสื่อสารใดก็ได้ที่ได้ศึกษาไปก่อนหน้านี้

การค้นหาของ Google ใช้การซิงค์ข้อมูลในเบื้องหลังเพื่อเก็บคำค้นหาที่ล้มเหลวเนื่องจากการเชื่อมต่อไม่ดี และลองค้นหาอีกครั้งในภายหลังเมื่อผู้ใช้ออนไลน์ เมื่อดำเนินการแล้ว ระบบจะแจ้งผลลัพธ์ให้ผู้ใช้ทราบผ่านการแจ้งเตือนแบบพุชบนเว็บ

แผนภาพแสดงหน้าเว็บที่ส่งพอร์ตไปยัง Service Worker เพื่อสร้างการสื่อสารแบบ 2 ทาง

การดึงข้อมูลในเบื้องหลัง

Browser Support

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

Source

สำหรับงานที่ค่อนข้างสั้น เช่น การส่งข้อความ หรือรายการ URL ที่จะแคช ตัวเลือก ที่สำรวจมาจนถึงตอนนี้เป็นตัวเลือกที่ดี หากงานใช้เวลานานเกินไป เบราว์เซอร์จะปิด Service Worker ไม่เช่นนั้นจะเป็นความเสี่ยงต่อความเป็นส่วนตัวและแบตเตอรี่ของผู้ใช้

Background Fetch API ช่วยให้คุณส่งต่อภาระงานที่ใช้เวลานานไปยัง Service Worker ได้ เช่น การดาวน์โหลดภาพยนตร์ พอดแคสต์ หรือเลเวล ของเกม

หากต้องการสื่อสารกับ Service Worker จากหน้าเว็บ ให้ใช้ backgroundFetch.fetch แทน 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,
    },
  );
});

ออบเจ็กต์ BackgroundFetchRegistration ช่วยให้หน้าเว็บรับฟังเหตุการณ์ progress เพื่อติดตาม ความคืบหน้าของการดาวน์โหลดได้

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}%`);
});
แผนภาพแสดงหน้าเว็บที่ส่งพอร์ตไปยัง Service Worker เพื่อสร้างการสื่อสารแบบ 2 ทาง
มีการอัปเดต UI เพื่อระบุความคืบหน้าของการดาวน์โหลด (ซ้าย) Service Worker ช่วยให้การดำเนินการทำงานต่อไปได้เมื่อปิดแท็บทั้งหมดแล้ว (ขวา)

ขั้นตอนถัดไป

ในคู่มือนี้ เราได้สำรวจกรณีทั่วไปที่สุดของการสื่อสารระหว่างหน้าเว็บและ Service Worker (การสื่อสารแบบ 2 ทาง)

หลายครั้งที่อาจต้องใช้เพียงบริบทเดียวในการสื่อสารกับอีกบริบทหนึ่งโดยไม่ต้องรับ การตอบกลับ ดูคำแนะนำเกี่ยวกับวิธีใช้เทคนิคแบบทิศทางเดียวในหน้าเว็บจากและไปยัง Service Worker พร้อมกรณีการใช้งานและตัวอย่างการใช้งานจริงได้ในคำแนะนำต่อไปนี้

  • คำแนะนำในการแคชแบบบังคับ: การเรียกใช้ Service Worker จากหน้าเว็บเพื่อ แคชทรัพยากรล่วงหน้า (เช่น ในสถานการณ์การดึงข้อมูลล่วงหน้า)
  • การอัปเดตการออกอากาศ: การเรียกหน้าเว็บจาก Service Worker เพื่อแจ้ง เกี่ยวกับการอัปเดตที่สำคัญ (เช่น มีเว็บแอปเวอร์ชันใหม่)