연락처 선택기 API

W3C 작업 초안,

이 문서에 대한 추가 정보
이 버전:
https://www.w3.org/TR/2024/WD-contact-picker-20240708/
최신 공개 버전:
https://www.w3.org/TR/contact-picker/
편집자 초안:
https://w3c.github.io/contact-picker/
이전 버전:
히스토리:
https://www.w3.org/standards/history/contact-picker/
피드백:
public-device-apis@w3.org 제목에 “[contact-picker] … 메시지 주제 …” 포함 (아카이브)
GitHub
편집자:
(Google)
이전 편집자:
(Google)

요약

사용자가 공유하는 데이터에 대해 완전한 제어권을 가지면서, 사용자의 연락처 정보에 일회성 접근을 제공하는 API입니다.

이 문서의 상태

이 섹션은 출판 당시 문서의 상태를 설명합니다. 최신 W3C 간행물과 이 기술 보고서의 최신 개정판 목록은 W3C 기술 보고서 인덱스 https://www.w3.org/TR/에서 확인할 수 있습니다.

이 문서는 디바이스 및 센서 워킹 그룹웹 애플리케이션 워킹 그룹에서 권고안 경로를 사용해 작업 초안으로 발행되었습니다. 이 문서는 W3C 권고안이 될 예정입니다.

이 문서에 대해 의견이 있으시면 명세 저장소에 이슈를 등록해 주시기 바랍니다.

작업 초안으로 출판된 것은 W3C 및 그 회원의 승인 의미가 아닙니다. 이 문서는 초안이므로 언제든지 업데이트, 대체 또는 폐기될 수 있습니다. 진행 중인 작업 외의 용도로 인용하는 것은 부적절합니다.

이 문서는 W3C 특허 정책에 따라 운영되는 그룹에서 작성되었습니다. W3C는 특허 공개(디바이스 및 센서)특허 공개(웹 애플리케이션)의 공개 목록을 관리하며, 각 그룹의 산출물과 관련된 특허 공개 방법도 안내합니다. 만약 어떤 특허에 필수적 청구항이 있다고 판단될 경우, W3C 특허 정책 6절에 따라 정보를 공개해야 합니다.

일반 워킹 그룹 공지, 관리 사항, 비기술적 내용은 public-device-apis@w3.org (구독, 아카이브) 또는 public-webapps@w3.org (구독, 아카이브)로 이메일을 보내주시기 바랍니다.

이 문서는 2023년 11월 3일 W3C 프로세스 문서에 따라 관리됩니다.

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

위 예시에서 selectRecipientsButtonHTMLButtonElement이고, 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);
  }

 // 폼으로 대체.
});

위 예시에서 selectRecipientButtonHTMLButtonElement이고, 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});
  }
});

위 예시에서 selectRecipientButtonHTMLButtonElement이고, imgContainerHTMLImageElement입니다.

2. 개인정보 보호 고려사항

연락처 정보를 노출하는 것은 관련 없는 제3자의 개인정보(PII)가 노출될 수 있기 때문에 명확한 개인정보 보호 영향이 있습니다. 선택기 모델을 적용함으로써 사용자 에이전트가 웹사이트에 어떤 정보가 공유되는지와 언제 공유되는지를 명확하게 알 수 있는 사용자 경험을 제공합니다.

다음과 같은 제약도 적용됩니다:

3. 보안 고려사항

4. Realm

모든 플랫폼 객체는 별도의 명시가 없는 한 this관련 Realm에서 생성됩니다.

5. 인프라스트럭처

연락처 선택기 작업 소스작업 소스입니다.

선택적 eventLoop ( 이벤트 루프, 디폴트는 호출자의 this관련 설정 객체책임 이벤트 루프)에 steps (steps)을 사용하여, 작업을 대기열에 추가함. eventLoop에서 연락처 선택기 작업 소스를 사용해 steps를 실행합니다.

5.1. 물리적 주소

