จัดการเหตุการณ์ด้วย Service Worker

บทแนะนำที่ครอบคลุมแนวคิดเกี่ยวกับผู้ปฏิบัติงานบริการส่วนขยาย

ภาพรวม

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

  • ลงทะเบียน Service Worker และนําเข้าโมดูล
  • แก้ไขข้อบกพร่องของ WOrker บริการของส่วนขยาย
  • จัดการสถานะและจัดการเหตุการณ์
  • ทริกเกอร์เหตุการณ์เป็นระยะ
  • สื่อสารกับสคริปต์เนื้อหา

ก่อนจะเริ่ม

คู่มือนี้จะถือว่าคุณมีประสบการณ์พื้นฐานในการพัฒนาเว็บ เราขอแนะนำให้อ่านส่วนขยาย 101 และ Hello World เพื่อดูข้อมูลเบื้องต้นเกี่ยวกับการพัฒนาส่วนขยาย

สร้างส่วนขยาย

เริ่มต้นด้วยการสร้างไดเรกทอรีใหม่ชื่อ quick-api-reference เพื่อเก็บไฟล์ส่วนขยาย หรือดาวน์โหลดซอร์สโค้ดจากที่เก็บตัวอย่าง GitHub ของเรา

ขั้นตอนที่ 1: ลงทะเบียน Service Worker

สร้างไฟล์ manifest ในรูทของโปรเจ็กต์ แล้วเพิ่มโค้ดต่อไปนี้

manifest.json:

{
  "manifest_version": 3,
  "name": "Open extension API reference",
  "version": "1.0.0",
  "icons": {
    "16": "images/icon-16.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "service-worker.js"
  }
}

ส่วนขยายจะลงทะเบียน Service Worker ในไฟล์ Manifest ซึ่งใช้ไฟล์ JavaScript เพียงไฟล์เดียว คุณไม่จำเป็นต้องเรียกใช้ navigator.serviceWorker.register() เหมือนกับในหน้าเว็บ

สร้างโฟลเดอร์ images แล้วดาวน์โหลดไอคอนลงในโฟลเดอร์ดังกล่าว

ดูขั้นตอนแรกของบทแนะนำเวลาในการอ่านเพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับข้อมูลเมตาและไอคอนของส่วนขยายในไฟล์ Manifest

ขั้นตอนที่ 2: นำเข้าโมดูล Service Worker หลายรายการ

โปรแกรมทำงานของบริการของเราจะใช้ฟีเจอร์ 2 อย่าง เราจะติดตั้งใช้งานแต่ละฟีเจอร์ในโมดูลแยกกันเพื่อให้ดูแลรักษาได้ง่ายขึ้น ก่อนอื่น เราต้องประกาศ Service Worker ให้เป็นโมดูล ES ในไฟล์ Manifest ซึ่งทำให้เรานำเข้าโมดูลใน Service Worker ได้

manifest.json:

{
 "background": {
    "service_worker": "service-worker.js",
    "type": "module"
  },
}

สร้างไฟล์ service-worker.js และนําเข้าโมดูล 2 รายการต่อไปนี้

import './sw-omnibox.js';
import './sw-tips.js';

สร้างไฟล์เหล่านี้และเพิ่มบันทึกคอนโซลลงในแต่ละไฟล์

sw-omnibox.js:

console.log("sw-omnibox.js");

sw-tips.js

console.log("sw-tips.js");

ดูการนําเข้าสคริปต์เพื่อดูวิธีอื่นๆ ในการนําเข้าไฟล์หลายไฟล์ใน Service Worker

ไม่บังคับ: การแก้ไขข้อบกพร่องของ Service Worker

เราจะอธิบายวิธีค้นหาบันทึกของ Service Worker และวิธีดูว่า Service Worker สิ้นสุดการทำงานแล้ว ก่อนอื่น ให้ทำตามวิธีการโหลดส่วนขยายที่แตกไฟล์แล้ว

