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

Andrew Guan
Andrew Guan

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

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

ในคู่มือนี้เราจะสำรวจวิธีต่างๆ ในการใช้การสื่อสารแบบ 2 ทางระหว่าง หน้าต่างและบริการ ผู้ปฏิบัติงานจากการสำรวจ API ต่างๆ ไลบรารีพื้นที่ทำงาน รวมทั้ง กรณีพิเศษบางอย่าง

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

การใช้ Workbox

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

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

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

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

โปรแกรมทำงานของบริการจะใช้ Listener ข้อความในอีกด้านหนึ่ง และตอบกลับ 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 เบราว์เซอร์ที่เราจะตรวจสอบในส่วนถัดไป: ข้อความ ช่อง แต่แอบสแตรกต์จำนวนมาก รายละเอียดการนำไปใช้งาน ทำให้ใช้งานง่าย ขณะเดียวกันก็ใช้ประโยชน์จากเบราว์เซอร์แบบกว้าง รองรับ API นี้

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

การใช้ API ของเบราว์เซอร์

หากไลบรารีของ Workbox ไม่เพียงพอสำหรับความต้องการของคุณ เรามี API ระดับล่างหลายรายการที่พร้อมให้คุณใช้งาน ใช้การสื่อสารแบบ "2 ทาง" ระหว่างเพจและโปรแกรมทำงานของบริการ ผลการค้นหาคล้ายกัน และความแตกต่าง ได้แก่

ความคล้ายคลึงกัน:

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

ความแตกต่าง:

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

API ช่องสำหรับการออกอากาศ

การรองรับเบราว์เซอร์

  • Chrome: 54
  • ขอบ: 79
  • Firefox: 38.
  • Safari: 15.4

แหล่งที่มา

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...
  }
};

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

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

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

API ของไคลเอ็นต์

การรองรับเบราว์เซอร์

  • Chrome: 40
  • ขอบ: 17
  • Firefox: 44
  • Safari: 11.1

แหล่งที่มา

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
  }
};

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

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

ในการสื่อสารกลับไปยังไคลเอ็นต์ โปรแกรมทำงานของบริการจะได้รับอาร์เรย์ 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 นี้ได้รับการสนับสนุนโดยผู้เผยแพร่โฆษณาหลักทั้งหมด เบราว์เซอร์ แต่วิธีการบางอย่างอาจไม่พร้อมใช้งาน ดังนั้น โปรดตรวจสอบการสนับสนุนของเบราว์เซอร์ก่อน ติดตั้งโค้ดนี้ในเว็บไซต์

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

การรองรับเบราว์เซอร์

  • Chrome: 2.
  • ขอบ: 12.
  • Firefox: 41
  • Safari: 5.

แหล่งที่มา

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

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

const messageChannel = new MessageChannel();

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

//Listen to messages
messageChannel.port1.onmessage = (event) => {
  // Process message
};
แผนภาพแสดงหน้าที่ส่งผ่านพอร์ตไปยังโปรแกรมทำงานของบริการเพื่อสร้างการสื่อสารแบบ 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 แบบเพื่อจัดการกับ สถานการณ์: ขาดการเชื่อมต่อและการดาวน์โหลดเป็นเวลานาน

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

การรองรับเบราว์เซอร์

  • Chrome: 49
  • ขอบ: 79
  • Firefox: ไม่สนับสนุน
  • Safari: ไม่รองรับ

แหล่งที่มา

แอปแชทอาจต้องการดูแลให้ข้อความต่างๆ ไม่สูญหายไปเนื่องจากการเชื่อมต่อที่ไม่ดี 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() ควรจะแสดงคำมั่นสัญญาที่ระบุความสำเร็จ/ความล้มเหลวของสิ่งต่างๆ พยายามจะทำอะไร หากเป็นไปตามเงื่อนไข การซิงค์ก็เสร็จสมบูรณ์ หากไม่สำเร็จ ระบบจะกำหนดเวลาซิงค์อีกครั้งเป็น ลองอีกครั้ง ลองซิงค์อีกครั้งที่รอการเชื่อมต่อ และใช้ Exponential Backoff

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

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

วันที่ แผนภาพแสดงหน้าที่ส่งผ่านพอร์ตไปยังโปรแกรมทำงานของบริการเพื่อสร้างการสื่อสารแบบ 2 ทาง

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

การรองรับเบราว์เซอร์

  • Chrome: 74
  • ขอบ: 79
  • Firefox: ไม่สนับสนุน
  • Safari: ไม่รองรับ

แหล่งที่มา

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

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

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

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

ในคู่มือนี้ เราศึกษากรณีทั่วไปที่สุดของการสื่อสารระหว่างเพจกับโปรแกรมทำงานของบริการ (การสื่อสารแบบ 2 ทิศทาง)

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

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