联系人选择器 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出版物列表和此技术报告的最新修订版本可以在https://www.w3.org/TR/找到。

本文档由设备与传感器工作组和Web应用程序工作组以工作草案的形式发布,使用推荐路径。本文档旨在成为W3C推荐标准。

如果你希望对此文档发表评论,请在规范仓库中提交问题。

作为工作草案发布并不意味着得到了W3C及其成员的认可。此文档为草案,可能随时被更新、替换或废弃。不应将此文档作为最终的工作引用。

本文档由依据W3C专利政策运作的工作组生成。W3C维护了公开的专利披露列表(设备与传感器)和公开的专利披露列表(Web应用程序),这些页面还包括披露专利的说明。任何知道包含必要声明的专利的个人必须根据W3C专利政策的第6节披露信息。

有关工作组的公告、行政事务和非技术事项的电子邮件发送至public-device-apis@w3.org (订阅,存档) 或 public-webapps@w3.org (订阅,存档)。

本文档受2023年11月3日W3C流程文档管理。

1. 简介

联系人选择器在各种桌面和原生移动应用程序中经常被用于多种使用场景。本规范定义了一个API,将联系人选择器引入到Web中,这将为Web应用程序启用新的使用场景,例如:

联系人选择器模型被选定是为了给用户对共享数据的完全控制,允许用户精确选择要提供给网站的联系人。联系人选择器模型为网站提供了一次性的访问用户联系人的权限,这意味着开发者每次需要访问用户联系人时都必须请求权限。这与某些原生联系人API有所不同,但这是确保用户联系人不会在用户不知情或未明确同意的情况下被访问所必需的。

1.1. 示例

在用户点击时请求联系人。
selectRecipientsButton.addEventListener('click', async () => {
  const contacts = await navigator.contacts.select(['name', 'email'], {multiple: true});

  if (!contacts.length) {
    // 选择器中未选择任何联系人。
    return;
  }

  // 使用 |contacts| 中的姓名和电子邮件地址填充网站的收件人字段。
  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;
    }

    // 长度为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;

    // 或者使用位图。
    const imgBitmap = await createImageBitmap(imgBlob);

    // 上传图标。
    const response = await fetch('/contacticon', {method: 'POST', body: imgBlob});
  }
});

在上述示例中,selectRecipientButton 是一个 HTMLButtonElement,而 imgContainer 是一个 HTMLImageElement。

2. 隐私注意事项

暴露联系信息对隐私有明显的影响,涉及到暴露无关方的个人身份信息(PII)。强制执行选择器模型,以便用户代理可以提供清晰的用户体验,明确说明何时与网站共享哪些信息。

以下约束也被强制执行:

3. 安全注意事项

4. 领域

除非另有规定,所有平台对象都在this的相关领域中创建。

5. 基础设施

联系人选择器任务源是一个任务源。

要排队联系人选择器任务, 在可选的 eventLoop(一个事件循环, 默认为调用者的this的相关设置对象的负责的事件循环)上,使用 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的联系人管理器。

可导航对象具有一个联系人选择器显示标志,初始状态为未设置。

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实例包括:

city属性的getter应返回this的地址的城市。

country属性的getter应返回this的地址的国家。

dependentLocality属性的getter应返回this的地址的附属地区。

organization属性的getter应返回this的地址的组织。

phone属性的getter应返回this的地址的电话号码。

postalCode属性的getter应返回this的地址的邮政编码。

recipient属性的getter应返回this的地址的收件人。

region属性的getter应返回this的地址的地区。

sortingCode属性的getter应返回this的地址的分拣代码。

addressLine属性的getter应返回this的地址的地址行。

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. 使用 contacts source 的 supported properties 解析 promise。
  3. 返回 promise。

6.4.2. select()

select(properties, options) 方法执行以下步骤:
  1. 将 global 定义为 this 的 相关全局对象。
  2. 将 navigable 定义为 global 的 navigable。
  3. 如果 navigable 不是 顶层 traversable,则返回 一个被拒绝的 promise,并包含 InvalidStateError DOMException。
  4. 如果 global 没有 瞬时激活,则返回 一个被拒绝的 promise,并包含 SecurityError DOMException。
  5. 否则,消耗 global 的用户激活。
  6. 如果 navigable 的 contact picker 显示标志 被设置,则返回 一个被拒绝的 promise,并包含 InvalidStateError DOMException。
  7. 如果 properties 是 空的,则返回 一个被拒绝的 promise,并包含 TypeError。
  8. 遍历 properties 中的每个 property:
    1. 如果 contacts source 的 supported properties 不 包含 property,则返回 一个被拒绝的 promise,并包含 TypeError。
  9. 设置 navigable 的 contact picker 显示标志。
  10. 将 promise 定义为 新建 promise。
  11. 并行执行以下步骤:

    1. 将 selectedContacts 定义为 启动 contact picker,并使用 options 的 multiple 成员和 properties。如果失败,则执行以下操作:
      1. 排队一个 contact picker 任务以运行这些步骤:
        1. 拒绝 promise,并包含 InvalidStateError DOMException。
        2. 取消 navigable 的 contact picker 显示标志。
        3. 中止这些步骤。
    2. 取消 navigable 的 contact picker 显示标志。
    3. 排队一个 contact picker 任务以运行以下步骤:
      1. 将 contacts 定义为一个空的 列表。
      2. 遍历 selectedContacts 中的每个 selectedContact:
        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. 使用 contacts 解析 promise。
  12. 返回 promise。