หลังจากผ่านไป 30 วินาที คุณจะเห็น "Service Worker (ไม่ทำงาน)" ซึ่งหมายความว่า Service Worker สิ้นสุดการทำงานแล้ว คลิกลิงก์ "Service Worker (ไม่ทำงาน)" เพื่อตรวจสอบ ภาพเคลื่อนไหวต่อไปนี้แสดงกระบวนการนี้

คุณสังเกตเห็นว่าการตรวจสอบ Service Worker ทำให้ Service Worker ตื่นขึ้นมาไหม การเปิด Service Worker ในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์จะทําให้ Service Worker ทำงานต่อไป อย่าลืมปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์เพื่อให้แน่ใจว่าส่วนขยายจะทํางานอย่างถูกต้องเมื่อ Service Worker สิ้นสุดการทำงาน

ให้พักการทำงานของส่วนขยายเพื่อดูว่าจะหาข้อผิดพลาดได้ที่ไหน วิธีหนึ่งในการทำเช่นนี้คือการลบ ".js" จากการนําเข้า './sw-omnibox.js' ในไฟล์ service-worker.js Chrome จะลงทะเบียน Service Worker ไม่ได้

กลับไปที่ chrome://extensions แล้วรีเฟรชส่วนขยาย คุณจะเห็นข้อผิดพลาด 2 ประการ ได้แก่

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

ดูวิธีอื่นๆ ในการแก้ไขข้อบกพร่องของ Service Worker ของส่วนขยายได้ที่การแก้ไขข้อบกพร่องของส่วนขยาย

ขั้นตอนที่ 4: เริ่มต้นสถานะ

Chrome จะปิดโปรแกรมทำงานของบริการหากไม่จำเป็น เราใช้ chrome.storage API เพื่อคงสถานะไว้ตลอดเซสชันของ Service Worker หากต้องการเข้าถึงพื้นที่เก็บข้อมูล เราต้องขอสิทธิ์ในไฟล์ Manifest โดยทำดังนี้

manifest.json:

{
  ...
  "permissions": ["storage"],
}

ก่อนอื่น ให้บันทึกคำแนะนำเริ่มต้นลงในพื้นที่เก็บข้อมูล เราเริ่มต้นสถานะได้เมื่อติดตั้งส่วนขยายเป็นครั้งแรกโดยการฟังเหตุการณ์ runtime.onInstalled() ดังนี้

sw-omnibox.js:

...
// Save default API suggestions
chrome.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === 'install') {
    chrome.storage.local.set({
      apiSuggestions: ['tabs', 'storage', 'scripting']
    });
  }
});

Service Worker ไม่มีสิทธิ์เข้าถึงออบเจ็กต์หน้าต่างโดยตรง ดังนั้นจึงใช้ window.localStorage เพื่อจัดเก็บค่าไม่ได้ นอกจากนี้ เวิร์กเกอร์ของบริการยังเป็นสภาพแวดล้อมการดำเนินการที่มีอายุสั้น โดยจะสิ้นสุดลงซ้ำๆ ตลอดเซสชันเบราว์เซอร์ของผู้ใช้ ซึ่งทำให้ใช้ร่วมกับตัวแปรส่วนกลางไม่ได้ แต่ให้ใช้ chrome.storage.local ซึ่งจัดเก็บข้อมูลไว้ในเครื่องแทน

ดูข้อมูลเกี่ยวกับตัวเลือกพื้นที่เก็บข้อมูลอื่นๆ สําหรับ WORKER ของบริการส่วนขยายได้ที่เก็บข้อมูลไว้แทนการใช้ตัวแปรส่วนกลาง

ขั้นตอนที่ 5: ลงทะเบียนกิจกรรม

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

ในตัวอย่างนี้ เราจะใช้ chrome.omnibox API แต่ก่อนอื่นเราต้องประกาศทริกเกอร์คีย์เวิร์ดของกล่องค้นหาอเนกประสงค์ในไฟล์ Manifest ดังนี้

manifest.json:

