טיפול באירועים עם קובצי שירות (service worker)

מדריך שמכסה מושגים של קובץ שירות (service worker) של תוסף

סקירה כללית

במדריך הזה אנחנו מסבירים על עובדי השירות של תוספי Chrome. במסגרת המדריך הזה, תכינו תוסף שמאפשר למשתמשים לנווט במהירות לדפי העזר של Chrome API באמצעות סרגל הכתובות. תוכלו ללמוד איך:

  • רישום קובץ השירות (service worker) וייבוא מודולים.
  • ניפוי באגים ב-Service Service Worker של התוסף.
  • ניהול מצב וטיפול באירועים.
  • הפעילו אירועים תקופתיים.
  • תקשורת באמצעות סקריפטים של תוכן.

לפני שמתחילים

מדריך זה מתבסס על ההנחה שיש לך ניסיון בסיסי בפיתוח אתרים. מומלץ לקרוא את התוספים 101 ו-Hello World כדי לקבל מבוא לפיתוח תוספים.

בניית התוסף

בתור התחלה, יוצרים ספרייה חדשה בשם quick-api-reference שתכיל את קובצי התוספים, או מורידים את קוד המקור ממאגר הדוגמאות של GitHub.

שלב 1: רושמים את קובץ השירות (service worker)

יוצרים את קובץ המניפסט ברמה הבסיסית (root) של הפרויקט ומוסיפים את הקוד הבא:

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) שלהם במניפסט, שמקבל רק קובץ JavaScript אחד. אין צורך להתקשר אל navigator.serviceWorker.register(), כמו בדף אינטרנט.

צור תיקיית images ולאחר מכן הורד את הסמלים אליה.

כדי לקבל מידע נוסף על המטא-נתונים והסמלים של התוסף, כדאי לעיין בשלבים הראשונים של המדריך בנושא זמן קריאה.

שלב 2: מייבאים מספר מודולים של Service Worker

קובץ השירות (service worker) שלנו מטמיע שתי תכונות. כדי לשפר את התחזוקה, נטמיע כל תכונה במודול נפרד. קודם כול, אנחנו צריכים להצהיר במניפסט שלנו על קובץ השירות (service worker) כמודול ES, שמאפשר לנו לייבא מודולים ב-Service Worker:

manifest.json:

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

יוצרים קובץ service-worker.js ומייבאים שני מודולים:

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 ומרעננים את התוסף. יוצגו שתי שגיאות:

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

למידע על דרכים נוספות לניפוי באגים ב-Service Worker של התוסף, קראו את המאמר ניפוי באגים בתוספים.

שלב 4: מאתחלים את המדינה

Chrome יכבה את עובדי השירות אם אין בהם צורך. אנחנו משתמשים ב-API chrome.storage כדי לשמור על עקביות בסשנים של Service Worker. כדי לקבל גישה לאחסון, עלינו לבקש הרשאה במניפסט:

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 Workers אין גישה ישירה לאובייקט window ולכן הם לא יכולים להשתמש ב-window.localStorage כדי לאחסן ערכים. בנוסף, Service Workers הם סביבות הפעלה לטווח קצר. הם נסגרים שוב ושוב במהלך הפעלת הדפדפן של המשתמש, ולכן הם לא תואמים למשתנים גלובליים. במקום זאת, צריך להשתמש בפונקציה chrome.storage.local, ששומרת את הנתונים במחשב המקומי.

למידע על אפשרויות אחסון אחרות ל-Service Workers של תוספים, ראו Persist data במקום להשתמש במשתנים גלובליים.

שלב 5: רושמים את האירועים

כל פונקציות event listener צריכות להיות רשומות סטטיות בהיקף הגלובלי של ה-Service Worker. במילים אחרות, ממשקי event listener לא צריכים להיות מקוננים בפונקציות אסינכרוניות. כך Chrome יכול להבטיח שכל הגורמים המטפלים באירועים ישוחזרו במקרה של הפעלה מחדש של Service Worker.

בדוגמה הזו נשתמש ב-API chrome.omnibox, אבל קודם צריך להצהיר על הטריגר של מילת המפתח בסרגל הכתובות במניפסט:

manifest.json:

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

עכשיו, רשמו את פונקציות event listener של סרגל הכתובות ברמה העליונה של הסקריפט. כשהמשתמש מזין את מילת המפתח בסרגל הכתובות (api) ולאחר מכן מקישים על Tab או על מקש הרווח, 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 האלה עלולים להיכשל, כי המתזמן יבטל את הטיימרים כשה-Service Worker יסתיים. במקום זאת, תוספים יכולים להשתמש ב-chrome.alarms API.

קודם כול צריך לבקש את ההרשאה "alarms" במניפסט. בנוסף, כדי לאחזר את הטיפים לגבי תוספים ממיקום שמתארח מרחוק, צריך לבקש את הרשאת המארח:

manifest.json:

{
  ...
  "permissions": ["storage", "alarms"],
  "permissions": ["storage"],
  "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.

מתחילים בהצהרה על סקריפט התוכן במניפסט ומוסיפים את דפוס ההתאמה התואם למסמכי התיעוד של 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;
}

השלב האחרון הוא להוסיף גורם handler של הודעות ל-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;
  }
});

בדיקה שזה עובד

מוודאים שמבנה הקובץ של הפרויקט נראה כך:

התוכן של תיקיית התוספים: תיקיית התמונות, המניפסט.json, service-worker.js, sw- האב-Search.js, sw-tips.js ו-content.js

יש לטעון את התוסף באופן מקומי

כדי לטעון תוסף לא ארוז במצב פיתוח, פועלים לפי השלבים המפורטים ב-Hello world.

פתיחת דף עזר

  1. מזינים את מילת המפתח 'api' בסרגל הכתובות של הדפדפן.
  2. מקישים על Tab או על מקש הרווח.
  3. מזינים את השם המלא של ה-API.
    • או בחר מתוך רשימה של חיפושים קודמים
  4. ייפתח דף חדש של דף העזר של Chrome API.

היא אמורה להיראות כך:

עיון מהיר בממשק API – פתיחת ההפניה ל-API בזמן ריצה
תוסף API Quick API פותח את Runtime API.

לפתוח את קצה היום

לחץ על הלחצן טיפ שנמצא בסרגל הניווט כדי לפתוח את הטיפ לתוסף.

פתיחת הטיפ היומי בעוד
תוסף API מהיר – טיפ חדש.

🎯 שיפורים אפשריים

בהתבסס על מה שלמדת היום, נסה להשיג כל אחת מהפעולות הבאות:

  • גלו דרך נוספת ליישום ההצעות מסרגל הכתובות.
  • יצירת חלון מותאם אישית משלך להצגת הטיפ לתוספים.
  • פתח דף נוסף לדפי ה-API של תוספי האינטרנט של MDN.

ממשיכים לפתח!

כל הכבוד על סיימת את המדריך הזה 🎉. רוצה לשדרג את המיומנויות שלך? כדאי להשלים מדריכים נוספים למתחילים:

Extension מה תלמדו
זמן קריאה להוסיף רכיב לקבוצת דפים ספציפית באופן אוטומטי.
מנהל הכרטיסיות כדי ליצור חלון קופץ לניהול הכרטיסיות בדפדפן.
מצב ריכוז כדי להריץ קוד בדף הנוכחי לאחר לחיצה על פעולת התוסף.

המשך בדיקה

כדי להמשיך במסלול הלמידה של קובץ השירות של התוסף, מומלץ לעיין במאמרים הבאים: