Standortbestimmung verwenden

Wenn Sie Informationen zur Standortbestimmung in Ihrer Chrome-Erweiterung abrufen möchten, verwenden Sie dieselbe navigator.geolocation Web Platform API wie jede andere Website. Dieser Artikel liegt daran, dass Berechtigungen für den Zugriff auf sensible Daten in Chrome-Erweiterungen anders verarbeitet werden als bei Websites. Die Standortbestimmung ist sehr sensible Daten. Deshalb sorgen Browser dafür, dass Nutzer ganz genau wissen und steuern können, wann und wo ihr genauer Standort geteilt wird.

Standortbestimmung in MV3-Erweiterungen verwenden

Im Web schützen Browser die Daten zur Standortbestimmung von Nutzern, indem sie die Nutzer auffordern, dem bestimmten Ursprungsort Zugriff auf ihren Standort zu gewähren. Dasselbe Berechtigungsmodell ist nicht immer für Erweiterungen geeignet.

Screenshot der Berechtigungsaufforderung, die angezeigt wird, wenn eine Website Zugriff auf die API zur Standortbestimmung anfordert
Aufforderung zur Berechtigung zur Standortbestimmung

Berechtigungen sind nicht der einzige Unterschied. Wie bereits erwähnt, ist navigator.geolocation eine DOM API. Das bedeutet, dass sie Teil der APIs ist, aus denen sich Websites zusammensetzen. Daher ist sie in Worker-Kontexten nicht zugänglich, z. B. im Extension Service Worker, der das Herzstück von Manifest V3-Erweiterungen ist. Du kannst geolocation aber trotzdem verwenden. Es gibt nur gewisse Nuancen, wie und wo sie eingesetzt wird.

Standortbestimmung für Service Worker verwenden

In Service Workern befindet sich kein navigator-Objekt. Sie ist nur in Kontexten verfügbar, die Zugriff auf das document-Objekt einer Seite haben. Wenn Sie innerhalb eines Service Workers Zugriff erhalten möchten, verwenden Sie einen Offscreen Document, der Zugriff auf eine HTML-Datei bietet, die Sie mit Ihrer Erweiterung bündeln können.

Füge als Erstes "offscreen" zum Abschnitt "permissions" deines Manifests hinzu.

manifest.json:

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

Nachdem du die Berechtigung „"offscreen"“ hinzugefügt hast, füge deiner Erweiterung eine HTML-Datei mit dem nicht sichtbaren Dokument hinzu. In diesem Fall werden keine Inhalte der Seite verwendet. Die Datei kann also fast leer sein. Es muss sich nur um eine kleine HTML-Datei handeln, die in Ihr Skript geladen wird.

offscreen.html:

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

Speichern Sie diese Datei im Stammverzeichnis Ihres Projekts als offscreen.html.

Wie bereits erwähnt, benötigen Sie ein Skript mit dem Namen offscreen.js. Sie müssen sie auch mit Ihrer Erweiterung bündeln. Er ist die Quelle der Standortinformationen für den Service Worker. Sie können Nachrichten zwischen diesem Dienst und Ihrem Service Worker weitergeben.

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

Jetzt sind Sie bereit, auf das Offscreen-Dokument im Service Worker zuzugreifen.

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

Wenn Sie auf ein nicht sichtbares Dokument zugreifen, müssen Sie ein reason einfügen. Der Grund „geolocation“ war ursprünglich nicht verfügbar. Geben Sie daher eine Alternative für DOM_SCRAPING an und erläutern Sie im Abschnitt justification, was der Code tatsächlich bewirkt. Diese Informationen werden im Überprüfungsprozess des Chrome Web Store verwendet, um sicherzustellen, dass nicht sichtbare Dokumente für einen gültigen Zweck verwendet werden.

Sobald Sie einen Verweis auf das nicht sichtbare Dokument haben, können Sie eine Nachricht senden, in der Sie darum gebeten werden, aktualisierte Informationen zur Standortbestimmung bereitzustellen.

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

Wenn Sie die Standortbestimmung durch Ihren Service Worker erhalten möchten, müssen Sie jetzt nur noch anrufen:

const location = await getGeolocation()

Standortbestimmung in einem Pop-up oder in einer Seitenleiste verwenden

Die Standortbestimmung in einem Pop-up oder einer seitlichen Seitenleiste ist sehr einfach. Pop-ups und Seitenleisten sind einfache Webdokumente und haben daher Zugriff auf die normalen DOM-APIs. Sie können direkt auf navigator.geolocation zugreifen. Der einzige Unterschied zu Standardwebsites besteht darin, dass Sie die Berechtigung "geolocation" über das Feld manifest.json "permission" anfordern müssen. Wenn Sie die Berechtigung nicht erteilen, haben Sie weiterhin Zugriff auf navigator.geolocation. Jeder Versuch, sie zu verwenden, führt jedoch sofort zu einem Fehler, so als ob der Nutzer die Anfrage abgelehnt hätte. Ein Beispiel dafür sehen Sie im Pop-up-Beispiel.

Standortbestimmung in einem Inhaltsskript

Genau wie ein Pop-up-Fenster hat ein Inhaltsskript vollen Zugriff auf die DOM API. Die Nutzer durchlaufen jedoch den normalen Ablauf für Nutzerberechtigungen. Wenn du also "geolocation" zu "permissions" hinzufügst, erhältst du nicht automatisch Zugriff auf die Geolocation-Informationen der Nutzer. Ein Beispiel für ein Content-Skript finden Sie hier.