Все статьи
mobiledeep linksiOSAndroid

Тестирование мобильных диплинков через туннель localhost

Universal Links на iOS и App Links на Android оба требуют JSON-файл, отдаваемый по HTTPS из корня вашего домена. iOS хочет /.well-known/apple-app-site-association. Android хочет /.well-known/assetlinks.json. localhost не может отдать ни один по имени хоста, которое ОС свяжет с вашим приложением. Поэтому туннелируем.

Реальное требование на каждой платформе

iOS Universal Links

ОС загружает https://ваш-домен.com/.well-known/apple-app-site-association (или /apple-app-site-association) при установке приложения. Сверяет содержимое с entitlement associated-domains вашего приложения. Загрузка должна пройти по настоящему HTTPS. iOS не доверяет для этого самоподписанным сертификатам и не доверяет localhost.

Android App Links

Android верифицирует App Links, загружая https://ваш-домен.com/.well-known/assetlinks.json. Файл должен перечислять SHA-256-отпечатки вашего приложения. Верификация происходит при установке и при первом запуске. То же ограничение: настоящий HTTPS, настоящий домен.

Почему localhost не может это напрямую

Даже когда mkcert даёт доверенный HTTPS в браузере ноутбука, мобильная ОС не доверяет вашему локальному CA. И домен в apple-app-site-association должен совпадать с associated-domain в entitlement приложения — это настоящий, разрешаемый через DNS домен, а не localhost.

Некоторые команды обходят это выделенным staging-доменом. Это работает, но цикл итераций медленный. localhost-туннель даёт настоящий HTTPS-URL на настоящем поддомене, до которого мобильные устройства достучатся без претензий.

Как это подключить

  1. Отдавайте apple-app-site-association и assetlinks.json с локального dev-сервера по пути /.well-known/. Оба файла. Оба маршрута.
  2. Запустите npx portpreview 3000 (или тот порт, что использует ваш dev-сервер).
  3. Запишите имя хоста туннеля — что-то вроде abc123.portpreview.dev.
  4. В entitlement associated-domains вашего iOS-приложения добавьте applinks:abc123.portpreview.dev. В intent-filter Android — тот же хост.
  5. Соберите и установите приложение на реальное устройство (у симуляторов странное поведение Universal Link).
  6. Откройте URL из другого приложения — Заметки, Почта, QR-код — и наблюдайте, как он направляется в установленное приложение, а не в браузер.

Подвох туннелирования для диплинков: имя хоста туннеля меняется между сессиями, если нет зарезервированного поддомена. Каждая ротация означает пересборку приложения с новой записью associated-domain. Если вы часто итерируете поведение диплинков, заведите зарезервированный поддомен.

Content-type важен для apple-app-site-association

iOS ожидает файл без расширения и либо content-type application/json (новее iOS), либо application/pkcs7-mime (старый, подписанный формат). Почти все современные приложения используют простой JSON-вариант. Убедитесь, что dev-сервер возвращает правильный content-type, иначе iOS молча отклонит файл без полезной ошибки.

Сначала протестируйте из десктоп-браузера: откройте https://ваш-туннель.portpreview.dev/.well-known/apple-app-site-association и убедитесь, что JSON рендерится, а заголовок content-type в dev tools верный. Если не так, почините до охоты на баги Universal Link в симуляторе.

Другие ловушки отладки диплинков

Несовпадение entitlement приложения

Если ваш entitlement associated-domains говорит applinks:abc.portpreview.dev, а apple-app-site-association перечисляет def.portpreview.dev, iOS не загружает. Имя хоста должно быть согласовано.

Universal Link изнутри Safari

Тап по Universal Link внутри Safari (того же приложения, где открыта вкладка) иногда открывает в Safari, а не в приложении. Это сделано намеренно — iOS предотвращает трюк «открыть приложение всякий раз, когда пользователь нажимает ссылку». Тестируйте из Заметок или Почты.

Android App Links и digital asset links

Android также поддерживает кастомные URL-схемы (вашеприложение://путь), которым не нужен HTTPS или assetlinks.json. Их проще тестировать, но они менее безопасны — любое приложение может зарегистрировать ту же схему. Для диплинкинга продакшен-качества ответ — App Links.

Наш процесс мобильного тестирования

Для проекта, выпускающего и iOS, и Android с диплинками:

  1. Запустите бэкенд, отдающий файлы .well-known.
  2. Туннелируйте его через npx portpreview 3000.
  3. Как только URL туннеля стабилен для сессии (или используйте зарезервированный поддомен), обновите записи associated-domain приложения и соберите.
  4. QA на физических устройствах — и свежие установки, и обновления.
  5. Для комбо OAuth-и-диплинк (провайдеры входа, редиректящие обратно в приложение) сочетайте это с тестированием OAuth-колбэков.

Настройка раздражает в первый раз. После этого URL туннеля — просто ещё одна env-переменная в схеме Xcode и Gradle.

О более широких паттернах мобильного тестирования см. мобильное тестирование через localhost-туннель. Запишитесь в лист ожидания PortPreview.

Часто задаваемые вопросы

Можно ли тестировать iOS Universal Links на localhost?
Не напрямую. Universal Links требуют, чтобы iOS загружал apple-app-site-association по настоящему HTTPS с настоящего домена. localhost не подходит. Туннель даёт HTTPS-URL на поддомене туннеля, который iOS свяжет с вашим приложением, как только вы укажете его в entitlement associated-domains.
Какой content-type должен использовать apple-app-site-association?
Современный iOS ожидает application/json. Старые версии также принимали application/pkcs7-mime для подписанных файлов. Большинство приложений сегодня используют простой JSON-вариант. Если iOS не подхватывает файл, сначала проверьте заголовок content-type из десктоп-браузера.
Нужен ли Android App Links туннель для локального тестирования?
Да, если хотите полную верификацию App Link (не кастомные URL-схемы). Android загружает assetlinks.json по настоящему HTTPS с домена в вашем intent-filter. Туннель предоставляет настоящий HTTPS-URL на домене, который устройство может верифицировать. Кастомным схемам это не нужно, но они менее безопасны.