C.W.K.
Stream
Lesson 02 of 05 · published

Declared vs optional permission — 두 grant 순간을 실용으로

~12 min · permissions, optional_permissions, chrome.permissions, user-gesture

Level 0Extension 입덕
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Declared permission 은 입장료. Optional permission 은 나중에 파는 upgrade. Lesson 2 가 chrome.permissions API 가 요청 defer 하고, feature gate 하고, '아니' 에서 복구하게 해 주는 법."

chrome.permissions API

네 method 가 runtime 이야기 cover:

  • chrome.permissions.contains({ permissions?, origins? }) — extension 이 현재 이것들 가졌어? Boolean 반환. 저렴, sync 느낌 (Promise-based 지만 즉시 resolve).
  • chrome.permissions.request({ permissions?, origins? }) — user 한테 지금 이것들 grant 요청. user-gesture handler 안에서 호출 필수. Grant 면 true, deny 나 dismiss 면 false 반환.
  • chrome.permissions.remove({ permissions?, origins? }) — 이전에 가졌던 permission drop. User 가 이전에 grant 한 upgrade 회수하게 해 주는 데 유용.
  • chrome.permissions.getAll() — full snapshot. { permissions: string[], origins: string[] } 반환.

Plus 변경 event: chrome.permissions.onAdded.addListenerchrome.permissions.onRemoved.addListener. Polling 없이 grant / 회수에 반응 원하면 SW 에서 구독.

User-gesture 제약

chrome.permissions.request 가 Chrome 이 user gesture 로 인정하는 handler 안에서만 동작:

  • Popup / side panel / options page 요소의 click handler.
  • chrome.action.onClicked, chrome.commands.onCommand, chrome.contextMenus.onClicked.
  • User-gesture-derive 된 popup click 으로부터의 message (transitive).

Timer, alarm, tabs event, 다른 non-gesture context 에서 호출 시 This function must be called during a user gesture. 로 reject. Fake 시도 안 함 — rejection 은 보안 feature, 버그 아님.

Two-step UX 패턴

Optional-permission-gated feature 의 깔끔한 shape:

  1. Feature 의 UI 정상 렌더 (버튼 / menu item / link).
  2. Click 시 먼저 chrome.permissions.contains 확인. 이미 grant 면 feature 실행.
  3. Grant 안 됐으면 chrome.permissions.request 호출. Chrome prompt 나타남.
  4. User 가 grant 하면 feature 실행 진행.
  5. User 가 deny 하면 친절한 inline message — "ClipDeck 이 export 위해 Downloads permission 필요. Grant 위해 재시도." — 표시하고 feature 버튼 visible 유지.