{
  ...
  "minimum_chrome_version": "102",
  "omnibox": {
    "keyword": "api"
  },
}

ตอนนี้ให้ลงทะเบียน Listener เหตุการณ์ของกล่องค้นหาแบบรวมที่ระดับบนสุดของสคริปต์ เมื่อผู้ใช้ป้อนคีย์เวิร์ดในแถบอเนกประสงค์ (api) ในแถบที่อยู่ ตามด้วยแท็บหรือเว้นวรรค Chrome จะแสดงรายการแนะนำโดยอิงตามคีย์เวิร์ดในพื้นที่เก็บข้อมูล เหตุการณ์ onInputChanged() ซึ่งใช้อินพุตปัจจุบันของผู้ใช้และออบเจ็กต์ suggestResult มีหน้าที่สร้างรายการคำแนะนำเหล่านี้

sw-omnibox.js:

...
const URL_CHROME_EXTENSIONS_DOC =
  'https://developer.chrome.com/docs/extensions/reference/';
const NUMBER_OF_PREVIOUS_SEARCHES = 4;

// Display the suggestions after user starts typing
chrome.omnibox.onInputChanged.addListener(async (input, suggest) => {
  await chrome.omnibox.setDefaultSuggestion({
    description: 'Enter a Chrome API or choose from past searches'
  });
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  const suggestions = apiSuggestions.map((api) => {
    return { content: api, description: `Open chrome.${api} API` };
  });
  suggest(suggestions);
});

หลังจากผู้ใช้เลือกคำแนะนำแล้ว onInputEntered() จะเปิดหน้าเอกสารอ้างอิง Chrome API ที่เกี่ยวข้อง

sw-omnibox.js:

...
// Open the reference page of the chosen API
chrome.omnibox.onInputEntered.addListener((input) => {
  chrome.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input });
  // Save the latest keyword
  updateHistory(input);
});

ฟังก์ชัน updateHistory() จะรับอินพุตจากแถบค้นหาอเนกประสงค์และบันทึกลงใน storage.local วิธีนี้ช่วยให้คุณใช้ข้อความค้นหาล่าสุดเป็นคำแนะนำของแถบอเนกประสงค์ในภายหลังได้

sw-omnibox.js:

...
async function updateHistory(input) {
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  apiSuggestions.unshift(input);
  apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
  return chrome.storage.local.set({ apiSuggestions });
}

ขั้นตอนที่ 6: ตั้งค่ากิจกรรมที่เกิดซ้ำ

โดยทั่วไปแล้ว วิธีการ setTimeout() หรือ setInterval() จะใช้เพื่อดําเนินการแบบล่าช้าหรือเป็นระยะ แต่ API เหล่านี้อาจล้มเหลวเนื่องจากเครื่องจัดตารางเวลาจะยกเลิกตัวจับเวลาเมื่อโปรแกรมทำงานของบริการสิ้นสุดลง แต่ส่วนขยายจะใช้ chrome.alarms API แทนได้

เริ่มต้นด้วยการขอสิทธิ์ "alarms" ในไฟล์ Manifest นอกจากนี้ หากต้องการดึงข้อมูลเคล็ดลับส่วนขยายจากตำแหน่งที่โฮสต์ระยะไกล คุณจะต้องขอสิทธิ์ของโฮสต์ โดยทำดังนี้

manifest.json:

{
  ...
  "permissions": ["storage"],
  "permissions": ["storage", "alarms"],
  "host_permissions": ["https://extension-tips.glitch.me/*"],
}

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

sw-tips.js:

// Fetch tip & save in storage
const updateTip = async () => {
  const response = await fetch('https://extension-tips.glitch.me/tips.json');
  const tips = await response.json();
  const randomIndex = Math.floor(Math.random() * tips.length);
  return chrome.storage.local.set({ tip: tips[randomIndex] });
};

const ALARM_NAME = 'tip';

