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

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

סקירה כללית

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

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

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

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

עכשיו מפרקים את התוסף כדי ללמוד איפה נמצאות השגיאות. אחת מהדרכים לעשות זאת היא למחוק את 'js .' מהייבוא של './sw-omnibox.js' בקובץ service-worker.js. ל-Chrome לא תהיה אפשרות לרשום את קובץ השירות.

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

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

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

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

בדוגמה הזו נשתמש ב-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() משמשות בדרך כלל לביצוע עיכובים או פעולות תקופתיות למשימות סיווג. עם זאת, יכול להיות שה-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;
}

השלב האחרון הוא להוסיף 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;
  }
});

בודקים שהכול עובד

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

התוכן של תיקיית התוסף: תיקיית תמונות, manifest.json,‏ service-worker.js,‏ sw-omnibox.js,‏ sw-tips.js ו-content.js

טעינה מקומית של התוסף

כדי לטעון תוסף unpacked במצב פיתוח, פועלים לפי השלבים שמפורטים בקטע Hello World.

פתיחת דף עזר

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

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

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

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

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

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

🎯 שיפורים פוטנציאליים

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

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

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

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

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

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

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