1. 소개
연락처 선택기는 다양한 데스크톱 및 네이티브 모바일 애플리케이션에서 다양한 사용 사례로 자주 볼 수 있습니다. 이 명세는 웹에 연락처 선택기를 도입하기 위한 API를 정의하며, 다음과 같은 웹 앱의 새로운 사용 사례를 가능하게 합니다:
-
소셜 네트워크에서 사용자의 소셜 그래프를 부트스트랩하기.
-
이메일 애플리케이션에서 메시지의 수신자를 선택하기.
연락처 선택기 모델은 사용자에게 공유 데이터에 대한 완전한 제어권을 제공하기 위해 선택되었습니다. 사용자는 웹사이트에 제공할 연락처를 정확하게 선택할 수 있습니다. 연락처 선택기 모델은 웹사이트에 사용자의 연락처에 대한 일회성 접근만 허용하므로, 개발자는 필요할 때마다 사용자의 연락처 접근을 요청해야 합니다. 이는 일부 네이티브 연락처 API와는 다르지만, 사용자의 명확한 동의와 인지 없이 연락처가 접근되는 것을 방지하기 위해 필요합니다.
1.1. 예시
selectRecipientsButton. addEventListener( 'click' , async () => { const contacts= await navigator. contacts. select([ 'name' , 'email' ], { multiple: true }); if ( ! contacts. length) { // 선택기에서 연락처가 선택되지 않았습니다. return ; } // |contacts|의 이름과 이메일 주소를 웹사이트 UI의 수신자 필드에 채웁니다. populateRecipients( contacts); });
위 예시에서 selectRecipientsButton
은 HTMLButtonElement
이고,
populateRecipients
는 개발자가 정의한 함수입니다.
selectRecipientButton. addEventListener( 'click' , async () => { // 주소 지원 여부나 브라우저에서 제공 가능한지 확실하지 않습니다. if (( await navigator. contacts. getProperties()). includes( 'address' )) { const contacts= await navigator. contacts. select([ 'address' ]); if ( ! contacts. length) { // 선택기에서 연락처가 선택되지 않았습니다. return ; } // 여러 연락처를 요청하지 않았으므로 length는 1입니다. sendGiftToAddress( contacts[ 0 ]. address); } // 폼으로 대체. });
위 예시에서 selectRecipientButton
은 HTMLButtonElement
이고,
sendGiftToAddress
는 개발자가 정의한 함수입니다.
selectRecipientButton. addEventListener( 'click' , async () => { // 아이콘 지원 여부나 브라우저에서 제공 가능한지 확실하지 않습니다. if (( await navigator. contacts. getProperties()). includes( 'icon' )) { const contacts= await navigator. contacts. select([ 'name' , 'icon' ]); if ( ! contacts. length) { // 선택기에서 연락처가 선택되지 않았습니다. return ; } if ( ! contacts[ 0 ]. name. length|| ! contacts[ 0 ]. icon. length) { // 정보 없음. 대체 사용. return ; } // 이름과 이미지는 하나씩만 필요합니다. const name= contacts[ 0 ]. name[ 0 ]; const imgBlob= contacts[ 0 ]. icon[ 0 ]; // 이미지 표시. const url= URL. createObjectURL( imgBlob); imgContainer. onload= () => URL. revokeObjectURL( url); imgContainer. src= url; // 또는 Bitmap을 사용. const imgBitmap= await createImageBitmap( imgBlob); // 아이콘 업로드. const response= await fetch( '/contacticon' , { method: 'POST' , body: imgBlob}); } });
위 예시에서 selectRecipientButton
은 HTMLButtonElement
이고,
imgContainer
는 HTMLImageElement
입니다.
2. 개인정보 보호 고려사항
연락처 정보를 노출하는 것은 관련 없는 제3자의 개인정보(PII)가 노출될 수 있기 때문에 명확한 개인정보 보호 영향이 있습니다. 선택기 모델을 적용함으로써 사용자 에이전트가 웹사이트에 어떤 정보가 공유되는지와 언제 공유되는지를 명확하게 알 수 있는 사용자 경험을 제공합니다.
다음과 같은 제약도 적용됩니다:
-
API를 시작하려면 사용자 제스처가 필요하며, 사용자의 연락처에 대한 프로그래밍적 요청을 허용하지 않습니다.
-
API는 최상위 탐색 가능 객체에서만 사용 가능하며, 반드시 보안 컨텍스트여야 합니다. 이러한 제한은 제공된 연락처 정보가 의도된 수신자에게 도달하도록 보장합니다.
-
API를 시작하려면 일시적 활성화가 필요하며, 사용자의 연락처에 대한 프로그래밍적 요청을 허용하지 않습니다.
3. 보안 고려사항
4. Realm
모든 플랫폼 객체는 별도의 명시가 없는 한 this의 관련 Realm에서 생성됩니다.
5. 인프라스트럭처
연락처 선택기 작업 소스는 작업 소스입니다.
5.1. 물리적 주소
물리적 주소는 다음으로 구성됩니다:
-
country,
DOMString
형태로 국가를 ISO3166-1 alpha-2 코드의 표준 대문자 형식 또는 빈 문자열로 표현합니다. 예: "JP". -
address line, 리스트 형태의
DOMString
로, 주소의 가장 구체적인 부분을 포함합니다. 예를 들어, 거리명, 건물 번호, 아파트 번호, 시골 우편 경로, 설명 지시사항, 또는 사서함 번호 등이 포함될 수 있습니다. -
region,
DOMString
형태로 국가의 최상위 행정 구역(예: 주, 도, 오블라스트, 현 등)을 나타냅니다. -
city,
DOMString
형태로 주소의 도시/읍/면 부분을 나타냅니다. -
dependent locality,
DOMString
형태로 도시 내의 하위 지역(예: 동네, 구, 지구, 영국의 dependent locality 등)을 나타냅니다. -
postal code,
DOMString
형태로 우편번호(또는 인도에서의 PIN 코드 등)를 나타냅니다. -
sorting code,
DOMString
형태로 분류 코드(예: 프랑스의 CEDEX 시스템 등)를 나타냅니다. -
organization,
DOMString
형태로 해당 주소의 조직, 회사, 기관명을 나타냅니다. -
recipient,
DOMString
형태로 해당 주소의 수신자 또는 연락처명을 나타냅니다. -
phone number,
DOMString
형태로 수신자 또는 연락처의 전화번호를 나타내며, 선택적으로 [E.164] 형식에 따를 수도 있습니다.
5.2. 사용자 연락처
사용자 연락처는 다음으로 구성됩니다:
-
emails, 리스트 형태의
DOMString
로, 각 항목은 사용자의 고유 유효한 이메일 주소를 나타냅니다. -
addresses, 리스트 형태의
ContactAddress
로, 각 항목은 사용자의 고유 물리적 주소를 나타냅니다. -
icons, 리스트 형태의
Blob
로, 각 항목은 사용자의 고유 이미지를 나타냅니다.참고: 아이콘
Blob
의type
값은 이미지 MIME 타입입니다.
사용자 연락처는 단일 사용자에 대한 데이터를 포함합니다.
참고: 리스트의 크기는 다를 수 있으며, 같은 인덱스의 항목이 서로 대응될 필요는 없습니다.
5.3. 연락처 소스
연락처 소스는 사용자 에이전트에 사용자의 연락처 정보를 제공하는 서비스입니다.
연락처 소스는 다음으로 구성됩니다:
-
supported properties, 리스트 형태의 지원되는
ContactProperty
값.
참고: 연락처 소스의 선택은 사용자 에이전트에 달려 있습니다.
6. API 설명
6.1. Navigator
확장
[Exposed =Window ]partial interface Navigator { [SecureContext ,SameObject ]readonly attribute ContactsManager contacts ; };
navigable에는 연락처 선택기 표시 플래그가 있으며, 초기에는 설정되지 않습니다.
6.2. ContactProperty
enum {
ContactProperty ,
"address" ,
"email" ,
"icon" ,
"name" };
"tel"
ContactProperty
는 연결된 사용자 연락처 필드를 사용자 에이전트가 접근할
수 있을 때 사용 가능으로 간주됩니다.
- "address"
- "email"
- "icon"
- "name"
- "tel"
6.3. ContactAddress
[Exposed =Window ]interface { [
ContactAddress Default ]object ();
toJSON readonly attribute DOMString city ;readonly attribute DOMString country ;readonly attribute DOMString dependentLocality ;readonly attribute DOMString organization ;readonly attribute DOMString phone ;readonly attribute DOMString postalCode ;readonly attribute DOMString recipient ;readonly attribute DOMString region ;readonly attribute DOMString sortingCode ;readonly attribute FrozenArray <DOMString >addressLine ; };
ContactAddress
인터페이스는 물리적 주소를 나타냅니다.
ContactAddress
인스턴스는 다음을 가집니다:
-
address (물리적 주소).
city
속성의 getter는 this의 address의 city를 반환해야
합니다.
country
속성의 getter는 this의 address의 country를 반환해야 합니다.
dependentLocality
속성의 getter는 this의 address의 dependent locality를 반환해야 합니다.
organization
속성의 getter는
this의 address의 organization을
반환해야 합니다.
phone
속성의 getter는 this의 address의 phone number를 반환해야 합니다.
postalCode
속성의 getter는
this의 address의 postal code를 반환해야
합니다.
recipient
속성의 getter는 this의 address의 recipient를 반환해야 합니다.
region
속성의 getter는 this의 address의 region을 반환해야 합니다.
sortingCode
속성의 getter는
this의
address의
sorting code를 반환해야 합니다.
addressLine
속성의 getter는
this의
address의
address line을 반환해야 합니다.
6.4. ContactsManager
dictionary {
ContactInfo sequence <ContactAddress >;
address sequence <DOMString >;
sequence <Blob >;
icon sequence <DOMString >;
name sequence <DOMString >; };
tel dictionary {
ContactsSelectOptions boolean =
multiple false ; }; [Exposed =Window ,SecureContext ]interface {
ContactsManager Promise <sequence <ContactProperty >>getProperties ();Promise <sequence <ContactInfo >>select (sequence <ContactProperty >,
properties optional ContactsSelectOptions = {}); };
options
6.4.1. getProperties()
getProperties()
메서드가 호출되면 다음 절차를 수행합니다:
-
promise를 새로운 promise로 둡니다.
-
다음 절차를 병렬로 실행합니다:
-
promise를 반환합니다.
6.4.2.
select()
select(properties, options)
메서드는 호출될 때 다음 단계를 실행한다:
-
navigable을 global의 navigable로 설정한다.
-
navigable이 최상위 traversable이 아니면, 거부된 프라미스와
InvalidStateError
DOMException
을 반환한다. -
global에 일시적 활성화가 없으면, 거부된 프라미스와
SecurityError
DOMException
을 반환한다. -
그 외의 경우, user activation을 global에서 소모한다.
-
navigable의 연락처 선택기 표시 플래그가 설정되어 있으면 거부된 프라미스와
InvalidStateError
DOMException
을 반환한다. -
각 properties의 property에 대해:
-
contacts source의 지원되는 속성에 property가 없다면, 거부된 프라미스와
TypeError
를 반환한다.
-
-
navigable의 연락처 선택기 표시 플래그를 설정한다.
-
promise를 새로운 프라미스로 설정한다.
-
다음 단계를 병렬로 실행한다:
-
selectedContacts를 연락처 선택기 실행 결과로 options의
multiple
멤버와 properties를 함께 사용해 얻는다. 실패하면:-
연락처 선택기 작업 대기를 다음 단계로 실행한다:
-
프라미스 거부를 promise와
InvalidStateError
DOMException
과 함께 실행한다. -
navigable의 연락처 선택기 표시 플래그를 해제한다.
-
이 단계를 중단한다.
-
-
-
navigable의 연락처 선택기 표시 플래그를 해제한다.
-
연락처 선택기 작업 대기를 다음 단계로 실행한다:
-
contacts를 빈 리스트로 설정한다.
-
각 selectedContact를 selectedContacts에서 반복한다:
-
contact를 새
ContactInfo
로 생성한다:address
-
selectedContact의 주소를, properties가 "address"를 포함하면, 그렇지 않으면 undefined로 설정한다.
email
-
selectedContact의 이메일을, properties가 "email"을 포함하면, 그렇지 않으면 undefined로 설정한다.
icon
-
selectedContact의 아이콘을, properties가 "icon"을 포함하면, 그렇지 않으면 undefined로 설정한다.
name
-
selectedContact의 이름을, properties가 "name"을 포함하면, 그렇지 않으면 undefined로 설정한다.
tel
-
selectedContact의 전화번호를, properties가 "tel"을 포함하면, 그렇지 않으면 undefined로 설정한다.
-
-
promise를 contacts와 함께 resolve한다.
-
-
-
promise를 반환한다.
7. 연락처 선택기
DOMString
들의
리스트),
사용자 에이전트는 다음 규칙을 따르는 사용자 인터페이스를 반드시 표시해야 한다:
-
사용자 인터페이스 표시가 실패하거나 연락처 소스의 사용 가능한 연락처 접근에 실패하면, 실패를 반환한다.
-
UI는 반드시 최상위 traversable의 origin을 눈에 띄게 표시해야 한다.
-
UI는 반드시 연락처의 어떤
properties
가 요청되고 있는지 명확하게 보여줘야 한다.참고: 이 정보는 properties에서 유추된다.
-
UI는 사용자가 특정 연락처 정보를 공유하지 않도록 선택할 수 있는 방법을 제공해야 한다.
참고: 사용자가 공유를 거부할 경우, 적절한 사용자 연락처 필드를 반환 전에 수정해야 한다. 사용자가 일부 정보를 공유하지 않기로 했는지 아니면 애초에 정보가 없었는지는 반환된 사용자 연락처로 구분할 수 없어야 한다.
-
UI는 반드시 어떤 정보가 공유될 것인지 명확하게 보여줘야 한다.
-
UI는 반드시 개별 연락처를 선택할 수 있는 방법을 제공해야 한다. allowMultiple가 false이면, 하나의 연락처만 선택할 수 있어야 한다.
-
UI는 반드시 연락처를 공유하지 않고 취소/돌아갈 수 있는 옵션을 제공해야 하며, 이 경우 UI를 제거하고 빈 리스트를 반환해야 한다.
-
UI는 반드시 사용자가 선택을 마쳤음을 표시하는 방법을 제공해야 하며, 이 경우 UI를 제거하고 리스트로 선택된 연락처를 사용자 연락처로 반환한다.
8. 사용자 입력으로 ContactAddress
생성하기
사용자 입력으로부터
ContactAddress
를 생성한다는 단계는 다음
알고리즘에 따라 진행된다.
알고리즘은 선택적으로 리스트 redactList를 받는다.
redactList가 전달되지 않으면, 기본값은 비어있는 리스트이다.
참고: redactList는
사용자 에이전트가 수신인에 대한 개인정보를
API를 요청하는 애플리케이션에 공유하는 양을 제한할 수 있도록 한다.
결과 ContactAddress
객체는 통신 또는 서비스 제공 등 필요한 작업을 수행할 수 있을 만큼의 정보를 제공하지만,
대부분의 경우 실제 위치 및 수신인을 고유하게 식별할 만큼의 정보는 제공하지 않는다.
그러나 redactList가 있더라도
수신인의 익명성이 보장되지는 않는다.
일부 국가에서는 우편번호가 매우 세분화되어 있어서 수신인을 고유하게 식별할 수 있기 때문이다.
-
details를 다음 맵으로 설정한다: « "addressLine" → 빈 리스트, "country" → "", "phone" → "", "city" → "", "dependentLocality" → "", "organization" → "", "postalCode" → "", "recipient" → "", "region" → "", "sortingCode" → "" ».
-
redactList가 "addressLine"을 포함하지 않으면, details["addressLine"]을 사용자 주소 입력값을 분할한 리스트로 설정한다.
참고: 주소 줄을 어떻게 분할할지는 로케일에 따라 다르며, 이 명세의 범위를 벗어난다.
-
redactList가 "country"를 포함하지 않으면, details["country"]를 사용자 입력 국가의 대문자 [ISO3166-1] alpha-2 코드로 설정한다.
-
redactList가 "phone"을 포함하지 않으면, details["phone"]을 사용자 입력 전화번호로 설정한다.
참고: 사용자의 개인정보 보호를 위해, 구현자는 연락처 주소의 전화번호가 최종 사용자와 같거나 다를 수 있음을 유의해야 한다. 따라서 최종 사용자의 동의 없이 전화번호를 제공하지 않도록 주의해야 한다.
-
redactList가 "city"를 포함하지 않으면, details["city"]를 사용자 입력 도시로 설정한다.
-
redactList가 "dependentLocality"를 포함하지 않으면, details["dependentLocality"]를 사용자 입력 세부 지역으로 설정한다.
-
redactList가 "organization"을 포함하지 않으면, details["organization"]을 사용자 입력 수신인 기관으로 설정한다.
-
redactList가 "postalCode"를 포함하지 않으면, details["postalCode"]를 사용자 입력 우편번호로 설정한다. 선택적으로 details["postalCode"]의 일부를 마스킹할 수 있다.
참고: 우편번호가 특정 국가에서는 매우 세분화되어 개인을 고유하게 식별할 수 있다. 이는 개인정보 보호 측면에서 문제가 될 수 있으며, 일부 사용자 에이전트는 애플리케이션에 충분하다고 판단되는 부분만 반환한다. 이는 국가 및 지역마다 다르므로, 우편번호의 일부 또는 전체를 마스킹할지 여부는 사용자의 개인정보 보호를 위해 구현자 재량에 맡긴다.
-
redactList가 "recipient"를 포함하지 않으면, details["recipient"]를 해당 연락처 정보의 사용자 입력 수신인으로 설정한다.
-
redactList가 "region"을 포함하지 않으면, details["region"]을 사용자 입력 지역으로 설정한다.
참고: 일부 국가(예: 벨기에)는 지역을 실제 주소에 포함하는 것이 일반적이지 않다 (비록 모든 국가의 지역이 [ISO3166-2]에 포함되어 있더라도). 사용자가 특정 국가의 주소를 입력하고 있음을 사용자 에이전트가 알고 있으면, 지역 입력 필드를 제공하지 않을 수 있다. 이 경우
ContactAddress
의region
속성 값은 빈 문자열이 되지만, 주소는 의도된 목적(예: 통신 또는 서비스 제공)에 여전히 유효하다. -
redactList가 "sortingCode"를 포함하지 않으면, details["sortingCode"]를 사용자 입력 정렬 코드로 설정한다.
-
details의 값을 기반으로 속성 값을 가지는 새로 생성된
ContactAddress
를 반환한다.
9. 감사의 글
웹용 연락처 API를 표준화하려는 시도는 여러 번 있었으며, 이 API는 그 풍부한 역사로부터 배움을 얻으려 합니다. 이전의 시도에는 Mozilla의 Contacts API, Contacts API W3C 회원 제출과 W3C 워킹 그룹의 표준화 작업: Contacts API, Pick Contacts Intent, 그리고 Contacts Manager API가 포함됩니다. Contact Picker API는 개인정보 보호에 초점을 맞춘 접근 방식이 다릅니다. 이전 시도들은 권한이 부여된 후 영구적인 접근을 허용하거나, 불명확한 개인정보 모델을 포함했지만, 이 명세는 UI 제한을 강제하여 사용자가 공유 데이터에 대해 완전한 통제권을 가지도록 하고 남용을 제한합니다. 예를 들어, 사용자 선택기 모델이 강제되어 사용자가 항상 공유되는 연락처 정보의 중개자로서 매번 요청 시에 완전한 통제권을 갖게 됩니다. 더 많은 역사적 맥락은 이전 시도들의 문서 상태 섹션을 참조하세요.