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

מדריך שמסביר את המושגים של שירותי העבודה של התוספים

סקירה כללית

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

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

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

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

בניית התוסף

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

שלב 1: רישום של ה-service worker

יוצרים את קובץ manifest ברמה הבסיסית (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 כמודול 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 (inactive)', כלומר ה-service worker הסתיים. לוחצים על הקישור Service worker (לא פעיל) כדי לבדוק אותו. האנימציה הבאה ממחישה את זה.

שמת לב שהבדיקה של קובץ השירות (service worker) העיר אותו? אם פותחים את Service Worker בכלי הפיתוח, הוא יישאר פעיל. כדי לוודא שהתוסף יפעל כראוי כשסוכם השירות יופסק, חשוב לזכור לסגור את DevTools.

עכשיו, מפרקים את התוסף כדי ללמוד איפה נמצאות השגיאות. אחת מהדרכים לעשות זאת היא למחוק את הסיומת '‎.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 כדי לשמור את המצב בסשנים של שירותי העבודה. כדי לקבל גישה לאחסון, אנחנו צריכים לבקש הרשאה במניפסט:

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

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

manifest.json:

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

עכשיו צריך לרשום את פונקציות ההאזנה לאירועים של סרגל החיפוש הכללי ברמה העליונה של הסקריפט. כשהמשתמש מזין את מילת המפתח של סרגל הכתובות (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-WebView.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() לביצוע משימות עם עיכוב או משימות תקופתיות. עם זאת, יכול להיות שה-APIs האלה יכשלו כי מתזמן המשימות יבטל את הטיימרים כשה-service worker יסתיים. במקום זאת, התוספים יכולים להשתמש ב-API של chrome.alarms.

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

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 את הטיפים היומיים.

מתחילים בהצהרה על סקריפט התוכן במניפסט ומוסיפים את דפוס ההתאמה התואם למסמכי העזרה של 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 או על מקש הרווח.
  3. צריך להזין את השם המלא של ה-API.
    • או לבחור מתוך רשימה של חיפושים קודמים
  4. ייפתח דף חדש עם דף העזרה של Chrome API.

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

Quick API Reference opening the runtime api reference
תוסף ה-API המהיר פותח את ה-Runtime API.

פתיחת העצה היומית

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

פתיחת הטיפ היומי ב-
תוסף API מהיר שפותח את הטיפים היומיים.

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

על סמך מה שלמדתם היום, נסו לבצע אחת מהמשימות הבאות:

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

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

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

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

מידע נוסף על היעדים

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