물리적 주소는 다음으로 구성됩니다:

5.2. 사용자 연락처

사용자 연락처는 다음으로 구성됩니다:

사용자 연락처는 단일 사용자에 대한 데이터를 포함합니다.

참고: 리스트의 크기는 다를 수 있으며, 같은 인덱스의 항목이 서로 대응될 필요는 없습니다.

5.3. 연락처 소스

연락처 소스는 사용자 에이전트에 사용자의 연락처 정보를 제공하는 서비스입니다.

연락처 소스는 다음으로 구성됩니다:

참고: 연락처 소스의 선택은 사용자 에이전트에 달려 있습니다.

6. API 설명

6.1. Navigator 확장

[Exposed=Window]
partial interface Navigator {
  [SecureContext, SameObject] readonly attribute ContactsManager contacts;
};
Navigator 에는 연락처 관리자(ContactsManager)가 있으며, 최초에는 새로운 ContactsManager입니다.

contacts 속성의 getter는 this연락처 관리자를 반환해야 합니다.

navigable에는 연락처 선택기 표시 플래그가 있으며, 초기에는 설정되지 않습니다.

6.2. ContactProperty

enum ContactProperty { "address", "email", "icon", "name", "tel" };

ContactProperty 는 연결된 사용자 연락처 필드를 사용자 에이전트가 접근할 수 있을 때 사용 가능으로 간주됩니다.

"address"

사용자 연락처addresses와 연관됨.

"email"

사용자 연락처emails와 연관됨.

"icon"

사용자 연락처icons와 연관됨.

"name"

사용자 연락처names와 연관됨.

"tel"

사용자 연락처numbers와 연관됨.

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 인스턴스는 다음을 가집니다:

city 속성의 getter는 thisaddresscity를 반환해야 합니다.

country 속성의 getter는 thisaddresscountry를 반환해야 합니다.

dependentLocality 속성의 getter는 thisaddressdependent locality를 반환해야 합니다.

organization 속성의 getter는 thisaddressorganization을 반환해야 합니다.

phone 속성의 getter는 thisaddressphone number를 반환해야 합니다.

postalCode 속성의 getter는 thisaddresspostal code를 반환해야 합니다.

recipient 속성의 getter는 thisaddressrecipient를 반환해야 합니다.

region 속성의 getter는 thisaddressregion을 반환해야 합니다.

sortingCode 속성의 getter는 thisaddresssorting code를 반환해야 합니다.

addressLine 속성의 getter는 thisaddressaddress line을 반환해야 합니다.

6.4. ContactsManager

