ในบางกรณี เว็บแอปอาจต้องสร้างช่องทางการสื่อสารแบบ 2 ทางระหว่างหน้าเว็บและ Service Worker
เช่น ใน PWA ของพอดแคสต์ ผู้ใช้อาจสร้างฟีเจอร์เพื่อให้ผู้ใช้ดาวน์โหลดตอนต่างๆ เพื่อดูแบบออฟไลน์ และช่วยให้ Service Worker แจ้งความคืบหน้าให้ทราบเป็นระยะ เพื่อให้เทรดหลักอัปเดต UI ได้
ในคู่มือนี้ เราจะสำรวจวิธีต่างๆ ในการใช้การสื่อสารแบบ 2 ทางระหว่างบริบทของหน้าต่างและผู้ปฏิบัติงานบริการ โดยการสำรวจ API ต่างๆ ไลบรารีพื้นที่ทำงาน และกรณีขั้นสูงบางรายการ
การใช้ 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);
Service Worker ใช้ 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 ของเบราว์เซอร์ที่เราจะพูดถึงในส่วนถัดไป ซึ่งได้แก่ MessageChannel แต่สรุปรายละเอียดการใช้งานมากมาย ซึ่งทำให้ใช้งานได้ง่ายขึ้น และในขณะเดียวกันก็ใช้ประโยชน์จากการรองรับเบราว์เซอร์แบบกว้างที่ API นี้มี
การใช้ API ของเบราว์เซอร์
หากไลบรารี Workbox ยังไม่พอสำหรับความต้องการของคุณ ยังมี API ระดับล่างอีกหลายรายการที่คุณสามารถใช้การสื่อสารแบบ "2 ทาง" ระหว่างเพจและ Service Worker ได้ ซึ่งก็มีความคล้ายคลึง และแตกต่างกันดังนี้
ความคล้ายคลึงกัน:
- ในทุกกรณี การสื่อสารจะเริ่มต้นทางฝั่งหนึ่งผ่านอินเทอร์เฟซ
postMessage()
และได้รับในอีกฝั่งหนึ่งโดยใช้ตัวแฮนเดิลmessage
- ในทางปฏิบัติแล้ว API ที่มีอยู่ทั้งหมดช่วยให้เรานำกรณีการใช้งานเดียวกันไปใช้ได้ แต่บางกรณีอาจทำให้การพัฒนาง่ายขึ้นในบางสถานการณ์
ความแตกต่าง:
- ทั้งคู่มีวิธีระบุอีกฝั่งของการสื่อสารที่แตกต่างกัน บางวิธีใช้การอ้างอิงที่ชัดแจ้งไปยังบริบทอื่น ในขณะที่บางคนสามารถสื่อสารโดยนัยผ่านออบเจ็กต์พร็อกซีที่จำลองอยู่ในแต่ละฝั่ง
- การรองรับเบราว์เซอร์จะแตกต่างกันไป
API ช่องการออกอากาศ
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...
}
};
ดังที่เห็น ไม่มีการอ้างอิงถึงบริบทหนึ่งๆ อย่างชัดเจน จึงไม่จำเป็นต้องได้รับข้อมูลอ้างอิงจากโปรแกรมทำงานของบริการหรือไคลเอ็นต์ใดๆ ก่อน
ข้อเสียก็คือ ในตอนที่เขียนบทความนี้ API รองรับ Chrome, Firefox และ Edge แต่เบราว์เซอร์อื่นๆ เช่น Safari ยังไม่รองรับในขณะนี้
API ไคลเอ็นต์
ไคลเอ็นต์ 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'});
}
});
Client API
เป็นตัวเลือกที่ดีในการสื่อสารกับแท็บที่ใช้งานอยู่ทั้งหมดจาก Service Worker ได้อย่างง่ายดายและตรงไปตรงมา เบราว์เซอร์หลักทั้งหมดรองรับ API นี้ แต่บางวิธีอาจใช้ไม่ได้ ดังนั้น โปรดตรวจสอบการรองรับเบราว์เซอร์ก่อนนําไปใช้ในเว็บไซต์
ช่องทางของข้อความ
ช่องทางข้อความจำเป็นต้องมีการกำหนดและส่งต่อพอร์ตจากบริบทหนึ่งไปยังอีกบริบทหนึ่งเพื่อสร้างช่องทางการสื่อสารแบบ 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
};
โปรแกรมทำงานของบริการจะได้รับพอร์ต จากนั้นบันทึกการอ้างอิงไปยังพอร์ตและใช้เพื่อส่งข้อความไปยังฝั่งอีกฝั่งหนึ่ง ดังนี้
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 ขั้นสูง: การซิงค์ในเบื้องหลังและการดึงข้อมูลในเบื้องหลัง
ในคู่มือนี้ เราได้พูดถึงวิธีต่างๆ ในการใช้เทคนิคการสื่อสารแบบ2 ทาง ในกรณีที่ค่อนข้างเรียบง่าย เช่น การส่งข้อความสตริงที่อธิบายการดำเนินการ หรือรายการ URL เพื่อแคชจากบริบทหนึ่งไปยังอีกบริบทหนึ่ง ในส่วนนี้ เราจะไปดู API สองตัวเพื่อจัดการกับสถานการณ์ต่างๆ คือ การขาดการเชื่อมต่อและการดาวน์โหลดที่ใช้เวลานาน
การซิงค์ในเบื้องหลัง
แอปแชทอาจต้องการตรวจสอบว่าข้อความจะไม่สูญหายเนื่องจากการเชื่อมต่อไม่ดี และ 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()
ควรแสดงผลสัญญาที่ระบุความสำเร็จ/ความล้มเหลวของสิ่งที่พยายามทำ หากทำงานสำเร็จ หมายความว่าการซิงค์เสร็จสมบูรณ์ หากไม่สำเร็จ ระบบจะกำหนดเวลาซิงค์อีกครั้งเพื่อให้ลองอีกครั้ง ลองซิงค์อีกครั้งจะรอการเชื่อมต่อและใช้การย้อนกลับแบบทวีคูณ
เมื่อดำเนินการแล้ว โปรแกรมทำงานของบริการจะสามารถติดต่อกลับผ่านหน้าเว็บเพื่ออัปเดต UI โดยใช้ API การสื่อสารที่สำรวจก่อนหน้านี้
Google Search ใช้การซิงค์ในเบื้องหลังเพื่อเก็บการค้นหาที่ล้มเหลวเนื่องจากการเชื่อมต่อที่ไม่ดี และลองใหม่ในภายหลังเมื่อผู้ใช้ออนไลน์ เมื่อดําเนินการแล้ว ผู้ใช้จะแจ้งผลลัพธ์ให้ผู้ใช้ทราบผ่านข้อความ Push บนเว็บ
การดึงข้อมูลในเบื้องหลัง
สำหรับงานที่ค่อนข้างสั้น เช่น การส่งข้อความหรือรายการ URL ที่จะแคช ตัวเลือกต่างๆ ที่ได้สำรวจมาจนถึงตอนนี้ก็เป็นตัวเลือกที่ดี หากงานใช้เวลานานเกินไป เบราว์เซอร์จะยุติการทำงานของบริการ มิเช่นนั้นอาจทำให้ความเป็นส่วนตัวของผู้ใช้และแบตเตอรี่ตกอยู่ในอันตราย
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 ทิศทาง)
บ่อยครั้งที่คนคนหนึ่งอาจต้องการเพียงบริบทเดียวเพื่อสื่อสารกับอีกฝ่ายโดยไม่ได้รับการตอบกลับ ดูคำแนะนำต่อไปนี้สำหรับแนวทางในการใช้เทคนิคแบบทิศทางเดียวในหน้าเว็บจากและไปยังโปรแกรมทำงานของบริการ รวมถึงกรณีการใช้งานและตัวอย่างเวอร์ชันที่ใช้งานจริง
- คำแนะนำในการแคชที่จำเป็น: การเรียกใช้ Service Worker จากหน้าเว็บเพื่อแคชทรัพยากรล่วงหน้า (เช่น ในสถานการณ์การดึงข้อมูลล่วงหน้า)
- อัปเดตการประกาศ: เรียกใช้หน้าเว็บจาก Service Worker เพื่อแจ้งการอัปเดตที่สำคัญ (เช่น มีเว็บแอปเวอร์ชันใหม่แล้ว)