Użyj geolokalizacji

Jeśli chcesz uzyskiwać w rozszerzeniu do Chrome informacje geolokalizacyjne, użyj tego samego interfejsu API platformy internetowej navigator.geolocation, który normalnie byłby dostępny w każdej witrynie. Ten artykuł został opracowany, ponieważ rozszerzenia do Chrome inaczej traktują uprawnienia dostępu do danych wrażliwych niż witryny. Geolokalizacja jest bardzo poufnymi danymi, więc przeglądarki mają pewność, że użytkownicy wiedzą, kiedy i gdzie ich dokładne położenie jest udostępniane, a także mają nad nimi pełną kontrolę.

Używanie geolokalizacji w rozszerzeniach MV3

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

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

Uprawnienia nie są jedyną różnicą. Jak wspomnieliśmy powyżej, navigator.geolocation to interfejs DOM API, który jest częścią interfejsów API tworzących witryny. W związku z tym nie jest dostępny w kontekstach instancji roboczych, takich jak skrypt service worker, który jest szkieletem rozszerzeń manifestu w wersji 3. Jednak bezwzględnie możesz nadal korzystać z usługi geolocation. Istnieją tylko niuanse związane z tym, jak i gdzie jej używa się.

Używanie geolokalizacji w mechanizmach Service Worker

W skryptach service worker nie ma obiektu navigator. Jest dostępny tylko w kontekstach, które mają dostęp do obiektu document strony. Aby uzyskać dostęp do wewnątrz skryptu service worker, użyj obiektu Offscreen Document. Zapewnia on dostęp do pliku HTML, który możesz w pakiecie z rozszerzeniem.

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

manifest.json:

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

Po dodaniu uprawnienia "offscreen" dodaj do rozszerzenia plik HTML zawierający dokument poza ekranem. W tym przypadku zawartość strony nie jest używana, więc plik może być prawie pusty. Wystarczy, że będzie to mały plik HTML, który zostanie wczytany w skrypcie.

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 też dodać ten kod do rozszerzenia. Będzie to źródło informacji geolokalizacyjnych dla skryptu service worker. Możesz przekazywać wiadomości między nim 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 uzyskać 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 otwierasz dokument poza ekranem, musisz dodać atrybut reason. Przyczyna wystąpienia geolocation nie była pierwotnie dostępna, więc określ wartość zastępczą DOM_SCRAPING i w sekcji justification wyjaśnij, jak działa kod. Te informacje są wykorzystywane w procesie weryfikacji w Chrome Web Store, aby zapewnić, że dokumenty poza ekranem są używane w prawidłowy sposób.

Po utworzeniu odniesienia do dokumentu poza ekranem możesz wysłać do niego wiadomość z prośbą o podanie zaktualizowanych 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();
}

Jeśli więc chcesz uzyskać geolokalizację z skryptu service worker, wystarczy zadzwonić pod ten adres:

const location = await getGeolocation()

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

Korzystanie z geolokalizacji w wyskakującym okienku lub w panelu bocznym jest bardzo proste. Wyskakujące okienka i panele boczne to po prostu dokumenty internetowe, więc mają dostęp do zwykłych interfejsów DOM API. Masz bezpośredni dostęp do navigator.geolocation. Jedyna różnica w stosunku do standardowych witryn polega na tym, że aby poprosić o uprawnienie "geolocation", należy użyć pola manifest.json "permission". Jeśli nie użyjesz tych uprawnień, będziesz mieć dostęp do navigator.geolocation. Każda próba jej użycia spowoduje jednak natychmiastowy błąd, tak jak w przypadku odrzucenia żądania przez użytkownika. Widać to w wyskakującym okienku.

Używanie geolokalizacji w skrypcie treści

Tak jak wyskakujące okienko, skrypt treści ma pełny dostęp do interfejsu DOM API. ale użytkownicy będą postępować zgodnie ze zwykłym procesem uzyskiwania zgody. Oznacza to, że dodanie "geolocation" do "permissions" nie zapewni Ci automatycznie dostępu do dane geolokalizacyjne. Widać to w przykładzie skryptu treści.