Contact Picker API позволяет предоставить сайту доступ к выбранным пользователем контактам из его записной книжки. В данной заметке мы разберём возможности, которые у нас появились, и некоторые, возможно, неочевидные моменты.


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


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


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


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


В Android 6+ и Chrome 80+, а также за флагом в iOS 14.5 beta 2. Вы можете попробовать API в демо от разработчиков.



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;
  • возможные ошибки, связанные с доступом к контактам или поддержкой со стороны ОС;
  • вариант, когда пользователь передумает и не выберет контакты;
  • вариант, когда желаемых данных у контакта не будет или наоборот, будет несколько (к примеру, два телефона или три почты).

Поддержка API в Chrome на других операционных системах


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


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


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


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


Ожидать какой-то реакции или, тем более, поддержки со стороны Safari не стоит, тем более что некая аналогичная базовая функциональность там уже есть, и реализована она просто как автозаполнение полей форм. Вот демо для вашего Safari. UPD: Внезапно, данное API без объявления войны было имплементировано за флагом в iOS 14.5 beta 2 и уже находится во вполне работоспособном состоянии. Когда фича будет доступна всем пользователям спрогнозировать сложно, остаётся только ждать. Работает API в самом Safari и в режиме рабочего стола, в альтернативных браузерах — не доступно. В десктопном Safari Technology Preview тоже имеется такой флаг, но попытка его использовать приводит к исключению.


Заключение


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


Ссылки