Użyj geolokalizacji

Jeśli chcesz uzyskać dane geolokalizacji w rozszerzeniu do Chrome, użyj tego samego interfejsu API platformy internetowej navigator.geolocation, z którego zwykle korzysta każda witryna. Ten artykuł istnieje, ponieważ rozszerzenia do Chrome obsługują uprawnienia do dostępu do danych wrażliwych inaczej niż witryny. Geolokalizacja to dane bardzo wrażliwe, więc przeglądarki zapewniają użytkownikom pełną świadomość i kontrolę nad tym, kiedy i gdzie udostępniane są ich dokładne położenie.

Używanie geolokalizacji w rozszerzeniach MV3

W internecie przeglądarki chronią dane geolokalizacyjne użytkowników, wyświetlając komunikat z prośbą o przyznanie dostępu do lokalizacji określonego źródła. Ten sam model uprawnień nie zawsze jest odpowiedni w przypadku rozszerzeń.

Zrzut ekranu z prośbą o uprawnienia, która wyświetla się, gdy strona prosi o dostęp do interfejsu API geolokalizacji
Prośba o uprawnienia do geolokalizacji

Uprawnienia to nie jedyna różnica. Jak już wspomnieliśmy, navigator.geolocation to interfejs API DOM, który jest częścią interfejsów API tworzących strony internetowe. W efekcie nie jest on dostępny w kontekstach instancji roboczych, takich jak skrypt service worker rozszerzenia, który jest szkieletem rozszerzeń platformy Manifest V3. Jednak nadal możesz używać geolocation. Istotne są jedynie niuanse związane z tym, jak i gdzie go używasz.

Używaj geolokalizacji w mechanizmach Service Worker

W skryptach service worker nie ma obiektu navigator. Jest dostępna tylko w kontekstach, które mają dostęp do obiektu document strony. Aby uzyskać dostęp do skryptu service worker, użyj Offscreen Document – pliku HTML, który możesz dołączyć do rozszerzenia.

Aby rozpocząć, dodaj "offscreen" do sekcji "permissions" w pliku manifestu.

manifest.json:

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

Po dodaniu uprawnienia "offscreen" dodaj do rozszerzenia plik HTML zawierający dokument niewidoczny na ekranie. W tym przypadku nie jest używana żadna zawartość strony, więc plik może być prawie pusty. Wystarczy, że będzie to mały plik HTML wczytywany do skryptu.

offscreen.html:

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

Zapisz ten plik w katalogu głównym projektu jako offscreen.html.

Jak już wspomnieliśmy, potrzebujesz skryptu o nazwie offscreen.js. Musisz go też połączyć z rozszerzeniem. Będzie to źródło informacji o geolokalizacji dla skryptu service worker. Możesz przekazywać wiadomości między nią a skryptem 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)
    );
  });
}

Gdy to zrobisz, możesz zacząć uzyskiwać dostęp do dokumentu poza ekranem w skrypcie service worker.

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

Pamiętaj, że gdy chcesz wyświetlić dokument poza ekranem, musisz dołączyć element reason. Przyczyna geolocation nie była pierwotnie dostępna, dlatego podaj wartość zastępczą DOM_SCRAPING i wyjaśnij w sekcji justification, jak faktycznie działa kod. Informacje te są wykorzystywane w ramach procesu weryfikacji w Chrome Web Store w celu zapewnienia, że dokumenty widoczne poza ekranem są używane w prawidłowym celu.

Gdy już będziesz mieć odniesienia do dokumentu poza ekranem, możesz wysłać do niego wiadomość z prośbą o udostępnienie aktualnych informacji o geolokalizacji.

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();
}

Teraz za każdym razem, gdy chcesz uzyskać dane geolokalizacji z poziomu skryptu service worker, wystarczy zadzwonić pod numer:

const location = await getGeolocation()

Użyj geolokalizacji w wyskakującym okienku lub w panelu bocznym

Korzystanie z geolokalizacji w wyskakującym okienku lub panelu bocznym jest bardzo proste. Wyskakujące okienka i panele boczne to tylko dokumenty internetowe, dlatego mają dostęp do zwykłych interfejsów API DOM. Możesz uzyskać bezpośredni dostęp do usługi navigator.geolocation. Jedyną różnicą w stosunku do witryn standardowych jest to, że aby poprosić o uprawnienie "geolocation", musisz użyć pola manifest.json "permission". Jeśli nie przyznasz tych uprawnień, nadal będziesz mieć dostęp do usługi navigator.geolocation. Jednak każda próba jego użycia spowoduje natychmiastowy błąd, tak samo jak w przypadku odrzucenia żądania przez użytkownika. Możesz to zobaczyć w przykładowym wyskakującym okienku.

Używanie geolokalizacji w skrypcie treści

Podobnie jak w przypadku wyskakujących okienek, skrypt treści ma pełny dostęp do interfejsu DOM API; użytkownicy przechodzą jednak przez zwykły proces zgody użytkownika. Oznacza to, że dodanie adresu "geolocation" do: "permissions" nie zapewni Ci automatycznie dostępu do danych geolokalizacji użytkowników. Widać to w przykładzie skryptu treści.