본문으로 건너뛰기

권한 처리 패턴

clipboard, geolocation, camera, microphone, contacts, photos 같은 민감한 디바이스 기능은 모두 동일한 권한 모델을 공유합니다. 이 가이드는 그 모델을 한 곳에서 정리합니다 — 각 API 페이지의 "권한" 섹션은 이 문서를 역참조해서 중복을 피합니다.

비공식 문서

이 페이지는 커뮤니티가 작성한 설명입니다. SDK의 동작은 상위의 @apps-in-toss/web-framework 배포본을 기준으로 합니다.

권한 모델 한눈에 보기

개념타입설명
PermissionName'clipboard' | 'contacts' | 'photos' | 'geolocation' | 'camera' | 'microphone'권한이 부여되는 단위. 한 네임스페이스의 모든 메서드는 같은 PermissionName을 공유합니다 (예: getClipboardText, setClipboardText 모두 clipboard).
PermissionStatus'notDetermined' | 'denied' | 'allowed'현재 사용자가 해당 권한에 대해 어떤 상태인지. 첫 진입은 notDetermined, 다이얼로그에서 거절했으면 denied, 승인했으면 allowed.
권한이 필요한 함수callable + .getPermission() + .openPermissionDialog()SDK는 권한이 필요한 함수에 두 유틸을 부착해 둡니다. 별도의 모듈을 import하지 않고도 호출부 가까이에서 권한을 다룰 수 있습니다.

Storage 네임스페이스는 이 모델에 포함되지 않습니다StoragePermissionName에 바인딩되어 있지 않고, getPermission() / openPermissionDialog() 유틸도 노출하지 않습니다.

표준 흐름: check → prompt → invoke

권한이 필요한 모든 호출은 다음 세 단계로 정리할 수 있습니다.

import { setClipboardText } from '@apps-in-toss/web-framework';

async function copyWithPermission(value: string) {
// 1. 현재 상태를 먼저 확인한다.
const status = await setClipboardText.getPermission();

// 2. 거절 상태면 호출 자체가 실패한다 — 사용자에게 설정 화면 안내가 필요.
if (status === 'denied') {
throw new Error('PERMISSION_DENIED');
}

// 3. 미결정 상태면 시스템 다이얼로그로 사용자에게 묻는다.
if (status === 'notDetermined') {
const result = await setClipboardText.openPermissionDialog();
if (result === 'denied') {
throw new Error('PERMISSION_DENIED');
}
}

// 4. allowed가 보장된 시점에서 실제 호출.
await setClipboardText(value);
}

openPermissionDialog()'allowed' | 'denied'로 resolve 합니다 — notDetermined로 다시 떨어지지 않으므로, 다이얼로그 직후엔 두 상태만 다루면 됩니다.

짧게: 호출 후 catch

위 4단계가 정석이지만, 단순한 UX(예: "복사" 버튼 한 번)에서는 곧장 호출하고 실패만 잡아도 충분합니다.

try {
await setClipboardText(value);
showAppToast('복사되었습니다');
} catch {
showAppToast('권한이 없어 복사하지 못했어요. 설정에서 허용해 주세요.');
}

이 단축형은 notDetermined 상태가 호출을 그대로 통과시키는 환경에서 동작합니다. 그래도 첫 호출 시점에 시스템 다이얼로그가 뜰 가능성이 있으므로, 사용자가 명시적으로 액션을 트리거한 시점에만 사용하세요. 백그라운드 자동 호출에는 적합하지 않습니다.

거절(denied) 처리

denied 상태에서의 호출은 throw 합니다. 표준 에러 shape이 SDK 문서로 명시돼 있지 않으므로 메시지에 매칭하지 말고, 다음 두 가지 사실에만 의존하세요:

  1. denied 상태의 호출은 반드시 실패한다. 호출 전 getPermission()으로 막거나 try/catch로 감싸야 한다.
  2. 앱 내에서 deniedallowed로 되돌리는 표준 SDK 경로는 없다. 한 번 거절되면 사용자가 OS 설정 화면에서 직접 권한을 허용해야 한다. 앱은 이 경로를 안내하는 메시지(예: "설정 → 토스 → 권한")를 보여 주는 것 외에는 할 수 있는 일이 없다.

따라서 권한 다이얼로그는 한 번에 한 번만, 사용자가 그 권한이 정말 필요한 화면에 도달했을 때만 띄우세요. "혹시 모르니 진입 시 다 받아 두자"는 패턴은 거절률만 올리고 회복도 어렵습니다.