7. 联系人选择器

要 启动 联系人选择器并使用 allowMultiple(boolean),以及 properties(列表,包含 DOMString),用户代理必须展示符合以下规则的用户界面:
  • 如果展示用户界面失败或访问 联系人来源的 可用联系人失败,则返回失败。

  • 用户界面必须突出显示 顶层 traversable 的 来源。

  • 用户界面必须明确显示哪些联系人 属性 被请求。

    注: 此信息源自 properties。

  • 用户界面应该提供用户拒绝共享某些联系人信息的方式。

    注: 如果用户选择退出,相关的 用户联系人字段应在返回所选联系人之前进行修改。应使返回的 用户联系人看起来无法区分是用户选择不共享某些信息,还是这些信息本身不存在。

  • 用户界面必须明确显示将要共享的信息。

  • 用户界面必须提供选择单个联系人的选项。如果 allowMultiple 为 false,则只能选择一个联系人。

  • 用户界面必须提供取消或返回而不共享任何联系人的选项,此时应移除用户界面并返回空的 列表。

  • 用户界面必须提供用户确认完成选择的方式,此时应移除用户界面并返回一个 列表,包含作为 用户联系人 的所选联系人。

8. 根据用户提供的输入创建ContactAddress

根据用户提供的输入创建ContactAddress的步骤如下。此算法可选择接收一个列表 redactList。如果redactList没有传入,则默认为空列表。

注意:redactList为用户代理提供了一个限制与请求应用共享的收件人个人信息数量的可能性。结果生成的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]二字母代码。

  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]的一部分)。因此,当用户代理知道用户正在为特定国家输入地址时,它可能不会提供供用户输入地区的字段。在这种情况下,用户代理将返回一个空字符串作为ContactAddress的region属性的值,但该地址仍可用于通信或服务交付等目的。

  11. 如果redactList不包含"sortingCode",将details["sortingCode"]设置为用户提供的排序代码。

  12. 返回一个新创建的ContactAddress,其属性值与details中的值相匹配。

9. 致谢

在此之前,已有多次尝试为网络标准化一个联系人 API,而本 API 力求从这些丰富的历史中汲取经验。早期的尝试包括 Mozilla 的 联系人 API、联系人 API W3C 会员提交的标准化努力,以及 W3C 工作组的标准化努力:联系人 API、选择联系人意图和联系人管理器 API。联系人选择器 API 在隐私方面采用了不同的方法,这是设计时的主要关注点。与之前的尝试不同,之前的 API 允许在授予权限后永久访问,或包含模糊的隐私模型,而此规范强制实施 UI 限制,用户对共享数据拥有完全控制权,并限制滥用。例如,强制执行选择器模型,用户始终作为共享联系人信息的中介,每次请求联系人时都拥有完全的控制权。有关更多历史背景,请参阅早期尝试的文件状态部分。

符合性

文档约定

符合性要求通过描述性断言和 RFC 2119 术语的组合来表达。 关键字“必须”、“不得”、“要求”、“应”、“不应”、“建议”、“可以”和“可选”在本文档的规范部分中,应按照 RFC 2119 的描述进行解释。 但是,为了可读性,这些词在本规范中不会全部以大写字母出现。

除明确标记为非规范性、示例和注释的部分外,本规范的所有内容均为规范性文本。 [RFC2119]

本规范中的示例以“例如”一词引入,或通过 class="example" 与规范文本区分开来,例如:

这是一个说明性示例。

说明性注释以“注”字开头,并通过 class="note" 与规范文本区分开来,例如:

注,这是一条说明性注释。

符合性算法

以命令式短语表达的算法部分要求(例如“去掉任何前导空格字符”或“返回 false 并中止这些步骤”)应按照引入算法时使用的关键字(例如“必须”、“应”、“可以”等)进行解释。

以算法或具体步骤形式表达的符合性要求可以通过任何方式实现,只要最终结果等效即可。 特别是,本规范中定义的算法旨在易于理解,并非为了性能优化。 鼓励实现者进行优化。

索引

本规范定义的术语

引用定义的术语

参考文献

规范性参考文献

[FileAPI]
Marijn Kruisselbrink. File API. 2024年5月24日. WD. URL: https://www.w3.org/TR/FileAPI/
[HTML]
Anne van Kesteren; et al. HTML 标准. 现行标准. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. 现行标准. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. 用于表示需求级别的关键词. 1997年3月. 最佳当前实践. 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月. 建议. 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 嗅探标准. 现行标准. 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 = {});
};