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

บทแนะนำที่ครอบคลุมแนวคิดของ Service Worker ส่วนขยาย

ภาพรวม

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

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

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

คุณสังเกตเห็นว่าการตรวจสอบ 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.

ดูการแก้ไขข้อบกพร่องของส่วนขยายสำหรับวิธีอื่นๆ ในการแก้ไขข้อบกพร่องของโปรแกรมทำงานของบริการส่วนขยาย

ขั้นตอนที่ 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 เพื่อจัดเก็บค่า นอกจากนี้ Service Worker ยังมีสภาพแวดล้อมการดำเนินการเป็นระยะเวลาสั้นๆ ถูกสิ้นสุดการใช้งานซ้ำๆ ตลอดเซสชันเบราว์เซอร์ของผู้ใช้ ซึ่งทำให้ไม่สามารถใช้กับ ตัวแปรร่วม แต่ให้ใช้ chrome.storage.local ซึ่งจัดเก็บข้อมูลไว้ในเครื่องของคุณแทน

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

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

Listener เหตุการณ์ทั้งหมดต้องลงทะเบียนแบบคงที่ในขอบเขตทั่วโลกของ 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/*"],
}

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

เปิดเคล็ดลับวันนี้

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

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

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

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

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

สร้างสรรค์ผลงานต่อไป!

ยินดีด้วยที่บทแนะนำนี้จบแล้ว 🎉 เพิ่มพูนทักษะของคุณอย่างต่อเนื่องโดยการพิชิตทักษะอื่นๆ บทแนะนำสำหรับผู้เริ่มต้น:

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

สำรวจต่อ

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