Contact Picker API, или как поделиться своими контактами с браузером

Сегодня нас ждёт релиз Chrome 80, а значит из Origin Trials во взрослую жизнь выходит Contact Picker API, позволяющее предоставить сайту доступ к выбранным пользователем контактам из его записной книжки. В данной заметке мы разберём возможности, которые у нас появились, и некоторые, возможно, неочевидные моменты.


Что это и зачем?


Представьте: вы хотите пригласить своего друга на ваш любимый сайт; оформляете интернет-заказ и хотите указать контактный номер жены как получателя; вызываете такси для коллеги и указываете его номер как номер пассажира (да, я понимаю, что самый популярный кейс тут первый, но с остальными я также столкнулся за последний месяц) или ещё как-то и где-то указываете номер телефона из своей записной книжки. Что вы делаете? Сворачиваете браузер или приложение, открываете приложение «Телефон», откуда переходите в «Контакты», ищете нужную запись, заходите в неё, копируете номер, снова открываете браузер или приложение, выбираете поле для ввода, вставляете скопированную запись. Квест выполнен!


Contact Picker API позволяет пользователю достичь той же цели значительно быстрее: нажать на кнопку, найти нужную запись, выбрать её, нажать «Готово». Круто? Да. Сложно для реализации? Нет.


Где и как это работает?


Пока только для Android 6+ и только для Chrome 80+. Если вы владеете данной комбинацией, вот демо от разработчиков.



API


API включает в себя класс ContactsManager и инстанс этого класса по ссылке window.navigator.contacts.


Имеются два асинхронных метода: getProperties, позволяющий узнать, какого рода информацию о контактах вообще может предоставить текущая связка браузера и операционной системы, и select, как раз отображающий пользователю запрос на выбор данных из записной книжки.


В текущий момент черновик спецификации говорит, что через API можно получить следующие типы полей: «address», «email», «icon», «name» и «tel». То есть прежде, чем просить пользователя поделиться информацией, скажем, о почте и изображении пользователя, хорошо было бы проверить, есть ли у пользователя такая возможность.


const supportedProps = await navigator.contacts.getProperties();
if (supportedProps.includes('address') && supportedProps.includes('icon')) {
    // всё хорошо, показываем волшебную кнопку для выбора контактов
}

После этого мы можем с чистой совестью попросить пользователя выбрать контакт или контакты.


Метод navigator.contacts.select первым аргументом принимает обязательный массив типов полей, которые нам нужны. Эта информация будет использована операционной системой для уведомления пользователя о том, чем именно он поделится. Те поля, которые вы не запросили явно, вы не получите. Вторым, опциональным аргументом можно передать объект дополнительных настроек. Сейчас это только multiple (по умолчанию false), т.е. один контакт можно будет выбрать пользователю, либо несколько.


В результате вызова вы получите массив (всегда, вне зависимости от значения multiple) объектов-описаний контактов, где ключами будут запрошенные типы полей, а значениями — массивы их значений (всегда — массивы). Пример:


[{
    "email": [],
    "name": ["Queen O’Hearts"],
    "tel": ["+1-206-555-1000", "+1-206-555-1111"]
}]

Поля «email», «name» и «tel» являются массивами строк. Для почты и телефона это логично. А вот если заполнить все поля, относящиеся к имени, которые предоставляет Android, то в качестве значения имени всё равно будет получен массив из лишь одной строки формата <обращение> <имя> <отчество> <фамилия>, <звание/титул>. Поле «address» приходит как массив объектов, соответствующих интерфейсу ContactAddress, расширяющему (но ничего не добавляющему) интерфейс PaymentAddress. Поле «icon» — как массив Blob-ов.


Теперь мы можем использовать эту информацию по своему усмотрению.


Что может пойти не так


Как можно было заметить выше, нам всегда приходят массивы значений. Это потому, что у контакта может быть записано как несколько номеров телефонов, так и ни одного. Это всегда нужно будет проверять и предусматривать в дизайне перед дальнейшим использованием.


Что ещё может пойти не так? Пользователь банально может нажать кнопку «Назад», не выбрав контакты. При этом результат вызова всё равно будет успешным и вы получите пустой массив.


В случае, если у приложения нет доступа к списку контактов (запрет на уровне системы), либо операционная система не поддерживает данную возможность, вы получите TypeError: Unable to open a contact selector.


Определение поддержки


Авторы рекомендуют проверять наличие поддержки таким образом:


const supported = ('contacts' in navigator && 'ContactsManager' in window);

Но дополнительно, возможно, стоит проверить, а не Android 5- ли сейчас перед вами. Сделать это можно по юзерагенту. Если процент пользователей этих версий мал, можно ограничиться общим отловом ошибок вида Unable to open a contact selector.


Безопасность и предоставление доступа


При первом обращении к этому API, если браузеру не был предоставлен доступ к контактам ранее, система запросит доступ для приложения браузера. В случае разрешения в последующие разы никаких дополнительных запросов не будет. Обратите внимание, что это совсем не похоже на привычную схему получения доступа сайтов к специфичным API. Если обычно мы делаем запрос для текущего ориджина, то есть можем запретить и разрешить доступ, например, к камере, индивидуально для каждого сайта, то тут вы автоматически делаете запрос для всего приложения, никаких дополнительных индивидуальных запросов на каждый ориджин вы делать не будете. А соответственно, вы не можете узнать статус разрешения данного API через Permission API, в любом случае придётся пробовать и обрабатывать исключения.


Почему это хорошо и правильно в данном случае? Несколько причин. Первая: для вызова метода navigator.contacts.select необходимо явное действие пользователя, вызвать программно, например, сразу после загрузки страницы не получится. Вторая: интерфейс выбора пользователя, судя по всему, относится именно к самой ОС, и у скриптов сайтов нет никакой возможности вмешаться в выбор пользователя. А значит, нет никакого шанса получить список контактов пользователя без его спроса. Каждый раз, когда пользователь наткнётся на интерфейс выбора контактов, это будет следствием его явного действия, и ему первым делом будет выдано пояснение, какой именно сайт и какую именно информацию получит, если пользователь решит продолжить.


Особенности дизайна


Что нужно предусмотреть:



Другие операционные системы


В настоящий момент данное API доступно только на Android. Потенциально в будущем оно может быть доступно и на других ОС, например, в Windows 10 для реализации Contact Picker API есть подходящее API Windows.ApplicationModel.Contacts, а в macOS — API Contacts. Но на данный момент никаких заявлений от разработчиков Chrome по поводу поддержки других ОС я не встречал.


Другие браузеры


Скорее всего все Chroimum-based-браузеры на Android автоматически получат это API после обновления движка до определённой версии, хоть это и не точно. Лично у меня пока есть небольшие сомнения насчёт браузеров Samsung Internet и Miui, они иногда имеют некоторые особенности.


Firefox пока не определился по поводу своей официальной позиции относительно этого API, но на настоящий момент отношение положительное.


Ожидать какой-то реакции или, тем более, поддержки со стороны Safari не стоит, тем более что некая аналогичная базовая функциональность там уже есть, и реализована она просто как автозаполнение полей форм. Вот демо для вашего Safari.


Заключение


Данное API — ещё один шаг Проекта Фугу по направлению к сокращению разрыва между вебом и нативными приложениями. Разумеется, не получится написать альтернативное приложение для управления контактами, но большинство сценариев использования, как мне кажется, оно покрывает и делает вашего пользователя немного счастливее. Напишите свои мысли по этому поводу или поделитесь возможными сценариями использования!


Ссылки