1. Introduction
Contact pickers are frequently seen in various desktop and native mobile applications for a variety of use cases. This specification defines an API to bring contact pickers to the web, which will enable new use cases for web apps, such as:
-
Bootstrapping a user’s social graph for social networks.
-
Selecting the recipients of a message within an e-mail application.
The contact picker model was chosen to give full control to users over the shared data, allowing users to choose exactly which contacts to provide to the website. The contact picker model gives websites one-off access to a user’s contacts, meaning developers have to request access to the user’s contacts every time they need it. This differs from some native contact APIs, but is necessary for ensuring users' contacts are not accessed without their knowledge and explicit consent.
1.1. Examples
selectRecipientsButton. addEventListener( 'click' , async () => { const contacts= await navigator. contacts. select([ 'name' , 'email' ], { multiple: true }); if ( ! contacts. length) { // No contacts were selected in the picker. return ; } // Use the names and e-mail addresses in |contacts| to populate the // recipients field in the website’s UI. populateRecipients( contacts); });
In the above example selectRecipientsButton
is a HTMLButtonElement
, and populateRecipients
is a developer-defined function.
selectRecipientButton. addEventListener( 'click' , async () => { // We are unsure if addresses are supported, or can be provided by the browser. if (( await navigator. contacts. getProperties()). includes( 'address' )) { const contacts= await navigator. contacts. select([ 'address' ]); if ( ! contacts. length) { // No contacts were selected in the picker. return ; } // length is 1 since we didn’t request multiple contacts. sendGiftToAddress( contacts[ 0 ]. address); } // Fallback to a form. });
In the above example selectRecipientButton
is a HTMLButtonElement
, and sendGiftToAddress
is a developer-defined function.
selectRecipientButton. addEventListener( 'click' , async () => { // We are unsure if icons are supported, or can be provided by the browser. if (( await navigator. contacts. getProperties()). includes( 'icon' )) { const contacts= await navigator. contacts. select([ 'name' , 'icon' ]); if ( ! contacts. length) { // No contacts were selected in the picker. return ; } if ( ! contacts[ 0 ]. name. length|| ! contacts[ 0 ]. icon. length) { // Info not found. Use fallback. return ; } // We only need one name and one image. const name= contacts[ 0 ]. name[ 0 ]; const imgBlob= contacts[ 0 ]. icon[ 0 ]; // Display image. const url= URL. createObjectURL( imgBlob); imgContainer. onload= () => URL. revokeObjectURL( url); imgContainer. src= url; // Alternatively use a Bitmap. const imgBitmap= await createImageBitmap( imgBlob); // Upload icon. const response= await fetch( '/contacticon' , { method: 'POST' , body: imgBlob}); } });
In the above example selectRecipientButton
is a HTMLButtonElement
, and imgContainer
is a HTMLImageElement
.
2. Privacy Considerations
Exposing contact information has a clear privacy impact, in terms of exposing PII of uninvolved parties. A picker model is enforced so that the user agent can offer a user experience that makes it clear what information is going to be shared with the website and when.
The following constraints are also enforced:
-
A user gesture is needed to initiate the API, to disallow programmatic requests to the user’s
-
The API is only available in a top-level traversable, which must also be a secure context. These restrictions help ensure that the provided contact information reaches its intended recipient.
-
Transient activation is needed to initiate the API, to disallow programmatic requests to the user’s contacts.
3. Security Considerations
-
The API is only available in a top-level traversables, which must also be a secure context. These restrictions help ensure that the provided contact information reaches its intended recipient.
4. Realms
All platform objects are created in the this's relevant Realm unless otherwise specified.
5. Infrastructure
The contact picker task source is a task source.
5.1. Physical address
A physical address consists of:
-
country, a
DOMString
representing the country of the address as an [ISO3166-1] alpha-2 code stored in its canonical uppercase form or the empty string. For example, "JP". -
address line, a list of
DOMString
s, containing the most specific part of the address. It can include, for example, a street name, a house number, apartment number, a rural delivery route, descriptive instructions, or a post office box number. -
region, a
DOMString
representing the top level administrative subdivision of the country. For example, this can be a state, a province, an oblast, or a prefecture. -
city, a
DOMString
representing the city/town portion of the address. -
dependent locality, a
DOMString
representing the dependent locality or sublocality within a city. For example, neighborhoods, boroughs, districts, or UK dependent localities. -
postal code, a
DOMString
representing the postal code or ZIP code, also known as PIN code in India. -
sorting code, a
DOMString
representing the sorting code system, such as the CEDEX system used in France. -
organization, a
DOMString
representing the organization, firm, company, or institution at the address. -
recipient, a
DOMString
representing the name of the recipient or contact person at the address. -
phone number, a
DOMString
representing the phone number of the recipient or contact person at the address, optionally structured to adhere to [E.164].
5.2. User contact
A user contact consists of:
-
names, a list of
DOMString
s, each item representing a unique name corresponding to the user. -
emails, a list of
DOMString
s, each item representing a unique valid email address of the user. -
numbers, a list of
DOMString
s, each item representing a unique phone number of the user. -
addresses, a list of
ContactAddress
es, each item representing a unique physical address of the user. -
icons, a list of
Blob
s, each item representing a unique image of the user.NOTE: An icon
Blob
'stype
is an image mime type.
A user contact contains data relating to a single user.
Note: The lists can be of different sizes, and entries with the same index don’t need to correspond to each other.
5.3. Contacts source
The contacts source is a service that provides the user’s contact information to the user agent.
A contacts source consists of:
-
available contacts, a list of user contacts.
-
supported properties, a list of available
ContactProperty
values.
Note: It is up to the user agent to choose the contacts source.
6. API Description
6.1. Extensions to Navigator
[Exposed =Window ]partial interface Navigator { [SecureContext ,SameObject ]readonly attribute ContactsManager contacts ; };
Navigator
has a contacts manager (a ContactsManager
), initially a new ContactsManager
.
The contacts
attribute’s getter must return the this's contacts manager.
The navigable has a contact picker is showing flag, initially unset.
6.2. ContactProperty
enum {
ContactProperty ,
"address" ,
"email" ,
"icon" ,
"name" };
"tel"
A ContactProperty
is considered to be available if its associated user contact field can be accessed by the user agent.
- "address"
-
Associated with user contact's addresses.
- "email"
-
Associated with user contact's emails.
- "icon"
-
Associated with user contact's icons.
- "name"
-
Associated with user contact's names.
- "tel"
-
Associated with user contact's 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 ; };
The ContactAddress
interface represents a physical address.
ContactAddress
instance has:
-
An address (a physical address).
The city
attribute’s getter must return the this's address' city.
The country
attribute’s getter must return the this's address' country.
The dependentLocality
attribute’s getter must return the this's address' dependent locality.
The organization
attribute’s getter must return the this's address' organization.
The phone
attribute’s getter must return the this's address' phone number.
The postalCode
attribute’s getter must return the this's address' postal code.
The recipient
attribute’s getter must return the this's address' recipient.
The region
attribute’s getter must return the this's address' region.
The sortingCode
attribute’s getter must return the this's address' sorting code.
The addressLine
attribute’s getter must return the this's 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()
method, when invoked, runs these steps:
-
Let promise be a new promise.
-
Run the following steps in parallel:
-
Resolve promise with contacts source's supported properties.
-
-
Return promise.
6.4.2. select()
select(properties, options)
method, when invoked, runs these steps:
-
Let global be the this's relevant global object.
-
Let navigable be global’s navigable.
-
If navigable is not a top-level traversable, then return a promise rejected with an
InvalidStateError
DOMException
. -
If global does not have transient activation, then return a promise rejected with a
SecurityError
DOMException
. -
Otherwise, consume user activation of the global.
-
If navigable’s contact picker is showing flag is set then return a promise rejected with an
InvalidStateError
DOMException
. -
If properties is empty, then return a promise rejected with a
TypeError
. -
For each property of properties:
-
If contacts source's supported properties does not contain property, then return a promise rejected with a
TypeError
.
-
-
Set navigable’s contact picker is showing flag.
-
Let promise be a new promise.
-
Run the following steps in parallel:
-
Let selectedContacts be be the result of launching a contact picker with options’
multiple
member and properties. If this fails, then:-
Queue a contact picker task to run these steps:
-
Reject promise an
InvalidStateError
DOMException
. -
Unset navigable’s contact picker is showing flag.
-
Abort these steps.
-
-
-
Unset navigable’s contact picker is showing flag.
-
Queue a contact picker task to run these steps:
-
Let contacts be an empty list.
-
For each selectedContact in selectedContacts:
-
Let contact be a new
ContactInfo
with:address
-
selectedContact’s addresses if properties contains "
address
", otherwise undefined. email
-
selectedContact’s emails if properties contains "
email
", otherwise undefined. icon
-
selectedContact’s icons if properties contains "
icon
", otherwise undefined. name
-
selectedContact’s names if properties contains "
name
", otherwise undefined. tel
-
selectedContact’s numbers if properties contains "
tel
", otherwise undefined.
-
Append contact to contacts.
-
-
Resolve promise with contacts.
-
-
-
Return promise.
7. Contact Picker
DOMString
s), the user agent MUST present a user
interface that follows these rules:
-
If presenting a user interface fails or accessing the contacts source's available contacts fails, then return failure.
-
The UI MUST prominently display the top-level traversable's origin.
-
The UI MUST make it clear which
properties
of the contacts are requested.NOTE: This information is derived from properties.
-
The UI SHOULD provide a way for users to opt out of sharing certain contact information.
NOTE: If the user opts out, the appropriate user contact fields should be modified before returning the selected contacts. It should be indistinguishable from the returned user contacts whether the user opted out from sharing some information or if the information was not present to begin with.
-
The UI MUST make it clear which information will be shared.
-
The UI MUST provide a way to select individual contacts. If allowMultiple is false, only one contact should be pickable.
-
The UI MUST provide an option to cancel/return without sharing any contacts, in which case remove the UI and return an empty list.
-
The UI MUST provide an a way for users to indicate that they are done selecting, in which case remove the UI and return a list of the selected contacts as user contacts.
8. Creating a ContactAddress
from user-provided input
The steps to create a ContactAddress
from user-provided input are given by the following algorithm.
The algorithm optionally takes a list redactList.
If the redactList is not passed, it defaults to an empty list.
NOTE: The redactList optionally gives user agents
the possibility to limit the amount of personal information
about the recipient that the API shares with the requesting application.
The resulting ContactAddress
object provides enough information
to perform necessary operations
such as communication or service delivery,
but, in most cases,
not enough information to physically locate and uniquely identify the recipient.
Unfortunately, even with the redactList,
recipient anonymity cannot be assured.
This is because in some countries
postal codes are so fine-grained that they can uniquely identify a recipient.
-
Let details be the map « "addressLine" → empty list, "country" → "", "phone" → "", "city" → "", "dependentLocality" → "", "organization" → "", "postalCode" → "", "recipient" → "", "region" → "", "sortingCode" → "" ».
-
If redactList doesn’t contain "addressLine", set details["addressLine"] to the result of splitting the user-provided address line into a list.
NOTE: How to split an address line is locale dependent and beyond the scope of this specification.
-
If redactList doesn’t contain "country", set details["country"] to the user-provided country as an upper case [ISO3166-1] alpha-2 code.
-
If redactList doesn’t contain "phone", set details["phone"] to the user-provided phone number.
NOTE: To maintain users' privacy, implementers need to be mindful that a contact address’s associated phone number might be different or the same from that of the end user’s. As such, implementers need to take care to not provide the end user’s phone number without the end user’s consent.
-
If redactList doesn’t contain "city", set details["city"] to the user-provided city.
-
If redactList doesn’t contain "dependentLocality", set details["dependentLocality"] to the user-provided dependent locality.
-
If redactList doesn’t contain "organization", set details["organization"] to the user-provided recipient organization.
-
If redactList doesn’t contain "postalCode", set details["postalCode"] to the user-provided postal code. Optionally, redact part of details["postalCode"].
NOTE: Postal codes in certain countries can be so specific as to uniquely identify an individual. This being a privacy concern, some user agents only return the part of a postal code that they deem sufficient for the application’s needs. This varies across countries and regions, and so the choice to redact part, or all, of the postal code is left to the discretion of implementers in the interest of protecting users' privacy.
-
If redactList doesn’t contain "recipient", set details["recipient"] to the user-provided recipient of the contact information.
-
If redactList doesn’t contain "region", set details["region"] to the user-provided region.
NOTE: In some countries (e.g., Belgium) it is uncommon for users to include a region as part of a physical address (even if all the regions of a country are part of [ISO3166-2]). As such, when the user agent knows that the user is inputting the address for a particular country, it might not provide a field for the user to input a region. In such cases, the user agent returns an empty string for both
ContactAddress
'sregion
attribute - but the address can still serve its intended purpose (e.g., be valid for communication or service delivery). -
If redactList doesn’t contain "sortingCode", set details["sortingCode"] to the user-provided sorting code.
-
Return a newly created
ContactAddress
whose attribute’s value’s match those in details.
9. Acknowledgments
There has been multiple earlier attempts to standardize a Contacts API for the web and this API strives to learn from this rich history. Earlier attempts include Mozilla’s Contacts API, Contacts API W3C Member submission and standardization efforts by W3C Working Groups: Contacts API, Pick Contacts Intent, and Contacts Manager API. The Contact Picker API differs in its approach to privacy, which was the main emphasis of the API when it was designed. Unlike previous attempts which allow for perpetual access after granted permission, or include a vague privacy model, this spec enforces UI restrictions which give users full control over shared data and limit abuse. For example, a picker model is enforced where the user always acts as an intermediary of the shared contact info with full control every time contacts are requested. For more historical context, please refer to the Status of the Document sections of the earlier attempts.