위치정보 사용

Chrome 확장 프로그램에서 위치정보를 가져오려면 일반적으로 웹사이트에서 사용하는 것과 동일한 navigator.geolocation 웹 플랫폼 API를 사용합니다. Chrome 확장 프로그램에서는 민감한 정보에 액세스하는 권한을 웹사이트와 다르게 처리하므로 이 도움말을 참고하세요. 위치정보는 매우 민감한 데이터이므로 브라우저는 사용자가 정확한 위치가 공유되는 시기와 위치를 완전히 인식하고 제어할 수 있도록 보장합니다.

MV3 확장 프로그램에서 위치정보 사용

웹에서 브라우저는 특정 출처에 사용자 위치에 액세스할 수 있는 권한을 부여하라는 메시지를 표시하여 사용자의 위치정보 데이터를 보호합니다. 동일한 권한 모델이 확장 프로그램에 항상 적합한 것은 아닙니다.

웹사이트에서 Geolocation API 액세스를 요청할 때 표시되는 권한 메시지 스크린샷
위치정보 권한 메시지

권한에만 차이가 없습니다. 위에서 언급했듯이 navigator.geolocationDOM API입니다. 즉, 웹사이트를 구성하는 API의 일부입니다. 따라서 매니페스트 v3 확장 프로그램의 백본인 확장 프로그램 서비스 워커와 같은 worker 컨텍스트 내에서는 액세스할 수 없습니다. 하지만 geolocation를 사용할 수는 있습니다. 단지 어디에서 어떻게 사용하는지에 차이가 있을 뿐입니다.

서비스 워커에서 위치정보 사용

서비스 워커 내에는 navigator 객체가 없습니다. 페이지의 document 객체에 액세스할 수 있는 컨텍스트 내에서만 사용할 수 있습니다. 서비스 워커 내에서 액세스하려면 Offscreen Document를 사용합니다. 이는 확장 프로그램과 함께 번들로 묶을 수 있는 HTML 파일에 대한 액세스를 제공합니다.

시작하려면 매니페스트의 "permissions" 섹션에 "offscreen"를 추가합니다.

manifest.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 웹 스토어의 검토 과정에서 오프스크린 문서가 유효한 용도로 사용되고 있는지 확인하는 데 사용됩니다.

오프스크린 문서에 대한 참조가 있으면 해당 문서에 메시지를 보내 업데이트된 위치정보 정보를 제공하도록 요청할 수 있습니다.

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

따라서 이제 서비스 워커에서 위치정보를 가져올 때마다 다음을 호출하기만 하면 됩니다.

const location = await getGeolocation()

팝업 또는 측면 패널에서 위치정보 사용

팝업 또는 측면 패널 내에서 위치정보를 사용하는 방법은 매우 간단합니다. 팝업 및 측면 패널은 웹 문서일 뿐이므로 일반 DOM API에 액세스할 수 있습니다. navigator.geolocation에 직접 액세스할 수 있습니다. 표준 웹사이트와의 유일한 차이점은 manifest.json "permission" 필드를 사용하여 "geolocation" 권한을 요청해야 한다는 것입니다. 권한을 포함하지 않아도 navigator.geolocation에 계속 액세스할 수 있습니다. 그러나 이를 사용하려고 하면 사용자가 요청을 거부한 것과 마찬가지로 즉각적인 오류가 발생합니다. 팝업 샘플에서 확인할 수 있습니다.

콘텐츠 스크립트에서 위치정보 사용

팝업과 마찬가지로 콘텐츠 스크립트는 DOM API에 대한 전체 액세스 권한을 보유합니다. 하지만 사용자는 일반적인 사용자 권한 흐름을 거치게 됩니다. 즉, "geolocation""permissions"에 추가해도 사용자의 위치정보에 자동으로 액세스할 수 있는 것은 아닙니다. 콘텐츠 스크립트 샘플에서 확인할 수 있습니다.