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

บทแนะนำที่ครอบคลุมแนวคิดของ 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 หลายรายการ

เรามีการใช้ 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 และแจ้งให้ทราบเมื่อมีการยุติการทำงาน ก่อนอื่น ให้ทำตามวิธีการโหลดส่วนขยายที่แตกไฟล์แล้ว

หลังจากผ่านไป 30 วินาที คุณจะเห็น "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']
    });
  }
});

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

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

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

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

ในตัวอย่างนี้ เราจะใช้ 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 รันไทม์
ส่วนขยาย Quick API ที่เปิด Runtime API

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

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

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

🎯 การปรับปรุงที่เป็นไปได้

พยายามทำตามข้อใดต่อไปนี้โดยอิงจากสิ่งที่ได้เรียนรู้ในวันนี้

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

สร้างต่อไป

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

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

สำรวจต่อ

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