Declared vs optional permission — 두 grant 순간을 실용으로
~12 min · permissions, optional_permissions, chrome.permissions, user-gesture
Level 0Extension 입덕
0 XP0/56 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 회수하게 해 주는 데 유용.
User-gesture-derive 된 popup click 으로부터의 message (transitive).
timer 안에서든, alarm 안에서든, tab event 안에서든, user가 뭘 누른 게 아닌 자리에서 부르면 This function must be called during a user gesture. 하고 거절당해. 흉내 내려 들지 마. 그 거절은 버그가 아니라 보안 장치야.
Two-step UX 패턴
Optional-permission-gated feature의 깔끔한 shape:
Feature의 UI 정상 렌더 (버튼 / menu item / link).
Click 시 먼저 chrome.permissions.contains 확인. 이미 grant 면 feature 실행.
Grant 안 됐으면 chrome.permissions.request 호출. Chrome prompt 나타남.
User가 grant 하면 feature 실행 진행.
user가 거절하면 그 자리에 친절하게 한 줄 띄워 줘 — "내보내려면 Downloads 권한이 필요해. 허락하려면 다시 눌러 봐." 그리고 그 기능 버튼은 계속 보이게 둬.
이 shape이 대칭적이고 용서적. User가 언제든 회수 가능 (Chrome이 chrome://extensions/?id=... 에 Permission 탭 표시), 같은 flow 다시 trigger 해 re-grant.
Request가 사는 곳
handler는 user가 뭘 눌렀다는 걸 아는 자리라면 어디든 둬도 돼. 흔히 두는 데가 셋 있어:
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 이랑 똑같이 동작하는데, 물어보는 창이 뜬 뒤에도 안 닫히고 남아 있어.
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.
물어보는 순간 popup이 닫히는 함정. 많은 Chrome 버전에서 권한 창이 뜨면 popup이 같이 닫혀. 흐름을 "누른다 → 물어본다 → 기능을 돌린다" 로 짜 뒀다면 마지막 단계가 아예 안 돌아. popup이 이미 사라졌으니까. 그러니 실제 일은 SW로 넘기거나 (허락받은 뒤 popup이 메시지를 보내는 식으로), 안 닫히는 side panel로 옮겨서 허락 이후 동작이on 닿게.
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 — 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 함).
});
clipdeck/popup.html에 id exportBtn 의 Export 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=<ClipDeck id> → Permissions 탭, Downloads revoke, 다시 시도 — prompt 다시 나타남. Deny 시 popup status 메시지 보이는지 확인.
Hint
chrome.permissions.request 가 This 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' 로직을 두지 마. 앞에서 보여 준 대로 SW로 옮겨.
Progress
Progress is local-only — sign in to sync across devices.