dictionary ContactInfo {
    sequence<ContactAddress> address;
    sequence<DOMString> email;
    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() 메서드가 호출되면 다음 절차를 수행합니다:
  1. promise새로운 promise로 둡니다.

  2. 다음 절차를 병렬로 실행합니다:

    1. promise연락처 소스지원하는 속성으로 resolve합니다.

  3. promise를 반환합니다.

6.4.2. select()

select(properties, options) 메서드는 호출될 때 다음 단계를 실행한다:
  1. globalthis관련 글로벌 객체로 설정한다.

  2. navigableglobalnavigable로 설정한다.

  3. navigable최상위 traversable이 아니면, 거부된 프라미스InvalidStateError DOMException을 반환한다.

  4. global일시적 활성화가 없으면, 거부된 프라미스SecurityError DOMException을 반환한다.

  5. 그 외의 경우, user activationglobal에서 소모한다.

  6. navigable연락처 선택기 표시 플래그가 설정되어 있으면 거부된 프라미스InvalidStateError DOMException을 반환한다.

  7. properties비어있으면, 거부된 프라미스TypeError를 반환한다.

  8. propertiesproperty에 대해:

    1. contacts source지원되는 속성property가 없다면, 거부된 프라미스TypeError를 반환한다.

  9. navigable연락처 선택기 표시 플래그를 설정한다.

  10. promise새로운 프라미스로 설정한다.

  11. 다음 단계를 병렬로 실행한다:

    1. selectedContacts연락처 선택기 실행 결과로 optionsmultiple 멤버와 properties를 함께 사용해 얻는다. 실패하면:

      1. 연락처 선택기 작업 대기를 다음 단계로 실행한다:

        1. 프라미스 거부promiseInvalidStateError DOMException과 함께 실행한다.

        2. navigable연락처 선택기 표시 플래그를 해제한다.

        3. 이 단계를 중단한다.

    2. navigable연락처 선택기 표시 플래그를 해제한다.

    3. 연락처 선택기 작업 대기를 다음 단계로 실행한다:

      1. contacts를 빈 리스트로 설정한다.

      2. selectedContactselectedContacts에서 반복한다:

        1. 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로 설정한다.

        2. contact를 contacts에 추가한다.

      3. promisecontacts와 함께 resolve한다.

  12. promise를 반환한다.

7. 연락처 선택기

연락처 선택기를 실행한다는 것은 allowMultiple (boolean 타입)과 properties (리스트 타입, DOMString들의 리스트), 사용자 에이전트는 다음 규칙을 따르는 사용자 인터페이스를 반드시 표시해야 한다:
  • 사용자 인터페이스 표시가 실패하거나 연락처 소스사용 가능한 연락처 접근에 실패하면, 실패를 반환한다.

  • UI는 반드시 최상위 traversableorigin을 눈에 띄게 표시해야 한다.

  • UI는 반드시 연락처의 어떤 properties가 요청되고 있는지 명확하게 보여줘야 한다.

    참고: 이 정보는 properties에서 유추된다.

  • UI는 사용자가 특정 연락처 정보를 공유하지 않도록 선택할 수 있는 방법을 제공해야 한다.

    참고: 사용자가 공유를 거부할 경우, 적절한 사용자 연락처 필드를 반환 전에 수정해야 한다. 사용자가 일부 정보를 공유하지 않기로 했는지 아니면 애초에 정보가 없었는지는 반환된 사용자 연락처로 구분할 수 없어야 한다.

  • UI는 반드시 어떤 정보가 공유될 것인지 명확하게 보여줘야 한다.

  • UI는 반드시 개별 연락처를 선택할 수 있는 방법을 제공해야 한다. allowMultiple가 false이면, 하나의 연락처만 선택할 수 있어야 한다.

  • UI는 반드시 연락처를 공유하지 않고 취소/돌아갈 수 있는 옵션을 제공해야 하며, 이 경우 UI를 제거하고 빈 리스트를 반환해야 한다.

  • UI는 반드시 사용자가 선택을 마쳤음을 표시하는 방법을 제공해야 하며, 이 경우 UI를 제거하고 리스트로 선택된 연락처를 사용자 연락처로 반환한다.

8. 사용자 입력으로 ContactAddress 생성하기

사용자 입력으로부터 ContactAddress를 생성한다는 단계는 다음 알고리즘에 따라 진행된다. 알고리즘은 선택적으로 리스트 redactList를 받는다. redactList가 전달되지 않으면, 기본값은 비어있는 리스트이다.

참고: redactList는 사용자 에이전트가 수신인에 대한 개인정보를 API를 요청하는 애플리케이션에 공유하는 양을 제한할 수 있도록 한다. 결과 ContactAddress 객체는 통신 또는 서비스 제공 등 필요한 작업을 수행할 수 있을 만큼의 정보를 제공하지만, 대부분의 경우 실제 위치 및 수신인을 고유하게 식별할 만큼의 정보는 제공하지 않는다. 그러나 redactList가 있더라도 수신인의 익명성이 보장되지는 않는다. 일부 국가에서는 우편번호가 매우 세분화되어 있어서 수신인을 고유하게 식별할 수 있기 때문이다.

  1. details를 다음 으로 설정한다: « "addressLine" → 빈 리스트, "country" → "", "phone" → "", "city" → "", "dependentLocality" → "", "organization" → "", "postalCode" → "", "recipient" → "", "region" → "", "sortingCode" → "" ».

  2. redactList가 "addressLine"을 포함하지 않으면, details["addressLine"]을 사용자 주소 입력값을 분할한 리스트로 설정한다.

    참고: 주소 줄을 어떻게 분할할지는 로케일에 따라 다르며, 이 명세의 범위를 벗어난다.

  3. redactList가 "country"를 포함하지 않으면, details["country"]를 사용자 입력 국가의 대문자 [ISO3166-1] alpha-2 코드로 설정한다.

  4. redactList가 "phone"을 포함하지 않으면, details["phone"]을 사용자 입력 전화번호로 설정한다.

    참고: 사용자의 개인정보 보호를 위해, 구현자는 연락처 주소의 전화번호가 최종 사용자와 같거나 다를 수 있음을 유의해야 한다. 따라서 최종 사용자의 동의 없이 전화번호를 제공하지 않도록 주의해야 한다.

  5. redactList가 "city"를 포함하지 않으면, details["city"]를 사용자 입력 도시로 설정한다.

  6. redactList가 "dependentLocality"를 포함하지 않으면, details["dependentLocality"]를 사용자 입력 세부 지역으로 설정한다.

  7. redactList가 "organization"을 포함하지 않으면, details["organization"]을 사용자 입력 수신인 기관으로 설정한다.

  8. redactList가 "postalCode"를 포함하지 않으면, details["postalCode"]를 사용자 입력 우편번호로 설정한다. 선택적으로 details["postalCode"]의 일부를 마스킹할 수 있다.

    참고: 우편번호가 특정 국가에서는 매우 세분화되어 개인을 고유하게 식별할 수 있다. 이는 개인정보 보호 측면에서 문제가 될 수 있으며, 일부 사용자 에이전트는 애플리케이션에 충분하다고 판단되는 부분만 반환한다. 이는 국가 및 지역마다 다르므로, 우편번호의 일부 또는 전체를 마스킹할지 여부는 사용자의 개인정보 보호를 위해 구현자 재량에 맡긴다.

  9. redactList가 "recipient"를 포함하지 않으면, details["recipient"]를 해당 연락처 정보의 사용자 입력 수신인으로 설정한다.

  10. redactList가 "region"을 포함하지 않으면, details["region"]을 사용자 입력 지역으로 설정한다.

    참고: 일부 국가(예: 벨기에)는 지역실제 주소에 포함하는 것이 일반적이지 않다 (비록 모든 국가의 지역이 [ISO3166-2]에 포함되어 있더라도). 사용자가 특정 국가의 주소를 입력하고 있음을 사용자 에이전트가 알고 있으면, 지역 입력 필드를 제공하지 않을 수 있다. 이 경우 ContactAddressregion 속성 값은 빈 문자열이 되지만, 주소는 의도된 목적(예: 통신 또는 서비스 제공)에 여전히 유효하다.

  11. redactList가 "sortingCode"를 포함하지 않으면, details["sortingCode"]를 사용자 입력 정렬 코드로 설정한다.

  12. details의 값을 기반으로 속성 값을 가지는 새로 생성된 ContactAddress를 반환한다.

9. 감사의 글

웹용 연락처 API를 표준화하려는 시도는 여러 번 있었으며, 이 API는 그 풍부한 역사로부터 배움을 얻으려 합니다. 이전의 시도에는 Mozilla의 Contacts API, Contacts API W3C 회원 제출과 W3C 워킹 그룹의 표준화 작업: Contacts API, Pick Contacts Intent, 그리고 Contacts Manager API가 포함됩니다. Contact Picker API는 개인정보 보호에 초점을 맞춘 접근 방식이 다릅니다. 이전 시도들은 권한이 부여된 후 영구적인 접근을 허용하거나, 불명확한 개인정보 모델을 포함했지만, 이 명세는 UI 제한을 강제하여 사용자가 공유 데이터에 대해 완전한 통제권을 가지도록 하고 남용을 제한합니다. 예를 들어, 사용자 선택기 모델이 강제되어 사용자가 항상 공유되는 연락처 정보의 중개자로서 매번 요청 시에 완전한 통제권을 갖게 됩니다. 더 많은 역사적 맥락은 이전 시도들의 문서 상태 섹션을 참조하세요.

적합성

문서 관례

적합성 요구사항은 설명적 단언과 RFC 2119 용어의 조합으로 표현됩니다. "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", "OPTIONAL"과 같은 주요 단어는 본 명세의 규범적 부분에서 RFC 2119에서 설명한 대로 해석되어야 합니다. 하지만 가독성을 위해 이 단어들은 본 명세에서 모두 대문자로 표시되지는 않습니다.

본 명세의 모든 텍스트는 명시적으로 비규범적, 예시, 또는 노트로 표시된 섹션을 제외하고는 규범적입니다. [RFC2119]

이 명세의 예시는 "for example"이라는 단어로 시작하거나, class="example"와 같이 규범적 텍스트와 분리되어 있습니다. 예시:

이것은 정보 제공용 예시입니다.

정보 제공용 노트는 "Note"로 시작하며, class="note"로 규범적 텍스트와 구분되어 있습니다. 예시:

Note, 이것은 정보 제공용 노트입니다.

적합한 알고리즘

알고리즘의 일부로 명령형으로 표현된 요구사항(예: "선행 공백 문자를 제거한다" 또는 "false를 반환하고 이 단계를 중단한다")은 해당 알고리즘을 소개할 때 사용된 주요 단어("must", "should", "may" 등)의 의미로 해석되어야 합니다.

알고리즘 또는 특정 단계로 표현된 적합성 요구사항은 최종 결과가 동등하다면 어떤 방식으로든 구현될 수 있습니다. 특히, 본 명세에서 정의된 알고리즘은 이해하기 쉽도록 설계된 것이며, 성능을 위해 설계된 것이 아닙니다. 구현자는 최적화를 권장합니다.

색인

이 명세서에서 정의된 용어

참조로 정의된 용어

참고 문헌

규범적 참고 문헌

[FileAPI]
Marijn Kruisselbrink. File API. 2024년 5월 24일. WD. URL: https://www.w3.org/TR/FileAPI/
[HTML]
Anne van Kesteren 외. HTML 표준. 현행 표준. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 표준. 현행 표준. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. RFC에서 요구 수준을 나타내기 위한 주요 단어. 1997년 3월. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 표준. 현행 표준. URL: https://webidl.spec.whatwg.org/

정보 제공 참고 문헌

[E.164]
국제 공개 통신 번호 계획. 2010년 11월. Recommendation. URL: https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-E.164-201011-I!!PDF-E&type=items
[ISO3166-1]
국가 및 그 하위 구분의 명칭을 나타내는 코드 — 1부: 국가 코드. 2020년 8월. 발행됨. URL: https://www.iso.org/standard/72482.html
[ISO3166-2]
ISO 3166: 국가 및 그 하위 구분의 명칭을 나타내는 코드 – 2부: 국가 하위 구분 코드.. 2020년 8월. 발행됨. URL: https://www.iso.org/standard/72483.html
[MIMESNIFF]
Gordon P. Hemsley. MIME Sniffing 표준. 현행 표준. URL: https://mimesniff.spec.whatwg.org/

IDL 색인

[Exposed=Window]
partial interface Navigator {
  [SecureContext, SameObject] readonly attribute ContactsManager contacts;
};

enum ContactProperty { "address", "email", "icon", "name", "tel" };

[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;
};

dictionary ContactInfo {
    sequence<ContactAddress> address;
    sequence<DOMString> email;
    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 = {});
};