// Check if alarm exists to avoid resetting the timer.
// The alarm might be removed when the browser session restarts.
async function createAlarm() {
  const alarm = await chrome.alarms.get(ALARM_NAME);
  if (typeof alarm === 'undefined') {
    chrome.alarms.create(ALARM_NAME, {
      delayInMinutes: 1,
      periodInMinutes: 1440
    });
    updateTip();
  }
}

createAlarm();

// Update tip once a day
chrome.alarms.onAlarm.addListener(updateTip);

ขั้นตอนที่ 7: สื่อสารกับบริบทอื่นๆ

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

เริ่มต้นด้วยการประกาศสคริปต์เนื้อหาในไฟล์ Manifest และเพิ่มรูปแบบการจับคู่ที่สอดคล้องกับเอกสารอ้างอิง Chrome API

manifest.json:

{
  ...
  "content_scripts": [
    {
      "matches": ["https://developer.chrome.com/docs/extensions/reference/*"],
      "js": ["content.js"]
    }
  ]
}

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

content.js:

(async () => {
  // Sends a message to the service worker and receives a tip in response
  const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });

  const nav = document.querySelector('.upper-tabs > nav');
  
  const tipWidget = createDomElement(`
    <button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px; height: 36px;">
      <span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
    </button>
  `);

  const popover = createDomElement(
    `<div id='tip-popover' popover style="margin: auto;">${tip}</div>`
  );

  document.body.append(popover);
  nav.append(tipWidget);
})();

function createDomElement(html) {
  const dom = new DOMParser().parseFromString(html, 'text/html');
  return dom.body.firstElementChild;
}

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

sw-tips.js:

...
// Send tip to content script via messaging
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.greeting === 'tip') {
    chrome.storage.local.get('tip').then(sendResponse);
    return true;
  }
});

ทดสอบว่าใช้งานได้

ตรวจสอบว่าโครงสร้างไฟล์ของโปรเจ็กต์มีลักษณะดังต่อไปนี้

เนื้อหาของโฟลเดอร์ส่วนขยาย ได้แก่ โฟลเดอร์รูปภาพ, manifest.json, service-worker.js, sw-omnibox.js, sw-tips.js และ content.js

โหลดส่วนขยายในเครื่อง

หากต้องการโหลดส่วนขยายที่คลายการแพ็กในโหมดนักพัฒนาซอฟต์แวร์ ให้ทําตามขั้นตอนในHello World

เปิดหน้าข้อมูลอ้างอิง

  1. ป้อนคีย์เวิร์ด "api" ในแถบที่อยู่ของเบราว์เซอร์
  2. กด "Tab" หรือ "Space"
  3. ป้อนชื่อ API แบบเต็ม
    • หรือเลือกจากรายการการค้นหาที่ผ่านมา
  4. หน้าใหม่จะเปิดขึ้นไปยังหน้าข้อมูลอ้างอิง Chrome API

ซึ่งควรมีลักษณะดังนี้

ข้อมูลอ้างอิง API ฉบับย่อที่เปิดข้อมูลอ้างอิง API รันไทม์
ส่วนขยาย API ด่วนที่เปิด Runtime API

เปิดเคล็ดลับประจำวัน

คลิกปุ่มเคล็ดลับในแถบนําทางเพื่อเปิดเคล็ดลับเกี่ยวกับส่วนขยาย

เปิดเคล็ดลับรายวันใน
ส่วนขยาย Quick API เปิดเคล็ดลับประจำวัน

🎯 เพิ่มประสิทธิภาพได้

ลองทำสิ่งต่อไปนี้ตามสิ่งที่ได้เรียนรู้ในวันนี้

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

สร้างต่อไป

ยินดีด้วยที่จบบทแนะนำนี้ 🎉 พัฒนาทักษะของคุณต่อไปโดยดูบทแนะนำสำหรับมือใหม่อื่นๆ ต่อไปนี้

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

สำรวจต่อ

หากต้องการดูเส้นทางการเรียนรู้เกี่ยวกับผู้ให้บริการส่วนขยายต่อ เราขอแนะนำให้อ่านบทความต่อไปนี้