Используйте геолокацию

Если вы хотите получить информацию о геолокации в своем расширении Chrome, используйте тот же API веб-платформы navigator.geolocation , который обычно используется на любом веб-сайте. Эта статья существует, поскольку расширения Chrome обрабатывают разрешения на доступ к конфиденциальным данным иначе, чем веб-сайты. Геолокация — это очень конфиденциальные данные, поэтому браузеры гарантируют, что пользователи полностью осведомлены и контролируют, когда и где передается их точное местоположение.

Используйте геолокацию в расширениях MV3

В Интернете браузеры защищают данные о геолокации пользователей, показывая запрос на предоставление этому конкретному источнику доступа к их местоположению. Одна и та же модель разрешений не всегда подходит для расширений.

Снимок экрана с запросом разрешения, который вы видите, когда веб-сайт запрашивает доступ к API геолокации.
Запрос на разрешение геолокации

Разрешения — не единственное различие. Как упоминалось выше, navigator.geolocation — это DOM API, то есть часть API, из которых состоят веб-сайты. В результате он недоступен внутри рабочих контекстов, например, рабочий процесс службы расширений , который является основой расширений манифеста v3. Однако вы все равно можете использовать geolocation . Просто есть нюансы с тем, как и где его использовать.

Используйте геолокацию в сервис-воркерах

Внутри сервис-воркеров нет объекта navigator . Он доступен только внутри контекстов, имеющих доступ к объекту document страницы. Чтобы получить доступ внутри сервис-воркера, используйте Offscreen Document , который предоставляет доступ к HTML-файлу, который вы можете связать с вашим расширением.

Для начала добавьте "offscreen" в раздел "permissions" манифеста.

манифест.json:

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

После добавления разрешения "offscreen" добавьте в расширение HTML-файл, содержащий ваш закадровый документ. В этом случае не используется содержимое страницы, поэтому это может быть почти пустой файл. Это просто должен быть небольшой HTML-файл, который загружается в ваш скрипт.

offscreen.html:

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

Сохраните этот файл в корне вашего проекта как offscreen.html .

Как уже упоминалось, вам понадобится скрипт offscreen.js . Вам также необходимо связать это с вашим расширением. Это будет источник информации о геолокации для сервисного работника. Вы можете передавать сообщения между ним и вашим сервисным работником.

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

Теперь вы готовы получить доступ к закадровому документу в сервис-воркере.

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, чтобы гарантировать, что закадровые документы используются в законных целях.

Получив ссылку на внеэкранный документ, вы можете отправить ему сообщение с просьбой предоставить вам обновленную информацию о геолокации.

сервис_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();
}

Итак, теперь, когда вы захотите получить геолокацию от своего сервис-воркера, вам просто нужно позвонить:

const location = await getGeolocation()

Используйте геолокацию во всплывающем окне или на боковой панели

Использовать геолокацию во всплывающем окне или боковой панели очень просто. Всплывающие окна и боковые панели — это просто веб-документы, поэтому они имеют доступ к обычным API-интерфейсам DOM. Вы можете получить прямой доступ navigator.geolocation . Единственное отличие от стандартных веб-сайтов заключается в том, что вам нужно использовать поле "permission" в manifest.json , чтобы запросить разрешение "geolocation" . Если вы не включите разрешение, у вас все равно будет доступ к navigator.geolocation . Однако любая попытка его использования приведет к немедленной ошибке, как если бы пользователь отклонил запрос. Вы можете увидеть это в образце всплывающего окна .

Использование геолокации в скрипте контента

Как и всплывающее окно, сценарий контента имеет полный доступ к DOM API; однако пользователи будут проходить обычный процесс получения разрешений пользователя. Это означает, что добавление "geolocation" к вашим "permissions" не предоставит вам автоматически доступ к информации о геолокации пользователей. Вы можете увидеть это в примере сценария контента .