שימוש במיקום גיאוגרפי

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

שימוש במיקום גיאוגרפי בתוספי MV3

דפדפנים מגינים על המשתמשים באינטרנט של נתוני מיקום גיאוגרפי, באמצעות הצגת הודעה שבה הם מתבקשים להעניק למקור הספציפי הזה גישה למיקום שלהם. אותו מודל הרשאות לא תמיד מתאים לתוספים.

צילום מסך של בקשת ההרשאה שמופיעה כשאתר מבקש גישה לממשק ה-API של המיקום הגיאוגרפי
ההצעה של הרשאת מיקום גיאוגרפי

ההרשאות הן לא ההבדל היחיד. כפי שצוין למעלה, navigator.geolocation הוא API של DOM. כלומר, הוא חלק מממשקי ה-API שמרכיבים אתרים. לכן, לא ניתן לגשת אליו בתוך הקשרים של עובדים, כמו קובץ שירות (service worker) של תוספים, שהוא עמוד התווך של תוספי המניפסט v3. עם זאת, עדיין אפשר להשתמש ב-geolocation. יש דקויות מסוימות בכל מה שקשור לאופן השימוש ולמקום שבו אתם משתמשים.

שימוש במיקום גיאוגרפי ב-Service Workers

אין אובייקט navigator בתוך Service Workers. היא זמינה רק בתוך הקשרים שיש להם גישה לאובייקט document של הדף. כדי לקבל גישה בתוך קובץ שירות (service worker), צריך להשתמש ב-Offscreen Document, שמספק גישה לקובץ HTML שאפשר לאגד יחד עם התוסף.

כדי להתחיל, צריך להוסיף את "offscreen" לקטע "permissions" במניפסט.

manifest.json:

{
  "name": "My extension",
    ...
  "permissions": [
    ...
   "offscreen"
  ],
  ...
}

לאחר הוספת ההרשאה "offscreen", מוסיפים לתוסף קובץ HTML שמכיל את המסמך שלא מוצג במסך. במקרה הזה לא נעשה שימוש בתוכן של הדף, כך שהקובץ אמור להיות כמעט ריק. הוא צריך להיות קובץ HTML קטן שנטען בסקריפט.

offscreen.html:

<!doctype html>
<title>offscreenDocument</title>
<script src="offscreen.js"></script>

שומרים את הקובץ הזה ברמה הבסיסית (root) של הפרויקט בתור offscreen.html.

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

offscreen.js:

chrome.runtime.onMessage.addListener(handleMessages);
function handleMessages(message, sender, sendResponse) {
  // Return early if this message isn't meant for the offscreen document.
  if (message.target !== 'offscreen') {
    return;
  }

  if (message.type !== 'get-geolocation') {
    console.warn(`Unexpected message type received: '${message.type}'.`);
    return;
  }

  // You can directly respond to the message from the service worker with the
  // provided `sendResponse()` callback. But in order to be able to send an async
  // response, you need to explicitly return `true` in the onMessage handler
  // As a result, you can't use async/await here. You'd implicitly return a Promise.
  getLocation().then((loc) => sendResponse(loc));

  return true;
}

// getCurrentPosition() returns a prototype-based object, so the properties
// end up being stripped off when sent to the service worker. To get
// around this, create a deep clone.
function clone(obj) {
  const copy = {};
  // Return the value of any non true object (typeof(null) is "object") directly.
  // null will throw an error if you try to for/in it. Just return
  // the value early.
  if (obj === null || !(obj instanceof Object)) {
    return obj;
  } else {
    for (const p in obj) {
      copy[p] = clone(obj[p]);
    }
  }
  return copy;
}

async function getLocation() {
  // Use a raw Promise here so you can pass `resolve` and `reject` into the
  // callbacks for getCurrentPosition().
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(
      (loc) => resolve(clone(loc)),
      // in case the user doesnt have/is blocking `geolocation`
      (err) => reject(err)
    );
  });
}

עכשיו, אתם יכולים לגשת למסמך מחוץ למסך ב-Service Worker.

