├── .github └── workflows │ └── build-validate-publish.yml ├── .pr-preview.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── index.bs ├── security-privacy-self-assessment.md └── w3c.json /.github/workflows/build-validate-publish.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: {} 4 | push: 5 | branches: [main] 6 | paths: 7 | - 'index.bs' 8 | 9 | jobs: 10 | main: 11 | name: Build, Validate, and Publish 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: w3c/spec-prod@v2 16 | with: 17 | SOURCE: index.bs 18 | DESTINATION: index.html 19 | TOOLCHAIN: bikeshed 20 | W3C_ECHIDNA_TOKEN: ${{ secrets.W3C_TR_TOKEN }} 21 | W3C_WG_DECISION_URL: https://lists.w3.org/Archives/Public/public-device-apis/2021May/0008.html 22 | GH_PAGES_BRANCH: gh-pages 23 | BUILD_FAIL_ON: nothing 24 | W3C_BUILD_OVERRIDE: | 25 | status: WD -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.bs", 3 | "type": "bikeshed", 4 | "params": { 5 | "force": 1 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All documentation, code and communication under this repository are covered by the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Web Platform Incubator Community Group 2 | 3 | This repository is being used for work in the W3C Web Platform Incubator Community Group, governed by the [W3C Community License 4 | Agreement (CLA)](http://www.w3.org/community/about/agreements/cla/). To make substantive contributions, 5 | you must join the CG. 6 | 7 | If you are not the sole contributor to a contribution (pull request), please identify all 8 | contributors in the pull request comment. 9 | 10 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 11 | 12 | ``` 13 | +@github_username 14 | ``` 15 | 16 | If you added a contributor by mistake, you can remove them in a comment with: 17 | 18 | ``` 19 | -@github_username 20 | ``` 21 | 22 | If you are making a pull request on behalf of someone else but you had no part in designing the 23 | feature, you can remove yourself with the above syntax. 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors 2 | under the 3 | [W3C Software and Document License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). 4 | 5 | Contributions to Specifications are made under the 6 | [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 7 | 8 | Contributions to Test Suites are made under the 9 | [W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Contact Picker API 2 | 3 | [Link to spec](https://w3c.github.io/contact-picker/spec/index.html) 4 | 5 | This is a proposal for adding a Contact Picker API to the Web. Contact pickers are frequently seen in native mobile applications for a variety of [use cases](#use-cases), and in various desktop applications such as e-mail clients and calendars. 6 | 7 | Contact information is among the most sensitive information on a device, which is why we're proposing a picker rather than a lower level API. This enables user agents to offer an experience that's consistent with platform expectations, and to highlight the exact information that is to be shared with the website. 8 | 9 | This proposal does not address the source of the contacts. Many operating systems offer functionality to access the user's contacts, and various browsers allow the user to log in which might provide access to contact information as well. 10 | 11 | ## Use cases 12 | * Social networks could use contact information to bootstrap a user's social graph. This is particularly important for emerging markets, where users might prefer a PWA over a native app due to storage constraints. 13 | * An e-mail application could allow the user to select recipients for a message without needing its own address book. 14 | 15 | ## Why do we need a new built-in API? 16 | * Excluding the operating system, there is no canonical place where users maintain a list of their contacts. Developers would have to integrate with a variety of services to provide a comprehensive experience. 17 | * User agents can offer an experience to users that's consistent with other applications on the user's device. The information that will be shared with the website can be clearly highlighted. 18 | * Contact information is sensitive. Individual contacts can have [any number of properties](https://en.wikipedia.org/wiki/VCard#Properties) associated with them. Websites seeking the e-mail address of a single user should not have access to all other contacts. 19 | 20 | ## Example 21 | ```javascript 22 | selectRecipientsButton.addEventListener('click', async () => { 23 | const contacts = await navigator.contacts.select(['name', 'email'], { multiple: true }); 24 | 25 | if (!contacts.length) { 26 | // Either no contacts were selected in the picker, or the picker could 27 | // not be launched. Exposure of the API implies expected availability. 28 | return; 29 | } 30 | 31 | // Use the names and e-mail addresses in |contacts| to populate the 32 | // recipients field in the website's UI. 33 | populateRecipients(contacts); 34 | }); 35 | ``` 36 | 37 | ## Security and Privacy 38 | Exposing contact information has a clear privacy impact. The spec proposes a picker model so that the user agent can make it clear what information is going to be shared with the website. This differs from native APIs where the permission is requested once, after which the application gets perpetual access to the user's contacts. With the picker model, the website gets a one-off list of contacts selected by the user. Furthermore, The API will only be available from secure top-level contexts. 39 | 40 | ## Abuse Scenarios 41 | 42 | ### Sharing contacts with unintended recipients 43 | The first mitigation against this abuse vector is that the API is only available from top-level contexts. This means embedded iframes can't request contact information. 44 | 45 | The spec also enforces that the picker UI shows which origin the contacts will be shared with, which contacts will be shared, and what information regarding the contacts will be shared. 46 | 47 | ### Websites forcing users to share contacts 48 | The picker UI MUST have a cancel/return option for the user to not share any contacts. The spec also recommends that the UI have a way for users to opt out of sharing certain contact information, in a way that the website cannot know whether the user opted out. 49 | 50 | Implementers should be wary of malicious website UI that can be rendered side-by-side with the picker. The UI can pretend to be part of the user agent's UI and encourage users to share more information than they intended to. A potential workaround to avoid this would be making the picker fullscreen. 51 | 52 | ### Websites spamming users with the picker 53 | To prevent abuse and user annoyance, the API has some usability restrictions. The picker can only be brought up if the API call was initiated by a user gesture. This means websites can't bring up the picker on website load. There can also be one instance of the picker at any given time, so websites can't request multiple stacked pickers on top of each other. 54 | 55 | ## Alternatives Considered 56 | 57 | ##### `` 58 | The Web Platform already supports a picker for external data through ``, which some user agents specialize based on the mime types that are being selected. 59 | 60 | Supporting contacts this way has a number of downsides: 61 | * There is much contention in file types for expressing contact information. For most use cases, a `FileReader` would be used to read and parse the file in order to access the individual properties. We can optimize for this. Libraries can be used for converting this data to a format of the developer's choosing. 62 | * Feature detection is harder, and would likely need a new API such as `HTMLInputElement.isTypeSupported()` mimicking the [`MediaRecorder`](https://www.w3.org/TR/mediastream-recording/#dom-mediarecorder-istypesupported). Unlike existing specializations of file pickers, contacts would be unlikely to gracefully degrade to a general picker. 63 | * Extensibility is harder, as it would rely on additional parameters being passed to the mime type. 64 | 65 | #### Previous Standardization Attempts 66 | There have been multiple standardization attempts to bring a Contacts API to the web. Here's a list of them and brief explanation of why they never materialized. 67 | * https://lists.w3.org/Archives/Public/public-device-apis/2009Apr/att-0001/contacts.html 68 | 69 | This attempt was _donated_ by Nokia rather than being formally put on the standardization track, and it seems to have not been picked up since. 70 | 71 | * https://www.w3.org/TR/2010/WD-contacts-api-20100121/ 72 | 73 | This attempt was shelved in 2014 in favour of Web Intents, which never materialized. 74 | 75 | * https://www.w3.org/TR/2015/NOTE-contacts-manager-api-20150602/ 76 | 77 | This attempt built on top of the 2010 one by adding more programmatic access to the user's address book. This seems to be quite distantly separated from today's interest from implementations. 78 | 79 | And some proprietary attempts. 80 | * https://wiki.mozilla.org/WebAPI/ContactsAPI 81 | 82 | This attempt was not put on the standardization track, and doesn't seem to have been picked up since. 83 | 84 | The current proposal 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. 85 | 86 | ## Potential follow-up work 87 | * As mentioned, more properties can be added iteratively as use cases are identified. It is a non-goal of this API to share _all_ information stored for a particular contact without filtering. 88 | * A Contact Provider API can be considered to complement data available from the operating system or the user's account. 89 | -------------------------------------------------------------------------------- /index.bs: -------------------------------------------------------------------------------- 1 |
  2 | Title: Contact Picker API
  3 | Shortname: contact-picker
  4 | Status: ED
  5 | Group: dap
  6 | Level: none
  7 | ED: https://w3c.github.io/contact-picker/
  8 | TR: https://www.w3.org/TR/contact-picker/
  9 | Editor: Peter Beverloo 44819, Google, beverloo@google.com
 10 | Former Editor: Rayan Kanso 115452, Google, rayankans@google.com
 11 | Abstract: An API to give one-off access to a user's contact information with full control over the shared data.
 12 | Markup Shorthands: css no, markdown yes
 13 | Indent: 2
 14 | text macro: JOINT yes
 15 | text macro: JOINTWEBAPPS yes
 16 | 