이 shape 이 대칭적이고 용서적. User 가 언제든 회수 가능 (Chrome 이 chrome://extensions/?id=... 에 Permission 탭 표시), 같은 flow 다시 trigger 해 re-grant.

Request 가 사는 곳

Handler 가 user-gesture access 가진 어디든 가능. 세 흔한 home:

  • Popup 버튼 — popup.js click handler. Popup 이 대부분 chrome.permissions.request flow 에 auto-close, grant 후 계속해야 하면 popup 다시 열기.
  • Options page 버튼 — 보통 opt-in feature 구성의 home. Settings page 가 request 동안 화면에 persist, 가장 부드러운 UX.
  • Side panel 버튼 — popup 과 같게 동작하지만 prompt 후 열려 있음.

SW 가 request 직접 시작 못 함 (SW 에 user gesture 없음), 하지만 그 surface 중 하나에서 메시지 받고 surface 가 grant 확인 후 downstream work trigger CAN.

Optional host 생김새

optional_host_permissions 가 URL pattern 에 같은 방식 동작. ClipDeck 이 install 시 user 가 가입 안 한 site 에 inject 필요할 때 사용. 예: popup 의 "이 site 에서 ClipDeck 허용" 버튼이 https://<current-host>/* 요청. Chrome 이 "ClipDeck 이 이 site 의 데이터 read 와 change 허용?" dialog 표시. User 가 Allow click 후부터 content script 가 거기 auto-inject.

Declared = essential, install 시 지불. Optional = nice-to-have, user 가 손 뻗을 때 지불. chrome.permissions.contains + chrome.permissions.request + graceful 'deny' 경로 가 full UX.
Request 시 popup-close 함정. 많은 Chrome 에서 permission prompt 가 popup 닫음. Popup flow 가 "click → request → run feature" 였으면, 'run feature' 코드 경로 실행 안 됨 — popup 사라짐. 실제 feature work 를 SW (popup 이 grant 후 메시지) 나 side panel (열린 채 유지) 로 옮겨 post-grant action 닿게.

Code

popup.js — permission 확보 후 SW 에 work 위임·javascript
// popup.js — chrome.permissions.request 로 feature-gated
async function ensureDownloadsPermission() {
  const has = await chrome.permissions.contains({ permissions: ["downloads"] });
  if (has) return true;
  return chrome.permissions.request({ permissions: ["downloads"] });
}

document.getElementById("exportBtn").addEventListener("click", async () => {
  const ok = await ensureDownloadsPermission();
  if (!ok) {
    document.getElementById("exportStatus").textContent =
      "Downloads permission denied. Click Export again to retry.";
    return;
  }
  // 실제 export trigger. Popup 이 prompt 에 닫힐 수 있어, 더 안전한 패턴은
  // SW 한테 메시지 보내고 다운로드 소유하게.
  await chrome.runtime.sendMessage({ type: "exportClips" });
  document.getElementById("exportStatus").textContent = "Exporting…";
});
background.js — popup 이 grant 확인 후 SW 가 chrome.downloads 소유·javascript
// background.js — SW 가 실제 chrome.downloads 호출 처리
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message?.type !== "exportClips") return;
  (async () => {
    const has = await chrome.permissions.contains({ permissions: ["downloads"] });
    if (!has) {
      sendResponse({ ok: false, reason: "no-downloads-permission" });
      return;
    }
    const { clips = [] } = await chrome.storage.local.get("clips");
    const json = JSON.stringify(clips, null, 2);
    const url = `data:application/json;base64,${btoa(unescape(encodeURIComponent(json)))}`;
    await chrome.downloads.download({
      url,
      filename: `clipdeck-export-${new Date().toISOString().slice(0, 10)}.json`,
      saveAs: true,
    });
    sendResponse({ ok: true, count: clips.length });
  })();
  return true;
});
background.js — live UI 반응성 위해 onAdded / onRemoved listen·javascript
// background.js — user 가 어떤 permission grant 나 revoke 할 때 반응
chrome.permissions.onAdded.addListener((perms) => {
  console.log("[ClipDeck SW] permissions granted:", perms.permissions, perms.origins);
});

chrome.permissions.onRemoved.addListener((perms) => {
  console.log("[ClipDeck SW] permissions revoked:", perms.permissions, perms.origins);
  // 예: downloads 가 revoke 됐으면 popup 의 Export 버튼 disable
  // (popup 이 매 open 시 chrome.permissions.contains 어쨌든 re-read 함).
});

External links

Exercise

clipdeck/popup.html 에 id exportBtnExport Clips 버튼과 status div exportStatus 추가. 첫 번째 code block 을 clipdeck/popup.js 에, 두 번째를 clipdeck/background.js 에 추가. Reload. Popup 열기, Export Clips click. Chrome 이 'Add Downloads' prompt 표시. Allow click — clip export 의 Save dialog 나타남. Popup 다시 열기; permission 이 이제 grant 됐으니, 두 번째 export 는 one click. 다음 chrome://extensions/?id= → Permissions 탭, Downloads revoke, 다시 시도 — prompt 다시 나타남. Deny 시 popup status 메시지 보이는지 확인.
Hint
chrome.permissions.requestThis function must be called during a user gesture 로 reject 하면, user gesture context 잃은 것 — 보통 request 호출 전 다른 거 await 해서. Handler 에서 request 를 먼저 호출, 다른 work 는 그 후. chrome.downloads.download 가 prompt 가 Allow 보여줬는데도 permission not granted error 면 SW 의 permission snapshot 이 stale — SW handler 시작에서 chrome.permissions.contains 다시 호출하면 현재 grant read. Popup-close 함정이 여기 물어: popup.js 의 request 뒤에 'continue export' logic 두지 안 함; 보인 대로 SW 로 옮기기.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.