Utiliser la géolocalisation

Si vous souhaitez obtenir des informations de géolocalisation dans votre extension Chrome, utilisez la même API de plate-forme Web navigator.geolocation que n'importe quel site Web. Cet article existe, car les extensions Chrome ne gèrent pas les autorisations d'accès aux données sensibles de la même façon que les sites Web. La géolocalisation est une donnée très sensible. C'est pourquoi les navigateurs s'assurent que les utilisateurs savent exactement à quel moment et à quel endroit leur position exacte est partagée, et qu'ils contrôlent leur emplacement.

Utiliser la géolocalisation dans les extensions MV3

Sur le Web, les navigateurs protègent les données de géolocalisation des utilisateurs en affichant une invite leur demandant d'autoriser cette origine spécifique à accéder à leur position. Le même modèle d'autorisation n'est pas toujours approprié pour les extensions.

Une capture d'écran de l'invite d'autorisation qui s'affiche lorsqu'un site Web demande à accéder à l'API de géolocalisation
Invite de l'autorisation de géolocalisation

Les autorisations ne sont pas la seule différence. Comme indiqué ci-dessus, navigator.geolocation est une API DOM, c'est-à-dire un élément qui fait partie des API qui composent les sites Web. Il n'est donc pas accessible dans les contextes de nœud de calcul, comme le service worker d'extension, qui est l'épine dorsale des extensions Manifest V3. Toutefois, vous pouvez tout à fait utiliser geolocation. Il existe juste des nuances quant à la manière et à l'endroit où vous l'utilisez.

Utiliser la géolocalisation dans les service workers

Les service workers ne contiennent aucun objet navigator. Elle n'est disponible que dans les contextes qui ont accès à l'objet document d'une page. Pour obtenir l'accès à l'intérieur d'un service worker, utilisez un Offscreen Document, qui donne accès à un fichier HTML que vous pouvez regrouper avec votre extension.

Pour commencer, ajoutez "offscreen" à la section "permissions" de votre fichier manifeste.

manifest.json:

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

Après avoir ajouté l'autorisation "offscreen", ajoutez à votre extension un fichier HTML incluant votre document hors écran. Dans ce cas, nous n'utilisons aucun contenu de la page. Le fichier peut donc être presque vide. Il s'agit simplement d'un petit fichier HTML qui se charge dans votre script.

offscreen.html:

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

Enregistrez ce fichier à la racine de votre projet sous le nom offscreen.html.

Comme indiqué précédemment, vous avez besoin d'un script appelé offscreen.js. Vous devez également l'associer à votre extension. Il s'agit de la source des informations de géolocalisation pour le service worker. Vous pouvez transmettre des messages à votre 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)
    );
  });
}

Une fois cela en place, vous êtes prêt à accéder au document hors écran dans le service worker.

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

Notez que lorsque vous accédez à un document hors écran, vous devez inclure un reason. Le motif geolocation n'était pas disponible à l'origine. Vous devez donc spécifier une valeur de remplacement de DOM_SCRAPING et expliquer dans la section justification ce que fait réellement le code. Ces informations sont utilisées lors du processus d'examen du Chrome Web Store pour s'assurer que les documents hors écran sont utilisés à des fins légitimes.

Une fois que vous avez fait référence au document hors écran, vous pouvez lui envoyer un message pour lui demander de fournir des informations de géolocalisation à jour.

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

Ainsi, dès que vous souhaitez obtenir la géolocalisation de votre service worker, il vous suffit d'appeler:

const location = await getGeolocation()

Utiliser la géolocalisation dans un pop-up ou un panneau latéral

L'utilisation de la géolocalisation dans une fenêtre pop-up ou un panneau latéral est très simple. Les fenêtres pop-up et les panneaux latéraux ne sont que des documents Web et ont donc accès aux API DOM normales. Vous pouvez accéder directement à navigator.geolocation. La seule différence par rapport aux sites Web standards est que vous devez utiliser le champ "permission" manifest.json pour demander l'autorisation "geolocation". Si vous n'incluez pas l'autorisation, vous aurez toujours accès à navigator.geolocation. Cependant, toute tentative de son utilisation entraînera une erreur immédiate, comme si l'utilisateur avait refusé la requête. Vous pouvez le constater dans l'exemple pop-up.

Utilisation de la géolocalisation dans un script de contenu

Tout comme dans une fenêtre pop-up, un script de contenu dispose d'un accès complet à l'API DOM. Toutefois, les utilisateurs suivent le flux d'autorisations normal de l'utilisateur. Par conséquent, si vous ajoutez "geolocation" à votre "permissions", vous ne pourrez pas accéder automatiquement aux informations de géolocalisation des utilisateurs. Pour voir cela, consultez l'exemple de script de contenu.