17 | 18 | 24 | 25 | # Introduction # {#intro} 26 | 27 | Contact pickers are frequently seen in various desktop and native mobile applications for a variety 28 | of use cases. This specification defines an API to bring contact pickers to the web, which will 29 | enable new use cases for web apps, such as: 30 | * Bootstrapping a user's social graph for social networks. 31 | * Selecting the recipients of a message within an e-mail application. 32 | 33 | The contact picker model was chosen to give full control to users over the shared data, allowing 34 | users to choose exactly which contacts to provide to the website. The contact picker model gives 35 | websites one-off access to a user's contacts, meaning developers have to request access to the 36 | user's contacts every time they need it. This differs from some native contact APIs, but is 37 | necessary for ensuring users' contacts are not accessed without their knowledge and explicit 38 | consent. 39 | 40 | ## Examples ## {#examples} 41 | 42 |
43 | Requesting contacts as a result of a user click. 44 | 45 |
 46 |     selectRecipientsButton.addEventListener('click', async () => {
 47 |       const contacts = await navigator.contacts.select(['name', 'email'], {multiple: true});
 48 | 
 49 |       if (!contacts.length) {
 50 |         // No contacts were selected in the picker.
 51 |         return;
 52 |       }
 53 | 
 54 |       // Use the names and e-mail addresses in |contacts| to populate the
 55 |       // recipients field in the website's UI.
 56 |       populateRecipients(contacts);
 57 |     });
 58 |   