import { getCurrentLocation } from '@apps-in-toss/web-framework';

async function ensureGeolocation() {
const status = await getCurrentLocation.getPermission();
if (status === 'allowed') return true;
if (status === 'denied') {
// 다이얼로그를 다시 띄워도 의미 없음. 안내만 보여 준다.
return false;
}
// notDetermined인 경우만 다이얼로그.
const result = await getCurrentLocation.openPermissionDialog();
return result === 'allowed';
}

권한은 네임스페이스 단위로 공유된다

같은 PermissionName을 쓰는 메서드끼리는 상태가 즉시 공유됩니다. 예를 들어 setClipboardText.getPermission()getClipboardText.getPermission()은 항상 같은 값을 돌려주고, 한쪽에서 받은 승인은 다른 쪽에도 즉시 반영됩니다. getCurrentLocation / startUpdateLocation도 마찬가지로 geolocation을 공유합니다.

따라서:

  • 어느 메서드의 getPermission()을 부르든 결과가 같습니다 — 호출부에서 가까운 쪽을 쓰는 것이 가독성이 좋습니다.
  • 한 화면에서 권한을 한 번 승인받았다면, 같은 네임스페이스의 다른 메서드에는 다이얼로그를 다시 띄우지 마세요.

환경별 차이

같은 코드라도 어디서 실행되느냐에 따라 동작이 미묘하게 다를 수 있습니다.

실 토스 앱 (iOS / Android)

  • 권한 다이얼로그는 OS 표준 시스템 다이얼로그입니다. 앱 내부 UI가 아닙니다.
  • 한 번 거절(denied)되면 OS 설정에서만 회복됩니다. iOS는 다이얼로그를 두 번째부터 띄우는 것 자체가 차단되는 경우가 많습니다.
  • 위치(geolocation)는 권한 단계가 한 단계 더 있습니다 — accessLocation: 'FINE' | 'COARSE'. "정확한 위치"를 거절당하면 allowed이지만 COARSE로 동작합니다. 자세한 건 getCurrentLocation의 caution을 참고하세요.
  • 백그라운드 권한(예: 위치 백그라운드 추적)은 별개의 등급으로 분리되어 있고, 앱이 포그라운드를 벗어나면 콜백이 throttle 되거나 멈출 수 있습니다.

웹 브라우저 (devtools mock 외 실제 브라우저)

  • clipboard처럼 표준 Web API에 대응이 있는 권한은 브라우저의 Permissions API 결과에 따라 동작이 갈립니다.
  • 사용자 제스처(클릭 등) 직후가 아니면 navigator.clipboard.writeText 같은 호출이 reject 될 수 있습니다 — 이 경우 권한과 무관한 이유로 실패하므로 try/catch로 일반 처리하세요.

devtools mock

  • @ait-co/devtools의 mock에서 권한 상태는 DevTools 패널의 "Permissions" 탭에서 직접 조정할 수 있습니다.
  • mock의 openPermissionDialog()는 OS 다이얼로그를 띄우지 않고 패널 상태를 그대로 allowed로 전환합니다 — 실제 거절 흐름을 테스트하려면 패널에서 denied로 미리 설정하세요.
  • mock의 denied 상태에서 호출하면 [@ait-co/devtools] <fn>: Permission "<name>" is denied. 형태의 명시적 에러가 던져집니다. 실 SDK의 메시지 포맷과 다를 수 있으므로, 프로덕션 코드에서는 메시지가 아니라 getPermission() 결과로 분기하세요.

API 페이지의 "권한" 섹션이 이 문서를 가리키는 방식

각 메서드 페이지의 "권한" 섹션은 다음 정보만 명시하고 나머지는 이 가이드로 위임합니다.

  • 어떤 PermissionName을 요구하는가 (clipboard, geolocation, …)
  • 거절 시 호출이 실패하는지 / 그대로 통과하는지
  • getPermission() / openPermissionDialog() 유틸 호출 예시 한 토막

표준 흐름·거절 처리·환경별 차이 같은 일반 규칙은 이 문서가 담당합니다. 같은 설명을 모든 API 페이지에 복제하지 않기 위해 의도적으로 분리했습니다.

Storage 네임스페이스 페이지의 "권한" 섹션은 "권한이 필요하지 않다"는 사실만 알리고 이 가이드로 링크합니다 — 다른 네임스페이스에서 권한이 어떻게 다뤄지는지 궁금한 사용자가 한 번에 도달할 수 있게 하기 위한 cross-link입니다.

관련 문서

외부 참조