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)。强制执行选择器模型,以便用户代理可以提供清晰的用户体验,明确说明何时与网站共享哪些信息。
以下约束也被强制执行:
-
需要用户手势来启动API,以禁止程序化地请求用户的联系人信息。
-
需要短暂激活来启动API,以禁止程序化地请求用户的联系人信息。
3. 安全注意事项
4. 领域
5. 基础设施
联系人选择器任务源是一个任务源。
5.1. 实际地址
实际地址包括:
-
国家,一个
DOMString
,表示地址所在国家,使用[ISO3166-1]的alpha-2代码并存储为大写形式或为空字符串。例如,“JP”。 -
地址行,一个列表,由
DOMString
组成,包含地址的最具体部分。它可以包括,例如街道名称、门牌号、公寓号、乡村投递路线、描述性说明或邮政信箱号。 -
区域,一个
DOMString
,表示国家的顶级行政区。例如,这可以是州、省、区或县。 -
城市,一个
DOMString
,表示地址中的城市或镇。 -
附属地方,一个
DOMString
,表示城市内的附属地方或小区。例如,社区、区、区或英国的附属地方。 -
邮政编码,一个
DOMString
,表示邮政编码或ZIP码,在印度也称为PIN码。 -
分类代码,一个
DOMString
,表示分类代码系统,例如法国使用的CEDEX系统。 -
组织,一个
DOMString
,表示地址的组织、公司、企业或机构。 -
收件人,一个
DOMString
,表示地址的收件人或联系人姓名。
5.2. 用户联系人
用户联系人包括:
用户联系人包含与单个用户相关的数据。
注意:列表可以有不同的大小,且相同索引的条目不必对应彼此。
5.3. 联系人来源
联系人来源是为用户代理提供用户联系人信息的服务。
联系人来源包括:
-
支持的属性,一个列表,包含可用的
ContactProperty
值。
注意:由用户代理决定选择哪个联系人来源。
6. API描述
6.1. Navigator
的扩展
[Exposed =Window ]partial interface Navigator { [SecureContext ,SameObject ]readonly attribute ContactsManager contacts ; };
可导航对象具有一个联系人选择器显示标志,初始状态为未设置。
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 FrozenArray <DOMString >addressLine ; };
ContactAddress
接口表示一个物理地址。
ContactAddress
实例包括:
-
一个地址(一个物理地址)。
country
属性的getter应返回this
的地址的国家。
dependentLocality
属性的getter应返回this
的地址的附属地区。
organization
属性的getter应返回this
的地址的组织。
phone
属性的getter应返回this
的地址的电话号码。
postalCode
属性的getter应返回this
的地址的邮政编码。
recipient
属性的getter应返回this
的地址的收件人。
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。
- 并行执行以下步骤:
- 使用 contacts source 的 supported properties 解析 promise。
- 返回 promise。
6.4.2.
select()
select(properties, options)
方法执行以下步骤:
- 将 global 定义为 this 的 相关全局对象。
- 将 navigable 定义为 global 的 navigable。
- 如果 navigable 不是 顶层 traversable,则返回 一个被拒绝的 promise,并包含
InvalidStateError
DOMException
。 - 如果 global 没有 瞬时激活,则返回 一个被拒绝的 promise,并包含
SecurityError
DOMException
。 - 否则,消耗 global 的用户激活。
- 如果 navigable 的 contact picker 显示标志 被设置,则返回 一个被拒绝的 promise,并包含
InvalidStateError
DOMException
。 - 如果 properties 是 空的,则返回 一个被拒绝的 promise,并包含
TypeError
。 - 遍历 properties 中的每个 property:
- 如果 contacts source 的 supported properties 不 包含 property,则返回 一个被拒绝的 promise,并包含
TypeError
。
- 如果 contacts source 的 supported properties 不 包含 property,则返回 一个被拒绝的 promise,并包含
- 设置 navigable 的 contact picker 显示标志。
- 将 promise 定义为 新建 promise。
- 并行执行以下步骤:
- 将 selectedContacts 定义为 启动 contact picker,并使用
options 的
multiple
成员和 properties。如果失败,则执行以下操作:- 排队一个 contact picker 任务以运行这些步骤:
- 拒绝
promise,并包含
InvalidStateError
DOMException
。 - 取消 navigable 的 contact picker 显示标志。
- 中止这些步骤。
- 拒绝
promise,并包含
- 排队一个 contact picker 任务以运行这些步骤:
- 取消 navigable 的 contact picker 显示标志。
- 排队一个 contact picker 任务以运行以下步骤:
- 将 contacts 定义为一个空的 列表。
- 遍历 selectedContacts 中的每个 selectedContact:
- 使用 contacts 解析 promise。
- 将 selectedContacts 定义为 启动 contact picker,并使用
options 的
- 返回 promise。
7. 联系人选择器
DOMString
),用户代理必须展示符合以下规则的用户界面:
-
用户界面必须突出显示 顶层 traversable 的 来源。
-
用户界面必须明确显示哪些联系人
属性
被请求。注: 此信息源自 properties。
-
用户界面应该提供用户拒绝共享某些联系人信息的方式。
注: 如果用户选择退出,相关的 用户联系人字段应在返回所选联系人之前进行修改。应使返回的 用户联系人看起来无法区分是用户选择不共享某些信息,还是这些信息本身不存在。
-
用户界面必须明确显示将要共享的信息。
-
用户界面必须提供选择单个联系人的选项。如果 allowMultiple 为 false,则只能选择一个联系人。
-
用户界面必须提供取消或返回而不共享任何联系人的选项,此时应移除用户界面并返回空的 列表。
8. 根据用户提供的输入创建ContactAddress
根据用户提供的输入创建ContactAddress
的步骤如下。此算法可选择接收一个列表
redactList。如果redactList没有传入,则默认为空列表。
注意:redactList为用户代理提供了一个限制与请求应用共享的收件人个人信息数量的可能性。结果生成的ContactAddress
对象提供了足够的信息来执行必要的操作,如通信或服务交付,但在大多数情况下,不足以物理定位和唯一识别收件人。不幸的是,即使使用redactList,也不能保证收件人的匿名性。这是因为在某些国家,邮政编码非常细化,能够唯一识别收件人。
-
将details设置为映射« "addressLine" → 空的列表, "country" → "", "phone" → "", "city" → "", "dependentLocality" → "", "organization" → "", "postalCode" → "", "recipient" → "", "region" → "", "sortingCode" → "" »。
-
如果redactList不包含"addressLine",将details["addressLine"]设置为将用户提供的地址行分割成列表的结果。
注意:如何分割地址行依赖于区域设置,超出本规范的范围。
-
如果redactList不包含"country",将details["country"]设置为用户提供的国家,以大写形式的[ISO3166-1]二字母代码。
-
如果redactList不包含"phone",将details["phone"]设置为用户提供的电话号码。
注意:为了维护用户的隐私,开发者需要意识到联系人地址关联的电话号码可能与最终用户的相同或不同。因此,开发者需要谨慎,不要在未经最终用户同意的情况下提供用户的电话号码。
-
如果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"]设置为用户提供的排序代码。
-
返回一个新创建的
ContactAddress
,其属性值与details中的值相匹配。
9. 致谢
在此之前,已有多次尝试为网络标准化一个联系人 API,而本 API 力求从这些丰富的历史中汲取经验。早期的尝试包括 Mozilla 的 联系人 API、联系人 API W3C 会员提交的标准化努力,以及 W3C 工作组的标准化努力:联系人 API、选择联系人意图和联系人管理器 API。联系人选择器 API 在隐私方面采用了不同的方法,这是设计时的主要关注点。与之前的尝试不同,之前的 API 允许在授予权限后永久访问,或包含模糊的隐私模型,而此规范强制实施 UI 限制,用户对共享数据拥有完全控制权,并限制滥用。例如,强制执行选择器模型,用户始终作为共享联系人信息的中介,每次请求联系人时都拥有完全的控制权。有关更多历史背景,请参阅早期尝试的文件状态部分。