59 | 60 | In the above example `selectRecipientsButton` is a {{HTMLButtonElement}}, and `populateRecipients` 61 | is a developer-defined function. 62 |
63 | 64 | 65 |
66 | Requesting an address to deliver a gift to. 67 | 68 |
 69 |     selectRecipientButton.addEventListener('click', async () => {
 70 | 
 71 |       // We are unsure if addresses are supported, or can be provided by the browser.
 72 |       if ((await navigator.contacts.getProperties()).includes('address')) {
 73 |         const contacts = await navigator.contacts.select(['address']);
 74 | 
 75 |         if (!contacts.length) {
 76 |           // No contacts were selected in the picker.
 77 |           return;
 78 |         }
 79 | 
 80 |         // length is 1 since we didn't request multiple contacts.
 81 |         sendGiftToAddress(contacts[0].address);
 82 |       }
 83 | 
 84 |      // Fallback to a form.
 85 |     });
 86 |   
87 | 88 | In the above example `selectRecipientButton` is a {{HTMLButtonElement}}, and `sendGiftToAddress` 89 | is a developer-defined function. 90 |
91 | 92 |
93 | Requesting a name and an icon. 94 | 95 |
 96 |     selectRecipientButton.addEventListener('click', async () => {
 97 | 
 98 |       // We are unsure if icons are supported, or can be provided by the browser.
 99 |       if ((await navigator.contacts.getProperties()).includes('icon')) {
100 |         const contacts = await navigator.contacts.select(['name', 'icon']);
101 | 
102 |         if (!contacts.length) {
103 |           // No contacts were selected in the picker.
104 |           return;
105 |         }
106 | 
107 |         if (!contacts[0].name.length || !contacts[0].icon.length) {
108 |           // Info not found. Use fallback.
109 |           return;
110 |         }
111 | 
112 |         // We only need one name and one image.
113 |         const name = contacts[0].name[0];
114 |         const imgBlob = contacts[0].icon[0];
115 | 
116 |         // Display image.
117 |         const url = URL.createObjectURL(imgBlob);
118 |         imgContainer.onload = () => URL.revokeObjectURL(url);
119 |         imgContainer.src = url;
120 | 
121 |         // Alternatively use a Bitmap.
122 |         const imgBitmap = await createImageBitmap(imgBlob);
123 | 
124 |         // Upload icon.
125 |         const response = await fetch('/contacticon', {method: 'POST', body: imgBlob});
126 |       }
127 |     });
128 |   
129 | In the above example `selectRecipientButton` is a {{HTMLButtonElement}}, and `imgContainer` 130 | is a {{HTMLImageElement}}. 131 |
132 | 133 | # Privacy Considerations # {#privacy} 134 | 135 | Exposing contact information has a clear privacy impact, in terms of exposing PII of uninvolved 136 | parties. A picker model is enforced so that the user agent can offer a user experience that makes 137 | it clear what information is going to be shared with the website and when. 138 | 139 | The following constraints are also enforced: 140 | * A user gesture is needed to initiate the API, to disallow programmatic requests to the user's 141 | * The API is only available in a [=navigable/top-level traversable=], which must also be a 142 | [=secure context=]. These restrictions help ensure that the provided contact information reaches 143 | its intended recipient. 144 | * [=Transient activation=] is needed to initiate the API, to disallow programmatic requests to the user's 145 | contacts. 146 | 147 | # Security Considerations # {#security-considerations} 148 | 149 | * The API is only available in a [=navigable/top-level traversables=], which must also be a 150 | [=secure context=]. These restrictions help ensure that the provided contact information reaches 151 | its intended recipient. 152 | 153 | # Realms # {#realms} 154 | 155 | All platform objects are created in the [=this=]'s [=relevant Realm=] unless otherwise 156 | specified. 157 | 158 | # Infrastructure # {#infrastructure} 159 | 160 | The contact picker task source is a [=task source=]. 161 | 162 |
163 | To queue a contact picker task on an optional |eventLoop| (an [=/event loop=], 164 | defaulting to the caller's [=this=]'s [=relevant settings object=]'s 165 | [=responsible event loop=]) with |steps| (steps), [=queue a task=] on |eventLoop| using the 166 | [=contact picker task source=] to run |steps|. 167 |
168 | 169 | ## Physical address ## {#infrastructure-physical-address} 170 | 171 | A physical address consists of: 172 |
173 | 174 | * country, a {{DOMString}} representing the country of the address as an 175 | [[ISO3166-1]] alpha-2 code stored in its canonical uppercase form or the empty string. For 176 | example, "JP". 177 | * address line, a [=list=] of {{DOMString}}s, containing the most specific part of the 178 | address. It can include, for example, a street name, a house number, apartment number, a rural 179 | delivery route, descriptive instructions, or a post office box number. 180 | * region, a {{DOMString}} representing the top level administrative subdivision of the 181 | country. For example, this can be a state, a province, an oblast, or a prefecture. 182 | * city, a {{DOMString}} representing the city/town portion of the address. 183 | * dependent locality, a {{DOMString}} representing the dependent locality or sublocality 184 | within a city. For example, neighborhoods, boroughs, districts, or UK dependent localities. 185 | * postal code, a {{DOMString}} representing the postal code or ZIP code, also known as 186 | PIN code in India. 187 | * sorting code, a {{DOMString}} representing the sorting code system, such as the CEDEX 188 | system used in France. 189 | * organization, a {{DOMString}} representing the organization, firm, company, or 190 | institution at the address. 191 | * recipient, a {{DOMString}} representing the name of the recipient or contact person at 192 | the address. 193 | * phone number, a {{DOMString}} representing the phone number of the recipient or contact 194 | person at the address, optionally structured to adhere to [[E.164]]. 195 | 196 |
197 | 198 | ## User contact ## {#infrastructure-user-contact} 199 | 200 | A user contact consists of: 201 |
202 | 203 | * names, a [=list=] of {{DOMString}}s, each [=list/item=] representing a unique name 204 | corresponding to the user. 205 | * emails, a [=list=] of {{DOMString}}s, each [=list/item=] representing a unique 206 | [=valid email address=] of the user. 207 | * numbers, a [=list=] of {{DOMString}}s, each [=list/item=] representing a unique phone 208 | number of the user. 209 | * addresses, a [=list=] of {{ContactAddress}}es, each [=list/item=] representing a 210 | unique [=physical address=] of the user. 211 | * icons, a [=list=] of {{Blob}}s, each [=list/item=] representing a unique image of the 212 | user. 213 | 214 | NOTE: An icon {{Blob}}'s {{Blob/type}} is an [=image mime type=]. 215 | 216 |
217 | 218 | A [=user contact=] contains data relating to a single user. 219 | 220 | Note: The lists can be of different sizes, and entries with the same index don't need to correspond 221 | to each other. 222 | 223 | ## Contacts source ## {#infrastructure-contacts-source} 224 | 225 | The contacts source is a service that provides the user's contact information to 226 | the user agent. 227 | 228 | A [=contacts source=] consists of: 229 |
230 | 231 | * available contacts, a [=list=] of [=user contacts=]. 232 | * supported properties, a [=list=] of [=available=] {{ContactProperty}} values. 233 | 234 |
235 | 236 | Note: It is up to the user agent to choose the [=contacts source=]. 237 | 238 | 239 | 240 | # API Description # {#api} 241 | 242 | ## Extensions to {{Navigator}} ## {#extensions-to-navigator} 243 | 244 | 250 | 251 |
252 | A {{Navigator}} has a contacts manager (a {{ContactsManager}}), initially a new 253 | {{ContactsManager}}. 254 | 255 | The contacts attribute's getter must return the [=this=]'s 256 | [=Navigator/contacts manager=]. 257 |
258 | 259 | The [=Window/navigable=] has a contact picker is showing flag, initially unset. 260 | 261 | ## {{ContactProperty}} ## {#contact-property} 262 | 263 | 266 | 267 | A {{ContactProperty}} is considered to be available if its associated [=user contact=] 268 | field can be accessed by the user agent. 269 | 270 | : "address" 271 | :: Associated with [=user contact=]'s [=user contact/addresses=]. 272 | : "email" 273 | :: Associated with [=user contact=]'s [=user contact/emails=]. 274 | : "icon" 275 | :: Associated with [=user contact=]'s [=user contact/icons=]. 276 | : "name" 277 | :: Associated with [=user contact=]'s [=user contact/names=]. 278 | : "tel" 279 | :: Associated with [=user contact=]'s [=user contact/numbers=]. 280 | 281 | ## {{ContactAddress}} ## {#contact-address} 282 | 283 | 299 | 300 | The {{ContactAddress}} interface represents a [=physical address=]. 301 | 302 |
303 | A {{ContactAddress}} instance has: 304 | 305 | * An address (a [=physical address=]). 306 | 307 | The city attribute's getter must return the [=this=]'s 308 | [=ContactAddress/address=]' [=physical address/city=]. 309 | 310 | The country attribute's getter must return the [=this=]'s 311 | [=ContactAddress/address=]' [=physical address/country=]. 312 | 313 | The dependentLocality attribute's getter must return the [=this=]'s 314 | [=ContactAddress/address=]' [=physical address/dependent locality=]. 315 | 316 | The organization attribute's getter must return the [=this=]'s 317 | [=ContactAddress/address=]' [=physical address/organization=]. 318 | 319 | The phone attribute's getter must return the [=this=]'s 320 | [=ContactAddress/address=]' [=physical address/phone number=]. 321 | 322 | The postalCode attribute's getter must return the [=this=]'s 323 | [=ContactAddress/address=]' [=physical address/postal code=]. 324 | 325 | The recipient attribute's getter must return the [=this=]'s 326 | [=ContactAddress/address=]' [=physical address/recipient=]. 327 | 328 | The region attribute's getter must return the [=this=]'s 329 | [=ContactAddress/address=]' [=physical address/region=]. 330 | 331 | The sortingCode attribute's getter must return the [=this=]'s 332 | [=ContactAddress/address=]' [=physical address/sorting code=]. 333 | 334 | The addressLine attribute's getter must return the [=this=]'s 335 | [=ContactAddress/address=]' [=physical address/address line=]. 336 |
337 | 338 | ## {{ContactsManager}} ## {#contacts-manager} 339 | 340 | 359 | 360 |
361 | 362 | ### {{ContactsManager/getProperties()}} ### {#contacts-manager-getproperties} 363 | 364 |
365 | The getProperties() method, when invoked, runs these steps: 366 | 367 | 1. Let |promise| be [=a new promise=]. 368 | 1. Run the following steps [=in parallel=]: 369 | 1. Resolve |promise| with [=contacts source=]'s [=contacts source/supported properties=]. 370 | 1. Return |promise|. 371 | 372 |
373 | 374 | ### {{ContactsManager/select()}} ### {#contacts-manager-select} 375 | 376 |
377 | The select(|properties|, |options|) method, when invoked, runs these steps: 378 | 379 | 1. Let |global| be the [=this=]'s [=relevant global object=]. 380 | 1. Let |navigable| be |global|'s [=Window/navigable=]. 381 | 1. If |navigable| is not a [=navigable/top-level traversable=], then return 382 | [=a promise rejected with=] an {{InvalidStateError}} {{DOMException}}. 383 | 1. If |global| does not have [=transient activation=], then return 384 | [=a promise rejected with=] a {{SecurityError}} {{DOMException}}. 385 | 1. Otherwise, [=consume user activation=] of the |global|. 386 | 1. If |navigable|'s [=contact picker is showing flag=] is set then return 387 | [=a promise rejected with=] an {{InvalidStateError}} {{DOMException}}. 388 | 1. If |properties| is [=list/empty=], then return [=a promise rejected with=] a {{TypeError}}. 389 | 1. [=list/For each=] |property| of |properties|: 390 | 1. If [=contacts source=]'s [=contacts source/supported properties=] does not [=list/contain=] 391 | |property|, then return [=a promise rejected with=] a {{TypeError}}. 392 | 1. Set |navigable|'s [=contact picker is showing flag=]. 393 | 1. Let |promise| be [=a new promise=]. 394 | 1. Run the following steps [=in parallel=]: 395 | 1. Let |selectedContacts| be be the result of [=launching a contact picker=] with |options|' 396 | `multiple` member and |properties|. If this fails, then: 397 | 1. [=Queue a contact picker task=] to run these steps: 398 | 1. [=Reject=] |promise| an {{InvalidStateError}} {{DOMException}}. 399 | 1. Unset |navigable|'s [=contact picker is showing flag=]. 400 | 1. Abort these steps. 401 | 1. Unset |navigable|'s [=contact picker is showing flag=]. 402 | 1. [=Queue a contact picker task=] to run these steps: 403 | 1. Let |contacts| be an empty [=list=]. 404 | 1. [=list/For each=] |selectedContact| in |selectedContacts|: 405 | 1. Let |contact| be a new {{ContactInfo}} with: 406 | : {{ContactInfo/address}} 407 | :: |selectedContact|'s [=user contact/addresses=] if |properties| [=list/contains=] 408 | "`address`", otherwise undefined. 409 | : {{ContactInfo/email}} 410 | :: |selectedContact|'s [=user contact/emails=] if |properties| [=list/contains=] 411 | "`email`", otherwise undefined. 412 | : {{ContactInfo/icon}} 413 | :: |selectedContact|'s [=user contact/icons=] if |properties| [=list/contains=] 414 | "`icon`", otherwise undefined. 415 | : {{ContactInfo/name}} 416 | :: |selectedContact|'s [=user contact/names=] if |properties| [=list/contains=] 417 | "`name`", otherwise undefined. 418 | : {{ContactInfo/tel}} 419 | :: |selectedContact|'s [=user contact/numbers=] if |properties| [=list/contains=] 420 | "`tel`", otherwise undefined. 421 | 1. [=list/Append=] |contact| to |contacts|. 422 | 1. Resolve |promise| with |contacts|. 423 | 1. Return |promise|. 424 |
425 | 426 | # Contact Picker # {#contact-picker} 427 | 428 |
429 | To launch a contact picker with |allowMultiple| (a 430 | [=boolean=]), and |properties| (a [=list=] of {{DOMString}}s), the user agent MUST present a user 431 | interface that follows these rules: 432 | 433 | * If presenting a user interface fails or accessing the [=contacts source=]'s 434 | [=contacts source/available contacts=] fails, then return failure. 435 | * The UI MUST prominently display the [=navigable/top-level traversable=]'s [=origin=]. 436 | * The UI MUST make it clear which `properties` of the contacts are requested. 437 | 438 | NOTE: This information is derived from |properties|. 439 | 440 | * The UI SHOULD provide a way for users to opt out of sharing certain contact information. 441 | 442 | NOTE: If the user opts out, the appropriate [=user contact=] fields should be modified before 443 | returning the selected contacts. It should be indistinguishable from the returned 444 | [=user contact=]s whether the user opted out from sharing some information or if the 445 | information was not present to begin with. 446 | 447 | * The UI MUST make it clear which information will be shared. 448 | * The UI MUST provide a way to select individual contacts. If |allowMultiple| is false, only one 449 | contact should be pickable. 450 | * The UI MUST provide an option to cancel/return without sharing any contacts, in which case 451 | remove the UI and return an empty [=list=]. 452 | * The UI MUST provide an a way for users to indicate that they are done selecting, in which case 453 | remove the UI and return a [=list=] of the selected contacts as [=user contacts=]. 454 |
455 | 456 | # Creating a `ContactAddress` from user-provided input # {#creating-contactaddress} 457 | 458 | The steps to create a `ContactAddress` from user-provided input 459 | are given by the following algorithm. 460 | The algorithm optionally takes a [=list=] |redactList|. 461 | If the |redactList| is not passed, it defaults to an [=list/empty=] [=list=]. 462 | 463 | NOTE: The |redactList| optionally gives user agents 464 | the possibility to limit the amount of personal information 465 | about the recipient that the API shares with the requesting application. 466 | The resulting {{ContactAddress}} object provides enough information 467 | to perform necessary operations 468 | such as communication or service delivery, 469 | but, in most cases, 470 | not enough information to physically locate and uniquely identify the recipient. 471 | Unfortunately, even with the |redactList|, 472 | recipient anonymity cannot be assured. 473 | This is because in some countries 474 | postal codes are so fine-grained that they can uniquely identify a recipient. 475 | 476 |
477 | 478 | 1. Let |details| be the [=map=] « 479 | "addressLine" → empty [=list=], 480 | "country" → "", 481 | "phone" → "", 482 | "city" → "", 483 | "dependentLocality" → "", 484 | "organization" → "", 485 | "postalCode" → "", 486 | "recipient" → "", 487 | "region" → "", 488 | "sortingCode" → "" 489 | ». 490 | 1. If |redactList| doesn't [=list/contain=] "addressLine", 491 | set |details|["addressLine"] to the result of splitting the user-provided address line into a [=list=]. 492 | 493 | NOTE: How to split an address line is locale dependent 494 | and beyond the scope of this specification. 495 | 496 | 1. If |redactList| doesn't [=list/contain=] "country", 497 | set |details|["country"] to the user-provided [=physical address/country=] as an upper case [[ISO3166-1]] alpha-2 code. 498 | 1. If |redactList| doesn't [=list/contain=] "phone", 499 | set |details|["phone"] to the user-provided [=physical address/phone number=]. 500 | 501 | NOTE: To maintain users' privacy, 502 | implementers need to be mindful 503 | that a contact address's associated phone number 504 | might be different or the same from that of the end user's. 505 | As such, 506 | implementers need to take care 507 | to not provide the end user's phone number without the end user's consent. 508 | 509 | 1. If |redactList| doesn't [=list/contain=] "city", 510 | set |details|["city"] to the user-provided [=physical address/city=]. 511 | 1. If |redactList| doesn't [=list/contain=] "dependentLocality", 512 | set |details|["dependentLocality"] to the user-provided [=physical address/dependent locality=]. 513 | 1. If |redactList| doesn't [=list/contain=] "organization", 514 | set |details|["organization"] to the user-provided recipient [=physical address/organization=]. 515 | 1. If |redactList| doesn't [=list/contain=] "postalCode", 516 | set |details|["postalCode"] to the user-provided [=physical address/postal code=]. 517 | Optionally, redact part of |details|["postalCode"]. 518 | 519 | NOTE: [=physical address/Postal codes=] 520 | in certain countries can be so specific 521 | as to uniquely identify an individual. 522 | This being a privacy concern, 523 | some user agents only return the part of a postal code 524 | that they deem sufficient for the application's needs. 525 | This varies across countries and regions, 526 | and so the choice to redact part, 527 | or all, 528 | of the postal code is left to the discretion of implementers 529 | in the interest of protecting users' privacy. 530 | 531 | 1. If |redactList| doesn't [=list/contain=] "recipient", 532 | set |details|["recipient"] to the user-provided [=physical address/recipient=] of the contact information. 533 | 1. If |redactList| doesn't [=list/contain=] "region", 534 | set |details|["region"] to the user-provided [=physical address/region=]. 535 | 536 | NOTE: In some countries (e.g., Belgium) 537 | it is uncommon for users to include a [=physical address/region=] 538 | as part of a [=physical address=] 539 | (even if all the regions of a country are part of [[ISO3166-2]]). 540 | As such, 541 | when the user agent knows that the user is inputting the address 542 | for a particular country, 543 | it might not provide a field for the user to input a [=physical address/region=]. 544 | In such cases, 545 | the user agent returns an empty string for both {{ContactAddress}}'s 546 | {{ContactAddress/region}} attribute - but the address can still serve its intended purpose 547 | (e.g., be valid for communication or service delivery). 548 | 549 | 1. If |redactList| doesn't [=list/contain=] "sortingCode", 550 | set |details|["sortingCode"] to the user-provided [=physical address/sorting code=]. 551 | 1. Return a newly created {{ContactAddress}} whose attribute's value's match those in |details|. 552 |
553 | 554 | 555 | 556 | # Acknowledgments # {#acknowledgments} 557 | 558 | There has been multiple earlier attempts to standardize a Contacts API for the web and this 559 | API strives to learn from this rich history. Earlier attempts include Mozilla's 560 | [Contacts API](https://wiki.mozilla.org/WebAPI/ContactsAPI), 561 | [Contacts API](https://lists.w3.org/Archives/Public/public-device-apis/2009Apr/att-0001/contacts.html) 562 | W3C Member submission and standardization efforts by W3C Working Groups: 563 | [Contacts API](https://www.w3.org/TR/2010/WD-contacts-api-20100121/), 564 | [Pick Contacts Intent](https://www.w3.org/TR/2014/NOTE-contacts-api-20140114/), and 565 | [Contacts Manager API](https://www.w3.org/TR/2015/NOTE-contacts-manager-api-20150602/). 566 | The Contact Picker API differs in its approach to privacy, which was the main emphasis of the API 567 | when it was designed. Unlike previous attempts which allow for perpetual access after granted 568 | permission, or include a vague privacy model, this spec enforces UI restrictions which give users 569 | full control over shared data and limit abuse. For example, a picker model is enforced where the 570 | user always acts as an intermediary of the shared contact info with full control every time 571 | contacts are requested. For more historical context, please refer to the Status of the Document 572 | sections of the earlier attempts. 573 | -------------------------------------------------------------------------------- /security-privacy-self-assessment.md: -------------------------------------------------------------------------------- 1 | https://www.w3.org/TR/security-privacy-questionnaire/ 2 | 3 | ### 3.1 Does this specification deal with personally-identifiable information? 4 | 5 | Yes: names, e-mail addresses and phone numbers of people in a device's contact database. 6 | 7 | ### 3.2 Does this specification deal with high-value data? 8 | 9 | Yes: see 3.1. 10 | 11 | ### 3.3 Does this specification introduce new state for an origin that persists across browsing sessions? 12 | 13 | No. 14 | 15 | ### 3.4 Does this specification expose persistent, cross-origin state to the web? 16 | 17 | Contact information is specific to the user, so depending on whether they choose to share the same information with multiple origins it may be. 18 | 19 | ### 3.5 Does this specification expose any other data to an origin that it doesn’t currently have access to? 20 | 21 | Yes: contact information in the form of names, e-mail addresses and phone numbers. Implementations MUST make it clear to the user what information is being shared, and the user MUST be in control of selecting which information is being shared. 22 | 23 | ### 3.6 Does this specification enable new script execution/loading mechanisms? 24 | 25 | No. 26 | 27 | ### 3.7 Does this specification allow an origin access to a user’s location? 28 | 29 | No. 30 | 31 | ### 3.8 Does this specification allow an origin access to sensors on a user’s device? 32 | 33 | No. 34 | 35 | ### 3.9 Does this specification allow an origin access to aspects of a user’s local computing environment? 36 | 37 | Yes: their contact information. 38 | 39 | ### 3.10 Does this specification allow an origin access to other devices? 40 | 41 | No. 42 | 43 | ### 3.11 Does this specification allow an origin some measure of control over a user agent’s native UI? 44 | 45 | No. 46 | 47 | ### 3.12 Does this specification expose temporary identifiers to the web? 48 | 49 | No. 50 | 51 | ### 3.13 Does this specification distinguish between behavior in first-party and third-party contexts? 52 | 53 | No. 54 | 55 | ### 3.14 How should this specification work in the context of a user agent’s "incognito" mode? 56 | 57 | No change in behavior. 58 | 59 | ### 3.15 Does this specification persist data to a user’s local device? 60 | 61 | No. 62 | 63 | ### 3.16 Does this specification have a "Security Considerations" and "Privacy Considerations" section? 64 | 65 | _Not yet!_ 66 | 67 | ### 3.17 Does this specification allow downgrading default security characteristics? 68 | 69 | No. 70 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": [ 43696, 114929 ] 3 | , "contacts": ["himorin"] 4 | , "repo-type": "rec-track" 5 | } 6 | --------------------------------------------------------------------------------