chrome.offscreen.createDocument({
  url: 'offscreen.html',
  reasons: [chrome.offscreen.Reason.GEOLOCATION || chrome.offscreen.Reason.DOM_SCRAPING],
  justification: 'geolocation access',
});

לתשומת ליבכם: כשניגשים למסמך שלא מופיע במסך, צריך לכלול reason. הסיבה geolocation לא הייתה זמינה במקור. לכן צריך לציין חלופה ל-DOM_SCRAPING, ולהסביר בקטע justification מה הקוד עושה בפועל. המידע הזה משמש את תהליך הבדיקה של חנות האינטרנט של Chrome כדי להבטיח שמסמכים מחוץ למסך משמשים למטרה חוקית.

ברגע שיש לכם הפניה למסמך שלא במסך, אתם יכולים לשלוח לו הודעה ולבקש ממנו לספק לכם פרטים מעודכנים על המיקום הגיאוגרפי.

service_worker.js:

const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html';
let creating; // A global promise to avoid concurrency issues

chrome.runtime.onMessage.addListener(handleMessages);

async function getGeolocation() {
  await setupOffscreenDocument(OFFSCREEN_DOCUMENT_PATH);
  const geolocation = await chrome.runtime.sendMessage({
    type: 'get-geolocation',
    target: 'offscreen'
  });
  await closeOffscreenDocument();
  return geolocation;
}

async function hasDocument() {
  // Check all windows controlled by the service worker to see if one
  // of them is the offscreen document with the given path
  const offscreenUrl = chrome.runtime.getURL(OFFSCREEN_DOCUMENT_PATH);
  const matchedClients = await clients.matchAll();

  return matchedClients.some(c => c.url === offscreenUrl)
}

async function setupOffscreenDocument(path) {
  //if we do not have a document, we are already setup and can skip
  if (!(await hasDocument())) {
    // create offscreen document
    if (creating) {
      await creating;
    } else {
      creating = chrome.offscreen.createDocument({
        url: path,
        reasons: [chrome.offscreen.Reason.GEOLOCATION || chrome.offscreen.Reason.DOM_SCRAPING],
        justification: 'add justification for geolocation use here',
      });

      await creating;
      creating = null;
    }
  }
}

async function closeOffscreenDocument() {
  if (!(await hasDocument())) {
    return;
  }
  await chrome.offscreen.closeDocument();
}

עכשיו, בכל פעם שתרצו לקבל את המיקום הגיאוגרפי מ-Service Worker צריך רק להתקשר:

const location = await getGeolocation()

שימוש במיקום גיאוגרפי בחלון קופץ או בחלונית צדדית

השימוש במיקום גיאוגרפי בתוך חלון קופץ או בחלונית הצדדית הוא פשוט מאוד. חלונות קופצים וחלוניות צדדיות הם רק מסמכי אינטרנט ולכן יש להם גישה לממשקי ה-API הרגילים של DOM. יש לך גישה ישירה אל navigator.geolocation. ההבדל היחיד לעומת אתרים רגילים הוא שצריך להשתמש בשדה manifest.json "permission" כדי לבקש את ההרשאה "geolocation". אם ההרשאה לא תישלח, עדיין תהיה לך גישה אל navigator.geolocation. עם זאת, כל ניסיון להשתמש בו יגרום לשגיאה מיידית, בדיוק כמו במקרה שהמשתמש דחה את הבקשה. אפשר לראות את זה בדוגמה בחלון קופץ.

שימוש במיקום גיאוגרפי בסקריפט של תוכן

בדיוק כמו בחלון קופץ, לסקריפט תוכן יש גישה מלאה ל-DOM API. עם זאת, משתמשים יעברו את תהליך ההרשאה הרגיל של המשתמש. המשמעות היא שהוספה של "geolocation" אל "permissions" לא תעניק לך באופן אוטומטי גישה לחשבון של המשתמשים מידע על מיקום גיאוגרפי. אפשר לראות את זה בדוגמה של סקריפט התוכן.